見出し画像

$14 CV Sample Drum - DIY Eurorack Modular Synthesizer

背景

自作モジュラーシンセの70作品目。

今年度の目標に、面実装基板(SMT)の部品実装アウトソーシングがある。
私はまだ面実装基板のモジュールを作成したことがないので、アウトソーシングをする前にSMTにチャレンジしたいと思った。

今回はSMTが新しいチャレンジとなるため、ハードウェアとソフトウェアは過去に制作実績のあるモジュールをベースにすべく、過去に作成したSeeed ESP32C3を使ったSample Drumモジュールのリメイクの企画に至った。

制作物のスペック

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

wavデータを再生可能な1 shot drumモジュール。
保存可能sample数:48sample
再生ビットレート:10bit
サンプリングレート:48kHz
最大sample再生時間:0.6sec

sampleはCVで選択可能。
選択可能なsample数は、1bankあたり6種類。
bank数は全8種類。

BANK POT: BANKの選択、全8種類。
SELECT POT: sampleの選択、全6種類。
PITCH POT: サンプルの再生スピードの調整。
SELECT CV: sampleの選択、全6種類。CV range 0-5V。
TRIG IN: トリガー入力
OUT: 音声出力 Vp-p=10V

音声出力はPWM。高調波ノイズはローパスフィルタで削っている。
Kick等の低音sampleは高調波ノイズが聞こえることがあるので、対策として追加ローパスフィルタの自動選択機能を備えている。
低音sampleは追加ローパスフィルタを有効にすることで、高調波ノイズを低減する。

製作費

総額約$14
---------------------------------
Front panel $2
Control PCB $0.5
Main PCB $0.5
Seeed Xiao ESP32C3 $5
可変抵抗 $0.6*3pcs
TL072 $0.4

部品の多くはDigikeyから購入した。
可変抵抗とピンヘッダーはTayda electronicsから購入。

PCB

Main PCB , Control PCBの2種類をJLCPCBに発注した。
JLCPCBはPCBWayと比較すると、製造オプションが少ないが、コストと輸送費が安い。

各20pcs、合計40pcsを発注して、送料込み$20。
基板1枚当たり$0.5と非常に安い。

なお、Front panelはNoiseモジュールの際に作成したものを流用している。
Control PCBのガーバーデータも、Noiseモジュールの流用だ。

PCBのガーバーデータ(注意:フロントパネルのシルク印字無し)、部品の型番と調達先を記載したBOMはPatreonで公開している。
今後もプロジェクトを継続するために、支援してくれると嬉しい。

ハードウェア

回路の多くは過去に作成したSample Drumをベースとしている。
将来、複数のプロジェクトで基板を流用できるように、いくつかの抵抗とコンデンサを追加できる構成にしている。回路図で「×」がついている部品は、将来の拡張用なので実装しなくてよい。

追加ローパスフィルタ

過去に作成したSample Drumは、トグルスイッチを切り替えることで、ローパスフィルタを追加してPWMの高調波ノイズを削っていた。

今回はトグルスイッチではなく、MCUの制御で追加ローパスフィルタを自動で有効にしている。回路図中の「AG」はESP32C3のD9pinに接続されている。
追加ローパスフィルタを無効にするときは、D9pinをINPUT(=high impedance)に設定する。追加ローパスフィルタを有効にするときは、D9pinをOUTPUT LOWに設定することでGNDに接続している。

面実装部品(SMT parts)の選定

初めての面実装のため、電子部品の多くを新規に調達する必要があった。
過去に採用したことのない部品もいくつかあるので、その説明。

ショットキーバリアダイオード:BAT54S
1部品に2個のSBDが内蔵されている。SBDに流れる電流は12Vの時に、約0.5mA(=12V/22kohm)。
0.5mAにおける順方向電圧Vf=0.2Vである。
ESP32C3の定格電圧VDD+0.3V及びVSS-0.3Vを下回るため、ESP32C3の過電圧保護、負電圧保護として機能する。

電源平滑用コンデンサ
電解コンデンサではなく、セラミックコンデンサを使用している。実装面積を小さくするためだ。
市販のユーロラックモジュールでも、大型のセラミックコンデンサを電源平滑用として使用しているものもある。(ex:FX AID)
電源平滑用のコンデンサの容量は、一般的に10uF~47uF程度だ。

大容量のセラミックコンデンサを使用するうえで注意したいのが「DCバイアス特性」だ。
高い電圧では、コンデンサの容量が低下する。今回使用した22uFのセラミックコンデンサは12Vでは容量が60%も減少して、実質的な静電容量は10uFとなる。

セラミックコンデンサの種類によっては、このDCバイアス特性が悪いものもある。12Vにおける静電容量の低下率が90%のセラミックコンデンサもある。コンデンサを選定するにあたっては、データシートを確認する必要がある。

ソフトウェア

過去に作成したSample Drumの大部分を流用してるので、技術的な記事はそちらを参照。
sample selectの方法が、ADCなのか、ロータリーエンコーダなのかの違いだ。

ドラム音声サンプルはバイナリデータに変換する必要がある。非常に手間のかかる作業だが、wgd modular "chia"のファームウェアを使用すれば、wavファイルからarduino用のソースコードを生成してくれる。
wgd modular , Thank you!

Trigger入力からSampleの再生には10msec~20msecの遅延がある。
メインのロジックの周期が10msec。トリガー検出から、CV電圧の安定待ち時間が10msecのためだ。
市販のCV sampleモジュールを調査したが、$300以上するモジュールもトリガー入力からサンプル再生まで10msec以上の遅延はあるので、許容範囲だろう。

ソースコード

ソースコードは2種類ある。
メインの音声出力の"ino file"と、ドラム音声バイナリデータの"sample.h"。

ino file

#include "sample.h"//sample file

float i; //sample play progress
float freq = 1;//sample frequency
bool trig1, old_trig1, done_trig1;
bool adc_stabi = 0;
int bank_AD = 0;//sample bank knob AD value
int bank = 0;//sanple bank number 0-7
int select_AD = 0;//selet sample knob AD value
int select_no = 0; //select sample number 0-5
int sound_out;//sound out PWM rate
int sample_no = 0;//select sample number

long timer = 0;//timer count for CV stabilized

const static bool ActiveGND[48] = {// 1 is active GND available , additional LPF work.
  1, 0, 0, 0, 0, 0,//bank0
  1, 0, 0, 0, 0, 0,//bank1
  1, 0, 0, 0, 0, 0,//bank2
  1, 0, 0, 0, 0, 0,//bank3
  1, 0, 0, 0, 0, 0,//bank4
  1, 0, 0, 0, 0, 0,//bank5
  1, 0, 0, 0, 0, 0,//bank6
  1, 0, 0, 0, 0, 0,//bank7
};

//-------------------------timer interrupt for sound----------------------------------
hw_timer_t *timer0 = NULL;
portMUX_TYPE timerMux0 = portMUX_INITIALIZER_UNLOCKED;
volatile uint8_t ledstat = 0;

void IRAM_ATTR onTimer() {
  portENTER_CRITICAL_ISR(&timerMux0) ; // enter critical range
  if (done_trig1 == 1) {//when trigger in
    i = i + freq;
    if (i >= 28800) {//when sample playd all ,28800 = 48KHz sampling * 0.6sec
      i = 0;
      done_trig1 = 0;
    }
  }
  sound_out = (((pgm_read_byte(&(smpl[sample_no][(int)i * 2]))) | (pgm_read_byte(&(smpl[sample_no][(int)i * 2 + 1]))) << 8) >> 6) ;//16bit to 10bit
  ledcWrite(1, sound_out + 511); //PWM output
  portEXIT_CRITICAL_ISR(&timerMux0) ; // exit critical range
}

void setup() {

  pinMode(D7, INPUT); //trigger in
  pinMode(D5, OUTPUT);//sound_out PWM
  timer = millis();//for ADC stability
  analogReadResolution(10);

  ledcSetup(1, 39000, 10);//PWM frequency and resolution
  ledcAttachPin(D5, 1);//(LED_PIN, LEDC_CHANNEL_0);//timer ch1 , apply D5 output

  timer0 = timerBegin(0, 1666, true);  // timer0, 12.5ns*1666 = 20.83usec(48kHz), count-up
  timerAttachInterrupt(timer0, &onTimer, true); // edge-triggered
  timerAlarmWrite(timer0, 1, true); // 1*20.83usec = 20.83usec, auto-reload
  timerAlarmEnable(timer0); // enable timer0
}

void loop() {
  //-------------------------trigger detect----------------------------------
  old_trig1 = trig1;
  trig1 = digitalRead(D7);
  if (trig1  == 1 && old_trig1 == 0 ) { //detect trigger signal low to high , before sample play was done
    adc_stabi = 1;
    timer = millis();
  }

  //--------------wait ADC stabilize and play sample-----------------------
  if (timer + 10 <= millis() &&  adc_stabi == 1) { //wait 10msec for ADC voltage to stabilize
    bank_AD = analogRead(A1);//bank select
    if (bank_AD < 64) {
      bank = 0;
    }
    else if (64 <= bank_AD && bank_AD < 231) {
      bank = 1;
    }
    else if (231 <= bank_AD && bank_AD < 398) {
      bank = 2;
    }
    else if (398 <= bank_AD && bank_AD < 565) {
      bank = 3;
    }
    else if (565 <= bank_AD && bank_AD < 732) {
      bank = 4;
    }
    else if (732 <= bank_AD && bank_AD < 899) {
      bank = 5;
    }
    else if (899 <= bank_AD && bank_AD < 1020) {
      bank = 6;
    }
    else if (1010 <= bank_AD) {
      bank = 7;
    }

    select_AD = analogRead(A2);//sample select
    if (select_AD < 16) {
      select_no = 0;
    }
    else if (16 <= select_AD && select_AD < 250) {
      select_no = 1;
    }
    else if (250 <= select_AD && select_AD < 500) {
      select_no = 2;
    }
    else if (500 <= select_AD && select_AD < 750) {
      select_no = 3;
    }
    else if (750 <= select_AD && select_AD < 1010) {
      select_no = 4;
    }
    else if (1010 <= select_AD) {
      select_no = 5;
    }

    sample_no = bank * 6 + min(select_no + analogRead(A0) / 171, 5); //sample number select
    if (ActiveGND[sample_no] == 1) {//additional LPF working
      pinMode(D9, OUTPUT);
      digitalWrite(D10, HIGH);

    }
    else if (ActiveGND[sample_no] == 0) {//additional LPF not working
      pinMode(D9, INPUT);
    }
    freq = 0.5 + analogRead(A3) * 0.0015;//pitch
    done_trig1 = 1;
    i = 0;
    adc_stabi = 0;
  }
}

sample.h
合計で3Mbyteのバイナリデータの羅列なので、ソースコードの全掲載は省略する。オリジナルのソースコードファイルはpatreonにアップロードする、今後の活動のために支援いただけると嬉しい。

const static byte smpl[48][58560] PROGMEM = {//58560=48kHz*0.61sec*2byte

//sample01
{
 0x02,0x00,0x0d,0x00,0x37,0x00,0xd5,0x00,0x83,0x02,0x25,0x06,0xad,0x0c,0x63,0x17,
 //omitting
 },
 {
 //omitting
   0x73,0x39,0x53,0x3a,0xb2,0x3a,0x83,0x3a,0xac,0x39,0x59,0x38,0x9d,0x36
  }
};


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