SEQUENCER
1.1 Introduction
This document describes a flexible sequencer architecture for the ALSA
project.
1.1.1 Currently available sequencer
The currently available sequencer interfaces for Linux are the
/dev/sequencer and /dev/music from the Open Sound System, OSS or OSS/Lite.
Though these interfaces are sufficiently useful for most sequencer
applications they have a few shortcomings:
- Only one application at a time can have access.
- Because of non real-time character of a time-shared system like Linux the
driver offers a queue in the kernel which is needed to prevent events to be
scheduled too late. This queue introduces big latency in event processing.
- It's one big monolithic driver.
Especially the 2nd issue restricts building midi oriented applications that
can perform on-par with applications on Apple Macinctoshes and Atari ST's
regarding real-time response. Examples:
- If one wants to have a sequencer perform a MIDI THRU function, it will
suffer form big delays, and using the OUT_OFF_BAND ioctl() results in
hanging notes because the driver has no clue what events are playing.
- Events have to be enqueued ahead. Parameter changes (eg. volume control,
muting tracks or instruments) will not be effective instantly.
In practice the currently available (OSS) sequencer seems to be best suited
for applications that only perform playback of event like eg. MIDI and MOD
file players.
1.1.2 New sequencer
To overcome these disadvantages ALSA will bring a new architecture for
scheduling and dispatching MIDI and MIDI oriented events within the Linux
sound driver. Note that this is still in the brainstorming phase, and has
yet to be developed.
Some of the ideas presented here are inspired by the
MidiShare "MIDI operating
system" (http://www.grame.fr/english/MidiShare.html), which exists for
Apple mac's.
This new sequencer is intended as a replacement for /dev/music and
/dev/sequencer. For compatibility with older applications these old
interfaces can be emulated if needed.
Highlights:
- Support for multiple in-depended event queues. Each event queue can have
it's own tempo.
- Supports multiple concurrent clients, both in user-land and in kernel space.
- Kernel space clients can be loaded as modules.
- The sequencer takes care of dispatching and routing of events, any
client can send messages to any other client or broadcast the message to all
clients that want to receive that message. The sequencer basicly handles
all the 'inter client communications'.
- Super fast event routing in case of messaging between kernel module clients.
- Events can be inserted into the queue in arbitrary order, the sequencer
uses a priority queue to make sure events are dequeued in the right order.
- This is only a sequencer framework, all I/O and intelligent processing
(eg. MIDI, synth) has to be performed by clients. There's nothing but
clients.
- Processing of high-level MIDI oriented messages: note on at timestamp
xxx, control change at timestamp xxx.
- Event queues can accept both timestamps in us and timestamps in tick.
- The sequencer handles tempo changes and supports synchronization, both
as master and as slave.
1.1.3 Concept of clients
The core sequencer only takes care of passing messages between clients and
delivering messages at the right time to the right client. All processing
has to be done within the clients. Because of this separation of
responsibility the sequencer can be optimized for it's main task: sequencing
events. All (complex) processing is left to be done by the clients.
This approach results in a modular configuration in which the clients of
which a service is needed can be loaded. Other clients that are not needed
do not have to be activated.
Two types of clients are supported:
- Kernel mode clients
- User-land clients
It's up to the client developer where to place his or her client: in or
outside the kernel. The right place depends on the needs of the specific
client.
Using this client architecture we get a few advantages:
- All the ugly hardware specific details that make life complicated can go
into the parts where they belong: the device specific drivers. No need to
pollute the sequencer core with it.
- There is no need to keep adding new functionality (like midi thru etc.)
to the sequencer because the sequencer won't do more than sequencing and
routing events from one client to another. All the bells and whistles ("I
want to have it make coffee") can implemented in clients.
- OSS compatibility can be guaranteed by creating a client with /dev/music
and /dev/sequencer interface that simply maps the OSS events to ALSA events.
- With a special client that calls the OSS to perform/play ALSA events we
can make use of hardware that is supported on OSS but not yet on ALSA (use
OSS as low-level I/O for the ALSA sequencer).
- The concept of the sequencer can be quite clean and easy so that is
would not be a problem to implement it. Being a simple system the interfaces
can reach a stable state quite fast, which is a good thing for developers of
sequencer clients.
Sample applications of clients to get a feeling of the flexibility of this
architecture:
- The high-level event interface for sound cards can be implemented as
clients. This client (for example for a Gravis Ultrasound) can take care of:
- Driving MIDI port, translating ALSA sequencer events into MIDI byte
stream and interpreting the incoming MIDI bytes and translate these into
ALSA events.
- Drive the on-board synthesizer. The client has to take care about voice
allocation, instrument mapping etc. to make the GUS GF1 synth react to ALSA
sequencer messages.
- The mixer on the sound card can also be presented as a device that can
react on control changes (volume).
- Bank managers, synth editors can be run parallel with sequencers. The
recording source for a sequencer does not need to be a MIDI input port, but
can also be the output of some other client, like for instance a bank
manager. All sys-ex to setup a midi device can so be recorded in the
sequencer.
- A single application does not need to provide all the functionality one
can think of. Why put a GM/XG mixer application within a sequencer
application like Steinberg's Cubase does if one can use an external
application, and let these interact. If one needs a 'meter bridge' that
shows the levels for all the MIDI channels in the system, but the sequencer
doesn't provide such a thing, a second 'meter bridge only' client can be
used! Similar for mixers, bank managers (download samples to Wavetable synth
on reception of program changes!).
- Other applications (apart from drivers) could be support modules for
high-end sequencers, that need fine-grained real-time control or last-minute
database changes.
- Linux based (embedded Linux) MIDI patch-bay: Receive MIDI events from the
input, do some processing, and send it (almost instantly) out.
- Imagine one would like to have a soft synth. Instead of going though the
trouble of developing one in kernel, the developer can start off with
running user-land timidity, and let the existing MIDI playing applications
send their data to the 'timidity client' instead of the 'GUS MIDI output
port' client. For the player it doesn't make any difference (only send
events to another destination), and voila, we have a soft-synth. Over time,
the developer can improve upon this soft-synth, make it more real-time, and
possibly running it as kernel client.
1.1.4 Architecture
The architecture is build around a priority queue. An internal clock is used
to determine dispatch time for the enqueued events. When an event is to
dispatched it is send through an event router, which sends the event to the
right client(s).
Graphic of the architecture, for only a single queue and 4 clients:

Because the sequencer architecture supports more parallel operating queues
the actual diagram looks slightly more complex. A sequencer with 4 queues
and a few more clients can be found in next diagram:

All events flow through the queue. Because a priority queue is used instead
of a simple FIFO, it's no problem to accept unordered events from multiple
clients.
Clients can either be user-land applications, accessing /dev/seq or
kernel modules that can directly submit events, and are directly
called when a event is dispatched to them (even from interrupt mode).