$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;
}
}
この記事が気に入ったらサポートをしてみませんか?