1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
|
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.cpp, 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
|