Arduinoでエアドラムのブチャラティ(Buccellati)作製

 久しぶりに電波プロジェクト。前から作っていたエアドラムのブチャラティが多少まともになってきたので公開。


Arduinoでエアドラム(Buccellati)を製作して演奏してみた

 こんな感じです。リズムがいまいちなのは、楽器の性能より自分の下手さが原因のような気がしてます。最後はカッとなってやってしまいました。今は後悔しています。

 詳しいことが知りたい人は、下の続きを読むをクリックしてください。ほとんど自分用のメモです。


システム図
090911_AirDrum03
090911_AirDrum03 posted by (C)karaage
 システムとしてはこんな感じ。4つの加速度センサから受けた信号を、Arduinoで処理してMIDI信号をドラムマシンに送っています。加速度センサは、3軸加速度センサモジュールのMMA7361L、ArduinoはArduino Duemilanove。ドラムマシンはM野君に借りたZOOMのRT-223。アンプ+スピーカは適当なFenderのギターアンプにつっこんでいます。
 実は、無線にする予定だったのですが。作ってから「あれ、これ思いっきり有線じゃん」という体たらくぶり。ほんとは無線にしたいのですが、時間となによりお金が…今後の課題とします。ドラムマシンやアンプ、スピーカが無い人は、パソコンでフリーのMIDI制御できるドラムマシン等を落として実現するという手があります。

外観図
090912_Airdrum02
090912_Airdrum02 posted by (C)karaage
 外見はこんな感じです。足の動きを感知する加速度センサは、スリッパにテープで貼付けて、スリッパを履いて演奏するようにしています。箱の中にArduinoが入っています。

回路図
エアドラム(Buccellati)回路図
エアドラム(Buccellati)回路図 posted by (C)karaage
 回路図はこんな感じです。Eagleというソフトで描きました。省略していますが、デジタルの5pin,11pin(PWM)にLED,3pin,6pin,13pinにスイッチを繋げてステータス表示や、音の切り替え、今後の拡張用に使用しています。無くても別に動くと言えば動きます。センサの出力は、バッファかましてインピーダンス下げてやらないと厳しいかなと思っていたのですが、そのまま繋いだだけで意外と動いてしまいました。


部品表:
Arduino Duemilanove ×1 メインマイコン。これが無いと始まらないです。
MMA7361L×4 3軸加速度センサモジュール。選んだ理由は、最も安かったからです。いらないWiiリモコンや、Wiiモーションプラスを分解するという手もあるかもしれません
MIDIコネクタ×1 MIDI用
抵抗 220Ω×1 MIDIプルアップ抵抗
LED×2 好きな色を好みに応じて
スイッチ×3 お好きなものをどうぞ
抵抗 1kΩ×4 LEDの電流制限、スイッチのプルダウンに。
ケース×1 お好きなものをどうぞ。

プログラムソース:

/* AirDrum Buccellati
 * http://d.hatena.ne.jp/karaage
 */
#define KICK_L 0  // select the input pin for the G Sensor
#define KICK_R 1  // select the input pin for the G Sensor
#define HAND_L_Z 2  // select the input pin for the G Sensor
#define HAND_L_X 3  // select the input pin for the G Sensor
#define HAND_R_Z 4  // select the input pin for the G Sensor
#define HAND_R_X 5  // select the input pin for the G Sensor
#define LED_HAT 5  // LED pin Close or Open
#define LED_STATE 11 // LED pin PWM
#define SWITCH_UP 3  // Up Switch pin
#define SWITCH_DOWN 6  // Down Switch pin
#define SWITCH_MODE 13 // changing mode switch

#define MIDI_SPEED 31250
#define MIDI_MAX 128 // midi char 128

#define SENSIVITY_HAND_Z 2
#define SENSIVITY_HAND_X 2
#define SENSIVITY_KICK 2

#define HAND_Z_THRESHOLD 15
#define HAND_X_THRESHOLD 25
#define KICK_R_THRESHOLD 15
#define KICK_L_THRESHOLD 3

// data average count
#define AVERAGE_MAX 3
// storage old data max
#define MAX_MEMORY 4
// hysteresis
#define HYSTERESIS_PLUS 50
#define HYSTERESIS_PLUS_KICK_L 3
// hysteresis
#define HYSTERESIS_MINUS 40
#define HYSTERESIS_MINUS_KICK_L 2

// hand status and distance
#define HIGH_HAT 0
//#define TAM 1
#define CYMBAL 1
#define MAX_DRUM 2

// MIDI
#define BASS 36
#define SNARE 38
#define TAM_2_MIDI 47
#define CLOSE_HAT_MIDI 42
#define OPEN_HAT_MIDI 46
#define CRASH_CYMBAL_MIDI 49

#define MIDIOUT_CH 0x99 // 10ch
#define MIDIOUT2_CH 0xc9 // 10ch

// Hat State
#define CLOSE 0
#define OPEN 1

// LED Blink
#define BLINK_PERIOD 128

// mode
#define MANUAL 0
#define AUTO 1
#define MAX_MODE 2

// max speed
#define MAX_SPEED 256

#define MAX_DRUMSETS 69

// state
int mode = MANUAL; // Manual or Auto
int drumsets = 0; // change drum set at only manual mode
int old_drumsets = drumsets; // old drumsets state(if not changed drumset, don't send midi data)
int auto_speed = 0;

int led_cont = 0; // LED Blink counter
int led_state = 0; // LED State(0:power on, 1:power off)


int margin_hand_l_x = 0;
int margin_hand_l_z = 0;
int margin_hand_r_x = 0;
int margin_hand_r_z = 0;
int margin_kick_r = 0;
int margin_kick_l = 0;

// Switch State
int sw_val_up = 0; // switch up
int old_sw_val_up = 0; // switch up(old state)
int sw_val_down = 0; // switch down
int old_sw_val_down = 0; // switch down(old state)

// sensor data
char kick_l;
char kick_r;
char hand_l_x;
char hand_l_z;
char hand_r_x;
char hand_r_z;

// old sensor data
char hand_r_z_old[MAX_MEMORY];
char hand_r_x_old[MAX_MEMORY];
char hand_l_z_old[MAX_MEMORY];
char hand_l_x_old[MAX_MEMORY];
char kick_r_old[MAX_MEMORY];
char kick_l_old[MAX_MEMORY];

// left foot state: CLOSE or OPEN
int kick_l_state = CLOSE;

// midi data
char hand_r_z_vel; // right hand velocity
char hand_r_x_vel; // right hand velocity
char hand_l_z_vel; // right hand velocity
char hand_l_x_vel; // right hand velocity
char kick_r_vel; // right hand velocity

void setup() {
  // start serial
  Serial.begin(MIDI_SPEED);
//  Serial.begin(SERIAL_SPEED);
  pinMode(LED_HAT, OUTPUT);
  pinMode(LED_STATE, OUTPUT);
//  noteOn2(0xC0, 0); // Midi 1ch
  noteOn2(MIDIOUT2_CH, 0); // Midi 10ch
}

void noteOn(char cmd, char data1, char data2) {
  Serial.print(cmd, BYTE);
  Serial.print(data1, BYTE);
  Serial.print(data2, BYTE);
}

void noteOn2(char cmd, char data) {
  Serial.print(cmd, BYTE);
  Serial.print(data, BYTE);
}

void DetectSwitchMode(){
  // detect short time push switch
  if(digitalRead(SWITCH_MODE) == HIGH){
    mode = MANUAL;
  }else{
    mode = AUTO;
  }
}

void DetectSwitchState(){
  // Switch detect
  sw_val_up = digitalRead(SWITCH_UP);
  // detect short time push switch
  if((sw_val_up == HIGH) && (old_sw_val_up == LOW)){
    if(mode == MANUAL){
      drumsets++;
      if(drumsets > MAX_DRUMSETS-1){
        drumsets = MAX_DRUMSETS-1;
      }
    }else{
      auto_speed++;
      if(auto_speed > MAX_SPEED-1){
        auto_speed = MAX_SPEED-1;
      }
    }
  }
  old_sw_val_up = sw_val_up;

  sw_val_down = digitalRead(SWITCH_DOWN);

  // detect short time push switch
  if((sw_val_down == HIGH) && (old_sw_val_down == LOW)){
    if(mode == MANUAL){
      drumsets--;
      if(drumsets < 0){
        drumsets = 0;
      }
    }else{
      auto_speed--;
      if(auto_speed < 0){
        auto_speed = 0;
      }
    }
  }
  old_sw_val_down = sw_val_down;
}

// Input Sensor data
void InputSensordata(){
  long tmp_hand_r_z=0;
  long tmp_hand_r_x=0;
  long tmp_hand_l_z=0;
  long tmp_hand_l_x=0;
  long tmp_kick_r=0;
  long tmp_kick_l=0;

  for(int cont=0; cont<AVERAGE_MAX; cont++){
    tmp_hand_r_z += analogRead(HAND_R_Z)/8;
    tmp_hand_r_x += analogRead(HAND_R_X)/8;
    tmp_hand_l_z += analogRead(HAND_L_Z)/8;
    tmp_hand_l_x += analogRead(HAND_L_X)/8;
    tmp_kick_r += analogRead(KICK_R)/8;
    tmp_kick_l += analogRead(KICK_L)/8;
  }

  hand_r_z = tmp_hand_r_z / AVERAGE_MAX;
  hand_r_x = tmp_hand_r_x / AVERAGE_MAX;
  hand_l_z = tmp_hand_l_z / AVERAGE_MAX;
  hand_l_x = tmp_hand_l_x / AVERAGE_MAX;
  kick_r = tmp_kick_r / AVERAGE_MAX;
  kick_l = tmp_kick_l / AVERAGE_MAX;
}

// Midi Out
void MidiOut(){
  // midi out
  if(kick_l_state == CLOSE){
    noteOn(MIDIOUT_CH, CLOSE_HAT_MIDI, hand_r_z_vel);
  }else{
    noteOn(MIDIOUT_CH, OPEN_HAT_MIDI, hand_r_z_vel);
  }    
  noteOn(MIDIOUT_CH, CRASH_CYMBAL_MIDI, hand_r_x_vel);
  noteOn(MIDIOUT_CH, SNARE, hand_l_z_vel);
  noteOn(MIDIOUT_CH, BASS, kick_r_vel);

  // if drumsets changed, send midi message
  if(drumsets != old_drumsets){
      noteOn2(MIDIOUT2_CH, drumsets); // Midi (drumsets)ch
      old_drumsets = drumsets;
  }
}

void ShowLED(){
  if(mode == AUTO){
    // display auto mode state(blink)
    led_cont++;
    if(led_cont > BLINK_PERIOD){
      led_cont = 0;
      led_state = 1-led_state;
      digitalWrite(LED_HAT,led_state);
    }
    // display speed state
    analogWrite(LED_STATE, auto_speed);
  }else{
    // display hat state(open or close)
    if(kick_l_state == CLOSE){
      digitalWrite(LED_HAT,LOW);
    }else{
      digitalWrite(LED_HAT,HIGH);
    }
    analogWrite(LED_STATE, drumsets);
  }
}

void CalculateData(){
  // right hand z axis
  if(hand_r_z - hand_r_z_old[MAX_MEMORY-1] > HAND_Z_THRESHOLD + margin_hand_r_z){
    hand_r_z_vel = (MIDI_MAX-hand_r_z)*SENSIVITY_HAND_Z;
    margin_hand_r_z += HYSTERESIS_PLUS;
  }else{
    hand_r_z_vel=0;
    margin_hand_r_z -= HYSTERESIS_MINUS;
    if(margin_hand_r_z < 0){
      margin_hand_r_z = 0;
    }
  }

  // left hand z axis
  if(hand_l_z - hand_l_z_old[MAX_MEMORY-1] > HAND_Z_THRESHOLD + margin_hand_l_z){
    hand_l_z_vel = (MIDI_MAX-hand_l_z)*SENSIVITY_HAND_Z;
    margin_hand_l_z += HYSTERESIS_PLUS;
  }else{
    hand_l_z_vel=0;
    margin_hand_l_z -= HYSTERESIS_MINUS;
    if(margin_hand_l_z < 0){
      margin_hand_l_z = 0;
    }
  }

  // right hand x axis
  if(hand_r_x - hand_r_x_old[MAX_MEMORY-1] > HAND_X_THRESHOLD + margin_hand_r_x){
    hand_r_x_vel = (MIDI_MAX-hand_r_x)*SENSIVITY_HAND_X;
    margin_hand_r_x += HYSTERESIS_PLUS;
  }else{
    hand_r_x_vel=0;
    margin_hand_r_x -= HYSTERESIS_MINUS;
    if(margin_hand_r_x < 0){
      margin_hand_r_x = 0;
    }
  }

  // right kick z axis
  if(kick_r - kick_r_old[MAX_MEMORY-1] > KICK_R_THRESHOLD + margin_kick_r){
    kick_r_vel = (MIDI_MAX-kick_r)*SENSIVITY_KICK;
    margin_kick_r += HYSTERESIS_PLUS;
  }else{
    kick_r_vel=0;
    margin_kick_r -= HYSTERESIS_MINUS;
    if(margin_kick_r < 0){
      margin_kick_r = 0;
    }
  }

  // left kick z axis
  if(kick_l - kick_l_old[MAX_MEMORY-1] > KICK_L_THRESHOLD + margin_kick_l){
    kick_l_state = OPEN;
    margin_kick_l += HYSTERESIS_PLUS_KICK_L;
  }else if(kick_l - kick_l_old[MAX_MEMORY-1] < -KICK_L_THRESHOLD - margin_kick_l){
    kick_l_state = CLOSE;
    margin_kick_l += HYSTERESIS_PLUS_KICK_L;
  }else{
    margin_kick_l -= HYSTERESIS_MINUS_KICK_L;
    if(margin_kick_l < 0){
      margin_kick_l = 0;
    }
  }

  // memory store
  for(int i=MAX_MEMORY-1; i>0 ; i--){
    hand_r_z_old[i] = hand_r_z_old[i-1];
    hand_r_x_old[i] = hand_r_x_old[i-1];
    hand_l_z_old[i] = hand_l_z_old[i-1];
    hand_l_x_old[i] = hand_l_x_old[i-1];
    kick_r_old[i] = kick_r_old[i-1];
    kick_l_old[i] = kick_l_old[i-1];
  }
  hand_r_z_old[0]=hand_r_z;
  hand_r_x_old[0]=hand_r_x;
  hand_l_z_old[0]=hand_l_z;
  hand_l_x_old[0]=hand_l_x;
  kick_r_old[0]=kick_r;
  kick_l_old[0]=kick_l;
}

void loop() {
  DetectSwitchMode();
  DetectSwitchState();
  ShowLED();
  InputSensordata();
  CalculateData();
  MidiOut();
}

 変数名とコメントはあまり見ないでください。特に解説はしません。MIDI信号を受ける機器の仕様に応じて、MIDIの設定は変える必要があります。運がよければ変な音くらいは出るかもしれません。

使い方:
4つの加速度センサを両手、両足に装備して、心のドラムを叩くと、熱い気持ちがMIDI信号に変換されてドラムマシンにから音がでるという仕組みです。MIDIのチャンネルは10ch(MIDIだとドラムは10chのことが多い)。押しボタンを押すことでドラムの音の切り替えができます。LEDは1つはハイハットのクローズかオープンかの表示(点灯しているとオープン)、もう1つは選択されているドラムの音に応じて明るさがかわり、なんとなく今のドラムの音がわかる仕組みです。Autoで演奏するモードも実装予定ですが予定は未定です。