はじめに
先日購入した自作キーボードHelixの製作およびMIDIファームの書き込みは以前の記事を見てもらえればと思います。
QMKファームウェアに入っているMIDIファームだと痒いところに手が届かなかったので、ちょっと改良を加えれればと思い思考錯誤していました。
結果だけを知りたい人は「5. QMKのカスタマイズ」を読んでください。
HelixとQMK
概ねのことは記事参照です。
自作キーボードの中でも人気の高いキーボードで、通販だと遊舎工房さんから購入することができます。
キーボードのソフト部分であるQMK Firmwareはオープンソースのファームで自由に編集して好みのキーボードに作り変えることができます。キーボード入力の他にMIDIで送受信もできるため、自作MIDIデバイスとして扱うことができます。
QMK内蔵のMIDIファーム
QMKに内蔵されているMIDIファームウェアの使用方法に関しては以前の記事を参照してください。
さて、このMIDI ADVANCEDですが、キーボードのキーマップとして「MI_VELU
」や「MI_VELD
」などのベロシティを変更するボタンや、「MI_CHU
」や「MI_CHD
」などのMIDIチャンネルを変更するボタンがあり、普通のMIDIコンとは勝手が違います。また、ベロシティは10段階で設定できますが、最大値が「120」と中途半端です(通常は127)。
痒いところに微妙に手が届かないので、この辺りを使いやすいように変えていきましょう。
QMKのMIDIデバイス化の確認
QMKとMIDIデバイス
MIDI ADVANCEDの利用に関しては以前の記事を。
MIDI Firmwareに関してはQMKのドキュメントのMIDIページを参照してください。
さて、MIDI ADVANCEDを有効にして、MIDIのキーマップがどのように動いているかを確認します。QMKのドキュメントからも確認できますが、
// process_midi.c 81行目-
bool process_midi(uint16_t keycode, keyrecord_t *record)
{
switch (keycode) {
case MIDI_TONE_MIN ... MIDI_TONE_MAX:
{
uint8_t channel = midi_config.channel;
uint8_t tone = keycode - MIDI_TONE_MIN;
uint8_t velocity = compute_velocity(midi_config.velocity);
if (record->event.pressed) {
uint8_t note = midi_compute_note(keycode);
midi_send_noteon(&midi_device, channel, note, velocity);
dprintf("midi noteon channel:%d note:%d velocity:%d\n", channel, note, velocity);
tone_status[tone] = note;
}
else {
uint8_t note = tone_status[tone];
if (note != MIDI_INVALID_NOTE)
{
midi_send_noteoff(&midi_device, channel, note, velocity);
dprintf("midi noteoff channel:%d note:%d velocity:%d\n", channel, note, velocity);
}
tone_status[tone] = MIDI_INVALID_NOTE;
}
return false;
}
とまあいろいろと書いてありますが、
大事なのは、
if (record->event.pressed) {
midi_send_noteon(&midi_device, channel, note, velocity);
}else {
midi_send_noteoff(&midi_device, channel, note, velocity);
}
です。
ボタンが押された時に指定されたMIDIチャンネル、ノート、ベロシティでノートオン情報を、離された時にノートオフ情報を送る、というものです。
このチャンネルやノートを別でいろいろと弄っているので複雑になっているというわけです。なので、このコードを自分で書いてカスタムキーコードとして登録してやれば自分の好きなようにMIDIを送信することができるというわけですね。
QMKのドキュメントのMIDIページをみるとわかりますが、ノートオンオフだけでなくCCなどいろいろな情報を送ることができます。
QMKのカスタムキーマップ
QMKのドキュメントのCustomizing Functionalityのページに書いてあるように、QMKのキーマップにはマクロ化(というより自分でコーディングすることによって自由な挙動をさせる)ことができます。
ドキュメント内に書いてあるように、keymap.c内に
enum my_keycodes {
FOO = SAFE_RANGE,
BAR
};
というようにキーコードの宣言をしたのち、
bool process_record_user(uint16_t keycode, keyrecord_t *record) {
switch (keycode) {
case FOO:
if (record->event.pressed) {
// Do something when pressed
} else {
// Do something else when release
}
return false; // Skip all further processing of this key
default:
return true; // Process all other keycodes normally
}
}
というようにprocess_record_user()
関数で処理を書きます。
Helixのkeymap.cにはすでにenum custom_keycodes
とbool process_record_user()
が宣言されているので、ここに書き足していく形になります。my_keycodes
内の = SAFERANGE
はカスタムされたキーコードとデフォルトのキーコードがバッティングしないようにするために必要です。
ここで、midi_send_noteon(&midi_device, channel, note, velocity);
はchannel
やnote
、velocity
の他に、&midi_device
の宣言が必要になります。&midi_device
はkey code.c内で宣言していないので、key code.c内に「extern MidiDevice midi_device;
」でmidi_device
を宣言する必要があります (普通externの宣言はヘッダファイルに書き込むもんだと思ってましたけど、他のがcファイルに書き込まれてるのでkeymap.cに書き込みます)。
ついでながらカスタムキーコードは1つだけの処理ではなく複数の同時処理(連続処理)を書くことができるので、たとえばMIDI信号を一つのボタンで複数飛ばしたり、文字入力とMIDI信号を同時に送ったりすることもできます。
QMKのカスタマイズ
ここまで来れればあとは規則に従って書いていくだけです。
今回は「qmk_firmware-master/keyboards/helix/rev2/keymaps/default/」をコピーして「qmk_firmware-master/keyboards/helix/rev2/keymaps/midi/」というフォルダとファイルを作り、それをカスタマイズしていきます。
基本的に「qmk_firmware-master/keyboards/helix/rev2/keymaps/midi/」内にあるファイルをいじる形になります。
Rules.mkの書き換え (MIDI_ENABLE)
まず、rules.mkファイルにあるMIDI_ENABLE
をMIDI_ENABLE = yes
に書き換えてmidi周りの処理を有効化します。
keymaps.cの書き換え
MidiDeviceの宣言
keymap.c内のどこでも良いですが、
extern MidiDevice midi_device;
の一文を書き加えます。
宣言周りの一番最後に書き加えました。
カスタムキーコードの宣言
MI_NOTEMIN,
MI_NOTE001 = MI_NOTEMIN,
MI_NOTE002,
MI_NOTE003,
MI_NOTE004,
MI_NOTE005,
MI_NOTE006,
MI_NOTE007,
MI_NOTE008,
MI_NOTE009,
MI_NOTE010,
MI_NOTE011,
MI_NOTE012,
キーコードの宣言です。enum
型で列挙します。今回はMI_NOTE001
から適当な個数を作りました。もちろん許す限りの数を書き加えて大丈夫ですし、名前も適当で大丈夫です。ここの名前がキーコードになります。enum
型は指定しなければ上から順に値が1ずつ増えていきます。そのため後々にカスタムキーコードが増えるたびに値がずれていってしまいます。後述しますが、MI_NOTEMIN
という宣言をしてやることでkeycode - MI_NOTEMIN
で値が0から始まるようにします。
カスタムキーコードの実行処理
case MI_NOTE001 ... MI_NOTE012:
if (record->event.pressed) {
uint8_t channel = 0;
uint8_t note = keycode - MI_NOTEMIN;
uint8_t velocity = 127;
midi_send_noteon(&midi_device, channel, note, velocity);
}else {
uint8_t channel = 0;
uint8_t note = keycode - MI_NOTEMIN;
uint8_t velocity = 0;
midi_send_noteoff(&midi_device, channel, note, velocity);
}
return false;
// break;
カスタムキーコードにMIDIの処理を追加します。if(record->event.pressed)
は押された時の処理、else
は離された時の処理です。channel
とvelocity
は固定、note
はkeycode
に依存させます。enum
型はMI_NOTE000 = MI_NOTEMIN
としているので、上から順に0, 1, 2…という値をとります。今回はMI_NOTE000
はnote = 0
、MI_NOTE001
はnote = 1
…となっています。
必要に応じて算術演算子を使ったり宣言をふやしてやります。
キーマップの編集
最後に、キーマップを編集してやればOKです。
キーマップの名前はenum型で決めた名前がそのままカスタムキーコードになっています。
以上でカスタマイズが終了です。
ターミナルを通してPro Microに書き込んで正常にMIDIデバイス化されていれば完了です。
オクターブシフトの代わりにキーボードのレイヤー分けをしてやったりすればいろいろと使いやすくなるかと思います。
あとコントロールチェンジとか。
まとめ
今回はQMKに入っているMIDI ADVANCEDを使わずにMIDIの送受信をできるようにしました。
とはいえ実際にやっていることはMIDI ADVANCEDと同じような挙動の処理なので、つまるところ車輪の再発明です。しかしながらkeymap.c内の編集で済むことからMIDI ADVANCEDの処理コードを編集するよりも保守性は高いかと思います。
他の機能とも絡めて使えるようになれば使いやすいMIDIコンになりそうですね。