Hobby Science&Experiment

愛と工作の日々

趣味でやっている工作や勉強したことのレポートです。

【Arduino MIDIドラムシーケンサ⑥】16分対応

ドラムシーケンサの続きです。8分だけではかなり単調なパターンしか演奏できないので、16部対応させてみました。好きなドラムパターンを再現できるのは楽しいですね。メトロノームの代わりに使うと楽しいです。

スケッチ

// Arduino MIDI Drum sequencer 
// Sending MIDI messages to the host PC.
// DAW: Waveform Free
//
// Author: Tara-chang
// 
// Revision History:
// Date        |    Author    |  Change
// ---------------------------------------------------
// 2021-05-09  |  Tara-chang  |  Initial Release
// 2021-05-09  |  Tara-chang  |  Added LCD and Trimpot to tempo control 
// 2021-05-16  |  Tara-chang  |  Added BUTTONS to change channel 
// 2021-05-19  |  Tara-chang  |  Sixteenth note available 


#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 16, 2);
#include <MIDI.h> 
MIDI_CREATE_DEFAULT_INSTANCE();

#include <avr/io.h>
#include <avr/interrupt.h>

#define CRUSH 49 //C#3
#define KICK 36 //C2
#define CLAP 39 //D#2
#define SNARE 38 //D2
#define RIDE 51 //D#3
#define HITOM 50 //D3
#define MIDTOM 48 //C3
#define LOWTOM 45 //A2
#define OPHIHAT 46 //A#2
#define CLHIHAT 42//F#2

byte DRUM[] = {SNARE, KICK, CLHIHAT, OPHIHAT, CRUSH, RIDE};
byte VELOCITY[] = {127, 100, 70, 70, 60, 60};
int BPMLIST[] = {175, 146, 150, 133, 150};
const byte NUM_DRUM = sizeof(DRUM)/sizeof(DRUM[0]);
const byte NUM_BEAT = 16;

boolean RTM0[NUM_DRUM][NUM_BEAT] = {
 //1   2   3   4   5   6   7   8
  {0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0},//SNARE
  {1,0,0,0,1,1,1,0,1,0,0,0,1,1,1,0},//KICK
  {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},//CLHIHAT
  {1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0},//OPHIHAT
  {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},//CRUSH
  {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}//RIDE
  };

boolean RTM1[NUM_DRUM][NUM_BEAT] = {
 //1   2   3   4   5   6   7   8
  {0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0},//SNARE
  {1,0,0,1,0,1,0,0,1,0,0,1,0,1,0,0},//KICK
  {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},//CLHIHAT  
  {1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0},//OPHIHAT
  {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},//CRUSH
  {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}//RIDE
  };

boolean RTM2[NUM_DRUM][NUM_BEAT] = {
 //1   2   3   4   5   6   7   8
  {0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0},//SNARE
  {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},//KICK
  {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},//CLHIHAT
  {1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0},//OPHIHAT  
  {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},//CRUSH
  {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}//RIDE
  };

boolean RTM3[NUM_DRUM][NUM_BEAT] = {
 //1   2   3   4   5   6   7   8
  {0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0},//SNARE
  {1,0,0,1,0,0,1,0,0,0,1,0,1,0,1,0},//KICK
  {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},//CLHIHAT
  {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},//OPHIHAT  
  {1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0},//CRUSH
  {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}//RIDE
  };

boolean RTM4[NUM_DRUM][NUM_BEAT] = {
 //1   2   3   4   5   6   7   8
  {0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0},//SNARE
  {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},//KICK
  {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},//CLHIHAT
  {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},//OPHIHAT  
  {1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0},//CRUSH
  {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}//RIDE
  };

byte nt = 0;
byte in = 0;
byte i = 0;
byte BPM = 0; //X beats/60sec = 60/X sec/beat
byte interval;// = 60/double(BPM)*(4/double(NUM_BEAT))*1000; // 

boolean changed = 1;
byte CHANNELS[] = {0, 1, 2, 3, 4};
byte BUTPIN[] =   {4, 5, 6, 8, 7};
byte LEDPIN[] =   {9,11,12,13};

const byte NUM_CHANNELS = sizeof(CHANNELS)/sizeof(CHANNELS[0]);
const byte NUM_BUTPIN = sizeof(BUTPIN)/sizeof(BUTPIN[0]);
const byte NUM_LEDPIN = sizeof(LEDPIN)/sizeof(LEDPIN[0]);

byte NEXT_CHANNEL = 1;
byte CHANNEL = 0;
byte BUTTON_INT0 = 2;
byte BUTTON_INT1 = 3;

byte firstline;
byte secondline;
byte nowchannel;

void setup()
{ 
  Serial.begin(115200);
  MIDI.begin(4); //

  for (i=0;i<NUM_BUTPIN;i++){   
    pinMode(BUTPIN[i], INPUT);
    digitalWrite(BUTPIN[i], HIGH);     
  }
  for (i=0;i<NUM_LEDPIN;i++){   
    pinMode(LEDPIN[i], OUTPUT);
    digitalWrite(LEDPIN[i], LOW);     
  }
  
  lcd.init();
  lcd.backlight();
  lcd.clear();

  pinMode(BUTTON_INT0, INPUT);
  digitalWrite(BUTTON_INT0, HIGH);    // Enable pullup resistor
  pinMode(BUTTON_INT1, INPUT);
  digitalWrite(BUTTON_INT1, HIGH); 
  
  EICRA |= (1 << ISC01);    // Trigger on falling edge
  EIMSK |= (1 << INT0);     // Enable external interrupt INT0 = pin2
  EICRA |= (1 << ISC11);    // Trigger on falling edge
  EIMSK |= (1 << INT1);     // Enable external interrupt INT1 = pin3
  sei();                    // Enable global interrupts

}

ISR(INT0_vect)//pin2
{
  BPMLIST[CHANNEL]++;
  changed =1;      
}

ISR(INT1_vect)//pin3
{
  changed =1;
  BPMLIST[CHANNEL]--;      
}


void loop(){    
  for (nt = 0; nt < NUM_BEAT; nt++){
    digitalWrite(LEDPIN[nt/(NUM_BEAT/4)], HIGH);
    for (in = 0; in < NUM_DRUM; in++){    
           
      if (GetRTM(CHANNEL, in, nt)){
      MIDI.sendNoteOn(DRUM[in],VELOCITY[in],1);  // (pitch, velocity, channel)         
      MIDI.sendNoteOff(DRUM[in],0,1);   //        
      }
    }
    NEXT_CHANNEL = Getchannel(CHANNEL);
    if((changed)){
      LCDisplay(BPMLIST[NEXT_CHANNEL],NEXT_CHANNEL);
      changed=0;
    }
    delay(interval);
    digitalWrite(LEDPIN[nt/(NUM_BEAT/4)], LOW);     
    }

  BPM = BPMLIST[NEXT_CHANNEL];
  interval = 60/double(BPM)*(4/double(NUM_BEAT))*1000;
  CHANNEL = NEXT_CHANNEL;            
}

byte Getchannel(byte oldchannel){
    for (i=0;i<NUM_BUTPIN;i++){
      if ((digitalRead(BUTPIN[i])==LOW)){
        nowchannel = CHANNELS[i];
        changed=1;
      }
      else{
      }    
    }
    return nowchannel;
}

boolean GetRTM(byte nowchannel, byte in, byte nt){
    if(nowchannel==0){return RTM0[in][nt];}
    else if(nowchannel==1){return RTM1[in][nt];}
    else if(nowchannel==2){return RTM2[in][nt];}
    else if(nowchannel==3){return RTM3[in][nt];}
    else if(nowchannel==4){return RTM4[in][nt];}
    else{return 0;}
}

void LCDisplay(byte firstline, byte secondline){
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Tempo:"+String(firstline));
    lcd.setCursor(0, 1);
    lcd.print("Ch:"+String(secondline));
}

動作

毎度ながら音小さめです。

課題

0,0,0…と数字が並んでると可読性、デバッグ性が悪いですね…。4分ごとにカッコで囲むとか出来たら良い感じだと思いますが、そこまでやるかはわかりません。32部以上とか2小節以上の対応はしたいですね。いちいちDAWを起動するのは面倒ですがギター・ベース練習用のドラムマシンとして使えそうではあります。

【Arduino MIDIドラムシーケンサ⑤】スイッチでテンポチェンジ、その他改良

前回はタクトスイッチでリズムパターン(チャンネル)の変更を行いました。テンポをトリムポットで調整するのはふら付きの問題があったため、今回はテンポをスイッチで変更できるようにしたいと思います。前回の記事で割り込みについて勉強したので、早速組み込んでみたいと思います。
jakejake.hatenablog.com

前回からの変更点まとめ

  • D2ピンとD3ピンは割り込みでテンポ変更に使用するようにした。
  • 各チャンネルのテンポをタクトスイッチで調節出来るようにした。
  • チャンネル変更用タクトスイッチの監視(Getchannel)を一小節ごとから一拍ごとに変更。
  • LCDの切り替えタイミングをテンポスイッチ変更時もしくはチャンネル変更時に変更。
  • 4分音符ごとにLEDを点灯。

コード

// Arduino MIDI Drum sequencer 
// Sending MIDI messages to the host PC.
// DAW: Waveform Free
//
// Author: Tara-chang
// 
// Revision History:
// Date        |    Author    |  Change
// ---------------------------------------------------
// 2021-05-09  |  Tara-chang  |  Initial Release
// 2021-05-09  |  Tara-chang  |  Added LCD and Trimpot to tempo control 
// 2021-05-16  |  Tara-chang  |  Added BUTTONS to change channel 
// 2021-05-17  |  Tara-chang  |  Added BUTTONS to change tempo 

#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 16, 2);
#include <MIDI.h> 
MIDI_CREATE_DEFAULT_INSTANCE();

#include <avr/io.h>
#include <avr/interrupt.h>

#define CRUSH 49 //C#3
#define KICK 36 //C2
#define CLAP 39 //D#2
#define SNARE 38 //D2
#define RIDE 51 //D#3
#define HITOM 50 //D3
#define MIDTOM 48 //C3
#define LOWTOM 45 //A2
#define OPHIHAT 46 //A#2
#define CLHIHAT 42//F#2

byte DRUM[] = {SNARE, KICK, CLHIHAT, CRUSH};
byte VELOCITY[] = {127, 100, 70, 30};
const byte NUM_DRUM = sizeof(DRUM)/sizeof(DRUM[0]);
const byte NUM_BEAT = 8;

boolean RTM0[NUM_DRUM][NUM_BEAT] = {
  {0,0,1,0,0,0,1,0},//SNARE
  {1,0,0,0,1,1,0,0},//KICK
  {1,0,1,0,1,0,1,0},//CLHIHAT
  {0,0,0,0,0,0,0,0}//RIDE
  };

boolean RTM1[NUM_DRUM][NUM_BEAT] = {
  {0,0,1,0,0,0,1,0},//SNARE
  {1,0,0,0,0,1,0,1},//KICK
  {0,1,0,1,0,1,0,1},//CLHIHAT
  {0,0,0,0,0,0,0,0}//RIDE
  };  

boolean RTM2[NUM_DRUM][NUM_BEAT] = {
  {0,0,0,0,1,0,0,0},//SNARE
  {1,1,1,1,1,1,1,1},//KICK
  {0,0,0,0,0,0,0,0},//CLHIHAT
  {1,0,0,0,1,0,0,0}//RIDE
  };

boolean RTM3[NUM_DRUM][NUM_BEAT] = {
  {0,0,1,0,0,0,1,0},//SNARE
  {1,0,1,0,1,0,1,0},//KICK
  {0,1,0,1,0,1,0,1},//CLHIHAT
  {0,0,0,0,0,0,0,0}//RIDE
  };  
  
boolean RTM4[NUM_DRUM][NUM_BEAT] = {
  {1,1,1,1,1,1,1,1},//SNARE
  {1,0,1,0,1,0,1,0},//KICK
  {0,0,0,0,0,0,0,0},//CLHIHAT
  {1,0,1,0,1,0,1,0}//RIDE
  };  

byte nt = 0;
byte in = 0;
byte i = 0;
int BPM = 0; //X beats/60sec = 60/X sec/beat
int interval;// = 60/double(BPM)*(4/double(NUM_BEAT))*1000; // 

boolean changed = 1;
int INTERVALS[] = {150, 170, 200, 150};
byte CHANNELS[] = {0,1,2,3};
byte LEDPIN[] = {9, 11, 12, 13};
byte BUTPIN[] = {4, 5, 6, 8};

const byte NUM_CHANNELS = sizeof(CHANNELS)/sizeof(CHANNELS[0]);
const byte NUM_BUTPIN = sizeof(BUTPIN)/sizeof(BUTPIN[0]);
const byte NUM_LEDPIN = sizeof(LEDPIN)/sizeof(LEDPIN[0]);

byte NEXT_CHANNEL = 1;
byte CHANNEL = 0;
byte BUTTON_INT0 = 2;
byte BUTTON_INT1 = 3;

byte firstline;
byte secondline;
byte nowchannel;

void setup()
{ 
  Serial.begin(115200);
  MIDI.begin(4); //

  for (i=0;i<NUM_BUTPIN;i++){   
    pinMode(BUTPIN[i], INPUT);
    digitalWrite(BUTPIN[i], HIGH);     
  }
  for (i=0;i<NUM_LEDPIN;i++){   
    pinMode(LEDPIN[i], OUTPUT);
    digitalWrite(LEDPIN[i], LOW);     
  }
  
  lcd.init();
  lcd.backlight();
  lcd.clear();

  pinMode(BUTTON_INT0, INPUT);
  digitalWrite(BUTTON_INT0, HIGH);    // Enable pullup resistor
  pinMode(BUTTON_INT1, INPUT);
  digitalWrite(BUTTON_INT1, HIGH); 
  
  EICRA |= (1 << ISC01);    // Trigger on falling edge
  EIMSK |= (1 << INT0);     // Enable external interrupt INT0 = pin2
  EICRA |= (1 << ISC11);    // Trigger on falling edge
  EIMSK |= (1 << INT1);     // Enable external interrupt INT1 = pin3
  sei();                    // Enable global interrupts

}

ISR(INT0_vect)//pin2
{
  INTERVALS[CHANNEL]++;
  changed =1;      
}

ISR(INT1_vect)//pin3
{
  changed =1;
  INTERVALS[CHANNEL]--;      
}


void loop(){    
  for (nt = 0; nt < NUM_BEAT; nt++){
    digitalWrite(LEDPIN[nt/2], HIGH);
    for (in = 0; in < NUM_DRUM; in++){    
           
      if (GetRTM(CHANNEL, in, nt)){
      MIDI.sendNoteOn(DRUM[in],VELOCITY[in],1);  // (pitch, velocity, channel)         
      MIDI.sendNoteOff(DRUM[in],0,1);   //        
      }
    }
    NEXT_CHANNEL = Getchannel(CHANNEL);
    if((changed)){
      LCDisplay(INTERVALS[NEXT_CHANNEL],NEXT_CHANNEL);
      changed=0;
    }
    delay(interval);
    digitalWrite(LEDPIN[nt/2], LOW);     
    }

  BPM = INTERVALS[NEXT_CHANNEL];
  interval = 60/double(BPM)*(4/double(NUM_BEAT))*1000;
  CHANNEL = NEXT_CHANNEL;            
}

byte Getchannel(byte oldchannel){
    for (i=0;i<NUM_BUTPIN;i++){
      if ((digitalRead(BUTPIN[i])==LOW)){
        nowchannel = CHANNELS[i];
        changed=1;
      }
      else{
      }    
    }
    return nowchannel;
}

boolean GetRTM(byte nowchannel, byte in, byte nt){
    if(nowchannel==0){return RTM0[in][nt];}
    else if(nowchannel==1){return RTM1[in][nt];}
    else if(nowchannel==2){return RTM2[in][nt];}
    else if(nowchannel==3){return RTM3[in][nt];}
    else{return 0;}
}

void LCDisplay(byte firstline, byte secondline){
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Tempo:"+String(firstline));
    lcd.setCursor(0, 1);
    lcd.print("Ch:"+String(secondline));
}

動作

イイ感じに動いております。狙った通りのものが出来ると楽しいですね。

課題

・テンポ調整は1ずつ行いたいのに、結構な割合で2以上変化してしまう。
・16分に対応していない。

Arduinoで割り込みISR()を使うコード例(2)ボタン2つの押下監視

前回はISR(割り込みルーチン)を使ってメインループに割り込んでボタンのプッシュを検知することが出来ましたが、一つのボタンにしか対応していない問題がありました。今回はISR()関数を2つにして2つのボタンに対応させることが出来たので記録しておきます。
jakejake.hatenablog.com

配線

・D2ピン、D3ピンにタクトスイッチ
・D9ピンにLEDを接続しています(なくてもOK)。

スケッチ

ISR(INT0_vect)がデジタルピン2を、ISR(INT1_vect)がデジタルピン3を検出する関数?です。またvoid setup内で3pinを有効にしています。1ボタンのプログラムの類推で書いてみたのですが、動作は良好そうでした。

#include <avr/io.h>
#include <avr/interrupt.h>

byte ledPin = 9;
byte BUTTON1 = 2;
byte BUTTON2 = 3;
byte CHANNEL;

void setup(void)
{
  Serial.begin( 9600 );
  pinMode(BUTTON1, INPUT);
  digitalWrite(BUTTON1, HIGH);    // Enable pullup resistor
  pinMode(BUTTON2, INPUT);
  digitalWrite(BUTTON2, HIGH); 
  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, LOW);
  
  EICRA |= (1 << ISC01);    // Trigger on falling edge
  EIMSK |= (1 << INT0);     // Enable external interrupt INT0 = pin2
  EICRA |= (1 << ISC11);    // Trigger on falling edge
  EIMSK |= (1 << INT1);     // Enable external interrupt INT1 = pin3
  sei();                    // Enable global interrupts
}

ISR(INT0_vect)//pin2
{
  CHANNEL = 2;
  Serial.println("pin2:Interrupted!");
}

ISR(INT1_vect)//pin3
{
  CHANNEL = 3;
  Serial.println("pin3:Interrupted!");
}

void loop(void)
{
    analogWrite(ledPin, 30);
    delay(500);  
    analogWrite(ledPin, 255);
    delay(500);
    Serial.println(CHANNEL);
    CHANNEL=1;
}

動作

LEDの明滅1セットごとにCHANNEL値が出力されていますが、ボタンを押すと割り込みが発生し、押したボタンに応じてCHANNELに2もしくは3が代入されています。
f:id:tara-chang:20210516163554p:plain

【Arduino MIDIドラムシーケンサ④】スイッチでリズムパターン切り替え

前回作成したドラムシーケンサでは1パターンのリズムしか演奏できませんでした。今回はタクトスイッチを使ってチャンネル1~4まで切り替えることで4種類のリズムパターンを演奏できるようにしてみました。
jakejake.hatenablog.com

配線

デジタルピンの3, 4, 5, 8にタクトスイッチを接続しました。タクトスイッチを押すとGNDにつながる(LOWになる)ようにします。

コード

主な追加個所はbyte Getchannelとboolean GetRTMの部分です。byte Getchannelはタクトスイッチの押下を監視し、boolean GetRTMはチャンネル、拍、楽器ごとのドラムパターンを出力します。

// Arduino MIDI Drum sequencer 
// Sending MIDI messages to the host PC.
// DAW: Waveform Free
//
// Author: Tara-chang
// 
// Revision History:
// Date        |    Author    |  Change
// ---------------------------------------------------
// 2021-05-09  |  Tara-chang  |  Initial Release
// 2021-05-09  |  Tara-chang  |  Added LCD and Trimpot to tempo control 
// 2021-05-16  |  Tara-chang  |  Added BUTTONS to change channel 

#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 16, 2);
#include <MIDI.h> 
MIDI_CREATE_DEFAULT_INSTANCE();

#define CRUSH 49 //C#3
#define KICK 36 //C2
#define CLAP 39 //D#2
#define SNARE 38 //D2
#define RIDE 51 //D#3
#define HITOM 50 //D3
#define MIDTOM 48 //C3
#define LOWTOM 45 //A2
#define OPHIHAT 46 //A#2
#define CLHIHAT 42//F#2

int DRUM[] = {SNARE, KICK, CLHIHAT, CRUSH};
const byte NUM_DRUM = sizeof(DRUM)/sizeof(DRUM[0]);
const byte NUM_BEAT = 8;

boolean RTM[NUM_DRUM][NUM_BEAT] = {
  {0,0,1,0,0,0,1,0},//SNARE
  {1,1,0,0,1,1,0,0},//KICK
  {1,1,1,1,1,1,1,1},//CLHIHAT
  {1,0,0,0,1,0,0,0}//RIDE
  };

boolean RTM2[NUM_DRUM][NUM_BEAT] = {
  {0,1,0,1,0,1,0,1},//SNARE
  {1,0,0,0,1,1,1,0},//KICK
  {1,0,1,0,1,0,1,0},//CLHIHAT
  {1,0,0,0,1,0,0,0}//RIDE
  };

boolean RTM3[NUM_DRUM][NUM_BEAT] = {
  {0,0,0,0,1,0,0,0},//SNARE
  {1,1,1,1,1,1,1,1},//KICK
  {0,0,0,0,0,0,0,0},//CLHIHAT
  {1,0,0,0,1,0,0,0}//RIDE
  };

boolean RTM4[NUM_DRUM][NUM_BEAT] = {
  {1,1,1,1,1,1,1,1},//SNARE
  {1,0,1,0,1,0,1,0},//KICK
  {0,0,0,0,0,0,0,0},//CLHIHAT
  {1,0,1,0,1,0,1,0}//RIDE
  };  
  
boolean RTM5[NUM_DRUM][NUM_BEAT] = {
  {1,1,1,1,1,1,1,1},//SNARE
  {1,0,1,0,1,0,1,0},//KICK
  {0,0,0,0,0,0,0,0},//CLHIHAT
  {1,0,1,0,1,0,1,0}//RIDE
  };  

byte nt = 0;
byte in = 0;
byte i = 0;
int BPM = 0; //X beats/60sec = 60/X sec/beat
int interval = 295;//60/double(BPM)*(NUM_BEAT/4)*1000; // 

boolean changed = 1;
int trimpot = 300;
int now_trimpot = 300;
#define INT_MIN 200
#define INT_MAX 1000
#define TRM_MIN 0 
#define TRM_MAX 1023
byte LEDPIN[] = {11, 12, 13, 10};
byte BUTPIN[] = {3, 4, 5, 8};
byte NUM_BUTPIN = sizeof(BUTPIN)/sizeof(BUTPIN[0]);
byte NUM_LEDPIN = sizeof(LEDPIN)/sizeof(LEDPIN[0]);
byte CHANNELS[] = {1,2,3,4};
byte NUM_CHANNELS = sizeof(CHANNELS)/sizeof(CHANNELS[0]);
byte NOW_CHANNEL = 1;
byte CHANNEL = 1;
byte nowchannel;

void setup()
{ 
  for (i=0;i<NUM_BUTPIN;i++){   
    pinMode(BUTPIN[i], INPUT);
    digitalWrite(BUTPIN[i], HIGH);     
  }
  for (i=0;i<NUM_LEDPIN;i++){   
    pinMode(LEDPIN[i], OUTPUT);
    digitalWrite(LEDPIN[i], LOW);     
  }
  
  Serial.begin(115200);
  MIDI.begin(4); //
  lcd.init();
  lcd.backlight();
  lcd.clear();
}

void loop(){

  NOW_CHANNEL = Getchannel(CHANNEL);  
  for (nt = 0; nt < NUM_BEAT; nt++){
    for (in = 0; in < NUM_DRUM; in++){    
      //String text = "(in,nt)=("+String(in)+","+String(nt)+")"+String(RTM[in][nt]);
           
      if (GetRTM(CHANNEL, in, nt)){
      MIDI.sendNoteOn(DRUM[in],127,1);  // (pitch, velocity, channel)         
      MIDI.sendNoteOff(DRUM[in],0,1);   //        
      }
    }
    delay(now_trimpot*(4/double(NUM_BEAT))); 
    }

    now_trimpot = map(analogRead(0), TRM_MAX, TRM_MIN, INT_MAX, INT_MIN);
    if ((now_trimpot!= trimpot)||(CHANNEL!=NOW_CHANNEL)){  
      trimpot = now_trimpot;
      CHANNEL = NOW_CHANNEL;
      changed = 1;
    }
    else{
      //Serial.println("trimpot changed");
    }         

    if(changed){
        BPM = 1/double(trimpot)*60*1000;
        lcd.clear();
        lcd.setCursor(0, 0);
        lcd.print(BPM);
        lcd.setCursor(0, 1);
        lcd.print(CHANNEL);
        changed=0;
    }
      
//  OLD_CHANNEL = NOW_CHANNEL;
}

byte Getchannel(byte oldchannel){
    nowchannel = oldchannel;
    for (i=0;i<NUM_BUTPIN;i++){
      if ((digitalRead(BUTPIN[i])==LOW)){
        nowchannel = CHANNELS[i];
      }
      else{
        //nowchannel = oldchannel;
      }    
    }
    return nowchannel;
}

boolean GetRTM(byte nowchannel, byte in, byte nt){
    if(nowchannel==1){
      return RTM[in][nt];
    }
    else if(nowchannel==2){
      return RTM2[in][nt];
    }
    else if(nowchannel==3){
      return RTM3[in][nt];
    }
    else if(nowchannel==4){
      return RTM4[in][nt];
    }
    else{
      return RTM[in][nt]; 
    }
}

課題

・トリムポットのAnalogRead変動によりBPMが微妙にふら付いてしてしまうので変更必要。
・各チャンネルごとのBPMを別々に設定したい。
・16分音符に増量したほうがメタル・パンク的には楽しそう。
・音がショボい。ドラムプラグインをもうちょっと良いの使いたいかも。

【Arduino MIDIドラムシーケンサ③】LCD追加とトリムポットでBPM変更

前回作成したドラムシーケンサBPMが固定であったため、BPMをトリムポットによって調整する機能を付けました。またLCD(液晶ディスプレイ)を追加することで現在のBPMを確認できるようにしました。

配線

A0:トリムポット(50kΩ等)
A5:LCD1602のSCL端子
A4:LCD1602のSDA端子
LCD1602には5Vの電源を供給します。写真では関係ないものもブレッドボードに載ってます(^^;)
f:id:tara-chang:20210516115137p:plain

コード

BPMは拍と拍の間のインターバル(delay)で調整します。トリムポットのAnalogRead値(0~1023)をインターバルの値(200ms~1000ms)でmapを使用して規格化しています。

// Arduino MIDI Drum sequencer 
// Sending MIDI messages to the host PC.
// DAW: Waveform Free
//
// Author: Tara-chang
// 
// Revision History:
// Date        |    Author    |  Change
// ---------------------------------------------------
// 2021-05-09  |  Tara-chang  |  Initial Release
// 2021-05-09  |  Tara-chang  |  Added LCD and Trimpot to tempo control 

#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 16, 2);
#include <MIDI.h> 
MIDI_CREATE_DEFAULT_INSTANCE();

#define CRUSH 49 //C#3
#define KICK 36 //C2
#define CLAP 39 //D#2
#define SNARE 38 //D2
#define RIDE 51 //D#3
#define HITOM 50 //D3
#define MIDTOM 48 //C3
#define LOWTOM 45 //A2
#define OPHIHAT 46 //A#2
#define CLHIHAT 42//F#2

int DRUM[] = {SNARE, KICK, CLHIHAT, RIDE};
const byte NUM_DRUM = sizeof(DRUM)/sizeof(DRUM[0]);
const byte NUM_BEAT = 8;
boolean RTM[NUM_DRUM][NUM_BEAT] = {
  {0,0,1,0,0,0,1,0},//SNARE
  {1,1,0,0,1,1,0,0},//KICK
  {1,1,1,1,1,1,1,1},//CLHIHAT
  {1,0,0,0,1,0,0,0}//RIDE
  };
//in order of SNARE, KICK, CLAP, CRUSH
byte nt = 0;
byte in = 0;
int BPM = 0; //X beats/60sec = 60/X sec/beat
int interval = 295;//60/double(BPM)*(NUM_BEAT/4)*1000; // 

boolean changed = 1;
int trimpot = 300;
int now_trimpot = 300;
#define INT_MIN 200
#define INT_MAX 1000
#define TRM_MIN 0 
#define TRM_MAX 1023

void setup()
{   
  Serial.begin(115200);
  MIDI.begin(4); //
  lcd.init();
  lcd.backlight();
  lcd.clear();
  pinMode(0, INPUT);
}

void loop(){

  for (nt = 0; nt < NUM_BEAT; nt++){
    for (in = 0; in < NUM_DRUM; in++){    
      //String text = "(in,nt)=("+String(in)+","+String(nt)+")"+String(RTM[in][nt]);
      
      if (RTM[in][nt]){
      MIDI.sendNoteOn(DRUM[in],127,1);  // (pitch, velocity, channel)         
      MIDI.sendNoteOff(DRUM[in],0,1);   //        
      }
    }
    delay(now_trimpot*(4/double(NUM_BEAT))); 
    }

    now_trimpot = map(analogRead(0), TRM_MAX, TRM_MIN, INT_MAX, INT_MIN);
    if (now_trimpot == trimpot){  
    }
    else{
      trimpot = now_trimpot;
      changed = 1;
      //Serial.println("trimpot changed");
    }         

    if(changed){
        BPM = 1/double(trimpot)*60*1000;
        lcd.clear();
        lcd.setCursor(0, 0);
        lcd.print("BPM");
        lcd.setCursor(0, 1);
        lcd.print(BPM);
        changed=0;
    }  

}

動作

課題

・ワンパターンのリズムしか演奏できないのは悲しいので、ボタンで切り替えられるようにしたいと思います。
・トリムポットのAnalogRead値にややふら付きがあるのでここも改善必要です。

Arduinoで割り込みISR()を使うコード例(1)ボタン押下監視を割り込みで

Arduinoでタクトスイッチの押下を監視したいのですが、一回のloopが長い場合、degitalread(polling)の丁度いいタイミングでスイッチを押さないといけなかったりします。先日作ったドラムマシンが一回のループが長い典型例(1秒越え)でした。
そこでInterruptというのを使うと、メインのループに割り込んだ操作が行えるようなのでサンプルコード試してみました。あまり私にとって分りやすい情報が多くなく、現時点では仕組みをあまり理解しておりませんが、動いたので記しておきます。

Arduino側の配線

9pin、13pinにLEDを接続、2pinにタクトスイッチを接続しています。

コードと動作

こちらのコードを一部改変しました。メインのloop内ではLEDが500ms周期で明滅します。一方でISR(INT0_vect)という関数?が定義されており、メインループとは独立にボタンが押された瞬間に実行されます。ISRが実行された場合13pin(ledPin_ISR)のON/OFFが切り替わり、ついでにCHANNEL=1が代入されます。

#include <avr/io.h>
#include <avr/interrupt.h>

byte ledPin = 9;
byte ledPin_ISR = 13;
byte BUTTON = 2;
boolean CHANNEL =0;

void setup(void)
{
  Serial.begin( 9600 );
  pinMode(BUTTON, INPUT);
  digitalWrite(BUTTON, HIGH);    // Enable pullup resistor
  pinMode(ledPin_ISR, OUTPUT);
  digitalWrite(ledPin_ISR, LOW);
  
  EICRA |= (1 << ISC01);    // Trigger on falling edge
  EIMSK |= (1 << INT0);     // Enable external interrupt INT0
  sei();                    // Enable global interrupts
}

// Interrupt Service Routine attached to INT0 vector
ISR(INT0_vect)
{
  digitalWrite(ledPin_ISR, !digitalRead(ledPin_ISR));    // Toggle LED on pin 13
  CHANNEL = 1;
  Serial.println("Interrupted!");
}

void loop(void)
{
    analogWrite(ledPin, 30);
    delay(500);  
    analogWrite(ledPin, 255);
    delay(500);
    Serial.println(CHANNEL);
    CHANNEL=0;
}

疑問と調べたこと

このコードの中で、どの部分が2pinをISRの実行トリガーに指定しているのか調べてみました。怪しい部分は以下の部分です。

  EICRA |= (1 << ISC01);    // Trigger on falling edge
  EIMSK |= (1 << INT0);     // Enable external interrupt INT0
  sei();                    // Enable global interrupts

ググってみたところ、
こちらのサイトにて説明が見つかりました。とりあえず2pinと3pinが使用可能なようですね。

  • EICRAは、割り込みの検出方法を制御
  • EIMSK(外部割り込みマスクレジスタ)は、外部割り込みを有効または無効にする
  • Arduino Unoでは、INT0(ピン2)とINT1(ピン3)を使用できる
  • ISC01とISC00はINT0(ピン2)の状態を制御
  • ISC11とISC10の組み合わせは、INT1(ピン3)の状態を制御

課題

・複数ボタンを監視したい場合の方法が分からない。
・アナログピンでも使えるのかな?

Arduino MIDIドラムシーケンサ(Drum Sequencer)

前回の記事でArduino MIDI libraryを使ってDAWのドラムを鳴らしました。今度はそこにプログラミングを加えて、一定のドラムパターンで演奏させてみたいと思います。多分これがドラムシーケンサーってやつなんじゃないでしょうか。
jakejake.hatenablog.com

下準備

前回の記事(上記リンク先)参照。DAWにはWaveform Freeを使用しました。

コード

{0,0,1,0}とかの部分は、4/4拍子で3拍目のみヒットがあることを示してます。16分音符にする場合は{0,0,・・・}を16個のアレーにすればよいわけです。
f:id:tara-chang:20210509182550p:plain
int DRUM[]の中身の楽器名を変えることで、ハイハットやライドなどを加えることもできます。BPMを指定すると一拍当たりの秒数(interval)に換算し、sleep(interval)に代入されます。あと、かっこいいのでコメント行を付けてみました(*ノωノ)

// Arduino MIDI Drum sequencer 
// Sending MIDI messages to the host PC.
// DAW: Waveform Free
//
// Author: Tara-chang
// 
// Revision History:
// Date        |    Author    |  Change
// ---------------------------------------------------
// 2021-05-09  |  Tara-chang  |  Initial Release

#include <MIDI.h> 
MIDI_CREATE_DEFAULT_INSTANCE();

#define CRUSH 49 //C#3
#define KICK 36 //C2
#define CLAP 39 //D#2
#define SNARE 38 //D2
#define RIDE 52 //D#3
#define HITOM 50 //D3
#define MIDTOM 48 //C3
#define LOWTOM 45 //A2
#define OPHIHAT 46 //A#2
#define CLHIHAT 42//F#2

int DRUM[] = {SNARE, KICK, CLAP, CRUSH};
const byte NUM_DRUM = sizeof(DRUM)/sizeof(DRUM[0]);
const byte NUM_BEAT = 4;
boolean RTM[NUM_DRUM][NUM_BEAT] = {
  {0,0,1,0},//SNARE
  {1,0,1,0},//KICK
  {1,1,1,1},//CLAP
  {1,0,0,0}//CRUSH
  };
//in order of SNARE, KICK, CLAP, CRUSH
byte nt = 0;
byte in = 0;
int BPM = 200; //X beats/60sec = 60/X sec/beat
int interval = 60/double(BPM)*(NUM_BEAT/4)*1000; // 

void setup()
{   
  Serial.begin(115200);
  MIDI.begin(4); 
}

void loop(){
  for (nt = 0; nt < 4; nt++){
    for (in = 0; in < 4; in++){    
      //String text = "(in,nt)=("+String(in)+","+String(nt)+")"+String(RTM[in][nt]);     
      if (RTM[in][nt]){
      MIDI.sendNoteOn(DRUM[in],127,1);  // ノートオン(pitch, velocity, channel)         
      MIDI.sendNoteOff(DRUM[in],0,1);   // ノートオフ       
      }
    }
    delay(interval);
    }
}

動作確認

ばっちりでした。今回は自分で全部書いたのですが、思ったように動くと嬉しいもんですね。
変数型の定義などC言語の勉強にもなりました。