#やりたいこと
は
- analogRead()した値の平均を返す関数
- 値が変更されたかを返す関数
を作って、可変抵抗を動かした時だけMIDI CCを出力する、ということです。
analogRead()用の関数作ってみた4の続きです。
前の記事でだいぶ見易くなってやりたい事もできてるんで、いいっちゃいいんですけど。
#見直してみたら,やっぱりダサい
ところが残っていてお恥しいかぎりです。
- 同じ式
foo = ++foo % bar
が複数箇所で使われている!関数にくくりださなくちゃ -
break;
でforループを抜ける?なんか答が出たってことだからそこは関数でしょ
そうしたら(うすうす気がついてはいたのですが)、すごいH本に出てくるsuccとかmodとかelemに見えてきました。
#そっちに寄せた感じで書き直してみた
のがこれです。
#include <MIDIUSB.h>
int mod_succ(int x,int MOD){ //x+1をMODで割った余りを返す
return (x+1) % MOD;
}
boolean elem(int x,const int ary[],int arySize){ //xはary[]に含まれているか?
for(int i=0;i < arySize;i++){
if(x == ary[i])return true;
}
return false;
}
mod_succ()は文字通りsuccしてmodする関数。
elem()はhaskellのelemにがんばって寄せてみました。
class GetAverage{ //関数GetAverageクラスを作る
static const int MOD = 8; //クラス変数 値の履歴の数
private: //インスタンスに内包される変数たち
int history[MOD]; //値の履歴
int sum; //合計
int index; //値を出し入れする場所のインデックス
public:
GetAverage(){ // コンストラクタ
history[MOD] = {}; //配列を0で初期化するにはこうだそうな
sum = 0; // 0で初期化
index = 0;
}
int operator()(int newVal){ //かっこ演算子()の(多重?)定義
sum += ( newVal - history[index] ); //新しい値と一番古い値の差分を合計に加算
history[index] = newVal; //一番古い値を捨て新しい値を登録
index = mod_succ(index, MOD); //インデックスを一つすすめる
return sum / MOD; //平均を返す
}
};
class IsChanged{
static const int MOD = 4;
private:
int history[MOD];
int index;
public:
IsChanged(){
history[MOD] = {};
index = 0;
}
boolean operator()(int newVal){ //かっこ演算子()の(多重?)定義
const boolean ANSWER = !elem(newVal, history, MOD); //新しい値が履歴に含まれていなければ真
history[index] = newVal; //一番古い値を捨て新しい値を登録
index = mod_succ(index, MOD); ////インデックスを一つ進める
return ANSWER; //答を返す
}
};
mod_succ()に関しては、見た目が変わる程度でした。
elem()はかなり効果ありです。
- 制御構造がなくなって(隠蔽されて)見易くなった
- 制御構造がシンプルになった break;がなくなった
- 値の入れ替えがなくなったので返り値がconstで良くなった
関数オブジェクトGetAverage,IsChangedが、同じような動作
- 引数と内部に記憶された配列から返り値を計算し
- 引数を配列のある場所に保存し
- その場所をひとつすすめ
- 値を返す
をしているというのがより明確になるような気がします。
(っていうか、こう書き直してみて気がついた...)
使い方は前と同じです。必要な数だけインスタンス化して使います。
GetAverage getAverage[4];
IsChanged isChanged[4];
const int ANAPIN[4]={0,1,2,3};
const byte CHANNEL = 0;
const byte CC[4] ={25,26,27,28};
void setup() {
Serial.begin(115200);
}
void loop() {
int temp;
for(int i=0;i < 4; i++){
if( isChanged[i]( temp = getAverage[i]( analogRead(ANAPIN[i]) >> 3 ))){
controlChange(CHANNEL,CC[i],temp);
MidiUSB.flush();
};
delay(10);
};
}
//controll value or velocity
// First parameter is the event type (0x0B = control change).
// Second parameter is the event type, combined with the channel.
// Third parameter is the control number number (0-119).
// Fourth parameter is the control value (0-127).
void controlChange(byte channel, byte control, byte value) {
midiEventPacket_t event = {0x0B, 0xB0 | channel, control, value};
MidiUSB.sendMIDI(event);
}
#まとめ
Arduinoでも関数でけっこういける。かな...
あと、配列まわりが難しいです。
- 要素数は固定。変更不可。
- 作る前に要素数がわかってないといけない。定数かstaticかenum(か#define)。
- 関数に渡すときは配列名と要素数を渡す。関数側で要素数が調べられないため。
だそうです。