Metronome

If you are looking for a metronome to keep precise timing in an ActionScript application, look no further. I give you: Metronome. It has a precision of ± 10 ms. (about 0.03% at moderate tempos) and never, ever drifts away from the master clock.

This class uses the sound subsystem as its time reference. Unlike timer events, sounds are not tied to the frame rate. This makes them better to precisely trigger events on an accurate schedule. Caveat: as highlighted by Pavel in the comments below, this means that this library cannot work on a system without a sound card. You can read more about the process behind the creation of this library in my post titled: Accurate Timing in ActionScript.

The library is super easy to use. The following code will trace “Tick!” at a tempo of 140BPM. You will also hear matching beeps (can be silenced) on each tick :

1
2
3
4
5
6
7
8
9
import cc.cote.metronome.*;
 
var metro:Metronome = new Metronome(140);
metro.addEventListener(MetronomeEvent.TICK, onTick);
metro.start();
 
public function onTick(e:MetronomeEvent):void {
    trace('Tick!');
}

By default, the beep sound is always the same. However, if you want, you can tell the metronome to play certain beats with a different, accented, sound. For example, to play the first of 4 beat accented, you would use the following:

1
var metro:Metronome = new Metronome(140, 1, [true, false, false, false]);

If you want to use the Metronome more like a (more accurate) timer, you can define the interval in milliseconds (instead of BPMs), silence it and assign a predetermined number of ticks after which it should stop:

1
2
3
4
5
6
7
8
9
10
11
var metro:Metronome = new Metronome();
metro.interval = 1000;
metro.volume = 0;
metro.maxTickCount = 5;
metro.addEventListener(MetronomeEvent.TICK, onTick);
metro.addEventListener(MetronomeEvent.STOP, onTick);
metro.start();
 
public function onTick(e:MetronomeEvent):void {
    trace(e);
}

For more information, check out the full API reference (also available in the download package).

Download Metronome 1.0b rev1 here as a zip file or directly on GitHub. Let me know if it works for you!

Using the library in Flash Pro

This library uses sound assets embedded with the [Embed] instruction. This will work fine in FlashBuilder (and tools that use similar compilers) but might give you problems in Flash Pro. If you are using Flash Pro, you should simply link the swc file to avoid any issues.

To do that, go to the File menu and select “ActionScript Parameters“. In the “Library Path” tab, simply add the Metronome.swc file (see below).

linking_metronome_swc

Comments

    1. Let me know how it works out for you in a production context. I want feedback from users to confirm this library actually stands up to demanding environments.

      Merci!

        1. I added some info about linking the Metronome.swc file in Flash Pro at the end of the article. Keep me posted!

          P.S. It seems this library is a perfect match for your sight-reading application.

  1. Many questions
    Let me know if I Can use French language (otherwise I will translate)
    Comment puis-je obtenir des événements sonores plus précis ?
    Même avec extraPrecise ce n’est pas assez précis pour un travail musical.
    Comment gerez-vous le son de accentedBeep et de regularBeep ? Comment contrôler le volume ?
    Serait-il possible de disposer des accentedBeep sur n’importe quelle impulsion ?

    var monclic: clic = new clic(); // clic.mp3 -- son importé dans la bibliothèque
    var canal: SoundChannel = new SoundChannel();
    var modifSonMetro: SoundTransform = new SoundTransform();
    modifSonMetro.volume = 0.7;
    canal.soundTransform = modifSonMetro;
    monclic.play();
    var pulse: int;
    var temps: int = 0;
    var nbTemps: int = 4;
    var metro2: Metronome = new Metronome();
    metro2.tempo = 560; //(eight note id quarter note = 140)
    metro2.base = 6;
    metro2.silent = true;
    metro2.extraPrecise = true;
    metro2.maxTickCount = 0;
    metro2.addEventListener(MetronomeEvent.TICK, onTick2);
    metro2.addEventListener(MetronomeEvent.STOP, onTick2);
    
    function onTick2(e: MetronomeEvent): void {
    	pulse = ((e.count - 1) % 6) + 1;
    	if (pulse == 1 || pulse == 3 || pulse == 4) {
    		monclic.play();
    	}
    	if (pulse == 1) {
    		timeText2.htmlText = String((temps % nbTemps) + 1);
    		temps++;
    		timeText2.setTextFormat(formatduTexteTime);
    	}
    }
    
    1. [Il n’y a pas de problème pour le français mais je vais répondre en anglais pour le bénéfice de tous.]

      Regarding precision, I am suprised that you don’t find it precise enough. It’s not perfect but with extraPrecise enabled, the standard deviation of the precision should be below 10ms (that’s what my tests show). What environment are you getting those results in (Adobe AIR, Flash Player, Flash CS6, Flash CC, Flash Builder, OS, etc.).

      Or maybe, you have another process that’s hogging up the CPU ? If you have access to Adobe Scout, you could check if each frame is processed within its allowed budget. If not, this is definitely what is causing the timing issues. I will try your code early next week and see what results I’m getting.

      To use custom beep sounds, you simply assign a Sound object to accentedSound or regularSound. Concerning the volume, I realize just now how huge of an oversight this was (whoops!)… I will add a volume property early next week (perhaps with an accentedVolume and regularVolume also).

      Regarding your last question, I’m nost sure what would be the best approach. Perhaps I could give you the possibility to pass an array of the beats you want accented ? I’ll think about it.

      In any case, thanks a lot for taking the time to post your comments. I’m away for the week-end but I’ll check it out next week.

      Merci.

  2. Hello

    In Air app it’s ok (it’s only in Flash pro test mode that it does not work very well).

    It would be really nice if we could apply silent for only regularBeep and also control the volume of accentedBeep

    All the best

    JPGN

    1. I did my precision tests in Flash Pro CC and was getting good results. What version of Flash Pro are you using ? What version of Flash Player are you targeting ? I’ll try to replicate your results.

      As for the silent setting and volume control, they are very good ideas. I’ll add them next week.

      Thanks again!

      1. The volume can now be controlled separately for regular and accented beeps. It is also now possible to pass an array of which beats should be accented. For this to work, I had to change the constructor’s signature, though. I hope this is what you were looking for.

  3. Great tool, thanks! There is an active trace() running in the Metronome._tick() function within the SWC (it is commented out in the AS3 code version, so that is a valid workaround.)

        1. If you have further ideas to contribute, let me know. If you don’t mind, when your application is ready, I’d like to link to its website as an example of what can be created using the library.

          Merci Jean-Paul.

    1. I totally agree with you. It would be so much cleaner this way. However, there is a documented bug (orphaned link) with the Sound.loadPCMFromByteArray() method that prevents the SoundChannel of a dynamically generated Sound from triggering its SOUND_COMPLETE event. Obviously, this library depends on that.

      A workaround to this bug is to dynamically generate a swf that includes the needed Sound objects and to load that into memory (this is what André Michelle did for tonfall). The problem with this workaround is that it would require us to wait for the loader to finish before starting to use the metronome. This felt like more of a hassle than a solution…

  4. I think it would be fair (for beginners) to mention, that this will not work on systems without sound cards.

    1. You are right. I will add that to this page and to the documentation. In fact, I will also check for a soundcard when the object is instantiated and throw an error if none is present. I just didn’t think of that. Thanks for the suggestion.

  5. Hi. Is there a way to use this class for android?
    Could you please explain in detail how can someone use this feature in android?

    1. If the device is equipped with a soundcard, I don’t see why it wouldn’t work precisely as described above. Unfortunately, I do not have an Android device on hand to test it.

  6. Hi Jean-Philippe, this looks like an awesome class to replace Timers (where high-precision is a must!)

    Got one question though – is this suited more for handling Sound playback (like creating a MIDI sequencer, that sort of thing?) or can it still be used for dispatching / triggering callbacks at very low MS intervals (say… every 10ms or something)?

    If I understand how you’re using the “SampleDataEvent” technique, even if I added a callback method to fire every 10ms, wouldn’t it fire several of those callbacks in the same SampleDataEvent cycle (meaning, pretty much executing all at the same time +/- the callback’s execution time)? If not, I’m curious how you would delay the invocation of those callbacks while running the SampleDataEvent.

    If you don’t mind sharing how your Metronome class functions internally, I’d appreciate it!
    Thanks 🙂

    1. Hi Pierre,

      It sure can be used as a Timer replacement for precise triggering of callbacks. Since it depends on the sound subsystem, you will need a sound card and one free SoundChannel. Your project does not have to be sound-related.

      However, you have to understand that this class was designed for accuracy and not for rapidity. As a matter of fact, it won’t allow intervals smaller than 100ms. The SampleDataEvent.SAMPLE_DATA event will fire (at best) every 46ms. That’s because the smallest allowed sound buffer in Flash/AIR is 2048 samples. If you divide that by the sample rate (which is fixed at 44100), you get 0.046 sec. or 46ms.

      When morePrecise is set to false (default), the class actually uses the Event.SOUND_COMPLETE event instead of SampleDataEvent.SAMPLE_DATA. In this scenario, you could theoretically set the interval to be as small as you want but the accuracy would probably suffer a lot. That’s why I decided to leave a minimum of 100ms. After all, it is a metronome, not a Timer ! 😉

Leave a Reply

Your email address will not be published. Required fields are marked *