home *** CD-ROM | disk | FTP | other *** search
/ NeXTSTEP 3.0 / NeXTSTEP3.0.iso / NextDeveloper / Examples / SoundAndMusic / Drivers / MidiDriver / playmidifile.c < prev    next >
Text File  |  1992-04-24  |  12KB  |  374 lines

  1. /*
  2.  * This example test program reads a standard Level 0 Midifile (.midi suffix)
  3.  * as defined by the Midi Manufacturer's Association, and plays it 
  4.  * through the MIDI Driver. Level 1 and level 2 files are not supported
  5.  * in this example.
  6.  *
  7.  * Original written by Gregg Kellogg and Danna Massie.
  8.  * Rewritten for release 3.0 MIDI driver by David Jaffe
  9.  */
  10. #import <mach/mach.h>
  11. #import <stdio.h>
  12. #import <stdlib.h>
  13. #import <mach/mach_error.h>
  14. #import <signal.h>
  15. #import <servers/netname.h>
  16. #import <libc.h>
  17. #import <mididriver/midi_driver.h>
  18. #import <mididriver/midi_spec.h>
  19. #import "midifile.h"
  20.  
  21. static port_t driverPort;    /* Port for driver on particular host. */
  22. static port_t ownerPort;     /* Port that represents ownership */
  23. static port_t queuePort;     /* Port for output queue notification messages */
  24. static port_t exceptionPort; /* Port for timing exceptions */
  25. static int maxQueueSize;     /* Maximum output queue size */
  26. static boolean_t allRead;    /* Flag signaling all data read from file. */
  27. static boolean_t allSent;    /* Flag signaling all data sent to driver. */ 
  28. static boolean_t someSent;   /* Flag signaling if  data was sent to driver.*/ 
  29. static NXStream *s;          /* Stream for reading input file */
  30. static int tempo = 60;
  31. static int unit = MIDI_PORT_A_UNIT; /* Serial port to send to */
  32. static port_set_name_t ports;/* Port set to listen for messages from driver */
  33.  
  34. /* Forward referenBS*/
  35. static void usage(void);
  36. static void checkForError(char *msg,int errorReturn);
  37. static port_t allocPort(void);
  38. static port_t createPortSet(port_t queuePort, port_t exceptionPort); 
  39. static void initFile(void);
  40. static void setTempo(int newTempo);
  41. static void myExceptionReply(port_t replyPort, int exception);
  42. static void myQueueReply(port_t replyPort, short unit);
  43. static void cleanup();
  44.  
  45. main(int argc, char **argv)
  46. {
  47.     int i;
  48.     int synchToTimeCode = FALSE;
  49.     kern_return_t r;
  50.     int synchUnit;           /* Serial port to listen for time code */
  51.     char *filename = NULL;
  52.     MIDIReplyFunctions funcs = {0};
  53.     signal (SIGINT, cleanup); /* Control-C routine to clean up gracefully */
  54.     while ((i = getopt(argc, argv, "p:f:t:s:")) != EOF)
  55.     switch (i) {
  56.     case 'p':
  57.         unit = ((!strcmp (optarg ,"a") || !strcmp(optarg,"A")) ? 
  58.             MIDI_PORT_A_UNIT : MIDI_PORT_B_UNIT);
  59.         break;
  60.     case 'f':
  61.         filename = optarg ;
  62.         break;
  63.     case 't':
  64.         tempo = atoi(optarg) ;
  65.         fprintf(stderr,"tempo= %d\n",tempo);
  66.         break;
  67.     case 's':
  68.         synchToTimeCode = TRUE;
  69.         synchUnit = (!strcmp (optarg ,"a")) ? MIDI_PORT_A_UNIT : MIDI_PORT_B_UNIT;
  70.         break;
  71.     case 'h':
  72.     case '?':
  73.     default:
  74.         usage();
  75.         exit(1);
  76.     }
  77.     
  78.     if (filename == NULL) {
  79.     fprintf(stderr,"No filename specified...\n");
  80.     usage();
  81.     exit(1);
  82.     }
  83.     fprintf(stderr,"using midi port: ");
  84.     fprintf(stderr,unit == MIDI_PORT_A_UNIT ? "A\n" : "B\n");
  85.     if (synchToTimeCode) {
  86.     fprintf(stderr,"Synching to MIDI time code on port: ");
  87.     fprintf(stderr,unit == MIDI_PORT_A_UNIT ? "A\n" : "B\n");
  88.     }
  89.     s = NXMapFile(filename,NX_READONLY);
  90.     if (!s) {
  91.     fprintf(stderr,"Cannot open file : %s\n", filename);
  92.     exit(1);
  93.     }
  94.     initFile();
  95.  
  96.     /* Set up MIDI driver */
  97.     r = netname_look_up(name_server_port, "","mididriver", &driverPort);
  98.     checkForError("playmidifile: netname_look_up error",r);
  99.     r = MIDIBecomeOwner(driverPort,ownerPort = allocPort());
  100.     checkForError("MIDIBecomeOwner",r);
  101.     r = MIDIClaimUnit(driverPort, ownerPort,unit);
  102.     checkForError("MIDIClaimUnit",r);
  103.     if (synchToTimeCode && synchUnit != unit) {
  104.     r = MIDIClaimUnit(driverPort, ownerPort,synchUnit);
  105.     checkForError("MIDIClaimUnit",r);
  106.     }
  107.     r = MIDISetClockMode(driverPort, ownerPort, synchUnit,
  108.              (synchToTimeCode ? MIDI_CLOCK_MODE_MTC_SYNC : 
  109.               MIDI_CLBSMODE_INTERNAL));
  110.     checkForError("MIDISetClockMode",r);
  111.     r = MIDISetClockQuantum(driverPort, ownerPort, 1000);
  112.     checkForError("MIDISetClockQuantum",r);
  113.     ports = createPortSet(queuePort=allocPort(), exceptionPort=allocPort());
  114.     r = MIDIRequestExceptions(driverPort, ownerPort, exceptionPort);
  115.     checkForError("MIDIRequestExceptions",r);
  116.     r = MIDIGetAvailableQueueSize(driverPort, ownerPort, unit, &maxQueueSize);
  117.     checkForError("MIDIGetAvailableQueueSize",r);
  118.     if (!synchToTimeCode) {
  119.     r = MIDISetClockTime(driverPort, ownerPort, 0);
  120.     checkForError("MIDISetClockTime",r);
  121.     /* We start clock now.  Alternatively, we could first queue up
  122.      * some messages and then start time.  That would insure that
  123.      * the first few notes come out correctly.  
  124.      */
  125.     r = MIDIStartClock(driverPort, ownerPort);
  126.     checkForError("MIDIStartTime",r);
  127.     }
  128.     /*
  129.      * We play the file by chaining invocations of myQueueReply().  To
  130.      * start the process, we ask the driver to invoke myQueueReply() when
  131.      * the queue is fully available (which will be immediately, since nothing
  132.      * has been sent yet.)
  133.      *
  134.      * Note: If this code is included in an Application, you must either
  135.      * run MIDIAwaitReply() in a separate Mach thread or use MIDIHandleReply()
  136.      * instead of MIDIAwaitReply() and register the port set with DPSAddPort().
  137.      * See <mididriver/midi_driver.h> for details. 
  138.      */    
  139.     r = MIDIRequestQueueNotification(driverPort, ownerPort, unit, queuePort, 
  140.                      maxQueueSize);
  141.     checkForError("MIDIRequestQueueNotification",r);
  142.     funcs.exceptionReply = myExceptionReply;
  143.     funcs.queueReply = myQueueReply;
  144.     if (synchToTimeCode) 
  145.     fprintf(stderr,"Waiting for time code to start...\n");
  146.     allSent = FALSE;
  147.     allRead = FALSE;
  148.     someSent = TRUE;
  149.     while (!allSent) {                /* Here's where the work happens */
  150.     r = MIDIAwaitReply(ports,&funcs,MIDI_NO_TIMEOUT);
  151.     checkForError("MIDIAwaitReply",r);
  152.     }
  153.  
  154.     /* Wait for our output to drain. */
  155.     r = MIDIRequestQueueNotification(driverPort, ownerPort, unit, queuePort, 
  156.                      maxQueueSize);
  157.     checkForError("MIDIRequestQueueNotification",r);
  158.     funcs.queueReply = NULL;
  159.     r = MIDIAwaitReply(ports,&funcs,MIDI_NO_TIMEOUT);
  160.     checkForError("MIDIAwaitReply",r);
  161.     cleanup();
  162. }
  163.  
  164. static MIDIRawEvent events[MIDI_MAX_EVENT];
  165. staBSbyteIndex = 0;
  166.  
  167. static void *fileStruct;        /* Used by MIDIFILE routines. */
  168. static MIDIFILEReadStruct fileEvent = {0};
  169.  
  170. static int getNextFileByte(void)
  171. {
  172.     static int fileEventCtr = 0;
  173.     if (fileEventCtr == 0)
  174.     if (!MIDIFILEReadEvent(fileStruct))
  175.         return FALSE;
  176.     if (fileEvent.metaEventFlag) 
  177.     fileEventCtr = 0;
  178.     else {
  179.     events[byteIndex].time = fileEvent.quanta;
  180.     events[byteIndex].byte = fileEvent.data[fileEventCtr++];
  181.     byteIndex++;
  182.     if (fileEventCtr == fileEvent.nData)
  183.         fileEventCtr = 0;
  184.     }
  185.     return TRUE;
  186. }
  187.  
  188. static int sendData(void) 
  189.     /* Sends the data. Returns FALSE if the driver's buffer is full. */
  190. {
  191.     kern_return_t r = 
  192.     MIDISendData(driverPort, ownerPort, unit, events, byteIndex);
  193.     if (r == MIDI_ERROR_QUEUE_FULL) {
  194.     /* Request notification when at least half the queue is available */
  195.     r = MIDIRequestQueueNotification(driverPort, ownerPort, 
  196.                      unit, queuePort, 
  197.                      maxQueueSize/2);
  198.     checkForError("MIDIRequestQueueNotification",r);
  199.     return FALSE;
  200.     } else checkForError("MIDISendData",r);
  201.     byteIndex = 0;
  202.     return TRUE;
  203. }
  204.  
  205. static void myQueueReply(port_t replyPort, short unit)
  206.     /* This gets invoked when the queue has enough room for more data. */
  207. {
  208.     for (;;) {
  209.     if (byteIndex == MIDI_MAX_EVENT-1)
  210.         if (!sendData())
  211.         return; 
  212.     if (!allRead) 
  213.         allRead = !getNextFileByte();
  214.     if (allRead) {
  215.         if (sendData()) 
  216.         allSent = TRUE;
  217.         return;
  218.     }
  219.     }
  220. }
  221.  
  222. static void myExceptionReply(port_t replyPort, int exception)
  223.     /* This gets invoked when exceptions occur. */
  224. {
  225.     switch (exception) {
  226.       case MIDI_EXCEPTION_MTC_STOPPED:
  227.     fprintf(stderr,"MIDI time code stopped.\n");
  228.     break;
  229.       case MIDI_EXCEPTION_MTC_STARTED_FORWARD:
  230.     fprintf(stderr,"MIDI time code started (forward).\n");
  231.     break;
  232.       case MIDI_EXCEPTION_MTC_STARTED_REVERSE:
  233.     fprintf(stderr,"MIDI time code started (reverse).\n");
  234.     break;
  235.       default:
  236.     break;
  237.     }
  238. }
  239.  
  240. static void usage(void)
  241. {
  242.     fprintf(stderr,
  243.         "usage: playmidifile -f file.midi [-p {A, B}] [-s {A, B}]\n"
  244.         "       -p is the serial port to send the MIDI\n"
  245.         "       -s is the serial port to receive MIDI time code, if any.\n");
  246. }
  247.  
  248. static void checkForError(char *msg,int errorReturn)
  249.     /* Checks for error.  If error, prints message and quits. */
  250. {
  251.     if (errorReturn != KERN_SUCCESS) {
  252.     switch (errorReturBS
  253.           case MIDI_ERROR_BUSY:
  254.         printf("%s: %s",msg,"MIDI driver busy.\n");
  255.         break;
  256.           case MIDI_ERROR_NOT_OWNER:
  257.         printf("%s: %s",msg,"You must be owner of the MIDI driver.\n");
  258.         break;
  259.           case MIDI_ERROR_QUEUE_FULL:
  260.         printf("%s: %s",msg,"MIDI driver queue full.\n");
  261.         break;
  262.           case MIDI_ERROR_BAD_MODE:
  263.         printf("%s: %s",msg,"Bad MIDI driver clock mode.\n");
  264.         break;
  265.           case MIDI_ERROR_UNIT_UNAVAILABLE:
  266.         printf("%s: %s",msg,"MIDI driver unit unavailable.\n");
  267.         break;
  268.           case MIDI_ERROR_ILLEGAL_OPERATION:
  269.         printf("%s: %s",msg,"MIDI driver illegal operation.\n");
  270.         break;
  271.       default: 
  272.         mach_error(msg,errorReturn);
  273.     }
  274.     exit(1);
  275.     }
  276. }
  277.  
  278. static port_t allocPort(void)
  279.     /* Allocates a port and returns it. */
  280. {
  281.     port_t aPort;
  282.     int r = port_allocate(task_self(), &aPort);
  283.     checkForError("allocPort",r);
  284.     return aPort;
  285. }
  286.  
  287. static port_t createPortSet(port_t queuePort, port_t exceptionPort) 
  288.     /* Creates the port set and adds the two ports.  */
  289. {
  290.     port_set_name_t aPortSet;
  291.     int r = port_set_allocate(task_self(), &aPortSet);
  292.     checkForError("createPortSet",r);
  293.     r = port_set_add(task_self(), aPortSet, queuePort);
  294.     checkForError("createPortSet",r);
  295.     r = port_set_add(task_self(), aPortSet, exceptionPort);
  296.     checkForError("createPortSet",r);
  297.     return aPortSet;
  298. }
  299.  
  300. static void setTempo(int newTempo)
  301. {
  302.     MIDIFILESetReadQuantaSize(fileStruct,(tempo = newTempo)/60.0 * 1000);
  303. }
  304.  
  305. static void initFile(void)    
  306. {
  307.     int level,trackCount;
  308.     fileStruct = MIDIFILEBeginReading(s,&fileEvent);
  309.     if (MIDIFILEReadPreamble(fileStruct,&level,&trackCount)) {
  310.     if (level != 0) {
  311.         fprintf(stderr,"playmidifile cannot play level %d files.\n",level);
  312.         exit(1);
  313.     }
  314.     }
  315.     else {
  316.     fprintf(stderr,"failed to read preamble!\n");
  317.     exit(1);
  318.     }
  319.     setTempo(tempo);
  320. }
  321.  
  322. static void allNotesOff(void) {
  323.     #define NOTEOFF_ARRAY_SIZE (128*2)          /* Use running status */
  324.     MIDIRawEvent arr[NOTEOFF_ARRAY_SIZE]; 
  325.     kern_return_t r;
  326.     int chan = 0;
  327.     int bytesToSend,i;
  328.     MIDIRawEvent op;
  329.     MIDIReplyFunctions funcs = {0};
  330.     for (i=0; i<128; i++) {  /* Initialize array */
  331.     arr[i*2].byte = i;   /* KeyNum */
  332.     arr[i*2+1].byte = 0; /* Velocity */
  333.     }
  334.     
  335.     while (MIDIAwaitReply(ports,&funcs,1) == KERN_SUCBS)
  336.     ;    /* Empty out ports of any old notification messages. */
  337.     r = MIDIClearQueue(driverPort,ownerPort,unit);
  338.     checkForError("MIDIClearQueue",r);
  339.     for (chan = 0; chan < 16; chan++) {
  340.     op.byte = MIDI_NOTEOFF | chan;
  341.     r = MIDISendData(driverPort,ownerPort,unit,&op,1);
  342.     checkForError("MIDISendData",r);
  343.     for (i = 0; i < NOTEOFF_ARRAY_SIZE; i += MIDI_MAX_EVENT) {
  344.         bytesToSend = NOTEOFF_ARRAY_SIZE - i;
  345.         if (bytesToSend > MIDI_MAX_EVENT)
  346.         bytesToSend = MIDI_MAX_EVENT;
  347.         r = MIDISendData(driverPort,ownerPort,unit,&arr[i],bytesToSend);
  348.         checkForError("MIDISendData",r);
  349.     }
  350.     r = MIDIFlushQueue(driverPort,ownerPort,unit);
  351.     checkForError("MIDIFlushQueue",r);
  352.     r = MIDIRequestQueueNotification(driverPort, ownerPort,unit,
  353.                      queuePort,
  354.                      ((chan==15) ? maxQueueSize : 
  355.                       NOTEOFF_ARRAY_SIZE+1));
  356.     checkForError("MIDIRequestQueueNotification",r);
  357.     r = MIDIAwaitReply(ports,&funcs,MIDI_NO_TIMEOUT);
  358.     checkForError("MIDIAwaitReply",r);
  359.     }
  360. }
  361.  
  362. static void cleanup() {
  363.     kern_return_t r;
  364.     if (!driverPort || !ownerPort)
  365.     exit(0);
  366.     if (someSent)
  367.     allNotesOff();
  368.     r = MIDIReleaseOwnership(driverPort,ownerPort);
  369.     checkForError("MIDIReleaseOwnership",r);
  370.     exit(0);
  371. }
  372.  
  373.  
  374.