Home All Groups Group Topic Archive Search About

Karl, I need to understand your Timer!

Author
13 Jun 2009 11:20 AM
MM
I'm trying out different timers again. I looked at CCRP Timer in the
past, but couldn't fathom its depths (Notify, Countdown, Stopwatch,
phew!)

All I want is a replacement for Sleep, as in

Sleep 32

as accurate as poss, where the duration might be as low as 1.

How can I best achieve that with the CCRP Timer?

Use the Countdown thingy? The Notify thingy?

I've already spent hundreds of hours (well, okay, slight exaggeration)
trying to work through the examples.

At present I'm using clsWaitableTimer.cls from A. N. Other and I
simply do

objTimer.Wait  DeltaTime

MM

Author
13 Jun 2009 12:23 PM
Schmidt
"MM" <kylix***@yahoo.co.uk> schrieb im Newsbeitrag
news:7g27355tppmghm92mo0raqt1smksrtms6m@4ax.com...

> All I want is a replacement for Sleep, as in
>
> Sleep 32
You can increase the resolution of the Sleep-command
to about 1-2msec (instead of the former 15msec).

with the usage of:
Private Declare Function timeBeginPeriod& Lib "winmm" (ByVal uPeriod&)
Private Declare Function timeEndPeriod& Lib "winmm" (ByVal uPeriod&)

On process-startup:
timeBeginPeriod 1

On process-exit:
timeEndPeriod 1


> as accurate as poss,
As said - Sleep will then work with a reachable minimum-interval
of about 2msec.
Also if you call:
Sleep 1
.... on most systems the Sleep-Call returns after 2msec only -
interestingly on my oldest machine here, a PIII-500MHz - the
Sleep-Call returns indeed after 1msec, but on all other systems
I've tested, Sleep 1 will give you 2msec "timeout" in case the
timeBeginPeriod 1 call was done at startup.

So, if that minimum-timeout of 2msec is enough, and
we talk about accuracy now, then you could simply specify
your longer sleep-calls (of about 10-30msec) just 1-2msec
lower than you really want, and simply loop the remaining
msec in a loop, which uses QueryPerformance-Frequency
to wait until the exact given delta is reached.

10msec delta, specified as Sleep 9 - will give you then
ca. 10% CPU-load, in case Sleep 9 returns relative exactly
after  9msec in most cases (the 10% load then caused,
because you have to perform your loop for the remaining
1msec duration until your delta is reached exactly).


> where the duration might be as low as 1.
As said, the minimum timeout of that "tuned Sleep-Call"
is mostly 2msec - you cannot reach below that - if you
need to go below these 2msec, then just do your endless-
loop, which is based on QueryPerformance-Counter "purely".

But not sure, what for you will need such small wait-timeouts
for Midi - the 2msec Minimum-Wait-Timeout of the normal
(tuned) Sleep-Call should be enough IMO - even the well-
working ASIO-drivers of professional Soundcards for example
only reach about 1-2msec latency - which in the Sound- and
Recording-scene (Cubase et al.) is considered "enough" -
the Pros only begin to "revolt", if the latency is above 4-5.

So I'd just call the plain (tuned) Sleep-Call - try to see,
if that already is enough for you (without that extra-efforts
regarding shorter given intervals + "rest-looping" with
QueryPerformance-timers).


Olaf
Are all your drivers up to date? click for free checkup

Author
13 Jun 2009 2:06 PM
MM
Show quote Hide quote
On Sat, 13 Jun 2009 14:23:34 +0200, "Schmidt" <s**@online.de> wrote:

>
>"MM" <kylix***@yahoo.co.uk> schrieb im Newsbeitrag
>news:7g27355tppmghm92mo0raqt1smksrtms6m@4ax.com...
>
>> All I want is a replacement for Sleep, as in
>>
>> Sleep 32
>You can increase the resolution of the Sleep-command
>to about 1-2msec (instead of the former 15msec).
>
>with the usage of:
>Private Declare Function timeBeginPeriod& Lib "winmm" (ByVal uPeriod&)
>Private Declare Function timeEndPeriod& Lib "winmm" (ByVal uPeriod&)
>
>On process-startup:
>timeBeginPeriod 1
>
>On process-exit:
>timeEndPeriod 1
>
>
>> as accurate as poss,
>As said - Sleep will then work with a reachable minimum-interval
>of about 2msec.
>Also if you call:
>Sleep 1
>... on most systems the Sleep-Call returns after 2msec only -
>interestingly on my oldest machine here, a PIII-500MHz - the
>Sleep-Call returns indeed after 1msec, but on all other systems
>I've tested, Sleep 1 will give you 2msec "timeout" in case the
>timeBeginPeriod 1 call was done at startup.
>
>So, if that minimum-timeout of 2msec is enough, and
>we talk about accuracy now, then you could simply specify
>your longer sleep-calls (of about 10-30msec) just 1-2msec
>lower than you really want, and simply loop the remaining
>msec in a loop, which uses QueryPerformance-Frequency
>to wait until the exact given delta is reached.
>
>10msec delta, specified as Sleep 9 - will give you then
>ca. 10% CPU-load, in case Sleep 9 returns relative exactly
>after  9msec in most cases (the 10% load then caused,
>because you have to perform your loop for the remaining
>1msec duration until your delta is reached exactly).
>
>
>> where the duration might be as low as 1.
>As said, the minimum timeout of that "tuned Sleep-Call"
>is mostly 2msec - you cannot reach below that - if you
>need to go below these 2msec, then just do your endless-
>loop, which is based on QueryPerformance-Counter "purely".
>
>But not sure, what for you will need such small wait-timeouts
>for Midi - the 2msec Minimum-Wait-Timeout of the normal
>(tuned) Sleep-Call should be enough IMO - even the well-
>working ASIO-drivers of professional Soundcards for example
>only reach about 1-2msec latency - which in the Sound- and
>Recording-scene (Cubase et al.) is considered "enough" -
>the Pros only begin to "revolt", if the latency is above 4-5.
>
>So I'd just call the plain (tuned) Sleep-Call - try to see,
>if that already is enough for you (without that extra-efforts
>regarding shorter given intervals + "rest-looping" with
>QueryPerformance-timers).
>
>
>Olaf

I have tried exactly what you describe with  timeBeginPeriod 1 and
timeEndPeriod 1, but it makes no difference. As soon as I start to
move the form, or scroll a scrollbar, the output stops. I've tried it
on 98SE and on XP.

This is the salient part of the output loop (I've removed any minor
details so as not to generate reams of code here):

'****** START OF ROUTINE ***** (where Dim's are done etc)

timeBeginPeriod 1

Do

   With MIDIEventQueue(PlayBackPtr)
        If .DeltaTime Then
            DeltaTime = .DeltaTime * DeltaTimeMultiplier
          ' objTimer.Wait DeltaTime  ' <---- what i've used until now
            Sleep DeltaTime     ' <---- and now with your suggestion 
         End If

        Select Case .Status
             ' Ignore following meta events
          Case METASEQN, METATEXT, METACOPYR, _
               METATNAME, METAINAME, METALYRIC, _
               METAMARKER, METACUEPT, METACHANPFX, _
               METAMIDIPORT, METAEOT, METASMPTEOFF, METASEQEVENT
          Case &H51 ' a Tempo change, so we must adjust the multiplier
               If SuppressEmbeddedTempoChanges = False Then
                  b(0) = 0
                  b(1) = .Data1  ' Microseconds
                  b(2) = .Data2  '              per
                  b(3) = .Data3  '                  quarternote
                  w = MakeLong(b())
                  tempo = MICROSECONDS_PER_MINUTE \ w
                  DeltaTimeMultiplier = ((MICROSECONDS_PER_MINUTE /
tempo) / 1000) / g_TicksPerBeat
               End If
          Case &H58, &H59   ' Ignore Time signature, key signature
          Case &HB0 To &HBF ' Control Change
               midiMsg = (.Data1 * &H100) + (.Data2 * &H10000) +
..Status
               Call midiOutShortMsg(g_MIDI_Out, midiMsg)
          Case &HC0 To &HCF ' Program Change
               midiMsg = (.Data1 * &H100) + .Status
               Call midiOutShortMsg(g_MIDI_Out, midiMsg)
          Case Else ' MIDI note on/off data
               Data2 = .Data2
               midiMsg = (.Data1 * &H100) + (Data2 * &H10000) +
..Status
               Call midiOutShortMsg(g_MIDI_Out, midiMsg)
        End Select
   End With

   DoEvents

   PlayBackPtr = PlayBackPtr + 1
Loop While (PlayBackPtr <= PlayBackTo)

timeEndPeriod 1

'****** END OF ROUTINE *******

However, even though the main problem, that of freeze-up when moving
the form, is still present, this exercise does show that Sleep is
adequate when the resolution is increased, as there is no perceptible
difference to the sound output (no lags etc).

But the freeze-up remains obstinate!

MM
Author
13 Jun 2009 3:03 PM
MM
PS: I also changed the call to timeBeginPeriod slightly to ensure that
it is being implemented:

If timeBeginPeriod(1) = TIMERR_NOERROR Then Debug.Print
"timeBeginPeriod ok"

MM
Author
13 Jun 2009 3:13 PM
Schmidt
"MM" <kylix***@yahoo.co.uk> schrieb im Newsbeitrag
news:73b7355ovggpocjoc06mmr6jadgh7dh4hk@4ax.com...

> I have tried exactly what you describe with  timeBeginPeriod 1
> and timeEndPeriod 1, but it makes no difference. As soon as
> I start to move the form, or scroll a scrollbar, the output stops.

The timeBeginPeriod/..endperiod-calls have nothing to do
with your sound-blocking issues in case your GUI is
"touched".

They are only there, to (normally) increase the accuracy
ot the multimedia-timer - but they also have that nice side-
effect on the sleep-call, which then is able to work below 15msec
granularity - just what you need IMO for your (wait-)purposes.

Your blocking-issues (at least on Win98) were not solvable
as I read in your other thread - the callback-approach, posted
by Alfie [UK] is a nice idea - but it seems to work only on
XP currently, if I read that correctly.

Hmm, maybe time now, to really think about a threaded
approach <g>.

But be aware, that if you want to do it properly, you will
have to move everything that's Midi-related into a Class
then (probably not that much of a problem) - but the
larger effort would be, that this Class (which later on runs
on its own thread) needs to communicate with your GUI
in an asynchronous way (at least as long as your loops
are running, which play a current continuous stream).
And that will cause side-effekts (coding-effort) on how
you need to work then with your GUI (rendering-) code.

So, are there more actions than playing a current stream
(wherever that stream comes from) which currently
need an "unblocking" - or is that the only one.

And if it is - in what format do you currently hold your
Data for a given Stream - if in an (userdefined) Array -
how large is it in the worst-case (does that structure
already contain the maximum of all 16-possible channels?).

Or do you organize each channel in a separate Array.

And what is with interaction whilst a stream is playing?
Start-Stop is clear - but do you want to allow live-playing
(or even additional recording) whilst a current stream is
played within the thread? I assume yes, if your App acts
something like a sequencer - but if Not, that would ease
the threading-design a bit.

Olaf
Author
13 Jun 2009 7:08 PM
MM
Show quote Hide quote
On Sat, 13 Jun 2009 17:13:03 +0200, "Schmidt" <s**@online.de> wrote:

>
>"MM" <kylix***@yahoo.co.uk> schrieb im Newsbeitrag
>news:73b7355ovggpocjoc06mmr6jadgh7dh4hk@4ax.com...
>
>> I have tried exactly what you describe with  timeBeginPeriod 1
>> and timeEndPeriod 1, but it makes no difference. As soon as
>> I start to move the form, or scroll a scrollbar, the output stops.
>
>The timeBeginPeriod/..endperiod-calls have nothing to do
>with your sound-blocking issues in case your GUI is
>"touched".
>
>They are only there, to (normally) increase the accuracy
>ot the multimedia-timer - but they also have that nice side-
>effect on the sleep-call, which then is able to work below 15msec
>granularity - just what you need IMO for your (wait-)purposes.
>
>Your blocking-issues (at least on Win98) were not solvable
>as I read in your other thread - the callback-approach, posted
>by Alfie [UK] is a nice idea - but it seems to work only on
>XP currently, if I read that correctly.
>
>Hmm, maybe time now, to really think about a threaded
>approach <g>.
>
>But be aware, that if you want to do it properly, you will
>have to move everything that's Midi-related into a Class
>then (probably not that much of a problem) - but the
>larger effort would be, that this Class (which later on runs
>on its own thread) needs to communicate with your GUI
>in an asynchronous way (at least as long as your loops
>are running, which play a current continuous stream).
>And that will cause side-effekts (coding-effort) on how
>you need to work then with your GUI (rendering-) code.
>
>So, are there more actions than playing a current stream
>(wherever that stream comes from) which currently
>need an "unblocking" - or is that the only one.
>
>And if it is - in what format do you currently hold your
>Data for a given Stream - if in an (userdefined) Array -
>how large is it in the worst-case (does that structure
>already contain the maximum of all 16-possible channels?).
>
>Or do you organize each channel in a separate Array.
>
>And what is with interaction whilst a stream is playing?
>Start-Stop is clear - but do you want to allow live-playing
>(or even additional recording) whilst a current stream is
>played within the thread? I assume yes, if your App acts
>something like a sequencer - but if Not, that would ease
>the threading-design a bit.
>
>Olaf

Thanks, Olaf. Here's an overview:

So, the MIDI data is held in a UDT of the following Type:

Type MIDIEventType
     DeltaTime As Long
     status As Byte
     data1 As Byte
     data2 As Byte
     data3 As Byte
     bytepointer As Long
End Type

Public MIDEventQueue() As MIDIEventType

The UDT is ReDimmed according to need as events are read from the MIDI
file and pumped into it. Thus the array could hold 1 event or 30,000.
There IS a limit, since it is only using memory, but I can hold enough
data for a Beethoven Sonata, for instance, which is more than enough
for the purpose of the app (i.e. to practise riffs on the piano!)

The Playback routine is started. Basically, this is a loop from
pointer=0 to UBound(MIDEventQueue)

During the Playback the program also "plays" the key on a virtual
keyboard (see picture at
http://www.littletyke.myzen.co.uk/ktn/index.html)

So, each midiOutShortMsg is preceded by PlayOrReleaseKey.

It all works VERY nicely! The virtual keys are exactly in sync with
the MIDI output. Just what I wanted it to do. But the slight fly in
the ointment is the blocking that occurs when moving the main form, or
the keyboard, or scrolling either the grid or scrolling the keyboard.

It is not a MAJOR problem, since the whole purpose of this app is to
help orientation around a piano or organ keyboard for those who have
never learned to sight-read successfully.

Actually, the *original* purpose was to provide a driver for an array
of LEDs (physical hardware LEDs) along a piano keyboard (see
http://www.littletyke.myzen.co.uk/vb6tobasicstamp2/html/the_application.html)
and that is the next stage on my ToDo list. That is where
PlayOrReleaseKey (see above) will be augmented by: SwitchLEDOnOff,
whereupon a suitable message is sent out of the serial port to the
BASIC Stamp and the BS causes the appropriate LED to be lighted (or
switched off).

Well it ~was~ intended as "only" a driver originally, but has turned
into a full-blown app in its own right over the past 18 months.

MM
Author
14 Jun 2009 12:10 PM
Schmidt
"MM" <kylix***@yahoo.co.uk> schrieb im Newsbeitrag
news:mus735tf17boel4buapbdqkpum6dfan5aq@4ax.com...

> It all works VERY nicely! The virtual keys are exactly in
> sync with the MIDI output. Just what I wanted it to do.
> But the slight fly in the ointment is the blocking that occurs
> when moving the main form, or the keyboard, or scrolling
> either the grid or scrolling the keyboard.

Yep.
From looking at your code again, this is caused by your
DoEvents-Call which is (necessarily) placed within
your PlayStream-Loop. It is acting as a black-hole
for win-messages, in case you cause many of them (scrolling,
moving a form) - and that "black-hole" returns back
to your loop only, when all "catched" message-blobs are
"done".

You have to get rid of that - but the question is "how" of
course (thereby allowing Control- and Form-Event-
processing further).

There are different solutions then.

The "cleanest" (and best working) singlethreaded approach would
be, to work with MidiStreamOpen/-Pause/-Restart/-Stop etc.,
since that is, what the MCI-Control does.
You can adjust also the Tempo and the Volume of a just playing
stream(chunk) - and you could also check cyclically with a normal
timer, where the current Stream-Position is - and you could
also register for notify-messages about the just played (last)
Midi-Events against a Window-Procedure (using subclassing).

But that would be a bit of work (preparing the stream-header-
structures - fill in the next chunk of Midi-Events, etc)... and
since your own current (single Message-based) stream-approach
works well enough, why should you change that (ok, to get rid
of the fly of course ;-)

The second-best singlethreaded approach, to get rid of the
black-hole (DoEvents) is (and here we get OnTopic again
in this thread), to work with one of the MultiMedia-Timer-
encapsulations.
You would have to set their resolution not too high (e.g.
1msec resolution would probably a bit "stressy", but 2msec
should work good enough for your purposes IMO).
These encapsulations usually work with a PostMessage-
Call under the hood, to a hidden  (internal) Window,
and that in turn is raising a normal VB-Event into your App.
In that HighResTimer-Event (which occurs with a resultion
of 2msec then) you will have to ensure a mechanism, which
exits the Event immediately, should the Time-Delta to your
next Midi-Note larger than the next tick-interval (2msec) -
but if the remaining time-delta is lower, then just fire up
your next note. You should nonetheless sum-up your
current StreamTime from another reliable Time-
measurement-Function, and not by simply suming up from
the incoming 2msec-ticks. This would ensure, that your
StreamTime/-Speed would be correct - only your played
Notes would be fired out with a somewhat reduced accuracy
of +- 1msec (in case of a 2msec interval) - but as already
mentioned - this is normally not heard - and in fact many
sequencers implement something like that "on purpose",
with even a bit larger "accuracy-intervals" than only 2msec,
to make a played Midi-Stream sound somewhat more
naturally, and not with an more "robot-like" exact timing.

But - although this approach would get rid of the blackhole,
you have now incoming (Window-)messages which work in
concurrency - e.g. a heavy Form-Movement - or heavy
Scrolling would cause a nice amount of messages in "parallel"
to the (per PostMessage) incoming MultiMediaTimer-Ticks.
Maybe you can lower these side-effects, if you decouple
your Scroll-Events for example with another (normal) timer,
to ensure something like a "privileged mode" for your tick-
events.
Maybe that worth a try, since it would require the least
amount of changes to your current (working) scheme.

And then there's the threading - you would have to decide,
which way you want to go - your ThreadClass then hosted
in an ActiveX-Exe - or in an external Dll (supported by an
additional Helper-Lib). Both approaches have pros and
cons. The ActiveX-Exe-approach a bit easier to implement
presumably (but not much) - and your Project could be
hosted and managed (and debuged) within a single VB-
project further.

Maybe give the MMTimer-approach (-controls) a try - and
see, how the "win-message-concurrency" works out,
compared with the entirely non-satisfying DoEvents-
"workaround" you currently have.

Olaf
Author
15 Jun 2009 1:01 PM
Alfie [UK]
On Sun, 14 Jun 2009 14:10:09 +0200, "Schmidt" <s**@online.de> wrote:

>Maybe give the MMTimer-approach (-controls) a try - and
>see, how the "win-message-concurrency" works out,
>compared with the entirely non-satisfying DoEvents-
>"workaround" you currently have.

An MMTimer is probably the best idea, as it runs in it's own thread,
and as far as I know isn't blocked by other messages like a system
timer.

As Steve McMahon mentions
http://www.vbaccelerator.com/home/vb/code/Libraries/HiResTimer/article.asp
it is better to encapsulate the MMTimer in it's own AX DLL to avoid
issues when working within the IDE.
--
Alfie [UK]
<http://www.delphia.co.uk/>
If god had meant us to travel economy class, he would have made us narrower.
Author
15 Jun 2009 3:02 PM
Schmidt
"Alfie [UK]" <alfie@mail.invalid> schrieb im Newsbeitrag
news:mvgc35lshp3rhle9hn7bsjitf17ffpgm4m@4ax.com...
> On Sun, 14 Jun 2009 14:10:09 +0200, "Schmidt" <s**@online.de> wrote:
>
> >Maybe give the MMTimer-approach (-controls) a try -
> >and see, how the "win-message-concurrency" works out,
> >compared with the entirely non-satisfying DoEvents-
> >"workaround" you currently have.
>
> An MMTimer is probably the best idea, as it runs in it's own
> thread,
Yep - but the decoupling from this thread-callback in such
implementations is usually done per PostMessage-Call,
which is also one of the MSDN-recommended calls, which
should be placed within such an MMTimer-callback-function.

> and as far as I know isn't blocked by other messages like
> a system timer.
Up to the *send* of the PostMessage-Call in the MMTimer-
Callback nothing is blocked or "delayed", yes.

But the *incoming* PostMessage-Call(s) (within the hidden
Window in such a MM-Timer-Implementation, in turn
able to raise Events to VB) are received in the Apps normal
MainThread then.

And within that Process(MainThread) of the Std-VBApp
these incoming (Post-)Messages are in concurrency to
other incoming messages within the same App (in the
Apps MainForm for example).

> As Steve McMahon mentions
> http://www.vbaccelerator.com/home/vb/code/Libraries/HiResTimer/article.asp
> it is better to encapsulate the MMTimer in it's own AX DLL
> to avoid issues when working within the IDE.
Yep - that's recommended in either case - but this Dll
(or OCX, whatever) also provides only a PostMessage-
based MMTimer-Event within the VB-ConsumerApp.

But as said - this should already work (at least a bit)
better than the current Loop-approach having a DoEvents-
Call within).

Olaf
Author
16 Jun 2009 5:30 PM
MM
Show quote Hide quote
On Mon, 15 Jun 2009 14:01:19 +0100, "Alfie [UK]" <alfie@mail.invalid>
wrote:

>On Sun, 14 Jun 2009 14:10:09 +0200, "Schmidt" <s**@online.de> wrote:
>
>>Maybe give the MMTimer-approach (-controls) a try - and
>>see, how the "win-message-concurrency" works out,
>>compared with the entirely non-satisfying DoEvents-
>>"workaround" you currently have.
>
>An MMTimer is probably the best idea, as it runs in it's own thread,
>and as far as I know isn't blocked by other messages like a system
>timer.
>
>As Steve McMahon mentions
>http://www.vbaccelerator.com/home/vb/code/Libraries/HiResTimer/article.asp
>it is better to encapsulate the MMTimer in it's own AX DLL to avoid
>issues when working within the IDE.

I am at last making progress! Using a multimedia timer I have just
successfully "played" a sequence of MIDI events that kept on playing
(on Windows 98SE nota bene!) even while I moved the form. I even added
a flexgrid to the form and in Form_Load filled it with 1000 rows of
dummy data so that I could stress the CPU by scrolling up and down
while the MIDI output is in progress. Again, NO interruption!

This is still early days, because I have to work out timing based on a
tick generator (the multimedia timer) rather than Sleep(ing) for a
precise period. Basically, as far as I can make out from Paul
Messick's Maximum MIDI (written in C/C++), the tick generator
generates ticks and at each one you increment a tick counter and
compare it with the delta time of the next MIDI event pending in the
queue. If the tick counter meets or exceeds the DT, then
midiOutShortMsg for that event and reset the counter. I'll give that a
go next.

MM
Author
17 Jun 2009 4:56 PM
Schmidt
"MM" <kylix***@yahoo.co.uk> schrieb im Newsbeitrag
news:h5lf355vliea08lsebhhfr4ut483v2jcei@4ax.com...

> This is still early days, because I have to work out timing based on a
> tick generator (the multimedia timer) rather than Sleep(ing) for a
> precise period. Basically, as far as I can make out from Paul
> Messick's Maximum MIDI (written in C/C++), the tick generator
> generates ticks and at each one you increment a tick counter and
> compare it with the delta time of the next MIDI event pending in the
> queue. If the tick counter meets or exceeds the DT, then
> midiOutShortMsg for that event and reset the counter. I'll give
> that a go next.

That's what I basically meant in my post with the comments
about the Delta-Timing.

But since suming-up only the ticks can be a bit unprecise
(unpredictable), because *our* VB-HighResTimer works
already decoupled (per Postmessage), you cannot rely on
the same accuracy as demonstrated e.g. in a C-Source,
in case these implementations work within the MMTimer-
Callback directly.

What I meant with my comment here:
  "You should nonetheless sum-up your current StreamTime
   from another reliable Time-measurement-Function, and not
   by simply suming up from the incoming 2msec-ticks..."

....is needed at the receiving end (our VB-Mainthread that
receives the redirected PostMessages - and implemented in
the following small Demo, which does not use a HighRes-
Timer-encapsulation from a secondary Dll, but works within
your App directly - to make it more obvious, what usually
happens in such a VB-HighResTimer-component.
Switch to a dedicated Dll-implementation for the HighRes-
Ticks, if you need a bit more Debug-stability - but the
Demo should also behave stable (also in Debug-Mode)
as long as you don't place a BreakPoint directly within
the CallBack-Procedure, which only contains the TypeLib-
defined PostMessage-Call. Another advantage regarding
IDE-behaviour in case you will use a secondary Dll is,
that in case you pressed the Stop-Button to end your
running App, then the (in case the timer was enabled)
timer will fire further - where a Class that is defined within
an external AX-Dll-Binary will go above Class_Terminate
in case you press the IDEs Stop-Button - and in case
of a HighResTimer-Dll that usually means, that the Timer
is properly cancelled (killed).

Ok, so for the following example you will need such a
small PostMessageCall-Typelib-definition, bound into a
normal StdExe-Test-project - e.g. the small typelib, which
is included in the vbAccelerator-Example will do... and
as always - such Typelibs don't have to be shipped with
your App once it is compiled.

Ok - here the Code now (no additional Controls needed,
just click the plain Form):

'***Into a Form
Option Explicit

Private Type SimpleNoteDef 'somewhat simplified
  Value As Long
  Duration As Long
End Type

Private WithEvents TickReceiver As PictureBox 'receives the HighRes-Ticks

Private T_StartNote As Long 'to store the Time, the first Note was played
Private T_NextNote As Long 'Time the next Note should be played
Private NextNoteIdx As Long 'the next Note-Idx that should be played
Private Notes() As SimpleNoteDef  'define our simple "Notes-Stream-Array"


Private Sub Form_Load()
  timeBeginPeriod 1 'switch to highres-environment
  Set TickReceiver = Controls.Add("VB.PictureBox", "TR") 'a hidden PB
  InitSimpleDemoStream
End Sub

Private Sub Form_Click()
  If Not AutoRedraw Then AutoRedraw = True
  Cls 'since the demo-output is directed (printed) to the Form
  PlayStream
End Sub

Private Sub Form_Unload(Cancel As Integer)
  StopTimer 'just ensure, that everything is on hold if we leave
  timeEndPeriod 1 'reset highres-environment
End Sub

Private Sub InitSimpleDemoStream()
Dim i As Long
  ReDim Notes(15) 'only 16 Notes here
  For i = 0 To UBound(Notes)
    Notes(i).Value = i
    Notes(i).Duration = 100 'msec Duration (for all Notes, to simplify)
  Next i
End Sub

Private Sub PlayStream(Optional ByVal FromNoteIdx As Long)
Dim i As Long
  StopTimer 'just to make sure
  T_StartNote = timeGetTime
  PlayNote FromNoteIdx 'play the start-note already here

  T_NextNote = T_StartNote 'initialize to the current start-time
  For i = 0 To FromNoteIdx
    T_NextNote = T_NextNote + Notes(i).Duration 'increase as needed
  Next i
  NextNoteIdx = FromNoteIdx + 1 'define the next NoteIdx
  StartTimer TickReceiver.hWnd, 2 'and enable the HighResTimer
End Sub

'just use a Msg with a somewhat higher prio than wm_keydown or wm_paint
Private Sub TickReceiver_Resize()
  CheckForNextNote timeGetTime
End Sub

Private Sub CheckForNextNote(ByVal T_Current As Long)
  If T_Current < T_NextNote Then Exit Sub

  PlayNote NextNoteIdx 'play the now current Note

  T_NextNote = T_NextNote + Notes(NextNoteIdx).Duration
  NextNoteIdx = NextNoteIdx + 1
  If NextNoteIdx > UBound(Notes) Then StopTimer 'check for stream-end
End Sub

Private Sub PlayNote(ByVal CurNoteIdx As Long)
  Print Notes(CurNoteIdx).Value, _
        "played at Abs-StreamTime:"; timeGetTime - T_StartNote
End Sub


'***and here the small MMTimer-redirection per PostMessage
'***put that into a module
Option Explicit

Public Declare Function timeBeginPeriod& Lib "winmm" (ByVal uPeriod&)
Public Declare Function timeEndPeriod& Lib "winmm" (ByVal uPeriod&)
Public Declare Function timeGetTime& Lib "winmm" ()
Private Declare Function timeSetEvent& Lib "winmm" (ByVal uDelay&, _
  ByVal uResolution&, ByVal lpFunction&, ByVal dwUser&, ByVal uFlags&)
Private Declare Function timeKillEvent& Lib "winmm" (ByVal uID&)

Private m_TID As Long

Public Function StartTimer(ByVal hWnd&, ByVal Interval As Long) As Boolean
   StopTimer 'don't start more than one timer in our small demo
   m_TID = timeSetEvent(Interval, 1, AddressOf TimerProc, hWnd, 1)
   StartTimer = m_TID
End Function

Public Sub StopTimer()
  If m_TID = 0 Then Exit Sub Else timeKillEvent m_TID: m_TID = 0
End Sub

'always use a typelib-defined PostMessage-call in here
Private Sub TimerProc(ByVal TID As Long, ByVal iMsg As Long, _
        ByVal dwUser As Long, ByVal dw1 As Long, ByVal dw2 As Long)
  PostMessage dwUser, &H5, 0, 0 'just send a simple wm_size
End Sub

Olaf
Author
18 Jun 2009 5:24 PM
MM
On Wed, 17 Jun 2009 18:56:17 +0200, "Schmidt" <s**@online.de> wrote:

>Ok, so for the following example you will need such a
>small PostMessageCall-Typelib-definition, bound into a
>normal StdExe-Test-project - e.g. the small typelib, which
>is included in the vbAccelerator-Example will do... and
>as always - such Typelibs don't have to be shipped with
>your App once it is compiled.

Do you mean I should add a Reference (in Project/References) to e.g.
...\HiResTimer6\TLB\vbalHRTA.tlb ?

That is what I've done anyway and your application works. Now I need
to study the code to see how it works.

By the way, can you explain the need for the Typelib? Why wasn't it
needed in VB5?

Many thanks for all your efforts - vielen Dank!

MM
Author
18 Jun 2009 6:21 PM
Schmidt
"MM" <kylix***@yahoo.co.uk> schrieb im Newsbeitrag
news:6mtk3555f8joel4if5bvesf9v24ce6lbpd@4ax.com...
> On Wed, 17 Jun 2009 18:56:17 +0200, "Schmidt" <s**@online.de> wrote:
>
> >Ok, so for the following example you will need such a
> >small PostMessageCall-Typelib-definition, bound into a
> >normal StdExe-Test-project - e.g. the small typelib, which
> >is included in the vbAccelerator-Example will do... and
> >as always - such Typelibs don't have to be shipped with
> >your App once it is compiled.
>
> Do you mean I should add a Reference (in Project/References)
> to e.g. ..\HiResTimer6\TLB\vbalHRTA.tlb ?
Yep - that's the small typelib I had in mind.

> That is what I've done anyway and your application works.
> Now I need to study the code to see how it works.
Just make sure, that you replace the code with the just
posted newer version, that works with the OneShot-
approach.

> By the way, can you explain the need for the Typelib?
> Why wasn't it needed in VB5?
The need for the Typelib is caused by VBs threading-
behaviour - VB allows threads, but these threads, to
be able to make use of COM in the desired way
have to ensure a special environment *within* such
a thread (Thread-Local-Storage initialization beforehand).

With VB5 these TLS-requirements were low... - up
to SP2 (IIRC) you were even able to use lightweight-
threading per CreateThread directly within a *.bas-
module-procedure which was given into the CreateThread-
call per AddressOf.
Then since SP2  VB5 was enforcing higher TLS-requirements,
and the direct usage of CreateThread - and also the Callbacks
from other lightweight-threads (as the MMTimer-Callback)
became more difficult. Nonetheless the TLS-inits (requirements)
are even higher within VB6, compared with VB5 as it is now.

In short - you may not use *any* VB.COM-Object within
a lightweight-thread (or a lightweight-thread-callback) without
initializing TLS correctly beforehand (on that thread).

But you can call TypeLib-declared functions, which bypass
VBs Err-Object (also a COM-Instance) in such scenarios
without the TLS-init-requirements then.
Normal VB-Declarations for such external Calls (as PostMessage
for example) would not do, since under the hood the VB-Error-
Object would be touched whilst performing Calls to such normally
declared API-Defs (e.g. per Err.LastDllError).

So you are on the safe side (with VB5 and also with VB6)
if you only perform a Typelib-defined Call within a
procedure, which is run from another (lightweight-)
thread. And in our case that is the tlb-defined PostMessage,
which decouples already, because the receiving end of
the posted message (our hWnd) is in our mainthread again.

That hwnd is provided by a dynamically created (invisible)
PictureBox - and to avoid subclassing, to be able to receive
the sent (Post)-Message - I've simply sent a WM_SIZE
to that hidden PictureBox.hWnd, resulting in a PictureBox_
Resize-Event then - you may call that a hack - but this
approach avoids subclassing and is IDE-safe to use.
After all it is only a Windows-Message, which reuses an
already implemented EventHandler in a Window-Class
that is already contained in the VB-Runtime (our PBox).

Olaf
Author
17 Jun 2009 8:25 PM
MM
Show quote Hide quote
On Mon, 15 Jun 2009 14:01:19 +0100, "Alfie [UK]" <alfie@mail.invalid>
wrote:

>On Sun, 14 Jun 2009 14:10:09 +0200, "Schmidt" <s**@online.de> wrote:
>
>>Maybe give the MMTimer-approach (-controls) a try - and
>>see, how the "win-message-concurrency" works out,
>>compared with the entirely non-satisfying DoEvents-
>>"workaround" you currently have.
>
>An MMTimer is probably the best idea, as it runs in it's own thread,
>and as far as I know isn't blocked by other messages like a system
>timer.
>
>As Steve McMahon mentions
>http://www.vbaccelerator.com/home/vb/code/Libraries/HiResTimer/article.asp
>it is better to encapsulate the MMTimer in it's own AX DLL to avoid
>issues when working within the IDE.

I note that Steve's HiRes Timer example TestHiResTmr6.exe hangs 98SE
totally when I move the form around.

Initially, the test app keeps on truckin', then I move the form a
fraction more, it hangs and I have to use Ctrl-Alt-Delete to remove
the hung application and get Windows back.

This DOESN'T happen with CCRP Timer. That works perfectly, even in the
IDE. I can move the form around no problem.

MM
Author
18 Jun 2009 3:02 PM
Schmidt
"MM" <kylix***@yahoo.co.uk> schrieb im Newsbeitrag
news:opji351lbpjb3a29i92hp8sv79epkumcsp@4ax.com...
> On Mon, 15 Jun 2009 14:01:19 +0100, "Alfie [UK]" <alfie@mail.invalid>
> wrote:
>
> >On Sun, 14 Jun 2009 14:10:09 +0200, "Schmidt" <s**@online.de> wrote:
> >
> >>Maybe give the MMTimer-approach (-controls) a try - and
> >>see, how the "win-message-concurrency" works out,
> >>compared with the entirely non-satisfying DoEvents-
> >>"workaround" you currently have.
>
> I note that Steve's HiRes Timer example TestHiResTmr6.exe
> hangs 98SE totally when I move the form around.
I can see that here on my Win98-VM too.
But I can reproduce the freezes also with the CCRP-
timer-Classes, if I switch the resolution of them
to the same high-frequencies, Steve is using in his
Demo - and of course also my "simple-approach"
is able, to freeze Win98, since it is using the same
PostMessage-Calls for decoupling.

For the CCRP-Timer-Class - you will have to switch
the default-resolution of 20msec to 1msec beforehand,
to work withing the same stress-conditions.
ccrpTimer.Stats.Frequency = 1
followed by
ccrpTimer.Interval = 1
can ensure that.

To reproduce that with my former example, you can
shorten the current Timer-Interval of 2msec to 1msec
too - and then simply define a somewhat longer
Notes-Array-Ubound - then just click the Form-
Caption on Win98 and hold the MouseKey down -
after a short while the incoming PostMessage-Calls
will "flood" the MessageQueue - and then you have
your freeze in all three cases.

XP has apparently a MessageQueue-Overflow-protection
built in - and Win98 does lack this feature as it seems.

How to work around:
Change the already somewhat StateMachine-like Scheme
to a "full one", which retriggers itself with a new OneShot-
Request against the MMTimer - as long as certain conditions
are (yet) true, which require "to stream further".

As I see it - the OneShot-Mode is also available in the
MMTimer-Components you already have.

Again my example, which already works in that mode -
and is now also doing fine on Win98 with these changes.

The OneShot-Mode also solves another problem -
even the following "source-only"-solution is working
stable now, in case you simply press the IDEs
Stop-Button - because in that mode it is impossible,
that a "forgotten" MMTimer-Instance fires further -
you don't even need an explicite Kill on the OneShot-
MMTimer-Handle, since it cleans up itself already
internally in each OneShot.

Ok, here the adapted code again - now with some more
comments.

'***the *.bas-module-code is reduced now as you see...but as
'***in the other example, this project needs the small TypeLib too
'***which only contains the PostMessage-Call declaration.
Option Explicit

Public Declare Function timeBeginPeriod& Lib "winmm" (ByVal uPeriod&)
Public Declare Function timeEndPeriod& Lib "winmm" (ByVal uPeriod&)
Public Declare Function timeGetTime& Lib "winmm" ()
Private Declare Function timeSetEvent& Lib "winmm" (ByVal uDelay&, _
  ByVal uResolution&, ByVal lpFunction&, ByVal dwUser&, ByVal uFlags&)

Public Function MMT_OneShot(ByVal hWnd&, ByVal Interval As Long) As Boolean
  'last Param at 0 ensures OneShot, no explicite Kill needed in that mode
  MMT_OneShot = timeSetEvent(Interval, 1, AddressOf TimerProc, hWnd, 0)
End Function

'always use a typelib-defined PostMessage-call in here
Private Sub TimerProc(ByVal TID As Long, ByVal iMsg As Long, _
        ByVal dwUser As Long, ByVal dw1 As Long, ByVal dw2 As Long)
  PostMessage dwUser, &H5, 0, 0 'just send a simple wm_size
End Sub


'*** And finally that into a Form again - then click the Form...
Option Explicit

Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)

Private Type SimpleNoteDef 'somewhat simplified
  Value As Long
  Duration As Long
End Type

Private WithEvents TickReceiver As PictureBox 'receives the HighRes-Ticks

Private T_StartNote As Long 'to store the Time, the first Note was played
Private T_NextNote As Long 'Time the next Note should be played
Private NextNoteIdx As Long 'the next Note-Idx that should be played
Private Notes() As SimpleNoteDef  'define our simple "Notes-Stream-Array"


Private Sub Form_Load()
  timeBeginPeriod 1 'switch to highres-environment
  Set TickReceiver = Controls.Add("VB.PictureBox", "TR") 'a hidden PB
  InitSimpleDemoStream
End Sub

Private Sub Form_Unload(Cancel As Integer)
  timeEndPeriod 1 'reset highres-environment
End Sub

'that Click-Event is "misused", to start the streaming
Private Sub Form_Click()
  If Not AutoRedraw Then AutoRedraw = True
  Cls 'since the demo-print-output is directed to the Form

  PlayStream 0 'here we start at Note-Index 0
End Sub

Private Sub StopStream() 'and that procedure is able, to stop streaming
  If T_NextNote = 0 Then Exit Sub 'nothing to stop
  T_NextNote = 0 'singalize "no-streaming"
  Sleep 2 'just wait for an eventual last, yet incoming PostMessage
  DoEvents 'this is allowed here, since we want to stop anyways
End Sub

Private Sub PlayStream(Optional ByVal FromNoteIdx As Long)
Dim i As Long
  StopStream
  T_StartNote = timeGetTime

  T_NextNote = T_StartNote 'initialize to the current time...
  For i = 0 To FromNoteIdx
    T_NextNote = T_NextNote + Notes(i).Duration '...increase as needed
  Next i
  NextNoteIdx = FromNoteIdx + 1 'define the next NoteIdx

  PlayNote FromNoteIdx 'play the start-note already here
  CheckForNextNote T_StartNote 'and enter our OneShot-Loop
End Sub

Private Sub InitSimpleDemoStream() 'just the init for our DemoStream-Array
Dim i As Long
  ReDim Notes(19) 'only 20 Notes here
  For i = 0 To UBound(Notes)
    Notes(i).Value = i
    Notes(i).Duration = 100 'msec Duration (for all Notes, to simplify)
  Next i
End Sub


'********* and the state-machine like retriggering, based
'********* on the OneShot-MMTimer-Events, which are delegated
'********* to the PictureBox-Resize-Event per PostMessage-Call

'just use a Msg with a somewhat higher prio than wm_keydown or wm_paint
Private Sub TickReceiver_Resize()
  'no retriggering, in case T_NextNote = 0 (this is "no-streaming" state)
  If T_NextNote = 0 Then Exit Sub

  CheckForNextNote timeGetTime
End Sub

Private Sub CheckForNextNote(ByVal T_Current As Long)
  If T_Current < T_NextNote Then 'we're not yet there, so...
    MMT_OneShot TickReceiver.hWnd, 1 '...just trigger the next round
    Exit Sub
  End If

  PlayNote NextNoteIdx 'first play the now current Note

  If NextNoteIdx >= UBound(Notes) Then 'we reached the end of the stream
    T_NextNote = 0 'T_NextNote at Zero is our "no-streaming" indicator
    Exit Sub
  End If

  'prepare for the next round
  T_NextNote = T_NextNote + Notes(NextNoteIdx).Duration
  NextNoteIdx = NextNoteIdx + 1

  'not yet at the end of the stream, so trigger the next MMTimer-OneShot
  MMT_OneShot TickReceiver.hWnd, 1
End Sub

Private Sub PlayNote(ByVal CurNoteIdx As Long)
  If CurNoteIdx > UBound(Notes) Then Exit Sub 'just to make sure ...

  Print Notes(CurNoteIdx).Value, _
        "played at Abs-StreamTime:"; timeGetTime - T_StartNote
End Sub

Olaf
Author
15 Jun 2009 9:14 PM
Karl E. Peterson
MM wrote:
> All I want is a replacement for Sleep, as in
>
> Sleep 32
>
> as accurate as poss, where the duration might be as low as 1.
>
> How can I best achieve that with the CCRP Timer?

I'd setup a notification interface, myself, but what really matters is how you
intend to respond to the notification.

Hard to tell with a quick browse of the thread -- did you get your objective(s)
worked out?
--
..NET: It's About Trust!
http://vfred.mvps.org

Bookmark and Share