[linux-audio-dev] a brief ramble on design

New Message Reply About this list Date view Thread view Subject view Author view Other groups

Subject: [linux-audio-dev] a brief ramble on design
From: Paul Barton-Davis (pbd_AT_Op.Net)
Date: Wed May 03 2000 - 08:41:40 EEST


Tonight, I found myself bitten once again by a design issue that
affected Quasimodo, was solved there, and reared its ugly head again
in SoftWerk. It could affect your programs too, at least if
you're ambitious.

Very few applications exist for Linux that use two event loops, both
of which have GUI implications. Under X, you collect input events
from the X server (i.e. ultimately from the keyboard, the mouse and/or
any other input devices known to X), and process them. Toolkits like
GTK offer ways to add new file descriptors to this event loop,
apparently making the task of merging all external events into a
single event stream fairly easy.

Alas, it isn't so. Putting the file descriptor for a MIDI port into
GTK's event input loop (with gtk_input_add()) will almost certainly
result in poor MIDI response, especially if your GUI is busy. If
time-sensitive MIDI data is coming in to your app, you will likely
have no choice but to collect it in *another* thread.

OK, so far so good. Two thread, two select(2) loops running, we're
fine.

Not so fast. What happens when you want incoming MIDI data to affect
the GUI ? To understand the question, you need to recall that X (and
GTK and Qt and Motif) are still essentially single-threaded. You can't
safely make X calls from multiple threads without a mutex to prevent
simultaneous collisions. But locks are bad for real-time work, so we
want to avoid them wherever possible. We therefore have a
problem. Consider the following scenario: an incoming MIDI note on is
intended to transpose all the notes in the sequence displayed in the
GUI. We want both the underlying data (the noteOn/noteOff values of
the sequence) to be modified - thats easy to do from the MIDI
thread. But we also want the GUI representation updated. OK, not so
difficult - we just get the GUI to install a callback on the
underlying data, so that whenever it changes, our callback is
executed, and the GUI is updated. But wait - the change in the data is
being done by the MIDI thread. Unless it takes a lock designed to
protect the X server/toolkit, we'll likely run into problems with
directly calling the GUI toolkit functions from the MIDI thread. And
we've agreed to not take locks. Fine, we'll make the MIDI thread just
queue a request with the GUI so that when it gets a chance, the GUI
will update the display in the right way. If our callback for changes
in the underlying data simply marks the updates needed, we should be
OK.

Things are looking good, until you remember that the GUI might be able
to cause changes in the underlying data as well. The callback we
install to pick up changes in the underlying data will be executed for
those changes caused by the GUI as well as those induced by the MIDI
thread. How can the GUI thread know that the changes it finds when it
comes to updates its display relate to its own activity (and may thus
require different handling for efficiency's sake) ?

The short answer to this long question is to make sure that you always
use an "EventSource" whenever you invoke a callback to notify one
object that another has changed. That way, the notified object can
properly distinguish what actions are needed based on the
object/source that initiated the change.

Failure to do this will result in many headaches. This is really just
basic "Model-View-Controller" programming, in which the Model is the
underlying data (the MIDI data for the sequence, for example; the View
is the GUI (just one in this case) and the Controllers are both the X
event loop and the MIDI input event loop. Without the ability to
identify which Controller caused a change in the Model, the View will
be forced to make suboptimal choices in how it responds to those
changes. For example, it may update a set of widgets that are already
up-to-date.

There is an alternative to this, which is to provide the View with
some way to check if its current "presentation" matches the current
state of the Model. This works very well when the Model contains
simple scalar data, and comparisons can be performed cheaply and
easily. If the data is more structured or is large, however, and
particularly if the presentation made by the view merges distinct
aspects of the Model into single visual objects (e.g. softwerk
represents a step in a sequence with both a number and a colored
background to indicate if it is active), then this kind of comparison
gets tricky and/or cannot be done correctly without locks. Knowing the
event source can help with this, I think.

OK, end of ramble. I've just checked in a version of SoftWerk which
incorporates most of these kinds of changes. I discovered this
afternoon that SoftWerk's "recorded MIDI" mode would reliably crash
it. Oh well, we live and (re)learn.

--p


New Message Reply About this list Date view Thread view Subject view Author view Other groups

This archive was generated by hypermail 2b28 : Wed May 03 2000 - 09:13:35 EEST