diff --git a/MuMIDI.h b/MuMIDI.h
new file mode 100644
index 0000000..b51982f
--- /dev/null
+++ b/MuMIDI.h
@@ -0,0 +1,99 @@
+//*********************************************
+//***************** NCM-UnB *******************
+//******** (c) Carlos Eduardo Mello ***********
+//*********************************************
+// This softwre may be freely reproduced,
+// copied, modified, and reused, as long as
+// it retains, in all forms, the above credits.
+//*********************************************
+
+/** @file MuMIDI.h
+ *
+ * @brief MIDI definitions
+ *
+ * @author Carlos Eduardo Mello
+ * @date 3/3/2019
+ *
+ * @details
+ *
+ * This file describes the MIDI related data structures and definitions
+ * which are use throughout the MuM Library for input and output within the
+ * new Realtime playback and input functionality. Some of the definitions
+ * here were previously found in other class header files. They were
+ * transfered to this place to facilitate project organization and #include
+ * directives.
+ *
+ **/
+
+#ifndef MuMIDI_H
+#define MuMIDI_H
+
+/**
+ * @brief MIDI event structure
+ *
+ * @details
+ *
+ * This structure is used to describe a typical MIDI event
+ * associated with a time stamp. MIDI events are ued by
+ * MuM to output note-on and note-off info, for playback and
+ * sequencing. See MuNote::MIDIOn() and MuNote::MIDIOff()
+ * for more details. This structure is also used by MuPlayer
+ * in output queues and by MuRecorder in input ring buffers
+ **/
+struct MuMIDIMessage
+{
+ //! @brief MIDI status byte: [1XXXCCCC]
+ unsigned char status;
+ //! @brief MIDI data byte: pitch number (0-127) [0VVVVVVV]
+ unsigned char data1;
+ //! @brief MIDI data byte: key velocity (0-127) [0VVVVVVV]
+ unsigned char data2;
+ //! @brief time stamp in seconds
+ float time;
+};
+typedef struct MuMIDIMessage MuMIDIMessage;
+
+/**
+ * @brief MIDI Buffer structure
+ *
+ * @details
+ *
+ * MuMIDIBuffer is a structure containing a buffer of
+ * MuMIDIMessages. The also structure contains two
+ * variables to keep track of the number of messages
+ * in the buffer.
+ *
+ * The 'data' field in this structure is a pointer to
+ * receive a dynamically allocated array of type MuMIDIMessage.
+ * Normally, code passing this struture is responsible for
+ * allocating this memory, while code in the receiving end
+ * should free the buffer when it is no longer needed. The
+ * other two fields, 'max' and 'count' control buffer size.
+ * 'max' should contain the maximum number of elements in
+ * the array. This value should be modified only once, when
+ * memory is allocated. 'count' stores the number of elements
+ * currently in use. For obvious reasons, 'count' should
+ * allways be less or equal to 'max'. This simple
+ * vector of MIDI messages is used to copy and pass
+ * blocks of MIDI data between various parts of MuM,
+ * particularly in the input class (see MuRecorder).
+ *
+ **/
+struct MuMIDIBuffer
+{
+ //! @brief pointer to the first message in the buffer
+ MuMIDIMessage * data;
+ //! @brief maximum number of messages allowed in the buffer
+ long max;
+ //! @brief number of used/valid messages in the buffer
+ long count;
+};
+typedef struct MuMIDIMessage MuMIDIMessage;
+
+//! @brief default size for MIDI message buffers
+const long DEFAULT_BUFFER_SIZE = 1024;
+
+
+
+
+#endif /* MuMIDI_H */
diff --git a/MuMaterial.cpp b/MuMaterial.cpp
index b9d29d8..b7a06e1 100755
--- a/MuMaterial.cpp
+++ b/MuMaterial.cpp
@@ -1032,7 +1032,7 @@ MuMaterial MuMaterial::GetNotes( int voiceNumber, long from, long through )
// if requested range is valid...
if( ( (from >= 0) && (from < n) ) &&
( (through >=0) && (through < n) ) &&
- ( (through > from) )
+ ( (through >= from) )
)
{
for( i = from; i <= through; i++ )
@@ -1131,6 +1131,57 @@ MuMaterial MuMaterial::GetNotesSoundingAt(int voiceNumber, float time)
return outMaterial;
}
+MuMaterial MuMaterial::GetFrase(int voiceNumber, long from)
+{
+ MuMaterial mat;
+ MuNote note;
+ long i, through;
+ bool foundRest = false;
+
+ if(voiceNumber >= NumberOfVoices())
+ {
+ lastError.Set(MuERROR_INVALID_VOICE_NUMBER);
+ return mat;
+ }
+
+ long n = NumberOfNotes(voiceNumber);
+
+ if(from >= n)
+ {
+ lastError.Set(MuERROR_INVALID_PARAMETER);
+ return mat;
+ }
+
+ // Starting at the requested note...
+ for(i = from; i < n; i++)
+ {
+ // check every note...
+ note = GetNote(i);
+
+ // if we find a rest...
+ if(note.Amp() == 0.0 || note.Pitch() == 0)
+ {
+ // we point to the last note before the rest...
+ through = (i - 1);
+ // and extract the frase up to that point...
+ mat = GetNotes(0, from, through);
+ // then flag that we found the rest...
+ foundRest = true;
+ // and get out of the loop...
+ break;
+ }
+ }
+
+ // if no rest was found...
+ if(!foundRest)
+ {
+ // return the notes through the end of the voice...
+ mat = GetNotes(0, from, (n-1));
+ }
+
+ return mat;
+}
+
bool MuMaterial::Contains( int voiceNumber, int pitch )
{
lastError.Set(MuERROR_NONE);
@@ -1870,6 +1921,10 @@ void MuMaterial::CycleRhythm(int voiceNumber, int times)
Sort(0);
}
+void MuMaterial::AddRestToNote(int voiceNumber, long noteNumber, float ratio)
+{
+
+}
// Segmentation
@@ -2807,6 +2862,125 @@ void MuMaterial::LoadScore(string fileName, short mode) // [PUBLIC]
}
}
+// populates the receiving material with data from a MIDI buffer...
+void MuMaterial::LoadMIDIBuffer(MuMIDIBuffer inBuffer, short mode)
+{
+ lastError.Set(MuERROR_NONE);
+ long i,j,n;
+ MuNote note;
+ MuMIDIMessage firstEvent,secondEvent;
+ bool foundNote = false;
+
+ // Go through every noteOn event, comparing it to the remaining
+ // events, until we find a suitable note termination...
+ n = inBuffer.count;
+ for(i = 0; i < n; i++)
+ {
+ firstEvent = inBuffer.data[i];
+ foundNote = false;
+
+ // if this is a noteOn event and key velocity is not zero...
+ if( ( (firstEvent.status & 0xF0) == 0x90) && (firstEvent.data2 != 0) )
+ {
+ // we look ahead for its corresponding noteOff event...
+ for(j = i+1; j < n; j++)
+ {
+ secondEvent = inBuffer.data[j];
+
+ // if next event is for the same channel and same pitch...
+ if(((firstEvent.status & 0x0F) == (secondEvent.status & 0x0F)) &&
+ (firstEvent.data1 == secondEvent.data1) )
+ {
+ // if this is a noteOff for the same pitch...
+ if( ((secondEvent.status & 0xF0) == 0x80) ||
+ // or if it is a noteOn and key velocity is 0...
+ (((secondEvent.status & 0xF0) == 0x90) && (secondEvent.data2 == 0))
+ )
+ {
+ // we store the complete note...
+ note.SetFromMIDI(firstEvent, secondEvent);
+ AddNote(note);
+ note.Clear();
+ // if we found a complete note, we skip the inner loop
+ // and move on to the next noteOn...
+ foundNote = true;
+ break;
+ }
+ }
+ }
+
+ // after comparing to all remaining events, if we couldn't find
+ // a note termination, we store the event without duration...
+ if (!foundNote)
+ {
+ note.SetStart(firstEvent.time);
+ note.SetPitch(firstEvent.data1);
+ note.SetAmp(firstEvent.data2/128.0);
+ note.SetInstr((firstEvent.status & 0x0F) + 1);
+ note.SetDur(0);
+ AddNote(note);
+ note.Clear();
+
+ }
+ }
+ }
+
+ // After checking the entire buffer for notes,
+ // decide what to do with the notes that have duration 0.
+ n = NumberOfNotes();
+ for(i = 0; i < n; i++)
+ {
+ note = GetNote(i);
+ if(note.Dur() == 0)
+ {
+ switch(mode)
+ {
+ // in purge mode, remove all incomplete notes...
+ case MIDI_BUFFER_MODE_PURGE:
+ {
+ RemoveNote(i);
+ i--;
+ n--;
+ break;
+ }
+
+ // in extend mode, incomplete notes last until the end
+ // of material. if note starts beyond the duration of
+ // all other notes, it gets purged.
+ case MIDI_BUFFER_MODE_EXTEND:
+ {
+ float dur = Dur()-note.Start();
+ if (dur != 0)
+ {
+ note.SetDur(dur);
+ SetNote(i, note);
+ }
+ else
+ {
+ RemoveNote(i);
+ }
+ break;
+ }
+ // in melodic mode, incomlete notes last until
+ // the begining of next note. if note is the last
+ case MIDI_BUFFER_MODE_MELODIC:
+ {
+ if(i == n-1)
+ {
+ RemoveNote(i);
+ }
+ else
+ {
+ MuNote nextNote = GetNote(i+1);
+ note.SetDur(nextNote.Start() - note.Start());
+ SetNote(i, note);
+ }
+ break;
+ }
+ }
+ }
+ }
+}
// Generates Orchestra Definitions...
string MuMaterial::Orchestra(void) // [PUBLIC]
diff --git a/MuMaterial.h b/MuMaterial.h
index f9da9d3..710e020 100755
--- a/MuMaterial.h
+++ b/MuMaterial.h
@@ -99,6 +99,10 @@ const short EIGHTH_DEGREE = 7;
const short LOAD_MODE_TIME = 0;
const short LOAD_MODE_DIRECT = 1;
+const short MIDI_BUFFER_MODE_PURGE = 0;
+const short MIDI_BUFFER_MODE_EXTEND = 1;
+const short MIDI_BUFFER_MODE_MELODIC = 2;
+
/**
* @class MuMaterial
*
@@ -925,6 +929,36 @@ class MuMaterial
*
**/
MuMaterial GetNotesSoundingAt(int voiceNumber, float time);
+
+ /**
+ * @brief Returns all the notes before the next rest
+ *
+ * @details
+ * GetFrase() scans the requested voice looking for a rest, starting at
+ * the note indicated by index 'from'. When it finds a rest, GetFrase()returns
+ * a material object containing (in voice 0) every note, within voice 'voiceNumber'
+ * of this material, between note 'from' and the last note before the rest.
+ *
+ * GetFrase() is meant to be used with voices that contain mostly melodic
+ * data. It also assumes that frases are separated by rests. When used with
+ * complex, chord-like textures, containing multiple rests, the result is
+ * unpredictable. If GetFrase() finds no rest in the requeste voice,
+ * the entire voice content is returned in the output material.
+ *
+ * (See also: in order to force frase limits without modifying the
+ * rhythmic flow of the music, it is possible to use AddRestToNote() to
+ * insert a small rest between frases. Check documentation for more info)
+ *
+ * @param
+ * voiceNumber (int) - voice index
+ * @param
+ * from (long) - index to starting note
+ *
+ * @return
+ * MuMaterial - material containing requested frase
+ *
+ **/
+ MuMaterial GetFrase(int voiceNumber, long from);
/**
* @brief returns true if material contains input note (pitch) at voice 'voiceNumber'
@@ -1388,6 +1422,34 @@ class MuMaterial
**/
void CycleRhythm(int voiceNumber, int times);
+ /**
+ * @brief Turns part of the note into a rest
+ *
+ * @details
+ * AddRestToNote() transforms part of a note's length into silence,
+ * by cropping some of the note's duration and appending a rest of the
+ * corresponding size right after it. The size of the rest will always
+ * be exactly the same as that of the time taken from the end of the note,
+ * so that the rhythmic flow stays untouched. surrounding notes remain untouched.
+ *
+ * The third argument is optional and informs how much of the note
+ * should be cropped. A value of 1.0 turns the entire note into a rest;
+ * a value of 0.5 removes half of the note's length; and so forth.
+ * If no value is provided, a default value of 0.25 (1/4th rest) is used.
+ *
+ * (See also: this method can be used in conjunction with GetFrase(), to
+ * mark frase limits, when extracting sections of a melody from a given voice)
+ *
+ * @param
+ * voiceNumber (int) - voice index
+ * @param
+ * noteNumber (long ) - index of the note to be shortened
+ * @param
+ * ratio (float) - rest length as a percentage of the note (1.0 = 100%)
+ *
+ **/
+ void AddRestToNote(int voiceNumber, long noteNumber, float ratio = 0.25);
+
// Segmentation
/**
@@ -2153,6 +2215,49 @@ class MuMaterial
*
**/
void LoadScore(string fileName, short mode = LOAD_MODE_TIME);
+
+
+ /**
+ * @brief
+ * Loads an MuMIDIBuffer into material
+ *
+ * @details
+ * Loads a MIDI buffer (as defined in MuMIDI.h), into the receiving material. The buffer
+ * is a structure that contains a pointer to an array of MIDI messages and counter variables
+ * that keep track of the number of messages in the array. 'inBuffer.data' contains the MID
+ * events to be loaded. Each event is a structure of type MuMIDIMessage. 'inBuffer.max'
+ * contains the size of the array, as it was allocated. 'inBuffer.count' contains the number
+ * of valid messages in the array. 'count' may, occasionaly be less than 'max', but it
+ * shoould never exceed it.
+ *
+ * MIDI buffers are mostly used for MIDI input by MuRecorder, but may be employed
+ * independently if there is any need to deal with music in MIDI format for some specific
+ * application. All related definitions are found in MuMIDI.h.
+ *
+ * LoadMIDIBuffer can be used in one of two modes. If MIDI_BUFFER_MODE_PURGE (the default)
+ * is selected and the method finds noteOn events with no corresponding noteOffs by the end
+ * of the buffer, these starting events are disarded to avoid MIDI panic situations. if
+ * instead the buffer was loaded in EXTEND mode (MIDI_BUFFER_MODE_EXTEND), the unpaired
+ * note starts will all be terminated with the last noteOff found in the buffer. In other
+ * words, the notes will be sustained or extended.
+ *
+ * @note
+ * If inBufer contains a valid MIDI buffer, LoadMIDIBuffer will release the associated memory
+ * after extracting its contents as notes.
+ *
+ * @param
+ * inBuffer (MuMIDIBuffer) - a structure containing a buffer of MIDI messages
+ *
+ * @param
+ * mode (short) - defines how incomplete notes are treated. MIDI_BUFFER_MODE_PURGE means
+ * unterminated notes are discarded; MIDI_BUFFER_MODE_EXTEND means all unterminated
+ * notes will be endend with the last noteOff.
+ *
+ * @return
+ * void - LoadMIDIBuffer returns void. If an error is found it is stored as the last error
+ * in the receiving material
+ **/
+ void LoadMIDIBuffer(MuMIDIBuffer inBuffer, short mode = MIDI_BUFFER_MODE_PURGE);
/**
* @brief
diff --git a/MuNote.cpp b/MuNote.cpp
index 55124e4..5244c70 100755
--- a/MuNote.cpp
+++ b/MuNote.cpp
@@ -326,3 +326,15 @@ MuMIDIMessage MuNote::MIDIOff(void)
noteOff.time = ( start + dur );
return noteOff;
}
+
+void MuNote::SetFromMIDI(MuMIDIMessage noteOn, MuMIDIMessage noteOff)
+{
+ // This was a quick and dirty implementation!!
+ // FIX: perform sanity checks on input data.
+ // FIX: verify ranges, data types and numeric conversions
+ SetStart(noteOn.time);
+ SetPitch(noteOn.data1);
+ SetAmp(noteOn.data2/128.0);
+ SetInstr((noteOn.status & 0x0F) + 1);
+ SetDur(noteOff.time - noteOn.time);
+}
diff --git a/MuNote.h b/MuNote.h
index d95488b..aab8634 100755
--- a/MuNote.h
+++ b/MuNote.h
@@ -27,6 +27,7 @@
#define _MU_NOTE_H_
#include "MuParamBlock.h"
+#include "MuMIDI.h"
// Constants
@@ -71,28 +72,6 @@ struct cs_pitch
};
typedef struct cs_pitch cs_pitch;
-/**
- * @brief Note information as MIDI events
- *
- * @details
- *
- * This structure is used to describe a note as MIDI data.
- * An MuNote object can output note-on and note-off info,
- * using this struct. See MuNote::MIDIOn() and MuNote::MIDIOff()
- * for more details.
- **/
-struct MuMIDIMessage
-{
- //! @brief MIDI status byte: [1XXXCCCC]
- unsigned char status;
- //! @brief MIDI data byte: pitch number (0-127) [0VVVVVVV]
- unsigned char data1;
- //! @brief MIDI data byte: key velocity (0-127) [0VVVVVVV]
- unsigned char data2;
- //! @brief time stamp in seconds
- float time;
-};
-typedef struct MuMIDIMessage MuMIDIMessage;
/**
* @class MuNote
@@ -598,5 +577,23 @@ class MuNote
*
**/
MuMIDIMessage MIDIOff(void);
+
+ /**
+ * @brief Returns a deactivation event for the note as an MuMIDIMessage struct
+ *
+ * @details
+ *
+ * MIDIOff() converts the note's data to MIDI format and returns the
+ * note-off event for the note. Note data is assigned as follows:
+ *
+ * - ::End() - becomes time stamp in seconds (time field)
+ *
- Instr - becomes channel choice in range 0-F (status field - bits 0 through 3)
+ *
- Pitch - becomes data1 field
+ *
- data2 field receives 0 (zero)
+ *
+ * @return MuMIDIMessage structure
+ *
+ **/
+ void SetFromMIDI(MuMIDIMessage noteOn, MuMIDIMessage noteOff);
};
#endif
diff --git a/MuPlayer.cpp b/MuPlayer.cpp
new file mode 100644
index 0000000..3a1788d
--- /dev/null
+++ b/MuPlayer.cpp
@@ -0,0 +1,475 @@
+//
+// MuPlayer.cpp
+// MuMRT
+//
+// Created by Carlos Eduardo Mello on 2/17/19.
+// Copyright © 2019 Carlos Eduardo Mello. All rights reserved.
+//
+
+#include "MuPlayer.h"
+
+bool MuPlayer::pause = false;
+bool MuPlayer::stop = false;
+pthread_mutex_t MuPlayer::sendMIDIlock;
+
+MuPlayer::MuPlayer(void)
+{
+ // initialize all fields of playback pool
+ // to default values...
+ for(int i = 0; i < MAX_QUEUES; i++)
+ {
+ eqPool[i].buffer = NULL;
+ eqPool[i].n = 0;
+ eqPool[i].active = false;
+ eqPool[i].loading = false;
+ eqPool[i].paused = false;
+ eqPool[i].next = 0;
+ eqPool[i].material.Clear();
+ eqPool[i].queueThread = 0;
+ eqPool[i].loadingTime = 0;
+ }
+ // initialize MIDI objects...
+ midiClient = 0;
+ midiOutPort = 0;
+ midiDest = 0;
+
+ // clear scheduler thread variable...
+ schedulerThread = 0;
+}
+
+MuPlayer::~MuPlayer(void)
+{
+
+}
+
+void MuPlayer::CleanPlaybackPool(void)
+{
+ // Clean Playback Pool
+ for(int i = 0; i < MAX_QUEUES; i++)
+ {
+ if(eqPool[i].buffer)
+ delete [] eqPool[i].buffer;
+ eqPool[i].buffer = NULL;
+ eqPool[i].n = 0;
+ eqPool[i].active = false;
+ eqPool[i].loading = false;
+ eqPool[i].paused = false;
+ eqPool[i].next = 0;
+ eqPool[i].material.Clear();
+ if(eqPool[i].queueThread != 0)
+ {
+ pthread_cancel(eqPool[i].queueThread);
+ eqPool[i].queueThread = 0;
+ }
+ eqPool[i].loadingTime = 0;
+ }
+}
+
+bool MuPlayer::Init(void)
+{
+ long n,i;
+ OSStatus err = noErr;
+
+ // create Client...
+ if(midiClient == 0)
+ {
+ err = MIDIClientCreate(CFSTR("MuM Playback"), NULL, NULL, &midiClient);
+ if(err == noErr)
+ {
+ // Create Output Port...
+ err = MIDIOutputPortCreate(midiClient, CFSTR("MuM Output"), &midiOutPort);
+ if(err == noErr)
+ {
+ // List Possible Destinations...
+ n = MIDIGetNumberOfDestinations();
+ if(n != 0)
+ {
+ CFStringRef name;
+ char cname[64];
+ MIDIEndpointRef dest;
+
+ // List Possible MIDI Destinations...
+ for(i = 0; i < n; i++)
+ {
+ dest = MIDIGetDestination(i);
+ if (dest != 0)
+ {
+ MIDIObjectGetStringProperty(dest, kMIDIPropertyName, &name);
+ CFStringGetCString(name, cname, sizeof(cname), 0);
+ CFRelease(name);
+ cout << "[Destination " << i << "]: " << cname << endl << endl;
+ }
+ }
+
+ // Choose a MIDI destination for playback...
+ midiDest = MIDIGetDestination(0);
+
+ if(StartScheduler())
+ return true;
+ }
+ else
+ {
+ cout << "No MIDI destinations present!\n" << endl;
+ }
+ }
+ else
+ {
+ cout << "Failed to open output port!\n" << endl;
+ }
+ }
+ else
+ {
+ cout << "Failed to create MIDI client!\n" << endl;
+ }
+ }
+ else
+ {
+ cout << "Client already initialized! (call reset MIDI)\n" << endl;
+ }
+
+ return false;
+
+}
+
+bool MuPlayer::SelectMIDIDestination(int destNumber)
+{
+ if(destNumber > 0)
+ {
+ midiDest = MIDIGetDestination(destNumber);
+ if (midiDest != 0)
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+string MuPlayer::ListDestinations(void)
+{
+ string list;
+ long n,i;
+
+ // Get number of destinations...
+ if(midiClient != 0)
+ {
+ n = MIDIGetNumberOfDestinations();
+ if(n != 0)
+ {
+ CFStringRef name;
+ char cname[64];
+ MIDIEndpointRef dest;
+
+ // List Possible MIDI Destinations...
+ for(i = 0; i < n; i++)
+ {
+ dest = MIDIGetDestination(i);
+ if (dest != 0)
+ {
+ MIDIObjectGetStringProperty(dest, kMIDIPropertyName, &name);
+ CFStringGetCString(name, cname, sizeof(cname), 0);
+ CFRelease(name);
+ list += cname;
+ list += "\n";
+ }
+ }
+ }
+ }
+
+ return list;
+}
+
+void MuPlayer::Reset(void)
+{
+ // Stop scheduler thread
+ if(schedulerThread != 0)
+ {
+ pthread_cancel(schedulerThread);
+ schedulerThread = 0;
+ }
+
+ // Release all queue buffers and threads
+ CleanPlaybackPool();
+
+ // Release MIDI components...
+ if(midiOutPort != 0)
+ {
+ CFRelease(&midiOutPort);
+ midiOutPort = 0;
+ }
+
+ if(midiClient != 0)
+ {
+ CFRelease(&midiClient);
+ midiClient = 0;
+ }
+
+ midiDest = 0;
+}
+
+bool MuPlayer::Play(MuMaterial & inMat, int mode)
+{
+ int i;
+ int selectedQueue = -1;
+ MuNote note;
+
+ // First find a usable event queue...
+ if(mode == PLAYBACK_MODE_NORMAL)
+ {
+ // at the end of this loop, if at
+ // least one queue is available,
+ // selectedQueue contains its index..
+ for (i = 0; i < MAX_QUEUES; i++)
+ {
+ // if the current queue is not being played or filled,...
+ if(eqPool[i].active == false && eqPool[i].loading == false)
+ {
+ // it can be selected for a new material...
+ selectedQueue = i;
+ // mark queue as under construction...
+ eqPool[i].loading = true;
+ break;
+ }
+ }
+ // if unused queue is found...
+ if(selectedQueue >= 0)
+ {
+ // start the queue's working thread...
+ StartQueueThread(inMat,selectedQueue);
+ }
+ else
+ {
+ // otherwise report failure...
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool MuPlayer::StartQueueThread(MuMaterial & inMat, int queueIdx)
+{
+ int res;
+
+ // make a copy of the input material so the thread can
+ // work on it safely, as it will be working assynchronously
+ eqPool[queueIdx].material = inMat;
+
+ // Start the thread...
+ res = pthread_create(&(eqPool[queueIdx].queueThread), NULL, MuPlayer::EnqueueMaterial, (void*)(&eqPool[queueIdx]));
+ if(res)
+ {
+ // if we fail, terminate process...
+ cout << "THREAD ERROR! - Terminating..." << endl;
+ exit(EXIT_FAILURE);
+ }
+
+ // if successful...
+ return true;
+}
+
+void * MuPlayer::EnqueueMaterial(void* arg)
+{
+ int numVoices, i;
+ MuNote note;
+ long numNotes, nextEvent, j, k;
+ EventQueue * queue = (EventQueue *)arg;
+
+ // get the total number of notes in input material...
+ numNotes = queue->material.NumberOfNotes();
+
+ // each note needs two MIDI events (on/off)
+ numNotes *= 2;
+
+ // allocate memory for the note events...
+ if(numNotes > 0)
+ {
+ // Attention! this buffer needs to be released when
+ // the scheduler is done sending its events...
+ queue->buffer = new MuMIDIMessage[numNotes];
+ if(queue->buffer)
+ queue->n = numNotes;
+ }
+
+ // extract MIDI events from notes...
+ if(queue->buffer)
+ {
+ nextEvent = 0;
+ numVoices = queue->material.NumberOfVoices();
+
+ for(i = 0; i < numVoices; i++)
+ {
+ numNotes = queue->material.NumberOfNotes(i);
+ for (j = 0; j < numNotes; j++)
+ {
+ note = queue->material.GetNote(i, j);
+ queue->buffer[nextEvent] = note.MIDIOn();
+ nextEvent++;
+ queue->buffer[nextEvent] = note.MIDIOff();
+ nextEvent++;
+ }
+ }
+
+ // sort events by timestamp...
+ long n = queue->n;
+ for(j = n; j >= 1; j-- )
+ {
+ for( k = 0; k < j-1; k++ )
+ {
+ if( queue->buffer[k].time > queue->buffer[k+1].time )
+ {
+ // swap messages...
+ MuMIDIMessage temp;
+ temp = queue->buffer[k];
+ queue->buffer[k] = queue->buffer[k+1];
+ queue->buffer[k+1] = temp;
+ }
+ }
+ }
+ queue->material.Clear();
+ queue->next = 0;
+ queue->paused = false;
+
+ // IMPORTANT: LOADING TIME
+ // The following timestamp is registering this moment, after
+ // the event buffer has been successfully allocated and filled,
+ // to be the initial time for playback of this queue. All events
+ // in the queue will be referenced from this point. The amount
+ // of microseconds retrieved hear will be added to the timestamp
+ // of every event so the scheduler can compare stamps and decide
+ // when to send the messages.
+ queue->loadingTime = ClockStamp();
+ //cout << "[Loading Time]: " << queue->loadingTime << endl;
+
+ // after the queue is set to 'active' the scheduler may
+ // use it at any moment (even at interrupt time). That's
+ // why this MUST BE THE LAST ACTION!
+ queue->active = true;
+
+ // after the queue is active we turn off the loading flag...
+ queue->loading = false;
+ }
+
+ // after the work is done we terminate this thread...
+ pthread_exit(NULL);
+}
+
+bool MuPlayer::StartScheduler(void)
+{
+ int res;
+ res = pthread_create(&schedulerThread, NULL, MuPlayer::ScheduleEvents, (void*)eqPool);
+ if(res)
+ {
+ cout << "THREAD ERROR! - Terminating..." << endl;
+ exit(EXIT_FAILURE);
+ }
+
+ return true;
+}
+
+// FIX FIX FIX: FINISH IMPLEMENTING THIS CAREFULLY!!
+// 1) REMEMBER TO RESET EMPTY QUEUES SO THEY CAN BE REUSED
+// 2) REMEMBER TO IMPLEMENT GLOBAL PAUSE AND STOP CORRECTLY
+// 3) Individual queue pause and stop must but planned better
+// for later
+void * MuPlayer::ScheduleEvents(void * pl)
+{
+ int i;
+
+ MuPlayer * player = (MuPlayer *)pl;
+ EventQueue * pool = player->eqPool;
+
+ // this thread will terminate
+ // when the Player's stop flag is set...
+ while (!MuPlayer::stop)
+ {
+ // only do work if the player is not paused...
+ if(!MuPlayer::pause)
+ {
+ for(i = 0; i < MAX_QUEUES; i++)
+ {
+ // if curent queue is active,...
+ if (pool[i].active == true)
+ {
+ // look for its next event...
+ MuMIDIMessage msg = pool[i].buffer[pool[i].next];
+ long msgTime = (long)(msg.time * ONE_SECOND) + (pool[i].loadingTime);
+ // get current time from the system
+ long currTime = ClockStamp();
+
+ // if the timestamp on the message is expired...
+ if( currTime >= msgTime)
+ {
+ // schedule it to be sent to destination...
+ SendMIDIMessage(msg,player->midiOutPort, player->midiDest);
+ // advance event counter...
+ pool[i].next += 1;
+ // if this is the last event in the buffer,
+ // this queue needs to be reset...
+ if(pool[i].next >= pool[i].n)
+ {
+ // reset queue
+ delete [] pool[i].buffer;
+ pool[i].buffer = 0;
+ pool[i].n = 0;
+ pool[i].paused = false;
+ pool[i].next = 0;
+ pool[i].queueThread = 0;
+ pool[i].loadingTime = 0;
+ // lastly deactivate queue
+ pool[i].active = false;
+ pool[i].loading = false;
+ }
+ }
+ }
+ } // end MAX_QUEUES loop
+ usleep(10); // idle for a moment...
+ } // end if(!pause)
+ else
+ {
+ usleep(100); // idle for a moment...
+ }
+ } // end infinite loop
+
+
+ pthread_exit(NULL);
+}
+
+void MuPlayer::SendMIDIMessage(MuMIDIMessage msg, MIDIPortRef outPort, MIDIEndpointRef dest)
+{
+ //pthread_mutex_lock(&sendMIDIlock);
+
+ if((outPort != 0) && (dest != 0))
+ {
+ Byte msgBuff[MESSAGE_LENGTH];
+ msgBuff[0] = msg.status;
+ msgBuff[1] = msg.data1;
+ msgBuff[2] = msg.data2;
+
+ MIDITimeStamp timestamp = 0.0;
+ Byte buffer[1024]; // storage space for MIDI Packets
+ MIDIPacketList * packetlist = (MIDIPacketList*)buffer;
+ MIDIPacket * packet = MIDIPacketListInit(packetlist);
+ packet = MIDIPacketListAdd( packetlist, sizeof(buffer),
+ packet, timestamp,
+ MESSAGE_LENGTH, msgBuff );
+ MIDISend(outPort, dest, packetlist);
+ }
+
+ //pthread_mutex_unlock(&sendMIDIlock);
+}
+
+
+void MuPlayer::Pause(bool T_F)
+{
+ pause = T_F;
+}
+
+void MuPlayer::Stop(void)
+{
+ stop = true;
+}
+
+
+
+
diff --git a/MuPlayer.h b/MuPlayer.h
new file mode 100644
index 0000000..cd39de1
--- /dev/null
+++ b/MuPlayer.h
@@ -0,0 +1,596 @@
+//
+// MuPlayer.hpp
+// MuMRT
+//
+// Created by Carlos Eduardo Mello on 2/17/19.
+// Copyright © 2019 Carlos Eduardo Mello. All rights reserved.
+//
+
+/** @file MuPlayer.h
+ *
+ * @brief MuPlayer Class Interface
+ *
+ * @author Carlos Eduardo Mello
+ * @date 2/17/2019
+ *
+ * @details
+ *
+ * MuPlayer orquestrates the realtime playback facilities
+ * in the MuM library. It handles everything from scheduling
+ * materials for playback to managing working threads
+ * and playback controls. Normally only a single object
+ * of this class should be instantiated for a MuM based
+ * application.
+ *
+ * For more details about how to use an MuPlayer object to
+ * play Musical Materials dynamically during progam execution
+ * see the MuPlayer Class Documentation.
+ *
+ **/
+
+
+#ifndef MU_PLAYER_H
+#define MU_PLAYER_H
+
+#include
+#include
+#include
+#include
+#include "MuMaterial.h"
+using namespace std;
+
+
+//!@brief Maximum number of queues objects in the playback pool
+const int MAX_QUEUES = 10;
+
+//!@brief Normal Playback Mode: imediate playback of scheduled materials
+const int PLAYBACK_MODE_NORMAL = 1;
+
+//!@brief Game Playback Mode: materials requested through callback
+const int PLAYBACK_MODE_GAME = 2;
+
+//!@brief size of midi message
+const int MESSAGE_LENGTH = 3;
+
+/**
+ * @brief Event Queue - MIDI events to be played
+ *
+ * @details
+ *
+ * This structure contais a pointer to an array of MIDI events
+ * and the number of events in this array. It is used to pass
+ * a sequence of events to a player event thread so it can quickly
+ * access the necessary data for playback. The structure is only
+ * a convenient wrapper for a data buffer and its size. It is assumed
+ * by the receiving end that the buffer pointer actually points to
+ * a valid vector.
+ *
+ **/
+struct EventQueue
+{
+ //! @brief address of an MuMIDIMessage array
+ MuMIDIMessage * buffer;
+ //! @brief number of valid indexes in the message array
+ long n;
+ //! @brief index of next message to be sent
+ long next;
+ //! @brief activation flag: true == active, false == inactive;
+ // a queue should not be picked for playback when it is active.
+ bool active;
+ //! @brief loading flag: set while the working thread is filling up the queue;
+ // a queue should not be picked for playback when it is loading.
+ bool loading;
+ //! @brief pause flag: true == paused, false == running
+ bool paused;
+ //! @brief queue thread: fills event queue with events from input MuMaterial
+ pthread_t queueThread;
+ //! @brief reference to input material to be associated with this queue
+ MuMaterial material;
+ //! @brief time in microseconds when the event queue is loaded and ready to be played
+ long loadingTime;
+};
+typedef struct EventQueue EventQueue;
+
+/**
+ * @class MuPlayer
+ *
+ * @brief MuPlayer Class
+ *
+ * @details
+ *
+ * INTRO:
+ *
+ * MuPlayer is the only class in the MuM Library realtime
+ * playback module. Playback is currently done with MIDI and
+ * can be directed to any enabled MIDI destinations in the system.
+ * Normally only a single Player object is needed to play various
+ * materials within the lifetime of a MuM based application.
+ * MuPlayer assigns MuMaterial objects to playback queues and
+ * schedules them for playback. A queue can be scheduled
+ * using one of two ways: normal mode or game mode. In normal mode
+ * queues are scheduled for immediate playback. In game mode, client
+ * code registers a callback function which the Player will call
+ * when it needs a new material to keep playback going.
+ *
+ * Alternatively, any MuMaterial can be played from code with
+ * Csound by calling PlaybackWithCsound() on the object to be
+ * heard. However this call is synchronous and it starts
+ * a system level process which will play the entire length
+ * of the material before returning control to the Library.
+ *
+ * @note
+ * currently only normal playback mode is implemented (check the
+ * MuM Library page on GitHub frequently for new functionality).
+ *
+ * INITIALIZATION:
+ *
+ * Before being used, an MuPlayer needs to be
+ * initialized. This initialization creates the necessary
+ * infrastructure for the player to interact with the MIDI
+ * system in the current platform. Initialization is done
+ * with a call to Init(). When using CoreMIDI, Init()
+ * creates a MIDI client and an output port associated with
+ * it. It also verifies the available MIDI destinations at the
+ * moment of the call and displays a list of destinations in
+ * standard output (std::cout). By default, Init() selects the
+ * first available destination, but this choice can be overruled
+ * with a subsequent call to SelectMIDIDestination(). Init() also
+ * starts the scheduler thread which is responsible for actually
+ * sending the MIDI events. ResetMIDI() releases all MIDI
+ * resources created by Init(). After this call, the Player
+ * needs to be initialized again in order be usable.
+ *
+ * USING PLAYERS:
+ *
+ * Once initialized, the player can receive requests
+ * to play musical materials with calls to Play(). This method
+ * takes an MuMaterial as argument and passes the material to
+ * a working queue. It also takes a choice of playback
+ * mode, which determines when/how the materials will be played.
+ * Assuming the player has been initialized beforehand,
+ * Play() can be called at any time during program execution.
+ *
+ * PLAYBACK CONTROLS:
+ *
+ * MuPlayer can be used to pause, resume or stop
+ * playback. It is possible to pause/resume/stop an individual
+ * (playback queue) or the entire playback system. See Pause()
+ * and Stop() for more details on how to use the playback
+ * controls of MuPlayer.
+ *
+ * UNDER THE HOOD
+ *
+ * In order to allow many materials to playback simultaneously,
+ * MuPlayer employs Posix Threads. The class contains a scheduler thread
+ * and several working queue threads. The queue threads are initiated
+ * when a request for playback is made. They strip the input materials
+ * extracting MIDI events, put them in chronological order inside a
+ * MIDI event queue and flag the queue as active. The scheduler, on
+ * the other hand, starts when the player is initialized and keeps
+ * looking for active queues in the pool. For every active queue it finds
+ * the next pending event and checks its timestamp. If it is expired the
+ * scheduler sends it. Then it moves to the next active queue and so on,
+ * until the applications terminates or the player is paused, stopped or
+ * reset. When the scheduler reaches the last event in a queue, it
+ * resets the queue and marks it as inactive, so it can be used again
+ * by the player.
+ *
+ * The player comunicates to its threads through one-way flags.
+ * For example, only the queue thread can set the active flag and it only
+ * does that once, when the event queue is filled up. After that the
+ * working queue thread will terminate. Only the scheduler thread
+ * will read this flag and turn it off when the event queue is completely
+ * empty. Only the player will look for inactive queue so it can
+ * play another material. Similarly, the playback controls are
+ * implemented using this same type of mechanism. Each queue has a
+ * pause flag which can only be set by the player and read by the
+ * scheduler thread. When the scheduler detects an acitve queue,
+ * it checks to see if it is paused. If it is, and there is a pending event
+ * to be played, it discards it, unless it is a noteOff event.
+ * if the entire player is paused, the scheduler ignores all queues
+ * and just idles for a few microseconds before checking again.
+ *
+ * SAMPLE:
+ *
+ * Normal workflow for playback with MuM can be summarized by the following
+ * piece of code:
+ *
+ * @code {.cpp}
+ *
+ * MuPlayer player;
+ * player.Init();
+ * //...
+ * MuMaterial mat;
+ * mat.MajorScale(0.5);
+ * player.Play(mat, PLAYBACK_MODE_NORMAL);
+ * // ...
+ * mat.Transpose(-5);
+ * player.Play(mat,PLAYBACK_MODE_NORMAL);
+ * //...
+ * player.Pause(true); // pause playback
+ * //...
+ * player.Pause(false); // resume playback
+ * //...
+ * @endcode
+ *
+ * @note
+ * Currently, only PLAYBACK_MODE_NORMAL is implemented.
+ *
+ * @warning
+ *
+ * MuPlayer's playback queue pool is allocated at compile time.
+ * Its size is limited by the MAX_QUEUES constant. Depending
+ * on expected density of materials in the application, it may
+ * be necessary to increase this value before compiling. Otherwise,
+ * once all queues in the pool are in use, requests to play new
+ * materials may not be honored.
+ *
+ **/
+
+
+class MuPlayer
+{
+ private:
+
+ EventQueue eqPool[MAX_QUEUES]; // Playback Pool
+ MIDIClientRef midiClient; // MIDI Client (CoreMIDI)
+ MIDIPortRef midiOutPort; // OUTPUT Port (CoreMIDI)
+ MIDIEndpointRef midiDest; // Destination Endpoint (CoreMIDI)
+
+ pthread_t schedulerThread;
+ static pthread_mutex_t sendMIDIlock;
+
+ static bool pause; // flag to communicate pause command to scheduler
+ static bool stop; // flag to communicate stop command to scheduler
+
+ public:
+
+ // Constructor/Destructor
+
+ /**
+ * @brief Default Constructor
+ *
+ * @details
+ * This constructor sets internal player data fields to reasonable default values
+ *
+ **/
+
+ MuPlayer(void);
+
+ /**
+ * @brief Destructor
+ *
+ * @details
+ * currently, the MuPlayer Destructor does not handle any specific tasks.
+ **/
+ ~MuPlayer(void);
+
+ /**
+ * @brief clears all pool data (buffers, materials) and zeroes fields
+ *
+ * @details
+ * This method clears all data from playback pool. It goes through each
+ * queue releasing memory buffers, zeroeing structure fields and emptying
+ * materials. It is called once by the Player's constructor and is reused
+ * when necessary, by other methods.
+ *
+ **/
+ void CleanPlaybackPool(void);
+
+ /**
+ * @brief Initializes the MuPlayer MIDI configurations and starts event scheduler thread
+ *
+ * @details
+ *
+ * Init() is responsible for initializing the MIDI environment
+ * for the Player. The CoreMIDI implementation of this method
+ * starts out by creating a MIDI client and an associated MIDI output
+ * port, so the Library can send MIDI events to the system. After that
+ * it requests the list of current destinations to CoreMIDI and displays
+ * the list to standard output (std::cout). Init() always selects the
+ * first available destination for playback, but this choice can be
+ * changed by a subsequent call to SelectMIDIDestination(), using one
+ * of the destination numbers displayed by Init().
+ *
+ * Normally there shouldn't be any problems with initialization, but it
+ * is always safer to check the return value for this method. If Init()
+ * for any reason returns 'false', it means one or more of the CoreMIDI
+ * calls failed, in which case the MuPlayer object should not be used.
+ * Init() may also return false if for some reason it cannot start the
+ * scheduler thread.
+ *
+ * @return
+ * bool - true for success, false for error in initializing the MIDI
+ * environment or starting the scheduler thread.
+ *
+ **/
+ bool Init(void);
+
+ /**
+ * @brief Selects a MIDI destination for playback
+ *
+ * @details
+ *
+ * SelectMIDIDestination() takes a destination number and stores it
+ * for use by the player, replacing any prior selections.
+ * Valid destination numbers are supplied by CoreMIDI and can be
+ * verified with a call to DisplayDestinations() or by checking
+ * Init()'s console output.
+ *
+ * @param
+ * destNumber (int) - number of the desired MIDI destination
+ *
+ * @return
+ * void
+ *
+ **/
+ bool SelectMIDIDestination(int destNumber);
+
+ /**
+ * @brief Lists MIDI destinations available for playback in the system
+ *
+ * @details
+ *
+ * ListDestinations() show the avialable MIDI destinations at the time
+ * of the call. The numbers in the list can be used by
+ * SelectMIDIDestination() to choose a target for playback. It should
+ * be noted that this information is inherently dynamic. MIDI devices
+ * or applications may be started or finished right after a call
+ * to ListDestinations(). Therefore it is important to allways check
+ * the return value from SelectMIDIDestination().
+ *
+ * @return
+ *
+ * string: a standard C++ string containing a list of possible MIDI
+ * destinations. This string needs to be parsed to extract each
+ * destination. Data in the string is organized according to the
+ * following scheme:
+ *
+ * line 0: Description for Destination 0
+ * line 1: Description for Destination 1
+ * line 2: Description for Destination 2
+ * line n: Description for Destination n
+ *
+ * Destinations are separated by a carriage return ("\n").
+ * Destination numbers are implied by the line position in
+ * the string. The lines are numbered starting from 0, so the
+ * very first line describes destination 0, the second line
+ * describes destination 1, and so forth. This string may
+ * simply be sent to standart output (std::cout) for visual
+ * verification at the console or parsed into discrete units
+ * for further manipulation.
+ **/
+ string ListDestinations(void);
+
+ /**
+ * @brief cancels MIDI setup, stops scheduler and releases all resources for MuPlayer
+ *
+ * @details
+ *
+ * Reset() releases all resources created by Init()
+ * and zeroes all the internal variables associated with them.
+ * It also stops the scheduler thread and throws away any active
+ * queues. Reset() effectively puts MuPlayer back at its original
+ * uninitialized state. After a call to ResetMIDI(), an MuPlayer
+ * object cannot be used until it is initialized again.
+ *
+ * @return
+ * void
+ *
+ **/
+ void Reset(void);
+
+ /**
+ * @brief initiates a playback queue for a requested material and mode
+ *
+ * @details
+ *
+ * Play() takes the input MuMaterial object contained in the 'inMat'
+ * argument and assigns an event queue from the Player's playback pool
+ * to handle that input material.
+ *
+ * Play() goes through the playback pool only once,
+ * looking for inactive queues to use. Once it finds one, it calls
+ * StartQueueThread() with a reference to the MuMaterial to be played.
+ * That method stores a copy of the material inside the queue structure
+ * and starts the working thread, which, in turn, extracts data from the
+ * material and activates the queue. If Play() cannot find an inactive
+ * queue to use, it returns false, in which case the playback request
+ * will not be honored.
+ *
+ * The playback pool is just an array of EventQueue structures, which
+ * can be used and reused during the course of the application.
+ * The pool has a fixed size which is determined at compile time.
+ * Since not all queues are in use all the time,
+ * recycling them allows more efficient use of resources.
+ * If for any reason the pool size turns out to be too small, it can be
+ * increased by changing the value of MAX_QUEUES at the begining of
+ * MuPlayer's header file.
+ *
+ * @code {.cpp}
+ *
+ * const int MAX_QUEUES = 100;
+ *
+ * @endcode
+ *
+ * @param
+ * inMat (MuMaterial&) - material to be played
+ *
+ * @param
+ * mode (int) - playback mode to be used
+ * (currently, only PLAYBACK_MODE_NORMAL is implemented)
+ *
+ * @return
+ *
+ * bool - Play() returns false if (a) it couldn't find an idle event
+ * queue in the pool or (b) the call to StartQueueThread() fails
+ * (see StartQueueThread() for details), otherwise it returns true.
+ *
+ **/
+ bool Play(MuMaterial & inMat, int mode);
+
+ /**
+ * @brief starts an event queue working thread
+ *
+ * @details
+ *
+ * Each queue has a working thread associated with it. It is
+ * used to fill up the queue with MIDI events extracted from
+ * the material being played. StartQueueThread() initiates this
+ * thread. The thread terminates automatically when all events
+ * from the input material are enqueued for playback.
+ *
+ * @param
+ * inMat (MuMaterial &): reference to the material to be enqueued
+ *
+ * @param
+ * queueIdx (int): index of the selected queue
+ *
+ * @return
+ * bool: StartQueueThread() returns false if it cannot start
+ * the working thread and true otherwise
+ *
+ **/
+ bool StartQueueThread(MuMaterial & inMat, int queueIdx);
+
+ /**
+ * @brief extracts MIDI events from input material and puts them
+ * in the corresponding playback event queue.
+ *
+ * @details
+ *
+ * EnqueueMaterial() is the thread function for a queue's working
+ * thread. It is initiated by StartQueueThread() and is responsible
+ * for getting each note from the input material converted to MIDI
+ * events and placed in the queue in chronological order, so they
+ * can be scheduled for playback by the scheduler thread. When this
+ * method concludes its work, it sets the queue's 'active' flag to
+ * true, so its events can be accessd by the scheduler.
+ *
+ * @return
+ * void *: EnqueueMaterial() terminates when the tread exits
+ *
+ **/
+ static void * EnqueueMaterial(void*);
+
+ /**
+ * @brief starts the event scheduling thread
+ *
+ * @details
+ *
+ * StartScheduler() initiates the MIDI event scheduling thread
+ * within an MuPlayer. Once successfully started, the scheduler
+ * will keep looking for pending events on every active queue
+ * untill it is stopped or paused, or the Player is destroyed.
+ *
+ * @return
+ * bool: StartScheduler() returns false if it cannot start
+ * the scheduler thread and true otherwise
+ *
+ **/
+ bool StartScheduler(void);
+
+ /**
+ * @brief
+ * scheduler thread function: gets data from queues and sends to MIDI
+ * system at the appropriate time
+ *
+ * @details
+ *
+ * ScheduleEvents() is the scheduler thread function. It is started
+ * from StartScheduler() and runs continuously until the Player is
+ * stopped. Before starting its main loop, ScheduleEvents() checks
+ * if the pause flag is set by the Player, in which case it will
+ * just idle until the next turn. If the player is not paused,
+ * it goes through each queue in the pool, checking if they are active.
+ * For any active queues, ScheduleEvents() will look for the next event
+ * in the queue, check its timestamp, and send it to the MIDI system
+ * if the stamp is expired.
+ *
+ * @param
+ * pool (void *): pointer to the plyback pool; as the scheduler thread
+ * function is static, it needs to be passed the playback pool to allow
+ * access to the event queues. This void pointer needs to be cast to
+ * (EventQueue *).
+ *
+ * @return
+ * void *: ScheduleEvents() terminates when the tread exits
+ *
+ **/
+ static void * ScheduleEvents(void * pool);
+
+ /**
+ * @brief sends MIDI messages to the MIDI System
+ *
+ * @details
+ *
+ * SendMIDIMessage() is called by ScheduleEvents()to deliver
+ * a single MIDI message at a time to its destination.
+ * The time stamp within 'msg' is always ignored.
+ * SendMIDIMessage() always delivers every message immediately.
+ * Keeping track of time between events is done by calling code.
+ *
+ * @note
+ *
+ * This method should NOT be called directly by client code. It is
+ * meant to be called by MuPlayer internal code running on another
+ * thread.
+ *
+ *
+ * @param
+ * msg (MuMIDIMessage) - MIDI event to be delivered
+ *
+ * @param
+ * outPort (MIDIPortRef) - MIDI Output Port associated with the
+ * Playback Manager's MIDI Client (CoreMIDI)
+ *
+ * @param
+ * dest (MIDIEndpointRef) - Destination Endpoint selected by the
+ * Playback Manager from the lst of available destinations in the
+ * MIDI system (CoreMIDI)
+ *
+ * @return
+ * void
+ *
+ **/
+ static void SendMIDIMessage(MuMIDIMessage msg, MIDIPortRef outPort, MIDIEndpointRef dest);
+
+ /**
+ * @brief pauses playback for all active queues in the playback pool
+ *
+ * @details
+ *
+ * Pause() can be used to pause and resume playback for all event queues
+ * controlled by the Player. Its single argument defines which
+ * action will take place after the call. If 'T_F' contains 'true', the
+ * Player pauses all queues. If it contains 'false', playback is resumed
+ * in all queues.
+ *
+ * @param
+ * T_F (bool) - true == pause, false == resume
+ *
+ * @return
+ * void
+ *
+ **/
+ void Pause(bool T_F);
+
+ /**
+ * @brief stops all playback and cancels all event queues
+ *
+ * @details
+ *
+ * Stop() can be used to stop playback. When Stop() is called,
+ * all event queues are deactivated. It is not possible to resume
+ * previously scheduled playback once the Player issues a
+ * stop command. it is possible, however to make new requests, as
+ * long as the player is not Reset().
+ *
+ * @return
+ * void
+ *
+ **/
+ void Stop(void);
+};
+
+#endif /* MU_PLAYER_H */
diff --git a/MuRecoder.cpp b/MuRecoder.cpp
new file mode 100644
index 0000000..6f5c7a0
--- /dev/null
+++ b/MuRecoder.cpp
@@ -0,0 +1,290 @@
+//
+// MuRecoder.cpp
+// MuMRT
+//
+// Created by Carlos Eduardo Mello on 3/4/19.
+// Copyright © 2019 Carlos Eduardo Mello. All rights reserved.
+//
+
+#include "MuRecorder.h"
+
+MuRecorder::MuRecorder(void)
+{
+ midiClient = 0;
+ midiInPort = 0;
+ midiSource = 0;
+
+ buff1.data = NULL;
+ buff1.max = 0;
+ buff1.count = 0;
+
+ buff2.data = NULL;
+ buff2.max = 0;
+ buff2.count = 0;
+
+ currentBuffer = NULL;
+
+ initialStamp = 0;
+}
+
+MuRecorder::~MuRecorder(void)
+{
+ if(midiInPort != 0 && midiSource != 0)
+ MIDIPortDisconnectSource(midiInPort,midiSource);
+
+ if(buff1.data != NULL)
+ delete [] buff1.data;
+
+ if(buff2.data != NULL)
+ delete [] buff2.data;
+}
+
+bool MuRecorder::Init(long buffSize = DEFAULT_BUFFER_SIZE)
+{
+ // remember when the Recorder started to run...
+ initialStamp = ClockStamp();
+
+ // ALLOCATE MIDI BUFFERS...
+ if(buff1.data == NULL)
+ {
+ buff1.data = new MuMIDIMessage[buffSize];
+ if(buff1.data != NULL)
+ {
+ buff1.max = buffSize;
+ buff1.count = 0;
+ }
+ }
+
+ if(buff2.data == NULL)
+ {
+ buff2.data = new MuMIDIMessage[buffSize];
+ if(buff2.data != NULL)
+ {
+ buff2.max = buffSize;
+ buff2.count = 0;
+ }
+ }
+
+ currentBuffer = &buff1;
+
+ // INITIALIZE MIDI PORTS...
+ long n,i;
+ OSStatus err = noErr;
+
+ // create Client...
+ if(midiClient == 0)
+ {
+ err = MIDIClientCreate(CFSTR("MuM Recorder"), NULL, NULL, &midiClient);
+ if(err == noErr)
+ {
+ // Create Input Port...
+ err = MIDIInputPortCreate(midiClient, CFSTR("MuM Input"), MIDIInputCallback, this, &midiInPort); if(err == noErr)
+ {
+ // Count Available MIDI Sources...
+ n = MIDIGetNumberOfSources();
+ if(n != 0)
+ {
+ CFStringRef name;
+ char cname[64];
+ MIDIEndpointRef source;
+
+ // List Possible Sources...
+ for(i = 0; i < n; i++)
+ {
+ source = MIDIGetSource(i);
+ if (source != 0)
+ {
+ MIDIObjectGetStringProperty(source, kMIDIPropertyName, &name);
+ CFStringGetCString(name, cname, sizeof(cname), 0);
+ CFRelease(name);
+ cout << "[Source " << i << "]: " << cname << endl << endl;
+ }
+ }
+
+ // Choose the first MIDI source to get input from...
+ midiSource = MIDIGetSource(0);
+ OSStatus result;
+ result = MIDIPortConnectSource(midiInPort, midiSource, NULL);
+ if(result == noErr)
+ return true;
+ }
+ else
+ {
+ cout << "No MIDI destinations present!\n" << endl;
+ }
+ }
+ else
+ {
+ cout << "Failed to open output port!\n" << endl;
+ }
+ }
+ else
+ {
+ cout << "Failed to create MIDI client!\n" << endl;
+ }
+ }
+ else
+ {
+ cout << "Client already initialized! (call reset MIDI)\n" << endl;
+ }
+ return false;
+}
+
+bool MuRecorder::SelectMIDISource(int sourceNumber)
+{
+ OSStatus result;
+
+ if(midiSource != 0)
+ {
+ result = MIDIPortDisconnectSource(midiInPort,midiSource);
+ if(result != noErr)
+ cout << "Couldn't disconnect from previously selected source..." << endl;
+ }
+
+ if(sourceNumber > 0)
+ {
+ midiSource = MIDIGetSource(sourceNumber);
+ if (midiSource != 0)
+ {
+ result = MIDIPortConnectSource(midiInPort, midiSource, NULL);
+ if(result == noErr)
+ return true;
+ else
+ cout << endl << "MIDI Source Connection Failed!" << endl;
+ }
+ }
+
+ return false;
+}
+
+void MuRecorder::ToggleCurrentBuffer(void)
+{
+ if (currentBuffer == &buff1)
+ currentBuffer = &buff2;
+ else
+ currentBuffer = &buff1;
+}
+
+MuMIDIBuffer MuRecorder::GetData(void)
+{
+ MuMIDIBuffer outBuffer;
+ outBuffer.data = NULL;
+ outBuffer.max = 0;
+ outBuffer.count = 0;
+ MuMIDIBuffer * previous;
+
+ // Keep the address of where the data is...
+ previous = currentBuffer;
+
+ // redirect input to the other buffer...
+ ToggleCurrentBuffer();
+
+ // allocate memory to copy data...
+ long i;
+ long n = previous->count;
+ if(n > 0)
+ {
+ outBuffer.data = new MuMIDIMessage[n];
+ if(outBuffer.data != NULL)
+ {
+ for(i = 0; i < n; i++)
+ outBuffer.data[i] = previous->data[i];
+ outBuffer.max = n;
+ outBuffer.count = n;
+
+ // flush previous buffer, so we don't run out of space
+ previous->count = 0;
+ }
+ }
+ return outBuffer;
+}
+
+void MuRecorder::MIDIInputCallback (const MIDIPacketList *list, void *procRef,void *srcRef)
+{
+ //cout << "MIDIInputCallback was called" << endl;
+
+ unsigned int i;
+ MuRecorder * recorder = (MuRecorder *)procRef;
+ const MIDIPacket *packet = &(list->packet[0]);
+ UInt16 nBytes,j;
+ MuMIDIMessage msg;
+ msg.time = ((ClockStamp() - recorder->initialStamp)/ (float)ONE_SECOND);
+
+ for ( i = 0; i < list->numPackets; i++)
+ {
+ nBytes = packet->length;
+
+ j = 0;
+ while(j < nBytes)
+ {
+ Byte next = packet->data[j];
+ // if this is a note event (on or off)...
+ if( ((next & 0xF0) == 0x90) || ((next & 0xF0) == 0x80))
+ {
+ // extract and store it...
+ msg.status = next;
+ msg.data1 = packet->data[j+1];
+ msg.data2 = packet->data[j+2];
+ recorder->AddMessageToBuffer(msg);
+ j += 3;
+ }
+ else // otherwise just ignore it and move to the next byte...
+ {
+ j++;
+ }
+ }
+
+ // when done with this packet, move to the next...
+ packet = MIDIPacketNext(packet);
+ }
+}
+
+void MuRecorder::AddMessageToBuffer(MuMIDIMessage msg)
+{
+ if(currentBuffer->count < (currentBuffer->max - 1))
+ {
+ currentBuffer->data[currentBuffer->count] = msg;
+ currentBuffer->count++;
+ }
+}
+
+// Obs.:
+// 1) upon return, if .count == 0 or .data == NULL, join operation failed
+// 2) calling code is responsible for releasing buffer memory.
+MuMIDIBuffer MuRecorder::JoinMIDIBuffers(MuMIDIBuffer buff1, MuMIDIBuffer buff2)
+{
+ MuMIDIBuffer res;
+ res.data = NULL;
+ res.max = 0;
+ res.count = 0;
+ long i;
+ long n = buff1.count + buff2.count;
+
+ if(n > 0)
+ {
+ // allocate memory enough to put data from both buffers...
+ MuMIDIMessage * temp = new MuMIDIMessage[n];
+ if(temp != NULL)
+ {
+ // copy data from buff1...
+ if((buff1.data != NULL) && (buff1.count > 0))
+ {
+ for (i = 0; i < buff1.count; i++)
+ temp[i] = buff1.data[i];
+ }
+
+ // copy data from buff2...
+ if((buff2.data != NULL) && (buff2.count > 0))
+ {
+ for(i = 0; i < buff2.count; i++)
+ temp[i+buff1.count] = buff2.data[i];
+ }
+
+ res.data = temp;
+ res.max = n;
+ res.count = n;
+ }
+ }
+
+ return res;
+}
\ No newline at end of file
diff --git a/MuRecorder.h b/MuRecorder.h
new file mode 100644
index 0000000..a4168de
--- /dev/null
+++ b/MuRecorder.h
@@ -0,0 +1,430 @@
+//*********************************************
+//***************** NCM-UnB *******************
+//******** (c) Carlos Eduardo Mello ***********
+//*********************************************
+// This softwre may be freely reproduced,
+// copied, modified, and reused, as long as
+// it retains, in all forms, the above credits.
+//*********************************************
+
+/** @file MuRecorder.h
+ *
+ * @brief MuRecorder Class Interface
+ *
+ * @author Carlos Eduardo Mello
+ * @date 3/3/2019
+ *
+ * @details
+ *
+ * MuRecorder introduces MIDI input to the MuM library.
+ * The class starts an independent thread that constantly
+ * looks for incomming MIDI data an adds it to a pair
+ * of input buffers that the rest of the class can access.
+ * When user code wants to check for available data, it
+ * calls GetData(), which copies available MIDI messages
+ * from one of the buffers, leaving the other one free
+ * to keep receiving other messages. Normally only a
+ * single object of this class needs to be instantiated
+ * within a MuM based application.
+ *
+ * For more details about how to use an MuRecorder object
+ * see the MuRecorder Class Documentation.
+ *
+ **/
+
+#ifndef MuRecoder_H
+#define MuRecoder_H
+
+#include
+#include
+#include "MuUtil.h"
+#include "MuMIDI.h"
+
+/**
+ * @class MuRecorder
+ *
+ * @brief MuRecorder Class
+ *
+ * @details
+ *
+ * INTRO:
+ *
+ * MuRecorder is the class responsible for MIDI input in the
+ * MuM Library. The class listens to system MIDI connections
+ * from devices and applications and stores received MIDI events
+ * in a pair of input buffers. From there, these events can be
+ * retrieved by calling code using MuRecorder's methods.
+ *
+ * INITIALIZATION:
+ *
+ * Before being used, an MuRecorder needs to be
+ * initialized. This initialization creates the necessary
+ * infrastructure for the player to interact with the MIDI
+ * system in the current platform. Initialization is done
+ * with a call to Init(). When using CoreMIDI, Init()
+ * creates a MIDI client and an input port associated with
+ * it. It also verifies the available MIDI sources at the
+ * moment of the call and displays a list of sources in
+ * standard output (std::cout). By default, Init() selects the
+ * first available source, but this choice can be overruled
+ * with a subsequent call to SelectMIDISource(). ResetMIDI()
+ * releases all MIDI resources created by Init(). After this call,
+ * the Recorder needs to be initialized again in order be usable.
+ *
+ * Once MIDI connections are in place, Init() starts a listener thread
+ * which is responsible for actually receiving MIDI events. The listener
+ * pols system resources frequently looking for MIDI messages. When
+ * a message is received, it gets copied to the current input buffer
+ * and stamped with current system time with ClockStamp().
+ *
+ * USING RECORDERS:
+ *
+ * Once initialized, the recorder object will immediately start
+ * listening to incomming events. Each event received from
+ * the system is timestamped and stored in the currently
+ * available buffer.
+ *
+ * Whenever client code needs to get MIDI data it
+ * calls GetData(). GetData() toggles current buffer
+ * redirecting input to the other buffer, while copying data.
+ * This way the listener thread doesn't have to wait to
+ * store new messages. GetData() makes a copy of the
+ * available data, puts it into a MIDI buffer structure
+ * (MuMIDIBuffer) and returnes it to the caller.
+ * It is important to note that these buffer
+ * structures depend on dynamic memory
+ * allocation and that CALLING CODE IS RESPONSIBLE FOR
+ * DEALLOCATING this memory when no longer needed.
+ *
+ * Internally the MIDI buffers returned by GetData() are very simple
+ * structures. They contain three fields: 'data' (which is a pointer
+ * a dynamic array of MIDI message structures, 'max' which is
+ * the number of elements allocated for this array and 'count'
+ * which contains the number of valid elements (elements that are
+ * in use). For more details about the buffer structure see MuMIDIBuffer.
+ * The buffers returned by GetData() can be added into larger buffers
+ * with MuRecorder::JoinMIDIBuffers() so user code can access
+ * all incomming that is continuously stored by the recorder.
+ * This data can also be converted to a music material for use in MuM
+ * with a call to MuMaterial::LoadMIDIBuffer();
+ *
+ * UNDER THE HOOD
+ *
+ * In order to keep working continuously and without generating wrong timestamps,
+ * the listener works in a separate thread. Also it stores the data collected
+ * from MIDI system in two separate, alternating buffers. Whenever GetData()
+ * gets called, the listener stops writing in the current buffer and moves
+ * to the other one. Meanwhile, GetData(), operating in the main thread,
+ * copies data from the first buffer and returns it to calling code. The
+ * two input buffers are pre-allocated when the recorder is initialized,
+ * so that when getting new data, the listener never has to allocate memory,
+ * or move data around. It just copies a MIDI message structure into a new
+ * address in one of the arrays. Switching arrays is also a very cheap
+ * operation (changing a pointer address) which is done by GetData(), so
+ * that, as far as the listener is concerned it is just storing another
+ * message to a given address.
+ *
+ * SAMPLE:
+ *
+ * There are many ways to use an MuRecorder in a MuM Application.
+ * It all depends on how we need to collect input and what we want
+ * to do with the it. For example, the recorder could be called
+ * continuosly in a tight loop to get small blocks of data and
+ * interpret them as they come. Or the app could listen for a
+ * certain amount of time or until a certain event arrives, before
+ * getting the data out. Whatever the strategy, using MuRecorder
+ * usually involves:
+ *
+ * @code {.cpp}
+ *
+ * // instantiating a recorder object
+ * MuPlayer rec;
+ *
+ * // initializing it...
+ * rec.Init();
+ *
+ * // giving it some time to record events...
+ * usleep(ONE_SECOND);
+ *
+ * // getting some data from the recorder...
+ * MuMIDIBuffer buffer = GetData();
+ *
+ * // puting it inside a music material so it can be manipulated...
+ * MuMaterial mat.LoadMIDIBuffer(buffer, MIDI_BUFFER_MODE_PURGE);
+ *
+ * // releasing buffer memory...
+ * if(buffer.data)
+ * delete [] buffer.data;
+ *
+ * @endcode
+ *
+ * @note
+ * Currently, only PLAYBACK_MODE_NORMAL is implemented.
+ *
+ * @warning
+ *
+ * MIDI BUFFERS MUST BE DEALLOCATED BY CALLING CODE to avoid
+ * memory leaks.
+ *
+ **/
+
+class MuRecorder
+{
+ private:
+
+ // DATA...
+ MuMIDIBuffer buff1;
+ MuMIDIBuffer buff2;
+ MuMIDIBuffer * currentBuffer; // points to one of the input buffers
+
+ // MIDI CONNECTIONS...
+ MIDIClientRef midiClient; // MIDI Client (CoreMIDI)
+ MIDIPortRef midiInPort; // Input Port (CoreMIDI)
+ MIDIEndpointRef midiSource; // Source Endpoint (CoreMIDI)
+
+ long initialStamp;
+
+ /**
+ * @brief toggles the current input buffer to be used by MIDI input
+ * callback function
+ *
+ * @details
+ *
+ * MuRecorder uses two alternating input buffers to store incomming MIDI
+ * events. When client code requests data, the MIDI callback has to be
+ * redirected to a different buffer in order to keep running smoothly
+ * and avoid conflicts with the data reading routine. So whenever GetData()
+ * is called, it runs ToggleCurrentBuffer() to point the listener to
+ * the next available buffer.
+ *
+ *
+ * @return
+ * void
+ *
+ **/
+ void ToggleCurrentBuffer(void);
+
+public:
+ // Constructor/Destructor
+
+ /**
+ * @brief Default Constructor
+ *
+ * @details
+ * This constructor sets internal player data fields to reasonable default values
+ *
+ **/
+ MuRecorder(void);
+
+ /**
+ * @brief Destructor
+ *
+ * @details
+ * MuRecorder destructor releases memory from the input buffers and
+ * disconnects the input port MIDI sources.
+ **/
+ ~MuRecorder(void);
+
+ /**
+ * @brief Initializes the MuRecorder MIDI configuration and installs
+ * MIDI callback function
+ *
+ * @details
+ *
+ * Init() is responsible for initializing the MIDI environment
+ * for the Recorder. The CoreMIDI implementation of this method
+ * starts out by creating a MIDI client and an associated MIDI input
+ * port, so the Library can receive MIDI events from the system.
+ * When creating the input port, Init() installs a MIDI input callback
+ * which is later called by the system whenever MIDI data is available
+ * for retrieval. After that, the method requests the list of current
+ * sources to CoreMIDI and displays that list to standard output
+ * (std::cout). Init() always selects the first available source for
+ * input, but this choice can be changed by a subsequent call to
+ * SelectMIDISource(), using one of the source numbers displayed by Init().
+ *
+ * Normally there shouldn't be any problems with initialization, but it
+ * is always safer to check the return value for this method. If Init()
+ * for any reason returns 'false', it means one or more of the CoreMIDI
+ * calls failed, in which case the MuRecorder object should not be used.
+ * Init() will also fail if it cannot allocate memory for the input buffers.
+ *
+ * @param
+ * buffSize (long) - size of input buffers; each buffer will be
+ * 'buffSize' events long.
+ *
+ * @return
+ * bool - true for success, false for error in initializing the MIDI
+ * environment or allocating the input buffers.
+ *
+ **/
+ bool Init(long buffSize);
+
+ /**
+ * @brief Selects a source for MIDI input
+ *
+ * @details
+ *
+ * SelectMIDISurce() takes a source number and stores it
+ * for use by the player, replacing any prior selections.
+ * Valid source numbers are supplied by CoreMIDI and can be
+ * verified with a call to DisplaySources() or by checking
+ * Init()'s console output.
+ *
+ * @param
+ * destNumber (int) - number of the desired MIDI source
+ *
+ * @return
+ * void
+ *
+ **/
+ bool SelectMIDISource(int sourceNumber);
+
+ /**
+ * @brief returns a buffer structure containing the latest input MIDI events
+ *
+ * @details
+ *
+ * GetData() returns recent input data collected by the MIDI input callback
+ * in one of the input buffers. Immediately after starting and before copying
+ * any data, GetData() redirects the current buffer pointer to a different
+ * input buffer, so that the MIDI callback thread can keep doing its job
+ * while data is being copied without any conflicts. Once all data is copied,
+ * the method resets the input buffer count to zero so the callback can use
+ * the buffer from the begining next time around.
+ *
+ *@attention
+ *
+ * Subsequent calls to GetData() will keep toggling from one buffer to
+ * the other, so that it will never be reading from the same place the
+ * Recorder is storing data. This means that once a block of MIDI data
+ * is copied from the input buffer by GetData() it will no longer be
+ * available, as the data will be overwritten by the callback after
+ * the next call to GetData(). Hence client code should store that
+ * data if it intends to reuse it.
+ *
+ * @return
+ *
+ * MuMIDIBuffer - GetData() returns a MIDI buffer structure containing a
+ * pointer to the data copied from the input buffers. The structure also
+ * has a 'max' field with data array size and a 'count' field reporting
+ * the number of elements actually used in the array.
+ * Obs.: the two length fields ('max' and 'count') can be used to populate
+ * only part of a buffer, when necessary. When returned by GetData(), however,
+ * these two fields will always contain the same value. The buffers returned
+ * by GetData() can be appended to a larger buffer with a call to
+ * JoinMIDIBuffers().
+ *
+ **/
+ MuMIDIBuffer GetData(void);
+
+ /**
+ * @brief gets called by MIDI system when there is MIDI data available
+ *
+ * @details
+ *
+ * MIDIInputCallback() is a readProc function that needs to be
+ * provided by client code when ceating a MIDI input port with CoreMIDI.
+ * It gets called directly by the system in a special high priority
+ * thread, providing MuRecorder with the latest received MIDI data.
+ * Data is received in the form of a MIDI packet list, which
+ * needs to be parsed in a specific manner in order to retrieve the
+ * MIDI packets and ultimately the MIDI events inside it.
+ * This entire process is implemented by MuRecorder in this function
+ * (for details on how to parse a MIDIPacketList, please see CoreMIDI
+ * documentation)
+ *
+ * @note
+ *
+ * MIDIInputCallback is a static method of the MuRecorder class but should
+ * not be called by client code.
+ *
+ * @param
+ *
+ * list (MIDIPacketList) - a CoreMIDI struture containing packets of MIDI data.
+ * THis list is parsed by the function to extract MIDI events which are in turn
+ * stored into MuRecorder's input buffers.
+ *
+ * @param
+ *
+ * procRef (void *) - This is a context pointer. It contains a pointer to
+ * the MuRecorder object where the callback was registered. This is needed
+ * to access the input buffers, since the function is static and has no direct
+ * connection to the object.
+ *
+ * @param
+ *
+ * srcRef (void *) - This other pointer points to the MIDI source CoreMIDI
+ * object to which the recorder's input port was conected. It may be used
+ * to retrieve information from that source, if necessary.
+ *
+ **/
+ static void MIDIInputCallback(const MIDIPacketList *list, void *procRef,void *srcRef);
+
+ /**
+ * @brief stores a single MIDI message in the current input buffer
+ *
+ * @details
+ *
+ * AddMessageToBuffer() stores the requested MuMIDIMessage in the next
+ * available position of the current input buffer. If the buffer is
+ * full, AddMessageToBuffer() fails silently. AddMessageToBuffer() is
+ * called by the MIDIInputCallback when it needs to add a new MIDI
+ * event to one of the input buffers.
+ *
+ * @warning
+ *
+ * MIDIInputCallback is a static method of the MuRecorder class but should
+ * NEVER be called directly by client code, as this would completely disrupt
+ * data count and possibly mess up array boundary controls in the input buffers.
+ *
+ * @param
+ *
+ * msg (MuMIDIMessage) - the message being stored by the function. This
+ * structure is provided by the MIDI callback function.
+ *
+ *
+ * @return
+ *
+ * void
+ *
+ **/
+ void AddMessageToBuffer(MuMIDIMessage msg);
+
+ /**
+ * @brief adds two MIDI buffers and returns a larger one with the joined data
+ *
+ * @details
+ *
+ * JoinMIDIBuffers() takes two MuMIDIBuffers as input and returns a larger
+ * buffer containing data from both. The method creates a new buffer and
+ * copies the contents of 'buff1' and then 'buff2' to it.
+ *
+ * @note
+ *
+ * This new larger buffer must be released by calling code when it is no
+ * longer needed.
+ *
+ * @param
+ *
+ * buff1 (MuMIDIBuffer) - first buffer to be added; data from this buffer
+ * goes at the begining of the resulting buffer.
+ *
+ * @param
+ *
+ * buff1 (MuMIDIBuffer) - second buffer to be added; data from this buffer
+ * goes at the end of the resulting buffer;
+ *
+ * @return
+ *
+ * MuMIDIBuffer - the return value is an MuMIDIBuffer structure containing
+ * the joined MIDI data. The memory allocated for the 'data' field in the
+ * returning buffer structure must be released to avoid memory leaks. If
+ * calling code needs a copy of this buffer data, the 'data' field should
+ * be deep copied.
+ *
+ **/
+ static MuMIDIBuffer JoinMIDIBuffers(MuMIDIBuffer buff1, MuMIDIBuffer buff2);
+};
+
+#endif /* MuRecoder_H */
diff --git a/MuUtil.cpp b/MuUtil.cpp
index e94253d..136fd4e 100644
--- a/MuUtil.cpp
+++ b/MuUtil.cpp
@@ -179,3 +179,17 @@ extern void ShowInts( int * array, int n )
cout << array[i] << " ";
cout << endl;
}
+
+// UTILITIES ==================================
+
+extern long ClockStamp(void)
+{
+ timeval tv;
+ gettimeofday(&tv, NULL);
+ return ((tv.tv_sec * ONE_SECOND) + (tv.tv_usec));
+}
+
+extern long TimeToStamp(float secs)
+{
+ return (long)(secs * ONE_SECOND);
+}
diff --git a/MuUtil.h b/MuUtil.h
index 300aa07..dc17991 100644
--- a/MuUtil.h
+++ b/MuUtil.h
@@ -23,6 +23,7 @@
#ifndef _MU_UTIL_H_
#define _MU_UTIL_H_
+#include
#include "MuError.h"
// CONSTANTS
@@ -54,6 +55,9 @@ const short ACC_FAVOR_FLATS = 1;
//!@brief acidentals to use for altered notes: sharps
const short ACC_FAVOR_SHARPS = 2;
+//!@brief One second duration in microseconds
+const long ONE_SECOND = 1000000;
+
// PROTOTYPES
@@ -161,5 +165,44 @@ extern void SortFloats( float * array, int size);
**/
extern void ShowInts( int * array, int size );
+// Utility...
+/**
+ * @brief looks up the current system time and returns it as a
+ * microsecond value
+ *
+ * @details
+ *
+ * ClockStamp() reads the current system time using gettimeofDay()
+ * It then converts the 'timeval' structure returned by the system call
+ * to the corresponding value in microseconds. This method is extern and
+ * can be used at any time by calling code for calculating time offsets
+ * and other usefull utilities.
+ *
+ * @return
+ * unsigned long: the current system time in microseconds (for
+ * information about reference time values type 'man gettimeofday'
+ * at a unix terminal)
+ *
+ **/
+extern long ClockStamp(void);
+
+/**
+ * @brief
+ *
+ * converts input time from seconds to microseconds
+ *
+ * @details
+ *
+ * TimeToStamp() simply returns the time provided in 'secs' to its
+ * corresponding value in microseconds. In otherwords, it multiplies
+ * 'secs' by 1000000. This method is extern and can be used
+ * at any time by calling code for calculating time offsets
+ * and other usefull utilities
+ *
+ * @return
+ * unsigned long: requested time in microseconds
+ *
+ **/
+extern long TimeToStamp(float secs);
#endif