Chris@559
|
1 /**********************************************************************/
|
Chris@559
|
2 /*! \class RtMidi
|
Chris@559
|
3 \brief An abstract base class for realtime MIDI input/output.
|
Chris@559
|
4
|
Chris@559
|
5 This class implements some common functionality for the realtime
|
Chris@559
|
6 MIDI input/output subclasses RtMidiIn and RtMidiOut.
|
Chris@559
|
7
|
Chris@559
|
8 RtMidi WWW site: http://music.mcgill.ca/~gary/rtmidi/
|
Chris@559
|
9
|
Chris@559
|
10 RtMidi: realtime MIDI i/o C++ classes
|
Chris@1397
|
11 Copyright (c) 2003-2016 Gary P. Scavone
|
Chris@559
|
12
|
Chris@559
|
13 Permission is hereby granted, free of charge, to any person
|
Chris@559
|
14 obtaining a copy of this software and associated documentation files
|
Chris@559
|
15 (the "Software"), to deal in the Software without restriction,
|
Chris@559
|
16 including without limitation the rights to use, copy, modify, merge,
|
Chris@559
|
17 publish, distribute, sublicense, and/or sell copies of the Software,
|
Chris@559
|
18 and to permit persons to whom the Software is furnished to do so,
|
Chris@559
|
19 subject to the following conditions:
|
Chris@559
|
20
|
Chris@559
|
21 The above copyright notice and this permission notice shall be
|
Chris@559
|
22 included in all copies or substantial portions of the Software.
|
Chris@559
|
23
|
Chris@559
|
24 Any person wishing to distribute modifications to the Software is
|
Chris@1397
|
25 asked to send the modifications to the original developer so that
|
Chris@1397
|
26 they can be incorporated into the canonical version. This is,
|
Chris@1397
|
27 however, not a binding provision of this license.
|
Chris@559
|
28
|
Chris@559
|
29 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
Chris@559
|
30 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
Chris@559
|
31 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
Chris@559
|
32 IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
Chris@559
|
33 ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
Chris@559
|
34 CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
Chris@559
|
35 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
Chris@559
|
36 */
|
Chris@559
|
37 /**********************************************************************/
|
Chris@559
|
38
|
Chris@1397
|
39 /*!
|
Chris@1397
|
40 \file RtMidi.h
|
Chris@1397
|
41 */
|
Chris@559
|
42
|
Chris@559
|
43 #ifndef RTMIDI_H
|
Chris@559
|
44 #define RTMIDI_H
|
Chris@559
|
45
|
Chris@1397
|
46 #define RTMIDI_VERSION "2.1.1"
|
Chris@1397
|
47
|
Chris@1397
|
48 #include <exception>
|
Chris@1397
|
49 #include <iostream>
|
Chris@559
|
50 #include <string>
|
Chris@1397
|
51 #include <vector>
|
Chris@1397
|
52
|
Chris@1397
|
53 /************************************************************************/
|
Chris@1397
|
54 /*! \class RtMidiError
|
Chris@1397
|
55 \brief Exception handling class for RtMidi.
|
Chris@1397
|
56
|
Chris@1397
|
57 The RtMidiError class is quite simple but it does allow errors to be
|
Chris@1397
|
58 "caught" by RtMidiError::Type. See the RtMidi documentation to know
|
Chris@1397
|
59 which methods can throw an RtMidiError.
|
Chris@1397
|
60 */
|
Chris@1397
|
61 /************************************************************************/
|
Chris@1397
|
62
|
Chris@1397
|
63 class RtMidiError : public std::exception
|
Chris@1397
|
64 {
|
Chris@1397
|
65 public:
|
Chris@1397
|
66 //! Defined RtMidiError types.
|
Chris@1397
|
67 enum Type {
|
Chris@1397
|
68 WARNING, /*!< A non-critical error. */
|
Chris@1397
|
69 DEBUG_WARNING, /*!< A non-critical error which might be useful for debugging. */
|
Chris@1397
|
70 UNSPECIFIED, /*!< The default, unspecified error type. */
|
Chris@1397
|
71 NO_DEVICES_FOUND, /*!< No devices found on system. */
|
Chris@1397
|
72 INVALID_DEVICE, /*!< An invalid device ID was specified. */
|
Chris@1397
|
73 MEMORY_ERROR, /*!< An error occured during memory allocation. */
|
Chris@1397
|
74 INVALID_PARAMETER, /*!< An invalid parameter was specified to a function. */
|
Chris@1397
|
75 INVALID_USE, /*!< The function was called incorrectly. */
|
Chris@1397
|
76 DRIVER_ERROR, /*!< A system driver error occured. */
|
Chris@1397
|
77 SYSTEM_ERROR, /*!< A system error occured. */
|
Chris@1397
|
78 THREAD_ERROR /*!< A thread error occured. */
|
Chris@1397
|
79 };
|
Chris@1397
|
80
|
Chris@1397
|
81 //! The constructor.
|
Chris@1397
|
82 RtMidiError( const std::string& message, Type type = RtMidiError::UNSPECIFIED ) throw() : message_(message), type_(type) {}
|
Chris@1397
|
83
|
Chris@1397
|
84 //! The destructor.
|
Chris@1397
|
85 virtual ~RtMidiError( void ) throw() {}
|
Chris@1397
|
86
|
Chris@1397
|
87 //! Prints thrown error message to stderr.
|
Chris@1397
|
88 virtual void printMessage( void ) const throw() { std::cerr << '\n' << message_ << "\n\n"; }
|
Chris@1397
|
89
|
Chris@1397
|
90 //! Returns the thrown error message type.
|
Chris@1397
|
91 virtual const Type& getType(void) const throw() { return type_; }
|
Chris@1397
|
92
|
Chris@1397
|
93 //! Returns the thrown error message string.
|
Chris@1397
|
94 virtual const std::string& getMessage(void) const throw() { return message_; }
|
Chris@1397
|
95
|
Chris@1397
|
96 //! Returns the thrown error message as a c-style string.
|
Chris@1397
|
97 virtual const char* what( void ) const throw() { return message_.c_str(); }
|
Chris@1397
|
98
|
Chris@1397
|
99 protected:
|
Chris@1397
|
100 std::string message_;
|
Chris@1397
|
101 Type type_;
|
Chris@1397
|
102 };
|
Chris@1397
|
103
|
Chris@1397
|
104 //! RtMidi error callback function prototype.
|
Chris@1397
|
105 /*!
|
Chris@1397
|
106 \param type Type of error.
|
Chris@1397
|
107 \param errorText Error description.
|
Chris@1397
|
108
|
Chris@1397
|
109 Note that class behaviour is undefined after a critical error (not
|
Chris@1397
|
110 a warning) is reported.
|
Chris@1397
|
111 */
|
Chris@1397
|
112 typedef void (*RtMidiErrorCallback)( RtMidiError::Type type, const std::string &errorText, void *userData );
|
Chris@1397
|
113
|
Chris@1397
|
114 class MidiApi;
|
Chris@559
|
115
|
Chris@559
|
116 class RtMidi
|
Chris@559
|
117 {
|
Chris@559
|
118 public:
|
Chris@559
|
119
|
Chris@1397
|
120 //! MIDI API specifier arguments.
|
Chris@1397
|
121 enum Api {
|
Chris@1397
|
122 UNSPECIFIED, /*!< Search for a working compiled API. */
|
Chris@1397
|
123 MACOSX_CORE, /*!< Macintosh OS-X Core Midi API. */
|
Chris@1397
|
124 LINUX_ALSA, /*!< The Advanced Linux Sound Architecture API. */
|
Chris@1397
|
125 UNIX_JACK, /*!< The JACK Low-Latency MIDI Server API. */
|
Chris@1397
|
126 WINDOWS_MM, /*!< The Microsoft Multimedia MIDI API. */
|
Chris@1397
|
127 RTMIDI_DUMMY /*!< A compilable but non-functional API. */
|
Chris@1397
|
128 };
|
Chris@1397
|
129
|
Chris@1397
|
130 //! A static function to determine the current RtMidi version.
|
Chris@1397
|
131 static std::string getVersion( void ) throw();
|
Chris@1397
|
132
|
Chris@1397
|
133 //! A static function to determine the available compiled MIDI APIs.
|
Chris@1397
|
134 /*!
|
Chris@1397
|
135 The values returned in the std::vector can be compared against
|
Chris@1397
|
136 the enumerated list values. Note that there can be more than one
|
Chris@1397
|
137 API compiled for certain operating systems.
|
Chris@1397
|
138 */
|
Chris@1397
|
139 static void getCompiledApi( std::vector<RtMidi::Api> &apis ) throw();
|
Chris@1397
|
140
|
Chris@559
|
141 //! Pure virtual openPort() function.
|
Chris@565
|
142 virtual void openPort( unsigned int portNumber = 0, const std::string portName = std::string( "RtMidi" ) ) = 0;
|
Chris@559
|
143
|
Chris@559
|
144 //! Pure virtual openVirtualPort() function.
|
Chris@559
|
145 virtual void openVirtualPort( const std::string portName = std::string( "RtMidi" ) ) = 0;
|
Chris@559
|
146
|
Chris@559
|
147 //! Pure virtual getPortCount() function.
|
Chris@559
|
148 virtual unsigned int getPortCount() = 0;
|
Chris@559
|
149
|
Chris@559
|
150 //! Pure virtual getPortName() function.
|
Chris@559
|
151 virtual std::string getPortName( unsigned int portNumber = 0 ) = 0;
|
Chris@559
|
152
|
Chris@559
|
153 //! Pure virtual closePort() function.
|
Chris@559
|
154 virtual void closePort( void ) = 0;
|
Chris@559
|
155
|
Chris@1397
|
156 //! Returns true if a port is open and false if not.
|
Chris@1397
|
157 virtual bool isPortOpen( void ) const = 0;
|
Chris@1397
|
158
|
Chris@1397
|
159 //! Set an error callback function to be invoked when an error has occured.
|
Chris@1397
|
160 /*!
|
Chris@1397
|
161 The callback function will be called whenever an error has occured. It is best
|
Chris@1397
|
162 to set the error callback function before opening a port.
|
Chris@1397
|
163 */
|
Chris@1397
|
164 virtual void setErrorCallback( RtMidiErrorCallback errorCallback = NULL, void *userData = 0 ) = 0;
|
Chris@1397
|
165
|
Chris@559
|
166 protected:
|
Chris@559
|
167
|
Chris@559
|
168 RtMidi();
|
Chris@1397
|
169 virtual ~RtMidi();
|
Chris@559
|
170
|
Chris@1397
|
171 MidiApi *rtapi_;
|
Chris@559
|
172 };
|
Chris@559
|
173
|
Chris@559
|
174 /**********************************************************************/
|
Chris@559
|
175 /*! \class RtMidiIn
|
Chris@559
|
176 \brief A realtime MIDI input class.
|
Chris@559
|
177
|
Chris@559
|
178 This class provides a common, platform-independent API for
|
Chris@559
|
179 realtime MIDI input. It allows access to a single MIDI input
|
Chris@559
|
180 port. Incoming MIDI messages are either saved to a queue for
|
Chris@559
|
181 retrieval using the getMessage() function or immediately passed to
|
Chris@559
|
182 a user-specified callback function. Create multiple instances of
|
Chris@559
|
183 this class to connect to more than one MIDI device at the same
|
Chris@1397
|
184 time. With the OS-X, Linux ALSA, and JACK MIDI APIs, it is also
|
Chris@1397
|
185 possible to open a virtual input port to which other MIDI software
|
Chris@1397
|
186 clients can connect.
|
Chris@559
|
187
|
Chris@1397
|
188 by Gary P. Scavone, 2003-2014.
|
Chris@559
|
189 */
|
Chris@559
|
190 /**********************************************************************/
|
Chris@559
|
191
|
Chris@1397
|
192 // **************************************************************** //
|
Chris@1397
|
193 //
|
Chris@1397
|
194 // RtMidiIn and RtMidiOut class declarations.
|
Chris@1397
|
195 //
|
Chris@1397
|
196 // RtMidiIn / RtMidiOut are "controllers" used to select an available
|
Chris@1397
|
197 // MIDI input or output interface. They present common APIs for the
|
Chris@1397
|
198 // user to call but all functionality is implemented by the classes
|
Chris@1397
|
199 // MidiInApi, MidiOutApi and their subclasses. RtMidiIn and RtMidiOut
|
Chris@1397
|
200 // each create an instance of a MidiInApi or MidiOutApi subclass based
|
Chris@1397
|
201 // on the user's API choice. If no choice is made, they attempt to
|
Chris@1397
|
202 // make a "logical" API selection.
|
Chris@1397
|
203 //
|
Chris@1397
|
204 // **************************************************************** //
|
Chris@559
|
205
|
Chris@559
|
206 class RtMidiIn : public RtMidi
|
Chris@559
|
207 {
|
Chris@559
|
208 public:
|
Chris@559
|
209
|
Chris@559
|
210 //! User callback function type definition.
|
Chris@559
|
211 typedef void (*RtMidiCallback)( double timeStamp, std::vector<unsigned char> *message, void *userData);
|
Chris@559
|
212
|
Chris@1397
|
213 //! Default constructor that allows an optional api, client name and queue size.
|
Chris@559
|
214 /*!
|
Chris@1397
|
215 An exception will be thrown if a MIDI system initialization
|
Chris@1397
|
216 error occurs. The queue size defines the maximum number of
|
Chris@1397
|
217 messages that can be held in the MIDI queue (when not using a
|
Chris@1397
|
218 callback function). If the queue size limit is reached,
|
Chris@1397
|
219 incoming messages will be ignored.
|
Chris@1397
|
220
|
Chris@1397
|
221 If no API argument is specified and multiple API support has been
|
Chris@1397
|
222 compiled, the default order of use is ALSA, JACK (Linux) and CORE,
|
Chris@1397
|
223 JACK (OS-X).
|
Chris@1397
|
224
|
Chris@1397
|
225 \param api An optional API id can be specified.
|
Chris@1397
|
226 \param clientName An optional client name can be specified. This
|
Chris@1397
|
227 will be used to group the ports that are created
|
Chris@1397
|
228 by the application.
|
Chris@1397
|
229 \param queueSizeLimit An optional size of the MIDI input queue can be specified.
|
Chris@559
|
230 */
|
Chris@1397
|
231 RtMidiIn( RtMidi::Api api=UNSPECIFIED,
|
Chris@1397
|
232 const std::string clientName = std::string( "RtMidi Input Client"),
|
Chris@1397
|
233 unsigned int queueSizeLimit = 100 );
|
Chris@559
|
234
|
Chris@559
|
235 //! If a MIDI connection is still open, it will be closed by the destructor.
|
Chris@1397
|
236 ~RtMidiIn ( void ) throw();
|
Chris@559
|
237
|
Chris@1397
|
238 //! Returns the MIDI API specifier for the current instance of RtMidiIn.
|
Chris@1397
|
239 RtMidi::Api getCurrentApi( void ) throw();
|
Chris@1397
|
240
|
Chris@1397
|
241 //! Open a MIDI input connection given by enumeration number.
|
Chris@559
|
242 /*!
|
Chris@1397
|
243 \param portNumber An optional port number greater than 0 can be specified.
|
Chris@1397
|
244 Otherwise, the default or first port found is opened.
|
Chris@1397
|
245 \param portName An optional name for the application port that is used to connect to portId can be specified.
|
Chris@559
|
246 */
|
Chris@1397
|
247 void openPort( unsigned int portNumber = 0, const std::string portName = std::string( "RtMidi Input" ) );
|
Chris@559
|
248
|
Chris@1397
|
249 //! Create a virtual input port, with optional name, to allow software connections (OS X, JACK and ALSA only).
|
Chris@559
|
250 /*!
|
Chris@1397
|
251 This function creates a virtual MIDI input port to which other
|
Chris@1397
|
252 software applications can connect. This type of functionality
|
Chris@1397
|
253 is currently only supported by the Macintosh OS-X, any JACK,
|
Chris@1397
|
254 and Linux ALSA APIs (the function returns an error for the other APIs).
|
Chris@1397
|
255
|
Chris@1397
|
256 \param portName An optional name for the application port that is
|
Chris@1397
|
257 used to connect to portId can be specified.
|
Chris@559
|
258 */
|
Chris@559
|
259 void openVirtualPort( const std::string portName = std::string( "RtMidi Input" ) );
|
Chris@559
|
260
|
Chris@559
|
261 //! Set a callback function to be invoked for incoming MIDI messages.
|
Chris@559
|
262 /*!
|
Chris@1397
|
263 The callback function will be called whenever an incoming MIDI
|
Chris@1397
|
264 message is received. While not absolutely necessary, it is best
|
Chris@1397
|
265 to set the callback function before opening a MIDI port to avoid
|
Chris@1397
|
266 leaving some messages in the queue.
|
Chris@1397
|
267
|
Chris@1397
|
268 \param callback A callback function must be given.
|
Chris@1397
|
269 \param userData Optionally, a pointer to additional data can be
|
Chris@1397
|
270 passed to the callback function whenever it is called.
|
Chris@559
|
271 */
|
Chris@559
|
272 void setCallback( RtMidiCallback callback, void *userData = 0 );
|
Chris@559
|
273
|
Chris@559
|
274 //! Cancel use of the current callback function (if one exists).
|
Chris@559
|
275 /*!
|
Chris@1397
|
276 Subsequent incoming MIDI messages will be written to the queue
|
Chris@1397
|
277 and can be retrieved with the \e getMessage function.
|
Chris@559
|
278 */
|
Chris@559
|
279 void cancelCallback();
|
Chris@559
|
280
|
Chris@559
|
281 //! Close an open MIDI connection (if one exists).
|
Chris@559
|
282 void closePort( void );
|
Chris@559
|
283
|
Chris@1397
|
284 //! Returns true if a port is open and false if not.
|
Chris@1397
|
285 virtual bool isPortOpen() const;
|
Chris@1397
|
286
|
Chris@559
|
287 //! Return the number of available MIDI input ports.
|
Chris@1397
|
288 /*!
|
Chris@1397
|
289 \return This function returns the number of MIDI ports of the selected API.
|
Chris@1397
|
290 */
|
Chris@559
|
291 unsigned int getPortCount();
|
Chris@559
|
292
|
Chris@559
|
293 //! Return a string identifier for the specified MIDI input port number.
|
Chris@559
|
294 /*!
|
Chris@1397
|
295 \return The name of the port with the given Id is returned.
|
Chris@1397
|
296 \retval An empty string is returned if an invalid port specifier is provided.
|
Chris@559
|
297 */
|
Chris@559
|
298 std::string getPortName( unsigned int portNumber = 0 );
|
Chris@559
|
299
|
Chris@559
|
300 //! Specify whether certain MIDI message types should be queued or ignored during input.
|
Chris@559
|
301 /*!
|
Chris@1397
|
302 By default, MIDI timing and active sensing messages are ignored
|
Chris@1397
|
303 during message input because of their relative high data rates.
|
Chris@1397
|
304 MIDI sysex messages are ignored by default as well. Variable
|
Chris@1397
|
305 values of "true" imply that the respective message type will be
|
Chris@1397
|
306 ignored.
|
Chris@559
|
307 */
|
Chris@559
|
308 void ignoreTypes( bool midiSysex = true, bool midiTime = true, bool midiSense = true );
|
Chris@559
|
309
|
Chris@559
|
310 //! Fill the user-provided vector with the data bytes for the next available MIDI message in the input queue and return the event delta-time in seconds.
|
Chris@559
|
311 /*!
|
Chris@1397
|
312 This function returns immediately whether a new message is
|
Chris@1397
|
313 available or not. A valid message is indicated by a non-zero
|
Chris@1397
|
314 vector size. An exception is thrown if an error occurs during
|
Chris@1397
|
315 message retrieval or an input connection was not previously
|
Chris@1397
|
316 established.
|
Chris@559
|
317 */
|
Chris@559
|
318 double getMessage( std::vector<unsigned char> *message );
|
Chris@559
|
319
|
Chris@1397
|
320 //! Set an error callback function to be invoked when an error has occured.
|
Chris@1397
|
321 /*!
|
Chris@1397
|
322 The callback function will be called whenever an error has occured. It is best
|
Chris@1397
|
323 to set the error callback function before opening a port.
|
Chris@1397
|
324 */
|
Chris@1397
|
325 virtual void setErrorCallback( RtMidiErrorCallback errorCallback = NULL, void *userData = 0 );
|
Chris@559
|
326
|
Chris@1397
|
327 protected:
|
Chris@1397
|
328 void openMidiApi( RtMidi::Api api, const std::string clientName, unsigned int queueSizeLimit );
|
Chris@559
|
329
|
Chris@559
|
330 };
|
Chris@559
|
331
|
Chris@559
|
332 /**********************************************************************/
|
Chris@559
|
333 /*! \class RtMidiOut
|
Chris@559
|
334 \brief A realtime MIDI output class.
|
Chris@559
|
335
|
Chris@559
|
336 This class provides a common, platform-independent API for MIDI
|
Chris@559
|
337 output. It allows one to probe available MIDI output ports, to
|
Chris@559
|
338 connect to one such port, and to send MIDI bytes immediately over
|
Chris@559
|
339 the connection. Create multiple instances of this class to
|
Chris@1397
|
340 connect to more than one MIDI device at the same time. With the
|
Chris@1397
|
341 OS-X, Linux ALSA and JACK MIDI APIs, it is also possible to open a
|
Chris@1397
|
342 virtual port to which other MIDI software clients can connect.
|
Chris@559
|
343
|
Chris@1397
|
344 by Gary P. Scavone, 2003-2014.
|
Chris@559
|
345 */
|
Chris@559
|
346 /**********************************************************************/
|
Chris@559
|
347
|
Chris@559
|
348 class RtMidiOut : public RtMidi
|
Chris@559
|
349 {
|
Chris@559
|
350 public:
|
Chris@559
|
351
|
Chris@565
|
352 //! Default constructor that allows an optional client name.
|
Chris@559
|
353 /*!
|
Chris@1397
|
354 An exception will be thrown if a MIDI system initialization error occurs.
|
Chris@1397
|
355
|
Chris@1397
|
356 If no API argument is specified and multiple API support has been
|
Chris@1397
|
357 compiled, the default order of use is ALSA, JACK (Linux) and CORE,
|
Chris@1397
|
358 JACK (OS-X).
|
Chris@559
|
359 */
|
Chris@1397
|
360 RtMidiOut( RtMidi::Api api=UNSPECIFIED,
|
Chris@1397
|
361 const std::string clientName = std::string( "RtMidi Output Client") );
|
Chris@559
|
362
|
Chris@559
|
363 //! The destructor closes any open MIDI connections.
|
Chris@1397
|
364 ~RtMidiOut( void ) throw();
|
Chris@1397
|
365
|
Chris@1397
|
366 //! Returns the MIDI API specifier for the current instance of RtMidiOut.
|
Chris@1397
|
367 RtMidi::Api getCurrentApi( void ) throw();
|
Chris@559
|
368
|
Chris@559
|
369 //! Open a MIDI output connection.
|
Chris@559
|
370 /*!
|
Chris@559
|
371 An optional port number greater than 0 can be specified.
|
Chris@559
|
372 Otherwise, the default or first port found is opened. An
|
Chris@559
|
373 exception is thrown if an error occurs while attempting to make
|
Chris@559
|
374 the port connection.
|
Chris@559
|
375 */
|
Chris@565
|
376 void openPort( unsigned int portNumber = 0, const std::string portName = std::string( "RtMidi Output" ) );
|
Chris@559
|
377
|
Chris@559
|
378 //! Close an open MIDI connection (if one exists).
|
Chris@1397
|
379 void closePort( void );
|
Chris@559
|
380
|
Chris@1397
|
381 //! Returns true if a port is open and false if not.
|
Chris@1397
|
382 virtual bool isPortOpen() const;
|
Chris@1397
|
383
|
Chris@1397
|
384 //! Create a virtual output port, with optional name, to allow software connections (OS X, JACK and ALSA only).
|
Chris@559
|
385 /*!
|
Chris@559
|
386 This function creates a virtual MIDI output port to which other
|
Chris@559
|
387 software applications can connect. This type of functionality
|
Chris@1397
|
388 is currently only supported by the Macintosh OS-X, Linux ALSA
|
Chris@1397
|
389 and JACK APIs (the function does nothing with the other APIs).
|
Chris@1397
|
390 An exception is thrown if an error occurs while attempting to
|
Chris@1397
|
391 create the virtual port.
|
Chris@559
|
392 */
|
Chris@559
|
393 void openVirtualPort( const std::string portName = std::string( "RtMidi Output" ) );
|
Chris@559
|
394
|
Chris@559
|
395 //! Return the number of available MIDI output ports.
|
Chris@1397
|
396 unsigned int getPortCount( void );
|
Chris@559
|
397
|
Chris@559
|
398 //! Return a string identifier for the specified MIDI port type and number.
|
Chris@559
|
399 /*!
|
Chris@1397
|
400 An empty string is returned if an invalid port specifier is provided.
|
Chris@559
|
401 */
|
Chris@559
|
402 std::string getPortName( unsigned int portNumber = 0 );
|
Chris@559
|
403
|
Chris@559
|
404 //! Immediately send a single message out an open MIDI output port.
|
Chris@559
|
405 /*!
|
Chris@559
|
406 An exception is thrown if an error occurs during output or an
|
Chris@559
|
407 output connection was not previously established.
|
Chris@559
|
408 */
|
Chris@559
|
409 void sendMessage( std::vector<unsigned char> *message );
|
Chris@559
|
410
|
Chris@1397
|
411 //! Set an error callback function to be invoked when an error has occured.
|
Chris@1397
|
412 /*!
|
Chris@1397
|
413 The callback function will be called whenever an error has occured. It is best
|
Chris@1397
|
414 to set the error callback function before opening a port.
|
Chris@1397
|
415 */
|
Chris@1397
|
416 virtual void setErrorCallback( RtMidiErrorCallback errorCallback = NULL, void *userData = 0 );
|
Chris@559
|
417
|
Chris@1397
|
418 protected:
|
Chris@1397
|
419 void openMidiApi( RtMidi::Api api, const std::string clientName );
|
Chris@1397
|
420 };
|
Chris@1397
|
421
|
Chris@1397
|
422
|
Chris@1397
|
423 // **************************************************************** //
|
Chris@1397
|
424 //
|
Chris@1397
|
425 // MidiInApi / MidiOutApi class declarations.
|
Chris@1397
|
426 //
|
Chris@1397
|
427 // Subclasses of MidiInApi and MidiOutApi contain all API- and
|
Chris@1397
|
428 // OS-specific code necessary to fully implement the RtMidi API.
|
Chris@1397
|
429 //
|
Chris@1397
|
430 // Note that MidiInApi and MidiOutApi are abstract base classes and
|
Chris@1397
|
431 // cannot be explicitly instantiated. RtMidiIn and RtMidiOut will
|
Chris@1397
|
432 // create instances of a MidiInApi or MidiOutApi subclass.
|
Chris@1397
|
433 //
|
Chris@1397
|
434 // **************************************************************** //
|
Chris@1397
|
435
|
Chris@1397
|
436 class MidiApi
|
Chris@1397
|
437 {
|
Chris@1397
|
438 public:
|
Chris@1397
|
439
|
Chris@1397
|
440 MidiApi();
|
Chris@1397
|
441 virtual ~MidiApi();
|
Chris@1397
|
442 virtual RtMidi::Api getCurrentApi( void ) = 0;
|
Chris@1397
|
443 virtual void openPort( unsigned int portNumber, const std::string portName ) = 0;
|
Chris@1397
|
444 virtual void openVirtualPort( const std::string portName ) = 0;
|
Chris@1397
|
445 virtual void closePort( void ) = 0;
|
Chris@1397
|
446
|
Chris@1397
|
447 virtual unsigned int getPortCount( void ) = 0;
|
Chris@1397
|
448 virtual std::string getPortName( unsigned int portNumber ) = 0;
|
Chris@1397
|
449
|
Chris@1397
|
450 inline bool isPortOpen() const { return connected_; }
|
Chris@1397
|
451 void setErrorCallback( RtMidiErrorCallback errorCallback, void *userData );
|
Chris@1397
|
452
|
Chris@1397
|
453 //! A basic error reporting function for RtMidi classes.
|
Chris@1397
|
454 void error( RtMidiError::Type type, std::string errorString );
|
Chris@1397
|
455
|
Chris@1397
|
456 protected:
|
Chris@1397
|
457 virtual void initialize( const std::string& clientName ) = 0;
|
Chris@1397
|
458
|
Chris@1397
|
459 void *apiData_;
|
Chris@1397
|
460 bool connected_;
|
Chris@1397
|
461 std::string errorString_;
|
Chris@1397
|
462 RtMidiErrorCallback errorCallback_;
|
Chris@1397
|
463 bool firstErrorOccurred_;
|
Chris@1397
|
464 void *errorCallbackUserData_;
|
Chris@1397
|
465 };
|
Chris@1397
|
466
|
Chris@1397
|
467 class MidiInApi : public MidiApi
|
Chris@1397
|
468 {
|
Chris@1397
|
469 public:
|
Chris@1397
|
470
|
Chris@1397
|
471 MidiInApi( unsigned int queueSizeLimit );
|
Chris@1397
|
472 virtual ~MidiInApi( void );
|
Chris@1397
|
473 void setCallback( RtMidiIn::RtMidiCallback callback, void *userData );
|
Chris@1397
|
474 void cancelCallback( void );
|
Chris@1397
|
475 virtual void ignoreTypes( bool midiSysex, bool midiTime, bool midiSense );
|
Chris@1397
|
476 double getMessage( std::vector<unsigned char> *message );
|
Chris@1397
|
477
|
Chris@1397
|
478 // A MIDI structure used internally by the class to store incoming
|
Chris@1397
|
479 // messages. Each message represents one and only one MIDI message.
|
Chris@1397
|
480 struct MidiMessage {
|
Chris@1397
|
481 std::vector<unsigned char> bytes;
|
Chris@1397
|
482 double timeStamp;
|
Chris@1397
|
483
|
Chris@1397
|
484 // Default constructor.
|
Chris@1397
|
485 MidiMessage()
|
Chris@1397
|
486 :bytes(0), timeStamp(0.0) {}
|
Chris@1397
|
487 };
|
Chris@1397
|
488
|
Chris@1397
|
489 struct MidiQueue {
|
Chris@1397
|
490 unsigned int front;
|
Chris@1397
|
491 unsigned int back;
|
Chris@1397
|
492 unsigned int size;
|
Chris@1397
|
493 unsigned int ringSize;
|
Chris@1397
|
494 MidiMessage *ring;
|
Chris@1397
|
495
|
Chris@1397
|
496 // Default constructor.
|
Chris@1397
|
497 MidiQueue()
|
Chris@1397
|
498 :front(0), back(0), size(0), ringSize(0) {}
|
Chris@1397
|
499 };
|
Chris@1397
|
500
|
Chris@1397
|
501 // The RtMidiInData structure is used to pass private class data to
|
Chris@1397
|
502 // the MIDI input handling function or thread.
|
Chris@1397
|
503 struct RtMidiInData {
|
Chris@1397
|
504 MidiQueue queue;
|
Chris@1397
|
505 MidiMessage message;
|
Chris@1397
|
506 unsigned char ignoreFlags;
|
Chris@1397
|
507 bool doInput;
|
Chris@1397
|
508 bool firstMessage;
|
Chris@1397
|
509 void *apiData;
|
Chris@1397
|
510 bool usingCallback;
|
Chris@1397
|
511 RtMidiIn::RtMidiCallback userCallback;
|
Chris@1397
|
512 void *userData;
|
Chris@1397
|
513 bool continueSysex;
|
Chris@1397
|
514
|
Chris@1397
|
515 // Default constructor.
|
Chris@1397
|
516 RtMidiInData()
|
Chris@1397
|
517 : ignoreFlags(7), doInput(false), firstMessage(true),
|
Chris@1397
|
518 apiData(0), usingCallback(false), userCallback(0), userData(0),
|
Chris@1397
|
519 continueSysex(false) {}
|
Chris@1397
|
520 };
|
Chris@1397
|
521
|
Chris@1397
|
522 protected:
|
Chris@1397
|
523 RtMidiInData inputData_;
|
Chris@1397
|
524 };
|
Chris@1397
|
525
|
Chris@1397
|
526 class MidiOutApi : public MidiApi
|
Chris@1397
|
527 {
|
Chris@1397
|
528 public:
|
Chris@1397
|
529
|
Chris@1397
|
530 MidiOutApi( void );
|
Chris@1397
|
531 virtual ~MidiOutApi( void );
|
Chris@1397
|
532 virtual void sendMessage( std::vector<unsigned char> *message ) = 0;
|
Chris@1397
|
533 };
|
Chris@1397
|
534
|
Chris@1397
|
535 // **************************************************************** //
|
Chris@1397
|
536 //
|
Chris@1397
|
537 // Inline RtMidiIn and RtMidiOut definitions.
|
Chris@1397
|
538 //
|
Chris@1397
|
539 // **************************************************************** //
|
Chris@1397
|
540
|
Chris@1397
|
541 inline RtMidi::Api RtMidiIn :: getCurrentApi( void ) throw() { return rtapi_->getCurrentApi(); }
|
Chris@1397
|
542 inline void RtMidiIn :: openPort( unsigned int portNumber, const std::string portName ) { rtapi_->openPort( portNumber, portName ); }
|
Chris@1397
|
543 inline void RtMidiIn :: openVirtualPort( const std::string portName ) { rtapi_->openVirtualPort( portName ); }
|
Chris@1397
|
544 inline void RtMidiIn :: closePort( void ) { rtapi_->closePort(); }
|
Chris@1397
|
545 inline bool RtMidiIn :: isPortOpen() const { return rtapi_->isPortOpen(); }
|
Chris@1397
|
546 inline void RtMidiIn :: setCallback( RtMidiCallback callback, void *userData ) { ((MidiInApi *)rtapi_)->setCallback( callback, userData ); }
|
Chris@1397
|
547 inline void RtMidiIn :: cancelCallback( void ) { ((MidiInApi *)rtapi_)->cancelCallback(); }
|
Chris@1397
|
548 inline unsigned int RtMidiIn :: getPortCount( void ) { return rtapi_->getPortCount(); }
|
Chris@1397
|
549 inline std::string RtMidiIn :: getPortName( unsigned int portNumber ) { return rtapi_->getPortName( portNumber ); }
|
Chris@1397
|
550 inline void RtMidiIn :: ignoreTypes( bool midiSysex, bool midiTime, bool midiSense ) { ((MidiInApi *)rtapi_)->ignoreTypes( midiSysex, midiTime, midiSense ); }
|
Chris@1397
|
551 inline double RtMidiIn :: getMessage( std::vector<unsigned char> *message ) { return ((MidiInApi *)rtapi_)->getMessage( message ); }
|
Chris@1397
|
552 inline void RtMidiIn :: setErrorCallback( RtMidiErrorCallback errorCallback, void *userData ) { rtapi_->setErrorCallback(errorCallback, userData); }
|
Chris@1397
|
553
|
Chris@1397
|
554 inline RtMidi::Api RtMidiOut :: getCurrentApi( void ) throw() { return rtapi_->getCurrentApi(); }
|
Chris@1397
|
555 inline void RtMidiOut :: openPort( unsigned int portNumber, const std::string portName ) { rtapi_->openPort( portNumber, portName ); }
|
Chris@1397
|
556 inline void RtMidiOut :: openVirtualPort( const std::string portName ) { rtapi_->openVirtualPort( portName ); }
|
Chris@1397
|
557 inline void RtMidiOut :: closePort( void ) { rtapi_->closePort(); }
|
Chris@1397
|
558 inline bool RtMidiOut :: isPortOpen() const { return rtapi_->isPortOpen(); }
|
Chris@1397
|
559 inline unsigned int RtMidiOut :: getPortCount( void ) { return rtapi_->getPortCount(); }
|
Chris@1397
|
560 inline std::string RtMidiOut :: getPortName( unsigned int portNumber ) { return rtapi_->getPortName( portNumber ); }
|
Chris@1397
|
561 inline void RtMidiOut :: sendMessage( std::vector<unsigned char> *message ) { ((MidiOutApi *)rtapi_)->sendMessage( message ); }
|
Chris@1397
|
562 inline void RtMidiOut :: setErrorCallback( RtMidiErrorCallback errorCallback, void *userData ) { rtapi_->setErrorCallback(errorCallback, userData); }
|
Chris@1397
|
563
|
Chris@1397
|
564 // **************************************************************** //
|
Chris@1397
|
565 //
|
Chris@1397
|
566 // MidiInApi and MidiOutApi subclass prototypes.
|
Chris@1397
|
567 //
|
Chris@1397
|
568 // **************************************************************** //
|
Chris@1397
|
569
|
Chris@1397
|
570 #if !defined(__LINUX_ALSA__) && !defined(__UNIX_JACK__) && !defined(__MACOSX_CORE__) && !defined(__WINDOWS_MM__)
|
Chris@1397
|
571 #define __RTMIDI_DUMMY__
|
Chris@1397
|
572 #endif
|
Chris@1397
|
573
|
Chris@1397
|
574 #if defined(__MACOSX_CORE__)
|
Chris@1397
|
575
|
Chris@1397
|
576 class MidiInCore: public MidiInApi
|
Chris@1397
|
577 {
|
Chris@1397
|
578 public:
|
Chris@1397
|
579 MidiInCore( const std::string clientName, unsigned int queueSizeLimit );
|
Chris@1397
|
580 ~MidiInCore( void );
|
Chris@1397
|
581 RtMidi::Api getCurrentApi( void ) { return RtMidi::MACOSX_CORE; };
|
Chris@1397
|
582 void openPort( unsigned int portNumber, const std::string portName );
|
Chris@1397
|
583 void openVirtualPort( const std::string portName );
|
Chris@1397
|
584 void closePort( void );
|
Chris@1397
|
585 unsigned int getPortCount( void );
|
Chris@1397
|
586 std::string getPortName( unsigned int portNumber );
|
Chris@1397
|
587
|
Chris@1397
|
588 protected:
|
Chris@1397
|
589 void initialize( const std::string& clientName );
|
Chris@1397
|
590 };
|
Chris@1397
|
591
|
Chris@1397
|
592 class MidiOutCore: public MidiOutApi
|
Chris@1397
|
593 {
|
Chris@1397
|
594 public:
|
Chris@1397
|
595 MidiOutCore( const std::string clientName );
|
Chris@1397
|
596 ~MidiOutCore( void );
|
Chris@1397
|
597 RtMidi::Api getCurrentApi( void ) { return RtMidi::MACOSX_CORE; };
|
Chris@1397
|
598 void openPort( unsigned int portNumber, const std::string portName );
|
Chris@1397
|
599 void openVirtualPort( const std::string portName );
|
Chris@1397
|
600 void closePort( void );
|
Chris@1397
|
601 unsigned int getPortCount( void );
|
Chris@1397
|
602 std::string getPortName( unsigned int portNumber );
|
Chris@1397
|
603 void sendMessage( std::vector<unsigned char> *message );
|
Chris@1397
|
604
|
Chris@1397
|
605 protected:
|
Chris@565
|
606 void initialize( const std::string& clientName );
|
Chris@559
|
607 };
|
Chris@559
|
608
|
Chris@559
|
609 #endif
|
Chris@1397
|
610
|
Chris@1397
|
611 #if defined(__UNIX_JACK__)
|
Chris@1397
|
612
|
Chris@1397
|
613 class MidiInJack: public MidiInApi
|
Chris@1397
|
614 {
|
Chris@1397
|
615 public:
|
Chris@1397
|
616 MidiInJack( const std::string clientName, unsigned int queueSizeLimit );
|
Chris@1397
|
617 ~MidiInJack( void );
|
Chris@1397
|
618 RtMidi::Api getCurrentApi( void ) { return RtMidi::UNIX_JACK; };
|
Chris@1397
|
619 void openPort( unsigned int portNumber, const std::string portName );
|
Chris@1397
|
620 void openVirtualPort( const std::string portName );
|
Chris@1397
|
621 void closePort( void );
|
Chris@1397
|
622 unsigned int getPortCount( void );
|
Chris@1397
|
623 std::string getPortName( unsigned int portNumber );
|
Chris@1397
|
624
|
Chris@1397
|
625 protected:
|
Chris@1397
|
626 std::string clientName;
|
Chris@1397
|
627
|
Chris@1397
|
628 void connect( void );
|
Chris@1397
|
629 void initialize( const std::string& clientName );
|
Chris@1397
|
630 };
|
Chris@1397
|
631
|
Chris@1397
|
632 class MidiOutJack: public MidiOutApi
|
Chris@1397
|
633 {
|
Chris@1397
|
634 public:
|
Chris@1397
|
635 MidiOutJack( const std::string clientName );
|
Chris@1397
|
636 ~MidiOutJack( void );
|
Chris@1397
|
637 RtMidi::Api getCurrentApi( void ) { return RtMidi::UNIX_JACK; };
|
Chris@1397
|
638 void openPort( unsigned int portNumber, const std::string portName );
|
Chris@1397
|
639 void openVirtualPort( const std::string portName );
|
Chris@1397
|
640 void closePort( void );
|
Chris@1397
|
641 unsigned int getPortCount( void );
|
Chris@1397
|
642 std::string getPortName( unsigned int portNumber );
|
Chris@1397
|
643 void sendMessage( std::vector<unsigned char> *message );
|
Chris@1397
|
644
|
Chris@1397
|
645 protected:
|
Chris@1397
|
646 std::string clientName;
|
Chris@1397
|
647
|
Chris@1397
|
648 void connect( void );
|
Chris@1397
|
649 void initialize( const std::string& clientName );
|
Chris@1397
|
650 };
|
Chris@1397
|
651
|
Chris@1397
|
652 #endif
|
Chris@1397
|
653
|
Chris@1397
|
654 #if defined(__LINUX_ALSA__)
|
Chris@1397
|
655
|
Chris@1397
|
656 class MidiInAlsa: public MidiInApi
|
Chris@1397
|
657 {
|
Chris@1397
|
658 public:
|
Chris@1397
|
659 MidiInAlsa( const std::string clientName, unsigned int queueSizeLimit );
|
Chris@1397
|
660 ~MidiInAlsa( void );
|
Chris@1397
|
661 RtMidi::Api getCurrentApi( void ) { return RtMidi::LINUX_ALSA; };
|
Chris@1397
|
662 void openPort( unsigned int portNumber, const std::string portName );
|
Chris@1397
|
663 void openVirtualPort( const std::string portName );
|
Chris@1397
|
664 void closePort( void );
|
Chris@1397
|
665 unsigned int getPortCount( void );
|
Chris@1397
|
666 std::string getPortName( unsigned int portNumber );
|
Chris@1397
|
667
|
Chris@1397
|
668 protected:
|
Chris@1397
|
669 void initialize( const std::string& clientName );
|
Chris@1397
|
670 };
|
Chris@1397
|
671
|
Chris@1397
|
672 class MidiOutAlsa: public MidiOutApi
|
Chris@1397
|
673 {
|
Chris@1397
|
674 public:
|
Chris@1397
|
675 MidiOutAlsa( const std::string clientName );
|
Chris@1397
|
676 ~MidiOutAlsa( void );
|
Chris@1397
|
677 RtMidi::Api getCurrentApi( void ) { return RtMidi::LINUX_ALSA; };
|
Chris@1397
|
678 void openPort( unsigned int portNumber, const std::string portName );
|
Chris@1397
|
679 void openVirtualPort( const std::string portName );
|
Chris@1397
|
680 void closePort( void );
|
Chris@1397
|
681 unsigned int getPortCount( void );
|
Chris@1397
|
682 std::string getPortName( unsigned int portNumber );
|
Chris@1397
|
683 void sendMessage( std::vector<unsigned char> *message );
|
Chris@1397
|
684
|
Chris@1397
|
685 protected:
|
Chris@1397
|
686 void initialize( const std::string& clientName );
|
Chris@1397
|
687 };
|
Chris@1397
|
688
|
Chris@1397
|
689 #endif
|
Chris@1397
|
690
|
Chris@1397
|
691 #if defined(__WINDOWS_MM__)
|
Chris@1397
|
692
|
Chris@1397
|
693 class MidiInWinMM: public MidiInApi
|
Chris@1397
|
694 {
|
Chris@1397
|
695 public:
|
Chris@1397
|
696 MidiInWinMM( const std::string clientName, unsigned int queueSizeLimit );
|
Chris@1397
|
697 ~MidiInWinMM( void );
|
Chris@1397
|
698 RtMidi::Api getCurrentApi( void ) { return RtMidi::WINDOWS_MM; };
|
Chris@1397
|
699 void openPort( unsigned int portNumber, const std::string portName );
|
Chris@1397
|
700 void openVirtualPort( const std::string portName );
|
Chris@1397
|
701 void closePort( void );
|
Chris@1397
|
702 unsigned int getPortCount( void );
|
Chris@1397
|
703 std::string getPortName( unsigned int portNumber );
|
Chris@1397
|
704
|
Chris@1397
|
705 protected:
|
Chris@1397
|
706 void initialize( const std::string& clientName );
|
Chris@1397
|
707 };
|
Chris@1397
|
708
|
Chris@1397
|
709 class MidiOutWinMM: public MidiOutApi
|
Chris@1397
|
710 {
|
Chris@1397
|
711 public:
|
Chris@1397
|
712 MidiOutWinMM( const std::string clientName );
|
Chris@1397
|
713 ~MidiOutWinMM( void );
|
Chris@1397
|
714 RtMidi::Api getCurrentApi( void ) { return RtMidi::WINDOWS_MM; };
|
Chris@1397
|
715 void openPort( unsigned int portNumber, const std::string portName );
|
Chris@1397
|
716 void openVirtualPort( const std::string portName );
|
Chris@1397
|
717 void closePort( void );
|
Chris@1397
|
718 unsigned int getPortCount( void );
|
Chris@1397
|
719 std::string getPortName( unsigned int portNumber );
|
Chris@1397
|
720 void sendMessage( std::vector<unsigned char> *message );
|
Chris@1397
|
721
|
Chris@1397
|
722 protected:
|
Chris@1397
|
723 void initialize( const std::string& clientName );
|
Chris@1397
|
724 };
|
Chris@1397
|
725
|
Chris@1397
|
726 #endif
|
Chris@1397
|
727
|
Chris@1397
|
728 #if defined(__RTMIDI_DUMMY__)
|
Chris@1397
|
729
|
Chris@1397
|
730 class MidiInDummy: public MidiInApi
|
Chris@1397
|
731 {
|
Chris@1397
|
732 public:
|
Chris@1397
|
733 MidiInDummy( const std::string /*clientName*/, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit ) { errorString_ = "MidiInDummy: This class provides no functionality."; error( RtMidiError::WARNING, errorString_ ); }
|
Chris@1397
|
734 RtMidi::Api getCurrentApi( void ) { return RtMidi::RTMIDI_DUMMY; }
|
Chris@1397
|
735 void openPort( unsigned int /*portNumber*/, const std::string /*portName*/ ) {}
|
Chris@1397
|
736 void openVirtualPort( const std::string /*portName*/ ) {}
|
Chris@1397
|
737 void closePort( void ) {}
|
Chris@1397
|
738 unsigned int getPortCount( void ) { return 0; }
|
Chris@1399
|
739 std::string getPortName( unsigned int /*portNumber*/ ) { return ""; }
|
Chris@1397
|
740
|
Chris@1397
|
741 protected:
|
Chris@1397
|
742 void initialize( const std::string& /*clientName*/ ) {}
|
Chris@1397
|
743 };
|
Chris@1397
|
744
|
Chris@1397
|
745 class MidiOutDummy: public MidiOutApi
|
Chris@1397
|
746 {
|
Chris@1397
|
747 public:
|
Chris@1397
|
748 MidiOutDummy( const std::string /*clientName*/ ) { errorString_ = "MidiOutDummy: This class provides no functionality."; error( RtMidiError::WARNING, errorString_ ); }
|
Chris@1397
|
749 RtMidi::Api getCurrentApi( void ) { return RtMidi::RTMIDI_DUMMY; }
|
Chris@1397
|
750 void openPort( unsigned int /*portNumber*/, const std::string /*portName*/ ) {}
|
Chris@1397
|
751 void openVirtualPort( const std::string /*portName*/ ) {}
|
Chris@1397
|
752 void closePort( void ) {}
|
Chris@1397
|
753 unsigned int getPortCount( void ) { return 0; }
|
Chris@1397
|
754 std::string getPortName( unsigned int /*portNumber*/ ) { return ""; }
|
Chris@1397
|
755 void sendMessage( std::vector<unsigned char> * /*message*/ ) {}
|
Chris@1397
|
756
|
Chris@1397
|
757 protected:
|
Chris@1397
|
758 void initialize( const std::string& /*clientName*/ ) {}
|
Chris@1397
|
759 };
|
Chris@1397
|
760
|
Chris@1397
|
761 #endif
|
Chris@1397
|
762
|
Chris@1397
|
763 #endif
|