VBAとWindows APIでMIDI操作【midiOutShortMsg関数(API)】
突然ですがVBAを使って音を鳴らしたいと思ったことはありませんか?
普通はないと思いますが本ページに来たということは少なからずはあるということでしょう。
というわけで本ページではVBAで音を鳴らす方法を紹介していきます。
VBAで音を鳴らす方法としてビープ音(通知などに使われるブザー音)を鳴らすための『Beep関数』というものが「Windows API」で用意されています。ただビープ音はあくまでもビープ音なので電子的な音色になってしまいますし、何より和音(複数の音を同時に鳴らす)が再現できないという大きな問題をかかえています。
そこで今回は同じくWindows APIで用意されている「midiOutGetNumDevs関数」「midiOutOpen関数」「midiOutShortMsg関数」「midiOutClose関数」を使い、VBAでピアノの音色を奏でる方法を紹介していきます。
(本ページでは和音については紹介していませんが、これら関数で和音の再現も可能です)
今回学ぶことのできる内容は以下のとおりです。
midiOutGetNumDevs関数の使い方
midiOutOpen関数の使い方
midiOutShortMsg関数の使い方
midiOutClose関数の使い方
上記4つの関数を使いピアノの音色で「ドレミ…」を鳴らす方法
本ページでは非常にシンプルなことしか行いませんが、逆にいえばこれらの関数を初めて使うという方でも簡単に理解することのできる内容となっています。
ほとんど触りのみの内容となっていますが、これらの関数は深く理解すればするほど表現できる音の幅が広がっていきます。音楽関係に興味のある方はぜひ本ページの内容で止まらずに掘り下げて行ってみてください。
MIDIを操作するためのAPI関数
VBAでMIDIを操作するには下記の4つのWindows APIの関数を使います。
・midiOutGetNumDevs関数
・midiOutOpen関数
・midiOutShortMsg関数
・midiOutClose関数
※本ページではこれら4つの関数をまとめて『MIDI関数』と記述していきます。
MIDIとは
まず大前提として『MIDI』について理解しておきましょう。
MIDI(ミディ)とは「Musical Instrument Digital Interface」の略で、
演奏データをデバイス間で転送/共有するための共通規格のことをいいます。
よくMIDIそのものが “音” だと思っている人もいますが、それは間違いでMIDIはあくまでも演奏データです。演奏データとは例えば「ピアノのドの音を鳴らす」や「ギターでラの音を出す」というような演奏するための情報です。
演奏データを共通規格とすることで、ある電子ピアノで演奏したデータをMIDIとして、その他のオーディオデバイスに送信し全く同じ演奏をさせることが可能になります。
MIDI関数は、このMIDIの情報を自在に操作し、コンピュータに装備されているオーディオデバイスで出力、つまりは音を出すことを可能にします。
好きな音を奏でるにはMIDIについて勉強する必要がありますが、MIDIを理解できればVBAからでも好きな音色を奏でることが可能になります。(※本ページではピアノの音色のみしか扱いません)
Windows APIとは
このMIDI関数はWindows APIで用意されているものでVBAとは別のものです。Windows APIとは簡単にいえばWindowsに用意されている機能がまとまっているセットのようなものです。
Windows APIにはMIDI関連の関数以外にも様々な関数があり、VBA上でそれらを呼び出すことで、Windowsに用意されている様々な機能を使用することができます。(たとえばWindowsのシステム効果音を鳴らしたり、指定した秒数だけ処理を止めることができます)
ちなみに名前からわかると思いますが、Windows APIではWindowsの機能を呼び出しているだけので基本的にはWindows以外のOSでは使用することはできません。
MIDI関数の使い方
MIDI関数を使うには、マクロコードのはじめで「Windows APIのMIDI関数を使うよ」と宣言しておく必要があります。
※宣言をしないとMIDI関数は使えずエラーになるので書き忘れに注意しましょう。
宣言時に書く内容は使用しているWindowsが32bitか64bitかによって変わってきます。
以下のどちらかをコードの一番初め(Option Explicitの次の行あたり)に書いておくことで、そのモジュール内でMIDI関数を使うことができるようになります。
‘【midiOutGetNumDevs関数】
Private Declare PtrSafe Function midiOutGetNumDevs Lib “winmm” () As Integer
‘【midiOutOpen関数】
Private Declare PtrSafe Function midiOutOpen Lib “winmm.dll” _
(lphMidiOut As LongPtr, ByVal uDeviceID As Long, ByVal dwCallback As LongPtr, _
ByVal dwInstance As LongPtr, ByVal dwflags As Long) As Long
‘【midiOutShortMsg関数】
Private Declare PtrSafe Function midiOutShortMsg Lib “winmm.dll” _
(ByVal hMidiOut As LongPtr, ByVal dwMsg As Long) As Long
‘【midiOutClose関数】
Private Declare PtrSafe Function midiOutClose Lib “winmm.dll” _
(ByVal hMidiOut As LongPtr) As Long
‘【midiOutGetNumDevs関数】
Private Declare Function midiOutGetNumDevs Lib “winmm” () As Integer
‘【midiOutOpen関数】
Private Declare Function midiOutOpen Lib “winmm.dll” _
(lphMidiOut As Long, ByVal uDeviceID As Long, ByVal dwCallback As Long, _
ByVal dwInstance As Long, ByVal dwFlags As Long) As Long
‘【midiOutShortMsg関数】
Private Declare Function midiOutShortMsg Lib “winmm.dll” _
(ByVal hMidiOut As Long, ByVal dwMsg As Long) As Long
‘【midiOutClose関数】
Private Declare Function midiOutClose Lib “winmm.dll” _
(ByVal hMidiOut As Long) As Long
※大文字/小文字が違うだけでエラーが発生するので書き間違いに注意
(VBAの構文と違い自動で大文字/小文字を変換してくれません)
上記のどちらのコードを書けばいいかわからない場合は、以下のコードをコピペしてモジュールの最上部(Option Explicitnの下)に書いておきましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#If Win64 Then Private Declare PtrSafe Function midiOutGetNumDevs Lib "winmm" () As Integer Private Declare PtrSafe Function midiOutOpen Lib "winmm.dll" (lphMidiOut As LongPtr, ByVal uDeviceID As Long, ByVal dwCallback As LongPtr, ByVal dwInstance As LongPtr, ByVal dwflags As Long) As Long Private Declare PtrSafe Function midiOutShortMsg Lib "winmm.dll" (ByVal hMidiOut As LongPtr, ByVal dwMsg As Long) As Long Private Declare PtrSafe Function midiOutClose Lib "winmm.dll" (ByVal hMidiOut As LongPtr) As Long Dim Handle As LongPtr #Else Private Declare Function midiOutGetNumDevs Lib "winmm" () As Integer Private Declare Function midiOutOpen Lib "winmm.dll" (lphMidiOut As Long, ByVal uDeviceID As Long, ByVal dwCallback As Long, ByVal dwInstance As Long, ByVal dwFlags As Long) As Long Private Declare Function midiOutShortMsg Lib "winmm.dll" (ByVal hMidiOut As Long, ByVal dwMsg As Long) As Long Private Declare Function midiOutClose Lib "winmm.dll" (ByVal hMidiOut As Long) As Long Dim Handle As Long #End If |
この構文を書いておくことで自動的に使うことのできる方の構文が使用されます。
VBE上では以下のように使えない方の構文が赤色で表示されますが、実行に影響はありません。
また、以降でも説明しますが音を鳴らすために「midiOutOpen関数の第1引数(lphMidiOut)」を変数で宣言しておく必要があります。ただ、この引数は64bitと32bitで型が変わってしまうため上記では合わせて「Handle」という変数を宣言しています。
MIDI関数の使う順番
宣言したMIDI関数は下記の順番で使用していきます。
② MIDIデバイスを開く 【midiOutOpen関数】
③ MIDIメッセージを送る(=音を鳴らす) 【midiOutShortMsg関数】
④ MIDIデバイスを閉じる 【midiOutClose関数】
① MIDIデバイスの取得
MIDIデバイスを取得するには『midiOutGetNumDevs関数』を使い以下のように書きます。
1 2 3 |
'MIDI出力デバイス取得 Dim Ret As Long Ret = midiOutGetNumDevs |
このとき、midiOutGetNumDevsの返り値が「0」の場合はMIDI音源がないことを意味します。
参考:midiOutGetNumDevs
② MIDIデバイスを開く
MIDIデバイスを開くには『midiOutOpen関数』を使い以下のように書きます。
1 2 |
'MIDIデバイスを開く Ret = midiOutOpen(Handle, -1, 0, 0, 0) |
上記ではmidiOutOpen関数の第1引数として「Handle」という変数を使っています。
この「Handle」は64bitの場合は「LongPtr型」、32bitの場合は「Long型」で、以降のMIDI関数を使用する際に使用します。
参考:midiOutOpen
③ MIDIメッセージを送る(=音を鳴らす)
MIDIメッセージを送るには『midiOutShortMsg関数』を使い以下のように書きます。
1 2 |
'MIDIメッセージを送る(=音を鳴らす) Call midiOutShortMsg(Handle, Msg) |
「Handle」には先に使用したMIDIデバイスのハンドル、
「Msg」には16進数で表したMIDIメッセージを入力します。
(※メッセージ内容は「MIDIメッセージ 16進数」などで検索してみてください)
MIDIメッセージは世界共通の内容で決められているものです。
ここではピアノの音(ドレミ…)しか扱いませんが、このメッセージ内容を変更するだけで他の音源を鳴らす事も可能になります。
参考:midiOutShortMsg
④ MIDIデバイスを閉じる
MIDIデバイスを閉じるには『midiOutClose関数』を使い以下のように書きます。
1 2 |
'MIDIデバイスを閉じる Ret = midiOutClose(Handle) |
MIDI関数の使用を終える際には、必ずこの関数を使いMIDIデバイスを閉じましょう。
この関数を使わずにVBA処理が終了すると、2回目以降の実行で音が鳴らなくなります。
そうなった場合はアプリケーション(ExcelやAccessなど)を開き直せば音が鳴るようになります。
(※エラーなどで処理が中断された場合も音が鳴らなくなるので注意しましょう)
参考:midiOutClose
MIDIメッセージ設定
MIDI関数の宣言が終わったら次に音を鳴らすためのプロシージャを用意しておきます。
下記のように音階(ドレミ)別のメッセージを作成し『midiOutShortMsg関数』でMIDIメッセージを送信させます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
Private Sub MIDI(key As Double) Dim Msg As Long Dim BaseMsg1 As Long BaseMsg1 = &H7F3C90 '基準の音(ド/C4) Select Case key Case 1 'ド Msg = BaseMsg1 Case 1.5 'ド# Msg = BaseMsg1 + 1 * 256 Case 2 'レ Msg = BaseMsg1 + 2 * 256 Case 2.5 'レ# Msg = BaseMsg1 + 3 * 256 Case 3 'ミ Msg = BaseMsg1 + 4 * 256 Case 4 'ファ Msg = BaseMsg1 + 5 * 256 Case 4.5 'ファ# Msg = BaseMsg1 + 6 * 256 Case 5 'ソ Msg = BaseMsg1 + 7 * 256 Case 5.5 'ソ# Msg = BaseMsg1 + 8 * 256 Case 6 'ラ Msg = BaseMsg1 + 9 * 256 Case 6.5 'ラ# Msg = BaseMsg1 + 10 * 256 Case 7 'シ Msg = BaseMsg1 + 11 * 256 Case 8 'ド Msg = BaseMsg1 + 12 * 256 End Select Call midiOutShortMsg(Handle, Msg) 'MIDIの音を鳴らす End Sub |
上記コードの場合 MIDI(1) を実行すれば「ド」の音が、 MIDI(2) を実行すれば「レ」の音が、MIDI(2.5) を実行すれば「レ#」の音が鳴ります。
サンプルコード
実際に今までのコードをまとめたサンプルコードを見てみましょう。
ここでは処理を指定した秒数だけ止めることのできる「Sleep関数」も使用しています。
(※これがないと連続で音を鳴らす際に一瞬しか音が鳴らなくなってしまいます)
同じWindows APIなので合わせて確認しておきましょう。
下記コードを実行することで、ピアノの音色で「ドレミファソラシド」が鳴ります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
#If Win64 Then 'MIDI API Private Declare PtrSafe Function midiOutGetNumDevs Lib "winmm" () As Integer Private Declare PtrSafe Function midiOutOpen Lib "winmm.dll" (lphMidiOut As LongPtr, ByVal uDeviceID As Long, ByVal dwCallback As LongPtr, ByVal dwInstance As LongPtr, ByVal dwflags As Long) As Long Private Declare PtrSafe Function midiOutShortMsg Lib "winmm.dll" (ByVal hMidiOut As LongPtr, ByVal dwMsg As Long) As Long Private Declare PtrSafe Function midiOutClose Lib "winmm.dll" (ByVal hMidiOut As LongPtr) As Long Dim Handle As LongPtr 'Sleep API Private Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As LongPtr) #Else 'MIDI API Private Declare Function midiOutGetNumDevs Lib "winmm" () As Integer Private Declare Function midiOutOpen Lib "winmm.dll" (lphMidiOut As Long, ByVal uDeviceID As Long, ByVal dwCallback As Long, ByVal dwInstance As Long, ByVal dwFlags As Long) As Long Private Declare Function midiOutShortMsg Lib "winmm.dll" (ByVal hMidiOut As Long, ByVal dwMsg As Long) As Long Private Declare Function midiOutClose Lib "winmm.dll" (ByVal hMidiOut As Long) As Long Dim Handle As Long 'Sleep API Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long) #End If '----------------------------------------------------------------------------------------------------------------------------------------------' Private Sub MIDI(key As Double) Dim Msg As Long Dim BaseMsg1 As Long BaseMsg1 = &H7F3C90 '基準の音(ド) Select Case key Case 1 'ド Msg = BaseMsg1 Case 1.5 'ド# Msg = BaseMsg1 + 1 * 256 Case 2 'レ Msg = BaseMsg1 + 2 * 256 Case 2.5 'レ# Msg = BaseMsg1 + 3 * 256 Case 3 'ミ Msg = BaseMsg1 + 4 * 256 Case 4 'ファ Msg = BaseMsg1 + 5 * 256 Case 4.5 'ファ# Msg = BaseMsg1 + 6 * 256 Case 5 'ソ Msg = BaseMsg1 + 7 * 256 Case 5.5 'ソ# Msg = BaseMsg1 + 8 * 256 Case 6 'ラ Msg = BaseMsg1 + 9 * 256 Case 6.5 'ラ# Msg = BaseMsg1 + 10 * 256 Case 7 'シ Msg = BaseMsg1 + 11 * 256 Case 8 'ド Msg = BaseMsg1 + 12 * 256 End Select Call midiOutShortMsg(Handle, Msg) 'MIDIメッセージを送る(=音を鳴らす) End Sub '----------------------------------------------------------------------------------------------------------------------------------------------' Sub MIDI_Play() 'MIDI出力デバイス取得 Dim Ret As Long Ret = midiOutGetNumDevs 'MIDIデバイスを開く If Ret = 0 Then MsgBox "MIDI音源が無いため利用できません。" Else Ret = midiOutOpen(Handle, -1, 0, 0, 0) End If 'MIDIメッセージを送る(=音を鳴らす) Dim i As Long For i = 1 To 8 MIDI (i) 'i番の音階を鳴らす Sleep (300) '300ミリ秒処理を止める Next i 'MIDIデバイスを閉じる Ret = midiOutClose(Handle) MsgBox "正常に終了しました。" End Sub |
まとめ
今回はMIDIを操作するためのWindows APIの4つ関数についての内容でした。
VBAでのMIDI操作に関しては基本的に書く内容は決まっているので、ほとんどコピペのみでOKです。
また、MIDIメッセージの内容さえ変えれば、音色や音の長さ、強弱なども調整できるようなので、ぜひいろいろ試してVBAで作曲ができるようなツールを作成してみてください。(調べると実際にそのようなツールがいろいろ出てきます)