/* ______ ___ ___ * /\ _ \ /\_ \ /\_ \ * \ \ \L\ \\//\ \ \//\ \ __ __ _ __ ___ * \ \ __ \ \ \ \ \ \ \ /'__`\ /'_ `\/\`'__\/ __`\ * \ \ \/\ \ \_\ \_ \_\ \_/\ __//\ \L\ \ \ \//\ \L\ \ * \ \_\ \_\/\____\/\____\ \____\ \____ \ \_\\ \____/ * \/_/\/_/\/____/\/____/\/____/\/___L\ \/_/ \/___/ * /\____/ * \_/__/ * * The core MIDI file player. * * By Shawn Hargreaves. * * Pause and seek functions by George Foot. * * get_midi_length by Elias Pschernig. * * See readme.txt for copyright information. */ #include #include #include "allegro.h" #include "allegro/internal/aintern.h" /* maximum number of layers in a single voice */ #define MIDI_LAYERS 4 /* how often the midi callback gets called maximally / second */ #define MIDI_TIMER_FREQUENCY 40 typedef struct MIDI_TRACK /* a track in the MIDI file */ { unsigned char *pos; /* position in track data */ long timer; /* time until next event */ unsigned char running_status; /* last MIDI event */ } MIDI_TRACK; typedef struct MIDI_CHANNEL /* a MIDI channel */ { int patch; /* current sound */ int volume; /* volume controller */ int pan; /* pan position */ int pitch_bend; /* pitch bend position */ int new_volume; /* cached volume change */ int new_pitch_bend; /* cached pitch bend */ int note[128][MIDI_LAYERS]; /* status of each note */ } MIDI_CHANNEL; typedef struct MIDI_VOICE /* a voice on the soundcard */ { int channel; /* MIDI channel */ int note; /* note (-1 = off) */ int volume; /* note velocity */ long time; /* when note was triggered */ } MIDI_VOICE; typedef struct WAITING_NOTE /* a stored note-on request */ { int channel; int note; int volume; } WAITING_NOTE; typedef struct PATCH_TABLE /* GM -> external synth */ { int bank1; /* controller #0 */ int bank2; /* controller #32 */ int prog; /* program change */ int pitch; /* pitch shift */ } PATCH_TABLE; volatile long midi_pos = -1; /* current position in MIDI file */ volatile long midi_time = 0; /* current position in seconds */ static volatile long midi_timers; /* current position in allegro-timer-ticks */ static long midi_pos_counter; /* delta for midi_pos */ volatile long _midi_tick = 0; /* counter for killing notes */ static void midi_player(void); /* core MIDI player routine */ static void prepare_to_play(MIDI *midi); static void midi_lock_mem(void); static MIDI *midifile = NULL; /* the file that is playing */ static int midi_loop = 0; /* repeat at eof? */ long midi_loop_start = -1; /* where to loop back to */ long midi_loop_end = -1; /* loop at this position */ static int midi_semaphore = 0; /* reentrancy flag */ static int midi_loaded_patches = FALSE; /* loaded entire patch set? */ static long midi_timer_speed; /* midi_player's timer speed */ static int midi_pos_speed; /* MIDI delta -> midi_pos */ static int midi_speed; /* MIDI delta -> timer */ static int midi_new_speed; /* for tempo change events */ static int old_midi_volume = -1; /* stored global volume */ static int midi_alloc_channel; /* so _midi_allocate_voice */ static int midi_alloc_note; /* knows which note the */ static int midi_alloc_vol; /* sound is associated with */ static MIDI_TRACK midi_track[MIDI_TRACKS]; /* the active tracks */ static MIDI_VOICE midi_voice[MIDI_VOICES]; /* synth voice status */ static MIDI_CHANNEL midi_channel[16]; /* MIDI channel info */ static WAITING_NOTE midi_waiting[MIDI_VOICES]; /* notes still to be played */ static PATCH_TABLE patch_table[128]; /* GM -> external synth */ static int midi_seeking; /* set during seeks */ static int midi_looping; /* set during loops */ /* hook functions */ void (*midi_msg_callback)(int msg, int byte1, int byte2) = NULL; void (*midi_meta_callback)(int type, AL_CONST unsigned char *data, int length) = NULL; void (*midi_sysex_callback)(AL_CONST unsigned char *data, int length) = NULL; /* lock_midi: * Locks a MIDI file into physical memory. Pretty important, since they * are mostly accessed inside interrupt handlers. */ void lock_midi(MIDI *midi) { int c; ASSERT(midi); LOCK_DATA(midi, sizeof(MIDI)); for (c=0; ctrack[c].data) { LOCK_DATA(midi->track[c].data, midi->track[c].len); } } } /* load_midi: * Loads a standard MIDI file, returning a pointer to a MIDI structure, * or NULL on error. */ MIDI *load_midi(AL_CONST char *filename) { int c; char buf[4]; long data; PACKFILE *fp; MIDI *midi; int num_tracks; ASSERT(filename); fp = pack_fopen(filename, F_READ); /* open the file */ if (!fp) return NULL; midi = malloc(sizeof(MIDI)); /* get some memory */ if (!midi) { pack_fclose(fp); return NULL; } for (c=0; ctrack[c].data = NULL; midi->track[c].len = 0; } pack_fread(buf, 4, fp); /* read midi header */ /* Is the midi inside a .rmi file? */ if (memcmp(buf, "RIFF", 4) == 0) { /* check for RIFF header */ pack_mgetl(fp); while (!pack_feof(fp)) { pack_fread(buf, 4, fp); /* RMID chunk? */ if (memcmp(buf, "RMID", 4) == 0) break; pack_fseek(fp, pack_igetl(fp)); /* skip to next chunk */ } if (pack_feof(fp)) goto err; pack_mgetl(fp); pack_mgetl(fp); pack_fread(buf, 4, fp); /* read midi header */ } if (memcmp(buf, "MThd", 4)) goto err; pack_mgetl(fp); /* skip header chunk length */ data = pack_mgetw(fp); /* MIDI file type */ if ((data != 0) && (data != 1)) goto err; num_tracks = pack_mgetw(fp); /* number of tracks */ if ((num_tracks < 1) || (num_tracks > MIDI_TRACKS)) goto err; data = pack_mgetw(fp); /* beat divisions */ midi->divisions = ABS(data); for (c=0; ctrack[c].len = data; midi->track[c].data = malloc(data); /* allocate memory */ if (!midi->track[c].data) goto err; /* finally, read track data */ if (pack_fread(midi->track[c].data, data, fp) != data) goto err; } pack_fclose(fp); lock_midi(midi); return midi; /* oh dear... */ err: pack_fclose(fp); destroy_midi(midi); return NULL; } /* destroy_midi: * Frees the memory being used by a MIDI file. */ void destroy_midi(MIDI *midi) { int c; if (midi == midifile) stop_midi(); if (midi) { for (c=0; ctrack[c].data) { UNLOCK_DATA(midi->track[c].data, midi->track[c].len); free(midi->track[c].data); } } UNLOCK_DATA(midi, sizeof(MIDI)); free(midi); } } /* parse_var_len: * The MIDI file format is a strange thing. Time offsets are only 32 bits, * yet they are compressed in a weird variable length format. This routine * reads a variable length integer from a MIDI data stream. It returns the * number read, and alters the data pointer according to the number of * bytes it used. */ static unsigned long parse_var_len(AL_CONST unsigned char **data) { unsigned long val = **data & 0x7F; while (**data & 0x80) { (*data)++; val <<= 7; val += (**data & 0x7F); } (*data)++; return val; } END_OF_STATIC_FUNCTION(parse_var_len); /* global_volume_fix: * Converts a note volume, adjusting it according to the global * _midi_volume variable. */ static INLINE int global_volume_fix(int vol) { if (_midi_volume >= 0) return (vol * _midi_volume) / 256; return vol; } /* sort_out_volume: * Converts a note volume, adjusting it according to the channel volume * and the global _midi_volume variable. */ static INLINE int sort_out_volume(int c, int vol) { return global_volume_fix((vol * midi_channel[c].volume) / 128); } /* raw_program_change: * Sends a program change message to a device capable of handling raw * MIDI data, using patch mapping tables. Assumes that midi_driver->raw_midi * isn't NULL, so check before calling it! */ static void raw_program_change(int channel, int patch) { if (channel != 9) { /* bank change #1 */ if (patch_table[patch].bank1 >= 0) { midi_driver->raw_midi(0xB0+channel); midi_driver->raw_midi(0); midi_driver->raw_midi(patch_table[patch].bank1); } /* bank change #2 */ if (patch_table[patch].bank2 >= 0) { midi_driver->raw_midi(0xB0+channel); midi_driver->raw_midi(32); midi_driver->raw_midi(patch_table[patch].bank2); } /* program change */ midi_driver->raw_midi(0xC0+channel); midi_driver->raw_midi(patch_table[patch].prog); /* update volume */ midi_driver->raw_midi(0xB0+channel); midi_driver->raw_midi(7); midi_driver->raw_midi(global_volume_fix(midi_channel[channel].volume-1)); } } END_OF_STATIC_FUNCTION(raw_program_change); /* midi_note_off: * Processes a MIDI note-off event. */ static void midi_note_off(int channel, int note) { int done = FALSE; int voice, layer; int c; /* can we send raw MIDI data? */ if (midi_driver->raw_midi) { if (channel != 9) note += patch_table[midi_channel[channel].patch].pitch; midi_driver->raw_midi(0x80+channel); midi_driver->raw_midi(note); midi_driver->raw_midi(0); return; } /* oh well, have to do it the long way... */ for (layer=0; layer= 0) { midi_driver->key_off(voice + midi_driver->basevoice); midi_voice[voice].note = -1; midi_voice[voice].time = _midi_tick; midi_channel[channel].note[note][layer] = -1; done = TRUE; } } /* if the note isn't playing, it must still be in the waiting room */ if (!done) { for (c=0; c= 0x1000) { (*note)++; (*bend) -= 0x1000; } } /* _midi_allocate_voice: * Allocates a MIDI voice in the range min-max (inclusive). This is * intended to be called by the key_on() handlers in the MIDI driver, * and shouldn't be used by any other code. */ int _midi_allocate_voice(int min, int max) { int c; int layer; int voice = -1; long best_time = LONG_MAX; if (min < 0) min = 0; if (max < 0) max = midi_driver->voices-1; /* which layer can we use? */ for (layer=0; layer= MIDI_LAYERS) return -1; /* find a free voice */ for (c=min; c<=max; c++) { if ((midi_voice[c].note < 0) && (midi_voice[c].time < best_time) && ((c < midi_driver->xmin) || (c > midi_driver->xmax))) { voice = c; best_time = midi_voice[c].time; } } /* if there are no free voices, kill a note to make room */ if (voice < 0) { voice = -1; best_time = LONG_MAX; for (c=min; c<=max; c++) { if ((midi_voice[c].time < best_time) && ((c < midi_driver->xmin) || (c > midi_driver->xmax))) { voice = c; best_time = midi_voice[c].time; } } if (voice >= 0) midi_note_off(midi_voice[voice].channel, midi_voice[voice].note); else return -1; } /* ok, we got it... */ midi_voice[voice].channel = midi_alloc_channel; midi_voice[voice].note = midi_alloc_note; midi_voice[voice].volume = midi_alloc_vol; midi_voice[voice].time = _midi_tick; midi_channel[midi_alloc_channel].note[midi_alloc_note][layer] = voice; return voice + midi_driver->basevoice; } END_OF_FUNCTION(_midi_allocate_voice); /* midi_note_on: * Processes a MIDI note-on event. Tries to find a free soundcard voice, * and if it can't either cuts off an existing note, or if 'polite' is * set, just stores the channel, note and volume in the waiting list. */ static void midi_note_on(int channel, int note, int vol, int polite) { int c, layer, inst, bend, corrected_note; /* it's easy if the driver can handle raw MIDI data */ if (midi_driver->raw_midi) { if (channel != 9) note += patch_table[midi_channel[channel].patch].pitch; midi_driver->raw_midi(0x90+channel); midi_driver->raw_midi(note); midi_driver->raw_midi(vol); return; } /* if the note is already on, turn it off */ for (layer=0; layer= 0) { midi_note_off(channel, note); return; } } /* if zero volume and the note isn't playing, we can just ignore it */ if (vol == 0) return; if (channel != 9) { /* are there any free voices? */ for (c=0; cvoices; c++) if ((midi_voice[c].note < 0) && ((c < midi_driver->xmin) || (c > midi_driver->xmax))) break; /* if there are no free voices, remember the note for later */ if ((c >= midi_driver->voices) && (polite)) { for (c=0; ckey_on(inst, corrected_note, bend, sort_out_volume(channel, vol), midi_channel[channel].pan); } END_OF_STATIC_FUNCTION(midi_note_on); /* all_notes_off: * Turns off all active notes. */ static void all_notes_off(int channel) { if (midi_driver->raw_midi) { midi_driver->raw_midi(0xB0+channel); midi_driver->raw_midi(123); midi_driver->raw_midi(0); return; } else { int note, layer; for (note=0; note<128; note++) for (layer=0; layer= 0) midi_note_off(channel, note); } } END_OF_STATIC_FUNCTION(all_notes_off); /* all_sound_off: * Turns off sound. */ static void all_sound_off(int channel) { if (midi_driver->raw_midi) { midi_driver->raw_midi(0xB0+channel); midi_driver->raw_midi(120); midi_driver->raw_midi(0); return; } } END_OF_STATIC_FUNCTION(all_sound_off); /* reset_controllers: * Resets volume, pan, pitch bend, etc, to default positions. */ static void reset_controllers(int channel) { midi_channel[channel].new_volume = 128; midi_channel[channel].new_pitch_bend = 0x2000; switch (channel % 3) { case 0: midi_channel[channel].pan = ((channel/3) & 1) ? 60 : 68; break; case 1: midi_channel[channel].pan = 104; break; case 2: midi_channel[channel].pan = 24; break; } if (midi_driver->raw_midi) { midi_driver->raw_midi(0xB0+channel); midi_driver->raw_midi(10); midi_driver->raw_midi(midi_channel[channel].pan); } } END_OF_STATIC_FUNCTION(reset_controllers); /* update_controllers: * Checks cached controller information and updates active voices. */ static void update_controllers(void) { int c, c2, vol, bend, note; for (c=0; c<16; c++) { /* check for volume controller change */ if ((midi_channel[c].volume != midi_channel[c].new_volume) || (old_midi_volume != _midi_volume)) { midi_channel[c].volume = midi_channel[c].new_volume; if (midi_driver->raw_midi) { midi_driver->raw_midi(0xB0+c); midi_driver->raw_midi(7); midi_driver->raw_midi(global_volume_fix(midi_channel[c].volume-1)); } else { for (c2=0; c2= 0)) { vol = sort_out_volume(c, midi_voice[c2].volume); midi_driver->set_volume(c2 + midi_driver->basevoice, vol); } } } } /* check for pitch bend change */ if (midi_channel[c].pitch_bend != midi_channel[c].new_pitch_bend) { midi_channel[c].pitch_bend = midi_channel[c].new_pitch_bend; if (midi_driver->raw_midi) { midi_driver->raw_midi(0xE0+c); midi_driver->raw_midi(midi_channel[c].pitch_bend & 0x7F); midi_driver->raw_midi(midi_channel[c].pitch_bend >> 7); } else { for (c2=0; c2= 0)) { bend = midi_channel[c].pitch_bend; note = midi_voice[c2].note; sort_out_pitch_bend(&bend, ¬e); midi_driver->set_pitch(c2 + midi_driver->basevoice, note, bend); } } } } } old_midi_volume = _midi_volume; } END_OF_STATIC_FUNCTION(update_controllers); /* process_controller: * Deals with a MIDI controller message on the specified channel. */ static void process_controller(int channel, int ctrl, int data) { switch (ctrl) { case 7: /* main volume */ midi_channel[channel].new_volume = data+1; break; case 10: /* pan */ midi_channel[channel].pan = data; if (midi_driver->raw_midi) { midi_driver->raw_midi(0xB0+channel); midi_driver->raw_midi(10); midi_driver->raw_midi(data); } break; case 120: /* all sound off */ all_sound_off(channel); break; case 121: /* reset all controllers */ reset_controllers(channel); break; case 123: /* all notes off */ case 124: /* omni mode off */ case 125: /* omni mode on */ case 126: /* poly mode off */ case 127: /* poly mode on */ all_notes_off(channel); break; default: if (midi_driver->raw_midi) { midi_driver->raw_midi(0xB0+channel); midi_driver->raw_midi(ctrl); midi_driver->raw_midi(data); } break; } } END_OF_STATIC_FUNCTION(process_controller); /* process_meta_event: * Processes the next meta-event on the specified track. */ static void process_meta_event(AL_CONST unsigned char **pos, long *timer) { unsigned char metatype = *((*pos)++); long length = parse_var_len(pos); long tempo; if (midi_meta_callback) midi_meta_callback(metatype, *pos, length); if (metatype == 0x2F) { /* end of track */ *pos = NULL; *timer = LONG_MAX; return; } if (metatype == 0x51) { /* tempo change */ tempo = (*pos)[0] * 0x10000L + (*pos)[1] * 0x100 + (*pos)[2]; midi_new_speed = (tempo/1000) * (TIMERS_PER_SECOND/1000); midi_new_speed /= midifile->divisions; } (*pos) += length; } END_OF_STATIC_FUNCTION(process_meta_event); /* process_midi_event: * Processes the next MIDI event on the specified track. */ static void process_midi_event(AL_CONST unsigned char **pos, unsigned char *running_status, long *timer) { unsigned char byte1, byte2; int channel; unsigned char event; long l; event = *((*pos)++); if (event & 0x80) { /* regular message */ /* no running status for sysex and meta-events! */ if ((event != 0xF0) && (event != 0xF7) && (event != 0xFF)) *running_status = event; byte1 = (*pos)[0]; byte2 = (*pos)[1]; } else { /* use running status */ byte1 = event; byte2 = (*pos)[0]; event = *running_status; (*pos)--; } /* program callback? */ if ((midi_msg_callback) && (event != 0xF0) && (event != 0xF7) && (event != 0xFF)) midi_msg_callback(event, byte1, byte2); channel = event & 0x0F; switch (event>>4) { case 0x08: /* note off */ midi_note_off(channel, byte1); (*pos) += 2; break; case 0x09: /* note on */ midi_note_on(channel, byte1, byte2, 1); (*pos) += 2; break; case 0x0A: /* note aftertouch */ (*pos) += 2; break; case 0x0B: /* control change */ process_controller(channel, byte1, byte2); (*pos) += 2; break; case 0x0C: /* program change */ midi_channel[channel].patch = byte1; if (midi_driver->raw_midi) raw_program_change(channel, byte1); (*pos) += 1; break; case 0x0D: /* channel aftertouch */ (*pos) += 1; break; case 0x0E: /* pitch bend */ midi_channel[channel].new_pitch_bend = byte1 + (byte2<<7); (*pos) += 2; break; case 0x0F: /* special event */ switch (event) { case 0xF0: /* sysex */ case 0xF7: l = parse_var_len(pos); if (midi_sysex_callback) midi_sysex_callback(*pos, l); (*pos) += l; break; case 0xF2: /* song position */ (*pos) += 2; break; case 0xF3: /* song select */ (*pos)++; break; case 0xFF: /* meta-event */ process_meta_event(pos, timer); break; default: /* the other special events don't have any data bytes, so we don't need to bother skipping past them */ break; } break; default: /* something has gone badly wrong if we ever get to here */ break; } } END_OF_STATIC_FUNCTION(process_midi_event); /* midi_player: * The core MIDI player: to be used as a timer callback. */ static void midi_player(void) { int c; long l; int active; if (!midifile) return; if (midi_semaphore) { midi_timer_speed += BPS_TO_TIMER(MIDI_TIMER_FREQUENCY); install_int_ex(midi_player, BPS_TO_TIMER(MIDI_TIMER_FREQUENCY)); return; } midi_semaphore = TRUE; _midi_tick++; midi_timers += midi_timer_speed; midi_time = midi_timers / TIMERS_PER_SECOND; do_it_all_again: for (c=0; c 0) { for (c=0; cdivisions; midi_new_speed = -1; } /* figure out how long until we need to be called again */ active = 0; midi_timer_speed = LONG_MAX; for (c=0; c 0) && (midi_pos >= midi_loop_end))) { if ((midi_loop) && (!midi_looping)) { if (midi_loop_start > 0) { remove_int(midi_player); midi_semaphore = FALSE; midi_looping = TRUE; if (midi_seek(midi_loop_start) != 0) { midi_looping = FALSE; stop_midi(); return; } midi_looping = FALSE; midi_semaphore = TRUE; goto do_it_all_again; } else { for (c=0; c<16; c++) { all_notes_off(c); all_sound_off(c); } prepare_to_play(midifile); goto do_it_all_again; } } else { stop_midi(); midi_semaphore = FALSE; return; } } /* reprogram the timer */ if (midi_timer_speed < BPS_TO_TIMER(MIDI_TIMER_FREQUENCY)) midi_timer_speed = BPS_TO_TIMER(MIDI_TIMER_FREQUENCY); if (!midi_seeking) install_int_ex(midi_player, midi_timer_speed); /* controller changes are cached and only processed here, so we can condense streams of controller data into just a few voice updates */ update_controllers(); /* and deal with any notes that are still waiting to be played */ for (c=0; c= 0) midi_note_on(midi_waiting[c].channel, midi_waiting[c].note, midi_waiting[c].volume, 0); midi_semaphore = FALSE; } END_OF_STATIC_FUNCTION(midi_player); /* midi_init: * Sets up the MIDI player ready for use. Returns non-zero on failure. */ static int midi_init(void) { int c, c2, c3; char **argv; int argc; char buf[32], tmp[64]; midi_loaded_patches = FALSE; midi_lock_mem(); for (c=0; c<16; c++) { midi_channel[c].volume = midi_channel[c].new_volume = 128; midi_channel[c].pitch_bend = midi_channel[c].new_pitch_bend = 0x2000; for (c2=0; c2<128; c2++) for (c3=0; c3track[c].data; end = p + midi->track[c].len; running_status = 0; while (p < end) { /* work through data stream */ #ifdef ALLEGRO_BEOS /* Is there a bug in this routine, or in gcc under BeOS/x86? --PW */ { int i; for (i=1; i; i--); } #endif event = *p; if (event & 0x80) { /* regular message */ p++; if ((event != 0xF0) && (event != 0xF7) && (event != 0xFF)) running_status = event; } else /* use running status */ event = running_status; switch (event>>4) { case 0x0C: /* program change! */ patches[*p] = TRUE; p++; break; case 0x09: /* note on, is it a drum? */ if ((event & 0x0F) == 9) drums[*p] = TRUE; p += 2; break; case 0x08: /* note off */ case 0x0A: /* note aftertouch */ case 0x0B: /* control change */ case 0x0E: /* pitch bend */ p += 2; break; case 0x0D: /* channel aftertouch */ p += 1; break; case 0x0F: /* special event */ switch (event) { case 0xF0: /* sysex */ case 0xF7: l = parse_var_len((AL_CONST unsigned char**) &p); p += l; break; case 0xF2: /* song position */ p += 2; break; case 0xF3: /* song select */ p++; break; case 0xFF: /* meta-event */ p++; l = parse_var_len((AL_CONST unsigned char**) &p); p += l; break; default: /* the other special events don't have any data bytes, so we don't need to bother skipping past them */ break; } break; default: /* something has gone badly wrong if we ever get to here */ break; } if (p < end) /* skip time offset */ parse_var_len((AL_CONST unsigned char**) &p); } } /* tell the driver to do its stuff */ return midi_driver->load_patches(patches, drums); } /* prepare_to_play: * Sets up all the global variables needed to play the specified file. */ static void prepare_to_play(MIDI *midi) { int c; ASSERT(midi); for (c=0; c<16; c++) reset_controllers(c); update_controllers(); midifile = midi; midi_pos = 0; midi_timers = 0; midi_time = 0; midi_pos_counter = 0; midi_speed = TIMERS_PER_SECOND / 2 / midifile->divisions; /* 120 bpm */ midi_new_speed = -1; midi_pos_speed = midi_speed * midifile->divisions; midi_timer_speed = 0; midi_seeking = 0; midi_looping = 0; for (c=0; c<16; c++) { midi_channel[c].patch = 0; if (midi_driver->raw_midi) raw_program_change(c, 0); } for (c=0; ctrack[c].data) { midi_track[c].pos = midi->track[c].data; midi_track[c].timer = parse_var_len((AL_CONST unsigned char**) &midi_track[c].pos); midi_track[c].timer *= midi_speed; } else { midi_track[c].pos = NULL; midi_track[c].timer = LONG_MAX; } midi_track[c].running_status = 0; } } END_OF_STATIC_FUNCTION(prepare_to_play); /* play_midi: * Starts playing the specified MIDI file. If loop is set, the MIDI file * will be repeated until replaced with something else, otherwise it will * stop at the end of the file. Passing a NULL MIDI file will stop whatever * music is currently playing: allegro.h defines the macro stop_midi() to * be play_midi(NULL, FALSE); Returns non-zero if an error occurs (this * may happen if a patch-caching wavetable driver is unable to load the * required samples). */ int play_midi(MIDI *midi, int loop) { int c; remove_int(midi_player); for (c=0; c<16; c++) { all_notes_off(c); all_sound_off(c); } if (midi) { if (!midi_loaded_patches) if (load_patches(midi) != 0) return -1; midi_loop = loop; midi_loop_start = -1; midi_loop_end = -1; prepare_to_play(midi); /* arbitrary speed, midi_player() will adjust it */ install_int(midi_player, 20); } else { midifile = NULL; if (midi_pos > 0) midi_pos = -midi_pos; else if (midi_pos == 0) midi_pos = -1; } return 0; } END_OF_FUNCTION(play_midi); /* play_looped_midi: * Like play_midi(), but the file loops from the specified end position * back to the specified start position (the end position can be -1 to * indicate the end of the file). */ int play_looped_midi(MIDI *midi, int loop_start, int loop_end) { if (play_midi(midi, TRUE) != 0) return -1; midi_loop_start = loop_start; midi_loop_end = loop_end; return 0; } /* stop_midi: * Stops whatever MIDI file is currently playing. */ void stop_midi(void) { play_midi(NULL, FALSE); } END_OF_FUNCTION(stop_midi); /* midi_pause: * Pauses the currently playing MIDI file. */ void midi_pause(void) { int c; if (!midifile) return; remove_int(midi_player); for (c=0; c<16; c++) { all_notes_off(c); all_sound_off(c); } } END_OF_FUNCTION(midi_pause); /* midi_resume: * Resumes playing a paused MIDI file. */ void midi_resume(void) { if (!midifile) return; install_int_ex(midi_player, midi_timer_speed); } END_OF_FUNCTION(midi_resume); /* midi_seek: * Seeks to the given midi_pos in the current MIDI file. If the target * is earlier in the file than the current midi_pos it seeks from the * beginning; otherwise it seeks from the current position. Returns zero * if successful, non-zero if it hit the end of the file (1 means it * stopped playing, 2 means it looped back to the start). */ int midi_seek(int target) { int old_midi_loop; MIDI *old_midifile; MIDI_DRIVER *old_driver; int old_patch[16]; int old_volume[16]; int old_pan[16]; int old_pitch_bend[16]; int c; if (!midifile) return -1; /* first stop the player */ midi_pause(); /* store current settings */ for (c=0; c<16; c++) { old_patch[c] = midi_channel[c].patch; old_volume[c] = midi_channel[c].volume; old_pan[c] = midi_channel[c].pan; old_pitch_bend[c] = midi_channel[c].pitch_bend; } /* save some variables and give temporary values */ old_driver = midi_driver; midi_driver = &_midi_none; old_midi_loop = midi_loop; midi_loop = 0; old_midifile = midifile; /* set flag to tell midi_player not to reinstall itself */ midi_seeking = 1; /* are we seeking backwards? If so, skip back to the start of the file */ if (target <= midi_pos) prepare_to_play(midifile); /* now sit back and let midi_player get to the position */ while ((midi_pos < target) && (midi_pos >= 0)) { int mmpc = midi_pos_counter; int mmp = midi_pos; mmpc -= midi_timer_speed; while (mmpc <= 0) { mmpc += midi_pos_speed; mmp++; } if (mmp >= target) break; midi_player(); } /* restore previously saved variables */ midi_loop = old_midi_loop; midi_driver = old_driver; midi_seeking = 0; if (midi_pos >= 0) { /* refresh the driver with any changed parameters */ if (midi_driver->raw_midi) { for (c=0; c<16; c++) { /* program change (this sets the volume as well) */ if ((midi_channel[c].patch != old_patch[c]) || (midi_channel[c].volume != old_volume[c])) raw_program_change(c, midi_channel[c].patch); /* pan */ if (midi_channel[c].pan != old_pan[c]) { midi_driver->raw_midi(0xB0+c); midi_driver->raw_midi(10); midi_driver->raw_midi(midi_channel[c].pan); } /* pitch bend */ if (midi_channel[c].pitch_bend != old_pitch_bend[c]) { midi_driver->raw_midi(0xE0+c); midi_driver->raw_midi(midi_channel[c].pitch_bend & 0x7F); midi_driver->raw_midi(midi_channel[c].pitch_bend >> 7); } } } /* if we didn't hit the end of the file, continue playing */ if (!midi_looping) install_int(midi_player, 20); return 0; } if ((midi_loop) && (!midi_looping)) { /* was file looped? */ prepare_to_play(old_midifile); install_int(midi_player, 20); return 2; /* seek past EOF => file restarted */ } return 1; /* seek past EOF => file stopped */ } END_OF_FUNCTION(midi_seek); /* get_midi_length: * Returns the length, in seconds, of the specified midi. This will stop any * currently playing midi. Don't call it too often, since it simulates playing * all of the midi to get the time even if the midi contains tempo changes. */ int get_midi_length(MIDI *midi) { play_midi(midi, 0); while (midi_pos < 0); /* Without this, midi_seek won't work. */ midi_seek(INT_MAX); return midi_time; } /* midi_out: * Inserts MIDI command bytes into the output stream, in realtime. */ void midi_out(unsigned char *data, int length) { unsigned char *pos = data; unsigned char running_status = 0; long timer = 0; ASSERT(data); midi_semaphore = TRUE; _midi_tick++; while (pos < data+length) process_midi_event((AL_CONST unsigned char**) &pos, &running_status, &timer); update_controllers(); midi_semaphore = FALSE; } /* load_midi_patches: * Tells the MIDI driver to preload the entire sample set. */ int load_midi_patches(void) { char patches[128], drums[128]; int c, ret; for (c=0; c<128; c++) patches[c] = drums[c] = TRUE; midi_semaphore = TRUE; ret = midi_driver->load_patches(patches, drums); midi_semaphore = FALSE; midi_loaded_patches = TRUE; return ret; } /* midi_lock_mem: * Locks all the memory that the midi player touches inside the timer * interrupt handler (which is most of it). */ static void midi_lock_mem(void) { LOCK_VARIABLE(midi_pos); LOCK_VARIABLE(midi_time); LOCK_VARIABLE(midi_timers); LOCK_VARIABLE(midi_pos_counter); LOCK_VARIABLE(_midi_tick); LOCK_VARIABLE(midifile); LOCK_VARIABLE(midi_semaphore); LOCK_VARIABLE(midi_loop); LOCK_VARIABLE(midi_loop_start); LOCK_VARIABLE(midi_loop_end); LOCK_VARIABLE(midi_timer_speed); LOCK_VARIABLE(midi_pos_speed); LOCK_VARIABLE(midi_speed); LOCK_VARIABLE(midi_new_speed); LOCK_VARIABLE(old_midi_volume); LOCK_VARIABLE(midi_alloc_channel); LOCK_VARIABLE(midi_alloc_note); LOCK_VARIABLE(midi_alloc_vol); LOCK_VARIABLE(midi_track); LOCK_VARIABLE(midi_voice); LOCK_VARIABLE(midi_channel); LOCK_VARIABLE(midi_waiting); LOCK_VARIABLE(patch_table); LOCK_VARIABLE(midi_msg_callback); LOCK_VARIABLE(midi_meta_callback); LOCK_VARIABLE(midi_sysex_callback); LOCK_VARIABLE(midi_seeking); LOCK_VARIABLE(midi_looping); LOCK_FUNCTION(parse_var_len); LOCK_FUNCTION(raw_program_change); LOCK_FUNCTION(midi_note_off); LOCK_FUNCTION(_midi_allocate_voice); LOCK_FUNCTION(midi_note_on); LOCK_FUNCTION(all_notes_off); LOCK_FUNCTION(all_sound_off); LOCK_FUNCTION(reset_controllers); LOCK_FUNCTION(update_controllers); LOCK_FUNCTION(process_controller); LOCK_FUNCTION(process_meta_event); LOCK_FUNCTION(process_midi_event); LOCK_FUNCTION(midi_player); LOCK_FUNCTION(prepare_to_play); LOCK_FUNCTION(play_midi); LOCK_FUNCTION(stop_midi); LOCK_FUNCTION(midi_pause); LOCK_FUNCTION(midi_resume); LOCK_FUNCTION(midi_seek); } /* midi_constructor: * Register my functions with the code in sound.c. */ #ifdef ALLEGRO_USE_CONSTRUCTOR CONSTRUCTOR_FUNCTION(void _midi_constructor(void)); #endif static struct _AL_LINKER_MIDI midi_linker = { midi_init, midi_exit }; void _midi_constructor(void) { _al_linker_midi = &midi_linker; }