見出し画像

dfplayer mini で作る800円サンプラーモジュラーシンセ

Arduinoプログラミングに挑戦しつつ、dfplayer miniでモジュラーシンセサイザーのサンプラーモジュールを自作したので、その備忘録。


背景
コードの書けないシステムエンジニア脱却のために始めたプログラミングの4作品目。新しい部品、新しい関数を使えるネタを探していた。

先日robaux LL8という8chのゲートシーケンサーを入手したのだが、「8個も音源リソースねえよ」という事で、安価なドラム音源を欲していた。

そんな思惑が交錯して、安価なサンプラーを作るに至った。

制作物のスペック
ユーロラック規格 3U 6HPサイズ
電源:73mA ( at 5V ) / 65mA ( at12V )
チャンネル:2ch (同時出力不可)
レイテンシ制御:0ms~900ms
サンプル数:計50sample ( 10sanple * 5bank )
ストレージ:micro SD
音源:mp3ファイル(44.1kHz 24bit)
総額:800円

EQは5種類から選択可能。LOOP再生機能あり。

画像3

DFPlayer miniについて
秋月で1000円で購入可能。
aliexpressならば130円で購入可能。模造品の可能性はあるが。

SDカードに保存したmp3ファイルを再生できる。
トリガーから再生までのレイテンシを実測したところ、80msくらいあった。

80msはドラム音源としては辛いが、対策としてモジュール側にレイテンシコントロールを実装。レイテンシを意図的に遅くして、実質0msにする魂胆。

画像1

プログラム
自作モジュラーシンセはプログラミングの勉強が目的なので、毎回新しい要素を織り込もうと意識している。

ロータリーエンコーダの制御は、Encoder.hのライブラリを使用した。
自作のプログラムではチャタリングノイズを取り切れず、ライブラリに頼ってしまった。
Encoder.hはソフトフィルタの効きも抜群で、ノイズ対策は0.01uFのセラコン1個で済んだ。

モジュールの起動時に、前回の設定値を読み込めるよう、EEPROMへのデータ保存も実装した。EEPROM.hのライブラリを使用。
書き込み寿命対策として、EEPROM.updateの関数を使用。保存した値が更新されていれば書き込むというもの。writeよりも使いやすい。

INPUT_PULLUPを使用することで、digitalRead端子のプルダウン抵抗と制限抵抗の削減をした。もっと早く存在知りたかった。

DFplayerのライブラリはリンク先の物を使用した。


ハードウェア
回路はいたってシンプル。INPUT_PULLUPを使用することでスイッチ周りの回路が簡略化してある。
dfplayer miniの信号線は3.3Vということで、分圧のケアをしてやる必要がある。

画像2

画像4

当初はarduino pro miniを使用する予定だったが、aliexpressで購入したチャイナ互換品が粗末な出来であった。クロック数が公表値の1/4しかなく通信ができない。また、PWM出力もできていないようだった。

海外フォーラムに同様の指摘をする指摘は複数あり。
「数十円のコストダウンのために、何時間も消耗する必要はない」という誰かの書き込みを見て、チャイナ互換品はゴミ箱行きとなった。


ソースコード
粗末ではあるが公開する。
ロータリーエンコーダは部品によって分解能が異なるので、使う部品に合わせてケアする必要あり。

#include <SoftwareSerial.h>
#include <DFPlayer_Mini_Mp3.h>
#include <Encoder.h>
#include <EEPROM.h>

SoftwareSerial mySerial(6, 5); // RX, TX

byte select_sw1 = 1;//モードセレクトスイッチ。D7pin
byte select_sw2 = 0;//モードセレクトスイッチ。D8pin
byte select_mode = 1;//セレクトスイッチの結果。
byte old_select_mode = 0;//セレクトスイッチの切り替え判定用
byte trigger1 = 0;//ch1のトリガー
byte old_trigger1 = 0;//
byte trigger2 = 0;//ch2のトリガー
byte old_trigger2 = 0;//
int latency = 0;//トリガーのレイテンシー
int old_latency;//一時停止判定用

Encoder myEnc(2, 3);//ロータリーエンコーダライブラリ用
float oldPosition  = -999;//ロータリーエンコーダライブラリ用
int bank_1 = 0;//ch1
int bank_2 = 1;//ch2
int bank_3 = 1;//option
int number_1 = 1;//ch1
int number_2 = 1;//ch2
int number_3 = 0;//option,0=NO_LOOP,1=LOOP
boolean loop_state = true;//option,0=NO_LOOP,1=LOOP
int mp3_play_number = 1;


void setup() {
 pinMode(7, INPUT_PULLUP);//select_sw1
 pinMode(8, INPUT_PULLUP);//select_sw2
 pinMode(12, INPUT);//trigger1
 pinMode(13, INPUT);//trigger2
 Serial.begin(9600);

 delay(2000); //← MP3モジュールの起動に時間がかかるようなのでちょっと待つ処理
 mySerial.begin (9600);//MP3モジュール操作用ソフトシリアル
 mp3_set_serial (mySerial); //set softwareSerial for DFPlayer-mini mp3 module
 delay(10); //wait 1ms for mp3 module to set volume
 mp3_set_volume (20);

 bank_1 = EEPROM.read(0);//前回終了時のデータ読み出し。
 number_1 = EEPROM.read(1);//前回終了時のデータ読み出し。
 bank_2 = EEPROM.read(2);  //前回終了時のデータ読み出し。
 number_2 = EEPROM.read(3);//前回終了時のデータ読み出し。
 bank_3 = EEPROM.read(4);//前回終了時のデータ読み出し。
 //  number_3 = EEPROM.read(5);//前回終了時のデータ読み出し。

 mp3_set_EQ (bank_3);//前回終了時のEQ書き込み。

 analogWrite(10, 80); //起動直後はLED全光とする。起動確認のため。
 analogWrite(11, 80); //起動直後はLED全光とする。起動確認のため。
}


void loop() {

 //---------セレクトモード判定-------------
 select_sw1 = digitalRead(7);
 select_sw2 = digitalRead(8);
 old_select_mode = select_mode;
 if (select_sw1 == 0 && select_sw2 == 1) {
   select_mode = 1;
 }
 else if (select_sw1 == 1 && select_sw2 == 1) {
   select_mode = 2;
 }
 else if (select_sw1 == 1 && select_sw2 == 0) {
   select_mode = 3;
 }

 //------SWを切り替えた時に、LED切り替えた後の表示をする。EEPROMにデータを保存する。-----------
 if ( old_select_mode != select_mode ) {

   EEPROM.update(0, bank_1); //変更データをEEPROMに書き込み
   EEPROM.update(1, number_1); //変更データをEEPROMに書き込み
   EEPROM.update(2, bank_2); //変更データをEEPROMに書き込み
   EEPROM.update(3, number_2); //変更データをEEPROMに書き込み
   EEPROM.update(4, bank_3); //変更データをEEPROMに書き込み
   //    EEPROM.update(5,number_3);//変更データをEEPROMに書き込み

   if ( select_mode == 1) {
     analogWrite(11, bank_1 * 2 * 5); //bankのLED。
     analogWrite(10, number_1 * 5); //numberのLED。
   }
   else if ( select_mode == 2) {
     analogWrite(11, bank_2 * 2 * 5); //bankのLED。
     analogWrite(10, number_2 * 5); //numberのLED。
   }
   else if ( select_mode == 3) {
     analogWrite(11, bank_3 * 2 * 5); //EQのLED。
     analogWrite(10, number_3 * 128); //LOOPのLED。numberのLED。
   }
 }

 //---------ロータリーエンコーダ--------------


 //select_modeがch1の場合
 if (select_mode == 1) {
   float newPosition = myEnc.read();
   if ( (newPosition - 4) / 4  > oldPosition / 4) {//ロータリーエンコーダの分解能4で割る
     bank_1 ++;
     if ( bank_1 >= 6 ) {
       bank_1 = 1;
     }
     analogWrite(11, 1); //切り替わりを明示するために、一瞬消灯させる。
     delay(5);
     analogWrite(11, bank_1 * 2 * 5 ); //bankのLED。
     mp3_play_number = (bank_1 - 1) * 10 + number_1 ;
     mp3_play (mp3_play_number);
     delay (10);
     oldPosition = newPosition;
   }
   else if ( (newPosition + 4) / 4  < oldPosition / 4 ) {//ロータリーエンコーダの分解能4で割る
     number_1 ++;
     if ( number_1 >= 11) {
       number_1 = 1;
     }
     analogWrite(10, 1); //切り替わりを明示するために、一瞬消灯させる。
     delay(5);
     analogWrite(10, number_1 * 5 ); //numberのLED。
     mp3_play_number = (bank_1 - 1) * 10 + number_1 ;
     mp3_play (mp3_play_number);
     delay (10);
     oldPosition = newPosition;
   }
 }


 //select_modeがch2の場合
 else if (select_mode == 2) {
   long newPosition = myEnc.read();
   if ((newPosition - 4) / 4 > oldPosition / 4) {//ロータリーエンコーダの分解能4で割る
     bank_2 ++;
     if ( bank_2 >= 6 ) {
       bank_2 = 1;
     }
     analogWrite(11, 1); //切り替わりを明示するために、一瞬消灯させる。
     delay(5);
     analogWrite(11, bank_2 * 2 * 5 ); //bankのLED。
     mp3_play_number = (bank_2 - 1) * 10 + number_2 ;
     mp3_play (mp3_play_number);
     delay (10);
     oldPosition = newPosition;
   }
   else if ( (newPosition + 4) / 4 < oldPosition / 4 ) {//ロータリーエンコーダの分解能4で割る
     number_2 ++;
     if ( number_2 >= 11) {
       number_2 = 1;
     }
     analogWrite(10, 1); //切り替わりを明示するために、一瞬消灯させる。
     delay(5);
     analogWrite(10, number_2 * 5 ); //numberのLED。
     mp3_play_number = (bank_2 - 1) * 10 + number_2 ;
     mp3_play (mp3_play_number);
     delay (10);
     oldPosition = newPosition;
   }
 }


 //select_modeがch3の場合
 else if (select_mode == 3) {
   long newPosition = myEnc.read();

   if ((newPosition - 4) / 4 > oldPosition / 4) {//ロータリーエンコーダの分解能4で割る
     bank_3 ++;
     mp3_set_EQ (bank_3);//EQ送信0~5
     analogWrite(11, 1); //切り替わりを明示するために、一瞬消灯させる。
     delay(5);
     analogWrite(11, bank_3 * 2 * 5 ); //bankのLED。
     if ( bank_3 >= 6 ) {
       bank_3 = 1;
     }
     oldPosition = newPosition;
   }
   else if ( (newPosition + 4) / 4 < oldPosition / 4 ) {//ロータリーエンコーダの分解能4で割る
     number_3 ++;
     if ( number_3 >= 2) {
       number_3 = 0;
     }
     if (number_3 == 1) {
       mp3_play_number = (bank_2 - 1) * 10 + number_2 ;
       mp3_play (mp3_play_number);
       delay (100);//短くすると動作不安定
       mp3_single_loop (true);
       analogWrite(10, 1); //切り替わりを明示するために、一瞬消灯させる。
       delay(5);
       analogWrite(10, number_3 * 128); //numberのLED。
     }
     else if ( number_3 == 0) {
       delay (100);//短くすると動作不安定
       mp3_single_loop (false);
       analogWrite(10, number_3 * 128); //numberのLED。
     }
             oldPosition = newPosition;
   }
 }

 //--------トリガー、レイテンシー-----------------------

 //  trigger = digitalRead(13);//◆開発用、プッシュスイッチでトリガー
 old_trigger1 = trigger1;
 old_trigger2 = trigger2;
 trigger1 = digitalRead(12);
 trigger2 = digitalRead(13);
 old_latency = latency;
 latency = analogRead(1);


 if (trigger1 == 1 && old_trigger1 == 0) {

   mp3_play_number = (bank_1 - 1) * 10 + number_1 ;
   delay (latency);
   mp3_play (mp3_play_number);
   delay (100);
 }
 else if (trigger2 == 1 && old_trigger2 == 0) {
   mp3_play_number = (bank_2 - 1) * 10 + number_2 ;
   delay (latency);
   mp3_play (mp3_play_number);
   delay (100);
 }

 //----------一時停止/再生用---------------
 if ( old_latency < 900 && latency >= 900 ) {
   mp3_pause ();
 }
 else if ( old_latency >= 900 && latency < 900) {
   mp3_play (mp3_play_number);
 }


 //-----------開発用-------------
 Serial.print(mp3_play_number);
 Serial.print(",");
 Serial.print(bank_1);
 Serial.print(",");
 Serial.print(number_1);
 Serial.println("");

}

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