先日より、「M5Stack用MIDIシンセサイザユニット」という製品をマイコンデバイスにつなぎ、Arduino IDEのスケッチで入力した音楽を演奏させて楽しんでいます(記事は こちら)。
「MIDIシンセサイザユニット」では、ドラム音を含めた様々な楽器の音色を同時に鳴らすことができ、とても気軽にいろいろな音楽を鳴らすことができます。
ただ、当たり前の話ですが、たくさんの音を鳴らそうと思うと、その分だけArduino IDEスケッチがどんどん長くなってしまいます。
ひとつの音符を鳴らして消すのに2行のコマンド記述が必要なので、例えば4種類の音色をつかって4小節のメロディーをつくり、1小節あたり4つの音符を使用すると、たったそれだけで「4 × 4 × 4 × 2 = 128」行の記述が必要になります。記述するのが面倒なだけでなく、音が多くなると煩雑になって間違いも多くなります。
そのため、もっと簡単に、ピアノロールアプリで音符を入力する程度の手間でArduino IDEスケッチをつくれないかと考えました。MIDIファイルをArduino IDEスケッチに変換できれば、スケッチ作成が劇的に簡単になりそうです。
以下のフローを考えてみました。
- フリーのピアノロールアプリを使って音楽を入力し、MIDIファイルを書き出す。
- フリーのアプリでMIDIファイルをCSVファイルに変換する。
- CSVファイルをArduino IDEコマンド形式に、テキストフォーマット変換する。
この手順でArduino IDEスケッチ作成の手間を軽減し、「ATOMS3」で音楽を鳴らしてみようと思います。
まず「ピアノロールアプリ」について、小学生でもつかえるぐらい簡単なフリーのWebアプリという条件で「ChatGPT」に聞いたところ、「Online Sequencer」というアプリを紹介されたので、これを使ってみることにしました。
- 「https://onlinesequencer.net」にアクセスすると、アカウント登録やログインなどは不要で、このようなウィンドウが表示されます。

- 音符を置きたい箇所をクリックすると音符が入力されます。入力された音符を選択して端をドラッグすると音符の長さを変更できます。複雑な処理はできないのかもしれませんが、その分非常に簡単に音楽を入力できました。

- 入力が完了したら、ウィンドウ上部の「アップロード」アイコンをクリックします。するとウィンドウ右上にアップロードされた先のURLが表示されます。

- URLをクリックすると新しいウィンドウが開きます。ウィンドウ右の「Download MIDI」をクリックすると、入力した音楽のMIDIファイルがPCにダウンロードできます。

次にMIDIファイルをCSVファイルに変換します。こちらも「ChatGPT」に聞いたところ、「MIDI Converter」というWebアプリを紹介されました。
- 「https://midi-to-csv.vercel.app/」にアクセスすると、このようなウィンドウが表示されます。

- ウィンドウ上部の「Update MIDI File」をクリックして、先ほどつくったMIDIファイルを選択すると、このようにひとつひとつの音符情報がテキスト形式で表示されます。

- ウィンドウ右上の「Download」をクリックすると、CSVファイルがPCにダウンロードできます。
最後のArduino IDEコマンド形式へのテキストフォーマット変換ですが、今回はごく簡単なawkスクリプトをつくることにしました。
- 私はMacを使っているのですが、Macの場合は標準インストールされている「ターミナル」を起動します。Windowsの場合は「Ubuntu」や「Git Bash」などのawkコマンドを実行できる環境をインストールして、それを起動します。
- 新規に「midicsv2unitsynth.awk」というファイルを開き、以下のとおり記述します。
BEGIN{
FS=",";
}
{
if($2=="note_on") {
printf(" if(cnt==%d) synth.setNoteOn(%d, %d, vol%d);\n", $7*1000, $1, $3, $1);
if(noteoff) printf(" if(cnt==%d) synth.setNoteOff(%d, %d, 0);\n", ($7+$6)*1000, $1, $3);
}
}
- 「awk -f ./midicsv2unitsynth.awk -v noteoff=1 [入力CSVファイル] > [出力Arduinoコマンド]」のようにスクリプトを実行すると、音符情報がArduino IDEコマンド形式でファイル出力されます。ここで「noteoff=1」というのは、ひとつの音符に対して音を鳴らすコマンドと音を消すコマンドの2行を出力する場合に設定するオプションです。オルガンの音など、音が鳴り続ける音色の場合は、明示的に音を消すコマンドを使わないと音が消えません。
if(cnt==0) synth.setNoteOn(0, 59, vol0);
if(cnt==409) synth.setNoteOff(0, 59, 0);
if(cnt==409) synth.setNoteOn(0, 57, vol0);
if(cnt==545) synth.setNoteOff(0, 57, 0);
if(cnt==545) synth.setNoteOn(0, 55, vol0);
if(cnt==818) synth.setNoteOff(0, 55, 0);
if(cnt==818) synth.setNoteOn(0, 57, vol0);
if(cnt==1090) synth.setNoteOff(0, 57, 0);
:
- 一方、ドラムの音などは明示的に音を消さなくても瞬時に音は消えるので、そのような場合は「noteoff=0」とします。
if(cnt==0) synth.setNoteOn(0, 59, vol0);
if(cnt==409) synth.setNoteOn(0, 57, vol0);
if(cnt==545) synth.setNoteOn(0, 55, vol0);
if(cnt==818) synth.setNoteOn(0, 57, vol0);
if(cnt==1090) synth.setNoteOn(0, 59, vol0);
if(cnt==1363) synth.setNoteOn(0, 59, vol0);
if(cnt==1636) synth.setNoteOn(0, 59, vol0);
:
- このawkスクリプトで生成できるのは音符に相当するArduinoコマンドだけなので、ここでつくったコマンドを大元のスケッチに貼り付けます。「loop()」では1ms毎に「playMusic()」関数を呼び出しており、「playMusic()」関数内に先ほど生成したコマンドを貼り付けています。「playMusic()」関数では引数で演奏開始からの経過時間を受け渡しており、その時間に応じて必要な音を鳴らしたり消したりしています。なお、「MIDIシンセサイザユニット」では、ドラム音はチャンネル9番で固定になっているので、ドラム音を貼り付ける際にはArduinoコマンドのチャンネル番号(引数のひとつめ)を「9」に書き換えてから貼り付けます。
#include <M5Unified.h>
#include "M5UnitSynth.h"
M5UnitSynth synth;
unsigned long prevTime = 0;
int musicTime = 0;
int vol0 = 127;
int vol1 = 95;
int vol2 = 63;
int vol9 = 63;
void setup() {
auto cfg = M5.config();
M5.begin(cfg);
delay(10);
synth.begin(&Serial2, UNIT_SYNTH_BAUD, 1, 2);
synth.setInstrument(0, 0, GrandPiano_1); // melody
synth.setInstrument(0, 1, HonkyTonkPiano);// chords
synth.setInstrument(0, 2, FingerBass); // bass
synth.setInstrument(0, 9, SynthDrum); // drum
synth.setAllNotesOff(0);
synth.setAllNotesOff(1);
synth.setAllNotesOff(2);
synth.setAllNotesOff(9);
while(true) {
M5.update();
if (M5.BtnA.wasPressed()) break;
delay(10);
}
}
void loop() {
M5.update();
unsigned long currentTime = millis();
if(currentTime-prevTime >= 1) {
playMusic(musicTime);
musicTime++;
if(musicTime>=8727) musicTime=0; // 60000ms/110bpm*4beats*4measures
prevTime = currentTime;
}
}
void playMusic(int cnt) {
if(cnt==0) synth.setNoteOn(0, 59, vol0);
if(cnt==409) synth.setNoteOff(0, 59, 0);
if(cnt==409) synth.setNoteOn(0, 57, vol0);
if(cnt==545) synth.setNoteOff(0, 57, 0);
if(cnt==545) synth.setNoteOn(0, 55, vol0);
if(cnt==818) synth.setNoteOff(0, 55, 0);
if(cnt==818) synth.setNoteOn(0, 57, vol0);
if(cnt==1090) synth.setNoteOff(0, 57, 0);
if(cnt==1090) synth.setNoteOn(0, 59, vol0);
if(cnt==1363) synth.setNoteOff(0, 59, 0);
if(cnt==1363) synth.setNoteOn(0, 59, vol0);
if(cnt==1636) synth.setNoteOff(0, 59, 0);
if(cnt==1636) synth.setNoteOn(0, 59, vol0);
if(cnt==2045) synth.setNoteOff(0, 59, 0);
if(cnt==2181) synth.setNoteOn(0, 57, vol0);
if(cnt==2454) synth.setNoteOff(0, 57, 0);
if(cnt==2454) synth.setNoteOn(0, 57, vol0);
if(cnt==2727) synth.setNoteOff(0, 57, 0);
if(cnt==2727) synth.setNoteOn(0, 57, vol0);
if(cnt==3136) synth.setNoteOff(0, 57, 0);
if(cnt==3272) synth.setNoteOn(0, 59, vol0);
if(cnt==3545) synth.setNoteOff(0, 59, 0);
if(cnt==3545) synth.setNoteOn(0, 62, vol0);
if(cnt==3818) synth.setNoteOff(0, 62, 0);
if(cnt==3818) synth.setNoteOn(0, 62, vol0);
if(cnt==4227) synth.setNoteOff(0, 62, 0);
if(cnt==4363) synth.setNoteOn(0, 59, vol0);
if(cnt==4772) synth.setNoteOff(0, 59, 0);
if(cnt==4772) synth.setNoteOn(0, 57, vol0);
if(cnt==4909) synth.setNoteOff(0, 57, 0);
if(cnt==4909) synth.setNoteOn(0, 55, vol0);
if(cnt==5181) synth.setNoteOff(0, 55, 0);
if(cnt==5181) synth.setNoteOn(0, 57, vol0);
if(cnt==5454) synth.setNoteOff(0, 57, 0);
if(cnt==5454) synth.setNoteOn(0, 59, vol0);
if(cnt==5727) synth.setNoteOff(0, 59, 0);
if(cnt==5727) synth.setNoteOn(0, 59, vol0);
if(cnt==5999) synth.setNoteOff(0, 59, 0);
if(cnt==5999) synth.setNoteOn(0, 59, vol0);
if(cnt==6272) synth.setNoteOff(0, 59, 0);
if(cnt==6272) synth.setNoteOn(0, 59, vol0);
if(cnt==6545) synth.setNoteOff(0, 59, 0);
if(cnt==6545) synth.setNoteOn(0, 57, vol0);
if(cnt==6818) synth.setNoteOff(0, 57, 0);
if(cnt==6818) synth.setNoteOn(0, 57, vol0);
if(cnt==7090) synth.setNoteOff(0, 57, 0);
if(cnt==7090) synth.setNoteOn(0, 59, vol0);
if(cnt==7499) synth.setNoteOff(0, 59, 0);
if(cnt==7499) synth.setNoteOn(0, 57, vol0);
if(cnt==7636) synth.setNoteOff(0, 57, 0);
if(cnt==7636) synth.setNoteOn(0, 55, vol0);
if(cnt==8181) synth.setNoteOff(0, 55, 0);
if(cnt==272) synth.setNoteOn(1, 50, vol1);
if(cnt==545) synth.setNoteOff(1, 50, 0);
if(cnt==272) synth.setNoteOn(1, 47, vol1);
if(cnt==545) synth.setNoteOff(1, 47, 0);
if(cnt==272) synth.setNoteOn(1, 43, vol1);
if(cnt==545) synth.setNoteOff(1, 43, 0);
if(cnt==818) synth.setNoteOn(1, 50, vol1);
if(cnt==1090) synth.setNoteOff(1, 50, 0);
if(cnt==818) synth.setNoteOn(1, 47, vol1);
if(cnt==1090) synth.setNoteOff(1, 47, 0);
if(cnt==818) synth.setNoteOn(1, 43, vol1);
if(cnt==1090) synth.setNoteOff(1, 43, 0);
if(cnt==1363) synth.setNoteOn(1, 50, vol1);
if(cnt==1636) synth.setNoteOff(1, 50, 0);
if(cnt==1363) synth.setNoteOn(1, 43, vol1);
if(cnt==1636) synth.setNoteOff(1, 43, 0);
if(cnt==1363) synth.setNoteOn(1, 47, vol1);
if(cnt==1636) synth.setNoteOff(1, 47, 0);
if(cnt==1909) synth.setNoteOn(1, 50, vol1);
if(cnt==2181) synth.setNoteOff(1, 50, 0);
if(cnt==1909) synth.setNoteOn(1, 47, vol1);
if(cnt==2181) synth.setNoteOff(1, 47, 0);
if(cnt==1909) synth.setNoteOn(1, 43, vol1);
if(cnt==2181) synth.setNoteOff(1, 43, 0);
if(cnt==2454) synth.setNoteOn(1, 50, vol1);
if(cnt==2727) synth.setNoteOff(1, 50, 0);
if(cnt==2454) synth.setNoteOn(1, 42, vol1);
if(cnt==2727) synth.setNoteOff(1, 42, 0);
if(cnt==2454) synth.setNoteOn(1, 45, vol1);
if(cnt==2727) synth.setNoteOff(1, 45, 0);
if(cnt==2999) synth.setNoteOn(1, 50, vol1);
if(cnt==3272) synth.setNoteOff(1, 50, 0);
if(cnt==2999) synth.setNoteOn(1, 45, vol1);
if(cnt==3272) synth.setNoteOff(1, 45, 0);
if(cnt==2999) synth.setNoteOn(1, 42, vol1);
if(cnt==3272) synth.setNoteOff(1, 42, 0);
if(cnt==3545) synth.setNoteOn(1, 50, vol1);
if(cnt==3818) synth.setNoteOff(1, 50, 0);
if(cnt==3545) synth.setNoteOn(1, 43, vol1);
if(cnt==3818) synth.setNoteOff(1, 43, 0);
if(cnt==3545) synth.setNoteOn(1, 47, vol1);
if(cnt==3818) synth.setNoteOff(1, 47, 0);
if(cnt==4090) synth.setNoteOn(1, 50, vol1);
if(cnt==4363) synth.setNoteOff(1, 50, 0);
if(cnt==4090) synth.setNoteOn(1, 47, vol1);
if(cnt==4363) synth.setNoteOff(1, 47, 0);
if(cnt==4090) synth.setNoteOn(1, 43, vol1);
if(cnt==4363) synth.setNoteOff(1, 43, 0);
if(cnt==4636) synth.setNoteOn(1, 50, vol1);
if(cnt==4909) synth.setNoteOff(1, 50, 0);
if(cnt==4636) synth.setNoteOn(1, 43, vol1);
if(cnt==4909) synth.setNoteOff(1, 43, 0);
if(cnt==4636) synth.setNoteOn(1, 47, vol1);
if(cnt==4909) synth.setNoteOff(1, 47, 0);
if(cnt==5181) synth.setNoteOn(1, 50, vol1);
if(cnt==5454) synth.setNoteOff(1, 50, 0);
if(cnt==5181) synth.setNoteOn(1, 43, vol1);
if(cnt==5454) synth.setNoteOff(1, 43, 0);
if(cnt==5181) synth.setNoteOn(1, 47, vol1);
if(cnt==5454) synth.setNoteOff(1, 47, 0);
if(cnt==5727) synth.setNoteOn(1, 50, vol1);
if(cnt==5999) synth.setNoteOff(1, 50, 0);
if(cnt==5727) synth.setNoteOn(1, 43, vol1);
if(cnt==5999) synth.setNoteOff(1, 43, 0);
if(cnt==5727) synth.setNoteOn(1, 47, vol1);
if(cnt==5999) synth.setNoteOff(1, 47, 0);
if(cnt==6272) synth.setNoteOn(1, 50, vol1);
if(cnt==6545) synth.setNoteOff(1, 50, 0);
if(cnt==6272) synth.setNoteOn(1, 43, vol1);
if(cnt==6545) synth.setNoteOff(1, 43, 0);
if(cnt==6272) synth.setNoteOn(1, 47, vol1);
if(cnt==6545) synth.setNoteOff(1, 47, 0);
if(cnt==6818) synth.setNoteOn(1, 50, vol1);
if(cnt==7090) synth.setNoteOff(1, 50, 0);
if(cnt==6818) synth.setNoteOn(1, 42, vol1);
if(cnt==7090) synth.setNoteOff(1, 42, 0);
if(cnt==6818) synth.setNoteOn(1, 45, vol1);
if(cnt==7090) synth.setNoteOff(1, 45, 0);
if(cnt==7363) synth.setNoteOn(1, 50, vol1);
if(cnt==7636) synth.setNoteOff(1, 50, 0);
if(cnt==7363) synth.setNoteOn(1, 42, vol1);
if(cnt==7636) synth.setNoteOff(1, 42, 0);
if(cnt==7363) synth.setNoteOn(1, 45, vol1);
if(cnt==7636) synth.setNoteOff(1, 45, 0);
if(cnt==7909) synth.setNoteOn(1, 50, vol1);
if(cnt==8181) synth.setNoteOff(1, 50, 0);
if(cnt==7909) synth.setNoteOn(1, 43, vol1);
if(cnt==8181) synth.setNoteOff(1, 43, 0);
if(cnt==7909) synth.setNoteOn(1, 47, vol1);
if(cnt==8181) synth.setNoteOff(1, 47, 0);
if(cnt==8181) synth.setNoteOn(1, 50, vol1);
if(cnt==8454) synth.setNoteOff(1, 50, 0);
if(cnt==8181) synth.setNoteOn(1, 43, vol1);
if(cnt==8454) synth.setNoteOff(1, 43, 0);
if(cnt==8181) synth.setNoteOn(1, 47, vol1);
if(cnt==8454) synth.setNoteOff(1, 47, 0);
if(cnt==0) synth.setNoteOn(2, 43, vol2);
if(cnt==136) synth.setNoteOff(2, 43, 0);
if(cnt==545) synth.setNoteOn(2, 38, vol2);
if(cnt==681) synth.setNoteOff(2, 38, 0);
if(cnt==1090) synth.setNoteOn(2, 43, vol2);
if(cnt==1227) synth.setNoteOff(2, 43, 0);
if(cnt==1636) synth.setNoteOn(2, 38, vol2);
if(cnt==1772) synth.setNoteOff(2, 38, 0);
if(cnt==2181) synth.setNoteOn(2, 42, vol2);
if(cnt==2318) synth.setNoteOff(2, 42, 0);
if(cnt==2727) synth.setNoteOn(2, 38, vol2);
if(cnt==2863) synth.setNoteOff(2, 38, 0);
if(cnt==3272) synth.setNoteOn(2, 43, vol2);
if(cnt==3409) synth.setNoteOff(2, 43, 0);
if(cnt==3818) synth.setNoteOn(2, 38, vol2);
if(cnt==3954) synth.setNoteOff(2, 38, 0);
if(cnt==4363) synth.setNoteOn(2, 43, vol2);
if(cnt==4499) synth.setNoteOff(2, 43, 0);
if(cnt==4909) synth.setNoteOn(2, 38, vol2);
if(cnt==5045) synth.setNoteOff(2, 38, 0);
if(cnt==5454) synth.setNoteOn(2, 43, vol2);
if(cnt==5590) synth.setNoteOff(2, 43, 0);
if(cnt==5999) synth.setNoteOn(2, 38, vol2);
if(cnt==6136) synth.setNoteOff(2, 38, 0);
if(cnt==6545) synth.setNoteOn(2, 42, vol2);
if(cnt==6681) synth.setNoteOff(2, 42, 0);
if(cnt==7090) synth.setNoteOn(2, 38, vol2);
if(cnt==7227) synth.setNoteOff(2, 38, 0);
if(cnt==7636) synth.setNoteOn(2, 43, vol2);
if(cnt==7772) synth.setNoteOff(2, 43, 0);
if(cnt==8181) synth.setNoteOn(2, 43, vol2);
if(cnt==8318) synth.setNoteOff(2, 43, 0);
if(cnt==0) synth.setNoteOn(9, 36, vol9);
if(cnt==272) synth.setNoteOn(9, 38, vol9);
if(cnt==545) synth.setNoteOn(9, 36, vol9);
if(cnt==818) synth.setNoteOn(9, 38, vol9);
if(cnt==1090) synth.setNoteOn(9, 36, vol9);
if(cnt==1363) synth.setNoteOn(9, 38, vol9);
if(cnt==1636) synth.setNoteOn(9, 36, vol9);
if(cnt==1909) synth.setNoteOn(9, 38, vol9);
if(cnt==2181) synth.setNoteOn(9, 36, vol9);
if(cnt==2454) synth.setNoteOn(9, 38, vol9);
if(cnt==2727) synth.setNoteOn(9, 36, vol9);
if(cnt==2999) synth.setNoteOn(9, 38, vol9);
if(cnt==3272) synth.setNoteOn(9, 36, vol9);
if(cnt==3545) synth.setNoteOn(9, 38, vol9);
if(cnt==3818) synth.setNoteOn(9, 36, vol9);
if(cnt==4090) synth.setNoteOn(9, 38, vol9);
if(cnt==4363) synth.setNoteOn(9, 36, vol9);
if(cnt==4636) synth.setNoteOn(9, 38, vol9);
if(cnt==4909) synth.setNoteOn(9, 36, vol9);
if(cnt==5181) synth.setNoteOn(9, 38, vol9);
if(cnt==5454) synth.setNoteOn(9, 36, vol9);
if(cnt==5727) synth.setNoteOn(9, 38, vol9);
if(cnt==5999) synth.setNoteOn(9, 36, vol9);
if(cnt==6272) synth.setNoteOn(9, 38, vol9);
if(cnt==6545) synth.setNoteOn(9, 36, vol9);
if(cnt==6818) synth.setNoteOn(9, 38, vol9);
if(cnt==7090) synth.setNoteOn(9, 36, vol9);
if(cnt==7363) synth.setNoteOn(9, 38, vol9);
if(cnt==7636) synth.setNoteOn(9, 36, vol9);
if(cnt==7909) synth.setNoteOn(9, 38, vol9);
if(cnt==8181) synth.setNoteOn(9, 36, vol9);
}
これでできあがりです。今回つくった「メリーさんのひつじ」は4小節の音楽で、「メロディー」「伴奏(コード)」「ベース」「ドラム」の4つのチャンネルをつかっています。音符は全部で220個ありますが、これらを入力して、Arduino IDEスケッチとして完成させるまでの全ての工程を10分程度で終わらせることができました。
Arduino IDEスケッチにコマンドを直接記述していたときに比べると、作成が格段に楽になりました。
竹内まりやの「プラスティック・ラブ」のイントロを入力してみました。
以前からやってみたかったのですが、入力が面倒でずっと躊躇していました。
今回構築したしくみを使うことで、とても簡単に入力することができました。これならいろいろな音楽を入力して楽しめそうです。