◆Accessで音楽演奏 その1

[ ホーム ] [ 上へ ] [ Accessでの音楽再生 その2 ]

・mciSendCommandの使用

私のPCのあるフォルダにはごっそり音楽ファイルがあります。CDを購入して mp3形式に変換して溜まったものです。結構増えたのでデータベースで管理したくなりAccessを 使おうと思ったのですが、こういうデータ扱うのはデータベースは得意なほうではないようです。 それでもAccessでは方法があります。一番手っ取り早いのは音楽ファイルのパスをmdbに データベース化してApplication.FollowHyperLinkHyperLinkAddressを 使うことです。それでも結局外部の音楽再生ソフトを起動することになり気に入らない方も いるかもしれません。そこでmciSendCommandという APIを使って力業?で音楽ファイルを管理してみます。

自前で演奏ができる代わりにハイパーリンクと違い実装は結構面倒です。 そこまでする必要がないような気もしますので、こういうやり方もあるという程度に 覚えておくのがいいかもしれません。このAPIはなにせ分かりづらいですが、 こちらでおおまかに使い方を書いていますので御一読ください。

・準備

環境は Windows XP, 2000 + MS-Access 2000で仕様は次の通りです。

音楽ファイルのパスをmdbのテーブルに格納し、 そのテーブルをレコードソースとしたフォームにコマンドボタンを配置してPlay、Pause、 Resume、Stopができるようにします。また、演奏位置を自由に変更もできるようにします。 対応ファイル形式は拡張子がwav、mid、mp3(WAVE、MIDI、MP3)の3形式とします。

<テーブルの作成>

新規のmdbファイルを作成してデータベースウィンドウを 表示してツールバーの挿入(I)から以下のように新規のテーブルを作成してください。

テーブル名 : T_Sounds
フィールド名 データ型
ID オートナンバー(主キー)
F_PATH テキスト型(フィールドサイズは255)

テーブルのデザインができたらF_PATHフィールドに対応する形式の音楽ファイルの フルパスを追加していってください。

<フォームの作成>

2つのフォームを使用します。まずはメインのフォームからです。

(FrmSoundフォーム)
データベースウィンドウを表示してツールバーの挿入(I)から新規のフォームを作成してください。 デザインビューが表示されたらツールバーの表示(V)→プロパティ(P)でプロパティシートを 表示させて以下のように設定してください。

プロパティ名 設定値
レコードソース T_Sounds
コード保持 はい
レコード移動時 [イベント プロシージャ]
読込み時
閉じる時
タイマ時

他の必要なプロパティはVBAコードでForm_Loadイベント プロシージャで設定するのでそのままにして、FrmSoundという名前で保存してください。

次にフォーム内のコントロールを下図のように配置して 各コントロールのプロパティを設定してください。

番号 コントロール名 コントロールの種類 プロパティ名 設定値
1 ID テキストボックス コントロールソース ID (表示→フィールドリストからD & D)
2 F_PATH テキストボックス コントロールソース F_PATH (表示→フィールドリストからD&D)
3 txt_curpos テキストボックス    
4 txt_seekpos テキストボックス    
5 cmd_play コマンドボタン 標題 Play
6 cmd_pause コマンドボタン 標題 Pause
7 cmd_stop コマンドボタン 標題 Stop
  任意 ラベル 標題 現在位置(0-1000)
  任意 ラベル 標題 変更位置(0-1000)

各コントロールの外観はお好みに応じて適当に 変更してください。また、この他の必要なプロパティの設定はForm_Load()イベントプロシージャで 設定するのでそのままにしてください。

(FrmSoundTimerフォーム)
次にFrmSoundフォームで使う2つめのフォームを作成します。このフォームは演奏終了を通知する タイマのためだけのもので、表示もしないのでデザインビューでは何もしません。プロパティシートで 次のプロパティだけを設定してFrmSoundTimerという名前(この名称は後で使うので重要)で 保存してください。

プロパティ名 設定値
コード保持 はい

・CSoundクラス

前セクションで作成したフォームのことはとりあえずおいておいて、 こちらで作成したように演奏を制御するクラスを作成します。 このセクションが今回の本題です。

VC++の時はMCI_PLAYでmciSendCommandを実行する際に終了・中断したとき MM_MCINOTIFYメッセージを送るようにしましたが、VBAの場合、そのメッセージを処理するためには Accessフォームのウィンドウプロシージャに手を入れなければなりません。つまり、 サブクラス化しないといけないのですが、ご存知の方も多いと思いますが動作が非常に不安定に なります。そこで必要に応じて残りの演奏時間を取得してその時間のタイマをセットして処理する 方法をとります。

Alt + F11でVBエディタを起動してください。 ツールバーの挿入(I)→クラス(C)でクラスモジュールを作成したら、F4でプロパティシートを アクティブにしてオブジェクト名をCSoundとしてください。できたらコードウィンドウに 以下のコードを実装してください。

'CSoundクラスモジュール////////////////////////////////
'//

Option Compare Database  
Option Explicit  

Private Declare Function mciSendCommand Lib "winmm.dll" Alias "mciSendCommandA" _  
    (ByVal wDeviceID As Long, _  
     ByVal uMessage As Long, _  
     ByVal dwParam1 As Long, _  
     dwParam2 As Any) As Long  

Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)  
'----------------------------------------------------------------
'定数
Private Const MCI_NOTIFY = &H1&  

Private Const MCI_OPEN = &H803  
    Private Const MCI_OPEN_TYPE = &H2000&  
    Private Const MCI_OPEN_ELEMENT = &H200&  

Private Const MCI_CLOSE = &H804  

Private Const MCI_PLAY = &H806  
    Private Const MCI_FROM = &H4&  
    Private Const MCI_TO = &H8&  

Private Const MCI_PAUSE = &H809  

Private Const MCI_RESUME = &H855  

Private Const MCI_STOP = &H808  

Private Const MCI_SEEK = &H807  
    Private Const MCI_SEEK_TO_START = &H100&  
    Private Const MCI_SEEK_TO_END = &H200&  
    '   Private Const MCI_TO = &H8&

Private Const MCI_SET = &H80D  
    Private Const MCI_SET_TIME_FORMAT = &H400&  
        Private Const MCI_FORMAT_MILLISECONDS = 0  


Private Const MCI_STATUS = &H814  
    Private Const MCI_STATUS_ITEM = &H100&  
    Private Const MCI_STATUS_START = &H200&  
        Private Const MCI_STATUS_LENGTH = &H1&  
        Private Const MCI_STATUS_MODE = &H4&  
        Private Const MCI_STATUS_POSITION = &H2&  

'-------------------------------------------------------------
'構造体
Private Type MCI_OPEN_PARMS  
    dwCallback        As Long  
    wDeviceID         As Long  
    lpstrDeviceType   As String  
    lpstrElementName  As String  
    lpstrAlias        As String  
End Type  

Private Type MCI_PLAY_PARMS  
    dwCallback  As Long  
    dwFrom      As Long  
    dwTo        As Long  
End Type  

Private Type MCI_SEEK_PARMS  
    dwCallback  As Long  
    dwTo        As Long  
End Type  

Private Type MCI_SET_PARMS  
    dwCallback    As Long  
    dwTimeFormat  As Long  
    dwAudio       As Long  
End Type  

Private Type MCI_STATUS_PARMS  
    dwCallback  As Long  
    dwReturn    As Long  
    dwItem      As Long  
    dwTrack     As Integer  
End Type  
'----------------------------------------------------------------
'列挙定数
Public Enum ENUM_DEVTYPE   'デバイスの種類  
    MIDI = 0  
    WAV = 1  
    MP3 = 2  
    AUTO = 3  
End Enum  

Public Enum ENUM_MODE_STATE 'デバイスの状態
    'MCI_STRING_OFFSET = 512  '  if this number is changed you MUST
    MODE_NOT_READY = (512 + 12) '(MCI_STRING_OFFSET + 12)
    MCI_MODE_PAUSE = (512 + 17) '(MCI_STRING_OFFSET + 17) 
    MCI_MODE_PLAY = (512 + 14) '(MCI_STRING_OFFSET + 14) 
    MCI_MODE_STOP = (512 + 13) '(MCI_STRING_OFFSET + 13) 
    MCI_MODE_OPEN = (512 + 18) '(MCI_STRING_OFFSET + 18) 
    MCI_MODE_RECORD = (512 + 15) '(MCI_STRING_OFFSET + 15) 
    MCI_MODE_SEEK = (512 + 16) '(MCI_STRING_OFFSET + 16) 
End Enum 
'----------------------------------------------------------------
'メンバ変数
Private vmtMop As MCI_OPEN_PARMS  


'デストラクタ
Private Sub Class_Terminate()  
    Close_ 
End Sub  

'デバイスOpen
'引数 argsPath : 音楽ファイルのパス  
'     argeType : デバイスの種類(拡張子から自動選択の場合はAUTO)  
'戻値            Open & 初期化成功時 TRUE
Public Function Open_(argsPath As String, _  
                      Optional argeType As ENUM_DEVTYPE = AUTO) As Boolean  
    Dim vvPoint    As Variant  
    Dim vvDevList  As Variant  
    Dim vlRet      As Long  
    Dim vtMsp      As MCI_SET_PARMS  

    '現在のデバイスクローズ
    Close_
    
    'ファイルパスが空文字列の時は終了
    If Len(argsPath) = 0 Then Exit Function  

    'デバイスの種類設定
    vvDevList = Array("Sequencer", "WaveAudio", "MPEGVideo")  

    If argeType = AUTO Then '自動設定
        vvPoint = InStrRev(argsPath, ".", -1, vbBinaryCompare)  

        If IsNull(vvPoint) Or vvPoint = 0 Then  
            vmtMop.lpstrDeviceType = ""  

        Else 
            Select Case Mid(argsPath, vvPoint + 1)  
                Case "Mid"  
                    vmtMop.lpstrDeviceType = vvDevList(0)  
                Case "Wav"  
                    vmtMop.lpstrDeviceType = vvDevList(1)  
                Case "Mp3"  
                    vmtMop.lpstrDeviceType = vvDevList(2)  
                Case Else  
                    vmtMop.lpstrDeviceType = ""  
            End Select  
        End If  

    Else 'デバイス種類が指定されている 

        vmtMop.lpstrDeviceType = vvDevList(argeType)  
    End If  

    'ファイル名設定
    vmtMop.lpstrElementName = argsPath  

    'デバイスOpen
    If mciSendCommand(0, MCI_OPEN, _  
                      MCI_OPEN_TYPE Or MCI_OPEN_ELEMENT, vmtMop) <> 0 Then  
        Exit Function  
    End If  

    '時間単位をミリ秒に設定
    vtMsp.dwTimeFormat = MCI_FORMAT_MILLISECONDS  
    mciSendCommand vmtMop.wDeviceID, MCI_SET, MCI_SET_TIME_FORMAT, vtMsp  

    Open_ = True  
End Function  

'デバイス状態
'戻値 MCI_MODE_NOT_READY, MCI_MODE_PAUSE,  MCI_MODE_PLAY, MCI_MODE_STOP,  
'     MCI_MODE_OPEN,      MCI_MODE_RECORD, MCI_MODE_SEEKまたは0
Public Function Status_() As ENUM_MODE_STATE  
    Dim vtMsp  As MCI_STATUS_PARMS  

    vtMsp.dwItem = MCI_STATUS_MODE  

    If mciSendCommand(vmtMop.wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, vtMsp) <> 0 Then  
        Status_ = 0  
    Else 
        Status_ = vtMsp.dwReturn  
    End If  

End Function  

'現在位置の取得
'戻値 0-1000の範囲で現在位置を返却(失敗時は-1)
Public Function CurrentPosition_() As Long  
    Dim vtMspCur    As MCI_STATUS_PARMS  
    Dim vtMspTotal  As MCI_STATUS_PARMS  

    If Not GetPos(vtMspCur, vtMspTotal) Then  
        CurrentPosition_ = -1: Exit Function  
    End If  

    CurrentPosition_ = vtMspCur.dwReturn / vtMspTotal.dwReturn * 1000  
End Function  

'残り時間取得
'戻値 ミリ秒単位単位で残り演奏時間の返却(失敗時は-1)
Public Function RestMillSec_() As Currency  
    Dim vtMspCur    As MCI_STATUS_PARMS  
    Dim vtMspTotal  As MCI_STATUS_PARMS  

    If Not GetPos(vtMspCur, vtMspTotal) Then  
        RestMillSec_ = -1: Exit Function  
    End If  

    RestMillSec_ = vtMspTotal.dwReturn - vtMspCur.dwReturn  
End Function  

'現在位置とTotal演奏時間の取得
Private Function GetPos(argtMspCur As MCI_STATUS_PARMS, _  
                        argtMspTotal As MCI_STATUS_PARMS) As Boolean  

    '現在の位置をミリ秒単位で取得
    argtMspCur.dwItem = MCI_STATUS_POSITION  
    If mciSendCommand(vmtMop.wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, argtMspCur) <> 0 Then  
        Exit Function  
    End If  

    '全体の長さをミリ秒単位で取得
    argtMspTotal.dwItem = MCI_STATUS_LENGTH  
    If mciSendCommand(vmtMop.wDeviceID, MCI_STATUS, _
                      MCI_STATUS_ITEM, argtMspTotal) <> 0 Then  
        Exit Function  
    End If  

    GetPos = True  
End Function  

'演奏開始
'引数 arglFrom : 演奏開始位置(0-1000)で設定(範囲外は現在位置から演奏) 
'     arglTo   : 演奏終了位置(0-1000)で設定(範囲外は最後まで演奏)  
'戻値 演奏成功時 TRUE
Public Function Play_(Optional arglFrom As Long = -1, _  
                      Optional arglTo As Long = -1) As Boolean  
    Dim vtMsp   As MCI_STATUS_PARMS  
    Dim vtMpp   As MCI_PLAY_PARMS  
    Dim vyFrom  As Boolean  
    Dim vyTo    As Boolean  

    '引数の有効性確認
    vyFrom = IIf(arglFrom < 0 Or arglFrom > 1000, False, True)  
    vyTo = IIf(arglTo < 0 Or arglTo > 1000, False, True)  

    If vyFrom Or vyTo Then 'どちらかの引数が有効の場合
        '全体の長さをミリ秒単位で取得
        vtMsp.dwItem = MCI_STATUS_LENGTH  

        If mciSendCommand(vmtMop.wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, vtMsp) <> 0 Then  
            vyFrom = False: vyTo = False  
        End If  

        '開始位置の特定
        If vyFrom Then vtMpp.dwFrom = vtMsp.dwReturn * arglFrom / 1000  
        '終了位置の特定
        If vyTo Then vtMpp.dwTo = vtMsp.dwReturn * arglTo / 1000  
    End If  

    '演奏開始
    If mciSendCommand(vmtMop.wDeviceID, MCI_PLAY, _  
                      IIf(vyFrom, MCI_FROM, 0) Or IIf(vyTo, MCI_TO, 0), _  
                      vtMpp) <> 0 Then  
        Exit Function  
    End If  

    Play_ = True  
End Function  

'演奏中止
'戻値 演奏中止成功 TRUE
Public Function Stop_() As Boolean  
    If mciSendCommand(vmtMop.wDeviceID, MCI_STOP, 0, ByVal 0) <> 0 Then  
        Exit Function  
    End If  

    If mciSendCommand(vmtMop.wDeviceID, MCI_SEEK, MCI_SEEK_TO_START, ByVal 0) <> 0 Then  
        Exit Function  
    End If  

    Stop_ = True  
End Function  

'デバイスClose (演奏中、一時停止の場合はStopを実行)  
'戻値 Close成功時 TRUE
Public Function Close_() As Boolean  
    Dim vlRet  As Long  

    If Status_ = MCI_MODE_PLAY Or Status_ = MCI_MODE_PAUSE Then  
        Stop_ 
    End If  

    vlRet = mciSendCommand(vmtMop.wDeviceID, MCI_CLOSE, 0, ByVal 0)  
    With vmtMop  
        .dwCallback = 0  
        .lpstrAlias = ""  
        .lpstrDeviceType = ""  
        .lpstrElementName = ""  
        .wDeviceID = 0  
    End With  

    Close_ = Not vlRet  
End Function  

'現在位置の検索移動
'引数 arglPos : 移動する位置を0-1000で設定  
'戻値 移動成功時 TRUE
Public Function Seek_(Optional arglPos As Long = 0) As Boolean  
    Dim vtMstatusp   As MCI_STATUS_PARMS  
    Dim vtMseekp     As MCI_SEEK_PARMS  
    Const clDist     As Long = 200  
    Const clMaxWait  As Long = 5000  
    Dim i            As Long  

    '範囲外のとき
    If arglPos < 0 Or arglPos > 1000 Then Exit Function  

    If arglPos = 0 Then '最初に戻る 
        If mciSendCommand(vmtMop.wDeviceID, MCI_SEEK, MCI_SEEK_TO_START, ByVal 0) <> 0 Then
            Exit Function  
        End If  
    ElseIf arglPos = 1000 Then '最後に移す 
        If mciSendCommand(vmtMop.wDeviceID, MCI_SEEK, MCI_SEEK_TO_END, ByVal 0) <> 0 Then  
            Exit Function  
        End If  
    Else 
        '全体の長さ取得
        vtMstatusp.dwItem = MCI_STATUS_LENGTH  
        If mciSendCommand(vmtMop.wDeviceID, MCI_STATUS, _
                          MCI_STATUS_ITEM, vtMstatusp) <> 0 Then  
            Exit Function  
        End If  

        '移動すべき位置を取得
        vtMseekp.dwTo = vtMstatusp.dwReturn * arglPos / 1000  
        '現在位置を移動
        If mciSendCommand(vmtMop.wDeviceID, MCI_SEEK, MCI_TO, vtMseekp) <> 0 Then  
            Exit Function  
        End If  
    End If  

    'Seek完了までWaitする
    For i = 0 To clMaxWait Step clDist  
        If Status_() <> MCI_MODE_SEEK Then Exit For  
        Sleep clDist  
    Next i  

    Seek_ = IIf(i > clMaxWait, False, True)  
End Function  

'一時停止
'戻値 一時停止成功時 TRUE
Public Function Pause_() As Boolean  
    If Status_ <> MCI_MODE_PLAY Then Exit Function  

    If mciSendCommand(vmtMop.wDeviceID, MCI_PAUSE, 0, ByVal 0) <> 0 Then  
        Exit Function  
    End If  

    Pause_ = True  
End Function  

'再開
'戻値 再開成功時 TRUE
Public Function Resume_() As Boolean  
    If Status_ <> MCI_MODE_PAUSE Then Exit Function  

    If mciSendCommand(vmtMop.wDeviceID, MCI_RESUME, 0, ByVal 0) <> 0 Then  
        Exit Function  
    End If  

    Resume_ = True  
End Function

基本的にこのCSoundオブジェクトのOpen_メソッドでファイルをオープンし、 Play_メソッドで演奏ができるので、あとはお好みでこれを使ってアプリを作成していけばいいですが、 次で使い方の一例として前セクションで作ったフォームを使って実際の演奏をしてみます。