音楽を演奏する方法は色々あるのでしょうが、
たぶん一番簡単なのはMIDIファイルをそのまま演奏する事です。
MFCにはそういう機能は用意されていませんが、
APIにはmciSendCommandというMIDIを演奏できる関数があります。
これは、CDの演奏やその他よく分からない物(総称してデバイスと呼ぶ)にも使えるらしいです。
MCIの使い方は、おおまかに言って次の様になります。
う〜ん、シンプル(笑)
他にも途中で演奏を止めたり、ポーズをかけたり、演奏開始位置を変えたりする事もできます。
mciSendCommandはデバイスにメッセージを送るAPIなので、
メッセージループが回っていなければなりません。
また、デバイスの再生時にコールバック関数を指定できますが、
その為にはウィンドウハンドルを必要とします。
したがってMFCからmciSendCommandを使う場合、
CMainFrameまたは
このページの例ではCMainFrameに書きます。
MainFrm.h: 後述
MainFrm.cpp:
〜 #include "MMsystem.h" 〜 //Midiデバイスをオープンする(成功したらTRUEを返す) BOOL CMainFrame::MciMidiOpen() { MCI_OPEN_PARMS prm; DWORD dwFlags=0; //デバイスにMidiを指定 dwFlags|=MCI_OPEN_TYPE|MCI_OPEN_TYPE_ID; prm.lpstrDeviceType=(LPCSTR)MCI_DEVTYPE_SEQUENCER; //演奏するファイル名を指定 dwFlags|=MCI_OPEN_ELEMENT; prm.lpstrElementName=m_sMidiName; //オープン dwFlags|=MCI_WAIT; MCIERROR ret=mciSendCommand(0,MCI_OPEN,dwFlags,(DWORD)&prm); if(ret==0){ m_wMidiDeviceID=prm.wDeviceID; }else{ //エラー char buf[256]; mciGetErrorString(ret,buf,sizeof(buf)); TRACE("MciMidiOpen:%d(%s)\r\n",ret,buf); m_wMidiDeviceID=0; return FALSE; } return TRUE; }
m_sMidiNameはMIDIファイルのファイル名です。
これはこの関数を呼び出す前にセットしておきます。
m_wMidiDeviceIDはデバイスのIDです。
MCI_OPENで返されたIDを再生や停止などに使います。
MainFrm.h: 後述
MainFrm.cpp:
//Midiデバイスを再生する(成功したらTRUEを返す)
BOOL CMainFrame::MciMidiPlay()
{
if(m_wMidiDeviceID==0) return FALSE;
MCI_PLAY_PARMS prm;
DWORD dwFlags=0;
//コールバック関数を使用
dwFlags|=MCI_NOTIFY;
prm.dwCallback=(DWORD)GetSafeHwnd();
//演奏開始
MCIERROR ret=mciSendCommand(m_wMidiDeviceID,MCI_PLAY,dwFlags,(DWORD)&prm);
if(ret){ //エラー
char buf[256];
mciGetErrorString(ret,buf,sizeof(buf));
TRACE("MciMidiPlay:%d(%s)\r\n",ret,buf);
MidiMciClose(); //デバイスを閉じておく
}
return ret==0;
}
dwFlagsにMCI_NOTIFYを指定すると、演奏が終わった時に MM_MCINOTIFYメッセージが指定したウィンドウに送られます。
MainFrm.h: 後述
MainFrm.cpp:
//Midiデバイスを停止する(成功したらTRUEを返す)
BOOL CMainFrame::MciMidiStop()
{
if(m_wMidiDeviceID==0) return TRUE;
MCI_GENERIC_PARMS prm;
MCIERROR ret=mciSendCommand(m_wMidiDeviceID,MCI_STOP,0,(DWORD)&prm);
if(ret){
char buf[256];
mciGetErrorString(ret,buf,sizeof(buf));
TRACE("MciMidiStop:%d(%s)\r\n",ret,buf);
}
return ret==0;
}
MainFrm.h: 後述
MainFrm.cpp:
//Midiデバイスの再生位置を先頭に移動する(成功したらTRUEを返す)
BOOL CMainFrame::MciMidiSeekToStart()
{
if(m_wMidiDeviceID==0) return FALSE;
MCI_SEEK_PARMS prm;
MCIERROR ret=mciSendCommand(m_wMidiDeviceID,MCI_SEEK,MCI_SEEK_TO_START,(DWORD)&prm);
if(ret){
char buf[256];
mciGetErrorString(ret,buf,sizeof(buf));
TRACE("MciMidiSeekToStart:%d(%s)\r\n",ret,buf);
}
return ret==0;
}
MainFrm.h: 後述
MainFrm.cpp:
//Midiデバイスをクローズする(成功したらTRUEを返す)
BOOL CMainFrame::MciMidiClose()
{
if(m_wMidiDeviceID==0) return TRUE;
MCI_GENERIC_PARMS prm;
DWORD flag=MCI_WAIT;
MCIERROR ret=mciSendCommand(m_wMidiDeviceID,MCI_CLOSE,flag,(DWORD)&prm);
if(ret){
char buf[256];
mciGetErrorString(ret,buf,sizeof(buf));
TRACE("MciMidiClose:%d(%s)\r\n",ret,buf);
}
m_wMidiDeviceID=0;
return ret==0;
}
MainFrm.h: 後述
MainFrm.cpp:
〜 BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) ON_MESSAGE(MM_MCINOTIFY,CMainFrame::OnMidiNotify) END_MESSAGE_MAP() 〜 //Midiの演奏が終わった時に呼ばれる afx_msg LRESULT CMainFrm::OnMidiNotify(WPARAM wParam,LPARAM lParam) { if(LOWORD(lParam)!=m_wMidiDeviceID) return FALSE; switch(wParam){ case MCI_NOTIFY_SUCCESSFUL: break; case MCI_NOTIFY_ABORTED: TRACE("MidiAbort:%d %d\n",wParam,lParam); return TRUE; default: TRACE("MidiNotSuccess:%d %d\n",wParam,lParam); MciMidiClose(); return TRUE; } if(m_bMidiLoop){ //演奏をループさせる MidiMciStop(); MidiMciSeekToStart(); MidiMciPlay(); }else{ MidiMciStop(); MidiMciClose(); } return TRUE; }switch〜case内の意味はヘルプを見て下さい。よく知らないっす。
MCIでのMIDIの演奏では曲のループ機能は無いので、
曲が終わった時に再び再生することで擬似的にループさせます。
m_bMidiLoopはループさせるかどうかの値を持たせています。事前に値をセットしておきます。
MainFrm.h: 後述
MainFrm.cpp:
//MIDIを演奏する
//引数:曲のファイル名、ループさせるかどうか
BOOL CMainFrame::MidiPlay(LPCTSTR pName,BOOL bLoop)
{
m_sMidiName=pName; //演奏するMidiファイル名
m_bMidiLoop=bLoop; //曲をループさせるかどうか
if(m_wMidiDeviceID){
MciMidiStop();
MciMidiClose();
}
if(!MciMidiOpen()) return FALSE;
return MciMidiPlay();
}
//MIDIの演奏を停止する
BOOL CMainFrame::MidiStop()
{
if(!MciMidiStop()) return FALSE;
return MciMidiClose();
}
アプリケーションからは、MidiPlay("なんちゃら.mid",TRUE);の様にして呼び出します。
MainFrm.h:
class CMainFrame : public CFrameWnd
{
〜
public: //音楽関連
BOOL MidiPlay(LPCTSTR pName,BOOL bLoop=TRUE);
BOOL MidiStop();
BOOL IsMidiPlay() const { return m_wMidiDeviceID!=0; }
afx_msg LRESULT OnMidiNotify(WPARAM wParam,LPARAM lParam);
private:
BOOL MciMidiOpen();
BOOL MciMidiPlay();
BOOL MciMidiStop();
BOOL MciMidiSeekToStart();
BOOL MciMidiClose();
CString m_sMidiName; //演奏するファイル名
BOOL m_bMidiLoop; //演奏をループするかどうか
WORD m_wMidiDeviceID; //演奏中のデバイスID
〜
};
冒頭にも書いた通り、mciSendCommandはデバイスにメッセージを送るAPIなので
メッセージループがちゃんと回っている必要があります。
タイマー割り込みのAPIであるtimeSetEventで指定され呼び出されたコールバック関数(TimerProc)の中で
mciSendCommandを使うとうまく鳴りません。ウィンドウがかたまります。
MFCの場合、素直にCWnd::SetTimerで割り込みを発生させてCWnd::OnTimerの中でmciSendCommandを呼び出すと大丈夫です。
また、CDを演奏する場合等も使い回せる部分は結構あるので(MCI_STOPやMCI_CLOSE等) うまくクラス化する事も出来ると思います。
MainFrm.h:
class CMainFrame : public CFrameWnd { 〜 public: //音楽関連 BOOL MidiPlay(LPCTSTR pName,BOOL bLoop=TRUE); afx_msg LRESULT DoMidiPlay(WPARAM wPrm,LPARAM lPrm); 〜 }; #define WM_DO_MIDI_PLAY (WM_USER+0)MainFrm.cpp:
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) //{{AFX_MSG_MAP(CMainFrame) 〜 //}}AFX_MSG_MAP ON_MESSAGE(WM_DO_MIDI_PLAY,CMainFrame::DoMidiPlay) END_MESSAGE_MAP() LRESULT CMainFrame::DoMidiPlay(WPARAM wPrm,LPARAM lPrm) { if(m_wMidiDeviceID){ MidiMciStop(); MidiMciClose(); } MidiMciOpen(); MidiMciPlay(); return 0; } BOOL CMainFrame::MidiPlay(LPCTSTR pName,BOOL bLoop) { m_sMidiName=pName; //演奏するMidiファイル名 m_bMidiLoop=bLoop; //曲をループさせるかどうか PostMessage(WM_DO_MIDI_PLAY); return TRUE; }ただし上記の実装のやり方では、MidiPlay()から帰った時点で演奏が本当に成功したかどうか 分からないところが欠点です。