This is playmidifile.c in view mode; [Download] [Up]
/* * This example test program reads a standard Level 0 Midifile (.midi suffix) * as defined by the Midi Manufacturer's Association, and plays it * through the MIDI Driver. Level 1 and level 2 files are not supported * in this example. * * The MIDI Driver is only the second commercial MACH driver ever written, * and Gregg Kellogg wrote it. Gregg also wrote the test program * this was based on. Lee Boynton wrote the Midifile parsing routines. * Dana Massie combined the two. * * modified by Brian Willoughby 2-92 * (added support for multiple file tempo messages) * (general cleanup of various bugs and typographical errors) */ #import <mach.h> #import <stdio.h> #import <stdlib.h> #import <fcntl.h> #import <mach_error.h> #import <servers/netname.h> #import <strings.h> #import <midi/midi_server.h> #import <midi/midi_reply_handler.h> #import <midi/midi_timer.h> #import <midi/midi_timer_reply_handler.h> #import <midi/midi_error.h> #import <midi/midi_timer_error.h> #import "midifile.h" #define max(a, b) ((a) > (b) ? (a) : (b)) char *midiA = "midi0"; char *midiB = "midi1"; #define A_MIDI_PORT midiA #define B_MIDI_PORT midiB #define DEFAULT_MIDI_PORT A_MIDI_PORT int open(); /* Not declared anywhere else. */ /* * These routines should be prototyped someplace in /usr/include! */ int getopt(int argc, char **argv, char *optstring); int read(int fd, char *data, int size); int open(char *file, int options, int mode); void usage(void); void msg_rcv_loop(void); port_t dev_port; port_t owner_port; port_t timer_port; port_t timer_reply_port; port_t xmit_port; port_t xmit_reply_port; port_t neg_port; port_set_name_t port_set; int secs = 1; int exit_rcv_loop; u_int queue_max = max(MIDI_COOKED_DATA_MAX, MIDI_RAW_DATA_MAX)*2; kern_return_t my_timer_event(void *arg, timeval_t timeval, u_int quanta, u_int usec_per_quantum, u_int real_usec_per_quantum, boolean_t timer_expired, boolean_t timer_stopped, boolean_t timer_forward); midi_timer_reply_t midi_timer_reply = { my_timer_event, // timer_event 0, // argument to pass to function 0 // timeout for RPC return msg_send }; kern_return_t my_queue_notify(void *arg, u_int queue_size); midi_reply_t midi_reply = { 0, // ret_raw_data 0, // ret_cooked_data 0, // ret_packed_data my_queue_notify,// queue_notify 0, // argument to pass to function 0 // timeout for RPC return msg_send }; midievent_t theEvent = {0, 0, 0}; NXStream *s; int tempo = 0; int tempoArg = 0; // specifying command-line tempo will override all tempo messages in the file void usage(void) { fprintf(stderr, "usage: playmidifile -f file.midi -p {a, b} (midi port) -t (tempo)\n"); } main(int argc, char **argv) { int i; kern_return_t r; extern char *optarg; extern int optind; char * midiPort, * filename = NULL; // file to read from int level, trackCount; midiPort = DEFAULT_MIDI_PORT; while ((i = getopt(argc, argv, "p:f:t:")) != EOF) switch (i) { case 'p': // bdw: previously accepted only 'a' if ((!strcmp(optarg, "a")) || (!strcmp(optarg, "A"))) midiPort = A_MIDI_PORT; else midiPort = B_MIDI_PORT; break; case 'f': filename = optarg; break; case 't': tempoArg = atoi(optarg); tempo = tempoArg; fprintf(stderr, "tempo= %d\n", tempo); break; case 'h': case '?': default: usage(); exit(1); } if (filename == NULL) { fprintf(stderr, "this guy needs a filename specified...\n"); usage(); exit(1); } fprintf(stderr, "using midi port: %c\n", (A_MIDI_PORT == midiPort) ? 'A' : ((B_MIDI_PORT == midiPort) ? 'B' : '?')); s = NXMapFile(filename, NX_READONLY); if (!s) { fprintf(stderr, "Cannot open file : %s\n", filename); exit(1); } if (MIDIFileReadPreamble(s, &level, &trackCount)) { if (level != 0) { fprintf(stderr, "playmidifile cannot play level %d files.\n", level); exit(1); } } else { fprintf(stderr, "failed to read preamble!\n"); exit(1); } /* * Get a connection to the midi driver. */ r = netname_look_up(name_server_port, "", midiPort, &dev_port); if (r != KERN_SUCCESS) { mach_error("timer_track: netname_look_up error", r); exit(1); } /* * Become owner of the device. */ r = port_allocate(task_self(), &owner_port); if (r != KERN_SUCCESS) { mach_error("allocate owner port", r); exit(1); } neg_port = PORT_NULL; r = midi_set_owner(dev_port, owner_port, &neg_port); if (r != KERN_SUCCESS) { midi_error("become owner", r); exit(1); } /* * Get the timer port for the device. */ r = midi_get_out_timer_port(dev_port, &timer_port); if (r != KERN_SUCCESS) { midi_error("output timer port", r); exit(1); } /* * Get the transmit port for the device. */ r = midi_get_xmit(dev_port, owner_port, &xmit_port); if (r != KERN_SUCCESS) { midi_error("xmit port", r); exit(1); } r = port_allocate(task_self(), &timer_reply_port); if (r != KERN_SUCCESS) { mach_error("allocate timer reply port", r); exit(1); } /* * Find out what time it is (and other vital information). */ r = port_allocate(task_self(), &xmit_reply_port); if (r != KERN_SUCCESS) { mach_error("allocate xmit reply port", r); exit(1); } /* * Set the protocol to indicate our preferences. */ r = midi_set_proto( xmit_port, // transmit port MIDI_PROTO_COOKED, // cooked data output FALSE, // absolute time codes wanted MIDI_PROTO_SYNC_SYS,// use system derived clock 10, // 10 (ms) clocks before data sent (recv only) 2, // 2 (ms) clock timeout between input chars queue_max); // maximum output queue size (number of msgs) if (r != KERN_SUCCESS) { mach_error("midi_set_proto", r); exit(1); } /* * Allocate port set. */ r = port_set_allocate(task_self(), &port_set); if (r != KERN_SUCCESS) { mach_error("allocate port set", r); exit(1); } /* * Add timer receive port to port set. */ r = port_set_add(task_self(), port_set, timer_reply_port); if (r != KERN_SUCCESS) { mach_error("add timer_reply_port to set", r); exit(1); } /* * Add driver reply port to port set. */ r = port_set_add(task_self(), port_set, xmit_reply_port); if (r != KERN_SUCCESS) { mach_error("add xmit_reply_port to set", r); exit(1); } /* * Start the timer up. */ r = timer_start(timer_port, owner_port); if (r != KERN_SUCCESS) { midi_error("timer start", r); exit(1); } #ifdef TIMERDISPLAY /* bdw: added this to complete code */ r = timer_quanta_req(timer_port, timer_reply_port, 0, // 0 quanta TRUE); // from now if (r != KERN_SUCCESS) { midi_timer_error("request timer", r); exit(1); } #endif /* bdw: this comment is wrong, no stdin support, only one MIDI file * For each MIDI file (or stdin, if no files) reset ownership * (to clear everything out), call the queue_notify proc to * startup (and continue, without blocking) writing data down. * Afterwards place a request in for notification when the output * queue size is zero, so we don't terminate too soon. */ /* * Send a message requesting a message when the * queue size is zero (as it should be now). */ r = midi_output_queue_notify(dev_port, owner_port, xmit_reply_port, 0); if (r != KERN_SUCCESS) { midi_error("(pre) await queue length zero", r); exit(1); } /* * Enter the receive loop. my_queue_notify() will set * exit_rcv_loop to TRUE when all data has been output * from the file (EOF is seen). This will cause the * receive loop to exit. */ exit_rcv_loop = FALSE; msg_rcv_loop(); /* * Do the same thing again, so that we can wait for our * output to drain. */ r = midi_output_queue_notify(dev_port, owner_port, xmit_reply_port, 0); if (r != KERN_SUCCESS) { midi_error("(post) await queue length zero", r); exit(1); } exit_rcv_loop = FALSE; msg_rcv_loop(); exit(0); } #define IN_SIZE max(MIDI_TIMER_REPLY_INMSG_SIZE, MIDI_REPLY_INMSG_SIZE) #define OUT_SIZE max(MIDI_TIMER_REPLY_OUTMSG_SIZE, MIDI_REPLY_OUTMSG_SIZE) void msg_rcv_loop(void) { int i; kern_return_t r; msg_header_t *in_msg, *out_msg; /* * Call my_queue_notify directly to get the output started. */ in_msg = (msg_header_t *)malloc(IN_SIZE); out_msg = (msg_header_t *)malloc(OUT_SIZE); while (!exit_rcv_loop) { in_msg->msg_size = IN_SIZE; in_msg->msg_local_port = port_set; r = msg_receive(in_msg, MSG_OPTION_NONE, 0); if (r != KERN_SUCCESS) { mach_error("msg_receive", r); exit(1); } if (in_msg->msg_local_port == xmit_reply_port) r = midi_reply_handler(in_msg, &midi_reply); else if (in_msg->msg_local_port == timer_reply_port) r = midi_timer_reply_handler(in_msg, &midi_timer_reply); else { fprintf(stderr, "unknown port\n"); r = KERN_FAILURE; } if (r != KERN_SUCCESS) mach_error("midi_timer_reply_server", r); } free((char *)in_msg); free((char *)out_msg); } kern_return_t my_timer_event(void *arg, timeval_t timeval, u_int quanta, u_int usec_per_quantum, u_int real_usec_per_quantum, boolean_t timer_expired, boolean_t timer_stopped, boolean_t timer_forward) { kern_return_t r; static int nquanta; nquanta += secs * 1000000 / usec_per_quantum; printf("time is %d usec/quantum %d\n", quanta, real_usec_per_quantum); if (!timer_expired) { printf("timer hasn't expired\n"); return KERN_SUCCESS; } r = timer_quanta_req(timer_port, timer_reply_port, nquanta, // secs seconds from FALSE); // from timer base (absolute time) if (r != KERN_SUCCESS) { midi_timer_error("request timer", r); exit(1); } return KERN_SUCCESS; } kern_return_t my_queue_notify(void *arg, u_int queue_size) { int n, i, current_track; kern_return_t r; boolean_t will_block = FALSE; midi_cooked_data_t cooked[MIDI_COOKED_DATA_MAX]; midi_cooked_data_t midi_cooked_data; /* * Loop through the MIDI data, until we would block, sending at most * MIDI_{COOKED,RAW}_DATA_MAX messages in a single call. */ while (!will_block) { if (!MIDIFileReadEvent(s, &theEvent)) { exit_rcv_loop = TRUE; return KERN_SUCCESS; } else { if (theEvent.metaevent) { if (theEvent.data[0] == MIDI_METAEVENT_TRACK_CHANGE) { current_track = *((short *)&(theEvent.data[1])); fprintf(stderr, "\n*** Track %d\n", current_track); } else if (theEvent.data[0] == MIDI_METAEVENT_TEMPO_CHANGE) { /* Brian Willoughby - 2/2/92 * The original code made no distinction between the current * tempo and a tempo specified on the command-line. The result * was that only the first tempo message in a file was * recognized. Additional tempo messages were ignored as * if the first had appeared on the command-line. * if (tempo == 0) * tempoArg has been added to allow the command-line to * override all tempo message in the file. Otherwise, each * tempo message from the file is honored. */ if (0 == tempoArg) tempo = *((short *)&(theEvent.data[1])); MIDISetQuantaSize(tempo/120.0 * 1000); /* bdw: removed since it is not printed in real time fprintf(stderr, "Set tempo to %d beats per minute\n", tempo); */ } else fprintf(stderr, "\t[Unknown meta event]\n"); } else { /* * copy the midi data into driver style structure */ midi_cooked_data.quanta = theEvent.quanta; midi_cooked_data.ndata = theEvent.ndata; for (i = 0; i < theEvent.ndata; i++) midi_cooked_data.data[i] = theEvent.data[i]; /* * Send the data. */ r = midi_send_cooked_data(xmit_port, &midi_cooked_data, 1, TRUE); if (r == MIDI_WILL_BLOCK) { will_block = TRUE; } else if (r != KERN_SUCCESS) { midi_error("midi_send_cooked_data", r); exit(1); } } } } /* * Get a message when the queue size is down * to half of it's max. Exit to await the message * so that we can start up again. */ r = midi_output_queue_notify(dev_port, owner_port, xmit_reply_port, queue_max/2); if (r != KERN_SUCCESS) { midi_error("midi_output_queue_notify", r); exit(1); } return KERN_SUCCESS; }
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.