見出し画像

$25 Hard/Soft SYNC VCO - DIY Eurorack Modular Synthesizer

背景

自作モジュラーシンセの76作品目。
2023年に発売されたArduino UNO R4はDACを搭載していて、5Vの入出力回路をもっているため、モジュラーシンセに使いやすい。
今回、Arduino UNO R4のモジュラーシンセへの有用性を検証するにあたり、VCOモジュールを作成した。

オシレータシンク

アナログVCOにはオシレータシンクという機能がついている。外部からのSYNC入力に合わせて波形をリセットして、多くの倍音を含む波形を作成する手段のひとつだ。
過去に私も多くのVCOモジュールを作成してきたが、オシレータシンク機能を備えるVCOを作成したことが無かったので、今回作成の企画を立てた。

制作物のスペック

ユーロラック規格 3U 6HPサイズ
電源:10mA( +12V ),10mA( -12V ),50mA(5V)

2種類のSYNC機能を備えるデジタルVCO。
SYNCレートはCVコントロールができるため、アナログVCOと比較して少ないモジュール数でSYNC機能を得られる。

FREQ POT:オシレータ周波数の微調整
SYNC POT:SYNCの割合制御
WAVE:4種類から波形選択(saw,squ,tri,sin)
OCT SW:オクターブ選択、3オクターブ
SYNC SW:SYNC mode選択(HARD SYNC , SOFT SYNC)
SYNC IN:SYNC割合のCV制御(0-5V)
V/OCT:オシレータ周波数の制御(0-5V)
OUT:音声出力(10Vp-p)

製作費

総額約$25
---------------------------------
FLINT ProMicro R4(Arduino uno R4互換ボード) $20
フロントパネル $1
TL072 $0.3
可変抵抗 $0.3*3pcs
他(汎用部品は下記リンク先参照)

ハードウェア

基本的な回路は、実証試作で作成した前回のUNO R4 VCOをベースとしている。

SW回路は内部プルアップ機能を使うことで、部品点数を削減している。

CV入力はTNとS端子をショートさせているので、パッチケーブル未接続時はADコンバータはGND電位となり、ノイズを影響を受けなくなる。(TNとS端子がオープンの場合、パッチケーブル未接続時のADコンバータ値は不定値となり音声出力が不安定になる。)

ソフトウェア

基本的なソフト構成はUNO R4 VCOと変わらないが、新たにSYNC機能を追加している。

HARD SYNC

2種類の異なるカウンターを準備している。片方のカウンターがMAXに達すると、もう片方のカウンターのカウントをリセットすることで、HARD SYNCを実装している。

    if (table_progress >= tableSize) {
      table_progress2 = 0;
      table_progress = table_progress - tableSize;
    }
    if (table_progress2 >= tableSize) {
      table_progress2 = table_progress2 - tableSize;
    }
    *DAC12_DADR0 = WaveTable[(int)table_progress2];
    table_progress = table_progress + pitch;     //pitch
    table_progress2 = table_progress2 + pitch2;  //sync rate

SOFT SYNC

位相が180度ずれたwavetableを準備する。
2種類の異なるカウンターのうち、片方のカウンターがMAXに達すると位相の異なるwavetableに切り替えることで、SOFT SYNCを実装している。

    if (table_progress >= tableSize) {
      table_progress = table_progress - tableSize;
      softsync_rev = 1 - softsync_rev;
      table_progress2 = tableSize - table_progress2;
    }
    if (table_progress2 >= tableSize) {
      table_progress2 = table_progress2 - tableSize;
    }
    if (softsync_rev == 0) {
      *DAC12_DADR0 = WaveTable[(int)table_progress2];
    }
    if (softsync_rev == 1) {
      *DAC12_DADR0 = RevWaveTable[(int)table_progress2];
    }
    table_progress = table_progress + pitch;     //pitch
    table_progress2 = table_progress2 + pitch2;  //sync rate
  }

宣伝:オープンソースプロジェクトの支援をお願いします

DIYモジュラーシンセのオープンソースプロジェクトを継続するために、patreonというサービスでパトロンを募集しています。
コーヒー一杯の支援をいただけると嬉しいです。
また、パトロン限定のコンテンツも配信しています。

ソースコード

粗末だが公開する。悪い点があれば指摘を貰えると嬉しい。
UNO R4 VCOと同じく、ソースコードにはWASHIYAMA GIKENさんのタイマー割り込みライブラリと、Grumpy-Mike さんのDAC出力コードを使わせていただいた。感謝申し上げる。

float table_progress = 0;
float pitch = 0;
float pitch_calb = 0.965;

float table_progress2 = 0;
float pitch2 = 0;

int selectwave, old_selectwave;  //select waveform by ADC
int octave_select;
int mode_select;  //soft or hard
long timer = 0;

int sync_pot, osc_pot, sync_cv, osc_cv, oct_swA, oct_swB;
int softsync_rev;  //for soft sync mode

//wavetable
float voct_table[2048];
const int tableSize = 2048;  // table size
uint16_t WaveTable[tableSize];
uint16_t RevWaveTable[tableSize];  //use only soft sync mode

// 12-Bit D/A Converter.The reference source for the DAC settings is below.
//https://github.com/Grumpy-Mike/Game_of_Life_with_sound
#define DACBASE 0x40050000                                                      // DAC Base - DAC output on A0 (P014 AN09 DAC)
#define DAC12_DADR0 ((volatile unsigned short *)(DACBASE + 0xE000))             // D/A Data Register 0
#define DAC12_DACR ((volatile unsigned char *)(DACBASE + 0xE004))               // D/A Control Register
#define DAC12_DADPR ((volatile unsigned char *)(DACBASE + 0xE005))              // DADR0 Format Select Register
#define DAC12_DAADSCR ((volatile unsigned char *)(DACBASE + 0xE006))            // D/A A/D Synchronous Start Control Register
#define DAC12_DAVREFCR ((volatile unsigned char *)(DACBASE + 0xE007))           // D/A VREF Control Register
#define MSTP_MSTPCRD ((volatile unsigned int *)(MSTP + 0x7008))                 // Module Stop Control Register D
#define MSTPD20 20                                                              // DAC12  - 12-Bit D/A Converter Module
#define MSTP 0x40040000                                                         // Module Registers
#define MSTP_MSTPCRB ((volatile unsigned int *)(MSTP + 0x7000))                 // Module Stop Control Register B
#define PFS_P014PFS ((volatile unsigned int *)(PORTBASE + P000PFS + (14 * 4)))  // A0 / DAC12
#define PORTBASE 0x40040000                                                     /* Port Base */
#define P000PFS 0x0800                                                          // Port 0 Pin Function Select Register

//timer setting
//https://github.com/washiyamagiken/AGTimer_R4_Library
#include "AGTimerR4.h"
#define FREQ_SAMPLING 80000.0f
volatile bool samplingStat = false;

void setup() {
  //pinMode setting
  pinMode(D10, INPUT_PULLUP);  //mode sw
  pinMode(D11, INPUT_PULLUP);  //octave sw
  pinMode(D12, INPUT_PULLUP);  //octave sw

  //AGTimerR4.h setting
  AGTimer.init(FREQ_SAMPLING, timerCallback);
  AGTimer.start();

  // make V/oct table
  for (int i = 0; i < 2048; ++i) {
    voct_table[i] = 1 * pow(2, i / 204.8);
  }

  make_table();

  timer = micros();

  //DAC setting
  *MSTP_MSTPCRD &= ~(0x01 << MSTPD20);  // Enable DAC12 module
  *DAC12_DADPR = 0x00;                  // DADR0 Format Select Register - Set right-justified format
                                        //  *DAC12_DAADSCR  = 0x80;             // D/A A/D Synchronous Start Control Register - Enable
  *DAC12_DAADSCR = 0x00;                // D/A A/D Synchronous Start Control Register - Default
                                        // 36.3.2 Notes on Using the Internal Reference Voltage as the Reference Voltage
  *DAC12_DAVREFCR = 0x00;               // D/A VREF Control Register - Write 0x00 first - see 36.2.5
  *DAC12_DADR0 = 0x0000;                // D/A Data Register 0
  delayMicroseconds(10);                // Needed delay - see data sheet
  *DAC12_DAVREFCR = 0x01;               // D/A VREF Control Register - Select AVCC0/AVSS0 for Vref
  *DAC12_DACR = 0x5F;                   // D/A Control Register -
  delayMicroseconds(5);                 // Needed delay - see data sheet
  *DAC12_DADR0 = 2048;                  // D/A Data Register 0 - value of mid range bias
  *PFS_P014PFS = 0x00000000;            // Port Mode Control - Make sure all bits cleared
  *PFS_P014PFS |= (0x1 << 15);          // ... use as an analog pin
}

//DAC output
void timerCallback() {

  if (mode_select == 0) {  //HARD SYNC
    if (table_progress >= tableSize) {
      table_progress2 = 0;
      table_progress = table_progress - tableSize;
    }
    if (table_progress2 >= tableSize) {
      table_progress2 = table_progress2 - tableSize;
    }
    *DAC12_DADR0 = WaveTable[(int)table_progress2];
    table_progress = table_progress + pitch;     //pitch
    table_progress2 = table_progress2 + pitch2;  //sync rate
  }

  if (mode_select == 1) {  //SOFT SYNC
    if (table_progress >= tableSize) {
      table_progress = table_progress - tableSize;
      softsync_rev = 1 - softsync_rev;
      table_progress2 = tableSize - table_progress2;
    }
    if (table_progress2 >= tableSize) {
      table_progress2 = table_progress2 - tableSize;
    }
    if (softsync_rev == 0) {
      *DAC12_DADR0 = WaveTable[(int)table_progress2];
    }
    if (softsync_rev == 1) {
      *DAC12_DADR0 = RevWaveTable[(int)table_progress2];
    }
    table_progress = table_progress + pitch;     //pitch
    table_progress2 = table_progress2 + pitch2;  //sync rate
  }
}

void loop() {
  if (timer + 10 <= micros()) {

    mode_select = digitalRead(D10);  //HIGH=durl osc mode , LOW = sync mode
    sync_pot = analogRead(A3);
    osc_pot = analogRead(A2);
    sync_cv = analogRead(A5);
    osc_cv = analogRead(A4);
    oct_swA = digitalRead(D12);
    oct_swB = digitalRead(D11);

    if (mode_select == 0) {                                                                               //HARD SYNC
      pitch = voct_table[(int)(sync_cv * pitch_calb) + sync_pot / 4] * octave_select * 0.5;               // pitch set
      pitch2 = voct_table[(int)(sync_cv * pitch_calb) + osc_pot / 2 + osc_cv / 3] * octave_select * 0.5;  // sync rate set
    }

    if (mode_select == 1) {                                                                               //SOFT SYNC
      pitch = voct_table[(int)(sync_cv * pitch_calb) + sync_pot / 4] * octave_select * 0.5;               // pitch set
      pitch2 = voct_table[(int)(sync_cv * pitch_calb) + osc_pot / 2 + osc_cv / 3] * octave_select * 0.5;  // sync rate set
    }

    if (oct_swA == 0) {
      octave_select = 1;
    };
    if (oct_swB == 0) {
      octave_select = 4;
    };
    if (oct_swA != 0 && oct_swB != 0) {
      octave_select = 2;
    };

    selectwave = analogRead(A1) / 256;  //wave select
    if (selectwave != old_selectwave) {
      make_table();
      old_selectwave = selectwave;
    }
    timer = micros();
  }
}

void make_table() {  // make wavetable

  // make wavetable
  switch (selectwave) {
    case 0:  // saw
      for (int i = 0; i < tableSize; i++) {
        WaveTable[i] = map(i, 0, tableSize - 1, 0, 4095);
        RevWaveTable[tableSize - 1 - i] = WaveTable[i];
      }
      break;

    case 1:  // square
      for (int i = 0; i < tableSize; i++) {
        if (i < tableSize / 2) {
          WaveTable[i] = 0;
          RevWaveTable[tableSize - 1 - i] = WaveTable[i];
        } else {
          WaveTable[i] = 4095;
          RevWaveTable[tableSize - 1 - i] = WaveTable[i];
        }
      }
      break;

    case 2:
      // triangle
      for (int i = 0; i < tableSize; i++) {
        if (i < tableSize / 2) {
          WaveTable[i] = i * 4;
          RevWaveTable[tableSize - 1 - i] = WaveTable[i];
        }
        if (i >= tableSize / 2) {
          WaveTable[i] = 4095 - i * 4;
          RevWaveTable[tableSize - 1 - i] = WaveTable[i];
        }
      }
      break;

    case 3:
      // sine
      for (int i = 0; i < tableSize; i++) {
        WaveTable[i] = 2047 + 2047 * sin(2 * PI * i / tableSize);
        RevWaveTable[tableSize - 1 - i] = WaveTable[i];
      }
      break;
  }
}

この記事が気に入ったらサポートをしてみませんか?