|
|
The MidiScheduler provides the interface to the MIDI system (be it a software or hardware driver). On top of this it implements a timed stream for reception and transmission of MidiEvents.
This is the TSE3 platform independant interface to the underlying MIDI hardware. (In fact, it might not even be hardware, this could be the interface to a software synth).
To create a MidiScheduler you will need to use an object derived from this class. The MidiSchedulerFactory classes help you to create this.
The interface provides a number of MIDI "ports" (which may be added to or removed at run time, depending on the type of system TSE3 is running on).
These ports are referenced by a unique number. You read or write data from/to ports via the MidiEvent data type. The MidiEvent has a "port" number field which identifies which port the command is destined for.
As well as having numbers, the API allows you read a port name. You can also set a "preferred" name for each port. This allows the user to give a more useful name to the port (perhaps for use in a GUI).
Port numbers are not necessarily contiguous and can have any value - this will depend on the phyiscal MIDI device in use. In order to find out what port numbers are in use use the following APIs:
Note that there are two reserved special port numbers:
As well as managing the MIDI ports, the MidiScheduler looks after a clock that it used to schedule delivery of these events. The API allows you to start/stop/move the time line, as well as set the clock's tempo.
The MidiScheduler can notify back to its clients if the timestream has moved, or been started/stopped by a device on the MIDI connection.
If the timestream is 'stopped' the MidiScheduler class will still remember a 'current' clock value, informally known as the 'resting clock'. When you ask for the time (see MidiScheduler::clock) you will be given this 'resting clock'.
Additionally, the MidiScheduler provides a MIDI remote control facility, where certain MIDI notes can trigger transport start/stop.
The keys that are set to be "remote controls" will not be passed up through the API in a MidiEvent if remote control is enabled.
If you switch this facility on, you have to bear in mind that the only time the MidiScheduler gets a chance to investigate the input note stream is as you consume events from it. (For simplicity it doesn't have a clever background thread implementation).
Therefore, read events from the MidiScheduler in a timely manner.
The MidiScheduler class does not incorporate a lot of other fancy utilities, like channel/port remapping, for example. Higher level TSE3 components do this, for example the MidiMapper class. The Transport class ties this all together.
If you are porting TSE3 to another platform, you will need to implement this MidiScheduler interface. It uses the template method pattern (GoF book) - you have to implement a few member functions and the base MidiScheduler class looks after most of the logic.
These template methods are in the protected section near the end and are all pretty easy to understand.
See also: MidiEvent
MidiScheduler ()
| MidiScheduler |
~MidiScheduler ()
| ~MidiScheduler |
[virtual]
If the MidiScheduler is running, it will be stopped.
const char * implementationName ()
| implementationName |
[const]
Returns a string describing the particular implementation of the MidiScheduler interface.
Returns: Implementation name string
size_t numPorts ()
| numPorts |
[const]
Returns the number of port addressable by this MidiScheduler. If this changes at any point the MidiSchedulerListener::MidiScheduler_Ports event is raised.
When a port is created it is assigned a "number" - this could be any positive integer. This is its permanent reference ID. It is the port number that is specified in the MidiCommand or MidiEvent.
Returns: Number of MIDI ports this MidiScheduler provides
bool validPort (int port)
| validPort |
[const]
Returns whether the specified port number is valid.
Parameters:
port | MIDI port number |
Returns: true if there is currently a port with that number
int portNumber (size_t index)
| portNumber |
[const]
Returns the "index"th port, if they were held in a list. So, to get the first port number set index to 0, for the seonc give index 1, and so on.
If you specify a value of numPorts or above, the result is undefined.
Parameters:
index | value |
Returns: Port number with specified index
See also: numPorts, portNumbers, numberToIndex
size_t numberToIndex (int number)
| numberToIndex |
[const]
Converts a port number to an index. This is the reverse operation of portNumber.
If the port number is not valid, this will return zero.
Parameters:
number | Port number |
Returns: "Index" of this port
See also: portNumber
void portNumbers (std::vector | portNumbers |
[const]
Empties the contents of the supplied std::vector, and inserts into it every available port number.
Parameters:
numbers | std::vector which is given the current MIDI port numbers |
const char * portName (int port)
| portName |
[const]
Returns a string describing the port with the given number.
If the port number is invalid the port name will be a suitable "invalid" string, not a zero pointer.
Parameters:
port | MIDI port number |
Returns: Port name string
const char * portType (int port)
| portType |
[const]
Returns a string describing the type of the port with the given number.
If the port number is invalid the port name will be a suitable "invalid" string, not a zero pointer.
Parameters:
port | MIDI port number |
Returns: Port type string
bool portReadable (int port)
| portReadable |
[const]
Returns whether or not the port is readable. If this function returns false, then you will never receive an event from rx from this port.
If the port number is invalid, this will return false.
Parameters:
port | MIDI port number |
Returns: Whether MidiEvent objects are readable from this port
See also: portWritable
bool portWriteable (int port)
| portWriteable |
[const]
Returns whether or not the port is writeable. If this function returns false, then you can't send MidiEvent objects out via this port (with tx). If you specify this port number in a MidiEvent given to tx, the MidiEvent will be ignored.
If the port number is invalid, this will return false.
Parameters:
port | MIDI port number |
Returns: Whether MidiEvent objects are readable from this port
See also: portReadable
bool portInternal (int port)
| portInternal |
[const]
Returns whether or not the port is "internal". If this function returns false, then MidiEvent objects sent here will be transmitted vai MIDI to an external device.
If the port number is invalid, this will return false.
Parameters:
port | MIDI port number |
Returns: Whether the port connects to an internal or external MIDI device.
See also: defaultInternalPort, defaultExternalPort
int defaultInternalPort ()
| defaultInternalPort |
[const]
Returns a "default" internal port number. If you don't know which internal port to use, choose this one! If there are no internal ports, this returns MidiCommand::NoPort.
(The default internal port will in fact be the first registered internal port from the platform implementation of MidiScheduler).
Returns: Default internal port, or MidiCommand::NoPort
See also: defaultExternalPort
int defaultExternalPort ()
| defaultExternalPort |
[const]
Returns a "default" external port number. If you don't know which external port to use, choose this one! If there are no external ports, this returns MidiCommand::NoPort.
(The default external port will in fact be the first registered external port from the platform implementation of MidiScheduler).
Returns: Default external port, or MidiCommand::NoPort
See also: defaultInternalPort
void start (Clock startTime)
| start |
Start the scheduler clock running, set to the given time.
Parameters:
startTime | The time to start the scheduler at |
void start ()
| start |
Start the scheduler clock at the current 'resting time' (as set by a call to stop).
void stop (Clock stopTime = -1)
| stop |
Stop the scheduler clock and flush the Tx buffer instantaneously. The stopTime is remembered as a 'resting time' and subsequent calls to clock will return this value.
Parameters:
stopTime | The time at which to stop (-1 means immediately) |
bool running ()
| running |
[const]
Enquire whether the scheduler clock is running.
Returns: Whether the clock is running
void moveTo (Clock moveTime, Clock newTime)
| moveTo |
Without stopping, move the scheduler clock to the given time newTime at time moveTime. Any further calls to clock() after this will return times in the new time line.
If the scheduler is not running, this just sets the 'resting time'.
Parameters:
moveTime | Time at which to perform the move |
newTime | Time to move to |
void moveTo (Clock moveTime)
| moveTo |
Without stopping, move the scheduler clock immediately.
Parameters:
moveTime | Time to move to |
Clock clock ()
| clock |
Read the scheduler clock.
This works whether the scheduler is running or not. If it has been stopped then it returns the time the scheduler was stopped at (or has since been moved to) - the 'resting time'.
Returns: MidiScheduler time value
int msecs ()
| msecs |
Read the scheduler clock in milliseconds.
Returns: MidiScheduler time value in milliseconds
int tempo ()
| tempo |
[const]
Read the tempo.
Returns: Current tempo
See also: setTempo
void setTempo (int newTempo, Clock changeTime)
| setTempo |
Set the tempo.
Parameters:
newTempo | The new tempo value in beats per minute (1-256) |
changeTime | Only used if the scheduler is running to indicate when the tempo change occurs. Any further calls to clock() before changeTime will return bogus values, as they will be in the new tempo's time scale. |
See also: clock, tempo
bool eventWaiting ()
| eventWaiting |
Enquire whether there is any data in the input buffer.
This function will return true if there has been any input from any of the readable MIDI ports that you haven't yet read.
Returns: Whether there is any input data ready to be processed
MidiEvent rx ()
| rx |
Return and remove a MidiEvent from scheduler receive buffer.
Note that it is possible for this function to return a MidiEvent with MidiCommand_Invalid status. All TSE3 classes must be able to cope with these events flying around.
If there is no event waiting (see eventWaiting()) then this API will return an invalid MidiEvent.
Returns: A MidiEvent containing the data. If the status of the MidiCommand is zero, there wasn't a whole MidiCommand in the buffer.
void tx (MidiCommand mc)
| tx |
Transmit a MidiCommand immediately.
This bypasses every other scheduled MidiEvent and is sent to the hardware before them.
Parameters:
mc | The MidiCommand to transmit |
void tx (MidiEvent event)
| tx |
Adds an event to scheduler transmit buffer, to be transmitted at the appropriate time.
Events must be given to this method in time order. This is regardless of what port it is destined for.
Note: if this is a MidiCommand_NoteOn, then the matching MidiCommand_NoteOff part of the MidiEvent is ignored, you have to schedule that separately.
If the clock is not running, the event will not be scheduled and will just be ignored.
Parameters:
event | MidiEvent to schedule to transmission |
void txSysEx (int port, const unsigned char *data, size_t size)
| txSysEx |
bool remoteControl ()
| remoteControl |
[const]
Read the remote control status.
Returns: true if remote control enabled, false if disabled
See also: setRemoteControl
void setRemoteControl (bool s)
| setRemoteControl |
Sets the remote control status.
Parameters:
s | New remote control status |
See also: remoteControl
bool consumeRemoveEvents ()
| consumeRemoveEvents |
[const]
unsigned int startNote ()
| startNote |
[const]
Returns the start note. When pressed this note will cause the scheduler to start.
Returns: Start note
See also: setStartNote
void setStartNote (unsigned int n)
| setStartNote |
Sets the start note.
Parameters:
n | New start note |
See also: startNote
unsigned int stopNote ()
| stopNote |
[const]
Returns the stop note. When pushed with the shift note held down, this note will cause the scheduler to stop.
Returns: Stop note
See also: setStopNote
void setStopNote (unsigned int n)
| setStopNote |
Sets the stop note.
Parameters:
n | New stop note |
See also: stopNote
MidiScheduler (const MidiScheduler &)
| MidiScheduler |
[protected]
MidiScheduler & operator= (const MidiScheduler &)
| operator= |
[protected]
Reimplemented from Notifier.
const char * impl_implementationName ()
| impl_implementationName |
[protected const pure virtual]
Implementation functions
To implement a MidiScheduler for a particular platform you implement the functions below. They are called by the public MidiScheduler functions, using the Template Method design pattern (GoF book).
You can assume that you will never be called with an invalid port number, and a number of other parameters are guaranteed to be valid, as documented.
Functions like impl_start and impl_stop are more requests than commands. The MidiScheduler will not assume that the clock has started until the implementation calls the clockStarted function.
You shouldn't need to perform any notifications, however, you do need to take care to call the next block of protected APIs to inform the MidiScheduler code what's going on. If you don't do this, the MidiScheduler class will not behave properly.
Note that in your destructor you will also want to put the following code first: // if playing, stop first! if (MidiScheduler::running()) stop();
Your implementation of the MidiScheduler will have to deal with timing. You will be sent events (via impl_tx) IN TIME ORDER, for some time in the not-too distant future (as usually specified by the Transport class's look-ahead). You have to do the work of sending the event at the exact timer tick. ***************************************************************
const char * impl_portName (int port)
| impl_portName |
[protected const pure virtual]
const char * impl_portType (int port)
| impl_portType |
[protected const pure virtual]
bool impl_portReadable (int port)
| impl_portReadable |
[protected const pure virtual]
bool impl_portWriteable (int port)
| impl_portWriteable |
[protected const pure virtual]
void impl_start (Clock clock)
| impl_start |
[protected pure virtual]
Current state is guaranteed to be stopped.
Don't forget to call clockStarted if the start succeeds.
void impl_stop (Clock clock)
| impl_stop |
[protected pure virtual]
Current state is guaranteed to be started. The clock
value
will not ever be -1.
Don't forget to call clockStopped if the start succeeds.
void impl_moveTo (Clock moveTime, Clock newTime)
| impl_moveTo |
[protected pure virtual]
Guaranteed running.
Don't forget to call clockMoved if the start succeeds.
Clock impl_clock ()
| impl_clock |
[protected pure virtual]
Guaranteed running.
int impl_msecs ()
| impl_msecs |
[protected pure virtual]
Guaranteed running.
void impl_setTempo (int tempo, Clock changeTime)
| impl_setTempo |
[protected pure virtual]
Guaranteed tempo > 0. For the duration of this method, tempo() will return the old tempo value.
Don't forget to call clockMoved if the start succeeds.
bool impl_eventWaiting ()
| impl_eventWaiting |
[protected pure virtual]
MidiEvent impl_rx ()
| impl_rx |
[protected pure virtual]
You'll buffer all MidiEvents recieved. Returns the top buffered MidiEvent (or MidiEvent() if none are buffered).
void impl_tx (MidiCommand mc)
| impl_tx |
[protected pure virtual]
Send this MidiCommand NOW. Bypass any queued events.
Timer may be running or not.
void impl_tx (MidiEvent mc)
| impl_tx |
[protected pure virtual]
Puts a MidiEvent on the queue ready for transmission. You will be given MidiEvents in time order, so your buffer can be a FIFO rather than a time-ordered queue.
The MidiEvent will be for some time in the future, you will have to arrange to transmit it at the appropriate point.
Timer may be running or not.
void impl_txSysEx (int port,
const unsigned char *data,
size_t size)
| impl_txSysEx |
[protected pure virtual]
Send a sysex package now, bypassing the transsion queue.
int addPort (int portIndex, bool isInternal, int requestedPort = 0)
| addPort |
[protected]
Tells the MidiScheduler that your implementation has an available MIDI port. This is safe to call in your ctor.
You can suggest a port number for the MidiScheduler, but you are not necessarily guaranteed to get it.
If you don't want to go to the effort of constructing a port number, always specify zero.
This function returns the actual port number you have been assigned.
Parameters:
portIndex | You implementation's port reference |
isInternal | Specify true if this is an internal sound generator, or false if it is a MIDI link to an external device. |
requestedPort | The port number you'd like to present to the user |
void removePort (int portIndex)
| removePort |
[protected]
You don't need to call removePort in your implementation's destructor.
void clockStarted (Clock startTime)
| clockStarted |
[protected]
void clockStopped (Clock stopTime)
| clockStopped |
[protected]
void clockMoved (const Clock moveTime, Clock newTime)
| clockMoved |
[protected]
void tempoChanged (int tempo, Clock changeTime)
| tempoChanged |
[protected]
Clock startClock | startClock |
[protected]
int clockToMs (Clock time)
| clockToMs |
[protected]
An internal method to convert Clock values to millisecond times.
Clock msToClock (int ms)
| msToClock |
[protected]
An internal method to convert millisecond time values to Clocks.