Heres what Im doing… My Metronome class extends AudioSource. I first create an instance of Synthesizer , lets call him samplerSynth. I create two .wav AudioFormatReaders, and load up my two sound files (one a high tick, one a lower tick). I create a MidiMessageCollector to store our the midi messages. Then I set the midi note ranges I want for each sound file (really just one midi note for each), add a Voice to my Synthesizer for playback, and then add the two sounds as SamplerSounds. This sounds like a lot but its actually pretty simple: (in my Metronomes constructor…)
audioFormatManager.registerFormat(wavAudioFormat = new WavAudioFormat(), true);
//First we must load out metronome .wav files into a reader
metroHighReader = audioFormatManager.createReaderFor(File("/Users/jhochenbaum/Documents/C++/Nuance/data/Sounds/MetronomeHigh.wav")); //TODO make this path relative
metroLowReader = audioFormatManager.createReaderFor(File("/Users/jhochenbaum/Documents/C++/Nuance/data/Sounds/MetronomeLow.wav"));//TODO make this path relative
//Define the midi note ranges each sound will be assigned to...
BigInteger highNotes;
highNotes.setRange (60, 60, true);
BigInteger lowNotes;
lowNotes.setRange (61, 61, true);
//then we must create SamplerSounds from the readers, and add them to our synth
samplerSynth.addVoice(new SamplerVoice()); //this voice will be used to play the sound
samplerSynth.addSound(new SamplerSound("metroHigh", *metroHighReader,highNotes,60,0,0,1.0));
samplerSynth.addSound(new SamplerSound("metroLow",*metroLowReader,lowNotes,61,0,0,1.0));
samplerSynth.setNoteStealingEnabled(false); //must turn note stealing off
Then to make sure samplerate is set correctly…
void Metronome::prepareToPlay (int /*samplesPerBlockExpected*/, double sampleRate){
midiCollector.reset (sampleRate);
samplerSynth.setCurrentPlaybackSampleRate (sampleRate);
}
void Metronome::getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill){
// the synth always adds its output to the audio buffer, so we have to clear it first..
bufferToFill.clearActiveBufferRegion();
// fill a midi buffer with incoming messages from the midi input.
MidiBuffer incomingMidi;
midiCollector.removeNextBlockOfMessages (incomingMidi, bufferToFill.numSamples);
// and now get the synth to process the midi events and generate its output.
samplerSynth.renderNextBlock (*bufferToFill.buffer, incomingMidi, 0, bufferToFill.numSamples);
}
I’m not sure how you are counting ticks and whatnot, but I’m doing mine in my threads Run() method. I wait for a tick duration, increment my tickCounter, then call a method called playSound
void Metronome::playSound() {
if (tickCounter % 96 == 0) {
samplerSynth.allNotesOff(0, false);
if (shouldPlay) //this gets set when the user enables the Metronome from the GUI
{
if (beatCount == 0){
samplerSynth.noteOn(0, 60, 1); //play sound one on the first beat
}else{
samplerSynth.noteOn(0, 61, 1 ); //else play sound two on all other beats
}
}
beatCount++;
if (beatCount >= beatMax)
beatCount = 0;
}
}
So yah, you might have a more accurate metronome counter-- I haven’t tested the consistency of how im counting my actual ticks, but whats important here I guess is that Im just creating a synthesizer, adding a voice, adding two sounds which get played back on specific midi notes (in this case 60 and 61), and then depending on which beat were on, I send a noteOn message to my Synthesizer, instructing it which sound to play back… your way definitely sounds interesting, but this seemed simplest to me… also allows an arbitrary sound to be used as the metro sounds which is kinda cool
Best,
J