locked
Metronome Flash Button Help RRS feed

  • Question

  • Hi,

    I built a metronome in my app based on dbasnett's code.  ( https://social.msdn.microsoft.com/Forums/en-US/186110b7-a3ec-4047-b5fa-c6260f50c84c/metronome-quality-timer-at-last?forum=Vsexpressvb )   I need to have the btnStart flash on the beat when it's playing the wav file.  Because of the structure of the code I can't seem to make it flash.  I tried it in the MakeSound sub before the threading and after.   Thoughts?   Code is below:

     Dim myMetro As New Metronome
        Private Sub btnStart_Click(ByVal sender As System.Object, _
                                  ByVal e As System.EventArgs) Handles Button2.Click
            myMetro.BPM = 120
            myMetro.StartMetronome()
        End Sub

        Private Sub Form1_FormClosing(ByVal sender As Object, _
                                      ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
            myMetro.Dispose()
        End Sub



    Public Class Metronome
        Implements IDisposable
        Private _bpm As Integer
        Private _beatTS As TimeSpan
        Private _OnOff As Long '<=0 = off, > 0 = on
        Private _beatMS As Long

        Public Property BPM() As Integer
            Get
                Return Me._bpm
            End Get
            Set(ByVal value As Integer)
                Me._bpm = value
                Me._beatTS = New TimeSpan(0, 0, 0, 0, 60000 \ Me._bpm) 'set ms between beats
                Threading.Interlocked.Exchange(Me._beatMS, CLng(Me._beatTS.TotalMilliseconds))
            End Set
        End Property

        Private tmkThrd As System.Threading.Thread
        Public Sub New()
            Me.BPM = -1
            Threading.Interlocked.Exchange(Me._OnOff, 0L) 'set to off
            'start thread to do timekeeping
            Me.tmkThrd = New System.Threading.Thread(AddressOf Me.TimeKeeper)
            't.IsBackground can be set to false IF the application calls Dispose
            't.IsBackground = True
            Me.tmkThrd.IsBackground = False
            Me.tmkThrd.Priority = Threading.ThreadPriority.Highest
            tmkThrd.Start()
        End Sub

        Public Sub New(ByVal BPM As Integer)
            MyBase.New()
            Me.BPM = BPM 'set beats per minute
        End Sub

        Public Sub StartMetronome()
            Threading.Interlocked.Exchange(Me._OnOff, 1L) 'start
            Me.tmkThrd.Interrupt() 'wake the thread
        End Sub

        Public Sub StopMetronome()
            Threading.Interlocked.Exchange(Me._OnOff, 0L) 'stop
        End Sub

        Private Sub TimeKeeper()
            Dim stpw As New Stopwatch
            Dim newSound As New Action(AddressOf MakeSound) 'play sound
            stpw.Start()
            Do
                Dim btMS As Long = Threading.Interlocked.Read(Me._beatMS)
                Dim offSlp As Long = btMS \ 4
                If Threading.Interlocked.Read(Me._OnOff) = 0L Then 'not running
                    stpw.Reset()
                    Try
                        Threading.Thread.Sleep(100)
                    Catch ex As Exception
                    End Try
                ElseIf Not stpw.IsRunning Then
                    stpw.Start()
                    Threading.Interlocked.Exchange(Me.isPlaying, 0L)
                ElseIf stpw.ElapsedMilliseconds + offSlp >= btMS Then
                    'in the ballpark
                    Do While stpw.ElapsedMilliseconds < btMS
                        'wait in a tight loop for the time to pass
                    Loop
                    stpw.Reset() 'reset and start the stopwatch before playing the sound
                    stpw.Start()
                    If Threading.Interlocked.Read(Me.isPlaying) = 0L Then newSound.Invoke()
                Else 'running, but not time to play sound
                    Threading.Thread.Sleep(CInt(offSlp))
                End If
            Loop While Not Me.disposed
        End Sub

        Private isPlaying As Long
        Private Sub MakeSound()
            Threading.Interlocked.Increment(Me.isPlaying)
            My.Computer.Audio.PlaySystemSound(Media.SystemSounds.Asterisk)
            Threading.Interlocked.Decrement(Me.isPlaying)
        End Sub

    #Region "Dispose"
        Private disposed As Boolean

        Protected Overridable Sub Dispose(ByVal disposing As Boolean)
            If Not Me.disposed Then
                If disposing Then
                    ' TODO: free managed resources when explicitly called 
                End If
                ' TODO: free shared unmanaged resources 
            End If
            Me.disposed = True
        End Sub

        Public Sub Dispose() Implements IDisposable.Dispose
            Dispose(True)
            GC.SuppressFinalize(Me)
        End Sub

        Protected Overrides Sub Finalize()
            Call Me.Dispose(False)
            Call MyBase.Finalize()
        End Sub
    #End Region
    End Class

    Monday, August 21, 2017 6:26 PM

Answers

  • This shows how to add an event to the class. When the sound plays the event is called. The event handler is in the form and changes the button backcolor. You can play with it for the effect you want.

    Public Class Form5
        Private WithEvents myMetro As New Metronome
    
        Private Sub Metronome_MetroTick(sender As Object, e As EventArgs) Handles myMetro.MetroTick
            Static thistick As Boolean
            thistick = Not thistick
            If thistick Then
                Button1.BackColor = Color.Red
            Else
                Button1.BackColor = SystemColors.ButtonFace
            End If
    
        End Sub
    
        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            myMetro.BPM = 120
            myMetro.StartMetronome()
        End Sub
    End Class
    
    Public Class Metronome
        Implements IDisposable
    
        Public Event MetroTick As EventHandler
    
        Private _bpm As Integer
        Private _beatTS As TimeSpan
        Private _OnOff As Long '<=0 = off, > 0 = on
        Private _beatMS As Long
    
        Public Property BPM() As Integer
            Get
                Return Me._bpm
            End Get
            Set(ByVal value As Integer)
                Me._bpm = value
                Me._beatTS = New TimeSpan(0, 0, 0, 0, 60000 \ Me._bpm) 'set ms between beats
                Threading.Interlocked.Exchange(Me._beatMS, CLng(Me._beatTS.TotalMilliseconds))
            End Set
        End Property
    
        Private tmkThrd As System.Threading.Thread
        Public Sub New()
            Me.BPM = -1
            Threading.Interlocked.Exchange(Me._OnOff, 0L) 'set to off
            'start thread to do timekeeping
            Me.tmkThrd = New System.Threading.Thread(AddressOf Me.TimeKeeper)
            't.IsBackground can be set to false IF the application calls Dispose
            't.IsBackground = True
            Me.tmkThrd.IsBackground = False
            Me.tmkThrd.Priority = Threading.ThreadPriority.Highest
            tmkThrd.Start()
        End Sub
    
        Public Sub New(ByVal BPM As Integer)
            MyBase.New()
            Me.BPM = BPM 'set beats per minute
        End Sub
    
        Public Sub StartMetronome()
            Threading.Interlocked.Exchange(Me._OnOff, 1L) 'start
            Me.tmkThrd.Interrupt() 'wake the thread
        End Sub
    
        Public Sub StopMetronome()
            Threading.Interlocked.Exchange(Me._OnOff, 0L) 'stop
        End Sub
    
        Private Sub TimeKeeper()
            Dim stpw As New Stopwatch
            Dim newSound As New Action(AddressOf MakeSound) 'play sound
            stpw.Start()
            Do
                Dim btMS As Long = Threading.Interlocked.Read(Me._beatMS)
                Dim offSlp As Long = btMS \ 4
                If Threading.Interlocked.Read(Me._OnOff) = 0L Then 'not running
                    stpw.Reset()
                    Try
                        Threading.Thread.Sleep(100)
                    Catch ex As Exception
                    End Try
                ElseIf Not stpw.IsRunning Then
                    stpw.Start()
                    Threading.Interlocked.Exchange(Me.isPlaying, 0L)
                ElseIf stpw.ElapsedMilliseconds + offSlp >= btMS Then
                    'in the ballpark
                    Do While stpw.ElapsedMilliseconds < btMS
                        'wait in a tight loop for the time to pass
                    Loop
                    stpw.Reset() 'reset and start the stopwatch before playing the sound
                    stpw.Start()
                    If Threading.Interlocked.Read(Me.isPlaying) = 0L Then newSound.Invoke()
                Else 'running, but not time to play sound
                    Threading.Thread.Sleep(CInt(offSlp))
                End If
            Loop While Not Me.disposed
        End Sub
    
        Private isPlaying As Long
        Private Sub MakeSound()
            RaiseEvent MetroTick(Me, New EventArgs)
            Threading.Interlocked.Increment(Me.isPlaying)
            My.Computer.Audio.PlaySystemSound(Media.SystemSounds.Asterisk)
            Threading.Interlocked.Decrement(Me.isPlaying)
        End Sub
    
    #Region "Dispose"
        Private disposed As Boolean
    
        Protected Overridable Sub Dispose(ByVal disposing As Boolean)
            If Not Me.disposed Then
                If disposing Then
                    ' TODO: free managed resources when explicitly called 
                End If
                ' TODO: free shared unmanaged resources 
            End If
            Me.disposed = True
        End Sub
    
        Public Sub Dispose() Implements IDisposable.Dispose
            Dispose(True)
            GC.SuppressFinalize(Me)
        End Sub
    
        Protected Overrides Sub Finalize()
            Call Me.Dispose(False)
            Call MyBase.Finalize()
        End Sub
    
    #End Region
    End Class

    • Proposed as answer by KareninstructorMVP Tuesday, August 22, 2017 7:20 PM
    • Marked as answer by J.Marq Monday, August 28, 2017 3:46 PM
    Monday, August 21, 2017 10:10 PM

All replies

  • This shows how to add an event to the class. When the sound plays the event is called. The event handler is in the form and changes the button backcolor. You can play with it for the effect you want.

    Public Class Form5
        Private WithEvents myMetro As New Metronome
    
        Private Sub Metronome_MetroTick(sender As Object, e As EventArgs) Handles myMetro.MetroTick
            Static thistick As Boolean
            thistick = Not thistick
            If thistick Then
                Button1.BackColor = Color.Red
            Else
                Button1.BackColor = SystemColors.ButtonFace
            End If
    
        End Sub
    
        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            myMetro.BPM = 120
            myMetro.StartMetronome()
        End Sub
    End Class
    
    Public Class Metronome
        Implements IDisposable
    
        Public Event MetroTick As EventHandler
    
        Private _bpm As Integer
        Private _beatTS As TimeSpan
        Private _OnOff As Long '<=0 = off, > 0 = on
        Private _beatMS As Long
    
        Public Property BPM() As Integer
            Get
                Return Me._bpm
            End Get
            Set(ByVal value As Integer)
                Me._bpm = value
                Me._beatTS = New TimeSpan(0, 0, 0, 0, 60000 \ Me._bpm) 'set ms between beats
                Threading.Interlocked.Exchange(Me._beatMS, CLng(Me._beatTS.TotalMilliseconds))
            End Set
        End Property
    
        Private tmkThrd As System.Threading.Thread
        Public Sub New()
            Me.BPM = -1
            Threading.Interlocked.Exchange(Me._OnOff, 0L) 'set to off
            'start thread to do timekeeping
            Me.tmkThrd = New System.Threading.Thread(AddressOf Me.TimeKeeper)
            't.IsBackground can be set to false IF the application calls Dispose
            't.IsBackground = True
            Me.tmkThrd.IsBackground = False
            Me.tmkThrd.Priority = Threading.ThreadPriority.Highest
            tmkThrd.Start()
        End Sub
    
        Public Sub New(ByVal BPM As Integer)
            MyBase.New()
            Me.BPM = BPM 'set beats per minute
        End Sub
    
        Public Sub StartMetronome()
            Threading.Interlocked.Exchange(Me._OnOff, 1L) 'start
            Me.tmkThrd.Interrupt() 'wake the thread
        End Sub
    
        Public Sub StopMetronome()
            Threading.Interlocked.Exchange(Me._OnOff, 0L) 'stop
        End Sub
    
        Private Sub TimeKeeper()
            Dim stpw As New Stopwatch
            Dim newSound As New Action(AddressOf MakeSound) 'play sound
            stpw.Start()
            Do
                Dim btMS As Long = Threading.Interlocked.Read(Me._beatMS)
                Dim offSlp As Long = btMS \ 4
                If Threading.Interlocked.Read(Me._OnOff) = 0L Then 'not running
                    stpw.Reset()
                    Try
                        Threading.Thread.Sleep(100)
                    Catch ex As Exception
                    End Try
                ElseIf Not stpw.IsRunning Then
                    stpw.Start()
                    Threading.Interlocked.Exchange(Me.isPlaying, 0L)
                ElseIf stpw.ElapsedMilliseconds + offSlp >= btMS Then
                    'in the ballpark
                    Do While stpw.ElapsedMilliseconds < btMS
                        'wait in a tight loop for the time to pass
                    Loop
                    stpw.Reset() 'reset and start the stopwatch before playing the sound
                    stpw.Start()
                    If Threading.Interlocked.Read(Me.isPlaying) = 0L Then newSound.Invoke()
                Else 'running, but not time to play sound
                    Threading.Thread.Sleep(CInt(offSlp))
                End If
            Loop While Not Me.disposed
        End Sub
    
        Private isPlaying As Long
        Private Sub MakeSound()
            RaiseEvent MetroTick(Me, New EventArgs)
            Threading.Interlocked.Increment(Me.isPlaying)
            My.Computer.Audio.PlaySystemSound(Media.SystemSounds.Asterisk)
            Threading.Interlocked.Decrement(Me.isPlaying)
        End Sub
    
    #Region "Dispose"
        Private disposed As Boolean
    
        Protected Overridable Sub Dispose(ByVal disposing As Boolean)
            If Not Me.disposed Then
                If disposing Then
                    ' TODO: free managed resources when explicitly called 
                End If
                ' TODO: free shared unmanaged resources 
            End If
            Me.disposed = True
        End Sub
    
        Public Sub Dispose() Implements IDisposable.Dispose
            Dispose(True)
            GC.SuppressFinalize(Me)
        End Sub
    
        Protected Overrides Sub Finalize()
            Call Me.Dispose(False)
            Call MyBase.Finalize()
        End Sub
    
    #End Region
    End Class

    • Proposed as answer by KareninstructorMVP Tuesday, August 22, 2017 7:20 PM
    • Marked as answer by J.Marq Monday, August 28, 2017 3:46 PM
    Monday, August 21, 2017 10:10 PM
  • Perfect - I love it - works like a champ!   Thank you!
    Monday, August 21, 2017 11:14 PM
  • Hi J.Marq,

    It seems that tommytwotrain's post solve your issue, please remember to close your thread by marking his post as answer, it is beneficial to other community members who face the same issue.

    Thanks for your understanding.

    Best Regards,

    Cherry


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    Wednesday, August 23, 2017 1:37 AM