I've got a small sequencer going on, but am facing a few issues with the Timer. I was happy to a certain degree for a while but now I want to tackle this again.
All these other sequencer apps like Cubase, Logic etc have smooth running playback systems where you can play the song and still do things in the background without affecting the playback in any way.
Whereas here with me, having 8 tracks or moving the form around, etc, pauses and hangs the timer.
KillTimer seems to run smoothly, but doesn't go below 1ms intervals.
QueryPerformanceCounter with a do/Loop is perfect in terms of accuracy & Tempo etc, but causes the hang.
So, can anyone figure out how to have a smooth timer going on without being interrupted ?
_____________________________________________________________________
----If this post has helped you. Please take time to Rate it.
----If you've solved your problem, then please mark it as RESOLVED from Thread Tools.
that is why I use borderstyle=0 and use my own method to move around the form.
I'm currently using (releaseCapture / sendmessage)
What's your method?
Originally Posted by The trick
Never ever use timers in such scenarios, you should synchronize your UI using your sound events like when you fill a buffer.
I'm not quite sure what you mean by that.
I have a an array() of events with Ticks that need triggering at certain points.
That's why I have a timer on a loop checking for matching Ticks to raise the PLAY event.
_____________________________________________________________________
----If this post has helped you. Please take time to Rate it.
----If you've solved your problem, then please mark it as RESOLVED from Thread Tools.
the modTimer uses a loop with a Doevents, that makes it stop you can check it with this simple code
Code:
Option Explicit
Private Declare Function ReleaseCapture Lib "user32" () As Long
Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long
Dim bFlag As Boolean
Dim Counter As Long
Private Sub Form_Load()
Me.Show
bFlag = True
Do While bFlag
Counter = Counter + 1
Cls
Me.Print Counter
DoEvents
Loop
End Sub
Private Sub Form_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)
Call ReleaseCapture
Call SendMessage(Me.hwnd, &H112, &HF012&, 0)
End Sub
Private Sub Form_Unload(Cancel As Integer)
bFlag = False
End Sub
I have a an array() of events with Ticks that need triggering at certain points.
For events you should use WaitForSingleObject or something like Waiter. But im sure you select a wrong way because you should have a single buffer which you should track. When you fill this buffer with the data you check your events. If the events has been triggered you just add the samples of your new audio so you get the resolution with your sample rate frequency.
I think you're presuming that I'm playing a single .wav file in a buffer and I'm seeking a way to find play positioning. That's not the scenario.
Think of musical notes in an array.
sNotes(0).start = 50 (ticks)
sNotes(1).start = 100 (ticks)
sNotes(2).start = 122 (ticks)
sNotes(4).start = 240 (ticks)
So the Timer goes through the sNotes array on each increment.
If timerTicks = sNotes(loop).start then PLAY
_____________________________________________________________________
----If this post has helped you. Please take time to Rate it.
----If you've solved your problem, then please mark it as RESOLVED from Thread Tools.
I'm having difficulty understanding your model.
Either way it needs some form of a Timer to know the elapsed time in order to trigger the next event.
Even instead of a LOOP/TIMER we calculate the different between currentTick vs Next Tick Event, you need a form of a timer to calculate that difference.
I'm currently exploring the WAITER class to see if it handles ticks (as opposed to ms), which may be another option.
_____________________________________________________________________
----If this post has helped you. Please take time to Rate it.
----If you've solved your problem, then please mark it as RESOLVED from Thread Tools.
theres many different methods to use instead of releasecapture. the same is if u hold mousedown on titlebar if using borderstyle>0
my method is just "when u press mouse down" it will store x/y position of the mouse relative to form left/top.
when mouse-move the form is "locked" so it know the -x/-y that I got from mouse down. and mouse-up, it will "release it".
now the form moves together with mouse and everything that is happening inside the form is showing. no delay/freeze.
Option Explicit
Private Declare Sub memcpy Lib "kernel32" _
Alias "RtlMoveMemory" ( _
ByRef Destination As Any, _
ByRef Source As Any, _
ByVal Length As Long)
Private Const NUM_OF_CHANNELS As Long = 4
Private Const SAMPLE_RATE As Long = 44100
Private Type TSound
lSamples As Long
iValues() As Integer
End Type
Private WithEvents m_cPlayer As clsTrickSound2
Private m_tSounds() As TSound
Private m_fTempo As Single
Private Sub Form_Load()
Dim lChIndex As Long
Dim lQIndex As Long
Dim lIndex As Long
For lChIndex = 0 To NUM_OF_CHANNELS - 1
For lQIndex = 0 To 15
If lIndex Then
Load chkKey(lIndex)
End If
With chkKey(lIndex)
.Move lQIndex * .Width + fraTempo.Left, lChIndex * .Height + fraTempo.Top * 2 + fraTempo.Height
.Visible = True
End With
lIndex = lIndex + 1
Next
Next
Me.Width = (Me.Width - Me.ScaleWidth) + 16 * chkKey(0).Width + fraTempo.Left * 2
Me.Height = (Me.Height - Me.ScaleHeight) + NUM_OF_CHANNELS * chkKey(0).Height + fraTempo.Top * 3 + fraTempo.Height
fraTempo.Width = Me.ScaleWidth - fraTempo.Left * 2
sldTempo.Move sldTempo.Left, sldTempo.Top, fraTempo.Width - sldTempo.Left * 2
sldTempo_Change
ReDim m_tSounds(NUM_OF_CHANNELS - 1)
ReDim m_tChannel(NUM_OF_CHANNELS - 1)
m_tSounds(0) = LoadSound(App.Path & "\kick.snd")
m_tSounds(1) = LoadSound(App.Path & "\snare.snd")
m_tSounds(2) = LoadSound(App.Path & "\hi-hat.snd")
m_tSounds(3) = LoadSound(App.Path & "\crash.snd")
Set m_cPlayer = New clsTrickSound2
m_cPlayer.InitPlayback 2, SAMPLE_RATE, 16, (SAMPLE_RATE * 2 * 0.05) And -2
m_cPlayer.StartProcess
End Sub
Private Sub m_cPlayer_NewData( _
ByVal DataPtr As Long, _
ByVal CountBytes As Long)
Static s_lBlock As Long
Static s_lChIdx(NUM_OF_CHANNELS - 1) As Long
Static s_bChState(NUM_OF_CHANNELS - 1) As Boolean
Static s_iData() As Integer
Static s_bDataInitialized As Boolean
Static s_lPrevUIBlock As Long
Dim lBIndex As Long
Dim lKIndex As Long
Dim lChIndex As Long
Dim lBlocks As Long
Dim lBlkPerQ As Long
Dim lSampleL As Long
Dim lSampleR As Long
Dim lPbOffset As Long
If Not s_bDataInitialized Then
ReDim s_iData(CountBytes \ 2 - 1)
s_bDataInitialized = True
End If
lBlocks = CountBytes \ 4
lBlkPerQ = 240 / m_fTempo * SAMPLE_RATE / 16
For lBIndex = 0 To lBlocks - 1
lSampleL = 0: lSampleR = 0
If (s_lBlock Mod lBlkPerQ) = 0 Then
lKIndex = s_lBlock \ lBlkPerQ
If lKIndex >= 16 Then
lKIndex = 0
s_lBlock = 0
End If
For lChIndex = 0 To NUM_OF_CHANNELS - 1
If chkKey(lChIndex * 16 + lKIndex).value Then
s_bChState(lChIndex) = True
s_lChIdx(lChIndex) = 0
End If
Next
End If
For lChIndex = 0 To NUM_OF_CHANNELS - 1
If s_bChState(lChIndex) Then
lSampleL = lSampleL + m_tSounds(lChIndex).iValues(s_lChIdx(lChIndex))
lSampleR = lSampleR + m_tSounds(lChIndex).iValues(s_lChIdx(lChIndex) + 1)
s_lChIdx(lChIndex) = s_lChIdx(lChIndex) + 2
If s_lChIdx(lChIndex) >= m_tSounds(lChIndex).lSamples Then
s_bChState(lChIndex) = False
End If
End If
Next
If lSampleL > 32767 Then
lSampleL = 32767
ElseIf lSampleL < -32768 Then
lSampleL = -32768
End If
If lSampleR > 32767 Then
lSampleR = 32767
ElseIf lSampleR < -32768 Then
lSampleR = -32768
End If
s_iData(lBIndex * 2) = lSampleL
s_iData(lBIndex * 2 + 1) = lSampleR
s_lBlock = s_lBlock + 1
Next
' // Update visual
lPbOffset = (m_cPlayer.BufferLengthSamples * m_cPlayer.BuffersCount + CountBytes \ 2) \ 2
lKIndex = ((s_lBlock - lPbOffset) \ lBlkPerQ) Mod 16
If lKIndex < 0 Then
lKIndex = 16 + lKIndex
End If
For lChIndex = 0 To NUM_OF_CHANNELS - 1
chkKey(lChIndex * 16 + lKIndex).BackColor = vbRed
If s_lPrevUIBlock <> lKIndex Then
chkKey(lChIndex * 16 + s_lPrevUIBlock).BackColor = vbWhite
End If
Next
s_lPrevUIBlock = lKIndex
memcpy ByVal DataPtr, s_iData(0), (UBound(s_iData) + 1) * 2
End Sub
Private Function LoadSound( _
ByRef sPath As String) As TSound
Dim tRet As TSound
Dim iFNum As Integer
Dim lSize As Long
iFNum = FreeFile
Open sPath For Binary As iFNum
lSize = LOF(iFNum)
If lSize Then
ReDim tRet.iValues(lSize \ 2)
Get iFNum, , tRet.iValues
End If
tRet.lSamples = lSize \ 2
Close iFNum
LoadSound = tRet
End Function
Private Sub sldTempo_Change()
m_fTempo = sldTempo.value
fraTempo.Caption = "Tempo (" & CStr(m_fTempo) & " BPM)"
End Sub
Private Sub sldTempo_Scroll()
sldTempo_Change
End Sub
@TheTrick: Thank you for the example. I will take some time to analyse it tomorrow, but having a quick glance seems to be a very very different approach to what I currently have. And I'm not sure, I can go ahead and change my whole project to a totally different approach at this point of time.
Do you think there could be an alternative approach with the Timer methods I'm currently using ?
_____________________________________________________________________
----If this post has helped you. Please take time to Rate it.
----If you've solved your problem, then please mark it as RESOLVED from Thread Tools.
Do you think there could be an alternative approach with the Timer methods I'm currently using ?
As i already wrote you shouldn't use timers for this purpose because they are not accurate. If you don't need accurate time periods you could use multimedia timers but it'll require either high CPU loading or threading issues. Most of the DAW i know uses approach which i showed you so i would hihgly recommend you to change your architecture.
As an alternative to the timer, you can use a waiting cycle with synchronization by the exact QueryPerformancaCounter(). I attach an example of a simple metronome based on this principle, the Sleep() function in the example is not used for synchronization, but to reduce the load on the CPU while waiting for the next event.
I use this .tlb: https://www.vbforums.com/showthread....und&highlight=
my recommendation is to follow The trick suggestions.
we can always do workarounds but in the end, when we implement experts solutions we notice the difference in quality.
so, even if this will require a startover it will reward u later.
If it is for "Midi-Notes only", then a normal Timer triggering the "Notes"
(with 15msec +-8msec interval) - seems entirely sufficient.
And due to the "slight imprecisions in timing" when triggering Notes,
it even sounds more like "natural playing"...
(less "mechanical" when compared to "msec-exact-outputs").
With the assistance of WinAPI commands, the accuracy of the system timer can be increased to 1 ms, then this is really enough for midi notes, but 16 ms is clearly not enough even for this.
But when mixing audio tracks, even 1 ms is very inaccurate, at a frequency of 500 Hz it will create an antiphase.