PDA

View Full Version : Replacing "busy wait" with QTimer



devdon
19th January 2012, 21:09
Using the RtMidi library, I'm setting up a loop to play multiple notes and voices. However the examples in RTMidi use a "busy wait" SLEEP to keep the sound sustained until the note ends which makes it impossible to play, say, quarter notes and whole notes together, as the quarter notes have to wait until the whole note has "SLEPT" until they can play.

I've tried using QTimer::singleShot and I've tried offloading the SLEEP with QtConcurrent::run to a different thread, and tried combining them. My working theory is that using either a timer or a thread or a combination can give the notes time to play without causing the other notes to have to wait.

Each QT call has worked as they are supposed to but don't achieve the desired effect of playing one note after another in the case of the quarter notes. They all sound at once. So far I haven't even gotten to the point of multi-voices but that is essential, too.

What is wrong with this code that I can't get the desired effect?


...
foreach (Note* tn, noteList) {
// Note On: 144, 64, 90
message[0] = 144;
message[1] = tn->midiNumber;
message[2] = tn->velocity;
midiout->sendMessage( &message );

// setup params in globals for now
mNum = tn->midiNumber;
int ticks = (tn->ticks * 125);

QTimer::singleShot(ticks , this, SLOT(noteOff())); // can't do parameters

/*
SLEEP (tn->ticks * 125); ////////////////////// original example code with SLEEP
// Note Off: 128, 64, 40
message[0] = 128;
message[1] = mNum; // tn->midiNumber;
message[2] = 100; //tn->velocity;
midiout->sendMessage( &message );
*/
}
...



void AMidi_Rt::noteOff()
{
// should fire accurately after each QTimer::singleShot timer times out
// this routine stops a specific note after a specific amount of time
message[0] = 128;
message[1] = mNum; // tn->midiNumber;
message[2] = 100; //tn->velocity;
midiout->sendMessage( &message );
}

wysota
19th January 2012, 22:14
So what causes the notes to start playing? The code looks like all notes will start playing at the same time (at the moment when you do the foreach loop) and will cause the noteOff() slot to be called multiple times with mNum set to the last note on the list.

devdon
19th January 2012, 23:28
The foreach loop sends the note -on message (// Note On: 144, 64, 90 - message[0] = 144;) then I have a singleshot timer which repeatedly calls a routine that turns the notes off one at a time according to the ticks variable in the singleShot timer.

Added after 1 8 minutes:

I see what you mean now about the notes starting at the same time with the singleShot. With the SLEEP in place, it buffers the notes so that they play separately. I didn't take that into account. Your help in replacing this SLEEP function is appreciated. I've tried double timers staggered by the ticks variable but the starting problem remains. I found that you can't put a timer into a slot to call a slot, or at least I couldn't make it work. Is that possible? If it were possible to cascade the timers it would work I think.

wysota
19th January 2012, 23:45
Do you want notes to play one after the other? If so then the most trivial approach would be:


void X::startPlaying(QList<Note*> noteList) {
m_notes = noteList;
m_currentlyPlaying = 0;
QTimer::singleShot(0, this, SLOT(playNextNote()));

}

void X::playNextNote() {
if(m_currentlyPlaying) {
// stop playing that note
message[0] = 128;
message[1] = m_currentlyPlaying->midiNumber;
message[2] = 100; //tn->velocity;
midiout->sendMessage( &message );
delete m_currentlyPlaying;
}
// check if anything left to play
if(m_notes.isEmpty()) {
m_currentlyPlaying = 0;
return;
}
// play next note
m_currentlyPlaying = m_notes.first();
m_notes.removeFirst();
message[0] = 144;
message[1] = m_currentlyPlaying->midiNumber;
message[2] = m_currentlyPlaying->velocity;
midiout->sendMessage( &message );

// schedule next note
int ticks = m_currentlyPlaying->ticks * 125;
QTimer::singleShot(ticks, this, SLOT(playNextNote()));
}

If you want to introduce pauses, just make them special cases of Note and treat them properly in "play next note" part of the code (that is don't send the message).
If you want multiple sounds at the same time then either have separate lists for each instrument or add a parameter to the structure that will differentiate between the finish offset of the current sound and beginning time of the next sound (e.g. setting it to 0 will make it play immediately).

devdon
20th January 2012, 12:21
wysota,

This looks interesting and I very much want to pursue it but my development computer crashed and I'm going to go ahead and wipe and reinstall on it so I'll have to get back to you later with results. Thanks for your help, I'll be back.