High Resolution Multimedia Timer

A Stable, Safe high resolution timer for VB applications, capable of 1ms resolution.

Speedy Pic, Tenuously Related To The Article

The standard timer supplied with VB is great for most tasks, but the frequency it updates at isn't acceptable for high-performance multimedia. In audio applications the system must be capable of firing audio events with a 1ms resolution, otherwise the ear will be able to discern the timing inaccuracy.

This article presents a small, hardcore multimedia timer capable of 1ms resolution in VB code.

Timer Overview

The VB timer is based on the standard Win32 timer, and this is not intended for time critical tasks. The basic minimum resolution of this timer is no better than about 50ms on a Pentium II system running Win9x, although it is somewhat better on NT/2000/XP, at around 10ms.

This leads to an irritating VB coding problem: if you write graphics code on an NT system using a timer it all seems to work nicely, but when you run it on Win9x it runs too slowly because the timer doesn't fire so quickly. Whilst one alternative to this sort of problem is using a DoEvents, Sleep and QueryPerformanceCounter loop, this is rarely what you want in a real world application.

About Multimedia Timers

Multimedia timers are implemented using the Win32 multimedia library (winmm.dll). Before creating a timer, it is important to be aware of a number of system limitations and VB problems which can occur when using multimedia timers.

Timers are a Limited Resource

In Win9x, the system is limited to a maximum of only 32 multimedia timers. (In NT, things are much better and each process on the system can have up to 16 timers.) To provide full Win9x support it is therefore desirable to minimise the number of timers you create to reduce the chance of running out of timer resources. A single application should never create more than one timer regardless of how many controls, DLLs or modules within that application require the timer's services.

Multimedia Timers are remorseless

If you build a multimedia timer directly into a VB application, you will almost certainly not be able to debug it with any consistency. Building this timer component was somewhat tricky for this reason! Any time the application stops for an error or a break-point, the timer keeps on firing from its own separate thread in the background. This immediately crashes or locks up the VB IDE. Adding Debug.Print messages doesn't help either - the debug window can't keep up with the high rate of messages fired by the timer and VB IDE locks up in a loop again.

You can only make a handful of calls from the timer event

The Win32 SDK states that only the following calls are allowed during a multimedia timer event:

  • PostMessage
  • timeGetSystemTime
  • timeGetTime
  • timeSetEvent
  • timeKillEvent
  • midiOutShortMsg
  • midiOutLongMsg
  • OutputDebugString

This isn't much! It seems to be correct for most standard operations. For example, I found just putting a Debug.Print statement in the timer event causes the timer to crash when you try to terminate it. Putting methods there to raise an event directly also caused an untimely crash. (Note that from other MSDN code, it would appear that DirectX calls are allowed)

Implementing a Stable HiRes Timer in VB

The first thing to do with this sort of component is to ensure the code can be isolated from the VB IDE by coding it into an ActiveX DLL. You really don't want to have to try to deal with the IDE locking up every time you set a break-point.

Once it is in the DLL then the next thing is to consider how to reduce the number of timer instances to ensure you aren't too likely to run out of them. In this code, the timer itself is implemented in a module. If you implement code in a module, this module will be global to all classes which use it within a process. As well as your executable project, this includes any OCX or DLL which attaches to the DLL. Therefore it is possible to create a single timer to service as many classes as you want. The trick is to ensure that the module doesn't get a reference to the classes that are attached, otherwise you will get a circular reference. You can prevent circular references by storing object pointers rather than direct references to classes as described in the article Subclassing without the crashes.

The final problem to work around is that you can only call a very limited selection of functions during a multimedia timer event. I work around this by using the PostMessage function. The function posts a custom WM_USER message to a hidden form owned by the timer module within the DLL. The form is subclassed by the DLL so when the custom message is received it is intercepted by the hidden form's WindowProc function, and this allows the module to forward the message on as an event to any objects which are using the DLL.

This method of working may seem rather stupid - a timer event is received by the DLL's timer module, then posted to a form and intercepted from the form using the same DLL - but it turns out this is vital to ensure the timer is stable during operation. There are various examples of how to crash your system by not doing this available elsewhere on the Internet (no names - you know who you are :)

When using the DLL provided with the download, you can get stable crash-free operation. You can press stop in the VB IDE when the timer is running without any problems. Even if you set the timer to do something at a silly rate which your code can't keep up with you shouldn't have difficulty provided you use the Event interface, because the PostMessage operation prevents things going wrong.

However, I should point out if you are running the DLL code in the group project, no such niceties apply. No point giving disclaimers about when and how you will crash - you will! It isn't a problem though, just restart VB. If you're interested in building this code into your project, I strongly advise using the DLL to begin with and then only incorporate the code when you come to make the EXE.

Using the HiRes Timer

The HiRes Timer offers a simple way of adding and removing timers, and there are two ways of receiving timer events (the Third Way, as proposed by UK's increasingly bizarre Prime Minister, will not be made available here).

  • Responding to timers the first way involves setting up the class as WithEvents. If you do this, the timer will call the Timer event every time it fires. This is the simplest and easiest to debug method to use.
  • The second way of receiving timer events is via an implemented interface. If the calling object Implements the ITimer interface, you will have to implement the ITimer_Timer(ByVal sKey As String) sub. If you do this, you should use the Connect method to specify the object which receives the timer notifications. By doing this, you create an early-bound interface between your code and the Timer, and you receive notifications with the minimum of system overhead. This method is the fastest but can cause trouble as it can occasionally overwhelm VB's IDE with event calls whilst you are debugging, something which can't happen with the event interface. The best idea is to use the Events interface for development and then switch to the implementation method for compiled executables.

The demonstration project supplied with the download demonstrates setting up two timer objects and enabling/disabling them. It also compares the performance of the High-Resolution timer against the standard VB Timer. On my Win95 system, the High-Resolution version manages to fire 40x more events than the standard VB version. (On NT4/2000/XP it manages about 10x more).

More!

If you aren't interested in the code for this sort of thing, then I recommend looking at the High-Resolution timer available from the Common Controls Replacement Project (CCRP). This offers extra interfaces such as a countdown timer and an About screen (Hmmm.)

Top code, but depends on your aim. If you want to create a MIDI or Audio sequencer, then you need the code, and CCRP can't give you that for their own personal reasons. An audio project has got to to be 1ms fast all the time and you don't want to know about the hacks that some other coder went through to get their version to happen - you need to make your own hacks and get it to run at the right speed. Hence the source code here.

One odd thing about the CCRP component is that it appears to be recommended as a general timer - I'm not sure that the Multimedia one does this job. The VB Timer is a nice thing in all ways except that it requires a form to site it on, but don't let that put you off. VB only ever uses one timer resource no matter how many apps run on the system (because all VB apps by definition are linked to the VB run-time library) so you are rarely in danger of running out of timers.