MCIコマンドによるCD再生

概要:CDの再生とトラック変更

単純に再生させるだけならCDも音楽ファイルも同じです。
しかしCDにはトラックがあります。
トラックを変更するにはどうしたら良いのか?
また、CD特有の挙動についても焦点を当てていきます。

■MCIデバイスを開く

デバイスタイプには "CDAudio" を指定します。
ファイル名は指定しません(場所は光学ドライブに決まり切っている)。

static MCI_OPEN_PARMS mop;

//オープン
mop.lpstrDeviceType="CDAudio";
mciSendCommand(NULL,MCI_OPEN,MCI_OPEN_TYPE,(DWORD)&mop);

■時間フォーマットの変更

トラックを変更するという事は、再生開始位置を現在の時間単位で指定する事に相当します。
時間単位の初期設定はミリ秒です。
ミリ秒単位で任意のトラックを指定する事は困難なので、時間単位を変更する必要があります。

時間単位の変更は MCI_SET コマンドを使います。
ウンザリする量のフラグが用意されていますが、時間単位の変更には MCI_SET_TIME_FORMAT フラグを使います。
時間単位は「トラック番号、分、秒、フレーム数」で指定できる MCI_FORMAT_TMSF にします。
他にも色々あるのでリファレンスをご覧下さい。

時間単位は MCI_SET_PARMS 構造体の dwTimeFormat メンバに設定します。

typedef struct {
  DWORD_PTR dwCallback;
  DWORD dwTimeFormat;
  DWORD dwAudio;
} MCI_SET_PARMS;

MCI_SET_PARMS msp;

//時間フォーマットの変更
msp.dwTimeFormat=MCI_FORMAT_TMSF;
mciSendCommand(mop.wDeviceID,MCI_SET,MCI_SET_TIME_FORMAT,(DWORD)&msp);

■トラック数の取得

最後のトラックに到達した時の為にトラック数を取得します。
わざわざ取得する理由は後述します。
コマンドは MCI_STATUS 、フラグは MCI_STATUS_ITEM を使います。
これも大量のフラグが用意されているので、リファレンスをご覧下さい。
そして、トラック数を知りたい場合は MCI_STATUS_NUMBER_OF_TRACKS を
MCI_STATUS_PARMS 構造体の dwItem メンバに設定します。

typedef struct {
  DWORD_PTR dwCallback;
  DWORD dwReturn;
  DWORD dwItem;
  DWORD dwTrack;
} MCI_STATUS_PARMS;


トラック数の取得に成功した場合は、再生可能なトラック数が dwReturn メンバに格納されます。

MCI_STATUS_PARMS status;
static DWORD track_sum;

//トラック数の取得
status.dwItem=MCI_STATUS_NUMBER_OF_TRACKS;
mciSendCommand(mop.wDeviceID,MCI_STATUS,MCI_STATUS_ITEM,(DWORD)&status);
track_sum=status.dwReturn;

■再生

CDが音楽ファイルと一番違うのは再生を指示した以降のトラックも再生されてしまう事です。
最後のトラックを再生すると停止します。
音楽プレイヤーならいいかもしれませんが、ゲームでは余計なお世話、迷惑な処理でしかありません。
従って、開始トラックと停止トラックを指定して、再生する範囲を限定します。

一曲だけ再生させたい場合は開始トラックの一つ後ろを停止トラックに設定します。
開始位置は MCI_PLAY_PARMS 構造体の dwFrom メンバ、
停止位置は MCI_PLAY_PARMS 構造体の dwTo メンバに指定します。
この時、「トラック番号、分、秒、フレーム数」の形式で指定する MCI_MAKE_TMSF マクロを使います。

DWORD MCI_MAKE_TMSF(
  BYTE tracks,
  BYTE minutes,
  BYTE seconds,
  BYTE frames
);


tracks が最下位バイトなのでトラック番号を指定するだけならこのマクロを使う必要はありませんが。
frame はCDには関係ありません(VCRの指定に使います)。

トラック番号は 1 から始まります( 0 番のトラックは存在しません)。
従って、有効なトラック番号は 1 〜 トラック数 です。

もし存在しないトラックを指定して再生を試みた場合は何も起こりません(エラーは発生します)。
再生中であれば、現在の再生が継続されます。
従って、最後のトラックを再生させたい場合は
停止トラック(最後のトラックの一つ後ろを指定する事になる)の指定が不正なので、再生が行われません。
この場合は停止トラックを指定しないで、開始トラックだけを指定します。

また、存在しないトラックを指定して再生を試みた場合、mciSendCommand 関数はエラーを返すので、
エラーが検出されたら再生を停止させてしまいましょう。
不正な指示である事が容易に確認できます。

MCI_PLAY_PARMS mpp;
static DWORD track;

case WM_LBUTTONDOWN:
    track++;
    mpp.dwFrom=MCI_MAKE_TMSF(track,0,0,0);
    mpp.dwTo=MCI_MAKE_TMSF(track+1,0,0,0);
    //再生
    if(mpp.dwFrom>=1 && mpp.dwTo<=track_sum){
        mciSendCommand(mop.wDeviceID,MCI_PLAY,MCI_FROM | MCI_TO,(DWORD)&mpp);
    }else if(mpp.dwFrom == track_sum){
        mciSendCommand(mop.wDeviceID,MCI_PLAY,MCI_FROM,(DWORD)&mpp);
    }else{
        mciSendCommand(mop.wDeviceID,MCI_STOP,0,0);
        track=0;
    }
    InvalidateRect(hWnd,NULL,TRUE);
    return 0;

■描画

再生中のトラック番号を表示します。

HDC hdc;
PAINTSTRUCT ps;
char str[32];

case WM_PAINT:
    hdc=BeginPaint(hWnd,&ps);
    wsprintf(str,"再生中のトラック = %d",track);
    TextOut(hdc,0,0,str,lstrlen(str));
    wsprintf(str,"( 0 番のトラックは存在しません)");
    TextOut(hdc,0,20,str,lstrlen(str));
    EndPaint(hWnd,&ps);
    return 0;

■MCIデバイスを閉じる

CDはMCIデバイスをクローズしても停止しません。
もし停止せずにクローズしてしまったら最後のトラックを再生し終えるまで停止しないので大変な事になります。
必ず停止させてからクローズしましょう。

case WM_DESTROY:
    //停止 CDはクローズしても停止しない!
    mciSendCommand(mop.wDeviceID,MCI_STOP,0,0);
    //クローズ
    mciSendCommand(mop.wDeviceID,MCI_CLOSE,0,0);
    PostQuitMessage(0);
    return 0;

★☆ ソースファイル表示 ☆★


戻る / ホーム