home *** CD-ROM | disk | FTP | other *** search
/ Sound Sensations! / sound_sensations.iso / miscprog / ad-prog / midiplay.c < prev    next >
C/C++ Source or Header  |  1990-04-19  |  17KB  |  594 lines

  1. /*
  2.      Copyright Ad Lib Inc., 1990
  3.  
  4.      This file is part of the Ad Lib Programmer's Manual product and is
  5.      subject to copyright laws.  As such, you may make copies of this file
  6.      only for the purpose of having backup copies.  This file may not be
  7.      redistributed in any form whatsoever.
  8.  
  9.      If you find yourself in possession of this file without having received
  10.      it directly from Ad Lib Inc., then you are in violation of copyright
  11.      laws, which is a form of theft.
  12. */
  13.  
  14. /*
  15.     MIDIPLAY.C
  16.  
  17.     8-May-89, Dale Glowinski, Ad Lib Inc.
  18.  
  19.     Driver to play a Standard MIDI File (1.0).
  20.  
  21.     The driver uses the PC's timer-0 for timing. (see TIMER.ASM )
  22.  
  23.     This file was compiled with Microsoft C5.1 as follows:
  24.        cl -AL -Zi -J -Ot -Zp -Gs -c -Foobject\\
  25.     The companion file, TIMER.ASM, was assembled with MASM 5.1:
  26.        masm -Zi timer.asm, object\\timer.obj;
  27.  
  28.     Interface instructions:
  29.        - Call Midi_Init() to install the interrupt vector.
  30.        - Call Midi_Play (*data) to play the file.
  31.          *data is a pointer to the already read file
  32.        - Call Midi_End() to remove the interrupt vector.
  33.  
  34.        - You do not need to call Midi_Init and Midi_End every time you
  35.          play something.  You can call them once at the beginning and
  36.          end of your program.
  37.        - The timer-0 is adjusted according to the tempo to adjust the clock
  38.          interrupt frequency.  The new interrupt routine takes care of
  39.          chaining to the old interrupt routine at the appropriate moment.
  40.          Timer-0 is set to its usual state when finished playing.
  41.        - See the file midimain.c for an example.
  42. */
  43.  
  44. #include  "cflags.h"
  45.  
  46. #include  <stdio.h>
  47. #include  <stdlib.h>
  48. #include  <fcntl.h>
  49. #include  <string.h>
  50.  
  51. /* Some MIDI codes. */
  52. #define   END_OF_TRACK   0x2f
  53. #define   TEMPO          0x51
  54. #define   NR_CHANS       16
  55. #define   SYSEX_F0       0xf0
  56. #define   SYSEX_F7       0xf7
  57. #define   META           0xff
  58. #define   SEQ_SPECIFIC   0x7f
  59.  
  60. static int      tracks = 0;            /* number of tracks */
  61. static int      tickQnote;             /* ticks per quarter note */
  62. static UCHAR    **musPtrPtr;           /* ptr into trkPtrs */
  63. static UCHAR    *status;               /* ptr to running status of current track */
  64. static unsigned clock_rate = 0;        /* original clock interrupt rate */
  65.  
  66. #define TRACKS 16
  67. static UCHAR *trkPtrs [TRACKS];        /* ptrs to each data track */
  68. static UCHAR trkStats [TRACKS];        /* running status for each track */
  69. static long  abs_time [TRACKS];        /* time of next event for each track */
  70.  
  71. /* This is to make the program easier to read. */
  72. #define  musPtr     (*musPtrPtr)
  73.  
  74. /* Keeps track of last change of volume to avoid unnecessary calls to
  75.    change of volume routine. */
  76. int current_vol [MAX_VOICES];
  77. int volume_flag = 1;
  78.  
  79. /* Flags */
  80. char    musRunning;                    /* != 0 if music is playing */
  81. static char    end_of_data;            /* != 0 if end of data */
  82. static char    clock_in = 0;           /* != 0 if installed */
  83.  
  84. /* Prototypes */
  85. static SetUp_Data (UCHAR *);
  86. static Start_Melo ();
  87.  
  88. /*-------------------------------------------------------------------------
  89.     Install the clock interrupt routine.
  90. */
  91. Midi_Init()
  92. {
  93.     if (clock_in) return;
  94.     Clk_install();
  95.     clock_in = 1;
  96. }
  97.  
  98.  
  99. /*-------------------------------------------------------------------------
  100.    Main routine for playing a MIDI file.  It receives an array of function
  101.    pointers which are used to call the routines which process the MIDI
  102.    events.
  103.    Returns 0 if interrupt routine not installed, else returns 1.
  104. */
  105. Midi_Play (dataPtr)
  106.    UCHAR *dataPtr;
  107. {
  108.    if (!clock_in) return (0);
  109.    SetUp_Data (dataPtr);
  110.    Start_Melo ();
  111.    return (1);
  112. }
  113.  
  114.  
  115. /*-------------------------------------------------------------------------
  116.     Uninstall the clock driver ...
  117. */
  118. Midi_End ()
  119. {
  120.    if (clock_in) Clk_uninstall();
  121.    clock_in = 0;
  122. }
  123.  
  124.  
  125. /*-------------------------------------------------------------------------
  126.    Get word value from data.  Value is stored MSB first. */
  127. static unsigned Get_Word (ptr)
  128.    UCHAR *ptr;
  129. {
  130.    unsigned n;
  131.    n = *ptr++;
  132.    n = (n << 8) + *ptr++;
  133.    return (n);
  134. }
  135.  
  136.  
  137. /* Get long value from data.  Value is stored MSB to LSB. */
  138. static long Get_Long (ptr)
  139.    UCHAR *ptr;
  140. {
  141.    long l = 0L;
  142.    int n;
  143.    for (n=0; n < 4; n++)
  144.       l = (l << 8) + *ptr++;
  145.    return (l);
  146. }
  147.  
  148.  
  149. /*-------------------------------------------------------------------------
  150.    Set up trkPtrs, which is an array of pointers, to point to the track
  151.    chunks. Does not modify musPtr. */
  152. static SetUp_Tracks (trcks, chunk)
  153.    int trcks;
  154.    UCHAR *chunk;
  155. {
  156.    int n;
  157.    long length;
  158.    UCHAR **tPtr = trkPtrs;
  159.  
  160.    for (n=0; n < trcks; n++) {
  161.       length = Get_Long (chunk + 4);
  162.       tPtr [n] = chunk + 8;
  163.       chunk += (length + 8L);
  164.    }
  165. }
  166.  
  167.  
  168. /*-------------------------------------------------------------------------
  169.    Reads a variable length value from the MIDI file data and advances the
  170.    data pointer.  */
  171.  
  172. static long Get_Length ()
  173. {
  174.    long value;
  175.    UCHAR c, *data;
  176.  
  177.    data = musPtr;
  178.    if ((value = *data++) & 0x80) {
  179.       value &= 0x7f;
  180.       do {
  181.          value = (value << 7) + ((c = *data++) & 0x7f);
  182.       } while (c & 0x80);
  183.    }
  184.    musPtr = data;
  185.    return (value);
  186. }
  187.  
  188.  
  189. /*-------------------------------------------------------------------------
  190.   Set up all of the data structures used in playing a MIDI file. */
  191. static SetUp_Data (dataPtr)
  192.    UCHAR *dataPtr;
  193. {
  194.    long length;
  195.    int i, j;
  196.  
  197.    /* Read file header */
  198.    length = Get_Long (dataPtr + 4);        /* header length */
  199.    tracks = Get_Word (dataPtr + 10);       /* number of tracks */
  200.    tickQnote = Get_Word (dataPtr + 12);    /* ticks per quarter note */
  201.  
  202.    /* Set musPtr to point to start of first chunk */
  203.    dataPtr += (length + 8L);
  204.  
  205.    /* Set up the array trkPtrs */
  206.    SetUp_Tracks (tracks, dataPtr);
  207.  
  208.    /* Initialize arrays */
  209.    for (i=0; i < tracks; i++) {
  210.       musPtrPtr = &trkPtrs [i];         /* set global data ptr */
  211.       abs_time [i] = Get_Length ();     /* set start time for track */
  212.       trkStats [i] = *musPtr;           /* current running status for track */
  213.    }
  214. }
  215.  
  216. /*-------------------------------------------------------------------------
  217.     Start playing a melody. Set some global pointers and the tempo.  Start
  218.     the clock driver with the first delay (>= 1) */
  219. static Start_Melo ()
  220. {
  221.     extern void StartTimeOut (int);
  222.  
  223.     musPtrPtr = trkPtrs;
  224.     status = trkStats;
  225.  
  226.     end_of_data = 0;
  227.     musRunning = 1;
  228.  
  229.     Set_Tempo (480, 500000L);
  230.  
  231. #ifdef INT_METHOD
  232.     StartTimeOut (1);
  233. #else
  234.     StartTimeOut (Do_Event ());
  235. #endif
  236. }
  237.  
  238.  
  239. /*-------------------------------------------------------------------------
  240.    Stop playing the melody. Reset the clock frequency to normal (18.2 Hz). */
  241. Stop_Melo()
  242. {
  243.     musRunning = 0;
  244.     Set_Original_Clock ();
  245. }
  246.  
  247.  
  248. /*-------------------------------------------------------------------------
  249.    Set clock rate to its original interrupt rate. Note that the clock rate
  250.    has been saved at 10 times its real value in order to preserve some
  251.    accuracy. */
  252. Set_Original_Clock ()
  253. {
  254.    SetClkRate (0);
  255. }
  256.  
  257.  
  258. /*-------------------------------------------------------------------------
  259.     Change the tempo.
  260.  
  261.     Reload the timer-0 with the proper divider for generating
  262.     the appropriate frequency.
  263.  
  264.     If tempo is zero, reprogram the counter for 18.2 Hz.
  265. */
  266. Set_Tempo (tickQnote, usec)
  267.     unsigned tickQnote;         /* ticks per quarter note */
  268.     long     usec;              /* micro-seconds per quarter note */
  269. {
  270.     long count;
  271.  
  272.     if (!tickQnote)
  273.         count = 0L;
  274.     else {
  275.         /* Calculate required interrupt rate (ticks per sec) */
  276.         usec /= 1000L;
  277.         count = (1194L * usec) / tickQnote;
  278.     }
  279.  
  280.     /* and set the counter: */
  281.     SetClkRate ((unsigned) count);
  282. }
  283.  
  284.  
  285. /*-------------------------------------------------------------------------
  286.    Finds the next event to be processed by checking 'abs_times', which
  287.    contains the absolute time (in ticks) of when the next event occurs
  288.    for each track. Sets the global data ptr musPtr to point to the next
  289.    event and sets the running status for that track as well.
  290.    Returns number of ticks until next event. */
  291.  
  292. static unsigned  Get_Next_Delay ()
  293. {
  294.    static long tickCount = 0;          /* current absolute time */
  295.    static int  ctrk = 0;               /* current track */
  296.    long delta;
  297.    int n, min;
  298.  
  299.    if (*status != END_OF_TRACK) {
  300.       delta = Get_Length ();              /* get delta time */
  301.       abs_time [ctrk] += delta;           /* set new time for track */
  302.    }
  303.    else abs_time [ctrk] = 0x7fffffffL;    /* impossibly large value */
  304.  
  305.    /* Find earliest time in abs_time array.  This determines which track
  306.       contains the next event.  */
  307.    for (min=0, n=1; n < tracks; n++)
  308.       if (abs_time [n] < abs_time [min] && trkStats [n] != END_OF_TRACK)
  309.          min = n;
  310.  
  311.    if (trkStats [min] == END_OF_TRACK) {
  312.       /* end of data condition for all tracks */
  313.       end_of_data = 1;
  314.       Stop_Melo ();
  315.       return (0);
  316.    }
  317.  
  318.    delta = abs_time [min] - tickCount; /* calculate time until next event */
  319.    tickCount = abs_time [min];         /* set current time */
  320.    musPtrPtr = &trkPtrs [min];         /* reset data ptr */
  321.    status = &trkStats [min];           /* set running status */
  322.    ctrk = min;
  323.  
  324.    return ((unsigned) delta);
  325. }
  326.  
  327. /*-------------------------------------------------------------------------*/
  328. static void myNoteOn (voice, note, volume)
  329.    int voice, note, volume;
  330. {
  331.    if (!volume_flag) {
  332.       /* Exit if sound is disabled. */
  333.       current_vol [voice] = volume;
  334.       return;
  335.    }
  336.    if (!volume) {
  337.       /* A note-on with a volume of 0 is equivalent to a note-off. */
  338.       NoteOff (voice);
  339.       current_vol [voice] = volume;
  340.    }
  341.    else {
  342.       /* Regular note-on */
  343.       if (current_vol [voice] != volume) {
  344.          SetVoiceVolume (voice, volume);
  345.          current_vol [voice] = volume;
  346.       }
  347.       NoteOn (voice, note);
  348.    }
  349. }
  350.  
  351. /*-------------------------------------------------------------------------
  352.    Process a regular MIDI event.  Which routine to call is determined by
  353.    using the 3 LSB's of the high nibble. */
  354.  
  355. static void  Midi_Event (event)
  356.    unsigned event;
  357. {
  358.    /* Table of # of data bytes which follow a regular midi status byte */
  359.    static int data_bytes [7] = { 2, 2, 2, 2, 1, 1, 2 };
  360.  
  361.    int stat, voice;
  362.  
  363.    stat = (event >> 4) & 7;
  364.    voice = event & 0x0f;
  365.  
  366.    if (voice < MAX_VOICES) switch (stat) {
  367.       case 0:
  368.          NoteOff (voice);
  369.          break;
  370.       case 1:
  371.          myNoteOn (voice, *musPtr, *(musPtr+1));
  372.          break;
  373.       case 2:
  374.          if (volume_flag) SetVoiceVolume (voice, *(musPtr+1));
  375.          current_vol [voice] = *(musPtr+1);
  376.          break;
  377.       case 5:
  378.          if (volume_flag) SetVoiceVolume (voice, *musPtr);
  379.          current_vol [voice] = *musPtr;
  380.          break;
  381.       case 6:
  382.          SetVoicePitch (voice, (*(musPtr+1) << 7) | *musPtr);
  383.          break;
  384.    }
  385.  
  386.    musPtr += data_bytes [stat];
  387. }
  388.  
  389.  
  390. /*-------------------------------------------------------------------------
  391.    Process an Ad Lib specifec meta-event. */
  392.  
  393. static void  AdLib_Specific (code, data)
  394.    int code;
  395.    unsigned char *data;
  396. {
  397.    if (code == 1) {
  398.       /* Instrument change code.  First byte of data contains voice number.
  399.          Following bytes contain instrument parameters.  */
  400.       extern void SetVoiceTimbre (int, unsigned *);
  401.       int n;
  402.       unsigned int params [28];
  403.       for (n=0; n < 28; n++) params [n] = data [n+1];
  404.       SetVoiceTimbre ((int) data [0], params);
  405.    }
  406.    else if (code == 2) {
  407.       /* Melo/perc mode code.  0 is melodic, !0 is percussive. */
  408.       extern SetMode (int);
  409.       SetMode ((int) data [0]);
  410.    }
  411.    else if (code == 3) {
  412.       /* Sets the interval over which pitch bend changes will be applied. */
  413.       SetPitchRange ((int) data [0]);
  414.    }
  415. }
  416.  
  417.  
  418. /*-------------------------------------------------------------------------
  419.    Process meta-event.  All events other than end-of-track and tempo events
  420.    are ignored.  */
  421. static void  Meta_Event ()
  422. {
  423.    /* musPtr points to the event type byte which follows the 0xff. */
  424.    if (*musPtr == END_OF_TRACK) {
  425.       *status = END_OF_TRACK;
  426.       musPtr--;                  /* leave ptr on EOT event */
  427.    }
  428.    else if (*musPtr == TEMPO) {
  429.       long l;
  430.       musPtr += 2;               /* event type and length bytes */
  431.       l = *musPtr;
  432.       l = (l << 8) + *(musPtr+1);
  433.       l = (l << 8) + *(musPtr+2);
  434.       musPtr += 3;
  435.       Set_Tempo (tickQnote, l);
  436.    }
  437.    else if (*musPtr == SEQ_SPECIFIC) {
  438.       UCHAR far *data;
  439.       long l;
  440.       musPtr++;                  /* event type byte */
  441.       l = Get_Length ();
  442.       data = musPtr;
  443.  
  444.       /* Ad Lib midi ID is 00 00 3f. */
  445.       if (data [0] == 0 && data [1] == 0 && data [2] == 0x3f) {
  446.          /* The first two bytes after the ID contain the Ad Lib event code.
  447.             The following bytes contain the data pertaining to the event. */
  448.          AdLib_Specific ((data [3] << 8) | data [4], &data [5]);
  449.       }
  450.       musPtr += l;
  451.    }
  452.    else {
  453.       musPtr += 1;               /* event type byte */
  454.       musPtr += Get_Length ();   /* event data */
  455.    }
  456. }
  457.  
  458.  
  459. /*-------------------------------------------------------------------------*/
  460. static void Sysex_Event (event)
  461.    UCHAR event;
  462. {
  463.    long len = Get_Length ();
  464.  
  465.    /* skip over system exclusive event */
  466.    musPtr += len;
  467. }
  468.  
  469.  
  470. #ifdef INT_METHOD
  471. /*-------------------------------------------------------------------------
  472.     Interrupt routine. Called by low-level clock driver when
  473.     the delay count has expired.
  474.  
  475.     'musPtr' always points to the first byte AFTER the timing byte.
  476.  
  477.     When this routine is called, the active SS  (stack segment) is not
  478.     the original of the application, so take care.
  479.     This routine, and all others called by, must be compiled
  480.     without stack-overflow checking, since the SS has changed!!!
  481.  
  482.     Return to caller the number of clock ticks to wait for before
  483.     the next call.
  484. */
  485. unsigned TimeOut()
  486. {
  487.     unsigned delay;
  488.     if (! musRunning)
  489.         /* Music has not started or has been stopped, so wait the minimum delay ... */
  490.         return 1;
  491.  
  492.     do {
  493.         /* If high bit set, set status, else this is running status. */
  494.         if (*musPtr & 0x80) {
  495.            *status = *musPtr;
  496.            musPtr++;
  497.         }
  498.  
  499.         /* Process event. */
  500.         if (*status == SYSEX_F7 || *status == SYSEX_F0)
  501.               Sysex_Event (*status);
  502.            else if (*status == META) Meta_Event ();
  503.              else Midi_Event (*status);
  504.  
  505.         /* Read next delta time. */
  506.         delay = Get_Next_Delay ();
  507.  
  508.     } while (delay == 0 && !end_of_data);
  509.  
  510.     if (delay == 0) return (1);
  511.       else return (delay);
  512. }
  513.  
  514.  
  515. #else
  516. /*-------------------------------------------------------------------------
  517.     Alternate method: instead of all the work being done by the interrupt
  518.     routine, the interrupt routine sets a flag and exits.  The main
  519.     program loops on this flag and processes an event whenever the flag
  520.     is set.  The main program then resets the flag and tells the interrupt
  521.     routine how long to wait until setting the flag again.
  522.     The avantage of this is that it can be used for debugging.  Codeview
  523.     cannot be used for debugging interrupt routines, for example.  As well,
  524.     using "printf" inside an interrupt routine will cause the system to
  525.     hang.
  526.     The avantage of the first method is that you can go off and do other
  527.     things while the music is playing.  With this method, you must watch
  528.     the flag constantly.  Use this method to debug and then switch to the
  529.     interrupt method for your final version.
  530.     To use this method, go into the file "cflags.h", put the define for
  531.     INT_METHOD in comments and recompile midimain.c and this file.
  532. */
  533.  
  534. static char timer_signal = 0;
  535.  
  536. /* Interrupt routine. Called by low-level clock driver when
  537.    the delay count has expired.
  538.  
  539.    When this routine is called, the active SS  (stack segment) is not
  540.    the original of the application, so take care.
  541.    This routine, and all others called by, must be compiled
  542.    without stack-overflow checking, since the SS has changed!!!
  543. */
  544. unsigned TimeOut()
  545. {
  546.    timer_signal = 1;
  547.    return (0x7fff);
  548. }
  549.  
  550. /*
  551.    Processes the next event and returns the delay until the next event
  552.    occurs.
  553.    'musPtr' always points to the first byte AFTER the timing byte.
  554. */
  555. static int  Do_Event ()
  556. {
  557.     unsigned delay;
  558.     timer_signal = 0;
  559.     if (! musRunning)
  560.         /* Music has not started or has been stopped, so wait the minimum delay ... */
  561.         return 1;
  562.  
  563.     do {
  564.         /* If high bit set, set status, else this is running status. */
  565.         if (*musPtr & 0x80) {
  566.            *status = *musPtr;
  567.            musPtr++;
  568.         }
  569.  
  570.         /* Process event. */
  571.         if (*status == SYSEX_F7 || *status == SYSEX_F0)
  572.               Sysex_Event (*status);
  573.            else if (*status == META) Meta_Event ();
  574.              else Midi_Event (*status);
  575.  
  576.         /* Read next delta time. */
  577.         delay = Get_Next_Delay ();
  578.  
  579.     } while (delay == 0 && !end_of_data);
  580.  
  581.     if (delay == 0) return (1);
  582.       else return (delay);
  583. }
  584.  
  585. int  Test_Event ()
  586. {
  587.    extern void StartTimeOut (int);
  588.  
  589.    if (!timer_signal) return;
  590.  
  591.    StartTimeOut (Do_Event ());
  592. }
  593. #endif
  594.