From f47f87cd5f6f6fa2f6dde1c1e94018459f8e41d6 Mon Sep 17 00:00:00 2001 From: willem Date: Fri, 10 Dec 2021 17:14:26 +0100 Subject: [PATCH] restored midi generators --- src/AudioGeneratorMIDI.cpp | 639 +++++++++++++++++++++++++++++++++++++ src/AudioGeneratorMIDI.h | 181 +++++++++++ 2 files changed, 820 insertions(+) create mode 100644 src/AudioGeneratorMIDI.cpp create mode 100644 src/AudioGeneratorMIDI.h diff --git a/src/AudioGeneratorMIDI.cpp b/src/AudioGeneratorMIDI.cpp new file mode 100644 index 0000000..e3be2b2 --- /dev/null +++ b/src/AudioGeneratorMIDI.cpp @@ -0,0 +1,639 @@ +/* + AudioGeneratorMIDI + Audio output generator that plays MIDI files using a SF2 SoundFont + + Copyright (C) 2017 Earle F. Philhower, III + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +/* + The MIDI processing engine is a heavily modified version of MIDITONES, + by Len Shustek, https://github.com/LenShustek/miditones . + Whereas MIDITONES original simply parsed a file beforehand to a byte + stream to be played by another program, this does the parsing and + playback in real-time. + + Here's his original header/readme w/MIT license, which is subsumed by the + GPL license of the ESP8266Audio project. +*/ + +/*************************************************************************** + + MIDITONES: Convert a MIDI file into a simple bytestream of notes + + ------------------------------------------------------------------------- + The MIT License (MIT) + Copyright (c) 2011,2013,2015,2016, Len Shustek + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR + IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +**************************************************************************/ + + +#include "AudioGeneratorMIDI.h" + +#pragma GCC optimize ("O3") + +#define TSF_NO_STDIO +#define TSF_IMPLEMENTATION +#include "libtinysoundfont/tsf.h" + +/**************** utility routines **********************/ + +/* announce a fatal MIDI file format error */ + +void AudioGeneratorMIDI::midi_error(const char *msg, int curpos) +{ + cb.st(curpos, msg); +#if 0 + int ptr; + audioLogger->printf("---> MIDI file error at position %04X (%d): %s\n", (uint16_t) curpos, (uint16_t) curpos, msg); + /* print some bytes surrounding the error */ + ptr = curpos - 16; + if (ptr < 0) ptr = 0; + buffer.seek( buffer.data, ptr ); + for (int i = 0; i < 32; i++) { + char c; + buffer.read (buffer.data, &c, 1); + audioLogger->printf((ptr + i) == curpos ? " [%02X] " : "%02X ", (int) c & 0xff); + } + audioLogger->printf("\n"); +#endif + running = false; +} + +/* check that we have a specified number of bytes left in the buffer */ + +void AudioGeneratorMIDI::chk_bufdata (int ptr, unsigned long int len) { + if ((unsigned) (ptr + len) > buflen) + midi_error ("data missing", ptr); +} + +/* fetch big-endian numbers */ + +uint16_t AudioGeneratorMIDI::rev_short (uint16_t val) { + return ((val & 0xff) << 8) | ((val >> 8) & 0xff); +} + +uint32_t AudioGeneratorMIDI::rev_long (uint32_t val) { + return (((rev_short ((uint16_t) val) & 0xffff) << 16) | + (rev_short ((uint16_t) (val >> 16)) & 0xffff)); +} + +/************** process the MIDI file header *****************/ + +void AudioGeneratorMIDI::process_header (void) { + struct midi_header hdr; + unsigned int time_division; + + chk_bufdata (hdrptr, sizeof (struct midi_header)); + buffer.seek (buffer.data, hdrptr); + buffer.read (buffer.data, &hdr, sizeof (hdr)); + if (!charcmp ((char *) hdr.MThd, "MThd")) + midi_error ("Missing 'MThd'", hdrptr); + num_tracks = rev_short (hdr.number_of_tracks); + time_division = rev_short (hdr.time_division); + if (time_division < 0x8000) + ticks_per_beat = time_division; + else + ticks_per_beat = ((time_division >> 8) & 0x7f) /* SMTE frames/sec */ *(time_division & 0xff); /* ticks/SMTE frame */ + hdrptr += rev_long (hdr.header_size) + 8; /* point past header to track header, presumably. */ + return; +} + + +/**************** Process a MIDI track header *******************/ + +void AudioGeneratorMIDI::start_track (int tracknum) { + struct track_header hdr; + unsigned long tracklen; + + chk_bufdata (hdrptr, sizeof (struct track_header)); + buffer.seek (buffer.data, hdrptr); + buffer.read (buffer.data, &hdr, sizeof (hdr)); + if (!charcmp ((char *) (hdr.MTrk), "MTrk")) + midi_error ("Missing 'MTrk'", hdrptr); + tracklen = rev_long (hdr.track_size); + hdrptr += sizeof (struct track_header); /* point past header */ + chk_bufdata (hdrptr, tracklen); + track[tracknum].trkptr = hdrptr; + hdrptr += tracklen; /* point to the start of the next track */ + track[tracknum].trkend = hdrptr; /* the point past the end of the track */ +} + +unsigned char AudioGeneratorMIDI::buffer_byte (int offset) { + unsigned char c; + buffer.seek (buffer.data, offset); + buffer.read (buffer.data, &c, 1); + return c; +} + +unsigned short AudioGeneratorMIDI::buffer_short (int offset) { + unsigned short s; + buffer.seek (buffer.data, offset); + buffer.read (buffer.data, &s, sizeof (short)); + return s; +} + +unsigned int AudioGeneratorMIDI::buffer_int32 (int offset) { + uint32_t i; + buffer.seek (buffer.data, offset); + buffer.read (buffer.data, &i, sizeof (i)); + return i; +} + +/* Get a MIDI-style variable-length integer */ + +unsigned long AudioGeneratorMIDI::get_varlen (int *ptr) { + /* Get a 1-4 byte variable-length value and adjust the pointer past it. + These are a succession of 7-bit values with a MSB bit of zero marking the end */ + + unsigned long val; + int i, byte; + + val = 0; + for (i = 0; i < 4; ++i) { + byte = buffer_byte ((*ptr)++); + val = (val << 7) | (byte & 0x7f); + if (!(byte & 0x80)) + return val; + } + return val; +} + + +/*************** Process the MIDI track data ***************************/ + +/* Skip in the track for the next "note on", "note off" or "set tempo" command, + then record that information in the track status block and return. */ + +void AudioGeneratorMIDI::find_note (int tracknum) { + unsigned long int delta_time; + int event, chan; + int note, velocity, controller, pressure, pitchbend, instrument; + int meta_cmd, meta_length; + unsigned long int sysex_length; + struct track_status *t; + const char *tag; + + /* process events */ + + t = &track[tracknum]; /* our track status structure */ + while (t->trkptr < t->trkend) { + + delta_time = get_varlen (&t->trkptr); + t->time += delta_time; + if (buffer_byte (t->trkptr) < 0x80) + event = t->last_event; /* using "running status": same event as before */ + else { /* otherwise get new "status" (event type) */ + event = buffer_byte (t->trkptr++); + } + if (event == 0xff) { /* meta-event */ + meta_cmd = buffer_byte (t->trkptr++); + meta_length = get_varlen(&t->trkptr); + switch (meta_cmd) { + case 0x00: + break; + case 0x01: + tag = "description"; + goto show_text; + case 0x02: + tag = "copyright"; + goto show_text; + case 0x03: + tag = "track name"; + goto show_text; + case 0x04: + tag = "instrument name"; + goto show_text; + case 0x05: + tag = "lyric"; + goto show_text; + case 0x06: + tag = "marked point"; + goto show_text; + case 0x07: + tag = "cue point"; +show_text: + break; + case 0x20: + break; + case 0x2f: + break; + case 0x51: /* tempo: 3 byte big-endian integer! */ + t->cmd = CMD_TEMPO; + t->tempo = rev_long (buffer_int32 (t->trkptr - 1)) & 0xffffffL; + t->trkptr += meta_length; + return; + case 0x54: + break; + case 0x58: + break; + case 0x59: + break; + case 0x7f: + tag = "sequencer data"; + goto show_hex; + default: /* unknown meta command */ + tag = "???"; +show_hex: + break; + } + t->trkptr += meta_length; + } + + else if (event < 0x80) + midi_error ("Unknown MIDI event type", t->trkptr); + + else { + if (event < 0xf0) + t->last_event = event; // remember "running status" if not meta or sysex event + chan = event & 0xf; + t->chan = chan; + switch (event >> 4) { + case 0x8: + t->note = buffer_byte (t->trkptr++); + velocity = buffer_byte (t->trkptr++); +note_off: + t->cmd = CMD_STOPNOTE; + return; /* stop processing and return */ + case 0x9: + t->note = buffer_byte (t->trkptr++); + velocity = buffer_byte (t->trkptr++); + if (velocity == 0) /* some scores use note-on with zero velocity for off! */ + goto note_off; + t->velocity = velocity; + t->cmd = CMD_PLAYNOTE; + return; /* stop processing and return */ + case 0xa: + note = buffer_byte (t->trkptr++); + velocity = buffer_byte (t->trkptr++); + break; + case 0xb: + controller = buffer_byte (t->trkptr++); + velocity = buffer_byte (t->trkptr++); + break; + case 0xc: + instrument = buffer_byte (t->trkptr++); + midi_chan_instrument[chan] = instrument; // record new instrument for this channel + break; + case 0xd: + pressure = buffer_byte (t->trkptr++); + break; + case 0xe: + pitchbend = buffer_byte (t->trkptr) | (buffer_byte (t->trkptr + 1) << 7); + t->trkptr += 2; + break; + case 0xf: + sysex_length = get_varlen (&t->trkptr); + t->trkptr += sysex_length; + break; + default: + midi_error ("Unknown MIDI command", t->trkptr); + } + } + } + t->cmd = CMD_TRACKDONE; /* no more notes to process */ + ++tracks_done; + + // Remove unused warnings..maybe some day we'll look at these + (void)note; + (void)controller; + (void)pressure; + (void)pitchbend; + (void)tag; +} + + +// Open file, parse headers, get ready tio process MIDI +void AudioGeneratorMIDI::PrepareMIDI(AudioFileSource *src) +{ + MakeStreamFromAFS(src, &afsMIDI); + tsf_stream_wrap_cached(&afsMIDI, 32, 64, &buffer); + buflen = buffer.size (buffer.data); + + /* process the MIDI file header */ + + hdrptr = buffer.tell (buffer.data); /* pointer to file and track headers */ + process_header (); + printf (" Processing %d tracks.\n", num_tracks); + if (num_tracks > MAX_TRACKS) + midi_error ("Too many tracks", buffer.tell (buffer.data)); + + /* initialize processing of all the tracks */ + + for (tracknum = 0; tracknum < num_tracks; ++tracknum) { + start_track (tracknum); /* process the track header */ + find_note (tracknum); /* position to the first note on/off */ + } + + notes_skipped = 0; + tracknum = 0; + earliest_tracknum = 0; + earliest_time = 0; +} + +// Parses the note on/offs until we are ready to render some more samples. Then return the +// total number of samples to render before we need to be called again +int AudioGeneratorMIDI::PlayMIDI() +{ + /* Continue processing all tracks, in an order based on the simulated time. + This is not unlike multiway merging used for tape sorting algoritms in the 50's! */ + + do { /* while there are still track notes to process */ + static struct track_status *trk; + static struct tonegen_status *tg; + static int tgnum; + static int count_tracks; + static unsigned long delta_time, delta_msec; + + /* Find the track with the earliest event time, + and output a delay command if time has advanced. + + A potential improvement: If there are multiple tracks with the same time, + first do the ones with STOPNOTE as the next command, if any. That would + help avoid running out of tone generators. In practice, though, most MIDI + files do all the STOPNOTEs first anyway, so it won't have much effect. + */ + + earliest_time = 0x7fffffff; + + /* Usually we start with the track after the one we did last time (tracknum), + so that if we run out of tone generators, we have been fair to all the tracks. + The alternate "strategy1" says we always start with track 0, which means + that we favor early tracks over later ones when there aren't enough tone generators. + */ + + count_tracks = num_tracks; + do { + if (++tracknum >= num_tracks) + tracknum = 0; + trk = &track[tracknum]; + if (trk->cmd != CMD_TRACKDONE && trk->time < earliest_time) { + earliest_time = trk->time; + earliest_tracknum = tracknum; + } + } while (--count_tracks); + + tracknum = earliest_tracknum; /* the track we picked */ + trk = &track[tracknum]; + if (earliest_time < timenow) + midi_error ("INTERNAL: time went backwards", trk->trkptr); + + /* If time has advanced, output a "delay" command */ + + delta_time = earliest_time - timenow; + if (delta_time) { + /* Convert ticks to milliseconds based on the current tempo */ + unsigned long long temp; + temp = ((unsigned long long) delta_time * tempo) / ticks_per_beat; + delta_msec = temp / 1000; // get around LCC compiler bug + if (delta_msec > 0x7fff) + midi_error ("INTERNAL: time delta too big", trk->trkptr); + int samples = (((int) delta_msec) * freq) / 1000; + timenow = earliest_time; + return samples; + } + timenow = earliest_time; + + /* If this track event is "set tempo", just change the global tempo. + That affects how we generate "delay" commands. */ + + if (trk->cmd == CMD_TEMPO) { + tempo = trk->tempo; + find_note (tracknum); + } + + /* If this track event is "stop note", process it and all subsequent "stop notes" for this track + that are happening at the same time. Doing so frees up as many tone generators as possible. */ + + else if (trk->cmd == CMD_STOPNOTE) + do { + // stop a note + for (tgnum = 0; tgnum < num_tonegens; ++tgnum) { /* find which generator is playing it */ + tg = &tonegen[tgnum]; + if (tg->playing && tg->track == tracknum && tg->note == trk->note) { + tsf_note_off (g_tsf, tg->instrument, tg->note); + tg->playing = false; + trk->tonegens[tgnum] = false; + } + } + find_note (tracknum); // use up the note + } while (trk->cmd == CMD_STOPNOTE && trk->time == timenow); + + /* If this track event is "start note", process only it. + Don't do more than one, so we allow other tracks their chance at grabbing tone generators. */ + + else if (trk->cmd == CMD_PLAYNOTE) { + bool foundgen = false; + /* if not, then try for any free tone generator */ + if (!foundgen) + for (tgnum = 0; tgnum < num_tonegens; ++tgnum) { + tg = &tonegen[tgnum]; + if (!tg->playing) { + foundgen = true; + break; + } + } + if (foundgen) { + if (tgnum + 1 > num_tonegens_used) + num_tonegens_used = tgnum + 1; + tg->playing = true; + tg->track = tracknum; + tg->note = trk->note; + trk->tonegens[tgnum] = true; + trk->preferred_tonegen = tgnum; + if (tg->instrument != midi_chan_instrument[trk->chan]) { /* new instrument for this generator */ + tg->instrument = midi_chan_instrument[trk->chan]; + } + tsf_note_on (g_tsf, tg->instrument, tg->note, trk->velocity / 127.0); // velocity = 0...127 + } else { + ++notes_skipped; + } + find_note (tracknum); // use up the note + } + } + while (tracks_done < num_tracks); + return -1; // EOF +} + + +void AudioGeneratorMIDI::StopMIDI() +{ + + buffer.close(buffer.data); + tsf_close(g_tsf); + printf (" %s %d tone generators were used.\n", + num_tonegens_used < num_tonegens ? "Only" : "All", num_tonegens_used); + if (notes_skipped) + printf + (" %d notes were skipped because there weren't enough tone generators.\n", notes_skipped); + + printf (" Done.\n"); +} + + +bool AudioGeneratorMIDI::begin(AudioFileSource *src, AudioOutput *out) +{ + // Clear out status variables + for (int i=0; iSetRate( freq )) return false; + if (!out->SetBitsPerSample( 16 )) return false; + if (!out->SetChannels( 1 )) return false; + if (!out->begin()) return false; + + output = out; + file = src; + + running = true; + + PrepareMIDI(src); + + samplesToPlay = 0; + numSamplesRendered = 0; + sentSamplesRendered = 0; + + sawEOF = false; + return running; +} + + +bool AudioGeneratorMIDI::loop() +{ + static int c = 0; + + if (!running) goto done; // Nothing to do here! + + // First, try and push in the stored sample. If we can't, then punt and try later + if (!output->ConsumeSample(lastSample)) goto done; // Can't send, but no error detected + + // Try and stuff the buffer one sample at a time + do { + c++; + if (c%44100 == 0) yield(); + +play: + + if (sentSamplesRendered < numSamplesRendered) { + lastSample[AudioOutput::LEFTCHANNEL] = samplesRendered[sentSamplesRendered]; + lastSample[AudioOutput::RIGHTCHANNEL] = samplesRendered[sentSamplesRendered]; + sentSamplesRendered++; + } else if (samplesToPlay) { + numSamplesRendered = sizeof(samplesRendered)/sizeof(samplesRendered[0]); + if ((int)samplesToPlay < (int)(sizeof(samplesRendered)/sizeof(samplesRendered[0]))) numSamplesRendered = samplesToPlay; + tsf_render_short_fast(g_tsf, samplesRendered, numSamplesRendered, 0); + lastSample[AudioOutput::LEFTCHANNEL] = samplesRendered[0]; + lastSample[AudioOutput::RIGHTCHANNEL] = samplesRendered[0]; + sentSamplesRendered = 1; + samplesToPlay -= numSamplesRendered; + } else { + numSamplesRendered = 0; + sentSamplesRendered = 0; + if (sawEOF) { + running = false; + } else { + samplesToPlay = PlayMIDI(); + if (samplesToPlay == -1) { + sawEOF = true; + samplesToPlay = freq / 2; + } + goto play; + } + } + } while (running && output->ConsumeSample(lastSample)); + +done: + file->loop(); + output->loop(); + + return running; +} + +bool AudioGeneratorMIDI::stop() +{ + StopMIDI(); + output->stop(); + return true; +} + + +int AudioGeneratorMIDI::afs_read(void *data, void *ptr, unsigned int size) +{ + AudioFileSource *s = reinterpret_cast(data); + return s->read(ptr, size); +} + +int AudioGeneratorMIDI::afs_tell(void *data) +{ + AudioFileSource *s = reinterpret_cast(data); + return s->getPos(); +} + +int AudioGeneratorMIDI::afs_skip(void *data, unsigned int count) +{ + AudioFileSource *s = reinterpret_cast(data); + return s->seek(count, SEEK_CUR); +} + +int AudioGeneratorMIDI::afs_seek(void *data, unsigned int pos) +{ + AudioFileSource *s = reinterpret_cast(data); + return s->seek(pos, SEEK_SET); +} + +int AudioGeneratorMIDI::afs_close(void *data) +{ + AudioFileSource *s = reinterpret_cast(data); + return s->close(); +} + +int AudioGeneratorMIDI::afs_size(void *data) +{ + AudioFileSource *s = reinterpret_cast(data); + return s->getSize(); +} + +void AudioGeneratorMIDI::MakeStreamFromAFS(AudioFileSource *src, tsf_stream *afs) +{ + afs->data = reinterpret_cast(src); + afs->read = &afs_read; + afs->tell = &afs_tell; + afs->skip = &afs_skip; + afs->seek = &afs_seek; + afs->close = &afs_close; + afs->size = &afs_size; +} + diff --git a/src/AudioGeneratorMIDI.h b/src/AudioGeneratorMIDI.h new file mode 100644 index 0000000..8a1ac3f --- /dev/null +++ b/src/AudioGeneratorMIDI.h @@ -0,0 +1,181 @@ +/* + AudioGeneratorMIDI + Audio output generator that plays MIDI files using a SF2 SoundFont + + Copyright (C) 2017 Earle F. Philhower, III + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef _AUDIOGENERATORMIDI_H +#define _AUDIOGENERATORMIDI_H + +#include "AudioGenerator.h" + +#define TSF_NO_STDIO +#include "libtinysoundfont/tsf.h" + +class AudioGeneratorMIDI : public AudioGenerator +{ + public: + AudioGeneratorMIDI() { freq=44100; running = false; }; + virtual ~AudioGeneratorMIDI() override {}; + bool SetSoundfont(AudioFileSource *newsf2) { + if (isRunning()) return false; + sf2 = newsf2; + MakeStreamFromAFS(sf2, &afsSF2); + return true; + } + bool SetSampleRate(int newfreq) { + if (isRunning()) return false; + freq = newfreq; + return true; + } + virtual bool begin(AudioFileSource *mid, AudioOutput *output) override; + virtual bool loop() override; + virtual bool stop() override; + virtual bool isRunning() override { return running; }; + + private: + int freq; + tsf *g_tsf; + struct tsf_stream buffer; + struct tsf_stream afsMIDI; + struct tsf_stream afsSF2; + AudioFileSource *sf2; + AudioFileSource *midi; + + protected: + struct midi_header { + int8_t MThd[4]; + uint32_t header_size; + uint16_t format_type; + uint16_t number_of_tracks; + uint16_t time_division; + }; + + struct track_header { + int8_t MTrk[4]; + uint32_t track_size; + }; + + enum { MAX_TONEGENS = 32, /* max tone generators: tones we can play simultaneously */ + MAX_TRACKS = 24 + }; /* max number of MIDI tracks we will process */ + + int hdrptr; + unsigned long buflen; + int num_tracks; + int tracks_done = 0; + int num_tonegens = MAX_TONEGENS; + int num_tonegens_used = 0; + unsigned int ticks_per_beat = 240; + unsigned long timenow = 0; + unsigned long tempo; /* current tempo in usec/qnote */ + // State needed for PlayMID() + int notes_skipped = 0; + int tracknum = 0; + int earliest_tracknum = 0; + unsigned long earliest_time = 0; + + struct tonegen_status { /* current status of a tone generator */ + bool playing; /* is it playing? */ + char track; /* if so, which track is the note from? */ + char note; /* what note is playing? */ + char instrument; /* what instrument? */ + } tonegen[MAX_TONEGENS]; + + struct track_status { /* current processing point of a MIDI track */ + int trkptr; /* ptr to the next note change */ + int trkend; /* ptr past the end of the track */ + unsigned long time; /* what time we're at in the score */ + unsigned long tempo; /* the tempo last set, in usec per qnote */ + unsigned int preferred_tonegen; /* for strategy2, try to use this generator */ + unsigned char cmd; /* CMD_xxxx next to do */ + unsigned char note; /* for which note */ + unsigned char chan; /* from which channel it was */ + unsigned char velocity; /* the current volume */ + unsigned char last_event; /* the last event, for MIDI's "running status" */ + bool tonegens[MAX_TONEGENS]; /* which tone generators our notes are playing on */ + } track[MAX_TRACKS]; + + int midi_chan_instrument[16]; /* which instrument is currently being played on each channel */ + + /* output bytestream commands, which are also stored in track_status.cmd */ + enum { CMD_PLAYNOTE = 0x90, /* play a note: low nibble is generator #, note is next byte */ + CMD_STOPNOTE = 0x80, /* stop a note: low nibble is generator # */ + CMD_INSTRUMENT = 0xc0, /* change instrument; low nibble is generator #, instrument is next byte */ + CMD_RESTART = 0xe0, /* restart the score from the beginning */ + CMD_STOP = 0xf0, /* stop playing */ + CMD_TEMPO = 0xFE, /* tempo in usec per quarter note ("beat") */ + CMD_TRACKDONE = 0xFF + }; /* no more data left in this track */ + + + + /* portable string length */ + int strlength (const char *str) { + int i; + for (i = 0; str[i] != '\0'; ++i); + return i; + } + + + /* match a constant character sequence */ + + int charcmp (const char *buf, const char *match) { + int len, i; + len = strlength (match); + for (i = 0; i < len; ++i) + if (buf[i] != match[i]) + return 0; + return 1; + } + + unsigned char buffer_byte (int offset); + unsigned short buffer_short (int offset); + unsigned int buffer_int32 (int offset); + + void midi_error (const char *msg, int curpos); + void chk_bufdata (int ptr, unsigned long int len); + uint16_t rev_short (uint16_t val); + uint32_t rev_long (uint32_t val); + void process_header (void); + void start_track (int tracknum); + + unsigned long get_varlen (int *ptr); + void find_note (int tracknum); + void PrepareMIDI(AudioFileSource *src); + int PlayMIDI(); + void StopMIDI(); + + // tsf_stream <-> AudioFileSource + static int afs_read(void *data, void *ptr, unsigned int size); + static int afs_tell(void *data); + static int afs_skip(void *data, unsigned int count); + static int afs_seek(void *data, unsigned int pos); + static int afs_close(void *data); + static int afs_size(void *data); + void MakeStreamFromAFS(AudioFileSource *src, tsf_stream *afs); + + int samplesToPlay; + bool sawEOF; + int numSamplesRendered; + int sentSamplesRendered ; + short samplesRendered[256]; +}; + + +#endif +