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.