home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
The World of Computer Software
/
World_Of_Computer_Software-02-385-Vol-1of3.iso
/
m
/
muzsrc1.zip
/
MIDI.CPP
< prev
next >
Wrap
C/C++ Source or Header
|
1992-07-22
|
10KB
|
284 lines
// **********************************************
// File: MIDI.CPP
// MIDI file creation functions
#include "muzika.h"
#include <alloc.h>
#include <dir.h>
#include <fstream.h>
#include <io.h>
#include <string.h>
#include <values.h>
// **********************************************
// MIDIEvent is a class type for event lists
// used in MIDISecondPass.
class MIDIEvent : public Object {
public:
unsigned long delta;
unsigned char freq;
enum EVENT {PAUSE, NOTEOFF} code;
MIDIEvent() {}
MIDIEvent(unsigned long d, unsigned char f, enum EVENT c)
{delta = d; freq = f; code = c;}
virtual classType isA() const {return 0;}
virtual char *nameOf() const {return NULL;}
virtual hashValueType hashValue() const {return 0;}
virtual int isEqual(const Object &) const {return 0;}
virtual void printOn(ostream &) const {}
};
// **********************************************
// MIDIStaff converts a multiple staff to a temporary
// MIDI-like file during the first pass of the translation.
// The temporary file contains information about note-on
// and note-off events, to be converted to a final format
// during the second pass.
void MIDIStaff(ostream &out, int baseStaff, int multiplicity)
{
// Allocate a few lists for use in the sequel
int *duration = (int *) malloc(multiplicity*sizeof(int));
int *index = (int *) malloc(multiplicity*sizeof(int));
BOOL *staffEnd = (int *) malloc(multiplicity*sizeof(BOOL));
memset(duration, 0, multiplicity*sizeof(int));
memset(index, 0, multiplicity*sizeof(int));
memset(staffEnd, 0, multiplicity*sizeof(BOOL));
BOOL multipleEnd;
// Loop as long as the end has not been reached on all single staves
do {
// Find the minimum next object duration among the staves
int minIndex = FindMinDuration(duration, multiplicity);
int minDuration = duration[minIndex];
int partIndex = 0, staffIndex = 0, anIndex = 0;
multipleEnd = TRUE;
// Subtract the minimum duration from all staves
while (partIndex < melody.part.number()) {
Part &p = *((Part *) &melody.part[partIndex]);
Staff &s = *((Staff *) &p.staff[baseStaff*p.multiplicity()+staffIndex]);
if (!(duration[anIndex] -= minDuration)) {
// The duration has reached zero as a result of the subtraction:
// place the next object in the temporary event file
int currX = ((PointObject *) &s.pointObject[index[anIndex]])->X();
while (!(staffEnd[anIndex] =
index[anIndex] == s.pointObject.number())) {
PointObject &obj =
*((PointObject *) &s.pointObject[index[anIndex]]);
if (obj.X() == currX) {
if (duration[anIndex] < obj.Duration())
duration[anIndex] = obj.Duration();
// Use the object's MIDIPlay virtual function to write
// the object information to the file
obj.MIDIPlay(out, partIndex);
++index[anIndex];
}
else break;
}
}
// Check whether the end has been reached on all single staves
multipleEnd = multipleEnd && staffEnd[anIndex];
++anIndex;
++staffIndex;
if (staffIndex >= p.staff.number()) {
++partIndex;
staffIndex = 0;
}
}
} while (!multipleEnd);
// Free the temporary lists on the heap
free(staffEnd);
free(index);
free(duration);
}
// **********************************************
// MIDIFirstPass performs the first pass of the
// melody-to-MIDI translation process, creating a temporary
// MIDI-like file with information about the played notes.
void MIDIFirstPass(ofstream &out)
{
// Calculate the total number of staves
int scoreMultiplicity = 0;
scoreStaves = 0;
for (int index = 0; index < melody.part.number(); ++index) {
Part &p = *((Part *) &melody.part[index]);
scoreMultiplicity += p.multiplicity();
if (p.staff.number()/p.multiplicity() > scoreStaves)
scoreStaves = p.staff.number()/p.multiplicity();
}
// Call MIDIStaff once per every multiple staff
for (int scoreIndex = 0; scoreIndex < scoreStaves; ++scoreIndex)
MIDIStaff(out, scoreIndex, scoreMultiplicity);
}
// **********************************************
// InsertEvent inserts a MIDI event in an IndexedList object
// during the second pass of the translation. The events
// are held in a delta list, with the delta field holding the
// times between events.
void InsertEvent(IndexedList &list, enum MIDIEvent::EVENT event,
unsigned char freq, unsigned long delay)
{
MIDIEvent *e;
// Find the index to insert the event at, reducing the delay in the way
for (int i = 0;
i < list.number() && (e = ((MIDIEvent *) &list[i]))->delta < delay;
++i)
delay -= e->delta;
// Subtract the new event's delay from the next event
if (i < list.number())
e->delta -= delay;
// Insert the new event in the list
list.insertAt(*new MIDIEvent(delay, freq, event), i);
}
// **********************************************
// WriteVariableLength converts a long-integer duration
// to the variable-length, MIDI-file format.
int WriteVariableLength(ostream &out, unsigned long duration)
{
out.put((char) duration);
return 1;
}
// **********************************************
// MIDISecondPass converts the temporary file, created by
// MIDIFirstPass, to the final-version MIDI file. The main job
// of MIDISecondPass is to insert note-off events at the right places.
// Whenever a note-on event is encountered, a note-off event is
// inserted in the appropriate event list, and is eventually
// written to the MIDI file.
void MIDISecondPass(ifstream &in, ostream &out)
{
const int n = melody.part.number();
IndexedList event[16];
unsigned long length = 0;
unsigned char code;
unsigned long duration;
BOOL allEmpty = TRUE, oneEmpty;
// Write the MIDI file header
out.write("MThd\0\0\0\6\0\0\0\1\0\x0C", 14);
out.write("MTrk\0\0\0\0", 8);
out.write("\0\xFF\x58\4\4\2\x18\8", 8);
out.write("\0\xFF\x51\3\8\x2C\xA2", 7);
length += 15;
for (int index = 0; index < n; ++index) {
out.put(0);
out.put(0xC0+index);
out.write("\x58\5", 2);
length += 4;
}
// Loop as long as the file and the event lists are not over with
while (!in.eof() || !allEmpty) {
// Check if there are empty lists
oneEmpty = FALSE;
for (index = 0; index < n; ++index)
oneEmpty = oneEmpty || (event[index].number() == 0);
// If at least one empty list, try to read another event from the file
if (oneEmpty && !in.eof()) {
if ((code = in.get()) >= 0x80 && code <= 0x8F) {
// The code is a Pause of some kind:
// Put a PAUSE event at end of list
in.read((char *) &duration, sizeof(int));
InsertEvent(event[code-0x80], MIDIEvent::PAUSE, 0, duration);
}
else if (code >= 0x90 && code <= 0x9F) {
// The code is a Note of some kind:
// Put a "note on" event in file
// and insert a NOTEOFF event at end of list
in.read((char *) &duration, sizeof(int));
out.put(0);
out.put(code);
unsigned char freq;
out.put(freq = in.get());
out.put(in.get());
length += 4;
InsertEvent(event[code-0x90], MIDIEvent::NOTEOFF, freq, duration);
}
else if (code == 0xFF)
// Code is a meta-event:
// do whatever is supported about meta-events (currently nothing)
;
}
else {
// No empty lists:
// Find the list with minimum delta-time
// in order to write its event into the MIDI file
unsigned long minDelta = MAXLONG;
for (index = 0; index < n; ++index)
if (event[index].number())
if (((MIDIEvent *) &event[index][0])->delta < minDelta)
minDelta = ((MIDIEvent *) &event[index][0])->delta;
duration = minDelta;
// Subtract the delta-time from all lists
for (index = 0; index < n; ++index)
if (event[index].number())
if (!(((MIDIEvent *) &event[index][0])->delta -= minDelta)) {
// Output the event to file and accumulate length
MIDIEvent &e = *((MIDIEvent *) &event[index][0]);
length += WriteVariableLength(out, duration)+3;
out.put(0x80+index);
out.put(e.freq);
out.put(0x7F);
duration = 0;
event[index].destroyAt(0);
}
}
// See if all lists have been processed
allEmpty = TRUE;
for (index = 0; index < n; ++index)
allEmpty = allEmpty && (event[index].number() == 0);
}
// Write a Track End event
out.write("\0\xFF\x2F\0", 4);
// Update the track length information
length += 4;
out.seekp(18);
char revlength[sizeof(length)];
memcpy(revlength, &length, sizeof(length));
for (index = sizeof(length)-1; index >= 0; --index)
out.put(revlength[index]);
}
// **********************************************
// CreateMIDIFile is called from the outside to create a MIDI file.
// It calls MIDIFirstPass with a temporary file and then
// MIDISecondPass to translate the temporary to a final file.
void CreateMIDIFile(ostream &out)
{
char path[MAXPATH];
strcpy(path, ".\\");
ofstream temp(creattemp(path, 0));
MIDIFirstPass(temp);
temp.close();
ifstream temp1(path, ios::binary | ios::in);
MIDISecondPass(temp1, out);
temp1.close();
unlink(path);
}