ちょっとご同業の方以外には伝わりにくい内容ですが、一応書きとめておきます。
動機
デジタルの音響卓を使っているのですが、ライブ中に...
こんなことがしたい
1. ある一つのチャンネルのPANを、一瞬で振り切ってしばらくしたらまた一瞬でセンターに戻す
これは何とかできます。そのチャンネルを選択しておいてPANノブを回せばよいだけなので。
次が面倒(というか手動では不可能)
2. となりあった二つのチャンネルのPANを、一瞬で同時に左右にいい感じに広げしばらくしたらまた一瞬でセンターに戻す
なぜ手動では無理なのかというと、これをやろうとすると
- セレクトスィッチを押して一方のチャンネルを選択する
- PANノブを回す
- もう一方のチャンネルのセレクトスィッチを押して選択する
- PANノブを回す
という過程を踏まねばならず、同時には無理。
アナログ卓だと両手でくいっとやれば済む話なんですけど...
デジタル卓なので、シーンを組んでオートメーションができるのですが
この卓は...
PANだけを対象にしたシーンは組めない
らしいです。
しょうがないので自作のMIDIコントローラー(中身はDuemilanove)をつなげて操作することにしました。
この卓は、卓の設定で外部からのMIDIコントロールチェンジで操作することが可能になります。
Ableton Live用のポン出し機を流用しています。スケッチもそれ用の焼き直しです。
やりたいことは:
- 下矢印を押すとCC#109 63,CC#114 63,CC#115 63 を送信
- 右矢印を押すとCC#109 127 を送信
- 左矢印、上矢印を押すとCC#114 47,CC#115 79 を送信
という単純なものです。
Arduinoだったら市販のMIDIコントローラーと違ってひとつのコントロールから複数のメッセージを送信できるはず、と思って始めたんだけど...
あれ? オブジェクト毎に要素数の違う配列をメンバにする?どうやるんだっけ?
C++のお作法がよくわかってない...
しばらくArduinoご無沙汰だったんでプチはまりました。
キモは配列のポインタと配列の要素数を組にしてやりとりすることだそうな。
その辺だけ抜粋すると:
// MIDIコントロールチェンジの構造体。
struct MIDICC{
const int CH;
const int NUM;
const int VAL;
constexpr MIDICC(int ch, int num, int val):CH(ch), NUM(num), VAL(val){}
};
// ボタンのメンバにするコントロールチェンジの配列
constexpr MIDICC CCS_0[] { MIDICC(0, 109, 63)
, MIDICC(0, 114, 63)
, MIDICC(0, 115, 63)
};
constexpr MIDICC CCS_1[] { MIDICC(0, 109, 127) };
constexpr MIDICC CCS_2[] { MIDICC(0, 114, 47)
, MIDICC(0, 115, 79)
};
// 配列の要素数を返す関数。
template<class T, size_t N> constexpr size_t countof(const T (&)[N])
{ return N; };
// ボタンのメンバにするコントロールチェンジの配列の要素数。
constexpr size_t N_CCS_0 ( countof(CCS_0) );
constexpr size_t N_CCS_1 ( countof(CCS_1) );
constexpr size_t N_CCS_2 ( countof(CCS_2) );
// ボタン構造体。
struct Button{
const int PIN;
const MIDICC* CCS; // 配列のポインタ
const size_t N; // 配列の要素数
Debounce debounce;
IsRaised isRaised;
IsDropped isDropped;
Button(const int pin, const MIDICC* ccs,const size_t n ) : PIN(pin)
, CCS(ccs)
, N(n)
, debounce()
, isRaised()
, isDropped(){};
};
// ボタンの配列。
Button BUTTONS[ NUM_BUTTONS ] { Button( IN_PINS[0], CCS_0, N_CCS_0)
, Button( IN_PINS[1], CCS_2, N_CCS_2)
, Button( IN_PINS[2], CCS_2, N_CCS_2)
, Button( IN_PINS[3], CCS_1, N_CCS_1)
};
MIDIコントロールチェンジの配列を作って、ポインタと要素数を引数としてButtonオブジェクト生成時に渡しています。
元スケッチではそこはCCナンバー一個だけを渡していました。
できたスケッチ
constexpr int NUM_BUTTONS ( 4 );
constexpr int LED_PIN ( 13 );
constexpr int IN_PINS[ NUM_BUTTONS ] { 4, 5, 6, 7 };
constexpr auto GND_PIN ( 2 );
// MIDIコントロールチェンジの構造体。
struct MIDICC{
const int CH;
const int NUM;
const int VAL;
constexpr MIDICC(int ch, int num, int val):CH(ch), NUM(num), VAL(val){}
};
// ボタンのメンバにするコントロールチェンジの配列
constexpr MIDICC CCS_0[] { MIDICC(0, 109, 63)
, MIDICC(0, 114, 63)
, MIDICC(0, 115, 63)
};
constexpr MIDICC CCS_1[] { MIDICC(0, 109, 127) };
constexpr MIDICC CCS_2[] { MIDICC(0, 114, 47)
, MIDICC(0, 115, 79)
};
// 配列の要素数を返す関数。
template<class T, size_t N> constexpr size_t countof(const T (&)[N])
{ return N; };
// ボタンのメンバにするコントロールチェンジの配列の要素数。
constexpr size_t N_CCS_0 ( countof(CCS_0) );
constexpr size_t N_CCS_1 ( countof(CCS_1) );
constexpr size_t N_CCS_2 ( countof(CCS_2) );
// ボタンのメンバにするデバウンスのクロージャー。
class Debounce{
static constexpr long RATE = 12;
static constexpr long THRESHOLD_L = 10;
static constexpr long THRESHOLD_H = 90;
long innerMul100;
bool debouncedValue;
public:
Debounce():innerMul100( 100 ), debouncedValue( true ){};
auto operator()( const bool& CURRENT_RAW )->bool{
innerMul100 = RATE * CURRENT_RAW + (100 - RATE) * innerMul100 / 100;
if( debouncedValue ) {
if( innerMul100 < THRESHOLD_L ) return debouncedValue = false;
return true;
}
if( innerMul100 > THRESHOLD_H ) return debouncedValue = true;
return false;
};
};
// ボタンが離されたか?のクロージャー。ボタンのメンバにする。
class IsRaised{
bool previousValue;
public:
IsRaised():previousValue ( false ) {}
auto operator()(const bool DATA)->bool{
const bool RESULT ( ! previousValue && DATA );
previousValue = DATA;
return RESULT;
}
};
// ボタンが押されたか?のクロージャー。ボタンのメンバにする。
class IsDropped{
bool previousValue;
public:
IsDropped():previousValue ( false ) {}
auto operator()(const bool DATA)->bool{
const bool RESULT ( previousValue && ! DATA );
previousValue = DATA;
return RESULT;
}
};
// ボタン構造体。
struct Button{
const int PIN;
const MIDICC* CCS;
const size_t N;
Debounce debounce;
IsRaised isRaised;
IsDropped isDropped;
Button(const int pin, const MIDICC* ccs,const size_t n ) : PIN(pin)
, CCS(ccs)
, N(n)
, debounce()
, isRaised()
, isDropped(){};
};
// ボタンの配列。
Button BUTTONS[ NUM_BUTTONS ] { Button( IN_PINS[0], CCS_0, N_CCS_0)
, Button( IN_PINS[1], CCS_2, N_CCS_2)
, Button( IN_PINS[2], CCS_2, N_CCS_2)
, Button( IN_PINS[3], CCS_1, N_CCS_1)
};
// 一個のコントロールチェンジを送信する。
auto txMidiCC (
[](const MIDICC& CC){
//const auto MESSAGE ( 176 + CC.CH );
Serial.write( 176 + CC.CH );
Serial.write( CC.NUM );
Serial.write( CC.VAL );
}
);
// コントロールチェンジの配列を取り、すべて送信する。
auto txMidiCCS (
[](const MIDICC* ccs, const size_t& n){
for (size_t i = 0; i < n; i++){
txMidiCC( ccs[i] );
}
}
);
// ボタンとデータをとり、ボタンが押されたときに何かして、データを返す。
auto doWhenDropped (
[]( Button& b, const bool& DATA ){
if( b.isDropped( DATA ) ){
txMidiCCS( b.CCS, b.N);
digitalWrite( LED_PIN, HIGH );
return DATA;
}
return DATA;
}
);
// ボタンとデータをとり、ボタンが離されたときに何かして、データを返す。
auto doWhenRaised (
[]( Button& b, const bool& DATA ){
if( b.isRaised( DATA ) ){
digitalWrite( LED_PIN, LOW );
return DATA;
}
return DATA;
}
);
void setup() {
pinMode( LED_PIN, OUTPUT );
for ( const auto& b : BUTTONS ) {
pinMode( b.PIN, INPUT_PULLUP );
}
pinMode(GND_PIN, OUTPUT);
digitalWrite(GND_PIN, LOW);
Serial.begin( 31250 );
}
void loop() {
// 各ボタン毎に、ピンの値を読んで、デバウンスして、押されたときに何かして、離したときに何かする。
for (auto&& b : BUTTONS ){
doWhenRaised( b, doWhenDropped( b, b.debounce( digitalRead( b.PIN )
) ) );
}
delay( 2 );
}
まとめ
ちゃんと思ったとおり動いてくれて大満足です。即本番採用。
この「ひとつのコントロールから複数のメッセージ」というのは結構いいかも。応用が効く。