VBAでwavを鳴らしたら珍現象が起きた話
VBAでwavを鳴らしたらち~んw珍現象が起きた
解決方法を知っている人がいたら、教えろ教えてください。
KitchenTimerクラス
前に、
こういうものを作ったら、職場で割とウケたので、「残り時間を表示するカウントダウンタイマーみてえなやつを作ったら面白えんじゃね?」と思って作ってみた。
……んだけど、ソースコードを職場に置いてきてしまったので、イチから書き直しw
ただ書き直すだけではおもんないんで、無駄にクラス化してみた。
クラスモジュール KitchenTimer
Option Explicit 'Win32API Declaration' 'For Calc Time' Private Declare Function GetTickCount Lib "kernel32" () As Long Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long) 'For Play Sound' Private Declare Function mciSendString _ Lib "winmm.dll" _ Alias "mciSendStringA" ( _ ByVal lpstrCommand As String, _ ByVal lpstrReturnString As String, _ ByVal uReturnLength As Long, _ ByVal hwndCallback As Long) As Long 'Constants' Private Enum TimerStatus tsRemaining tsPaused tsFinished End Enum 'Module Level Variables' Private fsObj As New FileSystemObject 'Module Level Variables; Fields' Private minutes As Long Private seconds As Long Private soundSrcPath_ As String Private totalSeconds As Long Private status_ As TimerStatus Private minDisp As Range Private secDisp As Range Private statDisp As Range Private isInitialized As Boolean Private isPaused As Boolean Private Sub Class_Initialize() totalSeconds = 0 isPaused = True isInitialized = False End Sub 'Properties' Private Property Get Status() As String Dim ret As String Select Case status_ Case tsRemaining: ret = "あと" Case tsPaused: ret = "一時停止" Case tsFinished: ret = "終了!" End Select Status = ret End Property Public Property Get HasDone() As Boolean HasDone = True If status_ = tsFinished Then Exit Property HasDone = False End Property 'Constructor' Public Sub init(ByVal minDisplay As Range, _ ByVal secDisplay As Range, _ ByVal statDisplay As Range, _ Optional ByVal soundSrcPath As String) Set minDisp = minDisplay Set secDisp = secDisplay Set statDisp = statDisplay If soundSrcPath <> "" Then If Not fsObj.FileExists(soundSrcPath) Or _ StrConv(Right(soundSrcPath, 4), vbLowerCase) <> ".wav" Then _ soundSrcPath = "" End If soundSrcPath_ = soundSrcPath isInitialized = True End Sub 'Methods' Public Sub countDown( _ Optional ByVal milliSecPerSec As Long = 1000) If Not isInitialized Then Exit Sub isPaused = False minutes = minDisp.Value If minutes < 0 Then minutes = 0 seconds = secDisp.Value If seconds < 0 Then seconds = 0 If seconds > 59 Then seconds = 0 totalSeconds = minutes * 60 + seconds If minutes + seconds = 0 Then Exit Sub Dim interval As Long interval = milliSecPerSec - 1 status_ = tsRemaining statDisp.Value = Status Do While totalSeconds > 0 If isPaused Then _ status_ = tsPaused: statDisp.Value = Status: _ Exit Do Call waitFor(interval) totalSeconds = totalSeconds - 1 minutes = totalSeconds \ 60 minDisp.Value = minutes seconds = totalSeconds Mod 60 secDisp.Value = seconds Loop DoEvents If totalSeconds > 0 Then status_ = tsPaused If totalSeconds = 0 Then status_ = tsFinished statDisp.Value = Status End If End Sub Public Sub pause() isPaused = Not isPaused End Sub Public Sub reset() isPaused = True totalSeconds = 0 minutes = 0 minDisp.Value = minutes seconds = 0 secDisp.Value = seconds status_ = tsFinished: statDisp.Value = Status End Sub 'Internal Methods' Private Function callGetTickCount() As Long Dim ret As Variant ret = GetTickCount() ret = CDec(ret) If ret < 0 Then ret = ret + 2 ^ 32 callGetTickCount = ret End Function Private Sub callSleep(ByVal milliSeconds As Long) Call Sleep(milliSeconds) End Sub Private Sub waitFor(ByVal milliSeconds As Long) Dim startTime As Long startTime = callGetTickCount() Dim endTime As Long Do Call Sleep(1) DoEvents endTime = callGetTickCount() Loop Until endTime - startTime > milliSeconds End Sub Public Sub playSound() If soundSrcPath_ = "" Then Exit Sub Dim rc As Long soundSrcPath_ = """" & soundSrcPath_ & """" rc = mciSendString("Open " & soundSrcPath_, "", 0, 0) rc = mciSendString("Play " & soundSrcPath_ & " wait", "", 0, 0) rc = mciSendString("Close " & soundSrcPath_, "", 0, 0) status_ = tsPaused End Sub
※上掲コードはご覧のとおり32bit版Officeのものです。64bit版をご利用の方は、しかるべく修正してお使いください。
まあ、64ビット対応っつってもこの場合だったらPtrSafe
入れるだけでいいと思うけど。
擬似コンストラクタinit
メソッドの冒頭を
Public Sub init(ByVal minDisplay As Range, _ ByVal secDisplay As Range, _ ByVal statDisplay As Range, _ Optional ByVal soundSrcPath As String)
このようにしている。
引数が計四つ。
第1引数minDisplay
には、「分」数を表示させるセルを指定。第2引数secDisplay
には、「秒」数を表示するセルを指定。第3引数statDisplay
には、カウントダウンの状態を表示するセルを指定。あと、省略可能の第4引数soundSrcPath
には、カウントダウン終了後に鳴らすwavファイルのフルパスを指定することにしている。
外部に公開するメソッドは「countDown
」、「pause
」、「reset
」の三つ。
何をするメソッドなのかは、名前を見たらわかるだろう。まさにキッチンタイマーを操作するときのアレとかアレである。
使ってみる
たとえば、ワークシート上に
このようにキッチンタイマー風の外観を作成しておき、次のようなコードで利用する。
リスト1
Private Sub testKitchenTimer() Dim kitTimer As KitchenTimer '……(1)' Set kitTimer = New KitchenTimer Call kitTimer.init(minDisplay:=Sh01Main.Range("B2"), _ secDisplay:=Sh01Main.Range("D2"), _ statDisplay:=Sh01Main.Range("A1"), _ soundSrcPath:=ThisWorkbook.Path & _ "\SoundSrc\GOLDEN AXE Voice Yell.wav") Call kitTimer.countDown(Sh01Main.Range("G1").Value) '……(2)' Call kitTimer.playSound '……(3)' End Sub
(1)の
Dim kitTimer As KitchenTimer Set kitTimer = New KitchenTimer Call kitTimer.init(minDisplay:=Sh01Main.Range("B2"), _ secDisplay:=Sh01Main.Range("D2"), _ statDisplay:=Sh01Main.Range("A1"), _ soundSrcPath:=ThisWorkbook.Path & _ "\SoundSrc\GOLDEN AXE Voice Yell.wav")
でインスタンス生成。引数祭りで申しわけないが、
- 「分」数表示セルはB2セル
- 「秒」数表示セルはD2セル
- 状態を表示するセルはA1セル
- カウントダウン終了時に鳴らすwavファイルは、このプロジェクトのあるフォルダの中の「
SoundSrc
」フォルダ内の「GOLDEN AXE Voice Yell.wav
」
とまあ、こんな感じの設定。
画面は
こんなの。
G1セルの「333
」というのは、カウントダウンの1秒を何ミリ秒にするか、という数値。
倍速でカウントダウンさせたければ、「500
」にすれば良い。
目分量だが、「850
」ぐらいまでならバレないw
画面では「333
」にしているので、約3倍速。
(2)の
Call kitTimer.countDown(Sh01Main.Range("G1").Value)
のように、countDown
メソッドの引数として渡してやる。
カウントダウンが終わったら、(3)の
Call kitTimer.playSound
でwavを再生して終わり。
ちなみに、画面ではコマンドボタンを置いているが、今回はcountDown
とplaySound
メソッドしか使わないので、ボタンの存在は無視してください。
ち~んw珍現象
上記リスト1を実行すると、カウントダウン終了→wavファイル鳴動、となる……はずである。ところが、
音が鳴らないのでわかりにくいかも知れないが、「あと0分01秒」で止まっているのがわかるだろうか。
止まっている間にwavが鳴っているのである。
コードの実行順から考えると、表示が「0分00秒」になる→wavが鳴る、の順にならねばならんはずなのに。
おわりに
ちなみに、リスト1の(3)の行にブレークポイントを置いて実行すると、ちゃんと「0分00秒」になってから止まる。
なのに、通し実行すると「0分01秒」のところで一旦音が鳴り、鳴り終わってから「0分00秒」になるのである。
何ででしょう???
誰かわかる人いたら教えろ教えてください。