BMidi
Derived from: none
Declared in: be/midi/Midi.h
Library: libmidi.so
more...
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:
- Connection functions. The connection functions let you connect the output of one BMidi object to the input of another BMidi object.
- Performance functions. 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.
- "Spray" functions. 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.
- "MIDI hook" functions. The "MIDI hook" functions define the object's response to a particular type of MIDI message. There's a hook function for each MIDI message (NoteOn(), NoteOff(), etc.). The hook functions are invoked automatically as "upstream" objects call the corresponding spray functions.
Forming Connections
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:
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).
Sources and Destinations
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.
Generating MIDI Messages
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:
void MyMidi::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" on page9.
|
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.
|
Spray Functions
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).
MIDI Hook Functions
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.
Calling MIDI Hooks Directly
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.
Creating a MIDI Filter
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:
void Transposer::NoteOn(uchar channel, uchar keyNumber,
uchar velocity, uint32 time)
{
uchar new_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.
Time
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:
void MyPerformer::NoteOn(uchar channel, uchar keyNumber,
uchar velocity, uint32 time)
{
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.
Spraying Time
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.
Running in Real 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.
Running Ahead of Time
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:
void MyTicker::Run()
{
uint32 when = B_NOW;
uchar key_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.
Hook Functions
Run()
Contains the loop that generates and broadcasts MIDI messages.
The MIDI hook functions (NoteOn(), NoteOff(), and so on) are listed in the section "MIDI Hook and Spray Functions" on page15.
Constructor and Destructor
|
BMidi()
|
Creates and returns a new BMidi object. The object's inflow thread is spawned and started in this function—in other words, BMidi objects are born with the ability to accept incoming messages. The "run" thread, on the other hand, isn't spawned until Start() is called.
|
~BMidi()
|
Kills the inflow and run threads after they've gotten to suitable stopping points (as defined below), deletes the list that holds the connections (but doesn't delete the objects contained in the list), then destroys the BMidi object.
The inflow thread is stopped after all currently-waiting MIDI hook messages have been read. No more messages are accepted while the "inflow queue" is being drained. The run thread is allowed to complete its current pass through the run loop and then told to stop (in the manner of the Stop() function).
While the destructor severs the connections that this BMidi object has formed, it doesn't sever the connections from other objects to this one. For example, consider the following (improper) sequence of calls:
/* DON'T DO THIS... */
a_midi->Connect(b_midi);
b_midi->Connect(c_midi);
...
delete b_midi;
|
|
The delete call severs the connection from b_midi to c_midi, but it doesn't disconnect a_midi and b_midi. You have to disconnect the object's "back-connections" explicitly:
/* ...DO THIS INSTEAD */
a_midi->Connect(b_midi);
b_midi->Connect(c_midi);
...
a_midi->Disconnect(b_midi);
delete b_midi;
|
|
See also: Stop()
Member Functions
|
AllNotesOff()
|
virtual void AllNotesOff(bool controlMsgOnly, uint32 time = B_NOW)
| |
Sends a B_ALL_NOTES_OFF Control Change message, passing along the timestamp, to all 16 MIDI channels. If channelMsgOnly is false, the function also sends a Note Off message for all key numbers on all channels.
|
Connect()
|
void Connect(BMidi *toObject)
| |
Connects the BMidi object's output to toObject's input. The BMidi object can connect its output to any number of other objects. Each of these connected objects receives a MIDI hook call as the BMidi sprays messages.
Any object that's been the argument in a Connect() call should eventually be disconnected through a call to Disconnect(). In particular, care should be taken to disconnect objects when deleting a BMidi object, as described in the destructor.
See also: ~BMidi(), Connections(), IsConnected()
|
Connections()
|
BList *Connections(void) const
| |
Returns a BList that contains the objects that are connected to this object's output.
|
Disconnect()
|
void Disconnect(BMidi *toObject)
| |
Severs the BMidi's connection to the argument. The connection must have previously been formed through a call to Connect() with a like disposition of receiver and argument.
See also: Connect()
|
IsConnected()
|
bool IsConnected(BMidi *toObject) const
| |
Returns true if the argument is present in the receiver's list of connected objects.
|
IsRunning()
|
bool IsRunning(void) const
| |
Returns true if the object's Run() loop is looping; in other words, if the object has received a Start() function call, but hasn't been told to Stop() (or otherwise hasn't fallen out of the loop).
|
KeepRunning()
|
protected:
Used by the Run() function to predicate its while() loop, as explained in the class description. This function should only be called from within Run().
|
Run()
|
private:
A BMidi-derived class places its data-generating machinery in the Run() function, as described in the section "Generating MIDI Messages" on page6. Of particular note: Your implementation of Run() must exit when KeepRunning() returns false:
void MyMidi::Run()
{
while (KeepRunning()) {
/* Generate a message and spray it. */
}
/* You MUST exit when KeepRunning() returns false. */
}
|
|
|
SnoozeUntil()
|
void SnoozeUntil(uint32 tick) const
| |
Puts the calling thread to sleep until tick milliseconds have elapsed since the computer was booted. This function is meant to be used in the implementation of the MIDI hook functions, as explained in the section "Time" on page9.
|
Start()
|
virtual status_t Start(void)
| |
Tells the object to spawn its run loop (wherein it executes the Run() function) and then immediately returns. You can override this function in a BMidi-derived class to provide your own pre-running initialization. Make sure you call the inherited version of this function within your implementation.
RETURN CODES
-
B_OK. The run thread was successfully spawned and resumed.
-
Thread and port error codes. Something went wrong.
|
Stop()
|
Tells the object to stop generating MIDI data. Calling Stop() tells the KeepRunning() function to return false, thus causing the run loop to terminate. Stop() may return before the thread is dead. This function doesn't affect the state of in-coming data: The object will still be able to receive MIDI messages through its MIDI hook functions.
You can override this function in a BMidi-derived class to predicate the stop, or to perform post-performance clean-up (as two examples). Make sure, however, that you invoke the inherited version of this function within your implementation.
MIDI Hook and Spray Functions
The protocols for the MIDI hook and spray functions are given below. What a particular function means (and the values of its arguments) is defined by the MIDI spec and isn't explained here. See "Spray Functions" and "MIDI Hook Functions" for more information on how these functions are used.
|
ChannelPressure(), SprayChannelPressure()
|
virtual void ChannelPressure(uchar channel, uchar pressure, uint32 time = B_NOW)
| |
protected:
void SprayChannelPressure(uchar channel, uchar pressure, uint32 time)
| |
|
ControlChange(), SprayControlChange()
|
virtual void ControlChange(uchar channel, uchar controlNumber,
uchar controlValue, uint32 time = B_NOW)
| |
protected:
void SprayControlChange(uchar channel, uchar controlNumber,
uchar controlValue, uint32 time)
| |
See also: AllNotesOff()
|
KeyPressure(), SprayKeyPressure()
|
virtual void KeyPressure(uchar channel, uchar note,
uchar pressure, uint32 time = B_NOW)
| |
protected:
void SprayKeyPressure(uchar channel, uchar note, uchar pressure, uint32 time)
| |
|
NoteOff(), SprayNoteOff()
|
virtual void NoteOff(uchar channel, uchar note,
uchar velocity, uint32 time = B_NOW)
| |
protected:
void SprayNoteOff(uchar channel, uchar note, uchar velocity, uint32 time)
| |
|
NoteOn(), SprayNoteOn()
|
virtual void NoteOn(uchar channel, uchar note, uchar velocity,
uint32 time = B_NOW)
| |
protected:
void SprayNoteOn(uchar channel, uchar note, uchar velocity, uint32 time)
| |
|
PitchBend(), SprayPitchBend()
|
virtual void PitchBend(uchar channel, uchar lsb, uchar msb, uint32 time = B_NOW)
| |
protected:
void SprayPitchBend(uchar channel, uchar lsb, uchar msb, uint32 time)
| |
|
ProgramChange(), SprayProgramChange()
|
virtual void ProgramChange(uchar channel, uchar progNum, uint32 time = B_NOW)
| |
protected:
void SprayProgramChange(uchar channel, uchar progNum, uint32 time)
| |
|
SystemCommon(), SpraySystemCommon()
|
virtual void SystemCommon(uchar status, uchar data1,
uchar data2, uint32 time = B_NOW)
| |
protected:
void SpraySystemCommon(uchar status, uchar data1, uchar data2, uint32 time)
| |
|
SystemExclusive(), SpraySystemExclusive()
|
virtual void SystemExclusive(void *data, size_t dataLength,uint32 time = B_NOW)
| |
protected:
void SpraySystemExclusive(void *data, size_t dataLength, uint32 time)
| |
|
SystemRealTime(), SpraySystemRealTime()
|
virtual void SystemRealTime(uchar status, uint32 time = B_NOW)
| |
protected:
void SpraySystemRealTime(uchar status, uint32 time)
| |
|
TempoChange(),SprayTempoChange()
|
void SprayTempoChange(int32 beatsPerMinute, uint32 time = B_NOW)
| |
protected:
void SprayTempoChange(int32 beatsPerMinute, uint32 time) |
The Be Book,
...in lovely HTML...
for BeOS Release 5.
Copyright © 2000 Be, Inc. All rights reserved..
|