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

8602 lines
213 KiB
Plaintext

/*
----
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 [2] Title: [{\large \begin {center}] [\end {center}}]
//[ae] [\"a]
//[ue] [\"u]
[1] MidiAlgebra
[2] Fachpraktikum 1591
[2] February 2004
[2] Florian B[ue]ttner, Jens Fache, Christopher Kroll, Volker L[ae]sche
[2] FernUniversit[ae]t Hagen
[1] Abstract
Midi is a standardized format to provide a way of interchanging time-stamped MIDI data between different programs on the same or different computers. One of the primary design goals was compact representation, which makes it very appropriate for disk-based file format, but less appropriate for quick access by a sequencer program.
This MidiAlgebra introduces Midi files to SECONDO. It is completely integrated into the persistency mechanism of SECONDO. Therefore you can store Midi files inside relations as well as use Midi objects as results of SECONDO queries. We are providing several operators to extract information from Midi files or to create new Midi files depending on the users desire.
The also available MidiViewer is a java-based Viewer using a graphical interface with comparable functionality to SECONDO. The big advantage of the Viewer beyond SECONDO is the opportunity to play Midi files and use additional features e.g. easily mute single tracks of a Midi. There you can also check the results you did to a Midi file using the inside SECONDO implemented operators.
*/
/*
----
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
----
\pagebreak
[1] Introduction
MIDI means *M*usical*I*nstrument*D*igital*I*nterface\footnote{www.midi.org} and is a standardized format to provide a way of interchanging time-stamped MIDI data between different programs on the same or different computers. It was originally designed by a group of manufacturers of electronical instruments ( MMA - Midi Manufacturer Association ) to connect their products to each other. Until then every manufacturer had designed their own languages. The created music was only usable on this only product and could not played on electronical instruments from other manufacturers.
A MIDI is not really music. It consists of all information necessary to *produce* music on electronical instruments. That means you have control information which can be converted by these intruments into hearable music. Finally no vocals are available.
Normally a MIDI consists of a header, some tracks and a lot of events. All these information are sequentielly organised inside a file. The header includes some basic information like format and number of tracks. Every tracks stores a variable number of single events. There are 3 different kinds of events existing. ~MidiEvents~ for music control, ~MetaEvents~ for general information and ~SystemEvents~ for system control. Sometimes you will find the expression message instead of event but both means the same.
The ~MidiEvents~ stores the control about e.g. notes ( on / off ), program or control changes and pitch wheel changes. These information is only valid for a specific note at a fixed position inside a track.
The ~MetaEvents~ stores general information about the song stored inside the Midi file. They provides i.e. copyright data, instrument names, the song lyrics and so on. You see, all these data are valid for the whole Midi file or at least for a specific track.
The ~SystemEvents~ includes global song messages like start, stop, continue and so on.
Midi is a standardized format to provide a way of interchanging time-stamped MIDI data between different programs on the same or different computers. One of the primary design goals was compact representation, which makes it very appropriate for disk-based file format, but less appropriate for quick access by a sequencer program.
This MidiAlgebra introduces Midi files to SECONDO. It is completely integrated into the persistency mechanism of SECONDO. Therefore you can store Midi files inside relations as well as use Midi objects as results of SECONDO queries. We are providing several operators to extract information from Midi files or to create new Midi files depending on the users desire.
The also available MidiViewer is a java-based Viewer using a graphical interface with comparable functionality to SECONDO. The big advantage of the Viewer beyond SECONDO is the opportunity to play Midi files and use additional features e.g. easily mute single tracks of a Midi. There you can also check the results you did to a Midi file using the inside SECONDO implemented operators.
At the moment the MidiAlgebra supports only Midi files with format 1, because only a few Midi files are created using the other two formats. You need not to care about this issue, because all formats can be read and stored. But not all operators are released for using all possible formats.
*/
/*
----
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]
\pagebreak
[TOC]
\pagebreak
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 ~StandardAttribute~. 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 "StandardAttribute.h"
#include "FLOB.h"
#include "Base64.h"
#include "MidiAlgebra.h"
#include "FTextAlgebra.h"
#include <string>
#include <cmath>
#include <vector>
#include <fstream>
#include <sstream>
using namespace std;
using namespace midialgebra;
extern NestedList* nl;
extern QueryProcessor *qp;
/*
The using of a namespace avoids the modifier ''static'' in function
declarations.
*/
namespace {
/*
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& midiIn)
{
Midi* midi = const_cast<Midi*>(&midiIn);
defined = midi->defined;
divisionMSB = midi->divisionMSB;
divisionLSB = midi->divisionLSB;
lengthOfHeader = midi->lengthOfHeader;
format = midi->format;
isDeletable = midi->isDeletable;
listOfTracks = DBArray<TrackEntry>(0);
for (int i = 0; i < midi->listOfTracks.Size(); i++)
{
TrackEntry trackEntry;
midi->listOfTracks.Get(i, trackEntry);
TrackEntry* newTrackEntry = new TrackEntry;
*newTrackEntry = trackEntry;
listOfTracks.Append(*newTrackEntry);
}
listOfEvents = DBArray<EventEntry>(0);
for (int i = 0; i < midi->listOfEvents.Size(); i++)
{
EventEntry eventEntry;
midi->listOfEvents.Get(i, eventEntry);
EventEntry* newEventEntry = new EventEntry;
*newEventEntry = eventEntry;
listOfEvents.Append(*newEventEntry);
}
eventData = DBArray<unsigned char>(0);
for (int i = 0; i < midi->eventData.Size(); i++)
{
unsigned char data;
midi->eventData.Get(i, data);
unsigned char* newData = new unsigned char(data);
eventData.Append(*newData);
}
}
/*
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& midiIn)
{
if (this == &midiIn)
{
return *this;
}
Midi* midi = const_cast<Midi*>(&midiIn);
defined = midi->defined;
divisionMSB = midi->divisionMSB;
divisionLSB = midi->divisionLSB;
lengthOfHeader = midi->lengthOfHeader;
format = midi->format;
isDeletable = midi->isDeletable;
if (midi->listOfTracks.Size() == 0)
{
listOfTracks.Clear();
}
else
{
listOfTracks.Resize(midi->listOfTracks.Size());
}
for (int i = 0; i < midi->listOfTracks.Size(); i++)
{
TrackEntry trackEntry;
midi->listOfTracks.Get(i, trackEntry);
TrackEntry* newTrackEntry = new TrackEntry;
*newTrackEntry = trackEntry;
listOfTracks.Append(*newTrackEntry);
}
if (midi->listOfEvents.Size() == 0)
{
listOfEvents.Clear();
}
else
{
listOfEvents.Resize(midi->listOfEvents.Size());
}
for (int i = 0; i < midi->listOfEvents.Size(); i++)
{
EventEntry eventEntry;
midi->listOfEvents.Get(i, eventEntry);
EventEntry* newEventEntry = new EventEntry;
*newEventEntry = eventEntry;
listOfEvents.Append(*newEventEntry);
}
if (midi->eventData.Size() == 0)
{
eventData.Clear();
}
else
{
eventData.Resize(midi->eventData.Size());
}
for (int i = 0; i < midi->eventData.Size(); i++)
{
unsigned char data;
midi->eventData.Get(i, data);
unsigned char* newData = new unsigned char(data);
eventData.Append(*newData);
}
return *this;
}
/*
2.1 GetTrack
Returns the by index selected track of this ~Midi~ object.
*/
Track* Midi::GetTrack(int index)
{
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 eventEntry;
listOfEvents.Get(eventPtr++, eventEntry);
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,(unsigned char) 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(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();
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)
{
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++)
{
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()
{
if(!defined)
{
return (0);
}
else
{
double long h;
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 StandardAttribute and copies all information from it
*/
void Midi::CopyFrom(StandardAttribute* right)
{
Midi* midi = (Midi*) right;
this->defined = midi->defined;
this->format = midi->format;
this->isDeletable = midi->isDeletable;
this->lengthOfHeader = midi->lengthOfHeader;
this->divisionMSB = midi->divisionMSB;
this->divisionLSB = midi->divisionLSB;
if (midi->listOfTracks.Size() == 0)
{
listOfTracks.Clear();
}
else
{
listOfTracks.Resize(midi->listOfTracks.Size());
}
for (int i = 0; i < midi->listOfTracks.Size(); i++)
{
TrackEntry trackEntry;
midi->listOfTracks.Get(i, trackEntry);
TrackEntry* newTrackEntry = new TrackEntry;
*newTrackEntry = trackEntry;
listOfTracks.Append(*newTrackEntry);
}
if (midi->listOfEvents.Size() == 0)
{
listOfEvents.Clear();
}
else
{
listOfEvents.Resize(midi->listOfEvents.Size());
}
for (int i = 0; i < midi->listOfEvents.Size(); i++)
{
EventEntry eventEntry;
midi->listOfEvents.Get(i, eventEntry);
EventEntry* newEventEntry = new EventEntry;
*newEventEntry = eventEntry;
listOfEvents.Append(*newEventEntry);
}
if (midi->eventData.Size() == 0)
{
eventData.Clear();
}
else
{
eventData.Resize(midi->eventData.Size());
}
for (int i = 0; i < midi->eventData.Size(); i++)
{
unsigned char data;
midi->eventData.Get(i, data);
unsigned char* newData = new unsigned char(data);
eventData.Append(*newData);
}
}
/*
2.1.1 Compare
Returns always 0 because two Midis are not comparable concerning $''>'' or ''<''$
*/
int Midi::Compare(Attribute * arg)
{
return 0;
}
/*
2.1.1 Adjacent
Returns always false because two Midis cannot be put into an order
*/
bool Midi::Adjacent(Attribute * arg)
{
return false;
}
/*
2.1.1 Standard Clone
Returns a copy of a Midi
*/
Midi* Midi::Clone()
{
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)
{
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 )
{
return os << MIDI_STRING << " Algebra" << endl;
}
/*
2.1.1 NumOfFLOBs
Returns the number of used DBArrays for Midi
*/
int Midi::NumOfFLOBs()
{
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.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()
{
return 14 + listOfTracks.Size() * 8 + eventData.Size();
}
unsigned char Midi::GetDivisionMSB()
{
return divisionMSB;
}
unsigned char Midi::GetDivisionLSB()
{
return divisionLSB;
}
bool Midi::IsDivisionInFramesFormat()
{
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
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;
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_STRING),
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_STRING),
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( Word& w )
{
Midi *midi = (Midi *)w.addr;
midi->Destroy();
delete midi;
w.addr = 0;
}
/*
2.8 ~Close~-function
*/
void CloseMidi( Word& w )
{
delete (Midi *)w.addr;
w.addr = 0;
}
/*
2.9 ~Clone~-function
*/
Word CloneMidi( 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_STRING ));
}
/*
2.15 Creation of the Type Constructor Instance
*/
TypeConstructor midi(
MIDI_STRING, //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.
*/
Event* Track::GetEvent(int index)
{
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++)
{
Event* currentEvent = GetEvent(k);
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)
{
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)
{
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()
{
assert(eventType == sysexmessage || eventType == metamessage);
return dataList.size();
}
/*
Returns the data of MetaEvents.
*/
vector<unsigned char>* Event::GetMetaData(vector<unsigned char>* result)
{
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()
{
assert(eventType == metamessage || eventType == sysexmessage);
return dataList.size();
}
/*
Returns the kind of event.
*/
unsigned char Event::GetMetaMessageType()
{
assert(eventType == metamessage && dataList.size() > 2);
return dataList[1];
}
/*
Returns the length of a MidiEvent.
*/
int Event::GetShortMessageDataLength()
{
assert(eventType == shortmessage);
return shortMessageDataLength;
}
/*
Return the data of a MidiEvent.
*/
unsigned char Event::GetShortMessageData(int index)
{
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()
{
assert(shortMessageRunningStatus == false);
assert(eventType == shortmessage);
return shortMessageType;
}
/*
Returns the kind of event.
*/
EventType Event::GetEventType()
{
return eventType;
}
/*
Returns the length of this event.
*/
unsigned int Event::GetDeltaTime()
{
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()
{
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)
{
Event* currentEvent;
string temp;
temp.clear();
CcString* result = new CcString(false, (STRING*)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*)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_STRING) && nl->IsEqual(arg2, "int") )
{
return arg1;
}
}
return nl->SymbolAtom("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;
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++ )
{
currentEvent = oldTrack->GetEvent(eventIndex);
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_STRING) &&
nl->IsEqual(arg2, "int") &&
nl->IsEqual(arg3, "int") &&
nl->IsEqual(arg4, "bool"))
{
return arg1;
}
}
return nl->SymbolAtom("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;
for (int i = 0; i < track->GetNumberOfEvents(); i++)
{
EventWithAbsTime* eventStruct = new EventWithAbsTime;
Event* event = track->GetEvent(i);
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.
*/
{
Event* firstPCOfDestTrack = 0;
for (int i = 0; i < destTrack->GetNumberOfEvents(); i++)
{
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++)
{
Event* currentEvent = srcTrack->GetEvent(i);
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_STRING) &&
nl->IsEqual(arg2, "int") &&
nl->IsEqual(arg3, "int"))
{
return arg1;
}
}
return nl->SymbolAtom("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_STRING) &&
nl->IsEqual(arg2, "bool" ) &&
nl->IsEqual(arg3, "bool" ) &&
nl->IsEqual(arg4, "bool" ))
{
return nl->SymbolAtom("text");
}
}
return nl->SymbolAtom("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 :: FText* outText = new FText::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_STRING) &&
nl->IsEqual( arg2, "string" ) &&
nl->IsEqual( arg3, "bool" ) &&
nl->IsEqual( arg4, "bool" ) &&
nl->IsEqual( arg5, "bool" ))
{
return nl->SymbolAtom("bool");
}
}
return nl->SymbolAtom("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_STRING) &&
nl->IsEqual(arg2, "string") &&
nl->IsEqual(arg3, "bool") &&
nl->IsEqual(arg4, "int") )
{
return nl->SymbolAtom("int");
}
}
return nl->SymbolAtom("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;
for (int i = 0; i < track->GetNumberOfEvents(); i++)
{
Event* event = track->GetEvent(i);
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_STRING) &&
nl->IsAtom(second) && nl->IsEqual(second, "string"))
{
return nl->SymbolAtom("bool");
}
}
return nl->SymbolAtom("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)
{
writeResult = out.write(byteArray, byteVector.size());
}
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_STRING))
{
return nl->SymbolAtom("int");
}
}
return nl->SymbolAtom("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_STRING) && nl->IsEqual(arg2, "int"))
{
return arg2;
}
}
return nl->SymbolAtom("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
*/
{
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_STRING) && nl->IsEqual(arg2, "int") )
{
return nl->SymbolAtom("string");
}
}
return nl->SymbolAtom("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_STRING) )
{
return nl->SymbolAtom("string");
}
}
return nl->SymbolAtom("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*)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
);
/*
5 Creating the Algebra
*/
class MidiAlgebra : public Algebra
{
public:
MidiAlgebra() : Algebra()
{
AddTypeConstructor( &midi );
midi.AssociateKind( "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() {};
};
} // ''local'' namespace
MidiAlgebra 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 (&midiAlgebra);
}
/*
----
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 $]
\pagebreak
1 Header file of MidiAlgebra
This header file essentially contains the definition of the classes ~Midi~, ~Event~ and ~Track~ used in the Midi Algebra. Only the class ~Midi~ respectively correspond to the memory representation for the type constructor ~Midi~.
2 Defines and includes
*/
#ifndef __MIDI_ALGEBRA_H__
#define __MIDI_ALGEBRA_H__
#include "StandardAttribute.h"
#include "DBArray.h"
#include <string>
#include <vector>
namespace midialgebra {
class Track;
class Event;
/*
Forward declaration for classes ~Track~ and ~Event~ .
*/
/*
2.1 Constants
Several values are fixed inside a Midi object according to the specification by MMA.
*/
static const int MIN_TRACKS_MIDI = 1;
/*
At least one track must be inside a Midi object.
*/
static const int MAX_TRACKS_MIDI = 65536;
/*
Maximum numbers of tracks inside a Midi object.
*/
static const int MAX_CHANNELS_MIDI = 16;
/*
Maximum numbers of channels inside a track object.
*/
static const int MIN_FORMAT_MIDI = 0;
static const int MAX_FORMAT_MIDI = 2;
/*
The Midi format is only valid between 0 and 2.
*/
static const string MIDI_STRING = "midi";
/*
Constant string for Midi.
*/
static const string MIDI_HEADER = "MThd";
/*
Constant string for a Midi object header.
*/
static const string TRACK_STRING = "track";
/*
Constant string for Track.
*/
static const string TRACK_HEADER = "MTrk";
/*
Constant string for a Midi track header.
*/
static const unsigned int LENGTH_OF_SECONDO_STRING = 49;
/*
Constant length of SECONDO its internal string representation.
*/
static const int ERROR_INT = -1;
/*
Constant integer e.g. for wrong input values.
*/
/*
3 Class Midi
This class implements the memory representation of the ~Midi~ type constructor.
*/
enum EventEntryType {shortmessageEntry, shortmessageRSEntry,
metamessageEntry, sysexmessageEntry};
/*
The types of events which can be stored in the element listOfEvents
*/
struct TrackEntry
{
int noOfEvents;
int eventPtr;
};
/*
Objects of type TrackEntry can be stored in listOfElements. The element noOfEvents contains the count of elements in this track, and eventPtr references the first element entry of this track in the DBArray listOfElements.
*/
struct EventEntry
{
int dataPtr;
int size;
EventEntryType type;
};
/*
Objects of the type EventEntry can be stored in listOfElements. The element dataPtr references the first byte of the event data in
the DBArray eventData. The element size represents the entire size of this event in bytes, the element type contains the type information of this event entry.
*/
class Midi: public StandardAttribute
{
public:
/*
3.1 Constructors and Destructor
There are two ways of constructing a ~Midi~:
*/
Midi();
/*
This constructor should not be used.
*/
Midi( const bool defined, const unsigned char divisionMSB,
const unsigned char divisionLSB );
/*
The first 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( const Midi& p );
/*
The second one receives a ~Midi~ object ~p~ as argument and creates a ~Midi~ that is a copy of ~p~.
*/
~Midi();
/*
The destructor.
*/
Midi& operator=(const Midi&);
/*
Overloading the assignment operator.
*/
/*
3.2 Member functions
*/
Track* GetTrack(int index);
/*
Returns the by index selected track of this ~Midi~ object.
*/
void Append( Track *inTrack);
/*
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.
*/
const int GetNumberOfTicks() const;
/*
Returns the number of ticks per quarter note of the ~Midi~. If IsDivisionInFramesFormat() returns true, the value of this function
will be undefined.
*/
const int GetNumberOfTracks() const;
/*
Returns the number of tracks of the ~Midi~ object.
*/
void SetFormat (const int format);
/*
Sets the format of this ~Midi~ object.
*/
const int GetFormat () const;
/*
Gets the ~Midi~ object`s format.
*/
void SetHeaderLength (const int lengthOfHeader);
/*
Sets the length of the ~Midi~ header. At the moment the value will be always 6.
*/
const int GetHeaderLength () const;
/*
Returns the ~Midi~ object`s header length.
*/
const string GetHeader () const;
/*
Returns the ~Midi~ object`s header. At the moment it is the constant string ''MThd''.
*/
const int GetFileSize();
/*
Returns the size in bytes of the input ~Midi~ file.
*/
Midi* Clone(const bool copyTracks);
/*
Overloads the Clone method. If copyTracks is set false, it returns a new Midi object with copied attributes, but without any event and track data. Otherwise it works like Clone().
*/
void AppendEmptyTrack();
/*
Appends an empty track to this ~Midi~ object.
*/
void Destroy();
/*
If this method is called, the destructor will destroy the physical representation of all DBArrays.
*/
bool IsDivisionInFramesFormat();
/*
Returns true if the division of this Midi object is stored in frames format.
*/
unsigned char GetDivisionMSB();
/*
Returns the division's most significant byte.
*/
unsigned char GetDivisionLSB();
/*
Returns the division's least significant byte.
*/
void SetDivisionMSB(const unsigned char msb);
/*
Sets the division's most significant byte.
*/
void SetDivisionLSB(const unsigned char lsb);
/*
Returns the division's least significant byte.
*/
void GetLyrics(string& result, bool all, bool lyr, bool any);
/*
Utility for operators extract\_lyrics and contains\_words
It puts the data of all lyric and titel textmessages into a result string.
For the meaning of the bool parameters see the description of extract\_lyrics
operator value mapping
*/
/*
3.3 Operations
*/
/*
3.3.11 Functions needed to import the ~Midi~ data type to tuple
There are totally 10 functions which are defined as virtual functions. They need to be defined here in order for the Midi data type to be used in Tuple definition as an attribute.
*/
bool IsDefined() const;
void SetDefined(bool Defined);
size_t HashValue();
void CopyFrom(StandardAttribute* right);
int Compare(Attribute * arg);
bool Adjacent(Attribute * arg);
Midi* Clone();
ostream& Print( ostream &os );
int NumOfFLOBs();
FLOB* GetFLOB(const int);
private:
/*
3.4 Attributes
*/
bool defined;
/*
A flag that tells if the Midi is defined or not.
*/
DBArray<TrackEntry> listOfTracks;
/*
Data structure for saving all tracks of a Midi object.
*/
DBArray<EventEntry> listOfEvents;
/*
Stores the EventEntry objects.
*/
DBArray<unsigned char> eventData;
/*
Stores the data of the events.
*/
unsigned char divisionMSB;
/*
Stores the most significant byte of the division of a Midi file.
*/
unsigned char divisionLSB;
/*
Stores the least significant byte of the division of a Midi file.
*/
int lengthOfHeader;
/*
Specifies the length of the header information.
*/
int format;
/*
specifies the format
[->] 0 = single-track
[->] 1 = multiple tracks, synchronous
[->] 2 = multiple tracks, asynchronous
*/
bool isDeletable;
/*
True, if all DBArrarys of this object can be destroyed.
*/
};
/*
4 Class Track
This class implements a track of a Midi object.
*/
class Track
{
public:
/*
4.1 Constructors and Destructor
*/
Track();
/*
This constructor is empty.
*/
~Track();
/*
The destructor.
*/
/*
4.2 Member functions
*/
Event* GetEvent(int index);
/*
Returns the by index selected event of this track.
*/
void Append( Event* inEvent);
/*
Appends the by index selected track of this Midi object
*/
const int GetNumberOfEvents() const;
/*
Returns the number of all currently stored events for this track.
*/
const string GetHeader () const;
/*
Returns the track`s header signature. At the moment it is the constant string ''MTrk''.
*/
void Transpose(bool inPerc, int hfTnSt, int& errorval );
/*
utilty for operators transpose\_track and transpose\_midi
transposes the calling track the given number of half\_tone\_steps.
The parameter inPerc decides whether percussion channel should be include or
exclude in transposing process
*/
private:
/*
4.4 Attributes
*/
vector<Event*> listOfEvents;
/*
Data structure for saving all events of a Midi object.
*/
};
enum EventType {shortmessage, metamessage, sysexmessage};
/*
In this context a shortmessage is the same as a MidiEvent.
*/
/*
5 Class Event
This class includes all kind of events like MidiEvent, MetaEvent and SysEx.
*/
class Event
{
public:
/*
5.1 Constructors and Destructor
*/
Event();
/*
This constructor should not be used.
*/
Event(EventType eventType, unsigned int deltaTime);
/*
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();
/*
The destructor.
*/
/*
5.2 Member functions
*/
EventType GetEventType();
/*
Returns the kind of event.
*/
void SetDeltaTime(unsigned int deltaTime);
/*
Sets the length of this event.
*/
unsigned int GetDeltaTime();
/*
Returns the length of this event.
*/
void SetShortMessageType(unsigned char shortMessageType);
/*
Stores MidiEvents.
*/
unsigned char GetShortMessageType();
/*
Returns the command of a MidiEvent. The returned value will be undefined if GetShortMessageRunningStauts() returns true.
*/
void SetShortMessageRunningStatus(bool b);
/*
Sets the ''running status'' of this Midi (channel) event. Default is false.
*/
bool GetShortMessageRunningStatus();
/*
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.
*/
void SetShortMessageData(int index, unsigned char data);
/*
Stores the data of a MidiEvent.
*/
unsigned char GetShortMessageData(int index);
/*
Return the data of a MidiEvent.
*/
void SetShortMessageDataLength(int length);
/*
Specifies the length of a MidiEvent.
*/
int GetShortMessageDataLength();
/*
Returns the length of a MidiEvent.
*/
void SetMetaData(vector<unsigned char>* data);
/*
Stores MetaEvents.
*/
vector<unsigned char>* GetMetaData(vector<unsigned char>* result);
/*
Returns the data of MetaEvents.
*/
int GetMetaDataLength();
/*
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.
*/
unsigned char GetMetaMessageType();
/*
Returns the kind of event.
*/
void SetSysexData(vector<unsigned char>* data);
/*
Stores the data of a SysexMessage.
*/
vector<unsigned char>* GetSysexData(vector<unsigned char>* result);
/*
Returns the data of a SysexMessage.
*/
int GetSysexDataLength();
/*
Returns the length of a SysexMessage.
*/
const int GetCommand () const;
/*
Returns the MidiEvent`s command.
*/
const int GetChannel() const;
/*
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.
*/
void GetTextFromMetaEvent(string& result);
/*
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 SetChannel(const int inChannel);
/*
Sets the MidiEvent`s channel.
*/
static const unsigned char ACTIVE_SENSING = 0xFE;
static const unsigned char CHANNEL_PRESSURE = 0xD0;
static const unsigned char CONTINUE = 0xFB;
static const unsigned char CONTROL_CHANGE = 0xB0;
static const unsigned char END_OF_EXCLUSIVE = 0xF7;
static const unsigned char MIDI_TIME_CODE = 0xF1;
static const unsigned char NOTE_OFF = 0x80;
static const unsigned char NOTE_ON = 0x90;
static const unsigned char PITCH_BEND = 0xE0;
static const unsigned char POLY_PRESSURE = 0xA0;
static const unsigned char PROGRAM_CHANGE = 0xC0;
static const unsigned char SONG_POSITION_POINTER = 0xF2;
static const unsigned char SONG_SELECT = 0xF3;
static const unsigned char START = 0xFA;
static const unsigned char STOP = 0xFC;
static const unsigned char SYSTEM_RESET = 0xF3;
static const unsigned char TIMING_CLOCK = 0xF8;
static const unsigned char TUNE_REQUEST = 0xF6;
/*
These constants represents all commands of a MidiEvent.
*/
static const unsigned char SEQUENCE_NUMBER = 0x00;
static const unsigned char ANY_TEXT = 0x01;
static const unsigned char COPYRIGHT = 0x02;
static const unsigned char TRACK_NAME = 0x03;
static const unsigned char INSTRUMENT_NAME = 0x04;
static const unsigned char LYRIC = 0x05;
static const unsigned char CHANNEL_PREFIX = 0x20;
static const unsigned char END_OF_TRACK = 0x2F;
static const unsigned char TEMPO = 0x51;
static const unsigned char SMPTE_OFFSET = 0x54;
static const unsigned char TIME_SIGNATURE = 0x58;
static const unsigned char KEY_SIGNATURE = 0x59;
static const unsigned char SEQ_SPECIFIC = 0x7F;
/*
These constants represents some MetaEvents.
*/
/*
5.4 Static methods
*/
static vector<unsigned char>* ComputeIntToBytes( unsigned int arg,
vector<unsigned char>* result);
/*
Utility method for computing an integer into a form called ''Variable Length Quantity''. The resulting bytes are stored in the vector result. For using in an expression, the result is also returned as the return value of this function. Explanation of ''Variable Length Quantity'': ''Some numbers in MIDI Files are represented in a form called VARIABLE-LENGTH QUANTITY. These numbers are represented 7 bits per byte, most significant bits first. All bytes except the last have bit 7 set, and the last byte has bit 7 clear. If the number is between 0 and 127, it is thus represented exactly as one byte.''
*/
static unsigned int ComputeBytesToInt(vector<unsigned char>* arg);
/*
Utility method for computing a number which is represented in ''Variable Length Quantity''. See Event::ComputeIntToBytes for details.
*/
static string FilterString(string textEvents );
/*
Utilty method for extract\_lyrics operator. The pure ASCI data in the
metaevents are not well readable because of several ''break'' characters like "/"
and "\".Moreover some control characters causes overwriting stringparts in the
outstream an have to be replaced as well .This is done here.
*/
/*
5.5 Attributes
*/
private:
EventType eventType;
/*
Specifies the kind of event ( MidiEvent, MetaEvent or SysexMessage ). See also definition of EventType.
*/
unsigned int deltaTime;
/*
Specifies the length of this event.
*/
unsigned char shortMessageType;
/*
Specifies the MidiEvent`s command.
*/
bool shortMessageRunningStatus;
/*
Stores the ''running status'' of a Midi (channel) event.
*/
int shortMessageDataLength;
/*
Specifies the length of a MidiEvent`s command.
*/
unsigned char shortMessageData[2];
/*
Stores data of MidiEvents.
*/
vector<unsigned char> dataList;
/*
Stores data of MetaEvents and SysexMessages.
*/
};
/*
6 Utitlity classes
*/
/*
6.1 Class SequenceParser
Helper class for parsing the input of the contains\_sequence operators.
The input string with notes of these operators must be following
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 | \#$
*/
class NoteStringToListParser
{
public:
NoteStringToListParser();
bool ParseNoteString(char* inputStr, vector<int>* resultList);
/*
Parses the given string and returns a vector with note numbers. If the
parsing is succesfull the function returns true. Occurs a syntax
error during the parsing the function returns false and the value of
the result vector is undefined.
*/
private:
char GetNextChar(char** inputStrPtr);
int ComputeBaseNoteNo(char currentChar);
};
} // namespace midialgebra
#endif
/*
\pagebreak
8 Operations of the Midi Algebra
In the following you can see the overall list of all implemented operations. Operators already defined in other algebras but also implemented in the MidiAlgebra are cleared out.
*/
#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
operator extract_track alias EXTRACT_TRACK pattern _ op [_]
operator merge_tracks alias MERGE_TRACKS pattern _ op [_, _, _]
operator transpose_track alias TRANSPOSE_TRACK pattern _ op [_, _]
operator transpose_midi alias TRANSPOSE_MIDI pattern _ op [_]
operator extract_lyrics alias EXTRACT_LYRICS pattern _ op [_, _, _]
operator contains_words alias CONTAINS_WORDS pattern _ op [_, _ ,_ ,_]
operator contains_sequence alias CONTAINS_SEQUENCE pattern _ op [_, _, _]
operator delete_track alias DELETE_TRACK pattern _ op [_]
operator expand_track alias EXPAND_TRACK pattern _ op [_]
operator tempo_ms alias TEMPO_MS pattern _ op
operator tempo_bpm alias TEMPO_BPM pattern _ op
operator format alias FORMAT pattern _ op
operator count_tracks alias COUNT_TRACKS pattern _ op
operator track_name alias TRACK_NAME pattern _ op [_]
operator time_signature alias TIME_SIGNATURE pattern _ op
operator beat alias BEAT pattern _ op
operator instrument_name alias INSTRUMENT_NAME pattern _ op [_]
operator count_channels alias COUNT_CHANNELS pattern _ op [_]
operator get_name alias GET_NAME pattern _ op
# Operator saveto is also defined in the BinaryFileAlgebra
operator saveto alias SAVETO pattern _ infixop _
/*
----
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{] [}]
//[ss] [{\ss}]
//[<=] [\leq]
//[#] [\neq]
//[tilde] [\verb|~|]
//[->] [$\rightarrow $]
\pagebreak
1 Implementation of the JavaGUI
This class provides the SECODNO viewer for Midi type objects and relations
containing midis. Therefore this viewer consists of two single
viewers combined to a whole one, being capable of displaying both.
2 Imports
*/
package viewer;
import javax.swing.*;
import javax.swing.text.*;
import javax.swing.event.*;
import javax.swing.border.*;
import javax.swing.table.*;
import java.util.Vector;
import java.util.*;
import java.awt.*;
import java.lang.Long;
import java.awt.event.*;
import gui.SecondoObject;
import gui.*;
import sj.lang.*;
import tools.*;
import viewer.midi.*;
import javax.swing.Timer;
import java.io.File;
import java.io.InputStream;
import java.io.IOException;
import java.io.*;
import javax.sound.midi.*;
/*
3 Class ~MidiViewer~
This class extends the class SecondoViewer and must therefore implement the
abstract methods inherited. Additionally it implements three
listener interfaces to act itself as ActionListener.
*/
public class MidiViewer extends SecondoViewer implements ChangeListener,
ItemListener,ListSelectionListener
{
/*
3.1 private components
The name of the viewer including its version
*/
private static final String name = "MidiViewer 1.22";
/*
*global swing-components*
A JTabbedPane with three tabs as the primary organising component
*/
private JTabbedPane jtp = new JTabbedPane(JTabbedPane.TOP);
/*
The first tab is the playPanel, containing the Midi player
*/
private JPanel playPanel = new JPanel();
private JPanel PlayerPanel = new JPanel();
private JPanel InfoPanel = new JPanel();
private JButton play = new JButton("play");
private JButton halt = new JButton("pause");
private JButton stop = new JButton("stop");
private JButton ff = new JButton("ff");
private JButton rew = new JButton("rew");
private JButton export = new JButton("export");
private JButton skipP = new JButton("skip +");
private JButton skipM = new JButton("skip -");
private JButton setTickPosition = new JButton("setTickPosition");
private JList list;
private JSlider slider = new JSlider(JSlider.HORIZONTAL, 0, 100000, 0);
private JLabel time = new JLabel("0:00 / 0:00");
private JPanel TrackPanel = new JPanel();
private JLabel infoNameLabel = new JLabel(("----- Welcome to " + name +" -----"), JLabel.CENTER);
private JLabel infoAssumedNameLabel = new JLabel();
private JLabel infoTempoLabel = new JLabel();
private JLabel infoLengthLabel = new JLabel();
private JLabel infoTickLengthLabel = new JLabel();
private JLabel infoTrackLabel = new JLabel();
private JLabel infoResLabel = new JLabel();
/*
The second tab is used to display the relations
*/
private JPanel relPanel = new JPanel();
private JComboBox ComboBox;
private JScrollPane ScrollPane;
private JTable CurrentTable;
private JPanel dummy;
/*
The third tab displays the meta informations of the currently playing
Midi
*/
private JPanel metaPanel = new JPanel();
/*
*global components of the adapted relation viewer*
*/
private Vector Tables;
private Vector midiTables = new Vector();
private boolean listSelectionAllowed = true;
/*
*global components of the Midi player*
All midis of the player are stored in a vector, representing the
~playlist~, as there should be no limitation of the maximum of queried
midis
*/
private Vector midiVector;
/*
The first approach to the MidiViewer project was to be as object
orientated as possible. Therefore every MidiFile had its own sequencer.
After some performance and threading problems we decided that there
should be only one sequencer managed by the viewer itself.
*/
private static Sequencer sequencer = null;
/*
As it is not guaranteed that a sequencer provided by the system is an
instance of ~Synthesizer~ there must be a way to call a synthsizer, a
receiver and a transmitter
*/
private Synthesizer synthesizer = null;
private Receiver receiver = null;
private Transmitter transmitter = null;
/*
The sequencer loads only the sequence from a MidiFile. That is why it is
necessary to have a reference to the currently loaded MidiFile.
*/
private MidiFile thisMidiFile = null;
/*
The slider should capable to change its value as the Midi is playing,
so that its position must be updated continuesly. On the other hand,
every change made by the user should set the current position.
The problem was, that every change of the slider position
made by the player, invoked the ActionListner waiting for
user commands. This caused unfortunately some bad behavior of the
sequencer. Therefore a flag is used to indicate whether the change was
made by the user or the system.
*/
private boolean mouseSliding = false;
/*
An array of the current tracks
*/
private MidiTrack[] currentTracks;
/*
As a sequencer does not implement a pause function, it is neccassary to
know the position to relaunch after stopping the sequencer for pausing
*/
private long startAt = 0;
/*
By using the skip buttons, the position shall be changed by ten
percent of the whole length
*/
private long skippy = 0;
/*
Length of the sequence is handled as ~String~
*/
private String length = "0:00 / 0:00";
private String lengthOfCurrentMidi = "0:00";
/*
During a Midi sequence the tempo might change. To display always the
correct tempo it's necessary to save the current displayed tempo to not
always update the displayed tempo when not needed
*/
private float displayedTempo = 0.0f;
/*
3.1 constructor
The constructor initializes the whole GUI in a couple of steps.
*/
public MidiViewer()
{
/*
Creates the GUI by calling the three methods to create and show the
three tabs of the viewer
*/
createAndShowPlayerGUI();
createAndShowRelationGUI();
createAndShowMetaPanel();
/*
As swing is not threadsafe the two update methods ~refreshTime~
and ~refreshSlider~ are called from a timer
*/
int delay = 50;
ActionListener taskPerformer = new ActionListener() {
public void actionPerformed(ActionEvent evt)
{
refreshTime();
refreshSlider();
}
};
new Timer(delay, taskPerformer).start();
setLayout(new BorderLayout());
/*
As the viewer manages the one and only sequencer of the whole
implementation, the ~MidiSystem~ and expecially the sequencer needs to be
initialized.
*/
try { sequencer = MidiSystem.getSequencer(); }
catch (MidiUnavailableException e)
{
System.out.println("MidiFile 3: Midi unavailable");
}
if (sequencer == null)
{
System.out.println("MidiFile 4: can't get a sequencer");
}
if (! (sequencer instanceof Synthesizer))
{
try
{
synthesizer = MidiSystem.getSynthesizer();
synthesizer.open();
receiver = synthesizer.getReceiver();
transmitter = sequencer.getTransmitter();
transmitter.setReceiver(receiver);
}
catch (MidiUnavailableException e)
{
System.out.println("MidiFile 7: can't get a synthesizer");
}
}
/*
Getting the GUI together
*/
jtp.addTab("Midi-Player", playPanel);
jtp.add("Relation", relPanel);
jtp.add("Meta-Informations", (new JScrollPane(metaPanel)));
add(jtp, BorderLayout.CENTER);
}
/*
3.2 inherited methods of SecondoViewer
3.3.3 getName
Returns the name of the viewer
*/
public String getName()
{
return name;
}
/*
3.2.3 addObject
Checks whether the given SecondoObject is a Midi or relation containing
midis. If it is a Midi, a MidiFile is created, automatically started and added to the
playlist. Otherwise a table is created containing the relation. From this table
it is possible to load the containing midis
*/
public boolean addObject(SecondoObject o)
{
ListExpr LE = o.toListExpr();
if (LE.first().isAtom() && LE.first().symbolValue().equals("midi"))
{
unload();
if (isDisplayed(o))
{
for (int i = 0; i < midiVector.size(); i++)
{
if(((MidiFile)midiVector.elementAt(i)).getID().equals(
o.getID().toString()))
{
thisMidiFile = (MidiFile)midiVector.elementAt(i);
load();
play();
return true;
}
}
return true;
}
else
{
Base64Decoder b64;
StringReader sr = new StringReader(LE.second().first().textValue());
b64 = new Base64Decoder(sr);
MidiFile midi = new MidiFile(b64.getInputStream(), o.getName(),
o.getID().toString());
midiVector.addElement(midi);
list.setListData(midiVector);
thisMidiFile = midi;
load();
long l = getLengthInTicks();
length = Long.toString(l);
play();
slider.setMaximum((int) getLengthInTicks());
currentTracks = thisMidiFile.getTrackArray();
setTrackPanel();
setInfoPanel();
jtp.setSelectedIndex(0);
return true;
}
}
else
{
if (isDisplayed(o))
{
selectObject(o);
return false;
}
else
{
JTable NTable = createTableFrom(o.toListExpr(), o.getName());
if(NTable==null)
{
return false;
}
else
{
Tables.add(NTable);
ComboBox.addItem(o.getName());
selectObject(o);
ScrollPane.setViewportView(NTable);
jtp.setSelectedIndex(1);
return true;
}
}
}
}
/*
3.3.3 removeObject
Removes the targeted object from the viewer. If a relation has to be
removed, so all objects are loaded from the relation into the player.
*/
public void removeObject(SecondoObject o)
{
ListExpr LE = o.toListExpr();
if (LE.first().isAtom() && LE.first().symbolValue().equals("midi"))
{
if (thisMidiFile.getID().equals(o.getID().toString()))
{
midiVector.remove(thisMidiFile);
unload();
setInfoPanel();
setTrackPanel();
setMetaPanel();
}
else
{
int finder = -1;
// searches the right midi
for (int i = 0; i < midiVector.size(); i++)
{
if (((MidiFile)midiVector.elementAt(i)).getID().equals(
o.getID().toString()))
{
finder = i;
}
}
if (finder != -1)
{
midiVector.remove(finder);
}
}
list.setListData(midiVector);
}
else
{
int index = getIndexOf(o.getName());
if( index >= 0 )
{
kickThem(o.getName());
setInfoPanel();
setTrackPanel();
setMetaPanel();
ComboBox.removeItemAt(index);
Tables.remove(index);
}
}
}
/*
3.3.3 removeAll
Simple and selfexplaining
*/
public void removeAll()
{
unload();
midiVector.removeAllElements();
list.setListData(midiVector);
ComboBox.removeAllItems();
Tables.clear();
jtp.setSelectedIndex(0);
setTrackPanel();
setMetaPanel();
}
/*
3.3.3 canDisplay
Checks if SecondoObject can be displayed. There are two possible types
which can be displayed. On the one hand a Midi can be displayed,
on the other hand relations can be displayed. If the type is rel, it is
checked if the relation contains midis at all.
*/
public boolean canDisplay(SecondoObject o)
{
ListExpr LE = o.toListExpr();
if(LE.listLength()!=2) return false;
if(LE.first().atomType()==ListExpr.SYMBOL_ATOM &&
LE.first().symbolValue().equals("midi"))
{
if (LE.second().first().atomType() == ListExpr.TEXT_ATOM)
return true;
else return false;
}
LE = LE.first();
if(LE.isAtom()) return false;
else
{
LE = LE.first();
if(LE.isAtom() && LE.atomType()==ListExpr.SYMBOL_ATOM &&
(LE.symbolValue().equals("rel") |
LE.symbolValue().equals("mrel")) )
{
if (containsMidiObject(o)) return true;
}
}
return false;
}
/*
4.4.4 isDisplayed
Checks whether an object is already displayed or not.
*/
public boolean isDisplayed(SecondoObject o)
{
ListExpr LE = o.toListExpr();
if (LE.first().isAtom() && LE.first().symbolValue().equals("midi"))
{
for (int i = 0; i < midiVector.size(); i++)
{
if(((MidiFile)midiVector.elementAt(i)).getID().equals(
o.getID().toString()))
{
return true;
}
}
return false;
}
else
{
return getIndexOf(o.getName())>=0;
}
}
/*
3.3.3 selectObject
Selected Object is displayed
*/
public boolean selectObject(SecondoObject o)
{
ListExpr LE = o.toListExpr();
if (LE.first().isAtom() && LE.first().symbolValue().equals("midi"))
{
unload();
for (int i = 0; i < midiVector.size(); i++)
{
if(((MidiFile)midiVector.elementAt(i)).getID().equals(
o.getID().toString()))
{
thisMidiFile = (MidiFile)midiVector.elementAt(i);
load();
play();
setInfoPanel();
setTrackPanel();
jtp.setSelectedIndex(0);
return true;
}
}
setTrackPanel();
return false;
}
else
{
int index = getIndexOf(o.getName());
if ( index < 0 )
{
return false;
}
else
{
ComboBox.setSelectedIndex(index);
showSelectedObject();
jtp.setSelectedIndex(1);
return true;
}
}
}
/*
2.2.2 getMenuVector
As there is no need for an extra menu on the GUI, null is returned
*/
public MenuVector getMenuVector()
{
return null;
}
/*
2.3.3 getDisplayQuality
If the SecondoObject is a Midi or a relation containing midis this
viewer shall be used to display it
*/
public double getDisplayQuality(SecondoObject o)
{
if (canDisplay(o))
{
return 0.9;
}
else
{
return 0;
}
}
/*
3.2 the player
3.3.3 setTrackPanel
The track panel is located in the lower left corner of the Midi player.
It shall display all tracks and their names. Also it must be possible
to mute and to solo a single track.
*/
void setTrackPanel()
{
TrackPanel.removeAll();
if (sequencer == null || !sequencer.isOpen())
{
TrackPanel.add(new JLabel("Nothing to display"));
TrackPanel.repaint();
}
else
{
int i;
JPanel headline = new JPanel(new FlowLayout(FlowLayout.LEFT));
headline.add(new JLabel(" Current Tracks - "));
headline.add(new JLabel("monitoring states"));
JButton playAll = new JButton("play all");
playAll.addMouseListener(new MouseAdapter()
{
public void mouseClicked(MouseEvent e)
{
unMuteAllTracks();
}
});
TrackPanel.add(headline);
JPanel playAllPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
playAllPanel.add(playAll);
TrackPanel.add(playAllPanel);
for (i = 0; i < currentTracks.length; i++)
{
boolean checked = !sequencer.getTrackMute(i);
JPanel jp = new JPanel(new FlowLayout(FlowLayout.LEFT));
JCheckBox cb = new JCheckBox(currentTracks[i].getName(),
checked);
JButton jb = new JButton("Solo");
jb.setName(i + " ");
cb.addItemListener(this);
jb.addMouseListener(new MouseAdapter()
{
public void mouseClicked(MouseEvent e)
{
int i = getIndexNo(((JButton)
e.getSource()).getName());
soloTrack(i);
}
});
jp.add(jb);
jp.add(cb);
TrackPanel.add(jp);
}
TrackPanel.setLayout(new GridLayout(i+2, 1));
}
}
/*
2.3.2 soloTrack
If a solo button is hit, every track but one needs to be set to the mute state.
(As said in the Java documentation it is possible that not every system
supports the solo functionality provided by Java. Therefore we are just
using mute functions.)
*/
void soloTrack(int trackSolo)
{
for (int i = 0; i < currentTracks.length; i++)
{
if (i != trackSolo)
{
sequencer.setTrackMute(i, true);
}
else
{
sequencer.setTrackMute(i, false);
}
}
setTrackPanel();
}
/*
3.3.3 unMuteAllTracks
Reverses the effect of soloTrack and unsets all mute states
*/
void unMuteAllTracks()
{
for(int i = 0; i < currentTracks.length; i++)
{
sequencer.setTrackMute(i, false);
}
setTrackPanel();
}
/*
The info panel is located right under the player controls in the lower
right corner. It is meant to give a quick overview of the current
playing Midi.
*/
void setInfoPanel()
{
if (sequencer == null || !sequencer.isOpen())
{
infoAssumedNameLabel.setText("");
infoTempoLabel.setText("");
infoLengthLabel.setText("");
infoTickLengthLabel.setText("");
infoTrackLabel.setText("");
infoResLabel.setText("");
}
else
{
infoAssumedNameLabel.setText("Assumed Name: "+thisMidiFile.getName());
displayedTempo = getTempo();
infoTempoLabel.setText("Tempo: " +
getTempo() / sequencer.getTempoFactor() + " bpm x " +
getTempoFactor() + " = " + getTempo()+ " bpm");
infoLengthLabel.setText("Length: "+ lengthOfCurrentMidi);
infoTickLengthLabel.setText("Length in Ticks: " + sequencer.getTickLength());
infoTrackLabel.setText("Tracks: "+ thisMidiFile.getNrTracks());
if (thisMidiFile.getDivisionType() == Sequence.PPQ)
{
infoResLabel.setText("Resolution in ticks per beat: "
+ thisMidiFile.getRes());
}
else
{
infoResLabel.setText("Resolution in ticks per frame: "
+ thisMidiFile.getRes());
}
}
}
/*
3.2.2 set MetaPanel
Calls the meta informations belonging to a MidiFile and displays them at
the third tab
*/
void setMetaPanel()
{
int zaehler = 0;
metaPanel.removeAll();
if (thisMidiFile == null)
{
metaPanel.add(new JLabel("nothing to display..."));
}
else
{
Vector v;
for (int i = 0; i < thisMidiFile.getNrTracks(); i++)
{
v = thisMidiFile.getMetaInfos(i);
for (int j = 0; j < v.size(); j++)
{
String s = (String) v.get(j);
JLabel jl = new JLabel(s);
metaPanel.add(jl);
zaehler++;
}
}
metaPanel.setLayout(new GridLayout(zaehler,1));
}
}
/*
3.4.3 createAndShowMetaPanel
Some kind of ~constructor~ for the meta panel. When it is called there
can be no Midi file loaded. So there is nothing to do.
*/
public void createAndShowMetaPanel()
{
metaPanel.add(new JLabel("nothing to display"));
}
/*
2.2.2 createAndShowPlayerGUI
In contrast to ~createAndShowMetaPanel~ this ~constructor~ of the player
is much more sophisticated as every button. ActionListener and all other
components needs to be initialized.
*/
public void createAndShowPlayerGUI()
{
midiVector = new Vector();
/*
the buttons and their listener are linked
*/
play.addMouseListener(new MouseAdapter()
{
public void mouseClicked (MouseEvent e)
{
if (sequencer.isOpen())
{
play();
setInfoPanel();
}
}
});
halt.addMouseListener(new MouseAdapter()
{
public void mouseClicked(MouseEvent e)
{
if (sequencer.isOpen())
{
halt();
//setInfoPanel();
}
}
});
ff.addMouseListener(new MouseAdapter()
{
public void mouseClicked(MouseEvent e)
{
if (sequencer.isOpen())
{
fastforward();
}
}
});
rew.addMouseListener(new MouseAdapter()
{
public void mouseClicked(MouseEvent e)
{
if (sequencer.isOpen())
{
rewind();
}
}
});
stop.addMouseListener(new MouseAdapter()
{
public void mouseClicked(MouseEvent e)
{
if (sequencer.isOpen())
{
stop();
}
}
});
export.addMouseListener(new MouseAdapter()
{
public void mouseClicked(MouseEvent e)
{
if(sequencer.isOpen())
{
thisMidiFile.export();
}
}
});
skipM.addMouseListener(new MouseAdapter()
{
public void mouseClicked(MouseEvent e)
{
if (sequencer.isOpen())
{
skipM();
}
}
});
skipP.addMouseListener(new MouseAdapter()
{
public void mouseClicked(MouseEvent e)
{
if(sequencer.isOpen())
{
skipP();
}
}
});
setTickPosition.addMouseListener(new MouseAdapter()
{
public void mouseClicked(MouseEvent e)
{
if (sequencer.isOpen())
{
try
{
String s = (String)JOptionPane.showInputDialog(
null, "Set the Tick Position","Enter the new Tick Position" ,
JOptionPane.QUESTION_MESSAGE);
long l = (Long.decode(s)).longValue();
if (sequencer.getTickLength() >= l)
{
sequencer.setTickPosition(l);
}
}
catch (Exception ede) {System.out.println("wrong Input");}
}
refreshSlider();
}
});
slider.addChangeListener(this);
/*
The borders of the buttons are set
*/
Border bd1 = BorderFactory.createEtchedBorder();
play.setBorder(bd1);
play.setForeground(new Color(0,0,255));
halt.setBorder(bd1);
ff.setBorder(bd1);
rew.setBorder(bd1);
stop.setBorder(bd1);
export.setBorder(bd1);
slider.setBorder(bd1);
skipM.setBorder(bd1);
skipP.setBorder(bd1);
/*
As layout manager GridBagLayout is used. All Components get their
constraints.
*/
InfoPanel.removeAll();
InfoPanel.add(infoNameLabel);
InfoPanel.add(infoAssumedNameLabel);
InfoPanel.add(infoTempoLabel);
InfoPanel.add(infoLengthLabel);
InfoPanel.add(infoTickLengthLabel);
InfoPanel.add(infoTrackLabel);
InfoPanel.add(infoResLabel);
InfoPanel.add(setTickPosition);
// The playlist is created
list = new JList(midiVector);
list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
list.addMouseListener(new MouseAdapter()
{
public void mouseClicked(MouseEvent e)
{
if (e.getClickCount() == 2 && listSelectionAllowed)
{
listSelectionAllowed = false;
unload();
thisMidiFile = ((MidiFile) list.getSelectedValue());
load();
play();
setInfoPanel();
setTrackPanel();
listSelectionAllowed = true;
}
}
});
JScrollPane listScrollPane = new JScrollPane(list);
// Trackpanel is generated
TrackPanel.setLayout(new GridLayout(1,1));
JScrollPane trackScrollPane = new JScrollPane(TrackPanel);
// playercontrols are generated
GridBagLayout playLayout = new GridBagLayout();
GridBagConstraints playConstraints = new GridBagConstraints();
PlayerPanel.setLayout(playLayout);
playConstraints.gridx = 0;
playConstraints.gridy = 0;
playConstraints.gridwidth = 2;
playConstraints.gridheight = 1;
playConstraints.weightx = 100;
playConstraints.weighty = 100;
playConstraints.fill = GridBagConstraints.BOTH;
playLayout.setConstraints(slider, playConstraints);
PlayerPanel.add(slider);
playConstraints.gridx = 2;
playLayout.setConstraints(time, playConstraints);
PlayerPanel.add(time);
playConstraints.gridwidth = 1;
playConstraints.gridx = 0;
playConstraints.gridy = 1;
playLayout.setConstraints(play, playConstraints);
PlayerPanel.add(play);
playConstraints.gridx = 1;
playLayout.setConstraints(halt, playConstraints);
PlayerPanel.add(halt);
playConstraints.gridx = 2;
playLayout.setConstraints(stop, playConstraints);
PlayerPanel.add(stop);
playConstraints.gridx = 3;
playLayout.setConstraints(export, playConstraints);
PlayerPanel.add(export);
playConstraints.gridx = 0;
playConstraints.gridy = 2;
playLayout.setConstraints(rew, playConstraints);
PlayerPanel.add(rew);
playConstraints.gridx = 1;
playLayout.setConstraints(skipM, playConstraints);
PlayerPanel.add(skipM);
playConstraints.gridx = 2;
playLayout.setConstraints(skipP, playConstraints);
PlayerPanel.add(skipP);
playConstraints.gridx = 3;
playLayout.setConstraints(ff, playConstraints);
PlayerPanel.add(ff);
// InfoPanel is generated
InfoPanel.setLayout(new GridLayout(8,1));
InfoPanel.setBorder(bd1);
playPanel.setLayout(new GridLayout(1,1));
GridBagLayout layoutLeftTop = new GridBagLayout();
GridBagConstraints constraintsLeftTop = new GridBagConstraints();
GridBagLayout layoutLeftButtom = new GridBagLayout();
GridBagConstraints constraintsLeftButtom = new GridBagConstraints();
GridBagLayout layoutRight = new GridBagLayout();
GridBagConstraints constraintsRight = new GridBagConstraints();
JPanel left = new JPanel(new GridLayout(1,1));
JPanel right = new JPanel(layoutRight);
JPanel top = new JPanel(layoutLeftTop);
JPanel buttom = new JPanel(layoutLeftButtom);
// adding the player controls
constraintsRight.gridx = 0;
constraintsRight.gridy = 0;
constraintsRight.gridwidth = 1;
constraintsRight.gridheight = 1;
constraintsRight.weightx = 100;
constraintsRight.weighty = 100;
constraintsRight.fill = GridBagConstraints.BOTH;
layoutRight.setConstraints(PlayerPanel, constraintsRight);
right.add(PlayerPanel);
// adding the InfoPanel
constraintsRight.gridy = 1;
layoutRight.setConstraints(InfoPanel, constraintsRight);
right.add(InfoPanel);
// adding the trackpanel
constraintsLeftButtom.gridx = 0;
constraintsLeftButtom.gridy = 0;
constraintsLeftButtom.gridwidth = 1;
constraintsLeftButtom.gridheight = 1;
constraintsLeftButtom.weightx = 100;
constraintsLeftButtom.weighty = 100;
constraintsLeftButtom.fill = GridBagConstraints.BOTH;
layoutLeftButtom.setConstraints(trackScrollPane, constraintsLeftButtom);
buttom.add(trackScrollPane);
// adding the list
constraintsLeftTop.gridx = 0;
constraintsLeftTop.gridy = 0;
constraintsLeftTop.gridwidth = 1;
constraintsLeftTop.gridheight = 1;
constraintsLeftTop.weightx = 100;
constraintsLeftTop.weighty = 100;
constraintsLeftTop.fill = GridBagConstraints.BOTH;
layoutLeftTop.setConstraints(listScrollPane, constraintsLeftTop);
top.add(listScrollPane);
JSplitPane splitPaneLeftVertical = new JSplitPane(JSplitPane.VERTICAL_SPLIT, top, buttom);
splitPaneLeftVertical.setContinuousLayout(true);
left.add(splitPaneLeftVertical);
JSplitPane splitPaneHorizontal = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, left, right);
splitPaneHorizontal.setContinuousLayout(true);
splitPaneHorizontal.setDividerLocation(400);
playPanel.add(splitPaneHorizontal);
setInfoPanel();
setTrackPanel();
}
/*
2.2.2 stateChanged
Implementation of the ChangeListener belonging to the slider
*/
public void stateChanged(ChangeEvent e)
{
if(slider.getValueIsAdjusting())
{
mouseSliding = true;
}
if(!slider.getValueIsAdjusting())
{
if(sequencer.isOpen() && mouseSliding)
{
setPosition(slider.getValue());
mouseSliding = false;
}
}
}
/*
2.3.2 refreshSlider
Refreshes the slider
*/
private void refreshSlider()
{
if (sequencer.isOpen())
{
if (!mouseSliding)
{
slider.setValue((int) getPosition());
if (displayedTempo != getTempo())
{
setInfoPanel();
}
}
}
}
/*
2.2.2 refreshTime
Refreshes the time display
*/
private void refreshTime()
{
if (sequencer.isOpen())
{
time.setText(getCurrentTime() + " / " + lengthOfCurrentMidi);
}
}
/*
3.3.3 getIndexNo
Extracts the first three digits of a given ~String~ and returns them as
an ~Integer~ value. This function is used to get the track number when a
mute check box has changed its value by using only the check box name.
*/
private int getIndexNo(String s)
{
// by constructing the strings it is guarenteed that there are at
// least 3 Characters
char c1 = s.charAt(0);
char c2 = s.charAt(1);
char c3 = s.charAt(2);
int result = 0;
int firstDigit = (int) '0';
boolean b = Character.isDigit(c3);
// indicates which of the three characters contains is relevant
if (b)
{
result += (((int) c3) - firstDigit);
result += (((int) c2) - firstDigit)*10;
result += (((int) c1) - firstDigit)*100;
return result;
}
b = Character.isDigit(c2);
if (b)
{
result += (((int) c2) - firstDigit);
result += (((int) c1) - firstDigit)*10;
return result;
}
else
{
result += (((int) c1) - firstDigit);
}
return result;
}
/*
2.2.2 itemStateChanged
Implementation of the ItemListener belonging to the checkboxes of the
track panel
*/
public void itemStateChanged(ItemEvent e)
{
JCheckBox cb = (JCheckBox)e.getSource();
int index = getIndexNo(cb.getText());
if (e.getStateChange() == ItemEvent.SELECTED)
{
changeMute(index, false);
}
else
{
changeMute(index, true);
}
}
/*
5.5.5 load
Loads the sequence of the current MidiFile into the sequencer and
starts playing
*/
public boolean load()
{
if (sequencer.isOpen()) unload();
try { sequencer.open(); }
catch (MidiUnavailableException e)
{
System.out.println("MidFile 5: Midi unavailable");
return false;
}
try { sequencer.setSequence(thisMidiFile.getSequence());}
catch (InvalidMidiDataException e)
{
System.out.println("MidiFile 6: Invalid Mididfile");
return false;
}
/*
A fresh Midi starts at the beginning
*/
startAt = 0;
/*
The skipping amount is calculated
*/
skippy = sequencer.getTickLength() / 20;
/*
And finally the length of the Sequence is expressed as MM:SS
*/
long ticklength = sequencer.getMicrosecondLength();
ticklength = ticklength / 1000000;
long minutes = ticklength / 60;
ticklength = ticklength - minutes*60;
// ticklength -> now the remaining seconds
lengthOfCurrentMidi = Long.toString(minutes);
lengthOfCurrentMidi += ":";
if (ticklength < 10) { lengthOfCurrentMidi += "0"; }
lengthOfCurrentMidi += Long.toString(ticklength);
slider.setMaximum((int) getLengthInTicks());
setInfoPanel();
setMetaPanel();
return true;
}
/*
2.2.2 unload
Closes the sequencer and releases the captured resources
*/
public void unload()
{
if(sequencer.isOpen())
{
sequencer.stop();
sequencer.close();
}
startAt = 0;
thisMidiFile = null;
}
/*
2.2.2 play
Starts the sequencer. If it is already running the speed is set to
~normal~.
*/
public void play()
{
if (!sequencer.isRunning())
{
sequencer.setTickPosition(startAt);
sequencer.start();
}
else
{
sequencer.setTempoFactor(1.0f);
}
}
/*
2.2.2 stop
Stops the sequencer
*/
public void stop()
{
sequencer.stop();
sequencer.setTickPosition(0);
startAt = 0;
}
/*
3.3.3 halt
Stops the sequencer, but the current Position remains - pause function
*/
public void halt()
{
startAt = sequencer.getTickPosition();
sequencer.stop();
}
/*
2.2.2 fastforward
Increases the speed by factor 2
*/
public void fastforward()
{
if (sequencer.getTempoFactor() < 8)
{
sequencer.setTempoFactor(sequencer.getTempoFactor() * 2.0f);
}
}
/*
9.9.9 rewind
Decreases the speedfactor by 2
~Not really a~ rewind ~function but the opposite of fastforward.~
*/
public void rewind ()
{
if (sequencer.getTempoFactor() > 0.25)
{
sequencer.setTempoFactor(sequencer.getTempoFactor() * 0.5f);
}
}
/*
2.2.2 skipP
Increases the actual position by 1/10 of the whole
*/
public void skipP()
{
if((skippy+sequencer.getTickPosition())<getLengthInTicks())
{
sequencer.setTickPosition(sequencer.getTickPosition() + skippy);
}
}
/*
4.4.4 skipM
Decreases the actual position by 1/10 of the whole
*/
public void skipM()
{
if (skippy < sequencer.getTickPosition())
{
sequencer.setTickPosition(sequencer.getTickPosition() - skippy);
}
else
{
sequencer.setTickPosition(0);
}
}
/*
6.6.6 setPosition
Sets the current position, necceassary for the slider
*/
public void setPosition(long tick)
{
sequencer.setTickPosition(tick);
}
/*
3.3.3 getPosition
Returns the current Position
*/
public long getPosition()
{
try
{
return sequencer.getTickPosition();
}
catch (NullPointerException e)
{
return 0;
}
}
/*
5.4.4 getLengthInTicks
Returns the length of the sequence in ticks
*/
public long getLengthInTicks()
{
return sequencer.getTickLength();
}
/*
2.2.2 getCurrentTime
Returns the current Position expressed as a ~String~ (MM:SS)
*/
public String getCurrentTime()
{
try
{
String mtime;
long ticklength = sequencer.getMicrosecondPosition();
ticklength = ticklength / 1000000;
long minutes = ticklength / 60;
ticklength = ticklength - minutes*60;
// length -> remaining seconds
mtime = Long.toString(minutes);
mtime += ":";
if (ticklength < 10) {mtime += "0";}
mtime += Long.toString(ticklength);
return mtime;
}
catch (NullPointerException e)
{
return "0:00";
}
}
/*
3.3.3 getTempoFactor
Returns the current tempo factor
*/
public String getTempoFactor()
{
try
{
return Float.toString(sequencer.getTempoFactor());
}
catch (NullPointerException e)
{
return "1.0";
}
}
/*
3.3.3 getTempo
Returns the tempo in BPM
*/
public float getTempo()
{
return (sequencer.getTempoInBPM() * sequencer.getTempoFactor());
}
/*
2.2.2 changeMute
Changes the mute state of target track
*/
public void changeMute(int track, boolean newMuteState)
{
if (track <= thisMidiFile.getNrTracks())
sequencer.setTrackMute(track-1, newMuteState);
}
/*
5.5.5 kickThem
Removes all objects with target ID from the playlist. Expecially needed
to remove all midis loaded from a relation, when the relation is removed.
*/
public void kickThem (String id)
{
if (thisMidiFile == null || thisMidiFile.getID().equals(id))
{
unload();
}
try
{
int midiVectorSize = midiVector.size();
// by removing elements, the vectors size is decreased
// to avoid NullPointerExceptions, the vector size must be updated
for (int i = 0; i < midiVectorSize; i++)
{
if (((MidiFile) midiVector.get(i)).getID().equals(id))
{
midiVector.removeElementAt(i);
i--;
midiVectorSize--;
}
}
list.setListData(midiVector);
}
catch (Exception e)
{
System.out.println("MidiViewer: incorrect Vectorhandling");
}
}
/*
3.3 The Relation Viewer
3.3.3 createAndShowRelationGUI
The ~construcor~ of the relation tab. Adapted from the standard
relation viewer.
*/
public void createAndShowRelationGUI()
{
ComboBox = new JComboBox();
ScrollPane = new JScrollPane();
dummy = new JPanel();
CurrentTable = null;
Tables = new Vector();
relPanel.setLayout(new BorderLayout());
relPanel.add(ComboBox,BorderLayout.NORTH);
relPanel.add(ScrollPane,BorderLayout.CENTER);
ComboBox.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent evt)
{
showSelectedObject();
}
});
}
/*
4.4.4 showSelectedObject
Shows the table of the selected relation
*/
private void showSelectedObject()
{
int index = ComboBox.getSelectedIndex();
if( index >= 0 )
{
CurrentTable = (JTable)Tables.get(index);
ScrollPane.setViewportView(CurrentTable);
}
else
ScrollPane.setViewportView(dummy);
}
/*
2.2.2 createTableFrom
Creates the table of a relation
*/
private JTable createTableFrom(ListExpr LE, String secondoName)
{
int midiPosition = -1;
boolean result = true;
JTable NTable = null;
Vector relMidis = new Vector();
if (LE.listLength()!=2)
return null;
else
{
ListExpr type = LE.first();
ListExpr value = LE.second();
ListExpr maintype = type.first();
// analyse type
if (type.listLength()!=2 || !maintype.isAtom() ||
maintype.atomType()!=ListExpr.SYMBOL_ATOM ||
!(maintype.symbolValue().equals("rel") |
maintype.symbolValue().equals("mrel"))) return null;
ListExpr tupletype = type.second();
ListExpr TupleFirst=tupletype.first();
if (tupletype.listLength()!=2 || !TupleFirst.isAtom() ||
TupleFirst.atomType()!=ListExpr.SYMBOL_ATOM ||
!(TupleFirst.symbolValue().equals("tuple") |
TupleFirst.symbolValue().equals("mtuple"))) return null;
ListExpr TupleTypeValue = tupletype.second();
String[] head=new String[TupleTypeValue.listLength()];
for(int i=0;!TupleTypeValue.isEmpty()&&result;i++)
{
ListExpr TupleSubType = TupleTypeValue.first();
if (TupleSubType.listLength()!=2) result = false;
else
{
head[i] = TupleSubType.first().writeListExprToString();
head[i] += " "+
TupleSubType.second().writeListExprToString();
if (TupleSubType.second().writeListExprToString().endsWith("midi"))
{
midiPosition = i;
}
}
TupleTypeValue = TupleTypeValue.rest();
}
if (result)
{
ListExpr TupleValue;
Vector V = new Vector();
String[] row;
int pos;
ListExpr Elem;
while (!value.isEmpty())
{
TupleValue = value.first();
row = new String[head.length];
pos = 0;
while(pos<head.length & !TupleValue.isEmpty())
{
Elem = TupleValue.first();
if (Elem.isAtom() && Elem.atomType()==ListExpr.STRING_ATOM)
{
row[pos] = Elem.stringValue();
}
else
if(Elem.isAtom() && Elem.atomType()==ListExpr.TEXT_ATOM)
{
if(Elem.textLength()<40)
{
row[pos] = Elem.textValue();
}
else
{
row[pos] = "a long text";
}
}
else
{
row[pos] = TupleValue.first().writeListExprToString();
System.out.println(row[pos]);
if (pos == midiPosition)
{
Base64Decoder b64;
StringReader sr = new StringReader(
TupleValue.first().first().textValue());
b64 = new Base64Decoder(sr);
MidiFile midi = new MidiFile(b64.getInputStream(),
( "Midi " + V.size() + " of Rel " +
secondoName), secondoName);
relMidis.addElement(midi);
row[pos] = midi.getName();
}
}
pos++;
TupleValue = TupleValue.rest();
}
V.add(row);
value = value.rest();
}
String[][] TableDatas = new String[V.size()][head.length];
for(int i=0;i<V.size();i++)
{
TableDatas[i]=(String[]) V.get(i);
}
midiTables.add(relMidis);
NTable = new JTable(TableDatas,head);
NTable.getSelectionModel().addListSelectionListener(this);
}
}
if(result)
return NTable;
else
return null;
}
/*
3.3.3 valueChanged
Implementation of the ListSelectionListener belonging to the relation
tables.
*/
public void valueChanged(ListSelectionEvent e)
{
if (!e.getValueIsAdjusting())
{
int last = CurrentTable.getSelectedRow();
MidiFile midi = (MidiFile)
((Vector)midiTables.get(ComboBox.getSelectedIndex())).get(last);
unload();
midiVector.addElement(midi);
list.setListData(midiVector);
thisMidiFile = midi;
load();
long l = getLengthInTicks();
length = Long.toString(l);
play();
slider.setMaximum((int) getLengthInTicks());
currentTracks = thisMidiFile.getTrackArray();
setTrackPanel();
setInfoPanel();
jtp.setSelectedIndex(0);
}
}
/*
5.5.5 containsMidiObject
Checks wheter a relation contains a Midi or not
*/
public boolean containsMidiObject(SecondoObject o)
{
ListExpr LE = o.toListExpr();
boolean result = true;
if (LE.listLength()!=2)
return false;
else
{
ListExpr type = LE.first();
ListExpr value = LE.second();
ListExpr maintype = type.first();
if (type.listLength()!=2 || !maintype.isAtom() ||
maintype.atomType()!=ListExpr.SYMBOL_ATOM ||
!(maintype.symbolValue().equals("rel") |
maintype.symbolValue().equals("mrel"))) return false;
ListExpr tupletype = type.second();
ListExpr TupleFirst=tupletype.first();
if (tupletype.listLength()!=2 || !TupleFirst.isAtom() ||
TupleFirst.atomType()!=ListExpr.SYMBOL_ATOM ||
!(TupleFirst.symbolValue().equals("tuple") |
TupleFirst.symbolValue().equals("mtuple"))) return false;
ListExpr TupleTypeValue = tupletype.second();
String[] head=new String[TupleTypeValue.listLength()];
for(int i=0;!TupleTypeValue.isEmpty()&&result;i++)
{
ListExpr TupleSubType = TupleTypeValue.first();
if (TupleSubType.listLength()!=2) result = false;
else
{
head[i] = TupleSubType.first().writeListExprToString();
head[i] += " "+
TupleSubType.second().writeListExprToString();
if (TupleSubType.second().writeListExprToString().endsWith("midi"))
{
return true;
}
}
TupleTypeValue = TupleTypeValue.rest();
}
return false;
}
}
/*
2.2.2 getIndexOf
Returns the ComboBox index of selected relation
*/
private int getIndexOf(String S)
{
int count = ComboBox.getItemCount();
int pos = -1;
for( int i = 0; i < count; i++)
{
if( ((String)ComboBox.getItemAt(i)).equals(S))
{
pos = i;
}
}
return pos;
}
}
/*
----
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{] [}]
//[ss] [{\ss}]
//[<=] [\leq]
//[#] [\neq]
//[tilde] [\verb|~|]
//[->] [$\rightarrow $]
\pagebreak
1 Implementation of MidiFile
This class provides a design to represent Midi objects in java. As the
MidiViewer handles only one sequencer, the current implementation of
MidiFile is some sort of a ~lightweight~ object design. In fact, only the
sequence and some (generated) meta informations are part of a
MidiFile object.
2 Imports
*/
package viewer.midi;
import javax.swing.*;
import javax.swing.filechooser.*;
import gui.SecondoObject;
import sj.lang.*;
import java.io.File;
import java.io.InputStream;
import java.io.IOException;
import java.io.*;
import javax.sound.midi.*;
import javax.sound.midi.Track.*;
import java.util.Vector;
/*
2 Class ~MidiFile~
*/
public class MidiFile
{
/*
2.1 private Components of a MidiFile
The main content of this object is the sequence, containing all
informations of an Midi file
*/
private Sequence sequence = null;
/*
A MidiFile has actually two names. The first one is the name it gets
from SECONDO itself. This name is a copy of its own query call. The
assumedName is generated from the Midi sequence itself, evaluating
its metamessages. Unfortunately there is no adequate standard for these
metamessage (in matters of name descriptions) as there is no special
metaevent for this purpose. So it is not possible to get the right
name surely. That's why the name is ~assumed~.
*/
private String name = "No Name declared... NOW!";
private String assumedName = "No Name found";
/*
The SECONDO-ID
*/
private String id;
/*
Trackhandling variables
*/
private Track[] tracks;
private int numberOfTracks = 0;
private int expectedTracks;
private MidiTrack[] mTracks;
/*
2.1 Constructor
The constructor is called with three arguments. First, an inputstream
representing the binary input of a Midi. Second, the name of the object
in Secondo terms (e.g. query midi1). Third, the constructor gets the
SECONDO-ID of the Midi file.
*/
public MidiFile (InputStream stream, String secondoName, String secondoID)
{
name = secondoName;
id = secondoID;
try {sequence = MidiSystem.getSequence(stream);}
catch (InvalidMidiDataException e)
{
System.out.println("MidiFile 1: Invalid Midifile");
name = "Error2";
}
catch (IOException e)
{
System.out.println("MidiFile 2: Readingerror");
name = "Error 2";
}
/*
Analyzing the Tracks, to find all usefull meta informations
*/
tracks = sequence.getTracks();
numberOfTracks = tracks.length;
mTracks = new MidiTrack[tracks.length];
boolean foundName = false;
for (int t =0; t < tracks.length; t++)
{
mTracks[t] = new MidiTrack(t, tracks[t]);
if (mTracks[t].foundPossibleName() && !foundName)
{
foundName = true;
assumedName = mTracks[t].getName().substring(3);
}
}
}
/*
2.1 get-Methods
2.1.1 getName
Returns the assumed name, generated by the meta informations provided
by the sequence itself
*/
public String getName()
{
return assumedName;
}
/*
2.1.1 getID
Returns the Secondo ID of this Object
*/
public String getID()
{
return id;
}
/*
2.1.1 getSequence
Returns the main information of a MidiFile, the sequence itself.
It's expecially needed by the viewer to load the sequence into the
sequencer.
*/
public Sequence getSequence()
{
return sequence;
}
/*
2.1.1 getNrTracks
Returns the number of tracks of the MidiFile
*/
public int getNrTracks()
{
return numberOfTracks;
}
/*
2.1.1 getResolution
Returns the Resolution of a sequence. The analysis of what kind
of divisiontype is currently used belongs to the viewer. It checks if it is
PPQ (ticks per beat) or SMTPE (ticks per Frame).
*/
public int getRes()
{
return sequence.getResolution();
}
/*
2.1.1 getTrackArray
Returns the array containing the tracks of the sequence
*/
public MidiTrack[] getTrackArray()
{
return mTracks;
}
/*
2.1.1 getMetaInfos
Returns a vector of the meta informations generated by the constructor
*/
public Vector getMetaInfos(int trackNo)
{
try
{
return mTracks[trackNo].getMeta();
}
catch (ArrayIndexOutOfBoundsException e)
{
return null;
}
}
/*
2.1.1 getDivisionType
Returns the divisiontype of the sequence
*/
public float getDivisionType()
{
return sequence.getDivisionType();
}
/*
2.1 additional Methods
2.1.1 equals
Checks if the MidiFile was generated from the Secondo-Object with
this ID
*/
public boolean equals(String secondoID)
{
return (id == secondoID);
}
/*
2.1.1 export
Provides a simple and easy way to save the running Midi.
Expecially, when midis are displayed out of a relation the
operator ~saveto~, would not be ones first choice.
*/
public void export()
{
final JFileChooser fc = new JFileChooser();
fc.addChoosableFileFilter(new MidiFilter());
int returnVal = fc.showSaveDialog(null);
File chosenFile = fc.getSelectedFile();
File file;
if (returnVal == JFileChooser.APPROVE_OPTION)
{
try
{
String ex = MidiFilter.getExtension(chosenFile);
if (ex == null || !(ex.equals("mid") || ex.equals("midi")))
{
file = new File(chosenFile.getAbsolutePath() + ".mid");
}
else
{
file = fc.getSelectedFile();
}
MidiSystem.write(sequence,1,file);
}
catch (Exception e) {System.out.println("Error in writing");}
}
}
/*
2.1.1 toString
Overrides the toString-Method. It is used to display the ~query~-name inside the playlist.
*/
public String toString()
{
return name;
}
}
/*
----
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{] [}]
//[ss] [{\ss}]
//[<=] [\leq]
//[#] [\neq]
//[tilde] [\verb|~|]
//[->] [$\rightarrow $]
\pagebreak
1 Implementation of MidiTrack
This class its purpose is to evaluate the meta informations provided by
the tracks meta messages
2 Imports
*/
package viewer.midi;
import javax.sound.midi.*;
import javax.sound.midi.Track.*;
import java.util.Vector;
/*
3 Class ~MidiTrack~
*/
public class MidiTrack
{
/*
3.1 private components of a MidiTrack
The name found by analyzing the tracks meta events
*/
private String name;
/*
The track number of the current track
*/
private int index;
/*
The vector contains all found meta informations. Used by the MidiViewer for creating the tab ~MetaInformations~.
*/
private Vector metaInfos = new Vector();
/*
If a possible name (assumedName) has been found, the foundName flag will
be set
*/
private boolean foundName = false;
/*
3.1 Constructor
The constructoer is called with two arguments: the tracknumber and the
track itself. It checks the whole track for any meta messages and
evaluates them. By assuming the name of the sequence, the meta message
has the type ~03~. As this message could also describe the tracks name
we assume that the track, to which the sequence name belongs, has a length
of zero ticks. This seems to be the only practicable approach.
*/
MidiTrack(int i, Track track)
{
name = "";
MidiEvent midiEvent;
MetaMessage metaMessage;
MidiMessage midiMessage;
String s;
// a simple reference to a String to get the messages
metaInfos.addElement(("Track Number: "+(i+1)));
for (int j = 0; j < track.size(); j++)
{
midiEvent = track.get(j);
midiMessage = midiEvent.getMessage();
if (midiMessage.getStatus() == 255) // that is a MetaMessage
{
metaMessage = (MetaMessage) midiEvent.getMessage();
if (metaMessage.getType() == 3) // Type 3 = Track or Sequence name
{
s = new String(metaMessage.getData());
name += s;
metaInfos.addElement("Name : " + name);
if (track.ticks() == 0)
{
foundName = true;
}
}
if (metaMessage.getType() == 1) // Type 1 = Text Event
{
s = new String(metaMessage.getData());
metaInfos.addElement(s);
}
if (metaMessage.getType() == 2) // Type 2 = copyright info
{
s = new String(metaMessage.getData());
metaInfos.addElement("Copyright: " + s);
}
if (metaMessage.getType() == 4) // Type 4 = Track instrument name
{
s = new String(metaMessage.getData());
metaInfos.addElement("Instrument: " + s);
}
}
s = null;
midiEvent = null;
metaMessage = null;
}
index = i;
metaInfos.addElement(("Tracklength in MidiTicks: " + track.ticks()));
metaInfos.addElement(("Number of Events: " + track.size()));
metaInfos.addElement("--------");
metaInfos.addElement("");
name = i+1 + " " + name;
}
/*
3.3 getMethods
3.3.3 getName
Returns the assumedName
*/
public String getName()
{
return name;
}
/*
3.3.3 getIndex
Returns the tracknumber of the current track
*/
public int getIndex()
{
return index;
}
/*
4.4.4 getMeta
Returns the generated meta informations as a vector of ~Strings~
*/
public Vector getMeta()
{
return metaInfos;
}
/*
5.5.5 foundPossibleName
Returns true, if the sequence name was found
*/
public boolean foundPossibleName()
{
return foundName;
}
}