C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

同時再生したものを別々で停止する方法について

[トピック内 4 記事 (1 - 4 表示)]  << 0 >>

■94708 / inTopicNo.1)  同時再生したものを別々で停止する方法について
  
□投稿者/ たぁー (1回)-(2020/05/08(Fri) 16:41:09)

分類:[VB.NET/VB2005 以降] 

Visual Studio 2019 CommunityのVBで開発しています。

作成しているプログラムの内容としては、
カウントダウンタイマーで、タイマー終了後に音再生とラベルのフラッシュを行わせています。
また、タイマーは複数押下する場合もあるので終了時に別々の音が再生されるようには出来たと思いますが
終了時に片方の終了ボタンを押すとどちらも停止してしまいます。
終了時にPlaySound(IntPtr.Zero, IntPtr.Zero, PlaySoundFlags.SND_PURGE)としていることから
開始されたものを指定できていないためPlaySound全体が停止してしまうということは理解していますが
これを別々で停止する方法などはないでしょうか?

また、自分では同時再生で別々の音が流れていると思っていますが間違っていましたら
その辺についてもご指摘頂ければありがたいです。

ソースコードは以下の通りです。
-----
Imports System.Threading.Tasks

Public Class Form1
    ' サウンドを再生するWin32 APIの宣言
    Public Enum PlaySoundFlags
        SND_SYNC = &H0
        SND_ASYNC = &H1
        SND_NODEFAULT = &H2
        SND_MEMORY = &H4
        SND_LOOP = &H8
        SND_NOSTOP = &H10
        SND_NOWAIT = &H2000
        SND_ALIAS = &H10000
        SND_ALIAS_ID = &H110000
        SND_FILENAME = &H20000
        SND_RESOURCE = &H40004
        SND_PURGE = &H40
        SND_APPLICATION = &H80
    End Enum
    <Runtime.InteropServices.DllImport("winmm.dll", CharSet:=Runtime.InteropServices.CharSet.Auto)>
    Private Shared Function PlaySound(ByVal pszSound As IntPtr, ByVal hmod As IntPtr, ByVal fdwSound As PlaySoundFlags) As Boolean
    End Function
    Private pH_gcHandle As Runtime.InteropServices.GCHandle
    Private NH3_A_gcHandle As Runtime.InteropServices.GCHandle
    Private pH_waveBuffer As Byte() = Nothing
    Private NH3_A_waveBuffer As Byte() = Nothing

    ' # カウントダウンタイマー変数
    Private pH_dTime As Date                                                ' 水素イオン指数
    Private NH3_A_dTime As Date  ' アンモニア

    ' # サウンドリソース変数
    Private pH_wav As IO.Stream = My.Resources.pH
    Private NH3_A_wav As IO.Stream = My.Resources.NH3_A

    ' # カウントダウン終了フラッシュ変数
    Private pH_Flash_Result As Boolean                                                                      ' 水素イオン指数
    Private NH3_A_Flash_Result As Boolean                                                                   ' アンモニア

    ' # WAVEファイルを再生、停止
    ' 水素イオン指数
    Private Sub pH_PlaySound()
        If Not pH_waveBuffer Is Nothing Then
            pH_StopSound()
        End If

        ' byte配列にする
        pH_waveBuffer = New Byte(pH_wav.Length) {}
        pH_wav.Read(pH_waveBuffer, 0, pH_waveBuffer.Length)

        ' GCによって移動されないようにする
        pH_gcHandle = Runtime.InteropServices.GCHandle.Alloc(pH_waveBuffer, Runtime.InteropServices.GCHandleType.Pinned)

        ' 非同期再生
        PlaySound(pH_gcHandle.AddrOfPinnedObject(), IntPtr.Zero, PlaySoundFlags.SND_MEMORY Or PlaySoundFlags.SND_LOOP Or PlaySoundFlags.SND_ASYNC)
    End Sub

    Private Sub pH_StopSound()
        If pH_waveBuffer Is Nothing Then
            Return
        End If

        ' 再生しているWAVEを停止する
        PlaySound(IntPtr.Zero, IntPtr.Zero, PlaySoundFlags.SND_PURGE)

        ' 解放する
        pH_gcHandle.Free()
        pH_waveBuffer = Nothing
    End Sub
    ' アンモニア
    Private Sub NH3_A_PlaySound()
        If Not NH3_A_waveBuffer Is Nothing Then
            NH3_A_StopSound()
        End If

        ' byte配列にする
        NH3_A_waveBuffer = New Byte(NH3_A_wav.Length) {}
        NH3_A_wav.Read(NH3_A_waveBuffer, 0, NH3_A_waveBuffer.Length)

        ' GCによって移動されないようにする
        NH3_A_gcHandle = Runtime.InteropServices.GCHandle.Alloc(NH3_A_waveBuffer, Runtime.InteropServices.GCHandleType.Pinned)

        ' 非同期再生
        PlaySound(NH3_A_gcHandle.AddrOfPinnedObject(), IntPtr.Zero, PlaySoundFlags.SND_MEMORY Or PlaySoundFlags.SND_LOOP Or PlaySoundFlags.SND_ASYNC)
    End Sub

    Private Sub NH3_A_StopSound()
        If NH3_A_waveBuffer Is Nothing Then
            Return
        End If

        ' 再生しているWAVEを停止する
        PlaySound(IntPtr.Zero, IntPtr.Zero, PlaySoundFlags.SND_PURGE)

        ' 解放する
        NH3_A_gcHandle.Free()
        NH3_A_waveBuffer = Nothing
    End Sub

    ' # ラベル点滅
    ' pH
    Private Async Sub pH_Flash(ByVal ObjLabel As Object)
        While pH_Flash_Result
            Await Task.Delay(100)
            ObjLabel.Visible = Not ObjLabel.Visible
        End While
        ObjLabel.Visible = True
    End Sub

    Private Async Sub NH3_A_Flash(ByVal ObjLabel As Object)
        While NH3_A_Flash_Result
            Await Task.Delay(100)
            ObjLabel.Visible = Not ObjLabel.Visible
        End While
        ObjLabel.Visible = True
    End Sub

    ' pH (水素イオン指数)
    Private Sub pH_Btn_Click(sender As Object, e As EventArgs) Handles pH_Btn.Click
        pH_dTime = pH.Text
        If pH_Btn.Text = "開始" Then
            pH_Timer.Enabled = True
            pH_Btn.Text = "終了"
        ElseIf pH_Btn.Text = "終了" Then
            pH_StopSound()
            pH_Flash_Result = False
            pH.BackColor = DefaultBackColor
        End If
    End Sub

    Private Sub pH_Timer_Tick(sender As Object, e As EventArgs) Handles pH_Timer.Tick
        pH_dTime = pH_dTime.AddSeconds(-1)
        pH.Text = pH_dTime

        If pH_dTime = "0:0:0" Then
            pH_Timer.Enabled = False
            pH_PlaySound()
            pH.BackColor = Color.Red
            pH_Flash_Result = True
            pH_Flash(pH)
        Else
            pH.BackColor = Color.Yellow
        End If
    End Sub

    ' NH3 (アンモニア)
    Private Sub NH3_Btn_Click(sender As Object, e As EventArgs) Handles NH3_A_Btn.Click
        NH3_A_dTime = NH3_A.Text
        If NH3_A_Btn.Text = "開始" Then
            NH3_A_Timer.Enabled = True
            NH3_A_Btn.Text = "終了"
        ElseIf NH3_A_Btn.Text = "終了" Then
            NH3_A_StopSound()
            NH3_A_Flash_Result = False
            NH3_A.BackColor = DefaultBackColor
        End If
    End Sub

    Private Sub NH3_A_Timer_Tick(sender As Object, e As EventArgs) Handles NH3_A_Timer.Tick
        NH3_A_dTime = NH3_A_dTime.AddSeconds(-1)
        NH3_A.Text = NH3_A_dTime

        If NH3_A_dTime = "0:0:0" Then
            NH3_A_Timer.Enabled = False
            NH3_A_PlaySound()
            NH3_A.BackColor = Color.Red
            NH3_A_Flash_Result = True
            NH3_A_Flash(NH3_A)
        Else
            NH3_A.BackColor = Color.Yellow
        End If
    End Sub
End Class
-----

以上、宜しくお願い致します。

引用返信 編集キー/
■94719 / inTopicNo.2)  Re[1]: 同時再生したものを別々で停止する方法について
□投稿者/ Hongliang (1026回)-(2020/05/09(Sat) 15:47:08)
環境依存なのかもしれませんが、私のところでは、PlaySound関数でa.wavを非同期再生中にb.wavを非同期再生させようとすると、a.wavの再生が停止してしまって「同時再生」になりませんでした。
PlaySound関数の引数的にも片方だけを識別して止めるなんてことはできなさそうなので、PlaySound関数でやるとしたら音を鳴らすだけの実行ファイルを作って、そのプロセス起動/終了で再生を制御するぐらいしかないと思います。
(プロセスの制御はSystem.Diagnostics.Processクラスで可能です)

標準ライブラリは音声のサポートが非常に少なく、System.Media.SoundPlayerのほかはSystem.Windows.Media.MediaPlayerぐらいしかないですね。
System.Media.SoundPlayerはPlaySound関数のラッパ(オンメモリデータを再生する部分でバグあり)なので論外。
System.Windows.Media.MediaPlayerはオンメモリのデータを再生する手段がなさそう。

サードパーティ製の音声ライブラリで真っ先に思いつくのはNAudioです。
WaveOutを複数作るのが正しいのかどうか分からないですが、とりあえずこれを使ってみてはいかがでしょうか。

外部ライブラリは使えないとなると、MCIでオンメモリデータの再生も不可能ではないですが面倒ですね…。
最低限の音楽再生機能部分だけならMedia Foundationをラップするのもまあ非現実的というほどではない、かな?
引用返信 編集キー/
■94720 / inTopicNo.3)  Re[2]: 同時再生したものを別々で停止する方法について
□投稿者/ Hongliang (1027回)-(2020/05/09(Sat) 21:28:33)
NAudioでWaveOut1つでどうにかできたのでメモ。

音源となる各種wavバイナリについてそれぞれ、WaveFileReaderを経由してWaveChannel32を作成。
ループさせる場合はNAudioのデモページにLoopStreamというのが公開されてるのでこれを間に挟むとよい。
https://github.com/naudio/NAudio/blob/master/NAudioDemo/LoopStream.cs
SilenceProviderを、上記で作ったWaveChannel32のWaveFormatを指定して作成。
MixingWaveProvider32を作って、SilenceProviderをAddInputStream。
WaveOutを作ってMixingWaveProvider32でInitし、Play。
鳴らせたいタイミングで、鳴らせたいWaveChannel32をMixingWaveProvider32にAddInputStream。
止めたいタイミングで、MixingWaveProvider32からRemoveInputStream。
一旦RemoveしたWaveChannel32を再びAddすると、Removeしたときの再生位置からの再生になる。先頭から再生したい場合はPosition = 0にする。

// ずっと無音を鳴らしっぱなしなので消費電力にやさしくない可能性あり。
引用返信 編集キー/
■94732 / inTopicNo.4)  Re[3]: 同時再生したものを別々で停止する方法について
□投稿者/ たぁー (2回)-(2020/05/11(Mon) 01:00:48)
No94720 (Hongliang さん) に返信
ありがとうございました。
NAudioじたいを知らなかったので、VB.Net標準?だけで何とかしようとしておりました。
違う音を2つ用意して試してみたところ全然同時再生は出来ていなかったのでNAudioでのループ再生をなんとかあちこちのサイトを見て出来るようになりました。
また、止めるのも制御したいということだったのですが、これも無事解決しました。

ありがとうございました。
解決済み
引用返信 編集キー/

このトピックをツリーで一括表示


トピック内ページ移動 / << 0 >>

このトピックに書きこむ