概要
キーボードの演奏を練習するための音ゲー風アプリケーションを作りました。
使用した機材
シンセサイザー:Roland JUNO-DS61
音が鳴る61鍵盤のキーボードです。USB Type-Bの出力ポートからPCにつないでMIDI入力とサウンド入力を同時に取ることができます。
プロジェクター:XGIMI HORIZON Pro
コンパクトさと発色の良さが特徴のプロジェクターです。デフォルト設定だと遅延が大きいですが、ゲームモードにすると遅延はほとんど感じませんでした。
モニターアーム:エルゴトロン LX (+ VESA変換プレート)
プロジェクターを上から床面に投影するために使いました。プロジェクター側のネジ穴間隔が微妙に揃っていなかったため完全には取り付けることができず、仕方なく対角線の二つだけ付けてます。
デスクトップを投影するとこんな感じです。そのままだと黒鍵に投影した光が見えなかったため上から白いテープを貼りました。
プログラムの実装
元々作っていたMIDIプレイヤーにキーボード入力機能を追加する形で実装しました。
MIDI入力
C++でリアルタイムのMIDI入出力を扱うことができるlibremidiを使いました。
libremidiを使うには、CMakeを使って事前にスタティックリンクライブラリをビルドする方法と、マクロ指定でヘッダオンリーライブラリとして使う方法の二つがあります。以下にヘッダオンリーの方でOpenSiv3Dから動かす例を示します。
#include <Siv3D.hpp>
#define LIBREMIDI_HEADER_ONLY 1
#define LIBREMIDI_WINMM 1
#include <libremidi/libremidi.hpp>
void Main()
{
HashSet<uint8> pressedNotes;
const auto callback = [&](const libremidi::message& message)
{
switch (static_cast<libremidi::message_type>(message.get_message_type()))
{
case libremidi::message_type::NOTE_OFF:
pressedNotes.erase(static_cast<uint8>(message.bytes[1]));
break;
case libremidi::message_type::NOTE_ON:
pressedNotes.emplace(static_cast<uint8>(message.bytes[1]));
break;
default:
break;
}
};
libremidi::observer observer;
auto inputs = observer.get_input_ports();
if (inputs.size() == 0)
{
Logger << U"No MIDI input devices found\n";
return;
}
libremidi::midi_in midiin{ {.on_message = callback}, libremidi::midi_in_configuration_for(observer) };
midiin.open_port(inputs[0]);
if (!midiin.is_port_connected())
{
Logger << U"Failed to connect to MIDI input port\n";
return;
}
while (System::Update())
{
ClearPrint();
Print << U"押されているキー:";
for (auto note : pressedNotes)
{
Print << note;
}
}
midiin.close_port();
}
使い方はlibremidi::midi_in
クラスにMIDIメッセージを受け取るコールバック関数を登録して、open_port
を呼び出して入力待ちに入ります。受け取ったメッセージには入力されたMIDIイベントが入っており、NOTE_ON / NOTE_OFF イベントの場合は message.bytes[1]
にノート番号、message.bytes[2]
にベロシティの値が入っています。またコールバック関数は別スレッドで走るため、実際に使うときは排他制御も多分必要です。
サウンド入力
OpenSiv3Dサンプル集にある「マイク入力のリアルタイム書き込み」のコードをそのまま使いました。再生遅延の大きさは違和感なく聞こえる0.05秒(2200サンプル)に設定しました。
音ゲー部分の処理
音ゲーっぽくしたいので押した鍵盤が光る処理を入れました。また、キーの入力開始タイミングで4段階の判定を付けて、キーの上部に表示しています。
誤差(±秒) | 判定 |
---|---|
0.1 | Perfect |
0.2 | Good |
0.4 | Bad |
それ以上 | Miss |
譜面が複数トラックに分かれていた場合は、そのうちの1つを演奏用として、他のトラックは自動演奏扱いにしています。
再利用性は低いですが、今回実装したコードはここに上げました。
動画
楽譜は読めないけど好きな曲を弾いた気分になれるので楽しいです。手元を見ずに弾けるくらいになったらプロジェクターを外して画面見ながらでも良いかも。