home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 10 Tools / 10-Tools.zip / midifil2.zip / mfwrite / mfwrite.c next >
Text File  |  1994-07-28  |  32KB  |  751 lines

  1. /* ===========================================================================
  2.  * mfwrite.c
  3.  *
  4.  * Demonstrates how to use MIDIFILE.DLL to write out a dummy MIDI file. We have statically
  5.  * defined MIDI data in this program's global data which we'll write out to a MIDI file.
  6.  * =========================================================================
  7.  */
  8.  
  9. #include <stdio.h>
  10. #include <os2.h>
  11.  
  12. #include "midifile.h"
  13.  
  14. /* function definitions */
  15. VOID initfuncs(CHAR * fn);
  16.  
  17. /* Let's define some "event" structures for the internal use of this program. Each will hold one
  18.     "event". The first ULONG will be the event's time (referenced from 0). Then, there will be 4
  19.     more bytes. The first of those 4 will be the event's Status, and the remaining 3 will be
  20.     interpreted differently depending upon the Status. By having all of the events the same size
  21.     (ie, 8 bytes), with the Time and Status first, this makes it easy to sort, insert, and delete
  22.     events from a block of memory. The only drawback is that 8 bytes is a little cramped to store
  23.     lots of info, so you could go with a larger structure, but 8 bytes is perfect for the majority of
  24.     events.
  25.     For fixed length MIDI events (ie, Status is 0x80 to 0xEF, 0xF1, 0xF2, 0xF3, 0xF6, 0xF8,
  26.     0xFA, 0xFB, 0xFC, and 0xFE), the Status is simply the MIDI status, and the subsequent Data
  27.     bytes are any subsequent MIDI data bytes. For example, for a Note-On, Status would be 0x90
  28.     to 0x9F, Data1 would be the note number, Data2 would be the velocity, and Data3 wouldn't be
  29.     used.
  30.     For Meta-Events, the Status will be the meta Type (ie, instead of 0xFF). Note that all
  31.     Meta-Event types are less than 0x80, so it's easy to distinguish them from MIDI status
  32.     bytes. For fixed length Meta-Events (ie, meta types of 0x00, 0x2F, 0x51, 0x54, 0x58, and
  33.     0x59), the remaining 3 bytes will be interpreted as follows:
  34.            TYPE          DATA BYTES 1, 2, and 3
  35.         Sequence Number  = Not Used, SeqNumLSB, SeqNumMSB
  36.         End Of Track      = Not Used...
  37.          Tempo          = BPM, Not Used, Not Used
  38.           SMPTE         = There's too much data to store in only 3 bytes. We could define
  39.                   the bytes to be an index into a list of larger structures, but I'll
  40.                   just ignore SMPTE for simplicity.
  41.         Time Signature   =    Numerator, Denominator, MIDI clocks per metronome click.
  42.          Key Signature    = Key, Minor, Not Used
  43.     For variable length Meta-Events (ie, type of 0x01 to 0x0F, or 0x7F), since these have data
  44.     that can be any length, we'll set Data1 to be the length of the data (ie, we'll set a limit of
  45.     255 bytes), and Data2 and Data3 will together form a USHORT index into a larger array
  46.     (ie, we'll set a limit of 65,535 such Meta-Events).
  47.      For SYSEX events (0xF0 or 0xF7), these also can be any length, so we'll set Data1 to
  48.     be an index into a larger array (ie, set a limit of 256 such events), and Data2 and Data3
  49.     will form a USHORT length of the SYSEX (not counting the Status).
  50. */
  51.  
  52. typedef struct _EVENT  /* general form of an "event" */
  53. {
  54.     ULONG Time;
  55.     UCHAR Status;
  56.     UCHAR Data1, Data2, Data3;
  57. } EVENT;
  58.  
  59. typedef struct _XEVENT
  60. {
  61.     ULONG  Time;
  62.     UCHAR  Status;
  63.     UCHAR  Index;
  64.     USHORT Length;
  65. } XEVENT;
  66.  
  67. typedef struct _SEQEVENT
  68. {
  69.     ULONG  Time;
  70.     UCHAR  Status;
  71.     UCHAR  UnUsed1;
  72.     USHORT SeqNum;
  73. } SEQEVENT;
  74.  
  75. typedef struct _TXTEVENT
  76. {
  77.     ULONG  Time;
  78.     UCHAR  Status;
  79.     UCHAR  Length;
  80.     USHORT Index;
  81. } TXTEVENT;
  82.  
  83. typedef struct _TEMPOEVENT
  84. {
  85.     ULONG  Time;
  86.     UCHAR  Status;
  87.     UCHAR  BPM, Unused1, UnUsed2;
  88. } TEMPOEVENT;
  89.  
  90. typedef struct _TIMEEVENT
  91. {
  92.     ULONG Time;
  93.     UCHAR Status;
  94.     UCHAR Nom, Denom, Clocks;
  95. } TIMEEVENT;
  96.  
  97. typedef struct _KEYEVENT
  98. {
  99.     ULONG Time;
  100.     UCHAR Status;
  101.     UCHAR Key, Minor, UnUsed1;
  102. } KEYEVENT;
  103.  
  104.  
  105.  
  106.  
  107. /* Need a CALLBACK structure for the DLL */
  108. CALLBACK cb;
  109.  
  110.  
  111.  
  112. /* Need a MIDIFILE structure for the DLL */
  113. MIDIFILE mfs;
  114.  
  115.  
  116.  
  117. /* OK, here's an artificially created block of EVENTS. We mix it up with a variety of Status,
  118.     to show you how you might store such. This is for writing a Format 0. In your own app, you
  119.     may choose to store data in a different way.
  120. */
  121.  
  122. EVENT evts[20] = {
  123.      {0, 0x04, 5, 0x01, 0x00}, /* Instrument Name Meta-Event. Status = 0x04. Note that Data2
  124.                       and Data3 form a USHORT index. Because Intel CPU uses
  125.                       reverse byte order, the seq number here is really 0x0001 */
  126.      {0, 0x01, 11, 0x00, 0x00}, /* A Text Meta-Event. Status = 0x01. */
  127.      {0, 0x58, 5, 4, 24},      /* A Time Signature Meta-Event. Status = 0x58. 5/4 */
  128.      {0, 0x59, 1, 0, 0},      /* A Key Signature Meta-Event. Status = 0x59. G Major */
  129.      {0, 0x51, 100, 0, 0},      /* A Tempo Meta-Event. Status = 0x51. */
  130.      {96, 0x90, 64, 127, 0},     /* A Note-on (ie, fixed length MIDI message) */
  131.      {96, 0xC0, 1, 0xFF, 0},     /* A Program Change (ie, also a fixed length MIDI message) */
  132.      {96, 0xF0, 0, 0x02, 0x00}, /* SYSEX (ie, 0xF0 type) */
  133.      {96, 0x90, 71, 127, 0},     /* A Note-on (ie, fixed length MIDI message) */
  134.      {192, 0xFC, 0, 0, 0},     /* MIDI REALTIME "Stop" */
  135.      {192, 0x90, 68, 127, 0},    /* A Note-on (ie, fixed length MIDI message) */
  136.      {288, 0x90, 64, 0, 0},     /* A Note-off (ie, Note-on with 0 velocity) */
  137.      {288, 0x51, 120, 0, 0},     /* A Tempo Meta-Event. Status = 0x51. */
  138.      {288, 0x90, 68, 0, 0},     /* A Note-off */
  139.      {384, 0xF0, 1, 0x01, 0x00}, /* SYSEX (ie, 0xF0 type, but the start of a series of packets,
  140.                        so it doesn't end with 0xF7) */
  141.      {384, 0xF7, 2, 0x01, 0x00}, /* SYSEX (ie, 0xF7 type, the next packet). */
  142.      {384, 0xF7, 3, 0x02, 0x00}, /* SYSEX (ie, 0xF7 type, the last packet, so it ends with 0xF7). */
  143.      {480, 0x7F, 4, 0x02, 0x00}, /* Proprietary Meta-Event. */
  144.      {480, 0x90, 71, 0, 0},      /* A Note-off */
  145.      {768, 0x2F, 0, 0, 0},       /* An End Of Track Meta-Event. Status = 0x2F. */
  146. };
  147.  
  148.  
  149.  
  150. /* Here's 2 artificially created blocks of EVENTS. These 2 are for writing a Format 1. We simply
  151.     separate the events in the above track into 2 tracks. With a format 1, the first track is
  152.     considered the "tempo map", so we put appropriate events in that (ie, all tempo and time
  153.     signature events should go in only this one track).
  154. */
  155.  
  156. EVENT trk1[6] = {
  157.      {0, 0x58, 5, 4, 24},
  158.      {0, 0x59, 1, 0, 0},
  159.      {0, 0x51, 100, 0, 0},
  160.      {288, 0x51, 120, 0, 0},
  161.      {480, 0x7F, 4, 0x02, 0x00},
  162.      {768, 0x2F, 0, 0, 0},
  163. };
  164.  
  165. EVENT trk2[15] = {
  166.      {0, 0x04, 5, 0x01, 0x00},
  167.      {0, 0x01, 11, 0x00, 0x00},
  168.      {96, 0x90, 64, 127, 0},
  169.      {96, 0xC0, 1, 0xFF, 0},
  170.      {96, 0xF0, 0, 0x02, 0x00},
  171.      {96, 0x90, 71, 127, 0},
  172.      {192, 0xFC, 0, 0, 0},
  173.      {192, 0x90, 68, 127, 0},
  174.      {288, 0x90, 64, 0, 0},
  175.      {288, 0x90, 68, 0, 0},
  176.      {384, 0xF0, 1, 0x01, 0x00},
  177.      {384, 0xF7, 2, 0x01, 0x00},
  178.      {384, 0xF7, 3, 0x02, 0x00},
  179.      {480, 0x90, 71, 0, 0},
  180.      {768, 0x2F, 0, 0, 0},
  181. };
  182.  
  183.  
  184.  
  185. /* Pointers to our tracks. Assume Format 0. In your own app, you may choose to do something
  186.     different.
  187.  */
  188. EVENT * trk_ptrs[2] = { &evts[0], &trk2[0] };
  189.  
  190.  
  191.  
  192. /* An array of text for Meta-Text events. In your own app, you may choose to do something
  193.     different. */
  194. UCHAR text[3][20] = {
  195.      { "Here's text" },  /* For Text */
  196.      { "Piano" },       /* For Instrument Name */
  197.      { 0, 1, 2, 3 },     /* For Proprietary */
  198. };
  199.  
  200.  
  201.  
  202. /* An array of track names. In your own app, you may choose to do something different. */
  203. UCHAR names[2][20] = {
  204.      { "Dummy" },
  205.      { "Blort" },
  206. };
  207.  
  208.  
  209.  
  210. /* An array of SYSEX data. In your own app, you may choose to do something
  211.     different. */
  212. UCHAR sysex[4][2] = {
  213.      { 0x01, 0xF7 },   /* an 0xF0 type always ends with 0xF7 unless it has an 0xF7 type to follow */
  214.      { 0x02 },
  215.      { 0x03 },        /* an 0xF7 type doesn't always end with 0xF7 unless it's the last packet */
  216.      { 0x04, 0xF7 },
  217. };
  218.  
  219.  
  220.  
  221. /* To point to the next event to write out */
  222. EVENT * TrkPtr;
  223.  
  224.  
  225.  
  226. /* To look up the address of sysex data */
  227. UCHAR ex_index;
  228.  
  229.  
  230.  
  231. /* ********************************** main() ***********************************
  232.  * Program entry point. Calls the MIDIFILE.DLL function to write out a MIDI file. The callbacks do
  233.  * all of the real work of feeding data to the DLL to write out.
  234.  *************************************************************************** */
  235.  
  236. main(int argc, char *argv[], char *envp[])
  237. {
  238.     LONG result;
  239.     UCHAR buf[60];
  240.  
  241.     /* If no filename arg supplied by user, exit with usage info */
  242.     if ( argc < 2 )
  243.     {
  244.      printf("This program writes out a 'dummy' MIDI (sequencer) file.\r\n");
  245.      printf("It requires MIDIFILE.DLL to run.\r\n");
  246.      printf("Syntax: MFWRITE.EXE filename [0, 1, or 2 (for Format)]\r\n");
  247.      exit(1);
  248.     }
  249.  
  250.     printf("Writing %s...\r\n", argv[1]);
  251.  
  252.     /* Initialize the pointers to our callback functions (ie, for the MIDIFILE.DLL to call) */
  253.     initfuncs(argv[1]);
  254.  
  255.     /* NOTE: We can initialize the MThd before the call to MidiWriteFile(), or we can do it in
  256.     our StartMThd callback. We'll do it now since we don't need to do anything else right
  257.     before the MThd is written out (ie, and so don't need a StartMThd callback) */
  258.     mfs.Format = 0;
  259.     mfs.NumTracks = 1;
  260.     if (argc>2)
  261.     {
  262.      /* User wants us to write out a particular Format. If not 0, then write 2 MTrk chunks */
  263.      if ( (mfs.Format = atoi(argv[2])) )
  264.      {
  265.           mfs.NumTracks = 2;
  266.           trk_ptrs[0] = &trk1[0];
  267.      }
  268.     }
  269.     mfs.Division = 96;     /* Arbitrarily chose 96 PPQN for my time-stamps */
  270.  
  271.     /* Make it easier for me to specify Tempo and Time Signature events */
  272.     mfs.Flags = MIDIBPM|MIDIDENOM;
  273.  
  274.     /* Tell MIDIFILE.DLL to write out the file, calling my callback functions */
  275.     result = MidiWriteFile(&mfs);
  276.  
  277.     /* Print out error message */
  278.     MidiGetErr(&mfs, result, &buf[0]);
  279.     printf(&buf[0]);
  280.  
  281.     exit(0);
  282. }
  283.  
  284.  
  285.  
  286. /* ******************************** startMTrk() *****************************
  287.  * This is called by MIDIFILE.DLL before it writes an MTrk chunk's header (ie, before it starts
  288.  * writing out an MTrk). The MIDIFILE's TrackNum field is maintained by the DLL, and reflects the
  289.  * MTrk number (ie, the first MTrk is 0, the second MTrk is 1, etc). We could use this to help us
  290.  * "look up" data and structures associated with that track number. (It's best not to alter this
  291.  * MIDIFILE field). At this point, we should NOT do any MidiReadBytes() because the header
  292.  * hasn't been written yet. What we perhaps can do is write out some chunk of our own creation
  293.  * before the MTrk gets written. We could do this with MidiWriteHeader(), MidiWriteBytes(), and
  294.  * then MidiCloseChunk(). But, that's probably not a good idea. There are many programs that have
  295.  * a very inflexible way of reading MIDI files. Such software might barf on a chunk that isn't an
  296.  * MTrk, even though it's perfectly valid to insert such chunks. The DLL correctly handles such
  297.  * situations. In fact, the CALLBACK allows you to specify a function for chunks that aren't MTrks.
  298.  *    This should return 0 to let the DLL write the MTrk header, after which the DLL starts calling
  299.  * our StandardEvt() callback (below) for each event that we wish to write to that MTrk chunk (up
  300.  * to an End Of Track Meta-Event). Alternately, if we already had all of the MTrk data in a buffer
  301.  * (minus the MTrk header), formatted and ready to be written out as an MTrk chunk, we could
  302.  * place the size and a pointer to that buffer in the MIDIFILE's EventSize and Time fields
  303.  * respectively, and return 0. In this case, the DLL will write out the MTrk chunk completely and
  304.  * then call our StandardEvt() callback once.
  305.  *    If this function returns non-zero, that will cause the write to abort and we'll return to
  306.  * main() after the call to MidiWriteFile(). The non-zero value that we pass back here will be
  307.  * returned from MidiWriteFile(). The only exception is a -1 which causes this MTrk to be
  308.  * skipped, but not the write to be aborted (ie, the DLL will move on to the next MTrk number).
  309.  ************************************************************************** */
  310.  
  311. LONG EXPENTRY startMTrk(MIDIFILE * mf)
  312. {
  313.     /* Display which MTrk chunk we're writing */
  314.     printf("Writing track #%d...\r\n", mf->TrackNum);
  315.  
  316.     /* Initialize a global ptr to the start of the EVENTs that we're going to write in this MTrk */
  317.     TrkPtr = trk_ptrs[mf->TrackNum];
  318.  
  319.     /* Here, we could write out some non-standard chunk of our own creation. It will end up
  320.     in the MIDI file ahead of the MTrk that we're about to write. Nah! */
  321.  
  322.     /* Return 0, but we didn't place our own buffer ptr into MIDIFILE's Time field, so the DLL
  323.     will call standardEvt() for each event. */
  324.     return(0);
  325. }
  326.  
  327.  
  328.  
  329. /* ****************************** store_seq() *******************************
  330.  * Called by standardEvt() to format the MIDIFILE for writing out a SEQUENCE NUMBER (and
  331.  * SEQUENCE NAME) Meta-Events
  332.  ************************************************************************ */
  333.  
  334. VOID store_seq(SEQEVENT * trk, METASEQ * mf)
  335. {
  336.     mf->NamePtr = &names[mf->TrackNum][0];
  337.     mf->SeqNum = trk->SeqNum;
  338. }
  339.  
  340.  
  341.  
  342. /* ****************************** store_tempo() *******************************
  343.  * Called by standardEvt() to format the MIDIFILE for writing out a TEMPO Meta-Event
  344.  ************************************************************************ */
  345.  
  346. VOID store_tempo(TEMPOEVENT * trk, METATEMPO * mf)
  347. {
  348.  
  349.     /* NOTE: We set MIDIBPM Flag so the DLL will calculate micros from this BPM.
  350.     Without MIDIBPM, we'd have to set mf->Tempo to the micros, and ignore
  351.     mf->TempoBPM. */
  352.     mf->TempoBPM = trk->BPM;
  353. }
  354.  
  355.  
  356.  
  357. /* ****************************** store_time() *******************************
  358.  * Called by standardEvt() to format the MIDIFILE for writing out a TIME SIGNATURE Meta-Event
  359.  ************************************************************************ */
  360.  
  361. VOID store_time(TIMEEVENT * trk, METATIME * mf)
  362. {
  363.     mf->Nom = trk->Nom;
  364.     mf->Denom = trk->Denom;
  365.     mf->Clocks = trk->Clocks;
  366.     mf->_32nds = 8;   /* hard-wire this to 8 for my app. You might do it differently */
  367. }
  368.  
  369.  
  370.  
  371. /* ****************************** store_key() *******************************
  372.  * Called by standardEvt() to format the MIDIFILE for writing out a KEY SIGNATURE Meta-Event
  373.  ************************************************************************ */
  374.  
  375. VOID store_key(KEYEVENT * trk, METAKEY * mf)
  376. {
  377.     mf->Key = trk->Key;
  378.     mf->Minor = trk->Minor;
  379. }
  380.  
  381.  
  382.  
  383. /* ****************************** store_meta() *******************************
  384.  * Called by standardEvt() to format the MIDIFILE for writing out one of the variable length
  385.  * Meta-Events (ie, types 0x01 to 0x0F, or 0x7F).
  386.  ************************************************************************ */
  387.  
  388. VOID store_meta(TXTEVENT * trk, METATXT * mf)
  389. {
  390.     /* If a variable length Meta-Event (ie, type is 0x01 to 0x0F, or 0x7F), then we set
  391.     the EventSize to the number of bytes that we expect to output. We also set the
  392.     Ptr (ie, ULONG at Data[2]) either to a pointer to a buffer containing the data bytes
  393.     to write, or 0. If we set a pointer, then when we return, the DLL writes the data,
  394.     and then calls StandardEvt for the next event. If we set a 0 instead, when we return,
  395.     the DLL will call our MetaText callback next, which is expected to MidiWriteBytes() that
  396.     data. Here, we'll supply the pointer and let the DLL do all of the work of writing out
  397.     the data. */
  398.  
  399.     /* Look up where I've stored the data for this meta-event (ie, in my text[] array), and
  400.        store this pointer in the MIDIFILE. */
  401.     mf->Ptr = &text[trk->Index][0];
  402.  
  403.     /* Set the event length. NOTE: If we set this to 0, then that tells the DLL that
  404.     we're passing a null-terminated string, and the DLL uses strlen() to get the length.
  405.     We could have done that here to really simplify our the structure of our TXTEVENT
  406.     since all of our strings happen to be null-terminated. But, if you ever need to write
  407.     out data strings with imbedded NULLs, you'll need to something like... */
  408.     mf->EventSize = (ULONG)trk->Length;
  409. }
  410.  
  411.  
  412.  
  413. /* ****************************** store_sysex() *******************************
  414.  * Called by standardEvt() to format the MIDIFILE for writing out a SYSEX Meta-Events
  415.  ************************************************************************ */
  416.  
  417. VOID store_sysex(XEVENT * trk, METATXT * mf)
  418. {
  419.  
  420.     /* For SYSEX, just store the status, and the length of the message. We also set the
  421.     the ULONG at Data[2], just like with variable length Meta-Events. (See comment
  422.     above). Upon return, the DLL will write the supplied buffer, or if none supplied,
  423.     call our SysexEvt callback which will MidiWriteBytes() the rest of the message.
  424.     Here, let's do the opposite of what we did in store_meta(), just for the sake
  425.     of illustration. The easier way is to let the DLL write out the SYSEX data, if that
  426.     data happens to be in one buffer. */
  427.  
  428.     /* Set the length */
  429.     mf->EventSize = (ULONG)trk->Length;
  430.  
  431.     /* Store index in a global for our sysexEvt callback. Alternately, we could put it
  432.     into the UnUsed2 field of the METATXT (since it's a UCHAR), and retrieve the
  433.     value in sysexEvt() */
  434.     ex_index = trk->Index;
  435.  
  436.     /* Set Ptr (ie, ULONG at Data[2]) to 0. This ensures that the DLL calls sysexEvt instead
  437.     of writing out a buffer for us */
  438.     mf->Ptr = 0;
  439. }
  440.  
  441.  
  442.  
  443. /* ****************************** standardEvt() *******************************
  444.  * This is called by MIDIFILE.DLL for each event to be written to an MTrk chunk (or it's only
  445.  * called once if we supplied a preformatted buffer in our startMTrk callback). This does the real
  446.  * work of feeding each event to the DLL to write out within an MTrk.
  447.  *     The MIDIFILE's TrackNum field is maintained by the DLL, and reflects the MTrk number.
  448.  * We could use this to help us "look up" data associated with that track number.
  449.  *     We need to place the event's time (referenced from 0 as opposed to the previous event
  450.  * unless we set the MIDIDELTA Flag) in the MIDIFILE's Time field. When we return from here,
  451.  * the DLL will write it out as a variable length quantity.
  452.  *    Next, we need to place the Status in the MIDIFILE's Status field. For fixed length MIDI
  453.  * events (ie, Status < 0xEF), then this is simply the MIDI Status byte. No running status. We
  454.  * must provide the proper status if it's a fixed length MIDI message. Then, we place the 1 or 2
  455.  * subsequent MIDI data bytes for this event in the MIDIFILE's Data[0] and Data[1]. Upon return,
  456.  * the DLL will completely write out the event, and then call this function again for the next
  457.  * event.
  458.  *    For fixed length Meta-Events (ie, meta types of 0x00, 0x2F, 0x51, 0x54, 0x58, and
  459.  * 0x59), the Status must be 0xFF, and Data[0] must be the meta type. The characters starting at
  460.  * Data[1] (ie, length byte) must be the rest of the message. Upon return, the DLL will completely
  461.  * write out the event, and then call this function again for the next event. Note that the DLL
  462.  * automatically set the length for this fixed length Meta-Events, so you need not bother setting
  463.  * Data[1]. For a Tempo Meta-Event, you have an option. Although Status=0xFF and Data[0]=0x51
  464.  * always, instead of placing the micros per quarter as a ULONG starting at Data[2] (ie, Data[2],
  465.  * Data[3], Data[4], and Data[5]), you can alternately place the tempo BPM in Data[6] and set the
  466.  * MIDIFILE's Flags MIDIBPM bit. Note that if you recast the MIDIFILE as a METATEMPO, it
  467.  * makes it easier to store the micros as a ULONG in the Tempo field, and BPM in the Tempo
  468.  * field. The DLL will format the micros bytes for you. NOTE: When you return with an End Of
  469.  * Track event (ie, Status=0xFF and Data[0]=0x2f), then the DLL will write out this event and
  470.  * close the MTrk chunk. It then moves on to the next MTrk if there is another, calling startMTrk
  471.  * again with the next TrackNum.
  472.  *    For SYSEX events (ie, Status = 0xF0 and 0xF7), you set the status byte and then set
  473.  * EventSize to how many bytes are in the message (not counting the status). You then can set
  474.  * the ULONG at Data[2] (ie, Data[2], Data[3], Data[4], and Data[5]) to point to a buffer
  475.  * containing the remaining bytes (after 0xF0 or 0xF7) to output. Alternately, you can set this
  476.  * ULONG to 0. The DLL will write out the status and length as a variable length quantity. Then,
  477.  * if you supplied the buffer pointer, it will write out the data. If you set the pointer to 0, then
  478.  * the DLL will call your SysexEvt callback which is expected to MidiWriteBytes() the actual data
  479.  * for this SYSEX message. Note that the SysexEvt could make numerous calls to MidiWriteBytes,
  480.  * before it returns. The number of bytes written must be the same as the previously specified
  481.  * EventSize.
  482.  *    For variable length Meta-Events (ie, types of 0x01 through 0x0F, and 0x7F), you only set
  483.  * the Status=0xFF, Data[0] = the meta type, and then set EventSize to how many bytes are in
  484.  * the message (not counting the status and type). You can then set the ULONG at Data[2]. (See
  485.  * the notes on SYSEX above). The DLL will write out the status, type, and length as a variable
  486.  * length quantity, and then either the supplied buffer, or if no supplied buffer, the DLL will call
  487.  * your MetaText callback which is expected to MidiWriteBytes() the actual data for this
  488.  * Meta-Event.
  489.  *    For MIDI REALTIME and SYSTEM COMMON messages (ie, 0xF1, 0xF2, 0xF3, 0xF6, 0xF8,
  490.  * 0xFA, 0xFB, 0xFC, and 0xFE), simply set the MIDIFILE's Status to the appropriate MIDI Status,
  491.  * and then store any subsequent data bytes (ie, 1 or 2) in Data[0] and Data[1]. Upon return, the
  492.  * DLL will completely write out the event, and then call this function again for the next event.
  493.  *    If this function returns non-zero, that will cause the write to abort and we'll return to
  494.  * main() after the call to MidiWriteFile(). The non-zero value that we pass back here will be
  495.  * returned from MidiWriteFile().
  496.  ************************************************************************** */
  497.  
  498. LONG EXPENTRY standardEvt(MIDIFILE * mf)
  499. {
  500.     register UCHAR chr;
  501.     register USHORT val;
  502.  
  503.     /* Store the Time of this event in MIDIFILE's Time field */
  504.     mf->Time = TrkPtr->Time;
  505.  
  506.     /* Get the Status for this event */
  507.     chr = mf->Status = TrkPtr->Status;
  508.  
  509.     /* Is this a Meta-Event? (ie, we use meta's Type for the status, and these values are always
  510.     less than 0x80) */
  511.     if ( !(chr & 0x80) )
  512.     {
  513.      /* Set Status to 0xFF */
  514.      mf->Status = 0xFF;
  515.      /* Set Data[0] = Type */
  516.      mf->Data[0] = chr;
  517.      switch (chr)
  518.      {
  519.           /* NOTE: MIDIFILE.DLL sets Data[1] to the proper length for the fixed length
  520.           meta types (ie, 0x00, 0x2F, 0x51, 0x54, 0x58, and 0x59). */
  521.  
  522.           /* ------- Sequence # ------- */
  523.           /* NOTE: If we use a MetaSeqNum callback, we wouldn't write out a Meta-Event
  524.          here, and so this case wouldn't be needed. */
  525.           case 0x00:
  526.            /* Note the recasting of the EVENT and the MIDIFILE structures to the versions
  527.                appropriate for a SEQUENCE NUMBER Meta-Event. */
  528.            store_seq( (SEQEVENT *)TrkPtr, (METASEQ *)mf );
  529.            break;
  530.  
  531.           /* ------- Set Tempo -------- */
  532.           case 0x51:
  533.            store_tempo( (TEMPOEVENT *)TrkPtr, (METATEMPO *)mf );
  534.            break;
  535.  
  536.           /* --------- SMPTE --------- */
  537.           case 0x54:
  538.            /* Right now, let's ignore SMPTE events. This was too much of a hassle
  539.                since there are too many data bytes to fit into the 8 bytes that I use to
  540.                express events internally in this program. */
  541.            break;
  542.  
  543.           /* ------- End of Track ------ */
  544.           case 0x2F:
  545.            printf("Closing track #%ld...\r\n", mf->TrackNum);
  546.            break;
  547.  
  548.           /* ------ Time Signature ----- */
  549.           case 0x58:
  550.            store_time( (TIMEEVENT *)TrkPtr, (METATIME *)mf );
  551.            break;
  552.  
  553.           /* ------ Key Signature ------ */
  554.           case 0x59:
  555.            store_key( (KEYEVENT *)TrkPtr, (METAKEY *)mf );
  556.            break;
  557.  
  558.           /* Must be a variable length Meta-Event (ie, type is 0x01 to 0x0F, or 0x7F) */
  559.           default:
  560.            store_meta( (TXTEVENT *)TrkPtr, (METATXT *)mf );
  561.      }
  562.     }
  563.  
  564.     /* Must be a real MIDI event (as opposed to a MetaEvent) */
  565.     else switch ( chr )
  566.     {
  567.      /* SYSEX (0xF0) or SYSEX CONTINUATION (0xF7) */
  568.      case 0xF0:
  569.      case 0xF7:
  570.           store_sysex( (XEVENT *)TrkPtr, (METATXT *)mf );
  571.           break;
  572.  
  573.      /* For other MIDI messages, they're all fixed length, and will fit into the MIDIFILE
  574.          structure, so we copy 2 more data bytes to the MIDIFILE (whether those data bytes
  575.          are valid or not -- the DLL takes care of writing the message out properly). */
  576.      default:
  577.           mf->Data[0] = TrkPtr->Data1;
  578.           mf->Data[1] = TrkPtr->Data2;
  579.     }
  580.  
  581.     /* Advance the ptr to the next event (ie, for the next call to this function) */
  582.     TrkPtr++;
  583.  
  584.     return(0);
  585. }
  586.  
  587.  
  588.  
  589. /* ******************************** sysexEvt() ********************************
  590.  * This is called by MIDIFILE.DLL if it needs me to write out the data bytes for a SYSEX
  591.  * message being written to an MTrk chunk. If the DLL called me, then standardEvt must have
  592.  * initiated writing a SYSEX event to an MTrk, but didn't supply a pointer to a buffer filled with
  593.  * data. Now, I need to write those data bytes here. NOTE: EventSize still contains the length of
  594.  * data to write.
  595.  ************************************************************************** */
  596.  
  597. LONG EXPENTRY sysexEvt(MIDIFILE * mf)
  598. {
  599.     register ULONG i;
  600.     register UCHAR * ptr;
  601.     register LONG result;
  602.  
  603.     /* Look up where I put the data for the SYSEX event that we're writing right now */
  604.     ptr = &sysex[ex_index][0];
  605.  
  606.     /* Here's an example of writing the data one byte at a time. This is slow, but it shows that
  607.     you can make multiple calls to MidiWriteBytes(). NOTE: commented out; just for
  608.     illustration. Also note that MidiWriteBytes() decrements EventSize, so we could have
  609.     done: while( mf->EventSize )  */
  610. /*    for (i = mf->EventSize; i; i--)
  611.        {
  612.           if ( (result = MidiWriteBytes(mf, ptr, 1)) ) return(result);
  613.           ptr++;
  614.        }
  615.        return(0);
  616. */
  617.  
  618.     /* Since we happen to have all of the bytes in one buffer, the fast way is to make one call
  619.     to MidiWriteBytes() for that block. */
  620.     return( MidiWriteBytes(mf, ptr, mf->EventSize) );
  621. }
  622.  
  623.  
  624.  
  625. /* ******************************* metaseq() *********************************
  626.  * This is called by MIDIFILE.DLL after it has called startMTrk (ie, the MTrk header has been
  627.  * written) but before standardEvt gets called. In other words, this gives us a chance to write
  628.  * the first event in an MTrk. Usually, this is the Sequence Number event, which MUST be first.
  629.  * If we had a Sequence Number event at the start of this example's internal data, we wouldn't
  630.  * need this, since standardEvt would write that out. But, this gives us an opportunity to
  631.  * illustrate how to write such an event now. We simply format the METASEQ for the DLL to write
  632.  * a Seq Number event followed by a Track Name event.
  633.  *     If we wished to write other events at the head of the MTrk, but which aren't actually
  634.  * events in our example track, we would need to make calls to MidiWriteEvt(). We would have to
  635.  * do this for the sequence number since that event must come first in an MTrk. Then, we would
  636.  * do MidiWriteEvt for all of our other desired "contrived events", except for the last one. We
  637.  * must always return at least one event in the MIDIFILE structure.
  638.  *     NOTE: MIDIFILE's Time field is 0 upon entry. Do not write out any events with a non-zero
  639.  * Time here. Do that in standardEvt().
  640.  ************************************************************************** */
  641.  
  642. LONG EXPENTRY metaseq(METASEQ * mf)
  643. {
  644.     LONG val;
  645.     MIDIFILE * mf2;
  646.  
  647.     mf->Type = 0xFF;
  648.     mf->WriteType = 0x00;
  649.  
  650.     /* Arbitrarily set Seq # to 10 + TrackNum */
  651.     mf->SeqNum = 10+mf->TrackNum;
  652.  
  653.     /* Set NamePtr to point to the null-terminated Track Name to write as a Meta-Event.
  654.     Alternately, we could set this to 0 if we don't want a Name event to be written (or if
  655.     we already have an explicit Name event within our track data, to be written out in standardEvt) */
  656.     mf->NamePtr = &names[mf->TrackNum][0];
  657.  
  658.     /* This code shows how we might write additional events. Commented out. */
  659.  
  660.     /* Write the sequence num and name events */
  661. /*    if ( (val = MidiWriteEvt((MIDIFILE *)mf)) ) return(val); */
  662.  
  663.     /* Fool compiler into regarding the METASEQ as a MIDIFILE, which it really is */
  664. /*    mf2 = (MIDIFILE *)mf; */
  665.  
  666.     /* We could write out more events here */
  667.  
  668.     /* Let DLL write the last of these events at Time of 0. In this case, a text event */
  669. /*    mf->Status = 0xFF; */
  670. /*    mf2->Data[0] = 0x01; */
  671. /*    mf2->EventSize = 0; */
  672. /*    set_ptr((ULONG *)&mf2->Data[2], "More text"); */
  673.  
  674.     /* Success */
  675.     return(0);
  676. }
  677.  
  678.  
  679.  
  680. /* ******************************* make_extra() *********************************
  681.  * This is called by MIDIFILE.DLL after all of the MTrk chunks have been successfully written out.
  682.  * We could use this to write a proprietary chunk not defined by the MIDI File spec (although a
  683.  * better way to store proprietary info is within a MTrk with the Proprietary Meta-Event since
  684.  * badly written MIDI file readers might barf on an undefined chunk -- MIDIFILE.DLL doesn't do
  685.  * that). Just for the sake of illustration, we'll write out a chunk with the ID of "XTRA" and
  686.  * it will contain 10 bytes, numbers 0 to 9.
  687.  ************************************************************************** */
  688.  
  689. LONG EXPENTRY make_extra(MIDIFILE * mf)
  690. {
  691.     LONG val;
  692.     USHORT i;
  693.     CHAR chr;
  694.  
  695.     /* Set the ID. Note that the ULONG contains the 4 ascii chars of 'X', 'T', 'R', and 'A',
  696.     except that the order has been reversed to 'A', 'R', 'T', and 'X'. This reversal is due to
  697.     the backwards byte order of the Intel CPU when storing longs. */
  698.     mf->ID = 0x41525458;
  699.  
  700.     /* Set the ChunkSize to 0 initially. We write an empty header, then after we write out all
  701.     of the bytes, MidiCloseChunk will automatically set the proper ChunkSize. */
  702.     mf->ChunkSize=0;
  703.  
  704.     /* Write the header */
  705.     if ( (val = MidiWriteHeader(mf)) ) return(val);
  706.  
  707.     /* Write the data bytes */
  708.     for (i=0; i<10; i++)
  709.     {
  710.      chr = i+'0';
  711.      if ( (val = MidiWriteBytes(mf, &chr, 1)) ) return(val);
  712.     }
  713.  
  714.     /* Close the chunk */
  715.     MidiCloseChunk(mf);
  716.  
  717.     /* Of course, we could write out more chunks here */
  718.  
  719.     /* Success */
  720.     return(0);
  721. }
  722.  
  723.  
  724.  
  725. /* ******************************** initfuncs() ********************************
  726.  * This initializes the CALLBACK structure containing pointers to functions. These are used by
  727.  * MIDIFILE.DLL to call our callback routines while reading the MIDI file. It also initializes a few
  728.  * fields of MIDIFILE structure.
  729.  ************************************************************************** */
  730.  
  731. VOID initfuncs(CHAR * fn)
  732. {
  733.     /* Place CALLBACK into MIDIFILE */
  734.     mfs.Callbacks = &cb;
  735.  
  736.     /* Let the DLL Open, Read, Seek, and Close the MIDI file */
  737.     cb.OpenMidi = 0;
  738.     cb.ReadWriteMidi = 0;
  739.     cb.SeekMidi = 0;
  740.     cb.CloseMidi = 0;
  741.     mfs.Handle = (ULONG)fn;
  742.  
  743.     cb.UnknownChunk = make_extra;
  744.     cb.StartMThd = 0;     /* Don't need this since I initialized the MThd before MidiWriteFile() */
  745.     cb.StartMTrk = startMTrk;
  746.     cb.StandardEvt = standardEvt;
  747.     cb.SysexEvt = sysexEvt;
  748.     cb.MetaSeqNum = metaseq;
  749.     cb.MetaText = 0;    /* Not needed since we supply the data buffer to the DLL in standardEvt() */
  750. }
  751.