ftp.nice.ch/pub/next/unix/music/NewMidiDrv.s.tar.gz#/NewMidiDriver/playmidifile.c

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.