Midi, Audio and Synchronization:
================================

1. Introduction
2. The midi manager
3. Midi synchronization
4. Audio timestamping and synchronization
5. Example code


  1. Introduction
  ---------------

Since aRts-1.0 (as shipped with KDE3.0), aRts provides a lot more
infrastructure to deal with midi, audio, and their synchronization. The main
goal is to provide a unified interface between sequencers (or other programs
that require notes or audio tracks to be played at certain given time stamps)
and underlying software/hardware that can play notes/audio tracks.

Currently, there exist five distinct destinations that aRts supports, which
can all be used at the same time or individually, that is:

 * aRts synthetic midi instruments
 * ALSA-0.5
 * ALSA-0.9
 * OSS
 * other aRts modules (including but not limited to the playback/recording
   of audio tracks)

  2. The midi manager
  -------------------

The midi manager is the basic component that connects between applications
that supply/record midi data, and devices that process midi data. Devices
might be both, virtual (as in software synthesis) or real (as in hardware
devices).

From the view of the midi manager, all event streams correspond to one midi
client. So, a midi client might be an application (such as a sequencer) that
provides events, or an ALSA hardware device that consumes events. If there
are multiple event streams, they correspond to multiple clients. That is,
if an application wishes to play three different midi tracks, one over ALSA,
and two over two different synthetic instruments, it needs to register itself
three times, with three different clients.

The midi managers job is to connect midi clients (as in event streams). It
maintains a list of connections that the user can modify with an application
like artscontrol. Applications could also, if they wish so, modify this
connection list. 

As a use case, we'll consider the following: you want to write a sequencer
application that plays back two different tracks to two different devices.
You want the user to be able to select these devices in a drop down box for
each track.

1) getting a list of choices:

First, you will want to obtain a list of choices which the user could possibly
connect your tracks to. You do so by reading the

interface MidiManager { // SINGLETON: Arts_MidiManager
    /**
     * a list of clients
     */
    readonly attribute sequence<MidiClientInfo> clients;

	//...
};

attribute. The three fields of each client that are interesting for you are

struct MidiClientInfo {
	long ID;

	//...

	MidiClientDirection direction;
	MidiClientType type;
	string title;
};

You would list those devices in the dropdown box that are of the appropriate
direction, which is mcdRecord, as you would want a client that receives midi
events (this might be confusing, but you look from the view of the client).

Then, there is the type field, which tells you whether the client is a device-
like thing (like a synthetic instrument), or another application (like another
application currently recording a track). While it might not be an impossible
setup that you send events between two applications, usually users will choose
such clients that have mctDestination as type.

Finally, you can list the titles in a drop down box, and keep the ID for making
a connection later.

2) registering clients:

You will need to register one client for each track. Use

	/**
	 * add a client
     *
	 * this creates a new MidiManagerClient
	 */ 
	MidiClient addClient(MidiClientDirection direction, MidiClientType type,
							string title, string autoRestoreID);

to do so.

3) connecting:

As you probably don't want your sequencer user to use artscontrol to setup
connections between your tracks and the devices, you will need to connect
your clients to the hardware devices for playing something.

You can connect clients to their appropriate destinations using

	/**
	 * connect two clients
	 */
	void connect(long clientID, long destinationID);

and

	/**
	 * disconnect two clients
	 */
	void disconnect(long clientID, long destinationID);

Keep in mind that a client might be connected to more than one destination
at the same time, so that you will need to disconnect the old destination
before connecting the new one.

4) playing events:

You can now play events to the tracks, using each client's

	MidiPort addOutputPort();

function for getting a port where you can send events to. However, you will
also need to ensure that the events will get synchronized as soon as you are
playing back events to different devices. Read the next section for details
on this.

  3. Midi synchronization
  -----------------------

As soon as you are writing a real sequencer, you might want to output to more
than one midi device at a time. For instance, you might want to let some of
your midi events be played by aRts synthesis, while others should be sent
over the external midi port.

To support this setup, a new interface called MidiSyncGroup has been added. To
output midi events synchronized over more than one port, you proceed as follows:

a) you obtain a reference to the midi manager object

   MidiManager midiManager = DynamicCast(Reference("global:Arts_MidiManager"));
   if(midiManager.isNull()) arts_fatal("midimanager is null");

b) you create a midi synchronization group which will ensure that the
   timestamps of your midi events will be synchronized
   
   MidiSyncGroup syncGroup = midiManager.addSyncGroup();

c) you add a client to the midi manager for each port you want to output
   midi data over

   MidiClient client = midiManager.addClient(mcdPlay, mctApplication, "midisynctest", "midisynctest");
   MidiClient client2 = midiManager.addClient(mcdPlay, mctApplication, "midisynctest2", "midisynctest2");

d) you insert the clients in the synchronization group

   syncGroup.addClient(client);
   syncGroup.addClient(client2);

e) you create ports for each client as usual

   MidiPort port = client.addOutputPort();
   MidiPort port2 = client2.addOutputPort();

f) at this point, you will need to ensure that the midi clients you created
   are connected, you can either leave the user with artscontrol for doing
   this, or use the clients and connect methods of the midiManager object
   yourself (see use case discussed in previous section)

g) you output events over the ports as usual

   /* where t is a suitable TimeStamp */
   MidiEvent e = MidiEvent(t,MidiCommand(mcsNoteOn|0, notes[np], 100));
   port.processEvent(e);
   port2.processEvent(e);

  4. Audio timestamping and synchronization
  -----------------------------------------

Audio in aRts is usually handled as structures consisting of small modules
that do something. While this model allows you to describe anything you want
to, from playing a sample to playing a synthetic sequence of notes with a
synthetic instruments, it doesn't give you any notion of time. More so, if
you build a large graph of objects, you might need quite some time for this,
and you will want to have them all started at the same time.

To solve this issue, an AudioSync interface has been introduced, that allows
you to start() and stop() either synchronized at a specific point in time.

Suppose you have two synthesis modules which together play back a sample.
What can you do to start them at the same time?

	Synth_PLAY_WAV wav = //... create on server
	Synth_AMAN_PLAY sap //... create on server
	AudioSync audioSync = //... create on server

	wav.filename("/opt/trinity/share/sounds/pop.wav");
	sap.title("midisynctest2");
	sap.autoRestoreID("midisynctest2");
	connect(wav,sap);

	// this queues back start() to be called atomically later
	audioSync.queueStart(wav);
	audioSync.queueStart(sap);

	// this line is a synchronized version of
	// wav.start();
	// sap.start();
	audioSync.execute();

You could also play them back at a specific time in the future and query the
current time using the time and executeAt methods:

interface AudioSync {
	/**
	 * the current time
	 */
	readonly attribute TimeStamp time;

	//...

	/**
	 * atomically executes all queued modifications to the flow system
	 * at a given time
	 */
	void executeAt(TimeStamp timeStamp);
};

Finally, to get synchronized midi and audio, you can insert the AudioSync
object into a midi synchronization group, then their timestamps will be
synchronized to those of the midi channels.

  5. Example code
  ---------------

An example that illustrates most things discussed in this document is
midisynctest.cc, which plays back two synchronized midi streams and samples.
Note that you might want to change the source code, as it hardcodes the
location of the .wav file.


Questions and comments are welcome.

Stefan Westerfeld
stefan@space.twc.de