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
+