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
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
|
<HTML>
<HEAD>
<TITLE>The Synthesis ToolKit in C++ (STK)</TITLE>
<LINK HREF="doxygen.css" REL="stylesheet" TYPE="text/css">
</HEAD>
<BODY BGCOLOR="#FFFFFF">
<CENTER>
<img src="princeton.gif"> <img src="ccrma.gif"><P>
<a class="qindex" href="index.html">Home</a> <a class="qindex" href="information.html">Information</a> <a class="qindex" href="classes.html">Classes</a> <a class="qindex" href="download.html">Download</a> <a class="qindex" href="usage.html">Usage</a> <a class="qindex" href="maillist.html">Mail List</a> <a class="qindex" href="system.html">Requirements</a> <a class="qindex" href="links.html">Links</a> <a class="qindex" href="tutorial.html">Tutorial</a></CENTER>
<HR>
<!-- Generated by Doxygen 1.3-rc3 -->
<h1>oldRtMidi.cpp</h1><div class="fragment"><pre>00001 <span class="comment">// RtMidi class declaration</span>
00002 <span class="comment">// Realtime MIDI input/output class.</span>
00003 <span class="comment">//</span>
00004 <span class="comment">// This class provides access to a single MIDI input or output port.</span>
00005 <span class="comment">// Create multiple instances of this class to connect to more than</span>
00006 <span class="comment">// one MIDI device at the same time.</span>
00007 <span class="comment">//</span>
00008 <span class="comment">// by Gary P. Scavone, 2003.</span>
00009
00010 <span class="preprocessor">#include "RtMidi.h"</span>
00011 <span class="preprocessor">#include <sstream></span>
00012 <span class="preprocessor">#include <sys/time.h></span>
00013
00014 <span class="keywordtype">void</span> midiInputCallback( <span class="keyword">const</span> MIDIPacketList *list, <span class="keywordtype">void</span> *procRef, <span class="keywordtype">void</span> *srcRef )
00015 {
00016 RtMidi::RtMidiCallbackInfo *info = static_cast<RtMidi::RtMidiCallbackInfo *> (procRef);
00017
00018 RtMidi::MidiMessage *message;
00019 <span class="keyword">const</span> MIDIPacket *packet = &list->packet[0];
00020 <span class="keywordflow">for</span> ( <span class="keywordtype">unsigned</span> <span class="keywordtype">int</span> i=0; i<list->numPackets; ++i ) {
00021
00022 <span class="comment">// For the moment, we're limiting ourselves to basic MIDI messages</span>
00023 <span class="comment">// and ignoring the (real) possibility that multiple MIDI messages</span>
00024 <span class="comment">// can be packed into a single MIDIPacket. FIXME!!!</span>
00025 <span class="keywordflow">if</span> ( packet->length > 3 ) <span class="keywordflow">continue</span>;
00026
00027 message = static_cast<RtMidi::MidiMessage *> (&info->buffer[info->writeIndex++]);
00028 message->bytes.assign( packet->data, &packet->data[packet->length] );
00029
00030 <span class="comment">// - have status byte:</span>
00031 <span class="comment">// - look for subsequent data bytes of known number (unless sysex ... unknown number)</span>
00032 <span class="comment">// - if find a system realtime:</span>
00033 <span class="comment">// - copy incomplete message to next buffer</span>
00034 <span class="comment">// - clear current buffer and write realtime message</span>
00035 <span class="comment">// - continue parsing data bytes</span>
00036 <span class="comment">// while !finished parsing:</span>
00037 <span class="comment">// if data byte</span>
00038 <span class="comment">// if ( incomplete ) {</span>
00039 <span class="comment">// for ( int i = 0; i < packet->length; i++ ) {</span>
00040 <span class="comment">// if while ( packet->data[</span>
00041 <span class="comment">// }</span>
00042 <span class="comment">//}</span>
00043
00044 <span class="comment">// Calculate time stamp.</span>
00045 message->timeStamp = 0.0;
00046 <span class="keywordflow">if</span> ( info->doDeltaTime ) {
00047 <span class="keywordtype">unsigned</span> <span class="keywordtype">long</span> time = packet->timeStamp;
00048 time -= info->lastTime;
00049 info->lastTime = packet->timeStamp;
00050 time = AudioConvertHostTimeToNanos( time );
00051 message->timeStamp = time * 0.000000001;
00052 }
00053 <span class="keywordflow">else</span> { <span class="comment">// absolute time in seconds since midnight (0 hour), January 1, 1970</span>
00054 timeval time;
00055 <span class="keywordflow">if</span> ( gettimeofday( &time, NULL ) != 0 ) {
00056 std::cerr << <span class="stringliteral">"RtMidi: error in midiInputCallback calculating time stamp."</span>;
00057 }
00058 <span class="keywordflow">else</span> {
00059 message->timeStamp = (double) time.tv_sec;
00060 message->timeStamp += (double) (time.tv_usec * 0.000001);
00061 }
00062 }
00063
00064 <span class="keywordflow">if</span> ( info->writeIndex >= nMessages )
00065 info->writeIndex = 0;
00066
00067 <span class="keywordflow">if</span> ( info->usingCallback ) {
00068 <a class="code" href="classRtMidi.html#s0">RtMidi::RtMidiCallback</a> callback = (RtMidi::RtMidiCallback) info->userCallback;
00069 callback( message, info->userData );
00070 }
00071
00072 packet = MIDIPacketNext(packet);
00073 }
00074 }
00075
00076 <a class="code" href="classRtMidi.html#a0">RtMidi :: RtMidi</a>( TimeType time )
00077 {
00078 <span class="keywordflow">if</span> ( time == ABSOLUTE_TIME ) callbackInfo_.doDeltaTime = <span class="keyword">false</span>;
00079 callbackInfo_.lastTime = AudioGetCurrentHostTime();
00080 readIndex_ = 0;
00081 connected_ = <span class="keyword">false</span>;
00082 }
00083
00084 <a class="code" href="classRtMidi.html#a0">RtMidi :: RtMidi</a>( PortType type, <span class="keywordtype">unsigned</span> <span class="keywordtype">int</span> portNumber, TimeType time )
00085 {
00086 <span class="keywordflow">if</span> ( time == ABSOLUTE_TIME ) callbackInfo_.doDeltaTime = <span class="keyword">false</span>;
00087 callbackInfo_.lastTime = AudioGetCurrentHostTime();
00088 readIndex_ = 0;
00089 connected_ = <span class="keyword">false</span>;
00090
00091 <span class="comment">// Attempt to make the connection.</span>
00092 this-><a class="code" href="classRtMidi.html#a3">openPort</a>( type, portNumber );
00093 }
00094
<a name="l00095"></a><a class="code" href="classRtMidi.html#a2">00095</a> <a class="code" href="classRtMidi.html#a2">RtMidi :: ~RtMidi</a>()
00096 {
00097 <span class="keywordflow">if</span> ( connected_ ) {
00098 MIDIClientDispose( client_ );
00099 MIDIPortDispose( port_ );
00100 }
00101 }
00102
<a name="l00103"></a><a class="code" href="classRtMidi.html#a3">00103</a> <span class="keywordtype">void</span> <a class="code" href="classRtMidi.html#a3">RtMidi :: openPort</a>( PortType type, <span class="keywordtype">unsigned</span> <span class="keywordtype">int</span> portNumber )
00104 {
00105 <span class="keywordflow">if</span> ( connected_ ) {
00106 errorString_ = <span class="stringliteral">"RtMidi::openPort: a valid connection already exists."</span>;
00107 error( RtError::WARNING );
00108 <span class="keywordflow">return</span>;
00109 }
00110
00111 <span class="keywordflow">if</span> ( portNumber < 0 ) {
00112 errorString_ = <span class="stringliteral">"RtMidi::openPort: the 'portNumber' argument must be greater than zero."</span>;
00113 error( RtError::INVALID_PARAMETER );
00114 }
00115
00116 std::ostringstream ost;
00117 <span class="keywordflow">if</span> ( type == MIDI_INPUT ) {
00118 <span class="keywordtype">unsigned</span> <span class="keywordtype">int</span> nSrc = MIDIGetNumberOfSources();
00119 <span class="keywordflow">if</span> (nSrc < 1) {
00120 errorString_ = <span class="stringliteral">"RtMidi::openPort: no MIDI input sources found!"</span>;
00121 error( RtError::NO_DEVICES_FOUND );
00122 }
00123
00124 <span class="keywordflow">if</span> ( portNumber >= nSrc ) {
00125 ost << <span class="stringliteral">"RtMidi::openPort: the 'portNumber' argument ("</span> << portNumber << <span class="stringliteral">") is invalid."</span>;
00126 errorString_ = ost.str();
00127 error( RtError::INVALID_PARAMETER );
00128 }
00129
00130 OSStatus result = MIDIClientCreate( CFSTR(<span class="stringliteral">"RtMidi Input Client"</span>), NULL, NULL, &client_ );
00131 <span class="keywordflow">if</span> ( result != noErr ) {
00132 errorString_ = <span class="stringliteral">"RtMidi::openPort: error creating OS-X MIDI client object."</span>;
00133 error( RtError::DRIVER_ERROR );
00134 }
00135
00136 result = MIDIInputPortCreate( client_, CFSTR(<span class="stringliteral">"RtMidi Virtual MIDI Input Port"</span>), midiInputCallback, (<span class="keywordtype">void</span> *)&callbackInfo_, &port_ );
00137 <span class="keywordflow">if</span> ( result != noErr ) {
00138 MIDIClientDispose( client_ );
00139 errorString_ = <span class="stringliteral">"RtMidi::openPort: error creating OS-X MIDI input port."</span>;
00140 error( RtError::DRIVER_ERROR );
00141 }
00142
00143 <span class="comment">// Get the desired input source identifier.</span>
00144 MIDIEndpointRef src = MIDIGetSource( portNumber );
00145 <span class="keywordflow">if</span> ( src == NULL ) {
00146 MIDIPortDispose( port_ );
00147 MIDIClientDispose( client_ );
00148 errorString_ = <span class="stringliteral">"RtMidi::openPort: error getting MIDI input source reference."</span>;
00149 error( RtError::DRIVER_ERROR );
00150 }
00151
00152 <span class="comment">// Make the connection.</span>
00153 result = MIDIPortConnectSource(port_, src, NULL );
00154 <span class="keywordflow">if</span> ( result != noErr ) {
00155 MIDIPortDispose( port_ );
00156 MIDIClientDispose( client_ );
00157 errorString_ = <span class="stringliteral">"RtMidi::openPort: error connecting OS-X MIDI input port."</span>;
00158 error( RtError::DRIVER_ERROR );
00159 }
00160
00161 isInputPort_ = <span class="keyword">true</span>;
00162 callbackInfo_.lastTime = AudioGetCurrentHostTime();
00163 }
00164 <span class="keywordflow">else</span> { <span class="comment">// MIDI_OUTPUT</span>
00165 <span class="keywordtype">unsigned</span> <span class="keywordtype">int</span> nDest = MIDIGetNumberOfDestinations();
00166 <span class="keywordflow">if</span> (nDest < 1) {
00167 errorString_ = <span class="stringliteral">"RtMidi::openPort: no MIDI output destinations found!"</span>;
00168 error( RtError::NO_DEVICES_FOUND );
00169 }
00170
00171 <span class="keywordflow">if</span> ( portNumber >= nDest ) {
00172 ost << <span class="stringliteral">"RtMidi::openPort: the 'portNumber' argument ("</span> << portNumber << <span class="stringliteral">") is invalid."</span>;
00173 errorString_ = ost.str();
00174 error( RtError::INVALID_PARAMETER );
00175 }
00176
00177 OSStatus result = MIDIClientCreate( CFSTR(<span class="stringliteral">"RtMidi Output Client"</span>), NULL, NULL, &client_ );
00178 <span class="keywordflow">if</span> ( result != noErr ) {
00179 errorString_ = <span class="stringliteral">"RtMidi::openPort: error creating OS-X MIDI client object."</span>;
00180 error( RtError::DRIVER_ERROR );
00181 }
00182
00183 result = MIDIOutputPortCreate( client_, CFSTR(<span class="stringliteral">"RtMidi Virtual MIDI Output Port"</span>), &port_ );
00184 <span class="keywordflow">if</span> ( result != noErr ) {
00185 MIDIClientDispose( client_ );
00186 errorString_ = <span class="stringliteral">"RtMidi::openPort: error creating OS-X MIDI output port."</span>;
00187 error( RtError::DRIVER_ERROR );
00188 }
00189
00190 <span class="comment">// Get the desired output port identifier.</span>
00191 destinationId_ = MIDIGetDestination( portNumber );
00192 <span class="keywordflow">if</span> ( destinationId_ == NULL ) {
00193 MIDIPortDispose( port_ );
00194 MIDIClientDispose( client_ );
00195 errorString_ = <span class="stringliteral">"RtMidi::openPort: error getting MIDI output destination reference."</span>;
00196 error( RtError::DRIVER_ERROR );
00197 }
00198
00199 isInputPort_ = <span class="keyword">false</span>;
00200 }
00201
00202 connected_ = <span class="keyword">true</span>;
00203 }
00204
<a name="l00205"></a><a class="code" href="classRtMidi.html#a4">00205</a> <span class="keywordtype">void</span> <a class="code" href="classRtMidi.html#a4">RtMidi :: closePort</a>( <span class="keywordtype">void</span> )
00206 {
00207 <span class="keywordflow">if</span> ( connected_ ) {
00208 MIDIClientDispose( client_ );
00209 MIDIPortDispose( port_ );
00210 }
00211 <span class="keywordflow">else</span> {
00212 errorString_ = <span class="stringliteral">"RtMidi::closePort: a port connection was not open!"</span>;
00213 error( RtError::WARNING );
00214 }
00215 connected_ = <span class="keyword">false</span>;
00216 }
00217
<a name="l00218"></a><a class="code" href="classRtMidi.html#a5">00218</a> <span class="keywordtype">unsigned</span> <span class="keywordtype">int</span> <a class="code" href="classRtMidi.html#a5">RtMidi :: getPortCount</a>( PortType type )
00219 {
00220 <span class="keywordflow">if</span> ( type == MIDI_INPUT )
00221 <span class="keywordflow">return</span> MIDIGetNumberOfSources();
00222 <span class="keywordflow">else</span> <span class="comment">// MIDI_OUTPUT</span>
00223 <span class="keywordflow">return</span> MIDIGetNumberOfDestinations();
00224 }
00225
<a name="l00226"></a><a class="code" href="classRtMidi.html#a6">00226</a> std::string <a class="code" href="classRtMidi.html#a6">RtMidi :: getPortName</a>( PortType type, <span class="keywordtype">unsigned</span> <span class="keywordtype">int</span> portNumber )
00227 {
00228 CFStringRef nameRef;
00229 MIDIEndpointRef portRef;
00230 std::ostringstream ost;
00231 <span class="keywordtype">char</span> name[128];
00232 <span class="keywordflow">if</span> ( type == MIDI_INPUT ) {
00233 <span class="keywordflow">if</span> ( portNumber < 0 || portNumber >= MIDIGetNumberOfSources() ) {
00234 ost << <span class="stringliteral">"RtMidi::getPortName: the 'portNumber' argument ("</span> << portNumber << <span class="stringliteral">") is invalid."</span>;
00235 errorString_ = ost.str();
00236 error( RtError::INVALID_PARAMETER );
00237 }
00238 portRef = MIDIGetSource( portNumber );
00239 }
00240 <span class="keywordflow">else</span> { <span class="comment">// MIDI_OUTPUT</span>
00241 <span class="keywordflow">if</span> ( portNumber < 0 || portNumber >= MIDIGetNumberOfDestinations() ) {
00242 ost << <span class="stringliteral">"RtMidi::getPortName: the 'portNumber' argument ("</span> << portNumber << <span class="stringliteral">") is invalid."</span>;
00243 errorString_ = ost.str();
00244 error( RtError::INVALID_PARAMETER );
00245 }
00246 portRef = MIDIGetDestination( portNumber );
00247 }
00248
00249 MIDIObjectGetStringProperty( portRef, kMIDIPropertyName, &nameRef );
00250 CFStringGetCString( nameRef, name, <span class="keyword">sizeof</span>(name), 0);
00251 CFRelease( nameRef );
00252 std::string stringName = name;
00253 <span class="keywordflow">return</span> stringName;
00254 }
00255
<a name="l00256"></a><a class="code" href="classRtMidi.html#a7">00256</a> <span class="keywordtype">void</span> <a class="code" href="classRtMidi.html#a7">RtMidi :: setInputCallback</a>( RtMidiCallback callback, <span class="keywordtype">void</span> *userData)
00257 {
00258 callbackInfo_.userCallback = (<span class="keywordtype">void</span> *) callback;
00259 callbackInfo_.userData = userData;
00260 callbackInfo_.usingCallback = <span class="keyword">true</span>;
00261 }
00262
00263 <span class="keyword">const</span> RtMidi::MidiMessage* <a class="code" href="classRtMidi.html#a10">RtMidi :: getMessage</a>( <span class="keywordtype">void</span> )
00264 {
00265 currentMessage_.bytes.clear();
00266 <span class="keywordflow">if</span> ( !connected_ ) {
00267 errorString_ = <span class="stringliteral">"RtMidi::getNextMessage: a port is not open!"</span>;
00268 error( RtError::WARNING );
00269 <span class="keywordflow">return</span> &currentMessage_;
00270 }
00271
00272 <span class="keywordflow">if</span> ( !isInputPort_ ) {
00273 errorString_ = <span class="stringliteral">"RtMidi::getNextMessage: this is not an input port!"</span>;
00274 error( RtError::WARNING );
00275 <span class="keywordflow">return</span> &currentMessage_;
00276 }
00277
00278 <span class="keywordflow">if</span> ( callbackInfo_.usingCallback ) {
00279 errorString_ = <span class="stringliteral">"RtMidi::getNextMessage: a user callback is currently set for this port."</span>;
00280 error( RtError::WARNING );
00281 <span class="keywordflow">return</span> &currentMessage_;
00282 }
00283
00284 <span class="keywordflow">if</span> ( readIndex_ == callbackInfo_.writeIndex ) <span class="keywordflow">return</span> &currentMessage_;
00285
00286 currentMessage_ = callbackInfo_.buffer[readIndex_++];
00287 <span class="keywordflow">if</span> ( readIndex_ >= nMessages ) readIndex_ = 0;
00288
00289 <span class="keywordflow">return</span> &currentMessage_;
00290 }
00291
00292 <span class="keywordtype">bool</span> <a class="code" href="classRtMidi.html#a11">RtMidi :: sendMessage</a>( <span class="keyword">const</span> MidiMessage *message )
00293 {
00294 <span class="keywordflow">if</span> ( !connected_ ) {
00295 errorString_ = <span class="stringliteral">"RtMidi::sendMessage: a port is not open!"</span>;
00296 error( RtError::WARNING );
00297 <span class="keywordflow">return</span> <span class="keyword">false</span>;
00298 }
00299
00300 <span class="keywordflow">if</span> ( isInputPort_ ) {
00301 errorString_ = <span class="stringliteral">"RtMidi::sendMessage: this is not an output port!"</span>;
00302 error( RtError::WARNING );
00303 <span class="keywordflow">return</span> <span class="keyword">false</span>;
00304 }
00305
00306 <span class="keywordtype">unsigned</span> <span class="keywordtype">int</span> nBytes = message->bytes.size();
00307 <span class="comment">// Pad the buffer for extra (unknown) structure data.</span>
00308 Byte buffer[nBytes+32];
00309 MIDIPacketList *pktlist = (MIDIPacketList *) buffer;
00310 MIDIPacket *curPacket = MIDIPacketListInit( pktlist );
00311
00312 MIDITimeStamp timeStamp = 0;
00313 <span class="comment">// If a non-zero delta or absolute time, convert to CoreMIDI's host time.</span>
00314 <span class="keywordflow">if</span> ( message->timeStamp != 0.0 ) {
00315 <span class="keywordflow">if</span> ( callbackInfo_.doDeltaTime ) {
00316 timeStamp = AudioConvertHostTimeToNanos( AudioGetCurrentHostTime() );
00317 timeStamp += (UInt64) (message->timeStamp * 1000000000.0);
00318 timeStamp = AudioConvertNanosToHostTime( timeStamp );
00319 }
00320 <span class="keywordflow">else</span> {
00321 timeval tvTime;
00322 <span class="keywordtype">double</span> time;
00323 <span class="keywordflow">if</span> ( gettimeofday( &tvTime, NULL ) != 0 ) {
00324 errorString_ = <span class="stringliteral">"RtMidi::sendMessage: error calculating time stamp."</span>;
00325 error( RtError::WARNING );
00326 }
00327 <span class="keywordflow">else</span> {
00328 time = (double) tvTime.tv_sec;
00329 time += (double) (tvTime.tv_usec * 0.000001);
00330 time = message->timeStamp - time;
00331 <span class="keywordflow">if</span> ( time > 0.0 ) {
00332 timeStamp = AudioConvertHostTimeToNanos( AudioGetCurrentHostTime() );
00333 timeStamp += (UInt64) (time * 1000000000.0);
00334 timeStamp = AudioConvertNanosToHostTime( timeStamp );
00335 }
00336 }
00337 }
00338 }
00339 curPacket = MIDIPacketListAdd( pktlist, <span class="keyword">sizeof</span>(buffer), curPacket, timeStamp, nBytes, &message->bytes[0]);
00340
00341 OSStatus result = MIDISend( port_, destinationId_, pktlist );
00342 <span class="keywordflow">if</span> ( result != noErr ) {
00343 errorString_ = <span class="stringliteral">"RtMidi::sendMessage: error sending MIDI message."</span>;
00344 error( RtError::WARNING );
00345 <span class="keywordflow">return</span> <span class="keyword">false</span>;
00346 }
00347
00348 <span class="keywordflow">return</span> <span class="keyword">true</span>;
00349 }
00350
00351 <span class="keywordtype">void</span> RtMidi :: error( RtError::Type type )
00352 {
00353 <span class="keywordflow">if</span> (type == RtError::WARNING) {
00354 std::cerr << <span class="charliteral">'\n'</span> << errorString_ << <span class="stringliteral">"\n\n"</span>;
00355 }
00356 <span class="keywordflow">else</span> <span class="keywordflow">if</span> (type == RtError::DEBUG_WARNING) {
00357 <span class="preprocessor">#if defined(__RTMIDI_DEBUG__)</span>
00358 <span class="preprocessor"></span> std::cerr << <span class="charliteral">'\n'</span> << errorString_ << <span class="stringliteral">"\n\n"</span>;
00359 <span class="preprocessor">#endif</span>
00360 <span class="preprocessor"></span> }
00361 <span class="keywordflow">else</span> {
00362 std::cerr << <span class="charliteral">'\n'</span> << errorString_ << <span class="stringliteral">"\n\n"</span>;
00363 <span class="keywordflow">throw</span> <a class="code" href="classRtError.html">RtError</a>( errorString_, type );
00364 }
00365 }
</pre></div><HR>
<table>
<tr><td><A HREF="http://www-ccrma.stanford.edu/software/stk/"><I>The Synthesis ToolKit in C++ (STK)</I></A></td></tr>
<tr><td>©1995-2002 Perry R. Cook and Gary P. Scavone. All Rights Reserved.</td></tr>
</table>
</BODY>
</HTML>
|