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 >
C/C++ Source or Header  |  1992-07-22  |  10KB  |  284 lines

  1. // **********************************************
  2. // File: MIDI.CPP
  3. // MIDI file creation functions
  4.  
  5. #include "muzika.h"
  6. #include <alloc.h>
  7. #include <dir.h>
  8. #include <fstream.h>
  9. #include <io.h>
  10. #include <string.h>
  11. #include <values.h>
  12.  
  13. // **********************************************
  14. // MIDIEvent is a class type for event lists
  15. // used in MIDISecondPass.
  16.  
  17. class MIDIEvent : public Object {
  18.   public:
  19.     unsigned long delta;
  20.     unsigned char freq;
  21.     enum EVENT {PAUSE, NOTEOFF} code;
  22.  
  23.     MIDIEvent() {}
  24.     MIDIEvent(unsigned long d, unsigned char f, enum EVENT c)
  25.       {delta = d; freq = f; code = c;}
  26.     virtual classType isA() const {return 0;}
  27.     virtual char *nameOf() const {return NULL;}
  28.     virtual hashValueType hashValue() const {return 0;}
  29.     virtual int isEqual(const Object &) const {return 0;}
  30.     virtual void printOn(ostream &) const {}
  31. };
  32.  
  33. // **********************************************
  34. // MIDIStaff converts a multiple staff to a temporary
  35. // MIDI-like file during the first pass of the translation.
  36. // The temporary file contains information about note-on
  37. // and note-off events, to be converted to a final format
  38. // during the second pass.
  39.  
  40. void MIDIStaff(ostream &out, int baseStaff, int multiplicity)
  41. {
  42.   // Allocate a few lists for use in the sequel
  43.   int *duration = (int *) malloc(multiplicity*sizeof(int));
  44.   int *index = (int *) malloc(multiplicity*sizeof(int));
  45.   BOOL *staffEnd = (int *) malloc(multiplicity*sizeof(BOOL));
  46.   memset(duration, 0, multiplicity*sizeof(int));
  47.   memset(index, 0, multiplicity*sizeof(int));
  48.   memset(staffEnd, 0, multiplicity*sizeof(BOOL));
  49.   BOOL multipleEnd;
  50.  
  51.   // Loop as long as the end has not been reached on all single staves
  52.   do {
  53.     // Find the minimum next object duration among the staves
  54.     int minIndex = FindMinDuration(duration, multiplicity);
  55.     int minDuration = duration[minIndex];
  56.     int partIndex = 0, staffIndex = 0, anIndex = 0;
  57.     multipleEnd = TRUE;
  58.  
  59.     // Subtract the minimum duration from all staves
  60.     while (partIndex < melody.part.number()) {
  61.       Part &p = *((Part *) &melody.part[partIndex]);
  62.       Staff &s = *((Staff *) &p.staff[baseStaff*p.multiplicity()+staffIndex]);
  63.  
  64.       if (!(duration[anIndex] -= minDuration)) {
  65.         // The duration has reached zero as a result of the subtraction:
  66.         // place the next object in the temporary event file
  67.         int currX = ((PointObject *) &s.pointObject[index[anIndex]])->X();
  68.         while (!(staffEnd[anIndex] =
  69.           index[anIndex] == s.pointObject.number())) {
  70.           PointObject &obj =
  71.             *((PointObject *) &s.pointObject[index[anIndex]]);
  72.  
  73.           if (obj.X() == currX) {
  74.             if (duration[anIndex] < obj.Duration())
  75.               duration[anIndex] = obj.Duration();
  76.  
  77.             // Use the object's MIDIPlay virtual function to write
  78.             // the object information to the file
  79.             obj.MIDIPlay(out, partIndex);
  80.             ++index[anIndex];
  81.           }
  82.           else break;
  83.         }
  84.       }
  85.  
  86.       // Check whether the end has been reached on all single staves
  87.       multipleEnd = multipleEnd && staffEnd[anIndex];
  88.       ++anIndex;
  89.       ++staffIndex;
  90.       if (staffIndex >= p.staff.number()) {
  91.         ++partIndex;
  92.         staffIndex = 0;
  93.       }
  94.     }
  95.   } while (!multipleEnd);
  96.  
  97.   // Free the temporary lists on the heap
  98.   free(staffEnd);
  99.   free(index);
  100.   free(duration);
  101. }
  102.  
  103. // **********************************************
  104. // MIDIFirstPass performs the first pass of the
  105. // melody-to-MIDI translation process, creating a temporary
  106. // MIDI-like file with information about the played notes.
  107.  
  108. void MIDIFirstPass(ofstream &out)
  109. {
  110.   // Calculate the total number of staves
  111.   int scoreMultiplicity = 0;
  112.   scoreStaves = 0;
  113.   for (int index = 0; index < melody.part.number(); ++index) {
  114.     Part &p = *((Part *) &melody.part[index]);
  115.     scoreMultiplicity += p.multiplicity();
  116.     if (p.staff.number()/p.multiplicity() > scoreStaves)
  117.       scoreStaves = p.staff.number()/p.multiplicity();
  118.   }
  119.  
  120.   // Call MIDIStaff once per every multiple staff
  121.   for (int scoreIndex = 0; scoreIndex < scoreStaves; ++scoreIndex)
  122.     MIDIStaff(out, scoreIndex, scoreMultiplicity);
  123. }
  124.  
  125. // **********************************************
  126. // InsertEvent inserts a MIDI event in an IndexedList object
  127. // during the second pass of the translation. The events
  128. // are held in a delta list, with the delta field holding the
  129. // times between events.
  130.  
  131. void InsertEvent(IndexedList &list, enum MIDIEvent::EVENT event,
  132.   unsigned char freq, unsigned long delay)
  133. {
  134.   MIDIEvent *e;
  135.  
  136.   // Find the index to insert the event at, reducing the delay in the way
  137.   for (int i = 0;
  138.     i < list.number() && (e = ((MIDIEvent *) &list[i]))->delta < delay;
  139.     ++i)
  140.     delay -= e->delta;
  141.  
  142.   // Subtract the new event's delay from the next event
  143.   if (i < list.number())
  144.     e->delta -= delay;
  145.  
  146.   // Insert the new event in the list
  147.   list.insertAt(*new MIDIEvent(delay, freq, event), i);
  148. }
  149.  
  150. // **********************************************
  151. // WriteVariableLength converts a long-integer duration
  152. // to the variable-length, MIDI-file format.
  153.  
  154. int WriteVariableLength(ostream &out, unsigned long duration)
  155. {
  156.   out.put((char) duration);
  157.   return 1;
  158. }
  159.  
  160. // **********************************************
  161. // MIDISecondPass converts the temporary file, created by
  162. // MIDIFirstPass, to the final-version MIDI file. The main job
  163. // of MIDISecondPass is to insert note-off events at the right places.
  164. // Whenever a note-on event is encountered, a note-off event is
  165. // inserted in the appropriate event list, and is eventually
  166. // written to the MIDI file.
  167.  
  168. void MIDISecondPass(ifstream &in, ostream &out)
  169. {
  170.   const int n = melody.part.number();
  171.   IndexedList event[16];
  172.   unsigned long length = 0;
  173.   unsigned char code;
  174.   unsigned long duration;
  175.   BOOL allEmpty = TRUE, oneEmpty;
  176.  
  177.   // Write the MIDI file header
  178.   out.write("MThd\0\0\0\6\0\0\0\1\0\x0C", 14);
  179.   out.write("MTrk\0\0\0\0", 8);
  180.   out.write("\0\xFF\x58\4\4\2\x18\8", 8);
  181.   out.write("\0\xFF\x51\3\8\x2C\xA2", 7);
  182.   length += 15;
  183.   for (int index = 0; index < n; ++index) {
  184.     out.put(0);
  185.     out.put(0xC0+index);
  186.     out.write("\x58\5", 2);
  187.     length += 4;
  188.   }
  189.  
  190.   // Loop as long as the file and the event lists are not over with
  191.   while (!in.eof() || !allEmpty) {
  192.     // Check if there are empty lists
  193.     oneEmpty = FALSE;
  194.     for (index = 0; index < n; ++index)
  195.       oneEmpty = oneEmpty || (event[index].number() == 0);
  196.  
  197.     // If at least one empty list, try to read another event from the file
  198.     if (oneEmpty && !in.eof()) {
  199.       if ((code = in.get()) >= 0x80 && code <= 0x8F) {
  200.         // The code is a Pause of some kind:
  201.         // Put a PAUSE event at end of list
  202.         in.read((char *) &duration, sizeof(int));
  203.         InsertEvent(event[code-0x80], MIDIEvent::PAUSE, 0, duration);
  204.       }
  205.       else if (code >= 0x90 && code <= 0x9F) {
  206.         // The code is a Note of some kind:
  207.         // Put a "note on" event in file
  208.         // and insert a NOTEOFF event at end of list
  209.         in.read((char *) &duration, sizeof(int));
  210.         out.put(0);
  211.         out.put(code);
  212.         unsigned char freq;
  213.         out.put(freq = in.get());
  214.         out.put(in.get());
  215.         length += 4;
  216.         InsertEvent(event[code-0x90], MIDIEvent::NOTEOFF, freq, duration);
  217.       }
  218.       else if (code == 0xFF)
  219.         // Code is a meta-event:
  220.         // do whatever is supported about meta-events (currently nothing)
  221.         ;
  222.     }
  223.     else {
  224.       // No empty lists:
  225.       // Find the list with minimum delta-time
  226.       // in order to write its event into the MIDI file
  227.       unsigned long minDelta = MAXLONG;
  228.       for (index = 0; index < n; ++index)
  229.         if (event[index].number())
  230.           if (((MIDIEvent *) &event[index][0])->delta < minDelta)
  231.             minDelta = ((MIDIEvent *) &event[index][0])->delta;
  232.       duration = minDelta;
  233.  
  234.       // Subtract the delta-time from all lists
  235.       for (index = 0; index < n; ++index)
  236.         if (event[index].number())
  237.           if (!(((MIDIEvent *) &event[index][0])->delta -= minDelta)) {
  238.             // Output the event to file and accumulate length
  239.             MIDIEvent &e = *((MIDIEvent *) &event[index][0]);
  240.             length += WriteVariableLength(out, duration)+3;
  241.             out.put(0x80+index);
  242.             out.put(e.freq);
  243.             out.put(0x7F);
  244.             duration = 0;
  245.             event[index].destroyAt(0);
  246.           }
  247.     }
  248.  
  249.     // See if all lists have been processed
  250.     allEmpty = TRUE;
  251.     for (index = 0; index < n; ++index)
  252.       allEmpty = allEmpty && (event[index].number() == 0);
  253.   }
  254.  
  255.   // Write a Track End event
  256.   out.write("\0\xFF\x2F\0", 4);
  257.  
  258.   // Update the track length information
  259.   length += 4;
  260.   out.seekp(18);
  261.   char revlength[sizeof(length)];
  262.   memcpy(revlength, &length, sizeof(length));
  263.   for (index = sizeof(length)-1; index >= 0; --index)
  264.     out.put(revlength[index]);
  265. }
  266.  
  267. // **********************************************
  268. // CreateMIDIFile is called from the outside to create a MIDI file.
  269. // It calls MIDIFirstPass with a temporary file and then
  270. // MIDISecondPass to translate the temporary to a final file.
  271.  
  272. void CreateMIDIFile(ostream &out)
  273. {
  274.   char path[MAXPATH];
  275.   strcpy(path, ".\\");
  276.   ofstream temp(creattemp(path, 0));
  277.   MIDIFirstPass(temp);
  278.   temp.close();
  279.   ifstream temp1(path, ios::binary | ios::in);
  280.   MIDISecondPass(temp1, out);
  281.   temp1.close();
  282.   unlink(path);
  283. }
  284.