今回は、C++で「MIDIキーボードの入力をWinMMで取得する方法」について解説していきます。
「MIDIキーボードの入力を自分のプログラムで使いたい」「音楽アプリやゲームでMIDI対応したい」と思ったことはありませんか?
この記事を読み終えると、あなたはWinMMを使ったMIDI入力の取得方法をマスターできると思いますので、ぜひ最後まで読んでいただけると嬉しいです。
MIDIとは?
MIDI(Musical Instrument Digital Interface)は、電子楽器やコンピューター間で音楽情報をやり取りするための規格です。
MIDIキーボードから送られるデータには、以下のような情報が含まれます。
- ノート情報:どの鍵盤が押された/離された
- ベロシティ:鍵盤を押す強さ(0~127)
- コントロールチェンジ(CC):つまみやスライダーの値
- ピッチベンド:音程を変化させるホイールの値
完全版のコードについて
この記事では、実装の要点を解説します。
完全なソースコードは、以下のGitHubリポジトリで公開しています。
🔗 https://github.com/it-kashiros/midi_winmm
リポジトリには以下のファイルが含まれています。
midi_input.h:MIDIクラスのヘッダーmidi_input.cpp:MIDIクラスの実装main.cpp:デバッグ用のモニタープログラム
Visual Studioで開いて、すぐにビルド・実行できます。
基本的な使い方
WinMMでMIDI入力を取得する基本的な流れは以下の通りです。
midiInOpen()でMIDIデバイスを開く- コールバック関数でMIDIメッセージを受信
- 受信したメッセージを解析して状態を更新
必要なヘッダーとライブラリ
▼midi_input.h(抜粋)
#pragma once
#include <windows.h>
#include <mmsystem.h>
#pragma comment(lib, "winmm.lib")コントローラーと同じく、winmm.libをリンクするだけです。
実装のポイント
1. MIDIデバイスのオープン
MIDIデバイスを開くには、midiInOpen()を使います。
▼midi_input.cpp(抜粋)
bool MidiInput::Initialize() {
// デバイスがなければ失敗
if (midiInGetNumDevs() == 0) {
return false;
}
// 最初のデバイスを開く
MMRESULT result = midiInOpen(
&s_hMidiIn,
0, // デバイスID(0が最初のデバイス)
reinterpret_cast<DWORD_PTR>(MidiCallback),
0,
CALLBACK_FUNCTION
);
if (result != MMSYSERR_NOERROR) {
return false;
}
// 入力開始
midiInStart(s_hMidiIn);
return true;
}midiInStart()を呼ぶことで、MIDIメッセージの受信が開始されます。
2. コールバック関数でメッセージ受信
MIDIメッセージは、コールバック関数で受信します。
void CALLBACK MidiInput::MidiCallback(
HMIDIIN hMidiIn, UINT wMsg,
DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) {
if (wMsg == MIM_DATA) {
// MIDIメッセージを処理
ProcessMessage(static_cast<DWORD>(dwParam1), static_cast<DWORD>(dwParam2));
}
}dwParam1にMIDIメッセージの生データが入っています。
3. MIDIメッセージの解析
MIDIメッセージは、ステータスバイトとデータバイトで構成されています。
void MidiInput::ProcessMessage(DWORD param1, DWORD param2) {
BYTE status = param1 & 0xFF;
BYTE byte1 = (param1 >> 8) & 0xFF;
BYTE byte2 = (param1 >> 16) & 0xFF;
BYTE channel = status & 0x0F; // チャンネル(0-15)
BYTE statusType = status & 0xF0; // メッセージタイプ
switch (statusType) {
case 0x90: // ノートオン
if (byte2 > 0) { // ベロシティが0ならノートオフ
s_state.noteOn[byte1] = true;
s_state.velocity[byte1] = byte2;
}
break;
case 0x80: // ノートオフ
s_state.noteOn[byte1] = false;
s_state.velocity[byte1] = 0;
break;
case 0xB0: // コントロールチェンジ
s_state.cc[byte1] = byte2;
break;
case 0xE0: // ピッチベンド
s_state.pitchBend = ((byte2 << 7) | byte1) - 8192;
break;
}
}ステータスバイトの上位4ビットでメッセージの種類を判別できます。
主なMIDIメッセージ
| ステータス | メッセージ | 説明 |
|---|---|---|
| 0x90 | ノートオン | 鍵盤が押された |
| 0x80 | ノートオフ | 鍵盤が離された |
| 0xB0 | コントロールチェンジ | つまみ・スライダーの値 |
| 0xE0 | ピッチベンド | ピッチホイールの値 |
| 0xC0 | プログラムチェンジ | 音色の切り替え |
| 0xD0 | チャンネルプレッシャー | 鍵盤全体の圧力 |
実際の使い方
実装したクラスは、以下のように使います。
▼使用例
#include "midi_input.h"
int main() {
// 初期化
if (!MidiInput::Initialize()) {
printf("MIDIデバイスが見つかりません\n");
return 1;
}
while (true) {
MidiInput::Update();
// 特定のノートが押されているか
if (MidiInput::IsNoteOn(60)) { // C4(ド)
printf("ドが押されています\n");
}
// 押されている鍵盤の数
int count = MidiInput::GetNoteOnCount();
printf("押されている鍵盤: %d\n", count);
// モジュレーションホイール(CC1)の値
float mod = MidiInput::GetCCFloat(1); // 0.0~1.0
// ピッチベンドの値
int bend = MidiInput::GetPitchBend(); // -8192~8191
Sleep(16);
}
MidiInput::Finalize();
return 0;
}デバッグモニター
リポジトリのmain.cppには、MIDIキーボードの入力をリアルタイムで表示するモニターが実装されています。
▼実行すると、以下のような画面が表示されます
===============================================================================
MIDI INPUT DEBUG
===============================================================================
Device: USB MIDI Keyboard
-------------------------------------------------------------------------------
Last: NoteOn Ch: 1 C4(60) Vel:82
-------------------------------------------------------------------------------
Piano (A0-E4):
___.__._____.__._____.__._____#__._____.__._____.__._
Piano (F4-C8):
___.__._____.__._____.__._____.__._____.__._____.__._
-------------------------------------------------------------------------------
Notes: 1 Low: C4(60) High: C4(60)
Active: C4
-------------------------------------------------------------------------------
Pitch: 0 | Mod(CC1): 0 | Vol(CC7): 127 | Sustain: OFF
===============================================================================
ESC: Exit
88鍵ピアノの鍵盤状態が視覚化され、押された鍵盤が#で表示されます。
ノート番号と音名の対応
MIDIでは、ノート番号0~127で音程を表します。
- 60:C4(中央のド)
- 69:A4(440Hz のラ)
- 21:A0(88鍵ピアノの最低音)
- 108:C8(88鍵ピアノの最高音)
リポジトリのコードには、ノート番号を音名に変換する関数が用意されています。
const char* MidiInput::GetNoteName(BYTE note) {
static char buf[8];
int octave = (note / 12) - 1;
int n = note % 12;
sprintf_s(buf, sizeof(buf), "%s%d", NOTE_NAMES[n], octave);
return buf;
}
// 使用例
printf("%s\n", MidiInput::GetNoteName(60)); // "C4"便利な機能
実装したクラスには、以下のような便利な機能があります。
押されている鍵盤の情報
// 何か鍵盤が押されているか
bool isAny = MidiInput::IsAnyNoteOn();
// 押されている鍵盤の数
int count = MidiInput::GetNoteOnCount();
// 最も低い音
int low = MidiInput::GetLowestNote();
// 最も高い音
int high = MidiInput::GetHighestNote();コントロールチェンジ(CC)
MIDIキーボードのつまみやスライダーの値を取得できます。
// CC1(モジュレーションホイール)
BYTE mod = MidiInput::GetCC(1); // 0~127
float modF = MidiInput::GetCCFloat(1); // 0.0~1.0
// CC7(ボリューム)
BYTE vol = MidiInput::GetCC(7);
// CC64(サステインペダル)
BYTE sustain = MidiInput::GetCC(64); // 0-63: OFF, 64-127: ONピッチベンド
// ピッチベンドの値
int bend = MidiInput::GetPitchBend(); // -8192~8191
float bendF = MidiInput::GetPitchBendFloat(); // -1.0~1.0注意点
1. コールバック関数の制約
MIDIコールバック関数は、システムのスレッドから呼ばれます。
そのため、以下のような処理は避けましょう。
- ❌ 時間のかかる処理
- ❌ ファイル入出力
- ❌ ウィンドウ操作
- ✅ 状態の更新のみ
2. デバイスの切断
MIDIキーボードが切断された場合、MIM_CLOSEメッセージが届きます。
リポジトリのコードでは、これを検知してconnectedフラグを更新しています。
3. ノートオフの扱い
MIDIでは、ベロシティが0のノートオンをノートオフとして扱うことがあります。
case 0x90: // ノートオン
if (byte2 > 0) {
// 本当のノートオン
s_state.noteOn[byte1] = true;
} else {
// ベロシティ0 = ノートオフ
s_state.noteOn[byte1] = false;
}
break;動作確認
リポジトリダウンロードして、Visual Studioで開いてください。
- Visual Studioでプロジェクトを開く
- コントローラーをPCに接続
- ビルド&実行(F5)
- コントローラーを操作して、入力が表示されることを確認
ESCキーで終了できます。
まとめ
WinMMを使ったMIDI入力取得についてまとめます。
- WinMM:Windows標準のマルチメディアAPI
midiInOpen()でデバイスを開く- コールバック関数でメッセージを受信
- ステータスバイトでメッセージの種類を判別
- ノートオン・オフ、CC、ピッチベンドなどを取得
- 完全なコードはGitHubリポジトリで公開中
これでWinMMを使ったMIDI入力の取得はバッチリですね!
GitHubのコードを参考に、音楽アプリやゲームにMIDI対応を組み込んでみてください。
ここまで読んでくださり、ありがとうございました。
この記事が皆様の学習に役立てば幸いです。

