denemo-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Denemo-devel] Real time playback in Denemo


From: Richard Shann
Subject: [Denemo-devel] Real time playback in Denemo
Date: Sun, 01 Jan 2012 18:13:30 +0000

I think the next big step for Denemo will be to get the playback working
in its own thread.

Below are some ideas for how to do this, first there are some notes
which you can cut out if you already know how the soc code is working.

8>< 8>< 8>< 8>< 8>< 8>< 8>< 8>< 8>< 8>< 8>< 8>< 8>< 8>< 8>< 8>< 8>< 8>< 
I have studied the soc branch code and made the following notes:

The playback works by using two threads, one is the
process_thread_func() of the backend chosen (I see alsabackend.c and
dummybackend.c so far) and the other is the queue_thread_func() in
audiointerface.c

The process_thread_func() creates a mutex and then runs a loop:
the loop gets the system time g_get_current_time() and to it adds 5ms (a
compiler constant called PLAYBACK_INTERVAL). This time is then used for
a timed wait on a condition with the mutex, that is a call to
g_cond_timed_wait(). This means that the thread waits for the condition
process_cond to be signalled or until the 5ms have elapsed.
The g_cond_timed_wait() unlocks the mutex before the thread sleeps and
locks it when it continues. 
***Question*** is it ok that the mutex has not been locked for the first
time (and correspondingly, at the end g_mutex_free() is called without
unlocking the mutex, is that ok?).

Next a check is made for quitting the loop - this is done by making an
atomic access to an int which is set by the alsa_seq_destroy() call.
***Question*** does that need to be an atomic access? The int in
question is just a boolean, so in C it just means that if any of the
bits are set it is true. So it really doesn't matter if another thread
is halfway through setting it.

Next is code which I think is for MIDI input - I ignore this here.

Now the playback code continues by getting the current time and
subtracting a value called playback_start_time. This latter value is set
up when starting the playing by getting the system time and subtracting
a value "playback_time" which is kept in audiointerface.c and controlled
by calls to the function update_playback_time() and initialized by
midi_play() to Denemo.gui->si->start_time. This last is the time in
seconds from the start of the piece which the user has set as the time
to start from (e.g. via d-SetPlaybackInterval etc).
So for the case of playing an entire movement, this is zero, and the
playback_start_time is the system time when the play was initiated.

Back to the loop which the process_thread_func() runs...

The difference between the current time and the playback_start_time is
called the playback_time and is time in seconds along the midi track
where the next event should be taken from.

After a check for reset (I ignore that here) an event is read from the
queue. This is the call read_event_from_queue() in audiointerface.c.
This is called with a parameter called until_time which is set to the
playback_time plus 5ms (the PLAYBACK_INTERVAL value again). After
checking for stop play conditions (discuss later) this calls into
eventqueue.c for the function event_queue_read_output(). This checks the
midi event time is < the until_time and returns TRUE and the event if so
otherwise FALSE.
Further events are read until this call to get an event from the queue
fails.
Finally before the loop begins again the playback_time that the backend
has, is "sent back" to audiointerface.c via update_playback_time().

8>< 8>< 8>< 8>< 8>< 8>< 8>< 8>< 8>< 8>< 8>< 8>< 8>< 8>< 8>< 8>< 8>< 8>< 

This code lacks the flexibility to speed up, slow down or pause the
playback - once the playback_start_time has been fixed the midi events
following that time will be sent out by referencing the current system
time.
This could be changed so that instead of executing g_get_current_time()
the time is obtained from a thread which manages the requests from the
user to pause, change tempo etc.

Currently, I think there are two examples of how to do this in Denemo.
The first was the one I wrote to allow live change of tempo - very
useful for getting the right tempo by adjusting the slider while the
music is playing. The second I wrote to allow the playback to follow a
user playing along with it.
Only the first of these is easy enough to understand that I still have
it in my head: when the tempo slider is moved all the elapsed times that
are obtained are scaled. To make this work, the playback_start_time has
to be re-calculated - so it is as if we had been playing since then at
the new tempo and had just reached the current playback time. In this
way we do not have to keep a history of all the tempo changes.
The second method of controlling the playback was for following the
player (or conductor). In this case the user is setting a time at which
the playback should pause (unless the user has updated it meanwhile).
I am not sure if these could be unified.

What would the new time-control thread look like? Well, for all the
paused time it could add up the total of pauses so far and subtract
these from the time value it returns. While actually in a pause, I guess
it would just keep returning the same time - only when the pause is
ended would it increment the total amount of pause time and subtract
that from the time value it would otherwise return. I guess it could
also manage the tempo change signal, adjusting the playback_start_time
and scaling the times it returns. Perhaps though, it should be returning
not absolute times but times since start? So it would be returning 0
during a pause and a scaled difference of (g_get_current_time() -
playback_start_time) and the total_pause_time. (?)

I would welcome comments.

Richard Shann




















reply via email to

[Prev in Thread] Current Thread [Next in Thread]