Files
secondo/Algebras/Midi/MidiAlgebra.cpp
2026-01-23 17:03:45 +08:00

5216 lines
127 KiB
C++

/*
----
This file is part of SECONDO.
Copyright (C) 2004, University in Hagen, Department of Computer Science,
Database Systems for New Applications.
SECONDO is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
SECONDO is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with SECONDO; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
----
//paragraph [1] Title: [{\Large \bf \begin {center}] [\end {center}}]
//paragraph [21] table1column: [\begin{quote}\begin{tabular}{l}] [\end{tabular}\end{quote}]
//paragraph [22] table2columns: [\begin{quote}\begin{tabular}{ll}] [\end{tabular}\end{quote}]
//paragraph [23] table3columns: [\begin{quote}\begin{tabular}{lll}] [\end{tabular}\end{quote}]
//paragraph [24] table4columns: [\begin{quote}\begin{tabular}{llll}] [\end{tabular}\end{quote}]
//[--------] [\hline]
//characters [1] verbatim: [$] [$]
//characters [2] formula: [$] [$]
//characters [3] capital: [\textsc{] [}]
//characters [4] teletype: [\texttt{] [}]
//[ae] [\"a]
//[oe] [\"o]
//[ue] [\"u]
//[ss] [{\ss}]
//[<=] [\leq]
//[#] [\neq]
//[tilde] [\verb|~|]
//[->] [$\rightarrow $]
//[TOC] [\tableofcontents]
December 2005, Victor Almeida deleted the deprecated algebra levels
(~executable~, ~descriptive~, and ~hibrid~). Only the executable
level remains. Models are also removed from type constructors.
[TOC]
1 Implementation of the Midi Algebra
This Algebra provides support for the MIDI music format inside SECONDO.
MIDI songs are using the binary interface via BASE64 coding. Several operators
are available to manipulate tracks, search for content and so on. For more
detailed information look inside the header file. A list containing all
operators you can easily find inside the SPEC file. Detailed information
concerning the operators you can find beside the implementation of each
operator.
The class design follows not directly the hierachical structure of a Midi file. Normally you will create a class ~Midi~ storing references to the included tracks. This class ~track~ would store the references to all included events. Later you would design inherited classes ( from ~Event~ ) for ~MidiEvent~, ~MetaEvent~ and ~SystemEvent~. Due to the restrictions of SECONDO you cannot work with references.
The used design is completely different.
The main class is ~Midi~. It is the only persistent class. ~Midi~ includes infomation about all used types. That means that this class stores information about ~tracks~, ~events~ and ~eventData~ inside separated DBArrays. By that were are able to store Midi files into SECONDO.
The also used classes ~Track~ and ~Event~ are only transient. They are not inherited from ~Attribute~. We use them to follow the object-oriented approach. ~Track~ ( for events ) and ~Event~ ( for event data ) includes their own structures for holding data. These structures are filled on the fly e.g. running SECONDO operators.
2 Defines and Includes
*/
#include "Algebra.h"
#include "NestedList.h"
#include "QueryProcessor.h"
#include "StandardTypes.h"
#include "Attribute.h"
#include "Base64.h"
#include "MidiAlgebra.h"
#include "Algebras/FText/FTextAlgebra.h"
#include <string>
#include <cmath>
#include <vector>
#include <fstream>
#include <sstream>
#include "Symbols.h"
using namespace std;
extern NestedList* nl;
extern QueryProcessor *qp;
/*
The using of a namespace avoids the modifier ''static'' in function
declarations.
*/
namespace midialgebra {
/*
2 Implementation of class ~Midi~
*/
/*
2.1 Basic Constructor
This constructor is not used but necessary
*/
Midi::Midi()
{ }
/*
2.1 Standard Constructor I
This one receives a boolean value ~d~ indicating if the Midi is defined and two
values for ~divisionMSB~ and ~divisionLSB~. Note that this constructor cannot
be called without arguments.
*/
Midi::Midi( const bool defined, const unsigned char divisionMSB,
const unsigned char divisionLSB ) :
defined ( defined ),
listOfTracks ( 0 ),
listOfEvents ( 0 ),
eventData ( 0 ),
divisionMSB ( divisionMSB ),
divisionLSB ( divisionLSB )
{
lengthOfHeader = 6;
format = 0;
isDeletable = false;
}
/*
2.1 Standard Constructor II
This one receives a ~Midi~ object ~p~ as argument and creates a ~Midi~ that is
a copy of ~p~.
*/
Midi::Midi(const Midi& midi):
listOfTracks ( 0 ),
listOfEvents ( 0 ),
eventData ( 0 )
{
defined = midi.defined;
divisionMSB = midi.divisionMSB;
divisionLSB = midi.divisionLSB;
lengthOfHeader = midi.lengthOfHeader;
format = midi.format;
isDeletable = midi.isDeletable;
listOfTracks.copyFrom(midi.listOfTracks);
listOfEvents.copyFrom(midi.listOfEvents);
eventData.copyFrom(midi.eventData);
}
/*
2.1 The destructor
*/
Midi::~Midi()
{
if (isDeletable)
{
listOfTracks.destroy();
listOfEvents.destroy();
eventData.destroy();
}
}
/*
2.1 Overloading equals
Overloading the assignment operator.
*/
Midi& Midi::operator=(const Midi& midi)
{
if (this == &midi)
{
return *this;
}
defined = midi.defined;
divisionMSB = midi.divisionMSB;
divisionLSB = midi.divisionLSB;
lengthOfHeader = midi.lengthOfHeader;
format = midi.format;
isDeletable = midi.isDeletable;
listOfTracks.copyFrom(midi.listOfTracks);
listOfEvents.copyFrom(midi.listOfEvents);
eventData.copyFrom(midi.eventData);
return *this;
}
/*
2.1 GetTrack
Returns the by index selected track of this ~Midi~ object.
*/
Track* Midi::GetTrack(int index) const
{
assert( listOfTracks.Size() && index < MAX_TRACKS_MIDI );
Track* track = new Track();
TrackEntry trackEntry;
listOfTracks.Get(index, trackEntry);
int eventPtr = trackEntry.eventPtr;
for (int i = 0; i < trackEntry.noOfEvents; i++)
{
EventEntry auxEventEntry;
listOfEvents.Get(eventPtr++, auxEventEntry);
EventEntry eventEntry( auxEventEntry );
int dataPtr = eventEntry.dataPtr;
vector<unsigned char> deltaTimeBytes;
unsigned char currentByte;
do
{
eventData.Get(dataPtr++, currentByte);
deltaTimeBytes.push_back(currentByte);
eventEntry.size--;
} while ((currentByte & 0x80));
unsigned int deltaTime = Event::ComputeBytesToInt(&deltaTimeBytes);
if (eventEntry.type == shortmessageEntry ||
eventEntry.type == shortmessageRSEntry)
{
Event* event = new Event(shortmessage, deltaTime);
if (eventEntry.type == shortmessageEntry)
{
event->SetShortMessageRunningStatus(false);
unsigned char c;
eventData.Get(dataPtr++, c);
event->SetShortMessageType(c);
eventEntry.size--;
}
else
{
event->SetShortMessageRunningStatus(true);
}
event->SetShortMessageDataLength(eventEntry.size);
for (int j = 0; j < eventEntry.size; j++)
{
unsigned char c;
eventData.Get(dataPtr++, c);
event->SetShortMessageData(j, c);
}
track->Append(event);
}
else
{
Event* event;
if (eventEntry.type == metamessageEntry)
{
event = new Event(metamessage, deltaTime);
}
else
{
event = new Event(sysexmessage, deltaTime);
}
vector<unsigned char> messageData;
for (int j = 0; j < eventEntry.size; j++)
{
unsigned char c;
eventData.Get(dataPtr++, c);
messageData.push_back(c);
}
event->SetMetaData(&messageData);
track->Append(event);
}
}
return track;
}
/*
2.1 Append
Appends the by index selected track of this ~Midi~ object. The caller of this
method is responsible for destroying the passed Track object after using.
*/
void Midi::Append(const Track *inTrack)
{
TrackEntry trackEntry;
trackEntry.noOfEvents = inTrack->GetNumberOfEvents();
trackEntry.eventPtr = listOfEvents.Size();
listOfTracks.Append(trackEntry);
for (int i = 0; i < inTrack->GetNumberOfEvents(); i++)
{
EventEntry eventEntry;
eventEntry.size = 0;
eventEntry.dataPtr = eventData.Size();
const Event* event = inTrack->GetEvent(i);
unsigned int deltaTime = event->GetDeltaTime();
vector<unsigned char> deltaTimeBytes;
Event::ComputeIntToBytes(deltaTime, &deltaTimeBytes);
for (unsigned int j = 0; j < deltaTimeBytes.size(); j++)
{
unsigned char c = deltaTimeBytes[j];
eventData.Append(c);
eventEntry.size++;
}
if (event->GetEventType() == shortmessage)
{
if (event->GetShortMessageRunningStatus())
{
eventEntry.type = shortmessageRSEntry;
}
else
{
eventEntry.type = shortmessageEntry;
unsigned char c = event->GetShortMessageType();
eventData.Append(c);
eventEntry.size++;
}
for (int j = 0; j < event->GetShortMessageDataLength(); j++)
{
unsigned char c = event->GetShortMessageData(j);
eventData.Append(c);
eventEntry.size++;
}
}
else
{
eventEntry.type = event->GetEventType() == metamessage ?
metamessageEntry : sysexmessageEntry;
vector<unsigned char> messageData;
event->GetMetaData(&messageData);
for (unsigned int j = 0; j < messageData.size(); j++)
{
unsigned char c = messageData[j];
eventData.Append(c);
eventEntry.size++;
}
}
listOfEvents.Append(eventEntry);
}
}
/*
2.1 AppendEmptyTrack
Appends an empty track to this ~Midi~ object.
*/
void Midi::AppendEmptyTrack()
{
Track* emptyTrack = new Track();
vector<unsigned char> metaData(14);
metaData[0] = 0xFF;
metaData[1] = Event::TRACK_NAME;
metaData[2] = 11;
metaData[3] = 'E';
metaData[4] = 'm';
metaData[5] = 'p';
metaData[6] = 't';
metaData[7] = 'y';
metaData[8] = ' ';
metaData[9] = 'T';
metaData[10] = 'r';
metaData[11] = 'a';
metaData[12] = 'c';
metaData[13] = 'k';
Event* trackNameEvent = new Event(metamessage, 0);
trackNameEvent->SetMetaData(&metaData);
emptyTrack->Append(trackNameEvent);
metaData.resize(3);
metaData[0] = 0xFF;
metaData[1] = Event::END_OF_TRACK;
metaData[2] = 0;
Event* endOfTrackEvent = new Event(metamessage, 0);
endOfTrackEvent->SetMetaData(&metaData);
emptyTrack->Append(endOfTrackEvent);
Append(emptyTrack);
delete emptyTrack;
}
/*
2.1 GetLyrics
Extract lyrics for operators and concatenats it into a single string
*/
void Midi::GetLyrics(string& result ,bool all, bool lyr, bool any) const
{
int nrTrcks = GetNumberOfTracks();
string eventdata;
int lastType = -1;
int currType;
for (int j = 0; j < nrTrcks; j++)
{
Track* currentTrack = GetTrack(j);
int nrEvts = currentTrack->GetNumberOfEvents();
for ( int k = 0; k < nrEvts; k++)
{
const Event* currentEvent = currentTrack-> GetEvent(k);
if (currentEvent->GetEventType() == metamessage)
{
currType = currentEvent->GetMetaMessageType();
if (( all & (currType > 0x00 ) & (currType<= 0xFF )) ||
( (lyr & currType) == Event::LYRIC ) ||
( (any & currType) == Event::ANY_TEXT ))
{
if(lastType != currType)
{
result += (char)10;
// newline added if kind of text event changes
}
currentEvent-> GetTextFromMetaEvent (result);
lastType = currType;
}
}
}
}
result = Event::FilterString(result);
}
/*
2.1 Implementation of SECONDO`s virtual fucntions
2.1.1 HashValue
Returns a hashvalue for a Midi reducing the complexity to a simple value
*/
size_t Midi::HashValue() const
{
if(!defined)
{
return (0);
}
else
{
double long h=0;
int val = eventData.Size() / 10;
unsigned char ch;
for (int k = 1; k < 10; k++)
{
eventData.Get((val* k),ch);
double z = ch *pow((double)2,(double) k-1);
h += z;
}
return size_t(h);
}
}
/*
2.1.1 CopyFrom
Takes any object of kind Attribute and copies all information from it
*/
void Midi::CopyFrom(const Attribute* right)
{
const Midi* midi = (const Midi*) right;
*this = *midi;
}
/*
2.1.1 Compare
Returns always 0 because two Midis are not comparable concerning $''>'' or ''<''$
*/
int Midi::Compare(const Attribute * arg) const
{
return 0;
}
/*
2.1.1 Adjacent
Returns always false because two Midis cannot be put into an order
*/
bool Midi::Adjacent(const Attribute * arg) const
{
return false;
}
/*
2.1.1 Standard Clone
Returns a copy of a Midi
*/
Midi* Midi::Clone() const
{
return new Midi(*this);
}
/*
2.1.1 Extended Clone
Returns a copy of a Midi without tracks if ~copyTracks~ = false, works like
Clone if ~copyTracks~ = true
*/
Midi* Midi::Clone(const bool copyTracks) const
{
if (copyTracks)
{
return this->Clone();
}
else
{
Midi *newMidi = new Midi( true, 0, 0);
newMidi->defined = this->defined;
newMidi->divisionMSB = this->divisionMSB;
newMidi->divisionLSB = this->divisionLSB;
newMidi->format = this->format;
newMidi->isDeletable = this->isDeletable;
newMidi->lengthOfHeader = this->lengthOfHeader;
return newMidi;
}
}
/*
2.1.1 Print
Returns a string including the description ''midi Algebra''
*/
ostream& Midi::Print( ostream &os ) const
{
return os << Midi::BasicType() << " Algebra" << endl;
}
/*
2.1.1 NumOfFLOBs
Returns the number of used DBArrays for Midi
*/
int Midi::NumOfFLOBs() const
{
return 3;
}
/*
2.1.1 GetFLOB
Returns the address of the required DBArray. Calling this method for the address of tracks you need to put in a 0, a 1 for events`s address and a 2 for eventData`s address.
*/
Flob* Midi::GetFLOB(const int i)
{
assert(i >= 0 && i < NumOfFLOBs());
switch (i)
{
case 0:
return &listOfTracks;
case 1:
return &listOfEvents;
case 2:
return &eventData;
}
assert(false); // may not happen
}
/*
2.1.1 Sizeof
Returns the size of a class instance.
*/
size_t Midi::Sizeof() const
{
return sizeof(*this);
}
/*
2.2 Destroy
Sets the attribute ~isDeletable~ to true for further destroying by SECONDO
*/
void Midi::Destroy()
{
isDeletable = true;
}
/*
2.1 Get methods for private attributes
They return the content of an attribute or calculate the selected value on the fly
*/
bool Midi::IsDefined() const
{
return defined;
}
const int Midi::GetNumberOfTracks () const
{
return listOfTracks.Size();
}
const int Midi::GetNumberOfTicks () const
{
return divisionMSB * 256 + divisionLSB;
}
const int Midi::GetFormat () const
{
return format;
}
const int Midi::GetHeaderLength () const
{
return lengthOfHeader;
}
const string Midi::GetHeader () const
{
return MIDI_HEADER;
}
const int Midi::GetFileSize() const
{
return 14 + listOfTracks.Size() * 8 + eventData.Size();
}
unsigned char Midi::GetDivisionMSB() const
{
return divisionMSB;
}
unsigned char Midi::GetDivisionLSB() const
{
return divisionLSB;
}
bool Midi::IsDivisionInFramesFormat() const
{
return divisionMSB & 0x80;
}
/*
2.2 Set methods for private attributes
They set the attribute to the specified value
*/
void Midi::SetDefined( bool Defined)
{
this->defined = Defined;
}
void Midi::SetFormat (const int inFormat)
{
assert( inFormat >= MIN_FORMAT_MIDI && inFormat <= MAX_FORMAT_MIDI );
this->format = inFormat;
}
void Midi::SetHeaderLength (const int inLength)
{
this->lengthOfHeader = inLength;
}
void Midi::SetDivisionLSB(const unsigned char lsb)
{
this-> divisionLSB = lsb;
}
void Midi::SetDivisionMSB(const unsigned char msb)
{
this-> divisionMSB = msb;
}
/*
2.2 List Representation
The list representation of a ~Midi~ are
---- ( <file>filename.mid</file---> )
----
and
---- ( <text><Base64 decoded file content></text---> )
----
If first representation is used, then the contents of a file is read into the
second representation. This is done automatically by the Secondo parser.
2.3 ~Out~-Function
*/
/*
First some utility functions
*/
void computeShortIntToBytes(int arg, unsigned char& msb,
unsigned char& lsb)
/*
Computes the msb and lsb of an integer in the range 0 - 65535.
*/
{
lsb = arg % 256;
msb = (arg - lsb) / 256;
}
void computeIntToBytes(unsigned int arg, unsigned char& byte3,
unsigned char& byte2, unsigned char& byte1,
unsigned char& byte0)
/*
Separates the unsigned integer arg into four bytes. The most significant byte of
the reuslt is byte3, the least significant byte is byte0.
*/
{
unsigned int rest;
rest = arg % 16777216; // 256 ^ 3 = 16777216
byte3 = (arg - rest) / 16777216;
arg -= byte3 * 16777216;
rest = arg % 65536; // 256 ^ 2 = 65536
byte2 = (arg - rest ) / 65536;
arg -= byte2 * 65536;
byte0 = arg % 256;
byte1 = (arg - byte0) / 256;
}
void midiToBytes(Midi* midi, vector<unsigned char>& resultVector)
{
/*
This functions returns a vector which contains the Midi file its bytes
representation of the passed Midi object. The result vector can be empty, it
will be resized by this function.
*/
resultVector.resize(14);
resultVector[0] = 0x4D;
resultVector[1] = 0x54;
resultVector[2] = 0x68;
resultVector[3] = 0x64;
resultVector[4] = 0;
resultVector[5] = 0;
resultVector[6] = 0;
resultVector[7] = 6;
resultVector[8] = 0;
resultVector[9] = midi->GetFormat();
int noOfTracks = midi->GetNumberOfTracks();
unsigned char lsbTracks;
unsigned char msbTracks;
computeShortIntToBytes(noOfTracks, msbTracks, lsbTracks);
resultVector[10] = msbTracks;
resultVector[11] = lsbTracks;
int noOfTicks = midi->GetNumberOfTicks();
unsigned char lsbTicks;
unsigned char msbTicks;
computeShortIntToBytes(noOfTicks, msbTicks, lsbTicks);
resultVector[12] = msbTicks;
resultVector[13] = lsbTicks;
/*
sets file header
*/
for (int i = 0; i < noOfTracks; i++)
{
Track* track = midi->GetTrack(i);
resultVector.push_back(0x4D);
resultVector.push_back(0x54);
resultVector.push_back(0x72);
resultVector.push_back(0x6B);
unsigned int trackSize = 0;
unsigned int trackSizePtr = resultVector.size();
resultVector.push_back(0);
resultVector.push_back(0);
resultVector.push_back(0);
resultVector.push_back(0);
/*
sets track header
*/
for (int j = 0; j < track->GetNumberOfEvents(); j++)
{
// write delta time of event
const Event* event = track->GetEvent(j);
vector<unsigned char> deltaTimeBytes;
Event::ComputeIntToBytes(event->GetDeltaTime(), &deltaTimeBytes);
for (unsigned int k = 0; k < deltaTimeBytes.size(); k++)
{
resultVector.push_back(deltaTimeBytes[k]);
trackSize++;
}
// write event data
if (event->GetEventType() == shortmessage)
{
if (!event->GetShortMessageRunningStatus())
{
resultVector.push_back(event->GetShortMessageType());
trackSize++;
}
for (int l = 0; l < event->GetShortMessageDataLength(); l++)
{
resultVector.push_back(event->GetShortMessageData(l));
trackSize++;
}
} else if (event->GetEventType() == metamessage) {
vector<unsigned char> data;
event->GetMetaData(&data);
for (unsigned int l = 0; l < data.size(); l++)
{
resultVector.push_back(data[l]);
trackSize++;
}
} else {
vector<unsigned char> data;
event->GetSysexData(&data);
for (unsigned int l = 0; l < data.size(); l++)
{
resultVector.push_back(data[l]);
trackSize++;
}
}
}
unsigned char byte0, byte1, byte2, byte3;
computeIntToBytes(trackSize, byte3, byte2, byte1, byte0);
resultVector[trackSizePtr++] = byte3;
resultVector[trackSizePtr++] = byte2;
resultVector[trackSizePtr++] = byte1;
resultVector[trackSizePtr++] = byte0;
delete track;
}
}
ListExpr OutMidi( ListExpr typeInfo, Word value )
/*
The real Out-function. Encodes the internal representation into Base64 format.
*/
{
Midi* midi = (Midi*) value.addr;
vector<unsigned char> byteVector;
midiToBytes(midi, byteVector);
char* bytes = new char[byteVector.size()];
for (unsigned int i = 0; i < byteVector.size(); i++)
{
bytes[i] = byteVector[i];
}
Base64 b;
string textBytes;
b.encode(bytes, byteVector.size(), textBytes);
delete[] bytes;
ListExpr textAtom = nl->TextAtom();
nl->AppendText(textAtom, textBytes);
ListExpr result = nl->ThreeElemList(textAtom,
nl->IntAtom(midi->GetFileSize()),
nl->IntAtom(midi->GetNumberOfTracks()));
return result;
}
/*
2.1 Utility functions used by infunction
The interaction between these functions works as follows:
The function ~InMidi~ constructs a Midi object with zero ticks and the
standard header. It takes the coded text representing the whole Midi file
and transforms it into a c++ string. Then it calls the ~InHeader~ function
with both Midi and string as parameters.
~InHeader~ puts the string into an stringstream and reads out all header
informations of the Midi file. Syntactical correctness is tested and
then all header informations are put in the Midi object.
Then ~InTracks~ is called with the Midi ( in construction ), the stream
( without header) and the number of tracks as parameters. ~InTracks~
realizes a loop over the number of tracks , counts the bytelength of
every track and reads the bytes according to one track out of the stream.
The track header information are read, checked and a Track object is
constructed and filled with Events ( which are in the current streampart )
by the ~InEvents~ function.
~InEvents~ reads out the stream event by event as long as the tracklength
allows. At first deltatime and the event type characterizing bytes are
read and than the corresponding Event object is constructed. For every
Event type there is a function ( ~InMetaEvent~, ~InSystemmessage~, ...) which
checks the syntax of this Event ( also some context checks are done ) and the
information of these events are put into corresponding structures. Then the
events are appended to the current track.
Given back this track to ~InTracks~ the
information of the whole track are put into the Midi object structures
( DBArrays ) by function ~AppendTrack~. Because these structures are
independend from track objects the track objects have to be deleted after
appending them.
Note that all deviation from Midi Standard found out in syntax or context
checks causes error messages and avoid constructing a Midi object. This
can lead to the situation that a Midi is playable in usual Midiplayer
program but not loadable into SECONDO. This decision follows our
intention concerning consistence and uniqueness of database objects.
*/
bool InSystemmessage(Event*& actualEvent,stringstream& byteStream,
unsigned char& typebyte, unsigned char& namebyte,
bool& multisysex, bool& sequenceSpecified,
bool& runmodepossible, int tracknr)
{
char ch;
if (multisysex)
{
cout << "Error in track "<< tracknr << "." << endl;
cout << "Sysexpackage interrupted | closed by F7 byte. ";
cout << "Last 2 read bytes are: " << (unsigned int)typebyte;
cout << ", " << (unsigned int)namebyte;
return false;
}
else
{
runmodepossible = false;
sequenceSpecified = true;
actualEvent->SetShortMessageType(typebyte);
if(typebyte == Event::SONG_POSITION_POINTER)
{
actualEvent->SetShortMessageDataLength(2);
actualEvent->SetShortMessageData(0,namebyte);
byteStream.get(ch);
actualEvent->SetShortMessageData(1, (unsigned char)ch);
}
if(typebyte == Event::SYSTEM_RESET)
{
actualEvent->SetShortMessageDataLength(1);
actualEvent->SetShortMessageData(0, namebyte);
}
if((typebyte == Event::TUNE_REQUEST) ||
(typebyte == Event::TIMING_CLOCK) ||
(typebyte == Event::START) ||
(typebyte == Event::CONTINUE) ||
(typebyte == Event::ACTIVE_SENSING))
{
actualEvent-> SetShortMessageDataLength(0);
byteStream.putback(namebyte);
}
return true;
}
}
bool InChannelVoiceRun(Event*& actualEvent,stringstream& byteStream,
unsigned char& typebyte, unsigned char& namebyte,
bool& multisysex, bool& sequenceSpecified,
bool& runmodepossible, int tracknr)
{
int datalength=2; // ???
if (!runmodepossible)
{
cout << "Error in track " << tracknr << "." << endl;
cout << "Error runmode event without ";
cout << "previuos channelmessage " ;
cout << (unsigned int)typebyte << "," << (unsigned int)namebyte;
return false;
}
if(multisysex)
{
cout << "Error in track " << tracknr << "." << endl;
cout << "Sysexpackage interupted or not closed ";
cout << "by F7 byte. Last two read bytes are: ";
cout << (unsigned int)typebyte << "," << (unsigned int)namebyte;
return false;
}
sequenceSpecified = true;
actualEvent->SetShortMessageRunningStatus(true);
if (datalength == 1 )
{
actualEvent-> SetShortMessageDataLength(1);
actualEvent-> SetShortMessageData(0, typebyte);
byteStream.putback(namebyte);
}
else
{
actualEvent-> SetShortMessageDataLength(2);
actualEvent-> SetShortMessageData(0, typebyte);
actualEvent-> SetShortMessageData(1, namebyte);
}
return true;
}
bool InChannelVoiceNoRun(Event *& actualEvent,stringstream& byteStream,
unsigned char& typebyte, unsigned char& namebyte,
bool& multisysex, bool& sequenceSpecified,
bool& runmodepossible, int tracknr)
{
//int datalength;
char ch;
if (multisysex)
{
cout << "Error in track " << tracknr << "." << endl;
cout << "Sysexpackage interrupted or closed by F7 byte. ";
cout << "Last two read bytes are: " << (unsigned int)typebyte;
cout << "," << (unsigned int)namebyte;
return false;
}
else
{
runmodepossible = true;
sequenceSpecified = true;
actualEvent->SetShortMessageType(typebyte);
actualEvent->SetShortMessageRunningStatus(false);
if((typebyte & 0xF0) == Event::PROGRAM_CHANGE ||
(typebyte & 0xF0) == Event::CHANNEL_PRESSURE)
{
//datalength = 1;
actualEvent->SetShortMessageDataLength(1);
actualEvent->SetShortMessageData(0, namebyte);
}
else
{
//datalength = 2;
actualEvent-> SetShortMessageDataLength(2);
actualEvent-> SetShortMessageData(0, namebyte);
byteStream.get(ch);
actualEvent->SetShortMessageData(1, (unsigned char)ch);
}
return true;
}
}
bool InSysex(Event*& actualEvent, stringstream& byteStream,
vector<unsigned char>*& data, unsigned char& typebyte,
unsigned char& secbyte, bool& multisysex,
bool& sequenceSpecified, bool& runmodepossible, int tracknr)
{
vector<unsigned char> * eventLengthVector = new vector<unsigned char>;
char ch;
if(typebyte == 0xF0)
{
if (multisysex)
{
cout << "Error in track " << tracknr << "." << endl;
cout << "Sysexpackage interrupted or not closed by F7 byte. " ;
cout << "Last two read bytes are: " << (unsigned int)typebyte;
cout << "," << (unsigned int)secbyte;
return false;
}
else
{
multisysex = true;
}
}
if(typebyte == 0xF7)
{
if (!multisysex)
{
cout << "Error in track " << tracknr << endl;
cout << "sysexpackage interrupted or not closed by F7 byte. " ;
cout << "Last two read bytes are: "<< (unsigned int)typebyte;
cout << "," << (unsigned int)secbyte;
return false;
}
}
runmodepossible = false;
data->push_back(typebyte);
data->push_back(secbyte);
int bytecounter = 1;
eventLengthVector->push_back(secbyte);
if (secbyte > 128 )
{
do
{
byteStream.get(ch);
eventLengthVector->push_back((unsigned char)ch);
data->push_back((unsigned char)ch);
bytecounter ++;
} while ((unsigned char)ch > 128 && bytecounter <= 5);
}
if ( bytecounter > 4 )
{
cout << "Error in track " << tracknr << "." << endl;
cout << "Eventlength longer than 4 bytes. ";
cout << "Last two read bytes are: " << (unsigned int)typebyte;
cout << "," << (unsigned int)secbyte;
return false;
}
else
{
int eventLength = Event::ComputeBytesToInt(eventLengthVector);
delete eventLengthVector;
int j = 0;
while (j < eventLength)
{
byteStream.get(ch);
data->push_back((unsigned char)ch);
j++;
}
typebyte = (unsigned char)ch;
if(typebyte != 0xF7)
{
multisysex &= true;
}
else
{
multisysex =false;
}
sequenceSpecified = true;
return true;
}
}
bool InSequenceNumberEvent(stringstream& byteStream,
vector<unsigned char>* & data,
bool& deltagreaterO, bool& sequenceSpecified,
int tracknr, unsigned char cha,
unsigned char chb)
{
char ch;
byteStream.get(ch); // first byte of length
if ((!sequenceSpecified) && (!deltagreaterO))
{
if ((unsigned int)ch == 0x02)
{ // valid length, get data of meta event
data->push_back(ch);
for( int j = 1; j < 3; j++)
{
byteStream.get(ch);
data->push_back((unsigned int)ch);
}
}
else
{
cout << "Error in track " << tracknr << "." << endl;
cout << "False length of sequence event." << endl ;
cout << "Last two read bytes are: " << (unsigned int)cha;
cout << "," << (unsigned int)chb;
return false;
}
}
else
{
cout << "Error in track " << tracknr << "." << endl;
cout << "Sequence event should be before any delta time > 0 ";
cout << "and any other seq specifying event. ";
cout << "Last two read bytes are: " << (unsigned int)cha;
cout << "," << (unsigned int)chb << endl;
return false;
}
return true;
}
bool InMetaEvent(Event*& actualEvent,stringstream& byteStream,
vector<unsigned char>*& data,
unsigned char& typebyte, unsigned char& namebyte,
bool& multisysex, bool& deltagreaterO,
bool& sequenceSpecified, bool& endOfTrackFound,
bool& runmodepossible, int tracknr)
{
vector<unsigned char>* eventLengthVector = new vector<unsigned char>;
char ch;
if(multisysex)
{
cout << "Error in track " << tracknr << "." << endl;
cout << "Sysexpackage interrupted or not closed by F7 byte.";
cout << endl << "Last two read bytes are: ";
cout << (unsigned int)typebyte << "," << (unsigned int) namebyte;
return false;
}
runmodepossible = false;
data->push_back(typebyte);
data->push_back(namebyte);
if( namebyte == 0x00) // sequenceNumberEvent found
{
if( !( InSequenceNumberEvent( byteStream, data,
deltagreaterO, sequenceSpecified,
tracknr, typebyte, namebyte)))
{
return false;
}
}
else
{
int bytecounter = 0;
do // counting of eventlength
{
byteStream.get(ch);
eventLengthVector->push_back((unsigned char)ch);
data->push_back((unsigned char) ch);
bytecounter ++;
} while ( (unsigned char)ch > 128 && bytecounter <= 5) ;
if (bytecounter > 4)
{
cout << "Error in track " << tracknr << "." << endl;
cout << "Event length > 4 bytes detected. ";
cout << "Last two read bytes are: ";
cout << (unsigned int)typebyte << ",";
cout << (unsigned int)namebyte;
return false;
}
int eventLength = Event::ComputeBytesToInt(eventLengthVector);
delete eventLengthVector;
if (namebyte == Event::END_OF_TRACK)
{
endOfTrackFound = true;
byteStream.get(ch);
}
for( int j = 0; j < eventLength; j++ )
//put message in vektor
{
byteStream.get(ch);
data->push_back((unsigned char)ch);
}
}
sequenceSpecified = true;
return true;
}
bool InEvents(Track* actualTrack, int nob, char* bytes, int tracknr )
{
stringstream byteStream;
byteStream.write(bytes,nob);
char ch;
unsigned char cha;
unsigned char chb;
int deltatime;
bool sequenceSpecified = false;
bool multisysex = false;
bool deltagreaterO = false;
bool runmodepossible = false;
bool endOfTrackFound = false;
int eventcount = 0;
vector<unsigned char>* variableLength =
new vector<unsigned char>;
vector<unsigned char>* eventLengthVector =
new vector<unsigned char>;
vector<unsigned char>* data = new vector<unsigned char>;
while (byteStream.good())
{
eventcount ++;
if(endOfTrackFound)
{
cout <<" left bytes after EndofTrack message at last Track";
return false;
}
int bytecounter = 0;
do
{
// counting of deltatime
byteStream.get(ch);
variableLength->push_back((unsigned int)ch);
bytecounter ++;
} while (((unsigned int) ch > 128) && (bytecounter < 6));
if (bytecounter > 4)
{
cout << endl << "Error in Track " << tracknr << "." << endl;
cout << "Deltatime > 4 bytes detected." << endl;
cout << "Last read bytes are : fortype : " << (unsigned int) cha;
cout << " forname: " << (unsigned int)chb;
return false;
}
deltatime = Event:: ComputeBytesToInt(variableLength);
deltagreaterO |= ( deltatime >0 );
byteStream.get(ch);
cha = (unsigned char)ch; // type of event
byteStream.get(ch);
chb = (unsigned char)ch; // name of event
if(cha == 0xFF) // metaEvent found
{
Event* actualEvent= new Event(metamessage, deltatime);
if(InMetaEvent(actualEvent, byteStream, data, cha, chb,
multisysex, deltagreaterO,
sequenceSpecified,endOfTrackFound,
runmodepossible,tracknr))
{
actualEvent->SetMetaData(data);
actualTrack->Append(actualEvent);
data->clear();
}
else
{
return false;
}
}
else // end of metamessage
{
if(( cha == 0xF0 ) || (cha == 0xF7)) // sysEx message found
{
Event* actualEvent= new Event(sysexmessage, deltatime);
if( InSysex(actualEvent, byteStream, data, cha, chb,
multisysex, sequenceSpecified,
runmodepossible, tracknr))
{
actualEvent->SetSysexData(data);
actualTrack->Append(actualEvent);
data->clear();
}
else
{
return false;
}
}
else // end of sysEx message
{
if (((cha & 0xF0) >= 0x80) & ((cha & 0xF0) <= 0xE0))
{
Event* actualEvent = new Event(shortmessage, deltatime);
if( InChannelVoiceNoRun( actualEvent, byteStream,
cha, chb, multisysex,
sequenceSpecified,
runmodepossible, tracknr))
{
actualTrack->Append(actualEvent);
}
else
{
return false;
}
}
else // end of channelshortmessage - no runmode
{
if( cha < 128 ) // running mode found
{
Event* actualEvent = new Event(shortmessage, deltatime);
if( InChannelVoiceRun( actualEvent,byteStream,
cha, chb, multisysex,
sequenceSpecified,
runmodepossible,tracknr))
{
actualTrack->Append(actualEvent);
}
else
{
return false;
}
}
else // end of running mode
{
if (((cha & 0xF0) == 0xF0) & // sysEx message found
((cha & 0x0F) != 0x0F))
{
Event* actualEvent = new Event(shortmessage, deltatime);
if ( InSystemmessage(actualEvent, byteStream,cha, chb,
multisysex, sequenceSpecified,
runmodepossible, tracknr))
{
actualTrack->Append(actualEvent);
}
else
{
return false;
}
}
else
{
cout << "error in track " << tracknr << ":";
cout << endl << "Unknown event found. ";
cout << "Last two bytes are: " << cha;
cout << "," << chb;
return false;
}
}
}
}
}
data->clear();
eventLengthVector->clear();
variableLength->clear();
}
if (!endOfTrackFound)
{
cout <<" endoftrack message missing after track " << tracknr;
return false;
}
if (multisysex)
{
cout << "sysexpackage not closed in track " << tracknr;
return false;
}
return true;
}
bool InTracks(Midi*& actualMidi ,stringstream& byteStream,
int nrtracks)
{
char* longw = new char[5];
unsigned int tracklen;
for (int k = 1; k <= nrtracks; k++)
{
byteStream.read(longw, 4);
longw[4] = '\0';
if(((string(longw).find(TRACK_HEADER))) == 0 )
// track header found
{
int exp = 3;
tracklen = 0;
for ( int j = 0; j <= 3; j++)
// count bytelength of track
{
byteStream.get(longw[j]);
tracklen += (unsigned char)longw[j] *
((unsigned int) pow((double)256, exp));
exp--;
}
char* evsOfTrack = new char[tracklen];
byteStream.read(evsOfTrack, tracklen);
Track* actualTrack = new Track() ;
if(!(InEvents(actualTrack, tracklen,evsOfTrack,k)))
{
return false;
}
else
{
delete[] evsOfTrack;
actualMidi->Append(actualTrack);
delete actualTrack;
}
}
else
{
cout << "Error: " << TRACK_HEADER << " missing after Track ";
cout << (k-1);
return false;
}
}
char ch;
byteStream.get(ch);
if(byteStream.good())
{
cout << "bytes left after last track";
return false;
}
else
{
return true;
}
}
bool InHeader (Midi*& actualmidi, string& b64s)
{
Base64 b;
char* longw = new char[5];
char* shortw = new char[2];
unsigned int nrtracks;
int frmt;
int headerlen = 0;
stringstream byteStream;
int sizeDecoded = b.sizeDecoded( b64s.size() );
char *bytes = new char[ sizeDecoded ];
int bytelength = b.decode(b64s, bytes);
byteStream.write( bytes, bytelength);
delete[] bytes;
byteStream.read(longw,4);
longw[4] = '\0';
if ( string(longw).find(MIDI_HEADER) == 0 ) // Midi header found
{
int exp = 3;
for (int j = 0; j <= 3 ;j++)
{
byteStream.get(longw[j]);
headerlen +=
(longw[j]) * ((unsigned int) pow ((double) 256, exp));
exp--;
}
if(headerlen == 6)
{
shortw = new char[2];
byteStream.read( shortw,2 );
frmt = shortw[1]+shortw[0]* 128;
if(frmt == 1 || frmt == 2 || frmt == 0) // used types
{
actualmidi->SetFormat( frmt);
byteStream.read( shortw, 2);
nrtracks = (unsigned char)shortw[1] +
(unsigned char)shortw[0] * 256;
if((frmt ==1) || (((frmt ==2) || frmt == 0) && nrtracks == 1 ))
{
// correct type -nrtr combination
byteStream.read( shortw, 2);
if ((unsigned int)shortw[0] < 128)
{
actualmidi->SetDivisionLSB((unsigned char)shortw[1]);
actualmidi->SetDivisionMSB((unsigned char)shortw[0]);
}
else
{
// framrate = shortw[0];
actualmidi->SetDivisionLSB((unsigned char)shortw[1]);
actualmidi->SetDivisionMSB((unsigned char)shortw[0]);
}
}
else
{
cout << "Error: invalid relation between ";
cout << "format and numberOfTracks.";
return false;
}
}
else
{
cout << "Error: unsupported number of format.";
return false;
}
}
else
{
cout << "Error: not supported length of header.";
return false ;
}
}
else
{
cout << "Error: " << MIDI_HEADER << " missing.";
return false ;
}
if ( InTracks( actualmidi, byteStream , nrtracks))
{
return true ;
}
else
{
return false;
}
}
/*
2.4 ~In~-Function
*/
static Word InMidi(const ListExpr typeInfo, const ListExpr instance,
const int errorPos, ListExpr& errorInfo, bool& correct)
{
if (((nl->IsAtom(instance) && nl->AtomType(instance) == TextType )) ||
((nl->ListLength(instance) == 3 ) && nl->IsAtom(nl->First(instance))
&& (nl->AtomType( nl->First(instance)) == TextType)
&& (nl->IsAtom(nl->Second(instance)))
&& (nl->AtomType( nl->Second(instance)) == IntType)
&& (nl->IsAtom(nl->Third(instance)))
&& (nl->AtomType( nl->Third(instance)) == IntType)))
{
Midi * midi = new Midi( true, 0, 0 );
string encoded;
if(nl->IsAtom(instance))
{
nl->Text2String( instance, encoded );
}
else
{
nl->Text2String( (nl->First( instance )), encoded );
}
if(InHeader(midi, encoded ))
{
correct = true;
return SetWord( midi);
}
correct = false;
errorInfo = nl->Append(errorInfo,nl->FourElemList(
nl->IntAtom(70), nl->SymbolAtom(Midi::BasicType()),
nl->IntAtom(1), nl->IntAtom(19)));
return SetWord(Address(0));
}
correct = false;
return SetWord(Address(0));
}
/*
2.5 The ~Property~-function
*/
ListExpr MidiProperty()
{
return (nl->TwoElemList(
nl->FiveElemList(nl->StringAtom("Signature"),
nl->StringAtom("Example Type List"),
nl->StringAtom("List Rep"),
nl->StringAtom("Example List"),
nl->StringAtom("Remarks")),
nl->FiveElemList(nl->StringAtom("-> DATA"),
nl->StringAtom(Midi::BasicType()),
nl->StringAtom("( <file>filename</file---> )"),
nl->StringAtom("( <file>Document.mid</file---> )"),
nl->StringAtom(""))));
}
/*
2.6 ~Create~-function
*/
Word CreateMidi( const ListExpr typeInfo )
{
return SetWord( new Midi( true, 0, 0 ) );
}
/*
2.7 ~Delete~-function
*/
void DeleteMidi( const ListExpr typeInfo, Word& w )
{
Midi *midi = (Midi *)w.addr;
midi->Destroy();
delete midi;
w.addr = 0;
}
/*
2.8 ~Close~-function
*/
void CloseMidi( const ListExpr typeInfo, Word& w )
{
delete (Midi *)w.addr;
w.addr = 0;
}
/*
2.9 ~Clone~-function
*/
Word CloneMidi( const ListExpr typeInfo, const Word& w )
{
return SetWord( ((Midi *)w.addr)->Clone() );
}
/*
2.10 ~SizeOf~-function
*/
int SizeOfMidi()
{
return sizeof(Midi);
}
/*
2.11 ~Cast~-function
*/
void* CastMidi( void* addr )
{
return new (addr) Midi;
}
/*
2.14 Kind Checking Function
This function checks whether the type constructor is applied correctly. Since
type constructor ~Midi~ does not have arguments, this is trivial.
*/
bool CheckMidi( ListExpr type, ListExpr& errorInfo )
{
return (nl->IsEqual( type, Midi::BasicType() ));
}
/*
2.15 Creation of the Type Constructor Instance
*/
TypeConstructor midi(
Midi::BasicType(), //name
MidiProperty, //property funct. describing signature
OutMidi, InMidi, //Out and In functions
0, 0, //list functions
CreateMidi, DeleteMidi, //object creation and deletion
0, 0, //object open and save
CloseMidi, CloneMidi, //object close and clone
CastMidi, //cast function
SizeOfMidi, //sizeof function
CheckMidi ); //kind checking function
/*
2 Implementation of class ~Track~
2.1 The constructor.
*/
Track::Track()
{ }
/*
2.1 The destructor.
*/
Track::~Track()
{
for (unsigned int i = 0; i < listOfEvents.size(); i++)
{
delete listOfEvents[i];
}
}
/*
2.1 GetEvent
Returns the by index selected event of this track.
*/
const Event* Track::GetEvent(int index) const
{
return listOfEvents[index];
}
/*
2.1 Append
Appends the by index selected track of this Midi object
*/
void Track::Append(Event* inEvent)
{
listOfEvents.push_back(inEvent);
}
/*
2.1 Get methods for private attributes
They return the content of an attribute or calculate the selected value on the fly
*/
const int Track::GetNumberOfEvents () const
{
return listOfEvents.size();
}
const string Track::GetHeader () const
{
return TRACK_HEADER;
}
/*
2.2 Utilty methods for operators inside class ~Track~
*/
/*
2.2.1 transpose
This is an utility for operator transpose\_midi implicit track given
number of halfsteps tones including/excluding transposing percussion channel
*/
void Track::Transpose( bool inPerc, int hfTnSt, int& errorval )
{
int note;
bool runmode = false;
bool perChannel = false;
int nrEvts = GetNumberOfEvents();
unsigned char currenttype;
for ( int k = 0; k < nrEvts; k++)
{
const Event* auxCurrentEvent = GetEvent(k);
Event *currentEvent = const_cast<Event*>( auxCurrentEvent );
if (currentEvent->GetEventType() == shortmessage)
{
runmode = currentEvent->GetShortMessageRunningStatus();
if(!runmode)
{
if((currentEvent->GetChannel() != 9)|| inPerc )
{
currenttype = currentEvent-> GetShortMessageType();
if((0xF0 & currenttype) == Event:: NOTE_ON ||
((0xF0 & currenttype) == Event:: NOTE_OFF))
{
note = (unsigned int)currentEvent->GetShortMessageData(0);
if (((note + hfTnSt)< 128) && ((note + hfTnSt) > -1))
{
currentEvent-> SetShortMessageData(0,
(unsigned char)(note + hfTnSt));
perChannel = false;
}
else
{
errorval = 1;
cout << "note " << note << " step " << hfTnSt;
return;
}
}
}
else // percussion channel and excluding them demanded
{
perChannel = true;
}
}
else // runmode
{
if(!perChannel)
{
note = (unsigned int)currentEvent->GetShortMessageData(0);
if (((note + hfTnSt) < 128) && ((note + hfTnSt) > -1))
{
currentEvent->SetShortMessageData(0,
(unsigned char)(note + hfTnSt));
perChannel = false;
}
else
{
errorval = 1;
return;
}
}
}
}
}
}
/*
2 Implementation of class ~Event~
2.1 Standard constructor I
This constructor should not be used but it is necessary.
*/
Event::Event()
{ }
/*
2.1 Standard constructor II
This one receives two values for ~eventType~ and ~deltaTime~. The value ~eventType~ specifies the used kind of event. Note that this constructor cannot be called without arguments.
*/
Event::Event(EventType eventType, unsigned int deltaTime)
{
this->eventType = eventType;
this->deltaTime = deltaTime;
shortMessageRunningStatus = false;
}
/*
2.1 The destructor
*/
Event::~Event()
{ }
/*
2.1 GetTextFromMetaEvent
Returns the text content from a meta event which contains text data of variable size. Some of the meta events between 0x01 and 0x58 are storing text in this way.
*/
void Event::GetTextFromMetaEvent(string& result) const
{
assert(eventType == metamessage);
int metaEvent = GetMetaMessageType();
assert(metaEvent >= 0x01 && metaEvent <= 0xFF );
vector<unsigned char> data;
vector<unsigned char> lengthBytes;
int dataPtr = 2;
do
{
lengthBytes.push_back(dataList[dataPtr++]);
} while ((dataList[dataPtr - 1] & 0x80));
int dataLength = Event::ComputeBytesToInt(&lengthBytes);
unsigned char* cstr = new unsigned char[dataLength + 1];
int cstrPtr = 0;
for (int k = 0; k < dataLength; k++)
{
cstr[cstrPtr] = dataList[dataPtr++];
cstrPtr++;
}
cstr[cstrPtr] = 0;
result +=(char*)cstr;
delete[] cstr;
}
/*
2.1 Get methods for private attributes
They return the content of an attribute or calculate the selected value on the fly
Returns the data of a SysexMessage.
*/
vector<unsigned char>* Event::GetSysexData(vector<unsigned char>*result) const
{
assert(eventType == sysexmessage || eventType == metamessage);
for (unsigned int i = 0; i < dataList.size(); i++)
{
result->push_back(dataList[i]);
}
return result;
}
/*
Returns the length of a SysexMessage.
*/
int Event::GetSysexDataLength() const
{
assert(eventType == sysexmessage || eventType == metamessage);
return dataList.size();
}
/*
Returns the data of MetaEvents.
*/
vector<unsigned char>* Event::GetMetaData(vector<unsigned char>* result) const
{
assert(eventType == metamessage || eventType == sysexmessage);
for (unsigned int i = 0; i < dataList.size(); i++)
{
result->push_back(dataList[i]);
}
return result;
}
/*
Returns the length of a MetaEvent. The returned value is the length of the entire Metamessage. Example: The Metamessage 0xFF 0x01 0x01 0x4D returns the value 4.
*/
int Event::GetMetaDataLength() const
{
assert(eventType == metamessage || eventType == sysexmessage);
return dataList.size();
}
/*
Returns the kind of event.
*/
unsigned char Event::GetMetaMessageType() const
{
assert(eventType == metamessage && dataList.size() > 2);
return dataList[1];
}
/*
Returns the length of a MidiEvent.
*/
int Event::GetShortMessageDataLength() const
{
assert(eventType == shortmessage);
return shortMessageDataLength;
}
/*
Return the data of a MidiEvent.
*/
unsigned char Event::GetShortMessageData(int index) const
{
assert(eventType == shortmessage && index >= 0 && index < 2);
return shortMessageData[index];
}
/*
Returns the command of a MidiEvent. The returned value will be undefined if GetShortMessageRunningStauts() returns true.
*/
unsigned char Event::GetShortMessageType() const
{
assert(shortMessageRunningStatus == false);
assert(eventType == shortmessage);
return shortMessageType;
}
/*
Returns the kind of event.
*/
EventType Event::GetEventType() const
{
return eventType;
}
/*
Returns the length of this event.
*/
unsigned int Event::GetDeltaTime() const
{
return deltaTime;
}
/*
Returns the MidiEvent`s channel. To call this method is prohibited if GetShortMessageRunningStatus() returns true. In this case this method will abort with an assertion.
*/
const int Event::GetChannel () const
{
assert(shortMessageRunningStatus == false);
assert(eventType == shortmessage);
assert((shortMessageType & 0xF0) != 0xF0);
int channel = shortMessageType & 0x0F;
return channel;
}
/*
Returns the ''running status'' of this Midi (channel) event. If the status is set to true, this Midi (channel) event has the same
ShortMessageType like the preceding event. In this case the return value of GetShortMessageType will be undefined.
*/
bool Event::GetShortMessageRunningStatus() const
{
return shortMessageRunningStatus;
}
/*
2.2 Set methods for private attributes
They sets the attribute to the specified value.
*/
void Event::SetDeltaTime(unsigned int deltaTime)
{
this->deltaTime = deltaTime;
}
void Event::SetShortMessageType(unsigned char shortMessageType)
{
assert(eventType == shortmessage);
this->shortMessageType = shortMessageType;
}
void Event::SetShortMessageData(int index, unsigned char data)
{
assert(eventType == shortmessage);
assert(index >= 0 && index < 2);
shortMessageData[index] = data;
}
void Event::SetShortMessageDataLength(int length)
{
assert(eventType == shortmessage && length >= 0 && length < 3);
shortMessageDataLength = length;
}
void Event::SetMetaData(vector<unsigned char>* data)
{
assert(eventType == metamessage || eventType == sysexmessage);
for (unsigned int i = 0; i < data->size(); i++)
{
dataList.push_back((*data)[i]);
}
}
void Event::SetSysexData(vector<unsigned char>* data)
{
assert(eventType == sysexmessage || eventType == metamessage);
for (unsigned int i = 0; i < data->size(); i++)
{
dataList.push_back((*data)[i]);
}
}
void Event::SetChannel(const int channel)
{
assert(eventType == shortmessage);
assert((shortMessageType & 0xF0) != 0xF0);
unsigned char channelNo = channel;
shortMessageType &= 0xF0;
shortMessageType |= channelNo;
}
void Event::SetShortMessageRunningStatus(bool status)
{
shortMessageRunningStatus = status;
}
/*
2.3 Static methods
2.1.1 ComputeIntToBytes
Transforms an integer value into the necessary byte representation used by Midi.
*/
vector<unsigned char>* Event::ComputeIntToBytes(unsigned int arg,
vector<unsigned char>* result)
{
result->clear();
if (!arg)
{
result->push_back(0);
return result;
}
bool lastDigits = false;
for (int i = 3; i >= 0; i--)
{
unsigned char rest =
(unsigned char)(arg % (unsigned int) pow((double) 128, i));
unsigned char divisor =
(unsigned char)((arg - rest) / (unsigned int) pow((double) 128, i));
if (lastDigits || divisor != 0)
{
lastDigits = true;
arg -= (divisor * (unsigned int) pow((double) 128, i));
divisor |= 0x80;
result->push_back(divisor);
}
}
(*result)[result->size() - 1] &= 0x7F;
return result;
}
/*
2.1.1 ComputeBytesToInt
Calculates the integer value out of an array of bytes.
*/
unsigned int Event::ComputeBytesToInt(vector<unsigned char>* arg)
{
int exp = 0;
unsigned int result = 0;
for (int i = arg->size() - 1; i >= 0; i--)
{
unsigned char value = (*arg)[i];
value &= 0x7F;
result += value * ((unsigned int) pow((double) 128, exp));
exp++;
}
return result;
}
/*
2.1.1 FilterString
Changes the content alculates the integer value out of an array of bytes.
*/
string Event::FilterString(string textEvents)
{
stringstream in(textEvents);
stringstream filtered;
char ch;
while (in)
{
in.get(ch);
switch((unsigned int)ch)
{
case 0x0d: // rollback
filtered.put(0x0a);
break;
case 0x2f: // "/"
filtered.put(0x20);
break;
case 0x5c: // "\"
filtered.put(0x0a);
break;
default:
filtered.put(ch);
}
}
return filtered.str();
}
/*
3 Utility functions
3.1 ~FindMetaEvent~
Examines the selected track object until it finds the specified MetaEvent or
the track ends.
*/
CcString* FindMetaEvent(Midi* inMidi, unsigned char searchedValue, int number)
{
const Event* currentEvent;
string temp;
temp.clear();
CcString* result = new CcString(false, (STRING_T*)temp.c_str());
Track* currentTrack = inMidi->GetTrack(number);
int numberEvents = currentTrack->GetNumberOfEvents();
bool found = false;
for ( int eventIndex = 0; eventIndex < numberEvents && !found; eventIndex++)
{
currentEvent = currentTrack->GetEvent(eventIndex);
if ( currentEvent->GetEventType() == metamessage &&
currentEvent->GetMetaMessageType() == searchedValue )
{
currentEvent->GetTextFromMetaEvent(temp);
result->Set(true, (STRING_T*)temp.c_str());
found = true;
}
}
return result;
}
/*
3.2 ~IntToString~, ~StringToInt~
Converting functions for int to string and string to int.
*/
string IntToString (int i)
{
ostringstream temp;
temp << i;
string result = temp.str();
return result;
}
bool StringToInt(string s, int& result)
/*
Returns false if it's not possible to convert the given string in an integer
value.
*/
{
stringstream sstr;
sstr << s;
sstr >> result;
return sstr.eof();
}
/*
3.3 ~BytesToIntNoClearMSB~
Converts an binary value into an integer without clearing the msb
Higher and lower byte are calculated with identical exponent, because
conversion into integer value ''low'' and ''high'' includes already
multiplication with 16. Therefor it is not allowed to increase the exponent for
the higher byte.
*/
unsigned long int BytesToIntNoClearMSB(vector<unsigned char>* arg)
{
int exp = 0;
unsigned long result = 0;
for (int i = arg->size() - 1; i >= 0; i--)
{
unsigned char value = (*arg)[i];
unsigned int low = value & 0x0F;
unsigned int high = value & 0xF0;
unsigned long basis = (unsigned long) pow ((double) 16, exp);
result += low * basis;
basis = (unsigned long) pow ((double) 16, exp);
result += high * basis;
exp += 2;
}
return result;
}
/*
3.4 Implementation of helper class ~NoteStringToListParser~
3.4.1 The constructor.
*/
NoteStringToListParser::NoteStringToListParser()
{ }
/*
3.4.1 ParseNoteString
*/
bool NoteStringToListParser::ParseNoteString(char* inputStr,
vector<int>* resultList)
{
resultList->clear();
if ( !(*inputStr) )
{
return false;
}
char currentChar = GetNextChar(&inputStr);
do
{
int baseNoteNo = ComputeBaseNoteNo(currentChar);
int octaveFactor = 0;
if (baseNoteNo == -1)
{
return false;
}
currentChar = GetNextChar(&inputStr);
if (currentChar == '#')
{
baseNoteNo++;
currentChar = GetNextChar(&inputStr);
}
else if (currentChar == 'b') {
baseNoteNo--;
currentChar = GetNextChar(&inputStr);
}
else if (!currentChar) {
return false;
}
stringstream sstr;
while (currentChar && currentChar != ',')
{
sstr << currentChar;
currentChar = GetNextChar(&inputStr);
}
string intString = sstr.str();
bool conversionResult = StringToInt(intString, octaveFactor);
if (!conversionResult || octaveFactor < -1 || octaveFactor > 9)
{
return false;
}
int noteNo = (octaveFactor + 1) * 12 + baseNoteNo;
if (noteNo < 0 || noteNo > 127)
{
return false;
}
resultList->push_back(noteNo);
if (!currentChar)
{
return true;
}
if (currentChar != ',')
{
return false;
}
currentChar = GetNextChar(&inputStr);
} while (true);
}
/*
3.4.1 GetNextChar
*/
char NoteStringToListParser::GetNextChar(char** inputStrPtr)
{
char result = *(*inputStrPtr);
(*inputStrPtr)++;
while (result == ' ') // skip spaces
{
result = *(*inputStrPtr);
(*inputStrPtr)++;
}
return result;
}
/*
3.4.1 ComputeBaseNoteNo
*/
int NoteStringToListParser::ComputeBaseNoteNo(char currentChar)
{
switch (currentChar)
{
case 'C':
return 0;
case 'D':
return 2;
case 'E':
return 4;
case 'F':
return 5;
case 'G':
return 7;
case 'A':
return 9;
case 'B':
return 11;
}
return ERROR_INT;
}
/*
4 Operators
4.1 Operator ~extract\_track~
Constructs a new Midi object which includes only the selected track.
4.1.1 Type mapping function of operator ~extract\_track~
Operator ~extract\_track~ accepts a Midi object and a integer representingthe
track number and returns a Midi object.
---- (midi int) -> midi
----
*/
ListExpr genericTrackTypeMap( ListExpr args )
{
if ( nl->ListLength(args) == 2 )
{
ListExpr arg1 = nl->First(args);
ListExpr arg2 = nl->Second(args);
if ( nl->IsEqual(arg1, Midi::BasicType()) &&
nl->IsEqual(arg2, CcInt::BasicType()) )
{
return arg1;
}
}
return nl->SymbolAtom(Symbol::TYPEERROR());
}
/*
4.1.2 Value mapping functions of operator ~extract\_track~
*/
int extractTrackFun(Word* args, Word& result, int message, Word& local,
Supplier s)
{
Midi *currentMidi = (Midi*)args[0].addr;
int extractedTrack = ((CcInt*)args[1].addr)->GetIntval();
Midi *newMidi = currentMidi->Clone(false);
/*
Gets the Midi object and the to be extracted track number out of the argument
list, then a new Midi instance is created with same content as currentMidi but
without tracks
*/
if ( currentMidi->GetNumberOfTracks() < extractedTrack ||
extractedTrack < MIN_TRACKS_MIDI )
{
cout << "Error in extract_track: Parameter is out of range, ";
cout << "the operator's input is passed without changes." << endl;
newMidi = currentMidi;
}
else
{
newMidi->Append(currentMidi->GetTrack(extractedTrack - 1));
}
/*
If the user specifies a track number outside the valid range we give back the
original input Midi. So always we have a valid result.
*/
result = qp->ResultStorage(s);
*((Midi *)result.addr) = *newMidi;
/*
Deletion was always succesfull [->] return new Midi instance by storing their
reference
*/
return 0;
}
/*
4.1.3 Specification of operator ~extract\_track~
*/
const string extractTrackSpec =
"( ( \"Signature\" \"Syntax\" \"Meaning\" "
"\"Example\" ) "
"( <text>(midi int) -> midi"
"</text--->"
"<text>_ extract_track [_]</text--->"
"<text>Constructs a new Midi object including "
"only the selected track.</text--->"
"<text>query midifile extract_track [2]</text--->"
") )";
/*
4.1.4 Definition of operator ~extract\_track~
*/
Operator extract_track (
"extract_track", //name
extractTrackSpec, //specification
extractTrackFun, //value mapping
Operator::SimpleSelect, //trivial selection function
genericTrackTypeMap //type mapping
);
/*
4.2 Operator ~delete\_track~
Deletes the selected track out of the specified Midi object.
4.2.1 Type mapping function of operator ~delete\_track~
Operator ~delete\_track~ accepts a Midi object and an integer value
representingthe number of the to be deleted track and returns a new Midi
object.
---- (midi int) -> midi
----
( please refer to function genericTrackTypeMap )
*/
/*
4.2.2 Value mapping functions of operator ~delete\_track~
*/
int deleteTrackFun(Word* args, Word& result, int message, Word& local,
Supplier s)
{
Midi *currentMidi = (Midi*)args[0].addr;
int deletedTrack = ((CcInt*)args[1].addr)->GetIntval() - 1;
int currentTracks = currentMidi->GetNumberOfTracks();
Midi *newMidi = currentMidi->Clone(false);
/*
Gets the Midi object and the to be deleted track number out of the argument
list, then a new Midi instance is created with same content as currentMidi but
without tracks
*/
result = qp->ResultStorage(s);
if ( currentTracks >= MIN_TRACKS_MIDI && currentTracks >= deletedTrack + 1 )
{
for ( int index = 0; index < currentTracks; index++ )
{
if ( index != deletedTrack )
{
newMidi->Append(currentMidi->GetTrack(index));
}
}
/*
Deletes the specified track by taking over the other ones from the instance
currentMidi, only allowed with more than MIN\_TRACKS\_MIDI tracks
*/
*((Midi *)result.addr) = *newMidi;
/*
Reduction was succesfull [->] return new Midi instance by storing their
reference
*/
}
else
{
cout << "Error in delete_track: Parameter is out of range, ";
cout << "the operator's input is passed without changes." << endl;
*((Midi *)result.addr) = *currentMidi;
/*
Error before track deletion ( too few remaining tracks inside the Midi Object )
[->] return original Midi object
*/
}
return 0;
}
/*
4.2.3 Specification of operator ~delete\_track~
*/
const string deleteTrackSpec =
"( ( \"Signature\" \"Syntax\" \"Meaning\" "
"\"Example\" ) "
"( <text>(midi int) -> midi"
"</text--->"
"<text>_ delete_track [_]</text--->"
"<text>Deletes the selected track out of the "
"specified Midi object. If the selected track "
"is lower or higher than the track`s valid "
"range, the original Midi object will be "
"returned</text--->"
"<text>query midifile delete_track [3]</text--->"
") )";
/*
4.2.4 Definition of operator ~delete\_track~
*/
Operator delete_track (
"delete_track", //name
deleteTrackSpec, //specification
deleteTrackFun, //value mapping
Operator::SimpleSelect, //trivial selection function
genericTrackTypeMap //type mapping
);
/*
4.3 Operator ~expand\_track~
A track could consists of up to 16 channels. This operator creates one new
track per each channel inside the selected track number.
Example: a Midi object consists of 3 tracks. Track 1 and 2 consists of 4
channels each, track 3 consists of 5 channels. Now this Midi object shall be
expanded with track 3. After this operation the Midi object consists of 7
tracks ( old 1 and 2 and the new 5 ones ( because of 5 channels in track 3 )
makes 7 ).
4.3.1 Type mapping function of operator ~expand\_track~
Operator ~expand\_track~ accepts a Midi object and an integer value
representingthe number of the to be expanded track and returns the changed Midi
object.
---- (midi int) -> midi
----
( please refer to function genericTrackTypeMap )
*/
/*
4.3.2 Value mapping functions of operator ~expand\_track~
*/
int expandTrackFun(Word* args, Word& result, int message, Word& local,
Supplier s)
{
Event *currentEvent;
const Event* auxCurrentEvent;
int currentChannel;
// number of current channel
bool foundChannels[MAX_CHANNELS_MIDI];
// stores found channels of to be expanded track
Track* newTracks[MAX_CHANNELS_MIDI];
bool expand = false;
// controls the expansition of metaEvents and sysEx
int runningChannel = 0;
// if expand == true, then it stores the MetaEvent`s channel
Midi *currentMidi = (Midi*)args[0].addr;
int expandedTrack = ((CcInt*)args[1].addr)->GetIntval() - 1;
int currentTracks = currentMidi->GetNumberOfTracks();
Midi *newMidi = currentMidi->Clone(false);
/*
Gets the Midi object and the to be extracted track number out of the argument
list, then a new Midi instance is created with same content as currentMidi but
without tracks
*/
for ( int index = 0; index < MAX_CHANNELS_MIDI; index++ )
{
foundChannels[index] = false;
newTracks[index] = NULL;
}
/*
Initialises the array for found channels and new tracks
*/
for ( int trackIndex = 0; trackIndex < currentTracks; trackIndex++ )
{
if ( trackIndex != expandedTrack )
{
newMidi->Append(currentMidi->GetTrack(trackIndex));
}
/*
All tracks except the to be expanded one are copied into the new Midi instance
*/
else
{
Track* oldTrack = currentMidi->GetTrack(expandedTrack);
int numberEvents = oldTrack->GetNumberOfEvents();
/*
First of all the number of different channels is counted, because we need to
know how many tracks needs to be created at first.
*/
for ( int eventIndex = 0; eventIndex < numberEvents; eventIndex++ )
{
auxCurrentEvent = oldTrack->GetEvent(eventIndex);
currentEvent = const_cast<Event*>( auxCurrentEvent );
if ( currentEvent->GetEventType() == metamessage )
{
if (currentEvent->GetMetaMessageType() == Event::CHANNEL_PREFIX)
{
expand = true;
string temp;
currentEvent->GetTextFromMetaEvent(temp);
runningChannel = temp[0];
/*
Normally SysEx and MetaEvents are not expanded, except MetaEvent
''CHANNEL\_PREFIX'' occured or is still valid inside the track. Then *all*
events are copied to the new track created by inside the MetaEvent mentioned
channel number
*/
}
else
{
expand = false;
runningChannel = MAX_CHANNELS_MIDI + 1;
/*
A different meta message to CHANNEL\_PREFIX inside the to be expanded track
clears it
*/
}
}
if ( currentEvent->GetEventType() == sysexmessage )
{
expand = false;
runningChannel = MAX_CHANNELS_MIDI + 1;
/*
A sysex message inside the to be expanded track clears the MetaEvent
''CHANNEL\_PREFIX''
*/
}
if ( currentEvent->GetEventType() == shortmessage && expand &&
!currentEvent->GetShortMessageRunningStatus() &&
currentEvent->GetChannel() != runningChannel )
{
expand = false;
runningChannel = MAX_CHANNELS_MIDI + 1;
/*
A new channel inside the to be expanded track clears the MetaEvent
''CHANNEL\_PREFIX''
*/
}
if (expand)
{
if ( !foundChannels[runningChannel] )
{
foundChannels[runningChannel] = true;
Track* newTrack = new Track();
newTrack->Append(currentEvent);
newTracks[runningChannel] = newTrack;
/*
New channel found [->] new track created and current event appended
*/
}
else
{
Track* existingTrack = newTracks[runningChannel];
existingTrack->Append(currentEvent);
newTracks[runningChannel] = existingTrack;
/*
Add Event to newly created track for this specific channel
*/
}
}
else // expand modus is invalid
{
if ( currentEvent->GetEventType() == shortmessage )
{
if ( !currentEvent->GetShortMessageRunningStatus() )
{
currentChannel = currentEvent->GetChannel();
if ( !foundChannels[currentChannel] )
{
foundChannels[currentChannel] = true;
Track* newTrack = new Track();
newTrack->Append(currentEvent);
newTracks[currentChannel] = newTrack;
runningChannel = currentChannel;
}
else
{
Track* existingTrack = newTracks[currentChannel];
existingTrack->Append(currentEvent);
newTracks[currentChannel] = existingTrack;
runningChannel = currentChannel;
}
}
else // uupps running status
{
Track* existingTrack = newTracks[runningChannel];
existingTrack->Append(currentEvent);
newTracks[runningChannel] = existingTrack;
/*
If a MidiEvent occured and ~runningStatus~ is valid, then ~runningChannel~
should have a valid eventIndex for copying into a new track.
*/
}
}
}
}
}
}
/*
Whilst walking through the entire to be expanded track, for each channel a new
track will be created.
*/
for ( int k = 0; k < MAX_CHANNELS_MIDI; k++ )
{
if ( foundChannels[k] )
{
Track* track = newTracks[k];
Event* lastEvent = new Event(metamessage, 0);
vector<unsigned char> v(3);
v[0] = 0xFF;
v[1] = Event::END_OF_TRACK;
v[2] = 0;
lastEvent->SetMetaData(&v);
track->Append(lastEvent);
newMidi->Append(newTracks[k]);
}
}
/*
Before appending all new tracks to the new Midi instance, they needs to be
finished with an ''end of track'' event.
*/
result = qp->ResultStorage(s);
if ( newMidi->GetNumberOfTracks() >= MIN_TRACKS_MIDI )
{
*((Midi *)result.addr) = *newMidi;
/*
Expand was succesfull [->] return new Midi instance by storing their reference
*/
}
else
{
cout << "Error in expand_track: too few remaining tracks" << endl;
*((Midi *)result.addr) = *currentMidi;
}
/*
error during track expand ( too few remaining tracks inside the Midi Object )
[->] return original Midi object
*/
return 0;
}
/*
4.3.3 Specification of operator ~expand\_track~
*/
const string expandTrackSpec =
"( ( \"Signature\" \"Syntax\" \"Meaning\" "
"\"Example\" ) "
"( <text>(midi int) -> midi"
"</text--->"
"<text>_ expand_track [_]</text--->"
"<text>Expands the selected track inside the "
"specified Midi object. Each channel inside "
"this track is copied into a new track "
"of the output Midi. If MetaEvent "
"CHANNEL_PREFIX is valid, then all "
"MetaEvents and SystemEvents are copied."
"Otherwise only MidiEvents are copied.</text--->"
"<text>query midifile expand_track [4]</text--->"
") )";
/*
4.3.4 Definition of operator ~expand\_track~
*/
Operator expand_track (
"expand_track", //name
expandTrackSpec, //specification
expandTrackFun, //value mapping
Operator::SimpleSelect, //trivial selection function
genericTrackTypeMap //type mapping
);
/*
4.3 Operator ~merge\_tracks~
This operator creates a new Midi object which includes one track merged
together out of two tracks.
4.3.1 Type mapping function of operator ~merge\_tracks~
Operator ~merge\_tracks~ accepts a Midi object and two integer values
representingthe number of the to be merged tracks and returns a new Midi
object.
---- (midi int int bool) -> midi
----
*/
ListExpr mergeTracksTypeMap( ListExpr args )
{
if ( nl->ListLength(args) == 4 )
{
ListExpr arg1 = nl->First(args);
ListExpr arg2 = nl->Second(args);
ListExpr arg3 = nl->Third(args);
ListExpr arg4 = nl->Fourth(args);
if ( nl->IsEqual(arg1, Midi::BasicType()) &&
nl->IsEqual(arg2, CcInt::BasicType()) &&
nl->IsEqual(arg3, CcInt::BasicType()) &&
nl->IsEqual(arg4, CcBool::BasicType()))
{
return arg1;
}
}
return nl->SymbolAtom(Symbol::TYPEERROR());
}
/*
4.3.2 Value mapping functions of operator ~merge\_tracks~
Utilities of the value mapping function.
*/
struct EventWithAbsTime
/*
Stores an event with the absolute time position.
*/
{
unsigned int absTime;
Event* event;
};
void computeEventsOfTrackWithAbsTime(Track* track,
vector<EventWithAbsTime>& result)
/*
Computes the absolute time position of each event in a track.
*/
{
unsigned int absPos = 0;
unsigned char shortMessageType=0;
for (int i = 0; i < track->GetNumberOfEvents(); i++)
{
EventWithAbsTime* eventStruct = new EventWithAbsTime;
Event *event;
const Event* auxEvent = track->GetEvent(i);
event = const_cast<Event*>( auxEvent );
if (event->GetEventType() == shortmessage &&
!event->GetShortMessageRunningStatus())
{
shortMessageType = event->GetShortMessageType();
}
if (event->GetEventType() == shortmessage &&
event->GetShortMessageRunningStatus())
{
event->SetShortMessageRunningStatus(false);
event->SetShortMessageType(shortMessageType);
}
absPos += event->GetDeltaTime();
eventStruct->absTime = absPos;
eventStruct->event = event;
result.push_back(*eventStruct);
}
}
void appendEventMergedTrack(
vector<EventWithAbsTime>::iterator eventStruct,
vector<EventWithAbsTime>& mergedTrackList,
bool destTrack)
/*
Append an event at the list of merged events and do some filtering.
*/
{
if (eventStruct->event->GetEventType() == shortmessage)
{
unsigned char shortMessageType = eventStruct->event->GetShortMessageType();
shortMessageType = shortMessageType & 0xF0;
if (destTrack)
{
// passes all events of the destination track without any filtering
mergedTrackList.push_back(*eventStruct);
return;
}
// at the moment no filtering will be done
mergedTrackList.push_back(*eventStruct);
return;
}
if (eventStruct->event->GetEventType() == metamessage)
{
unsigned char metaMessageType =
eventStruct->event->GetMetaMessageType();
if (metaMessageType == Event::END_OF_TRACK ||
metaMessageType == Event::TRACK_NAME)
{
return;
}
if (destTrack)
{
// passes all events of the destination track without any filtering
mergedTrackList.push_back(*eventStruct);
return;
}
// filter some meta messages of the source track
if (metaMessageType == Event::INSTRUMENT_NAME)
{
return;
}
mergedTrackList.push_back(*eventStruct);
return;
}
else
{
// it is a sysex message
mergedTrackList.push_back(*eventStruct);
return;
}
}
void getTrackFromEventStructList(string& mergedTrackName,
vector<EventWithAbsTime>& mergedTrackList,
Track* result)
/*
Create a track from a merged list with entries of type EventWithAbsTime.
*/
{
unsigned int prevAbsTime = 0;
Event* trackNameMetaMessage = new Event(metamessage, 0);
vector<unsigned char> metaData(mergedTrackName.size() + 3);
metaData[0] = 0xFF;
metaData[1] = Event::TRACK_NAME;
metaData[2] = mergedTrackName.size();
const char* mergedTrackNamecstr = mergedTrackName.c_str();
for (unsigned int i = 0; i < mergedTrackName.size(); i++)
{
metaData[3 + i] = mergedTrackNamecstr[i];
}
trackNameMetaMessage->SetMetaData(&metaData);
result->Append(trackNameMetaMessage);
for (unsigned int i = 0; i < mergedTrackList.size(); i++)
{
Event* event = new Event(*(mergedTrackList[i].event));
// Set the new delta time
event->SetDeltaTime(mergedTrackList[i].absTime - prevAbsTime);
result->Append(event);
prevAbsTime = mergedTrackList[i].absTime;
}
Event* lastEvent = new Event(metamessage, 0);
vector<unsigned char> v(3);
v[0] = 0xFF;
v[1] = Event::END_OF_TRACK;
v[2] = 0;
lastEvent->SetMetaData(&v);
result->Append(lastEvent);
}
void constructMergedTrackName(int track1No, int track2No,
string& result)
/*
Construct a string ''Track $<srcTrackNo>, <destTrackNo>$ merged''
*/
{
result.clear();
string track1NoStr = IntToString(track1No + 1);
string track2NoStr = IntToString(track2No + 1);
result.append("*Tracks ");
result.append(track1NoStr);
result.append(", ");
result.append(track2NoStr);
result.append(" merged*");
}
void mergeTracks(Track* track1, int track1No, Track* track2,
int track2No, Track* result)
/*
This function merges two tracks into one track.
*/
{
vector<EventWithAbsTime> track1List;
vector<EventWithAbsTime> track2List;
// Create lists of event objects with absolute time
computeEventsOfTrackWithAbsTime(track1, track1List);
computeEventsOfTrackWithAbsTime(track2, track2List);
vector<EventWithAbsTime>::iterator it1 = track1List.begin();
vector<EventWithAbsTime>::iterator it2 = track2List.begin();
vector<EventWithAbsTime> mergedTrackList;
// A small mergesort implementation...
while (it1 != track1List.end() && it2 != track2List.end())
{
if (it1->absTime <= it2->absTime)
{
appendEventMergedTrack(it1, mergedTrackList, false);
it1++;
}
else
{
appendEventMergedTrack(it2, mergedTrackList, true);
it2++;
}
}
while (it1 != track1List.end())
{
appendEventMergedTrack(it1, mergedTrackList, false);
it1++;
}
while (it2 != track2List.end())
{
appendEventMergedTrack(it2, mergedTrackList, true);
it2++;
}
string mergedTrackName;
constructMergedTrackName(track1No, track2No, mergedTrackName);
getTrackFromEventStructList(mergedTrackName, mergedTrackList, result);
}
void replaceProgramChangeValuesForTrackMerging(Track* srcTrack,
Track* destTrack)
/*
Replaces all ~program changes~ values in the source track with the value of the
first ~program change~ in the destination track.
*/
{
const Event* firstPCOfDestTrack = 0;
for (int i = 0; i < destTrack->GetNumberOfEvents(); i++)
{
const Event* currentEvent = destTrack->GetEvent(i);
if (currentEvent->GetEventType() == shortmessage &&
!currentEvent->GetShortMessageRunningStatus() &&
(currentEvent->GetShortMessageType()&0xF0) == Event::PROGRAM_CHANGE)
{
firstPCOfDestTrack = currentEvent;
}
}
if (!firstPCOfDestTrack)
{
return;
}
/*
No program change in destination track! We hope the best for the result of
merging...
*/
unsigned char programChangeValue =
firstPCOfDestTrack->GetShortMessageData(0);
/*
Replace all program changes in the source track with the value of the found
program change's value
*/
for (int i = 0; i < srcTrack->GetNumberOfEvents(); i++)
{
const Event* auxCurrentEvent = srcTrack->GetEvent(i);
Event *currentEvent = const_cast<Event*>( auxCurrentEvent );
if (currentEvent->GetEventType() == shortmessage &&
!currentEvent->GetShortMessageRunningStatus() &&
(currentEvent->GetShortMessageType()&0xF0) == Event::PROGRAM_CHANGE)
{
currentEvent->SetShortMessageData(0, programChangeValue);
}
}
}
int mergeTracksFun(Word* args, Word& result, int message,
Word& local, Supplier s)
/*
The value mapping function.
*/
{
Midi* midiIn = (Midi*) args[0].addr;
Midi* midiOut = midiIn->Clone(false);
midiOut->Destroy();
int track1No = ((CcInt*) args[1].addr)->GetIntval() - 1;
int track2No = ((CcInt*) args[2].addr)->GetIntval() - 1;
bool filterProgramChanges = ((CcBool*) args[3].addr)->GetBoolval();
if (track1No != track2No &&
track1No >= 0 && track1No < midiIn->GetNumberOfTracks() &&
track2No >= 0 && track2No < midiIn->GetNumberOfTracks())
{
Track* track1 = midiIn->GetTrack(track1No);
Track* track2 = midiIn->GetTrack(track2No);
if (filterProgramChanges)
{
replaceProgramChangeValuesForTrackMerging(track1, track2);
}
Track* mergedTrack = new Track();
mergeTracks(track1, track1No, track2, track2No, mergedTrack);
// filterProgramChanges
for (int i = 0; i < midiIn->GetNumberOfTracks(); i++)
{
if (i != track1No)
{
if (i == track2No)
{
midiOut->Append(mergedTrack);
}
else
{
midiOut->Append(midiIn->GetTrack(i));
}
}
}
delete track1;
delete track2;
delete mergedTrack;
}
else
{
/*
''Official'' output of operator merge\_tracks. Please do not comment the
following two lines.
*/
cout << "Operator merge_tracks: Parameters are out of range, ";
cout << "the operator's input is passed without changes.\n";
delete midiOut;
midiOut = new Midi(*midiIn);
midiOut->Destroy();
}
result = SetWord(midiOut);
return 0;
}
/*
4.3.3 Specification of operator ~merge\_tracks~
*/
const string mergeTracksSpec =
"( ( \"Signature\" \"Syntax\" \"Meaning\" "
"\"Example\" ) "
"( <text>(midi int int bool) -> midi"
"</text--->"
"<text>_ merge_tracks [_,_,_]</text--->"
"<text>Merges the first specified track into the "
"second track. If the boolean flag is set true, the notes of the "
"first track will be played with the instrument's voice of the second"
"track.</text--->"
"<text>query mymidi merge_tracks [2,4,FALSE]</text--->"
") )";
/*
4.3.4 Definition of operator ~merge\_tracks~
*/
Operator merge_tracks (
"merge_tracks", //name
mergeTracksSpec, //specification
mergeTracksFun, //value mapping
Operator::SimpleSelect, //trivial selection function
mergeTracksTypeMap //type mapping
);
/*
4.3 Operator ~transpose\_track~
This operator transposes the selected track inside the specified Midi object by
the number of specified halftones.
4.3.1 Type mapping function of operator ~transpose\_track~
Operator ~transpose\_track~ accepts a Midi object, an 2 integer values. It
returns a Midi object.
---- (midi int int -> midi
----
*/
ListExpr transposeTrackTypeMap( ListExpr args )
{
if ( nl->ListLength(args) == 3 )
{
ListExpr arg1 = nl->First(args);
ListExpr arg2 = nl->Second(args);
ListExpr arg3 = nl->Third(args);
if ( nl->IsEqual(arg1, Midi::BasicType()) &&
nl->IsEqual(arg2, CcInt::BasicType()) &&
nl->IsEqual(arg3, CcInt::BasicType()))
{
return arg1;
}
}
return nl->SymbolAtom(Symbol::TYPEERROR());
}
/*
4.3.2 Value mapping functions of operator ~transpose\_track~
*/
int transposeTrackFun(Word* args, Word& result, int message, Word& local,
Supplier s)
{
int errorinfo = 0;
Midi* currentMidi = (Midi*)args[0].addr;
CcInt* tracknr = (CcInt*)args[1].addr;
CcInt* htsteps = (CcInt*)args[2].addr;
int stepval = htsteps->GetIntval();
Track* currentTrack;
Midi* transposedMidi = new Midi (true, currentMidi->GetDivisionMSB(),
currentMidi->GetDivisionLSB());
result = qp->ResultStorage(s);
if(stepval < 128 && stepval > -128)
{
transposedMidi->SetFormat(currentMidi->GetFormat());
if((currentMidi->GetNumberOfTracks() + 1) > tracknr->GetIntval())
{
int k = 1;
do
{
currentTrack = currentMidi->GetTrack(k-1);
if(k == (tracknr->GetIntval()))
{
currentTrack->Transpose(true,stepval,errorinfo);
}
transposedMidi->Append(currentTrack);
k++;
} while(!errorinfo && k < (currentMidi->GetNumberOfTracks()));
if(errorinfo == 1 )
{
*(Midi *)result.addr = *currentMidi;
cout << endl << "crossing range of playable values";
cout << endl << "in Track " << k-1 << " !!";
cout << endl<< " nothing done ";
return 0;
}
}
else
{
cout << "tracknr too big or small - nothing done";
*(Midi *)result.addr = *currentMidi;
return 0;
}
}
else
{
cout << endl << "too many steps- nothing done";
*(Midi *)result.addr = *currentMidi;
return 0;
}
*(Midi *)result.addr = *transposedMidi;
delete currentTrack;
return 0;
}
/*
4.3.3 Specification of operator ~transpose\_track~
*/
const string transposeTrackSpec =
"( ( \"Signature\" \"Syntax\" \"Meaning\" "
"\"Example\" ) "
"( <text>(midi int real) -> midi"
"</text--->"
"<text>_ transpose_track [_,_]</text--->"
"<text>Transposes the selected track inside the "
"specified Midi object by -second parameter-"
"number of halftones.including perussion channel</text--->"
"<text>query yoursong.mid transpose_track [2,1]</text--->"
") )";
/*
4.3.4 Definition of operator ~transpose\_track~
*/
Operator transpose_track (
"transpose_track", //name
transposeTrackSpec, //specification
transposeTrackFun, //value mapping
Operator::SimpleSelect, //trivial selection function
transposeTrackTypeMap //type mapping
);
/*
4.4 Operator ~transpose\_midi~
This operator transposes the specified Midi object by the number of specified
halftones.(exluding the precussion/drumchannel !!)
4.3.1 Type mapping function of operator ~transpose\_midi~
Operator ~transpose\_midi~ accepts a Midi object and an integer value. It
returns a Midi object.
---- (midi int ) -> midi
----
( please refer to function genericTrackTypeMap )
*/
/*
4.3.2 Value mapping functions of operator ~transpose\_midi~
*/
int transposeMidiFun(Word* args, Word& result, int message, Word& local,
Supplier s)
{
int errorinfo = 0;
Midi* currentMidi = (Midi*)args[0].addr;
CcInt* htsteps = (CcInt*)args[1].addr;
int stepval = htsteps->GetIntval();
Track* currentTrack;
Midi* transposedMidi = new Midi (true, currentMidi->GetDivisionMSB(),
currentMidi->GetDivisionLSB());
result = qp->ResultStorage(s);
if(stepval < 128 && stepval > -128)
{
transposedMidi->SetFormat(currentMidi->GetFormat());
int k = 1;
do
{
currentTrack = currentMidi->GetTrack(k-1);
currentTrack->Transpose(false,stepval,errorinfo);
transposedMidi->Append(currentTrack);
k++;
} while(!errorinfo && k< currentMidi->GetNumberOfTracks());
if(errorinfo)
{
cout << "crossing limits of playable values";
cout << endl << "in track " << k-1 << " !! ";
*(Midi *)result.addr = *currentMidi;
return 0;
}
}
else
{
cout << endl << "too many steps";
cout << endl << "no changes done";
*(Midi *)result.addr = *currentMidi;
return 0;
}
*(Midi *)result.addr = *transposedMidi;
delete currentTrack;
return 0;
}
/*
4.4.3 Specification of operator ~transpose\_midi~
*/
const string transposeMidiSpec =
"( ( \"Signature\" \"Syntax\" \"Meaning\" "
"\"Example\" ) "
"( <text>(midi int) -> midi"
"</text--->"
"<text>_ transpose_midi _ </text--->"
"<text>transposes the specified Midi object the given "
" parameter number of halftones, excluding percussion</text--->"
"<text>query mysong.mid transpose_midi [2] </text--->"
") )";
/*
4.3.4 Definition of operator ~transpose\_track~
*/
Operator transpose_midi (
"transpose_midi", //name
transposeMidiSpec, //specification
transposeMidiFun, //value mapping
Operator::SimpleSelect, //trivial selection function
genericTrackTypeMap //type mapping
);
/*
4.3 Operator ~extract\_lyrics~
This operator filters out the lyrics/text hidden in MetaEvents and puts out
an quite pretty formated text object. Using bool flags you can decide which
kind of meta(text) events are included in search.
Flags have following meaning:
~all~
all text events will be included - independend from other flags
(that means e.g. copyright information, cuepointer,
instrument information, track and file names,
stupid advertisement and other surprices :))
~lyr~
lyric events will be included (FF05)
~any~
anyText events will be included (FF01) (in karaoke files lyrics are mostly hidden here)
4.3.1 Type mapping function of operator ~extract\_lyrics~
Operator ~extract\_lyrics~ accepts a Midi object and returns a string object.
---- (midi, bool, bool, bool) -> text
----
*/
ListExpr extractLyricsTypeMap( ListExpr args )
{
if ( nl->ListLength(args) == 4 )
{
ListExpr arg1 = nl->First( args );
ListExpr arg2 = nl->Second( args );
ListExpr arg3 = nl->Third( args );
ListExpr arg4 = nl->Fourth( args );
if ( nl->IsEqual(arg1, Midi::BasicType()) &&
nl->IsEqual(arg2, CcBool::BasicType() ) &&
nl->IsEqual(arg3, CcBool::BasicType() ) &&
nl->IsEqual(arg4, CcBool::BasicType() ))
{
return nl->SymbolAtom(FText::BasicType());
}
}
return nl->SymbolAtom(Symbol::TYPEERROR());
}
/*
4.3.2 Value mapping functions of operator ~extract\_lyrics~
*/
int extractLyricsFun(Word* args, Word& result, int message, Word& local,
Supplier s)
{
Midi* currentMidi = (Midi*)args[0].addr;
bool all = ((CcBool*)args[1].addr)->GetBoolval();
bool lyr = ((CcBool*)args[2].addr)->GetBoolval();
bool any = ((CcBool*)args[3].addr)->GetBoolval();
string lyS;
currentMidi->GetLyrics( lyS, all, lyr, any );
FText* outText = new FText(false, NULL);
outText->Set(true,lyS.c_str());
result = qp->ResultStorage(s);
*((FText*)result.addr) = * outText;
return 0;
}
/*
4.3.3 Specification of operator ~extract\_lyrics~
*/
const string extractLyricsSpec =
"( ( \"Signature\" \"Syntax\" \"Meaning\" "
"\"Example\" ) "
"( <text>(midi text) -> string"
"</text--->"
"<text>_ extract_lyrics (_, _, _)</text--->"
"<text>Extracts the included text of the "
"midi object into a text object. "
"With 3 flags you "
"can decide which kind of text events are "
"included in extracting: "
"1.Flag TRUE: all kind of Text(FF01 ..FF0F) is incl, "
"2.Flag TRUE: all Lyric Events(FF05), "
"3.Flag TRUE: Any_Text_ Events (FF01). "
"If first flag is set the others do not matters "
"</text--->"
"<text>query hersong.mid extract_lyrics [FALSE,TRUE,FALSE)</text--->"
") )";
/*
4.3.4 Definition of operator ~extract\_lyrics~
*/
Operator extract_lyrics (
"extract_lyrics", //name
extractLyricsSpec, //specification
extractLyricsFun, //value mapping
Operator::SimpleSelect, //trivial selection function
extractLyricsTypeMap //type mapping
);
/*
4.3 Operator ~contains\_words~
This operator examines an existing Midi object and looks for a specified text
if it is included in lyricsevents.
4.3.1 Type mapping function of operator ~contains\_words~
Operator ~contains\_words~ accepts a Midi object and the text sequence to be
searched for. With the 3 flags you can specify the search for different kind
of Text - the same way like in extract\_lyrics operators. True will be returned
if the search was succesfull, false if not.
---- (midi, string, bool, bool, bool) -> bool
----
*/
ListExpr containsWordsTypeMap( ListExpr args )
{
if ( nl->ListLength(args) == 5 )
{
ListExpr arg1 = nl->First( args );
ListExpr arg2 = nl->Second( args );
ListExpr arg3 = nl->Third( args );
ListExpr arg4 = nl->Fourth( args );
ListExpr arg5 = nl->Fifth( args );
if ( nl->IsEqual(arg1, Midi::BasicType()) &&
nl->IsEqual( arg2, CcString::BasicType() ) &&
nl->IsEqual( arg3, CcBool::BasicType() ) &&
nl->IsEqual( arg4, CcBool::BasicType() ) &&
nl->IsEqual( arg5, CcBool::BasicType() ))
{
return nl->SymbolAtom(CcBool::BasicType());
}
}
return nl->SymbolAtom(Symbol::TYPEERROR());
}
/*
4.3.2 Value mapping functions of operator ~contains\_words~
*/
int containsWordsFun(Word* args, Word& result, int message, Word& local,
Supplier s)
{
Midi* currentMidi = (Midi*)args[0].addr;
CcString * tofind = (CcString*)args[1].addr;
bool all = ((CcBool*)args[2].addr)->GetBoolval();
bool lyr = ((CcBool*)args[3].addr)->GetBoolval();
bool any = ((CcBool*)args[4].addr)->GetBoolval();
string lyS;
currentMidi->GetLyrics( lyS, all,lyr,any );
result = qp->ResultStorage(s);
((CcBool*)result.addr)->
Set(true,(lyS.find(*(tofind->GetStringval())) != string :: npos));
return 0;
}
/*
4.3.3 Specification of operator ~contains\_words~
*/
const string containsWordsSpec =
"( ( \"Signature\" \"Syntax\" \"Meaning\" "
"\"Example\" ) "
"( <text>(midi string) -> bool"
"</text--->"
"<text>_ contains_words [_,_,_,_,_]</text--->"
"<text>Examines the selected Midi object if it "
"includes the searched word(string)."
"with 3 flags you can decide which kind"
" of text events are included in search: "
"1.Flag TRUE: all kind of text(FF01..FF0F) is incl, "
"2.Flag TRUE: all Lyric events(FF05), "
"3.Flag TRUE: any_Text_events (FF01) "
"If first flag is set the others no matters"
"</text--->"
"<text>query oursong.mid contains_words "
"[""jens"",TRUE,FALSE,FALSE]</text--->"
") )";
/*
4.3.4 Definition of operator ~contains\_words~
*/
Operator contains_words (
"contains_words", //name
containsWordsSpec, //specification
containsWordsFun, //value mapping
Operator::SimpleSelect, //trivial selection function
containsWordsTypeMap //type mapping
);
/*
4.3 Operator ~contains\_sequence~
This operator examines a track of an existing Midi object and looks for
a specified sequence of notes if it is included. The searched notes are
passed by a string in the syntax below.
$<input\_list> := <note>,<note\_list>$
$<note\_list> := <note> | <note>,<note\_list>$
$<note> := <note\_char><note\_No> |
<note\_char><flat\_sharp\_ident><note\_No>$
$<note\_char> := C | D | E | F | G | A | G | A | B$
$<note\_No> := -1 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9$
$<flat\_sharp\_ident> := b | \#$
The next argument allows transposing. If is set TRUE, the input list of
notes will be interpreted as a sequence of halftone steps.
The first occurrence of the sequence will be found. The last argument
specifies the to be examined track. A searching for a sequence with
this operator is only useful in tracks with a monophonic voice.
4.3.2 Type mapping function of operator ~contains\_sequence~
Operator ~contains\_sequence~ accepts a Midi object, the string with
the searched notes, a boolean value for allowed transposition and an
integer value representing the to be examined track.
If the search was succesfull, the position will be returned, -1 otherwise.
---- (midi string bool int) -> int
----
*/
ListExpr containsSequenceTypeMap( ListExpr args )
{
if ( nl->ListLength(args) == 4 )
{
ListExpr arg1 = nl->First(args);
ListExpr arg2 = nl->Second(args);
ListExpr arg3 = nl->Third(args);
ListExpr arg4 = nl->Fourth(args);
if ( nl->IsEqual(arg1, Midi::BasicType()) &&
nl->IsEqual(arg2, CcString::BasicType()) &&
nl->IsEqual(arg3, CcBool::BasicType()) &&
nl->IsEqual(arg4, CcInt::BasicType()) )
{
return nl->SymbolAtom(CcInt::BasicType());
}
}
return nl->SymbolAtom(Symbol::TYPEERROR());
}
/*
4.3.3 Value mapping functions of operator ~contains\_sequence~
Utility functions of operator ~contains\_sequence~
*/
void computeNoteOnEventsWithAbsTime(Track* track,
vector<EventWithAbsTime>& result)
/*
Computes the absolute time position of each note on event with a none zero
velocity and filters all other events of the given track.
*/
{
unsigned int absPos = 0;
unsigned char shortMessageType=0;
for (int i = 0; i < track->GetNumberOfEvents(); i++)
{
const Event* auxEvent = track->GetEvent(i);
Event *event = const_cast<Event*>( auxEvent );
if (event->GetEventType() == shortmessage &&
!event->GetShortMessageRunningStatus())
{
shortMessageType = event->GetShortMessageType();
}
if (event->GetEventType() == shortmessage &&
event->GetShortMessageRunningStatus())
{
event->SetShortMessageRunningStatus(false);
event->SetShortMessageType(shortMessageType);
}
absPos += event->GetDeltaTime();
if (event->GetEventType() == shortmessage &&
(event->GetShortMessageType() & 0xF0) == Event::NOTE_ON &&
event->GetShortMessageData(1))
{
EventWithAbsTime* eventStruct = new EventWithAbsTime;
eventStruct->absTime = absPos;
eventStruct->event = event;
result.push_back(*eventStruct);
}
}
}
int searchNoteSequenceAbsolute(vector<int>* searchedNotes,
Track* track)
/*
Searches for the passed sequence of note numbers in the specified track.
Returns the position in ticks if the sequence is found, -1
otherwise.
*/
{
vector<EventWithAbsTime> noteEvents;
computeNoteOnEventsWithAbsTime(track, noteEvents);
for (unsigned int i = 0; i < noteEvents.size() - searchedNotes->size(); i++)
{
bool found = true;
for (unsigned int j = 0; j < searchedNotes->size(); j++)
{
if (noteEvents[i + j].event->GetShortMessageData(0) !=
(*searchedNotes)[j])
{
found = false;
break;
}
}
if (found)
{
return noteEvents[i].absTime;
}
}
return ERROR_INT;
}
int searchNoteSequenceRelative(vector<int>* searchedNotes, Track* track)
/*
Searches for the passed sequence of notes in the specified track. This sequence
is translated in a sequence of musical intervals
and the functions searches for this interval sequence. Returns the position in
ticks if the sequence is found, -1 otherwise.
*/
{
unsigned int i, j, k;
vector<int> intervals(searchedNotes->size() - 1);
for ( i = 0; i < searchedNotes->size() - 1; i++)
{
intervals[i] = (*searchedNotes)[i + 1] - (*searchedNotes)[i];
}
vector<EventWithAbsTime> noteEvents;
computeNoteOnEventsWithAbsTime(track, noteEvents);
for ( j = 0; j < noteEvents.size() - searchedNotes->size(); j++)
{
bool found = true;
for ( k = 0; k < intervals.size(); k++)
{
int currentInterval =
noteEvents[j + k + 1].event->GetShortMessageData(0) -
noteEvents[j + k].event->GetShortMessageData(0);
if (currentInterval != intervals[k])
{
found = false;
break;
}
}
if (found)
{
return noteEvents[i].absTime;
}
}
return ERROR_INT;
}
/*
The value mapping function
*/
int containsSequenceFun(Word* args, Word& result, int message, Word& local,
Supplier s)
/*
contains predicate for a sequence inside the Midi object on one track
*/
{
Midi* midiIn = (Midi*) args[0].addr;
char* inputStr = (char*) (((CcString*) args[1].addr)->GetStringval());
bool transposeAllowed = ((CcBool*) args[2].addr)->GetBoolval();
int trackNo = ((CcInt*) args[3].addr)->GetIntval() - 1;
result = qp->ResultStorage(s);
if (trackNo < 1 || trackNo > midiIn->GetNumberOfTracks())
{
/*
''Official'' output of operator contains\_sequence. Please do not comment the
following line.
*/
cout << "Operator contains_sequence: Parameters are out of range.\n";
((CcInt*) result.addr)->Set(true, ERROR_INT);
return 0;
}
vector<int> noteList;
NoteStringToListParser noteParser;
bool parseResult = noteParser.ParseNoteString(inputStr, &noteList);
Track* track = midiIn->GetTrack(trackNo);
int resultValue = ERROR_INT;
if (parseResult && noteList.size() > 1)
{
if (transposeAllowed)
{
resultValue = searchNoteSequenceRelative(&noteList, track);
}
else
{
resultValue = searchNoteSequenceAbsolute(&noteList, track);
}
}
else
{
/*
''Official'' output of operator contains\_sequence. Please do not comment the
following line.
*/
cout << "Operator contains_sequence: Syntax error in search string.\n";
}
delete track;
((CcInt*) result.addr)->Set(true, resultValue);
return 0;
}
/*
4.3.4 Specification of operator ~contains\_sequence~
*/
const string containsSequenceSpec =
"( ( \"Signature\" \"Syntax\" \"Meaning\" "
"\"Example\" ) "
"( <text>(midi string bool int) -> int"
"</text--->"
"<text>_ contains_sequence [_,_,_]</text--->"
"<text>Examines the selected Midi object if it "
"contains the specified sequence of notes. The second argument "
"allows transposing. If is set TRUE, the input list of notes will be "
"interpreted as a sequence of halftone steps. The first occurrence of "
"the sequence will be found. The third argument specifies the to be "
"examined track. A searching for a sequence with this operator is "
"only useful in tracks with a monophonic voice.</text--->"
"<text>query mymidi contains_sequence [\"D4,F#4,A4\", TRUE, 2]</text--->"
") )";
/*
4.3.5 Definition of operator ~contains\_sequence~
*/
Operator contains_sequence (
"contains_sequence", //name
containsSequenceSpec, //specification
containsSequenceFun, //value mapping
Operator::SimpleSelect, //trivial selection function
containsSequenceTypeMap //type mapping
);
/*
4.4 Operator ~saveto~
This operator saves a Midi object to a file.
4.4.1 Type mapping function of operator ~saveto~
The operator ~saveto~ accepts a Midi object and a string representation of the
file name. It will return a bool with the value true if the saving is
succesfull, false otherwise.
*/
ListExpr SavetoMidiTypeMap (ListExpr args)
{
if (nl->ListLength(args) == 2)
{
ListExpr first = nl->First(args);
ListExpr second = nl->Second(args);
if (nl->IsAtom(first) && nl->IsEqual(first, Midi::BasicType()) &&
nl->IsAtom(second) && nl->IsEqual(second, CcString::BasicType()))
{
return nl->SymbolAtom(CcBool::BasicType());
}
}
return nl->SymbolAtom(Symbol::TYPEERROR());
}
/*
4.4.2 Value mapping function of operator ~saveto~
---- (midi string ) -> bool
----
*/
int SavetoMidiFun(Word* args, Word& result, int message, Word& local,
Supplier s)
{
result = qp->ResultStorage(s);
Midi* midi = (Midi*) (args[0].addr);
CcString* filename = (CcString*) args[1].addr;
if (!midi->IsDefined())
{
((CcBool*) result.addr)->Set(true, false);
}
vector<unsigned char> byteVector;
midiToBytes(midi, byteVector);
char* byteArray = new char[byteVector.size()];
for (unsigned int i = 0; i < byteVector.size(); i++)
{
byteArray[i] = byteVector[i];
}
char* fname = (char*) (filename->GetStringval());
ofstream out(fname, ios::out | ios::binary);
bool writeResult = false;
if (out)
{
out.write(byteArray, byteVector.size());
writeResult = out.good();
}
else
{
writeResult = false;
}
delete[] byteArray;
((CcBool*) result.addr)->Set(true, writeResult);
return 0;
}
/*
4.4.3 Specification of operator ~saveto~
*/
const string SavetoMidiSpec =
"( ( \"Signature\" \"Syntax\" \"Meaning\" "
"\"Example\" ) "
"( <text>(midi string) -> bool"
"</text--->"
"<text>_ saveto _</text--->"
"<text>Saves the selected Midi object to a file.</text--->"
"<text>query m saveto \"myfile.mid\"</text--->"
") )";
/*
4.4.4 Definition of operator ~saveto~
*/
Operator savetoMidi (
"saveto", //name
SavetoMidiSpec, //specification
SavetoMidiFun, //value mapping
Operator::SimpleSelect, //trivial selection function
SavetoMidiTypeMap //type mapping
);
/*
4.5 Operator ~tempo\_bpm~
This operator returns the tempo of a Midi object shown as the number beats per
minute.
4.5.1 Type mapping function of operator ~tempo\_bpm~
The operator ~tempo\_bpm~ accepts a Midi object and return an integer object.
---- (midi) -> int
----
*/
ListExpr genericMidiTypeMap( ListExpr args )
{
if ( nl->ListLength(args) == 1 )
{
ListExpr arg1 = nl->First(args);
if ( nl->IsEqual(arg1, Midi::BasicType()))
{
return nl->SymbolAtom(CcInt::BasicType());
}
}
return nl->SymbolAtom(Symbol::TYPEERROR());
}
/*
4.5.2 Value mapping functions of operator ~tempo\_bpm~
*/
template<bool isBpm> int tempoFun(Word* args, Word& result, int message,
Word& local, Supplier s)
/*
Describes the velocity of a Midi object
*/
{
unsigned long value;
vector<unsigned char> meta;
Midi *currentMidi = (Midi*)args[0].addr;
int currentTracks = currentMidi->GetNumberOfTracks();
/*
Gets the Midi object out of the argument list
*/
bool found = false;
CcString* metaEventValue = new CcString();
for ( int i = 0; i < currentTracks && !found; i++ )
{
metaEventValue = FindMetaEvent(currentMidi,Event::TEMPO,i);
if (metaEventValue->IsDefined())
{
found = true;
}
}
/*
Walks through all tracks and searches for the MetaEvent ''tempo'' from the Midi
object, the value ~defined~ from CcString
is used to declare a valid result ( ''undefined'' would say no result was
received ).
Normally the metaEvent ''Tempo'' is located inside the first track. To be sure
all tracks are examined.
*/
result = qp->ResultStorage(s);
if (found)
{
char* metaString = (char*)metaEventValue->GetStringval();
for ( int i = 0; i < 3; i++)
{
unsigned char c = (unsigned char)metaString[i];
meta.push_back(c);
}
if (isBpm)
{
value = 60 * 1000 * 1000 / BytesToIntNoClearMSB(&meta);
/*
Conversion from microseconds per quarter note into beats per minute
*/
}
else
{
value = BytesToIntNoClearMSB(&meta);
}
}
else
{
value = 0;
}
((CcInt*)result.addr)->Set(metaEventValue->IsDefined(), (int)value);
/*
The first argument says the integer value is defined, the second is the integer
value
*/
return 0;
}
/*
4.5.3 Specification of operator ~tempo\_bpm~
*/
const string tempoBpmSpec =
"( ( \"Signature\" \"Syntax\" \"Meaning\" "
"\"Example\" ) "
"( <text>(midi) -> int"
"</text--->"
"<text>_ tempo_bpm</text--->"
"<text>Returns the tempo of the selected "
"Midi object. It is shown as BPM "
"(beats per minute ).</text--->"
"<text>query midifile tempo_bpm</text--->"
") )";
/*
4.5.4 Definition of operator ~tempo\_bpm~
*/
Operator tempo_bpm (
"tempo_bpm", //name
tempoBpmSpec, //specification
tempoFun<true>, //value mapping
Operator::SimpleSelect, //trivial selection function
genericMidiTypeMap //type mapping
);
/*
4.5 Operator ~tempo\_ms~
This operator returns the tempo of a Midi object shown as the number of
microseconds per quarter note.
4.5.1 Type mapping function of operator ~tempo\_ms~
The operator ~tempo\_ms~ accepts a Midi object and return an integer object.
---- (midi) -> int
----
( please refer to function genericMidiTypeMap )
*/
/*
4.5.2 Value mapping functions of operator ~tempo\_ms~
( please refer to function tempoBpm )
*/
/*
4.5.3 Specification of operator ~tempo\_ms~
*/
const string tempoMsSpec =
"( ( \"Signature\" \"Syntax\" \"Meaning\" "
"\"Example\" ) "
"( <text>(midi) -> int"
"</text--->"
"<text>_ tempo_ms</text--->"
"<text>Returns the tempo of the selected "
"Midi object. It is shown as number of "
"microseconds per quarter note.</text--->"
"<text>query midifile tempo_ms</text--->"
") )";
/*
4.5.4 Definition of operator ~tempo\_ms~
*/
Operator tempo_ms (
"tempo_ms", //name
tempoMsSpec, //specification
tempoFun<false>, //value mapping
Operator::SimpleSelect, //trivial selection function
genericMidiTypeMap //type mapping
);
/*
4.6 Operator ~format~
This operator returns the format of a Midi object.
4.6.1 Type mapping function of operator ~format~
The operator ~format~ accepts a Midi object and returns an integer object.
---- (midi) -> int
----
( please refer to function genericTrackTypeMap )
*/
/*
4.6.2 Value mapping functions of operator ~format~
*/
int formatFun(Word* args, Word& result, int message, Word& local,
Supplier s)
/*
Describes the format of a Midi object
*/
{
Midi *currentMidi = (Midi*)args[0].addr;
int format = currentMidi->GetFormat();
/*
Gets the Midi object out of the argument list and determines the format of this
Midi object
*/
result = qp->ResultStorage(s);
if ( format >= MIN_FORMAT_MIDI && format <= MAX_FORMAT_MIDI )
{
((CcInt*)result.addr)->Set(true, format);
}
else
{
((CcInt*)result.addr)->Set(false, format);
}
/*
The first argument says if the integer value is defined or not, the second is
the integer value. The format will be returned as undefined, if the value is
not inside their specified range.
*/
return 0;
}
/*
4.6.3 Specification of operator ~format~
*/
const string formatSpec =
"( ( \"Signature\" \"Syntax\" \"Meaning\" "
"\"Example\" ) "
"( <text>(midi) -> int"
"</text--->"
"<text>_ format</text--->"
"<text>Returns the format of the selected "
"Midi object. It is shown as integer 0, "
"1 or 2.</text--->"
"<text>query midifile format</text--->"
") )";
/*
4.6.4 Definition of operator ~format~
*/
Operator format (
"format", //name
formatSpec, //specification
formatFun, //value mapping
Operator::SimpleSelect, //trivial selection function
genericMidiTypeMap //type mapping
);
/*
4.7 Operator ~count\_tracks~
This operator returns the number of used tracks of a Midi object.
4.7.1 Type mapping function of operator ~count\_tracks~
The operator ~count\_tracks~ accepts a Midi object and returns an integer
object.
---- (midi) -> int
----
( please refer to function genericMidiTypeMap )
*/
/*
4.7.2 Value mapping functions of operator ~count\_tracks~
*/
int countTracksFun(Word* args, Word& result, int message, Word& local,
Supplier s)
/*
Counts the number of tracks of a Midi object
*/
{
Midi *currentMidi = (Midi*)args[0].addr;
int tracks = currentMidi->GetNumberOfTracks();
/*
Gets the Midi object out of the argument list and determines the number of
tracks
*/
result = qp->ResultStorage(s);
((CcInt*)result.addr)->Set(true, tracks);
/*
The first argument says the integer value is defined, the second is the integer
value
*/
return 0;
}
/*
4.7.3 Specification of operator ~count\_tracks~
*/
const string countTracksSpec =
"( ( \"Signature\" \"Syntax\" \"Meaning\" "
"\"Example\" ) "
"( <text>(midi) -> int"
"</text--->"
"<text>_ count_tracks</text--->"
"<text>Returns the number of used tracks "
"of the selected Midi object.</text--->"
"<text>query midifile count_tracks</text--->"
") )";
/*
4.7.4 Definition of operator ~count\_tracks~
*/
Operator count_tracks (
"count_tracks", //name
countTracksSpec, //specification
countTracksFun, //value mapping
Operator::SimpleSelect, //trivial selection function
genericMidiTypeMap //type mapping
);
/*
4.8 Operator ~count\_channels~
This operator returns the number of used channels inside a Midi object.
4.8.1 Type mapping function of operator ~count\_channels~
The operator ~count\_channels~ accepts a Midi object and an integer object. It
returns an integer object.
---- (midi int) -> int
----
*/
ListExpr countChannelsTypeMap( ListExpr args )
{
if ( nl->ListLength(args) == 2 )
{
ListExpr arg1 = nl->First(args);
ListExpr arg2 = nl->Second(args);
if ( nl->IsEqual(arg1, Midi::BasicType()) &&
nl->IsEqual(arg2, CcInt::BasicType()))
{
return arg2;
}
}
return nl->SymbolAtom(Symbol::TYPEERROR());
}
/*
4.8.2 Value mapping functions of operator ~count\_channels~
*/
int countChannelsFun(Word* args, Word& result, int message, Word& local,
Supplier s)
/*
counts the number of channels of a Midi object
*/
{
const Event* currentEvent;
int foundChannel = 0;
int countedChannels = 0;
bool channels[MAX_CHANNELS_MIDI];
Midi *currentMidi = (Midi*)args[0].addr;
int track = ((CcInt*)args[1].addr)->GetIntval();
/*
gets the Midi object and the to be examined track number out of the argument
list
*/
result = qp->ResultStorage(s);
if ( track >= MIN_TRACKS_MIDI &&
track <= currentMidi->GetNumberOfTracks() )
{
track = track - 1;
Track* currentTrack = currentMidi->GetTrack(track);
int numberEvents = currentTrack->GetNumberOfEvents();
for ( int j = 0; j < MAX_CHANNELS_MIDI; j++ )
{
channels[j] = false;
}
/*
initialises the array for counted channels
*/
for ( int eventIndex = 0; eventIndex < numberEvents; eventIndex++)
{
currentEvent = currentTrack->GetEvent(eventIndex);
if ( currentEvent->GetEventType() == shortmessage &&
!currentEvent->GetShortMessageRunningStatus())
{
foundChannel = currentEvent->GetChannel();
if ( !channels[foundChannel] )
{
channels[foundChannel] = true;
countedChannels++;
}
}
}
/*
Walks through the whole track and counts the number of *different* channels
inside
*/
((CcInt*)result.addr)->Set(true, countedChannels);
/*
The first argument says the integer value is defined, the second is the integer
value
*/
}
else
{
cout << "Error in count_channels: Parameter is out of range, ";
cout << "the result is set to 0." << endl;
((CcInt*)result.addr)->Set(true, 0);
/*
Error before track deletion ( too few remaining tracks inside the Midi Object )
[->] return original Midi object
*/
}
return 0;
}
/*
4.8.3 Specification of operator ~count\_channels~
*/
const string countChannelsSpec =
"( ( \"Signature\" \"Syntax\" \"Meaning\" "
"\"Example\" ) "
"( <text>(midi) -> int"
"</text--->"
"<text>_ count_channels[_]</text--->"
"<text>Returns the number of used channels "
"inside the specified track "
"of the selected Midi object."
"If the selected track is outside the "
"valid range of the Midi file, the return "
"value is set to 0.</text--->"
"<text>query midifile count_channels[2]</text--->"
") )";
/*
4.8.4 Definition of operator ~count\_channels~
*/
Operator count_channels (
"count_channels", //name
countChannelsSpec, //specification
countChannelsFun, //value mapping
Operator::SimpleSelect, //trivial selection function
countChannelsTypeMap //type mapping
);
/*
4.9 Operator ~track\_name~
This operator returns the name of the specified track number.
4.9.1 Type mapping function of operator ~track\_name~
The operator ~track\_name~ accepts a Midi object and an integer object. It
returns a string object.
---- (midi int) -> string
----
*/
ListExpr trackStringTypeMap( ListExpr args )
{
if ( nl->ListLength(args) == 2 )
{
ListExpr arg1 = nl->First(args);
ListExpr arg2 = nl->Second(args);
if ( nl->IsEqual(arg1, Midi::BasicType()) &&
nl->IsEqual(arg2, CcInt::BasicType()) )
{
return nl->SymbolAtom(CcString::BasicType());
}
}
return nl->SymbolAtom(Symbol::TYPEERROR());
}
/*
4.9.2 Value mapping functions of operator ~track\_name~
*/
int trackNameFun(Word* args, Word& result, int message, Word& local,
Supplier s)
/*
Returns the name of the selected track out of a Midi object
*/
{
Midi *currentMidi = (Midi*)args[0].addr;
int namedTrack = ((CcInt*)args[1].addr)->GetIntval() - 1;
/*
gets the Midi object and the to be examined track number out of the argument
list
*/
CcString* metaEventValue = FindMetaEvent(currentMidi, Event::TRACK_NAME,
namedTrack);
/*
Searches for the track name in the specified track
*/
result = qp->ResultStorage(s);
((CcString*)result.addr)->Set(metaEventValue->IsDefined(),
metaEventValue->GetStringval());
/*
Examination was succesfull [->] return text string
*/
return 0;
}
/*
4.9.3 Specification of operator ~track\_name~
*/
const string trackNameSpec =
"( ( \"Signature\" \"Syntax\" \"Meaning\" "
"\"Example\" ) "
"( <text>(midi int) -> string"
"</text--->"
"<text>_ track_name[_]</text--->"
"<text>Returns the name of the selected "
"track.</text--->"
"<text>query midifile track_name [2]</text--->"
") )";
/*
4.9.4 Definition of operator ~track\_name~
*/
Operator track_name (
"track_name", //name
trackNameSpec, //specification
trackNameFun, //value mapping
Operator::SimpleSelect, //trivial selection function
trackStringTypeMap //type mapping
);
/*
4.10 Operator ~time\_signature~
This operator returns the time signature of a Midi object.
4.10.1 Type mapping function of operator ~time\_signature~
The operator ~time\_signature~ accepts a Midi object and returns a string
object.
---- (midi) -> string
----
*/
ListExpr stringTypeMap( ListExpr args )
{
if ( nl->ListLength(args) == 1 )
{
ListExpr arg1 = nl->First(args);
if ( nl->IsEqual(arg1, Midi::BasicType()) )
{
return nl->SymbolAtom(CcString::BasicType());
}
}
return nl->SymbolAtom(Symbol::TYPEERROR());
}
/*
4.10.2 Value mapping functions of operator ~time\_signature~
*/
template<bool isBeat> int timeSignatureFun(Word* args, Word& result, int
message, Word& local, Supplier s)
/*
Returns the time signature of a Midi object
*/
{
string signature;
Midi *currentMidi = (Midi*)args[0].addr;
int currentTracks = currentMidi->GetNumberOfTracks();
/*
Gets the Midi object and the number of used tracks out of the argument list
*/
bool found = false;
CcString* metaEventValue = new CcString();
for ( int i = 0; i < currentTracks && !found; i++ )
{
metaEventValue = FindMetaEvent(currentMidi,Event::TIME_SIGNATURE,i);
if (metaEventValue->IsDefined())
{
found = true;
}
}
/*
Walks through all tracks and searches for the MetaEvent ''time\_signature''
from the Midi object, the value ~defined~ from CcString
is used to declare a valid result ( undefined would say no result was received
)
*/
signature.clear();
/*
An empty result string is always good, it is filled only if defined. If not
then it will be returned undefined and empty.
*/
if (metaEventValue->IsDefined())
{
unsigned char* metaEventBytes = (unsigned char*)
metaEventValue->GetStringval();
/*
Gets the defined result and moves it into a vector for further processing
*/
vector<unsigned char> temp;
temp.push_back(metaEventBytes[0]);
unsigned short int numerator = Event::ComputeBytesToInt(&temp);
temp.clear();
/*
Processes the numerator, together with the denominator they are representing
the time signature as the Midi would be notated
*/
temp.push_back(metaEventBytes[1]);
unsigned short int denominator = Event::ComputeBytesToInt(&temp);
denominator = (unsigned int) pow((double)denominator, 2);
temp.clear();
/*
Processes the denominator, inside a Midi it is stored as a negative power of 2
[->] a 3 means an eight-note
*/
temp.push_back(metaEventBytes[2]);
unsigned short int midiClocks = Event::ComputeBytesToInt(&temp);
temp.clear();
/*
Processes the Midi clocks, a value expressing the number of Midi clocks in a
metronome click
*/
temp.push_back(metaEventBytes[3]);
unsigned short int notation = Event::ComputeBytesToInt(&temp);
temp.clear();
/*
Processes the notation, a value expressing the number of notated 32nd-notes in
a Midi quarter-note ( 24 Midi clocks )
*/
string c = IntToString(numerator);
signature.assign(c);
signature.append("/");
c = IntToString(denominator);
signature.append(c);
if (!isBeat)
{
signature.append(" ");
c = IntToString(midiClocks);
signature.append(c);
signature.append(" ");
c = IntToString(notation);
signature.append(c);
}
signature.append("\0");
/*
Return string is built out of numerator, denominator, midiClocks and notation
for TIME\_SIGNATURE. For BEAT only numerator and denominator are built
together.
*/
}
result = qp->ResultStorage(s);
((CcString*)result.addr)->Set(metaEventValue->IsDefined(),
(STRING_T*)signature.c_str());
/*
Examination was succesfull [->] return text string
*/
return 0;
}
/*
4.10.3 Specification of operator ~time\_signature~
*/
const string timeSignatureSpec =
"( ( \"Signature\" \"Syntax\" \"Meaning\" "
"\"Example\" ) "
"( <text>(midi) -> string"
"</text--->"
"<text>_ time_signature</text--->"
"<text>Returns the time signature "
"of the selected Midi object.</text--->"
"<text>query midifile time_signature</text--->"
") )";
/*
4.10.4 Definition of operator ~time\_signature~
*/
Operator time_signature (
"time_signature", //name
timeSignatureSpec, //specification
timeSignatureFun<false>, //value mapping
Operator::SimpleSelect, //trivial selection function
stringTypeMap //type mapping
);
/*
4.11 Operator ~beat~
This operator returns the beat of the Midi.
4.11.1 Type mapping function of operator ~beat~
The operator ~beat~ accepts a Midi object and returns an string object.
---- (midi) -> string
----
( please refer to function stringTypeMap )
*/
/*
4.11.2 Value mapping functions of operator ~beat~
( please refer to function timeSignatureFun )
*/
/*
4.11.3 Specification of operator ~beat~
*/
const string beatSpec =
"( ( \"Signature\" \"Syntax\" \"Meaning\" "
"\"Example\" ) "
"( <text>(midi) -> string"
"</text--->"
"<text>_ beat</text--->"
"<text>Returns the beat of the "
"Midi object shown as a string."
"Example of result : 3/4</text--->"
"<text>query midifile beat</text--->"
") )";
/*
4.11.4 Definition of operator ~beat~
*/
Operator beat (
"beat", //name
beatSpec, //specification
timeSignatureFun<true>, //value mapping
Operator::SimpleSelect, //trivial selection function
stringTypeMap //type mapping
);
/*
4.11 Operator ~instrument\_name~
This operator returns the name of the instrument for a track.
4.11.1 Type mapping function of operator ~instrument\_name~
The operator ~instrument\_name~ accepts a Midi object and an integer value. It
returns an string object.
---- (midi int) -> string
----
( please refer to function trackStringTypeMap )
*/
/*
4.11.2 Value mapping functions of operator ~instrument\_name~
*/
int instrumentNameFun(Word* args, Word& result, int message, Word& local,
Supplier s)
/*
Returns the name of the used instrument in a track
*/
{
Midi *currentMidi = (Midi*)args[0].addr;
int currentTrack = ((CcInt*)args[1].addr)->GetIntval() - 1;
/*
Gets the Midi object out of the argument list and gets the number of used
tracks
*/
CcString* metaEventValue = new CcString();
metaEventValue = FindMetaEvent(currentMidi, Event::INSTRUMENT_NAME,
currentTrack);
/*
Walks through all tracks and searches for the MetaEvent ''instrument\_name''
from the track object, the value ~defined~ from
CcString is used to declare a valid result ( undefined would say no result was
received )
*/
result = qp->ResultStorage(s);
((CcString*)result.addr)->Set(metaEventValue->IsDefined(),
metaEventValue->GetStringval());
/*
Examination was succesfull [->] return text string
*/
return 0;
}
/*
4.11.3 Specification of operator ~instrument\_name~
*/
const string instrumentNameSpec =
"( ( \"Signature\" \"Syntax\" \"Meaning\" "
"\"Example\" ) "
"( <text>(midi int) -> string"
"</text--->"
"<text>_ instrument_name[_]</text--->"
"<text>Returns the name of the used "
"instrument inside the track.</text--->"
"<text>query midifile instrument_name[3]</text--->"
") )";
/*
4.11.4 Definition of operator ~instrument\_name~
*/
Operator instrument_name (
"instrument_name", //name
instrumentNameSpec, //specification
instrumentNameFun, //value mapping
Operator::SimpleSelect, //trivial selection function
trackStringTypeMap //type mapping
);
/*
4.12 Operator ~get\_name~
This operator returns the name of a Midi object.
4.10.1 Type mapping function of operator ~get\_name~
The operator ~get\_name~ accepts a Midi object and returns a string object.
---- (midi) -> string
----
( please refer to function stringTypeMap )
*/
/*
4.12.2 Value mapping functions of operator ~get\_name~
*/
int getNameMidiFun(Word* args, Word& result, int message, Word& local,
Supplier s)
/*
Returns the name of a Midi object
*/
{
Midi *currentMidi = (Midi*)args[0].addr;
CcString* seqName = FindMetaEvent(currentMidi, Event::TRACK_NAME, 0);
/*
Searches in track 1 for the MetaEvent ''track\_name'' from the Midi object.
According to the specification the name of format 0 and 1 Midi files is inside
track 1.
*/
result = SetWord(seqName);
return 0;
}
/*
4.12.3 Specification of operator ~get\_name~
*/
const string getNameMidiSpec =
"( ( \"Signature\" \"Syntax\" \"Meaning\" "
"\"Example\" ) "
"( <text>(midi) -> string"
"</text--->"
"<text>_ get_name</text--->"
"<text>Returns the name of the "
"selected Midi object. The name is taken "
" out of track 1</text--->"
"<text>query midifile get_name</text--->"
") )";
/*
4.12.4 Definition of operator ~get\_name~
*/
Operator get_name_midi (
"get_name", //name
getNameMidiSpec, //specification
getNameMidiFun, //value mapping
Operator::SimpleSelect, //trivial selection function
stringTypeMap //type mapping
);
} // namespace midialgebra
/*
5 Creating the Algebra
*/
using namespace midialgebra;
class MidiAlgebra : public Algebra
{
public:
MidiAlgebra() : Algebra()
{
AddTypeConstructor( &midi );
midi.AssociateKind( Kind::DATA() );
AddOperator( &extract_track );
AddOperator( &merge_tracks );
AddOperator( &transpose_track );
AddOperator( &transpose_midi );
AddOperator( &extract_lyrics );
AddOperator( &contains_words );
AddOperator( &contains_sequence );
AddOperator( &delete_track );
AddOperator( &expand_track );
AddOperator( &savetoMidi );
AddOperator( &tempo_ms );
AddOperator( &tempo_bpm );
AddOperator( &format );
AddOperator( &count_tracks );
AddOperator( &track_name );
AddOperator( &time_signature );
AddOperator( &beat );
AddOperator( &instrument_name );
AddOperator( &count_channels );
AddOperator( &get_name_midi );
}
~MidiAlgebra() {};
};
/*
6 Initialization
Each algebra module needs an initialization function. The algebra manager has a
reference to this function if this algebra is included in the listof required
algebras, thus forcing the linker to include this module.
The algebra manager invokes this function to get a reference to the instanceof
the algebra class and to provide references to the globalnested listcontainer
(used to store constructor, type, operator and object information)and to the
query processor.
The function has a C interface to make it possible to load the algebra
dynamically at runtime.
*/
extern "C"
Algebra*
InitializeMidiAlgebra( NestedList* nlRef, QueryProcessor* qpRef )
{
nl = nlRef;
qp = qpRef;
return (new MidiAlgebra());
}