The Midi Kit: BMidiStore

Derived from: public BMidi

Declared in: <midi/MidiStore.h>


Overview

The BMidiStore class defines a MIDI recording and playback mechanism. The MIDI messages that a BMidiStore object receives (at its input) are stored as events in an event list, allowing a captured performance to be played back later. The object can also read and write--or import and export--standard MIDI files. Typically, the performance and file techniques are combined: A BMidiStore is often used to capture a performance and then export it to a file, or to import a file and then perform it.


Recording

The ability to record a MIDI performance is vested in BMidiStore's input functions (NoteOn(), NoteOff(), and so on, as declared by the BMidi class). When a BMidiStore input function is invoked, the function fabricates a discrete event based on the data it has received in its arguments, and adds the event to its event list. The event list, in a manner of speaking, is the recording.

Since the ability to record is provided by the input functions, you don't need to tell a BMidiStore to start recording; it can record from the moment it's constructed.

For example, to record a performance from an external MIDI keyboard, you connect a BMidiStore to a BMidiPort object and then tell the BMidiPort to start:

   /* Record a keyboard performance. */
   BMidiStore *MyStore = new BMidiStore();
   BMidiPort *MyPort = new BMidiPort();
   
   MyPort->Connect(MyStore);
   MyPort->Start();
   /* Start playing... */

At the end of the performance, you tell the BMidiPort to stop:

   MyPort->Stop();

Timestamps

Events are added to a BMidiStore's event list immediately upon arrival. Each event is given a timestamp as it arrives; the value of the timestamp is the value of the time argument that was passed to the input function by the "upstream" object's spray function. For example, the time argument that a BMidiPort object passes through its spray functions is always B_NOW. Since B_NOW is a shorthand for "the current tick," and since time tends to move forward at a reasonably steady rate (at least so far), the events that are recorded from a BMidiPort are guaranteed to be in chronological order (as they appear in the event list).

There's no guarantee that other spraying objects will generate time arguments that procede in chronological order, however. And the BMidiStore object doesn't time-sort its events as they arrive; thus, after a recording has been made, events in the event list might not be in chronological order. If you want to ensure that the events are properly ordered, you should call Sort() after you've added events to the event list.

Note that BMidiStore's input functions don't call SnoozeUntil() : A BMidiStore writes to its event list as soon as it gets a new message, it doesn't wait until the time indicated by the time argument.

Erasing and Editing a Recording

You can't. If you make a mistake while you're recording (for example) and want to try again, you can simulate emptying the object by disconnecting the input to the BMidiStore, destroying the object, making a new one, and re-connecting. For example:

   MyPort->Disconnect(MyStore);
   delete MyStore;
   MyStore = new BMidiStore();
   MyPort->Connect(MyStore); 

Editing the events in the event list is less than impossible (were such a state possible). You can't do it, and you can't simulate it, at least not with the default implementation of BMidiStore. If you want to edit MIDI data, you have to provide your own BMidi-derived class.


Playback

To "play" a BMidiStore's list of events, you call the object's Start() function. For example, by reversing the roles taken by the BMidiStore and BMidiPort objects, you can send the BMidiStore's recording to an external synthesizer:

   /* First we disconnect the objects. */
   MyPort->Disconnect(MyStore);
   
   /* Now connect in the other direction...*/
   MyStore->Connect(MyPort);
   
   /* ...and start the playback. */
   MyStore->Start();

As described in the BMidi class specification, Start() invokes Run(). In BMidiStore's implementation of Run(), the function reads events in the order that they appear in the event list, and sprays the appropriate messages to the connected objects. You can interrupt a BMidiStore playback by calling Stop(); uninterrupted, the object will stop by itself after it has sprayed the last event in the list.

The events' timestamps are used as the time arguments in the spray functions that are called from within Run(). But with a twist: The time argument that's passed in the first spray call (for a given performance) is always B_NOW; subsequent time arguments are re-computed to maintain the correct timing in relation to the first event. In other words, when you tell a BMidiStore to start playing, the first event is performed immediately regardless of the actual value of its timestamp.

Setting the Current Event

A playback needn't begin with the first event in the event list. You can tell the BMidiStore to start somewhere in the middle of the list by calling SetCurrentEvent() before starting the playback. The function takes an integer argument that gives the index of the event that you want to begin with.

If you want to start playing from a particular time offset into the event list, you first have to figure out which event lies at that time. To do this, you ask for the event that occurs at or after the time offset (in milliseconds) through the EventAtDelta() function. The value that's returned by this function is suitable as the argument to SetCurrentEvent(). Here, we prime a playback to begin three seconds into the event list:

   long firstEvent = MyStore->EventAtDelta(3000);
   MyStore->SetCurrentEvent(firstEvent);

Keep in mind that EventAtDelta() returns the index of the first event at or after the desired offset. If you need to know the actual offset of the winning event, you can pass its index to DeltaOfEvent():

   long firstEvent = MyStore->EventAtDelta(3000);
   long actualDelta = MyStore->DeltaOfEvent(firstEvent);


Reading and Writing MIDI Files

You can also add events to a BMidiStore's event list by reading, or importing, a Standard MIDI File. To do this, you locate the file that you want to read, create a BFile to represent it, and pass the object to the Import() function:

   BFile midi_file;
   
   /* We'll assume that a_dir is a legitimate directory.  */
   if (a_dir.Contains("myfile.mid"))
   {
      /* Get the file...*/
      a_dir.GetFile("myfile.mid", &midi_file);
   
      /* ...and import it. */
      MyStore->Import(&midi_file);
   }

Note that the BFile object isn't open (you shouldn't call BFile's Open() function before you call Import()).

You can import any number of files into the same BMidiStore object. After you import a file, the event list is automatically sorted.

One thing you shouldn't do is import a MIDI file into a BMidiStore that contains events that were previously recorded from a BMidiPort (in an attempt to mix the file and the recording). Nor does the reverse work: You can't import a file and then record from a BMidiPort. The file's timestamps are incompatible with those that are generated for events that are received from the BMidiPort; the result certainly won't be satisfactory.

To write the event list as a MIDI file, you call BMidiStore's Export() function:

   BFile midi_file;
   
   /* We'll assume that a_dir is a legitimate directory. The 
    * file should be empty, so we delete it first if it exists. 
    */
   if (a_dir.Contains("myfile.mid"))
   {
      a_dir.GetFile("myfile.mid", &midi_file);
      a_dir.Remove(&midi_file);
   }
   
   /* Create the file. */
   a_dir.Create(&midi_file);
   
   /* And export the BMidiStore. */
   MyStore->Export(&midi_file, 1);

Export()'s second argument is an integer that declares the format of the file. The MIDI specification provides three formats: 0, 1, and 2. As with Import(), the BFile mustn't be open.


Constructor and Destructor


BMidiStore()

      BMidiStore(void)

Creates a new, empty BMidiStore object.


~BMidiText()

      virtual ~BMidiStore(void)

Frees the memory that the object allocated to store its events.


Member Functions


BeginTime()

      inline ulong BeginTime(void)

Returns the time, in ticks, at which the most recent performance started. This function is only valid if the object has actually performed.


CountEvents()

      inline ulong CountEvents(void)

Returns the number of events in the object's event list.


CurrentEvent()

      inline ulong CurrentEvent(void)

Returns the index of the event that will be performed next.

See also: SetCurrentEvent()


DeltaOfEvent()

      ulong DeltaOfEvent(ulong index)

Returns the "delta time" of the index'th event in the object's list of events. An event's delta time is the time span, in ticks, between the first event in the event list and itself.

See also: EventAtDelta()


EventAtDelta()

      ulong EventAtDelta(ulong delta)

Returns the index of the event that occurs on or after delta ticks from the beginning of the event list.

See also: DeltaOfEvent()


Export()

      void Export(BFile *aFile, long format)

Writes the object's event list as a standard MIDI file in the designated format. The BFile must be allocated, must refer to an actual file, and its data portion must not be open. The events are time-sorted before they're written.

See also: Import()


Import()

      void Import(BFile *aFile)

Reads the standard MIDI file from the BFile given by the argument. The BFile must not be open.

See also: Export()


SetCurrentEvent()

      void SetCurrentEvent(ulong index)

Sets the object's "current event"--the event that it will perform next --to the event at index in the event list.

See also: CurrentEvent()


SetTempo()

      void SetTempo(ulong beatsPerMinute)

Sets the object's tempo--the speed at which it performs events--to beatsPerMinute. The default tempo is 60 beats-per-minute.

See also: Tempo()


SortEvents()

      void SortEvents(bool force = FALSE)

Time-sorts the events in the BMidiStore. The object maintains a (conservative) notion of whether the events are already sorted; if force is FALSE (the default) and the object doesn't think the operation is necessary, the sorting isn't performed. If force is TRUE, the operation is always performed, regardless of its necessity.


Tempo()

      ulong Tempo(void)

Returns the object's tempo in beats-per-minute.

See also: SetTempo()




The Be Book, HTML Edition, for Developer Release 8 of the Be Operating System.

Copyright © 1996 Be, Inc. All rights reserved.

Be, the Be logo, BeBox, BeOS, BeWare, and GeekPort are trademarks of Be, Inc.

Last modified September 6, 1996.