安価なArduinoブレスコントローラー自作 - USB-MIDI対応でDAW直結
必要な部品と材料
| 部品 | 数量 | 備考 |
|---|---|---|
| Arduino Micro(ATmega32U4) | 1 | ネイティブUSB必須。クローンでも可だが純正推奨 |
| ハーフサイズ ブレッドボード | 1 | |
| MPS20N0040D-D 圧力センサー | 1 | 安価で入手しやすい |
| LM358 オペアンプ | 1 | センサー信号増幅用 |
| 1MΩ 抵抗 | 2 | |
| 単芯ワイヤー (22AWG等) | 適宜 | 赤=5V, 黒=GND, 黄=信号等で色分け推奨 |
| ベビーナーザルアスピレーター(鼻吸い器) | 1 | マウスピース側と赤ちゃん側を使用。フィルター付き推奨 |
| 5mm エアラインチューブ(水槽用) | 1 | アスピレーターのチューブが短い場合に延長用 |
| 3ウェイ エアチューブコネクター | 1 | センサーに直付け可能なタイプ。アクアリウムショップで入手可 |
必要なツール
- ワイヤーストリッパー/カッター (32-20AWG対応)
使用ソフトウェア
- Arduino IDE
- ライブラリ:
Oversampling,USB-MIDI(ライブラリマネージャからインストール)
プロジェクト概要
市販のブレスコントローラー(呼吸コントローラー)は高価ですが、安価な部品で自作し、USB-MIDIクラス準拠デバイスとしてPC/Macに認識させ、DAWやソフトシンセでブレスコントロール(ピッチベンド、エクスプレッション等)を行えるようにします。総コストは市販品の数分の一です。基本版は圧力センサーのみですが、将来的にバイト(噛み)センサーや傾きセンサーの追加も計画されています。
制作手順
- Arduino Microをブレッドボードに挿す(写真/回路図参照)
- 圧力センサーとオペアンプを向き(側面の切り欠き)に注意して挿す
- 抵抗を適切な長さにカットして挿す
- 単芯ワイヤーをカット/ストリップして配線(色分け推奨)
- マウスピース、チューブ、3ウェイコネクター、アスピレーターを写真通り接続。排気用チューブをカットして接続
- 3ウェイコネクターをセンサーにしっかり押し込み固定
- Arduino IDEで
OversamplingとUSB-MIDIライブラリをインストール - USBケーブルで接続し、添付コードをアップロード
- DAW/音楽ソフトでMIDIデバイスとして認識されることを確認。ブレスコントローラー対応プラグインにルーティング
上級者向け情報・調整ポイント
唾液対策(重要)
吹き込みによる唾液がチューブ内に溜まると気流変動でノイズになります。3ウェイコネクターで唾液を「排気」チューブへ逃がす構造にしています。マウスピースから3ウェイコネクターまで**常に下り勾配**になるようチューブ長を調整し、低い位置に唾液が溜まらないようにしてください。赤ちゃん側アスピレーター(フィルター付き)を排気側につけることで、滴下防止・消音・センサーへの気流確保を兼ねます。
コード内の調整パラメータ
sensorRange1/2:吹き/吸いのレンジ。最大値127に達しにくければ減らし、すぐ達しすぎれば増やすcontrollerNumber1/2:出力MIDIコントロール番号(0-127: CC, 128: アフタータッチ, 129: ピッチベンドUp, 130: ピッチベンドDown)。デフォルト両方CC#2in1/out1, in2/out2:入出力カーブ(任意の段数で定義可)。例:ソフトカーブ、レンジ制限等lowThreshold, highThreshold:休止時/最大時のチャタリング防止閾値refreshCycle:送信間隔(ms)。小さいほど高頻度更新
キャリブレーション
起動直後の数回読み取りを平均化してレストポジションを校正します。デバイス接続/リセット時は**息を吹き込まない**でください。
MIDIデバイス名変更
boards.txt を編集することで、DAWに表示されるデバイス名を変更可能(OSごとに手順が異なるため検索のこと)
回路図
ソースコード(全文)
/* Breath Controller */
// Libraries - install from Tools > Manage Libraries
#include <Oversampling.h>
#include <USB-MIDI.h>
// Debug mode (uncomment to enable)
//#define DEBUG 1
USBMIDI_CREATE_DEFAULT_INSTANCE();
Oversampling adc(10, 13, 6); // 10bit ADC, 13番ピン? 実際はオーバーサンプリング設定
// ***************** User Setup *****************
const int sensorPin1 = A0;
int sensorRange1 = 800;
int sensorRange2 = 800;
int controllerNumber1 = 2; // 吹き
int controllerNumber2 = 2; // 吸い
int controllerChannel1 = 1;
int controllerChannel2 = 1;
const int lowThreshold1 = 5;
const int lowThreshold2 = 5;
const int highThreshold1 = 0;
const int highThreshold2 = 0;
// カーブ定義(要素数一致、inは昇順)
int in1[] = {0, 127};
int out1[] = {0, 127};
int in2[] = {0, 127};
int out2[] = {0, 127};
// 例: ソフトカーブ
// int in1[] = {0, 6, 24, 78, 127};
// int out1[] = {0, 32, 64, 96, 127};
int refreshCycle = 0;
// ***************** Implementation *****************
int sensorValue1 = 0, sensorValue2 = 0;
int sensorMin1, sensorMin2;
int controllerValue1 = 0, controllerValue2 = 0;
int previousControllerValue1 = 0, previousControllerValue2 = 0;
int outputRange1, outputRange2;
int sensorLow1, sensorLow2, sensorHigh1, sensorHigh2;
void setup() {
MIDI.begin(1);
#ifdef DEBUG
Serial.begin(115200);
#endif
sensorMin1 = adc.read(sensorPin1);
sensorMin2 = 0;
outputRange1 = outputRange(controllerNumber1);
outputRange2 = outputRange(controllerNumber2);
}
void loop() {
sensorValue1 = adc.read(sensorPin1); // 吹き
sensorValue2 = sensorMin1 - sensorValue1; // 吸い(負圧)
previousControllerValue1 = controllerValue1;
previousControllerValue2 = controllerValue2;
sensorLow1 = sensorMin1 + lowThreshold1;
sensorLow2 = sensorMin2 + lowThreshold2;
sensorHigh1 = sensorLow1 + sensorRange1 - highThreshold1;
sensorHigh2 = min(sensorMin1, sensorRange2) - highThreshold2;
controllerValue1 = map(mapToCurve(constrain(sensorValue1, sensorLow1, sensorHigh1),
sensorLow1, sensorHigh1, in1, out1, sizeof(in1)/sizeof(int)),
sensorLow1, sensorHigh1, 0, outputRange1);
controllerValue2 = map(mapToCurve(constrain(sensorValue2, sensorLow2, sensorHigh2),
sensorLow2, sensorHigh2, in2, out2, sizeof(in2)/sizeof(int)),
sensorLow2, sensorHigh2, 0, outputRange2);
if (controllerValue1 != previousControllerValue1)
sendSensorOutput(controllerNumber1, controllerValue1, controllerChannel1);
if (controllerValue2 != previousControllerValue2)
sendSensorOutput(controllerNumber2, controllerValue2, controllerChannel2);
#ifdef DEBUG
Serial.print(controllerValue1);
Serial.print(",");
Serial.println(controllerValue2);
#endif
delay(refreshCycle);
}
void sendSensorOutput(int number, int value, int channel) {
if (number < 128) MIDI.sendControlChange(number, value, channel);
else if (number == 128) MIDI.sendAfterTouch(value, channel);
else if (number == 129) MIDI.sendPitchBend(value, channel);
else if (number == 130) MIDI.sendPitchBend(-value, channel);
}
int outputRange(int number) {
return (number > 128) ? 8191 : 127;
}
// マルチマップ関数(カーブ適用) - Rob Tillaart氏のコード改変
int mapToCurve(int val, int sensorLow, int sensorHigh, int* _in, int* _out, uint8_t size) {
if (val <= map(_in[0], 0, 127, sensorLow, sensorHigh)) return map(_out[0], 0, 127, sensorLow, sensorHigh);
if (val >= map(_in[size-1], 0, 127, sensorLow, sensorHigh)) return map(_out[size-1], 0, 127, sensorLow, sensorHigh);
uint8_t pos = 1;
while (val > map(_in[pos], 0, 127, sensorLow, sensorHigh)) pos++;
int inPos = map(_in[pos], 0, 127, sensorLow, sensorHigh);
int outPos = map(_out[pos], 0, 127, sensorLow, sensorHigh);
int inPrv = map(_in[pos-1], 0, 127, sensorLow, sensorHigh);
int outPrv = map(_out[pos-1], 0, 127, sensorLow, sensorHigh);
if (val == inPos) return outPos;
return ((long)val - (long)inPrv) * ((long)outPos - (long)outPrv) / ((long)inPos - (long)inPrv) + (long)outPrv;
}
製造プロセス
- Arduino Nano 33 IoT製 フライトシミュレーター2020用コックピットパネル - LCD&CDI表示
- Web制御DMX照明システム:Arduino Megaマスターコントローラー
- DIY Arduino USBゲームコントローラー - 高性能ゲームパッドを自作
- Unopad: Ableton Live連携シームレスなArduino MIDIコントローラー
- Bluetoothテキストテレポーター:Arduino + Androidプロジェクト
- Arduinoベースの蛍光表示管(VFD)コントローラーキット
- Alexa対応Arduinoホームコントローラー:音声でスマートホーム自動化
- 学校向けスマート体温監視システム: 正確、リアルタイム、安全
- Arduino用8ビットI/Oポートライブラリ:デジタル制御を拡張
- Arduino用絶縁アナログ入力回路の設計:LM331 V-F変換+フォトカプラでノイズ耐性を確保
- Arduino向けスマート屋内ナビゲーションロボットキット