はじめに
この記事では、HDD、ステッピングモーター、ソレノイドを用いた演奏システムの技術的詳細について、書いていきます。
こういうやつです。
システム概要
今回紹介するシステムは、以下の図のようになっています。
以下で、利用技術の詳細について説明します。
電源
今回は、アクチュエータ用の電源と、マイコンチップ用の電源を別々に用意しています。理由は、モーターやソレノイドの駆動に伴って、電源電圧に大きなノイズが乗るので、それによってマイコンの動作に不具合が現れるのを防ぐためです。
マイコン制御用電源
マイコン用には、秋月電子で買った9VのACアダプターを、三端子レギュレータで5Vに落として使っています。
アクチュエータ駆動用電源
HDD, ステッピングモーター, ソレノイドを駆動しているのは、Amazonで買った5V, 30Aの電源です。本当に30Aまで使えるかは不明です。
アクチュエータと電源は、今回は5Vのものを選定しましたが、5Vにこだわらず、12Vなどを選ぶのも手だと思います。
MIDI送信ソフトウェア
- PC: Macbook Air
- MIDI作成ソフト: MuseScore 3
- MIDI送信ソフト: Cubase LE AI Elements 11
- USB-MIDI変換ケーブル: Roland UM-ONE
Cubaseはオーディオインターフェイスを買ったときについてきたものです。ソフトウェア単体だと、(たぶん)有料です。
MIDI受信回路
MIDIケーブルから来た信号を、フォトカプラを通してマイコンに送ります。フォトカプラはTLP2630を使いましたが、現在の秋月電子の通販サイトに見当たりませんね。動作の速いフォトカプラを選ぶ必要があるので、気を付けてください。
制御用マイコンチップ
Arduino Unoの上に載ってるマイコンチップ(ATmega328P)の下位互換ATmega8を使っています。選定理由は、Arduinoと同じように使えて、ATmega328Pよりも安く、性能が(今回の用途では)十分だからです。もちろん、Arduino Unoを用いることもできます。
買ってきたATmega8は、そのままではArduinoのように使えないので、ブートローダーを書き込む必要があります。Arduino Unoがあれば、ATmegaチップにブートローダーを書き込むことができます。
ATmega8にブートローダーを書き込む手順
注意:この過程は、失敗しやすいわりに、失敗すると簡単には治せないので、気を付けてください。環境が違うと、再現性がなくなるかもしれません。自己責任でお願いします。
-
Arduino IDEにMiniCoreを追加する。これによって、Arduino IDEでATmega8にプログラムを書き込めるようになります。インストール手順はリンク先にあります(英語ですが)。
-
Arduino IDEにあるスケッチ例の中のArduinoISPをArduino Unoに書き込む。これで、Arduino Unoをライターとして使うことができます。
-
Arduino UnoとATmega8を、10→RESET, 11→MOSI, 12→MISO, 13→SCK, GND→GND, +5V→VCC&AVCCと接続する。ATmega8のピン配置はMiniCoreにあります。今回は、ATmega8の内部クロックを使用するので、水晶振動子などは取り付けません。
-
Arduino IDEで、ボード: "ATmega8", Clock: "Internal 8 MHz", 書込装置: "Arduino as ISP"を選択し、「ブートローダーを書き込む」を押す。
-
ATmega8にプログラムを書き込むときは、同じ配線で「書込装置を使って書き込む」を押します。
詳しいことは、MiniCoreのページを見たりしてください。
HDDを用いた演奏
HDDによる演奏の原理
HDDには、アームの位置を制御するためのコイルが存在しています。そのコイルをスピーカー代わりに使うことで、音を鳴らすことができます。
HDDの入手
秋葉原のジャンク屋さんで入手することができます。個人的には、iconというお店と、ハードオフというお店にお世話になりました。
HDDの改造
下の動画がわかりやすいです。
HDDのふたと裏についている基板を取り外し、裏に現れた端子のうち、コイルにつながっている二つの電極を探します。そこに適当な配線をはんだ付けして完成です。
HDD駆動回路
HDDに流れる電流を、MOSFETによって制御しています。HDDはただのコイルなので、極性はありませんが、アームが動く向きと電流の向きには対応関係があります。
ちなみに、後に示すプログラムには、LEDへの出力も含まれていますが、LEDと適当な抵抗を通ってGNDに落ちているだけなので、図は割愛します。
HDD制御プログラム
以下に示すのが、ATmega8に書き込んだプログラムです。MIDIライブラリでMIDIを受信した後、その音階を表すノートナンバーに応じて、HDDに対して異なる周期の矩形波を送ります。
#include <MIDI.h>
// MIDIのノートナンバーと、矩形波の周期のうちミリ秒の部分の対応関係
int msInt[80] = {
15,
14,
13,
12,
12,
(中略)
};
// MIDIのノートナンバーと、矩形波の周期のうちマイクロ秒の部分の対応関係
int usInt[80] = {
405,
538,
720,
948,
219,
(中略)
};
const byte CHANNEL = 1; // 受信するMIDIチャンネルを選ぶ
byte NOTE_NUMBER;
bool ENABLE = false;
const byte LED_PIN = 19;
const byte HDD_PIN = 8;
byte HDD_STATUS = 0;
MIDI_CREATE_DEFAULT_INSTANCE();
void handleNoteOn(byte channel, byte note_number, byte velocity) {
if (channel == CHANNEL){
// 高すぎる音を、オクターブ下げる
while (note_number >= 80) {
note_number -= 12;
}
NOTE_NUMBER = note_number;
ENABLE = true;
digitalWrite(LED_PIN, LOW);
}
}
void handleNoteOff(byte channel, byte note_number, byte velocity) {
if (channel == CHANNEL){
ENABLE = false;
digitalWrite(LED_PIN, HIGH);
digitalWrite(HDD_PIN, LOW);
HDD_STATUS = 0;
}
}
void setup() {
pinMode(LED_PIN, OUTPUT);
pinMode(HDD_PIN, OUTPUT);
digitalWrite(LED_PIN, HIGH);
digitalWrite(HDD_PIN, LOW);
MIDI.setHandleNoteOn(handleNoteOn);
MIDI.setHandleNoteOff(handleNoteOff);
MIDI.begin(MIDI_CHANNEL_OMNI);
}
void loop() {
MIDI.read();
if (ENABLE == true) {
drive();
}
}
void drive() {
switch (HDD_STATUS) {
case 0:
digitalWrite(HDD_PIN, HIGH);
break;
case 1:
digitalWrite(HDD_PIN, LOW);
break;
}
HDD_STATUS = (HDD_STATUS + 1) % 2;
delay(msInt[NOTE_NUMBER]);
delayMicroseconds(usInt[NOTE_NUMBER]);
}
チューニングについて
今回は、ATmega8の内部発振子を利用しており、その周波数にはわずかに個体差があります。また、マイクロ秒レベルで周期を制御していますが、プログラムの実行時間についても考慮する必要があります。そのため、チューニングが必要です。
例えば、ラを表す音の周波数は
f=440\ \mathrm{Hz}
であり、周期は
T = \frac{1}{f}=2.273\ \mathrm{ms}
です。
今回は、内部発振子のずれの係数$c$、1ループあたりのプログラムの実行時間$\tau$、プログラムされた待ち時間$T_0$に関して、
T=c(T_0+\tau)
が成り立つとモデル化します。そして、全ての音階で$c$と$\tau$が等しいと仮定します。
以下がチューニング手順です。
- $c=1, \tau=0$とします。また、$T_0$を平均律の周波数の逆数とします。
- 音を鳴らしてみて、チューナー(もしくは絶対音感)でずれを測定します。
- ずれが高い音と低い音で違う場合は、$\tau$を少し変更します。
- 2に戻ります。
- 全体的に同じようにずれている場合は、$c$を少し変更します。
- 2に戻ります。
- ずれが許容範囲内に収まったら、チューニング完了です。
この方法は、少し面倒な割には、3オクターブくらいしかカバーできませんでした。より正確なチューニングには、より適切なモデルが必要になると思います。
ステッピングモーターを用いた演奏
ステッピングモーターによる演奏の原理
ステッピングモーターは、カクッカクッと一定の角度ずつステップ状に回転するモーターです。その周期を、音の周波数に合わせると、回転の際に発生する振動で、音が発生します。
今回は、木材の床の上で振動させていますが、プラスチックの箱に押しつけたりすると、また少し違った音色になります。また、振動を伝える物質に明確な固有振動があると、音の大きさが音階によってばらつくので、なるべく固有振動のようなものが少ない形状のものを選ぶのが良いと思います。
ステッピングモーターの選定
ステッピングモーターには、いくつかの種類が存在します。今回は、配線の少なさ、モータードライバの入手性、ステッピングモーターの入手性などの観点から、秋月電子の2相バイポーラステッピングモーターを選定しました。
ステッピングモーター駆動回路
ステッピングモーターの駆動には、秋月電子のステッピングモータドライバDRV8835を使用しています。回路は、モータードライバとATmega8とステッピングモーターを適切に繋ぐだけです。
ステッピングモーター制御プログラム
基本的には、HDDの制御プログラムと同じです。こちらも、適当にチューニングした方が良いと思います。
ステッピングモーターはベースとして使っているので、低音に特化した音域の周期テーブルを定義しています。また、delay関数による待ち時間が長くなりすぎると、MIDIの受信に問題が出てきたので、一つの待ち時間を4分割しています。
#include <MIDI.h>
int msInt[80] = {
31,
29,
27,
26,
24,
(中略)
};
int usInt[80] = {
183,
430,
776,
214,
740,
(中略)
};
const byte CHANNEL = 7;
byte NOTE_NUMBER;
bool ENABLE = false;
const byte LED_PIN = 19;
const byte STM_A_PIN = 6;
const byte STM_B_PIN = 8;
const byte STM_E_PIN = 7;
byte STM_STATUS = 0;
MIDI_CREATE_DEFAULT_INSTANCE();
void handleNoteOn(byte channel, byte note_number, byte velocity) {
if (channel == CHANNEL){
while (note_number >= 80) {
note_number -= 12;
}
NOTE_NUMBER = note_number;
ENABLE = true;
digitalWrite(LED_PIN, LOW);
digitalWrite(STM_E_PIN, HIGH);
}
}
void handleNoteOff(byte channel, byte note_number, byte velocity) {
if (channel == CHANNEL){
ENABLE = false;
digitalWrite(LED_PIN, HIGH);
digitalWrite(STM_E_PIN, LOW);
}
}
void setup() {
pinMode(LED_PIN, OUTPUT);
pinMode(STM_A_PIN, OUTPUT);
pinMode(STM_B_PIN, OUTPUT);
pinMode(STM_E_PIN, OUTPUT);
digitalWrite(LED_PIN, HIGH);
digitalWrite(STM_A_PIN, LOW);
digitalWrite(STM_B_PIN, LOW);
digitalWrite(STM_E_PIN, LOW);
MIDI.setHandleNoteOn(handleNoteOn);
MIDI.setHandleNoteOff(handleNoteOff);
MIDI.begin(MIDI_CHANNEL_OMNI);
}
void loop() {
MIDI.read();
if (ENABLE == true) {
drive();
}
}
void drive() {
switch (STM_STATUS) {
case 0:
digitalWrite(STM_A_PIN, LOW);
break;
case 4:
digitalWrite(STM_B_PIN, LOW);
break;
case 8:
digitalWrite(STM_A_PIN, HIGH);
break;
case 12:
digitalWrite(STM_B_PIN, HIGH);
break;
} // switch
STM_STATUS = (STM_STATUS + 1) % 16;
delay(msInt[NOTE_NUMBER]);
delayMicroseconds(usInt[NOTE_NUMBER]);
}
ソレノイドによる演奏
ソレノイドによる演奏の原理
今回は、ソレノイドでプラ棒を振り下ろすことで、ドラムとして使っています。少し変更すれば、鉄琴やキーボードなどの自動演奏にも使えると思います。
ソレノイドの選定
今回は、秋月電子のソレノイドを選定しました。理由は、5Vの動作電圧、入手性、性能と価格のバランスです。このソレノイドは、そこまで力強くないので、いろいろ探してみるのも良いと思います。
ソレノイドは、薄めのプラ板に対して、テグスで固定しています。3Dプリンターが使える人は、そっちの方がいい気がします。
ソレノイド駆動回路
HDD駆動回路のHDDをソレノイドに置き換えるだけです。
ソレノイド制御プログラム
以下に、ATmega8に書き込んだプログラムを示します。任意の音階のオンの信号を受信した時に、ソレノイドが一瞬だけオンになるようにしています。
#include <MIDI.h>
const byte CHANNEL = 10; // MIDIのチャンネル10で受信
const byte LED_PIN = 19;
const byte SOL_PIN = 8;
MIDI_CREATE_DEFAULT_INSTANCE();
void handleNoteOn(byte channel, byte note_number, byte velocity) {
if (channel == CHANNEL){
digitalWrite(LED_PIN, LOW);
digitalWrite(SOL_PIN, HIGH);
delay(50); // 適当な値に変更するのが良い
digitalWrite(LED_PIN, HIGH);
digitalWrite(SOL_PIN, LOW);
}
}
void setup() {
pinMode(LED_PIN, OUTPUT);
pinMode(SOL_PIN, OUTPUT);
digitalWrite(LED_PIN, HIGH);
digitalWrite(SOL_PIN, LOW);
MIDI.setHandleNoteOn(handleNoteOn);
MIDI.begin(MIDI_CHANNEL_OMNI);
}
void loop() {
MIDI.read();
}
楽譜の作成
今回は、MuseScore 3というフリーソフトで、MIDIファイルを作成しています。
今回のシステムに送信するMIDIは、少し制約があります。
- HDDとステッピングモーターは、和音には対応していません。和音のない楽譜を作る必要があります。
- ステッピングモーターは、高すぎる音では脱調してしまい、回りません。
- ソレノイドドラムは、1チャンネルで1楽器です。バス、スネア、ハイハットの3種類を用意したければ、それぞれトラックを分ける必要があります。
- ソレノイドドラムは、32分音符のような、速い連打に対応していません。
おわりに
新たに参入してくる方のハードルを下げられたら嬉しいです。