The Run()
function must exit when
KeepRunning()
returns false
. If it doesn't, you'll leak threads.
BMidi
is an abstract class that defines protocols and default
implementations for functions that most of the other Midi Kit classes
inherit. The functions that
BMidi
defines fall into four categories:
The connection functions let you connect the output of one
BMidi
object to the input of another
BMidi
object.
BMidi
objects that generate MIDI data (by
reading from a port or file, as examples) implement the
Run()
hook function.
Run()
is the brains of a MIDI performance;
Start()
and
Stop()
control the performance.
The "spray" functions send MIDI messages to each
of the objects that are connected to the output of the sprayer. There's
a spray function for each type of MIDI message; for example,
SprayNoteOn()
corresponds to MIDI's Note On message.
NoteOn()
,
NoteOff()
,
etc.). The hook functions
are invoked automatically as "upstream" objects call the corresponding
spray functions.
MIDI data streams through your application, passing from one
BMidi
-derived
object to another. Each object does whatever it's designed
to do: Sends the data to a MIDI port, writes it to a file, modifies it
and passes it on, and so on.
You form a tree of
BMidi
objects through BMidi
's
Connect()
function, which takes a single
BMidi
argument. By calling
Connect()
,
you connect the output of the calling object to the input of the argument object.
(i.e. the caller is the source; the argument is the destination.)
Let's say you want to connect a MIDI keyboard to your computer, play it,
and have the performance recorded in a file. You connect a
BMidiPort
object, which reads data from the MIDI port, to a
BMidiStore
object,
which stores the data that's sent to it and can write it to a file:
/* Connect the output of a BMidiPort to the input of a BMidiStore. */BMidiPort
m_port
;BMidiStore
m_store
;m_port
.Connect
(&m_store
);
You also have to tell the
BMidiPort
to start listening to the MIDI port,
by calling its
Start()
function. This is explained in a later section.
Once you've made the recording, you can play it back by re-connecting the objects in the opposite direction:
/* We'll disconnect first, although this isn't strictly necessary. */m_port
.Disconnect
(&m_store
);m_store
.Connect
(&m_port
);
In this configuration, a
Start()
call to m_store
would cause its MIDI
data to flow into the
BMidiPort
(and thence to a synthesizer, for example, for realization).
A BMidi
object can be the source for any number of other objects:
a_midi
.Connect
(&b_object
);a_midi
.Connect
(&c_object
);a_midi
.Connect
(&d_object
);
A source can get a list of its destinations through the
Connections()
function.
A BMidi object can be the destination for any number of sources:
b_midi
.Connect
(&e_object
);c_midi
.Connect
(&e_object
);d_midi
.Connect
(&e_object
);
However, a destination can't get a list of its sources.
If your class wants to generate new MIDI data (as opposed to filtering or
realizing the data it receives), it must implement the
Run()
function. An implementation of
Run()
should include a while() loop that broadcasts one
or more MIDI message on each pass (typically it broadcasts only one), by
invoking a spray function. To predicate the loop you must test the value
of the
KeepRunning()
boolean function.
The outline of a
Run()
implementation looks like this:
voidMyMidi
::Run
() { while (KeepRunning
()) { /* Generate a message and spray it. For example... */SprayNoteOn
(...); } /* You MUST exit when KeepRunning() returns false. */ }
The Run()
function must exit when
KeepRunning()
returns false
. If it doesn't, you'll leak threads.
To tell an object to perform its
Run()
function, you call the object's
Start()
function—you never call
Run()
directly.
Start()
causes the object to spawn a thread (its "run" thread) and execute
Run()
within it. When you're tired of the object's performance, you call its
Stop()
function.
Keep in mind that the
Run()
function is only needed in classes that
introduce new MIDI data into a performance. As examples,
BMidiStore
's
Run()
sprays messages that correspond to the MIDI data that it stores,
and BMidiPort
reads data from the MIDI port and sprays messages accordingly.
Another point to keep in mind is that the
Run()
function can run ahead of
real time. It doesn't have to generate and spray data precisely at the
moment that the data needs to be realized. This is further explained in
the section
"Time".
The
BMidiSynthFile
class differs from the other classes in the way that
it implements and uses its
Run()
function; in particular, it doesn't
spawn a run thread. See the
BMidiSynthFile
class for more information.
The spray functions send data to the
BMidi
objects that are connected to
the running object's output. There's a separate spray function for each
of the MIDI message types:
SprayNoteOn()
,
SprayNoteOff()
,
SprayPitchBend()
,
and so on. Spray functions are always found within a
BMidi
's
Run()
loop, but they can be found in other places as well. For
example, if you're creating a MIDI filter, you would use spray functions
in the implementations of the object's MIDI hook functions (as explained
next).
The MIDI hook functions are hooks that are invoked upon an object's
connections when the object sprays MIDI data. The functions take the
names of the MIDI messages to which they respond:
NoteOn()
responds to a Note On message,
NoteOff()
responds to a Note Off, and so on. For example, this…
a_midi
.Connect
(&b_midi
);a_midi
.SprayNoteOn
(...);
…causes b_midi
's
NoteOn()
function to be invoked. The arguments that are passed to
NoteOn()
are taken directly from the
SprayNoteOn()
call.
BMidi
doesn't provide default implementations for any of the MIDI hooks;
it's up to each
BMidi
-derived
class to decide how to respond to MIDI messages.
Every BMidi
object automatically spawns an "inflow" thread when it's
constructed. It's in this thread that the spray-invoked MIDI hooks are
executed. The inflow thread is always running—the
Start()
and
Stop()
functions don't affect it. As soon as you construct an object,
it's ready to receive data.
You can also feed MIDI data to a
BMidi
object by invoking the MIDI hook
functions directly. For example, let's say you just want to play a note
on the General MIDI synthesizer. You don't have to create your own
BMidi
class simply to implement a
Run()
function that sprays the note; instead, all you have to do is this:
BMidiSynth
midiSynth
; /* Initialize the BMidiSynth as described in that class. */ ... /* Play a note. */midiSynth
.NoteOn
(...);
Keep in mind that when you invoke a hook function directly, it executes synchronously in the calling thread. Furthermore, the object may also be receiving MIDI messages in its inflow thread. For the Midi Kit-defined classes, this isn't a problem.
Some BMidi objects "act as filters: They receive data, modify it, and then pass it on. To do this, you call the appropriate spray functions from within the implementations of the MIDI hooks. Below is the implementation of the NoteOn() function for a proposed class called Transposer. It takes each Note On, transposes it up a half step, and then sprays it:
voidTransposer
::NoteOn
(ucharchannel
, ucharkeyNumber
, ucharvelocity
, uint32time
) { ucharnew_key
=max
(keyNumber
+ 1, 127);SprayNoteOn
(channel
,new_key
,velocity
,time
); }
There's a subtle but important distinction between a filter class and a
"performance" class (where the latter is a class that's designed to
actually realize the MIDI data it receives). The distinction has to do
with time, and is explained in the next section. An implication of the
distinction that affects the current discussion is that it may not be a
great idea to invest, in a single object, the ability to filter and
perform MIDI data. Both
BMidiStore
and
BMidiPort
are performance classes—objects of these classes realize the data they receive, the
former by caching it, the latter by sending it out the MIDI port. In
neither of these classes do the MIDI hooks spray data.
Every spray and MIDI hook function takes a final time argument. This
argument declares when the message that the function represents should be
performed. The argument is given in ticks (milliseconds). Tick 0 occurs
when you boot your computer; the tick counter automatically starts
running at that point. To get the current tick measurement, you call the
global, Kernel Kit-defined
system_time()
function and divide by 1000
(system_time()
returns microseconds).
By convention, time arguments are applied at an object's input. In other
words, a MIDI hook should look at the time argument, wait until the
designated time, and then do whatever it does that it does do. However,
this only applies to
BMidi
-derived
classes that are designed to perform
MIDI data. Objects that simply filter data shouldn't apply the time
argument.
To apply the time argument, you call the
SnoozeUntil()
function, passing
the value of time. For example, a "performance"
NoteOn()
function would look like this:
voidMyPerformer
::NoteOn
(ucharchannel
, ucharkeyNumber
, ucharvelocity
, uint32time
) {SnoozeUntil
(time
); /* Perform the data here. */ }
If time designates a tick that has already tocked,
SnoozeUntil()
returns immediately; otherwise it tells the inflow thread to snooze until the
designated tick is at hand.
If you're implementing the
Run()
function, then you have to generate a
time value yourself which you pass as the final argument to each spray
function that you call. The value you generate depends on whether your
class runs in real time, or ahead of time.
If your class conjures MIDI data that needs to be performed immediately,
you should use the B_NOW
macro as the value of the time arguments that
you pass to your spray functions. B_NOW
is simply a cover for
(system_time()
/1000)
(converted to uint). By using B_NOW
as the time
argument you're declaring that the data should be performed in the same
tick in which it was generated. This probably won't happen; by the time
the MIDI hooks are called and the data realized, a couple of ticks may
have elapsed. In this case, the MIDI hooks'
SnoozeUntil()
calls will see
that the time value has passed and will return immediately, allowing the
data to be realized as quickly as possible.
If you're generating data ahead of its performance time, you need to compute the time value so that it pinpoints the correct time in the future. For example, if you want to create a class that generates a note every 100 milliseconds, you need to do something like this:
voidMyTicker
::Run
() { uint32when
=B_NOW
; ucharkey_num
; while (KeepRunning
()) { /* Make a new note. */SprayNoteOn
(1, 60, 64,when
); /* Turn the note off 99 ticks later. */when
+= 99;SprayNoteOff
(1, 60, 0,when
); /* Bump the when variable so the next Note On * will be 100 ticks after this one. */when
+= 1; } }
When a MyTicker
object is told to start running, it generates a sequence
of Note On/Note Off pairs, and sprays them to its connected objects.
Somewhere down the line, a performance object will apply the time value
by calling
SnoozeUntil()
.
But what keeps MyTicker
from running wild and generating thousands or
millions of notes—which aren't scheduled to be played for
hours—as fast as possible? It's because the spray functions pass
data to the MIDI hooks through Kernel Kit ports that are 1 (one) message
deep. So, as long as one of the MIDI hooks calls
SnoozeUntil()
,
the spraying object will never be more than one message ahead.
A useful feature of this mechanism is that if you connect a series of
BMidi
object that don't invoke
SnoozeUntil()
,
you can process MIDI data
faster than real-time. For example, let's say you want to spray data from
one
BMidiStore
object, pass the data through a filter, and then store it in another
BMidiStore
.
The BMidiStore
MIDI hooks don't call
SnoozeUntil()
;
thus, data will flow out of the first object, through the
filter, and into its destination as quickly as possible, allowing you to
process hours of real-time data in just a few seconds. Of course, if you
add a performance object into this mix (so you can hear the data while
it's being processed), the data flow will be tethered, as described above.