fix bug that eliminated duplicate notes unintentionally; add -noduplicates option

master
Len Shustek 3 years ago
parent 3a0d14ce12
commit 5d5acb2e84
  1. 39
      README.txt
  2. 155
      miditones.c
  3. BIN
      miditones.exe
  4. 36
      miditones_scroll.c
  5. BIN
      miditones_scroll.exe

@ -44,7 +44,7 @@
(from MIDI channel 10) are generated from longer sampled waveforms of a complete
instrument strike. Each generator's volume is independently adjusted according to
the MIDI velocity of the note being played before all channels are mixed.
www.github.com/LenShustek/Playtune_Teensy
The fifth version is for the Teensy 3.1/3.2, and uses the four Periodic Interval
Timers in the Cortex M4 processor to support up to 4 simultaneous notes.
@ -52,7 +52,6 @@
(This was written to experiment with multi-channel multi-Tesla Coil music playing,
where I use Flexible Timer Module FTM0 for generating precise one-shot pulses.
But I ultimately switched to the polling version to play more simultaneous notes.)
www.github.com/LenShustek/Playtune_Teensy
www.github.com/LenShustek/ATtiny-playtune
This is a much simplified version that fits, with a small song, into an ATtiny
@ -64,7 +63,7 @@
(Imagine what you can do with the $1 8-pin ATtiny85 with a whopping 8K!)
MIDITONES may also prove useful for other simple music synthesizers. There are
various forks of the code, and the Playtune players, on Githib.
various forks of this code, and of the Playtune players, on Githib.
*** THE PROGRAM
@ -136,8 +135,8 @@
The following are lesser-used command-line options:
-c=n Only process the channel numbers whose bits are on in the number "n".
For example, -c3 means "only process channels 0 and 1". In addition to decimal,
"n" can be also specified in hex using a 0x prefix.
For example, -c3 means "only process channels 0 and 1". In addition to
decimal, "n" can be also specified in hex using a 0x prefix.
-dp Generate Arduino IDE-dependent C code that uses PROGMEM for the bytestream.
@ -151,10 +150,10 @@
-n=x Put about "x" items on each line of the C file output
-p Only parse the MIDI file, and don't generate an output file.
Tracks are processed sequentially instead of being merged into chronological order.
This is mostly useful for debugging MIDI file parsing problems.
Tracks are processed sequentially instead of being merged into chronological
order. This is mostly useful for debugging MIDI file parsing problems.
-pi Ignore notes in the MIDI percussion track 9 (also called 10 by some)
-pi Ignore notes in the MIDI percussion track 9 (also called 10 in some documents)
-r Terminate the output file with a "restart" command instead of a "stop" command.
@ -164,14 +163,18 @@
-h Give command-line help.
-showskipped Display information to the console each note that had to be skipped
because there weren't enough tone generators.
-showskipped Display information to the console about each note that had to be
skipped because there weren't enough tone generators.
-noduplicates Remove identical notes played on identical instruments for the same time
that come from different tracks. This can reduce the number of tone
generators needed, and make the file smaller.
-delaymin=x Don't generate delays less than x milliseconds long, to reduce the number
of "delay" commands and thus make the bytestream smaller, at the expense of
moving notes slightly. The deficits are accumulated and eventually used,
so that there is no loss of synchronization in the long term.
The default is 0, which means timing is exact to the millisecond.
The default is 0, which means note timing is exact to the millisecond.
-releasetime=x Stop each note x milliseconds before it is supposed to end. This results
in better sound separation between notes. It might also allow more notes to
@ -187,10 +190,10 @@
too short. (Only valid with -v.)
-attacknotemax=x Notes larger than x milliseconds won't have the attack/sustain profile
applied. That allows sustained organ-like pedaling.
applied. That allows sustained organ-like pedaling. (Only valid with -v.)
-sustainlevel=p The volume level during the sustain phase is p percent of the starting
note volume. (Only valid with -v.)
note volume. The default is 50. (Only valid with -v.)
-scorename Use <basefilename> as the name of the score in the generated C code
instead of "score", and name the file <basefilename>.h instead of
@ -203,7 +206,7 @@
*** THE SCORE BYTESTREAM
The generated bytestream is a series of commands to turn notes on and off,
The generated bytestream is a series of commands that turn notes on and off,
change instruments, and request a delay until the next event time.
Here are the details, with numbers shown in hexadecimal.
@ -218,8 +221,8 @@
8t Stop playing the note on tone generator t.
Ct ii Change tone generator t to play instrument ii from now on. This will only
be generated if the -i option was given.
Ct ii Change tone generator t to play instrument ii from now on. This will
only be generated if the -i option was given.
F0 End of score; stop playing.
@ -250,7 +253,7 @@
ff2 Another byte of flags, currently undefined
tt The number (in one byte) of tone generators actually used in this music.
Any subsequent header bytes covered by the count, if present, are currently undefined
Any subsequent header bytes included in the length are currently undefined
and should be ignored by players.
Len Shustek, 2011 to 2021; see the change log.
Len Shustek, 2011 to 2021; see the change log.

@ -46,7 +46,7 @@
(from MIDI channel 10) are generated from longer sampled waveforms of a complete
instrument strike. Each generator's volume is independently adjusted according to
the MIDI velocity of the note being played before all channels are mixed.
www.github.com/LenShustek/Playtune_Teensy
The fifth version is for the Teensy 3.1/3.2, and uses the four Periodic Interval
Timers in the Cortex M4 processor to support up to 4 simultaneous notes.
@ -54,7 +54,6 @@
(This was written to experiment with multi-channel multi-Tesla Coil music playing,
where I use Flexible Timer Module FTM0 for generating precise one-shot pulses.
But I ultimately switched to the polling version to play more simultaneous notes.)
www.github.com/LenShustek/Playtune_Teensy
www.github.com/LenShustek/ATtiny-playtune
This is a much simplified version that fits, with a small song, into an ATtiny
@ -66,7 +65,7 @@
(Imagine what you can do with the $1 8-pin ATtiny85 with a whopping 8K!)
MIDITONES may also prove useful for other simple music synthesizers. There are
various forks of the code, and the Playtune players, on Githib.
various forks of this code, and of the Playtune players, on Githib.
*** THE PROGRAM
@ -138,8 +137,8 @@
The following are lesser-used command-line options:
-c=n Only process the channel numbers whose bits are on in the number "n".
For example, -c3 means "only process channels 0 and 1". In addition to decimal,
"n" can be also specified in hex using a 0x prefix.
For example, -c3 means "only process channels 0 and 1". In addition to
decimal, "n" can be also specified in hex using a 0x prefix.
-dp Generate Arduino IDE-dependent C code that uses PROGMEM for the bytestream.
@ -153,10 +152,10 @@
-n=x Put about "x" items on each line of the C file output
-p Only parse the MIDI file, and don't generate an output file.
Tracks are processed sequentially instead of being merged into chronological order.
This is mostly useful for debugging MIDI file parsing problems.
Tracks are processed sequentially instead of being merged into chronological
order. This is mostly useful for debugging MIDI file parsing problems.
-pi Ignore notes in the MIDI percussion track 9 (also called 10 by some)
-pi Ignore notes in the MIDI percussion track 9 (also called 10 in some documents)
-r Terminate the output file with a "restart" command instead of a "stop" command.
@ -166,14 +165,18 @@
-h Give command-line help.
-showskipped Display information to the console each note that had to be skipped
because there weren't enough tone generators.
-showskipped Display information to the console about each note that had to be
skipped because there weren't enough tone generators.
-noduplicates Remove identical notes played on identical instruments for the same time
that come from different tracks. This can reduce the number of tone
generators needed, and make the file smaller.
-delaymin=x Don't generate delays less than x milliseconds long, to reduce the number
of "delay" commands and thus make the bytestream smaller, at the expense of
moving notes slightly. The deficits are accumulated and eventually used,
so that there is no loss of synchronization in the long term.
The default is 0, which means timing is exact to the millisecond.
The default is 0, which means note timing is exact to the millisecond.
-releasetime=x Stop each note x milliseconds before it is supposed to end. This results
in better sound separation between notes. It might also allow more notes to
@ -189,10 +192,10 @@
too short. (Only valid with -v.)
-attacknotemax=x Notes larger than x milliseconds won't have the attack/sustain profile
applied. That allows sustained organ-like pedaling.
applied. That allows sustained organ-like pedaling. (Only valid with -v.)
-sustainlevel=p The volume level during the sustain phase is p percent of the starting
note volume. (Only valid with -v.)
note volume. The default is 50. (Only valid with -v.)
-scorename Use <basefilename> as the name of the score in the generated C code
instead of "score", and name the file <basefilename>.h instead of
@ -205,7 +208,7 @@
*** THE SCORE BYTESTREAM
The generated bytestream is a series of commands to turn notes on and off,
The generated bytestream is a series of commands that turn notes on and off,
change instruments, and request a delay until the next event time.
Here are the details, with numbers shown in hexadecimal.
@ -220,8 +223,8 @@
8t Stop playing the note on tone generator t.
Ct ii Change tone generator t to play instrument ii from now on. This will only
be generated if the -i option was given.
Ct ii Change tone generator t to play instrument ii from now on. This will
only be generated if the -i option was given.
F0 End of score; stop playing.
@ -252,7 +255,7 @@
ff2 Another byte of flags, currently undefined
tt The number (in one byte) of tone generators actually used in this music.
Any subsequent header bytes covered by the count, if present, are currently undefined
Any subsequent header bytes included in the length are currently undefined
and should be ignored by players.
Len Shustek, 2011 to 2021; see the change log.
@ -382,6 +385,15 @@
22 April 2021, Len Shustek, V2.2
-Add -showskipped to log the places where notes had to be discarded because
there aren't enough tone generators.
25 April 2021, Len Shustek, V2.3
-Report how many notes were generated
5 May 2021, Len Shustek, V2.4
-Fix bug: when finding an idle tone generator, makes sure that the track and
instrument of the currently playing note matches before deciding it's a sustain,
otherwise multiple identical notes on identical instruments will disappear.
(Thanks to Jonathan Oakley for providing an example of the problem.)
-But sometimes, to reduce the number of tone generators, it's helpful to eliminate
identical notes. So add a -noduplicates option to do just that.
future version ideas
@ -397,7 +409,7 @@ future version ideas
channel 8 // organ
options -attacktime=1000 -sustainlevel=80% -releasetime=100 -notemin=200
*/
#define VERSION "2.2"
#define VERSION "2.4"
/*--------------------------------------------------------------------------------------------
@ -408,7 +420,7 @@ Gleaned from http://www.music.mcgill.ca/~ich/classes/mumt306/StandardMIDIfilefor
but also check out http://midi.teragonaudio.com/tech/miditech.htm
Notation:
<xxx> is 1-4 bytes of 7-bit data, concatenated into one 7- to 28-bit number. The high bit of the last byte is 0.
<xxx> is 1-4 bytes of 7-bit data, concatenated into one 7 to 28-bit number. The high bit of the last byte is 0.
lower case letters are hex digits. If preceeded by 0, only low 7 bits are used.
"xx" are ascii text characters
{xxx}... means indefinite repeat of xxx
@ -457,28 +469,31 @@ a Sysex event track_event is:
a meta event track_event is:
FF 00 02 ssss specify sequence number
FF 01 <len> "xx"... arbitrary text
FF 01 <len> "xx"... arbitrary description text
FF 02 <len> "xx"... copyright notice
FF 03 <len> "xx"... sequence or track name
FF 04 <len> "xx"... instrument name
FF 05 <len> "xx"... lyric to be sung
FF 06 <len> "xx"... name of marked point in the score
FF 07 <len> "xx"... description of cue point in the score
FF 08 <len> "xx"... program name
FF 09 <len> "xx"... device (port) name
FF 20 01 0c default channel for subsequent events without a channel is c
FF 21 01 pp MIDI port is pp
FF 2F 00 end of track
FF 51 03 tttttt set tempo in microseconds per quarter-note, for all tracks
FF 51 03 tttttt set tempo in microseconds per beat (quarter-note), for all tracks
FF 54 05 hhmmssfrff set SMPTE time to start the track
FF 58 04 nnddccbb set time signature
FF 59 02 sfmi set key signature
FF 7F <len> data sequencer-specific data
Note that "set tempo" events are supposed to occur in only one track (generally the first),
which may or may not also contain MIDI note events.
which may or may not also contain MIDI note events. That isn't always true, however,
See https://stackoverflow.com/questions/1080297/how-does-midi-tempo-message-apply-to-other-tracks
--------------------------------------------------------------------------------------------*/
/*--------------- processing outline -----------------------------------
Lots of details are omitted. Note that MIDI track parsing is based
on counting "ticks", but our queueing is based on real-time seconds.
The number of ticks per second changes with the tempo.
@ -586,7 +601,7 @@ struct track_header {
bool loggen, logparse, parseonly, strategy1, strategy2, binaryoutput, define_progmem,
volume_output, instrumentoutput, percussion_ignore, percussion_translate, do_header,
gen_restart, scorename, showskipped;
gen_restart, scorename, showskipped, noduplicates;
FILE *infile, *outfile, *logfile;
uint8_t *buffer, *hdrptr;
unsigned long buflen;
@ -719,6 +734,7 @@ void SayUsage(char *programName) {
" -s1 strategy 1: favor track 1",
" -s2 strategy 2: try to assign tracks to specific tone generators",
" -showskipped display information about each note that had to be skipped",
" -noduplicates remove identical notes playing on different channels",
" -delaymin=x minimum delay is x msec, to save bytestream space",
" -attacktime=x the high volume attack phase lasts x msec",
" -attacknotemax=x notes over x msec don't use the attack/sustain profile",
@ -733,7 +749,8 @@ void SayUsage(char *programName) {
int HandleOptions (int argc, char *argv[]) {
/* returns the index of the first argument that is not an option,
i.e. does not start with a dash or a slash */
int i, firstnonoption = 0; for (i = 1; i < argc; i++) {
int i, firstnonoption = 0;
for (i = 1; i < argc; i++) {
if (argv[i][0] == '/' || argv[i][0] == '-') {
int tempint;
char *arg = argv[i] + 1;
@ -769,6 +786,7 @@ int HandleOptions (int argc, char *argv[]) {
else if (opt_key(arg, "s2")) strategy2 = true;
else if (opt_key(arg, "scorename")) scorename = true;
else if (opt_key(arg, "showskipped")) showskipped = true;
else if (opt_key(arg, "noduplicates")) noduplicates = true;
else if (opt_int(arg, "t", &num_tonegens, 1, MAX_TONEGENS))
printf("Using %d tone generators\n", num_tonegens);
else if (opt_key(arg, "v")) volume_output = true;
@ -879,7 +897,11 @@ void outfile_items (int n) {
//******* structures for recording track, channel, and tone generator status
// Note that the tempo can change as notes are played, maybe many times.
// Note that the tempo can change while notes are being played, maybe many times.
// Although the score timing is specified in the MIDI file in ticks, we queue and
// issue note events based on microseconds since the start of the song. We convert
// ticks to microseconds, using the current tempo, as the MIDI events from the
// various tracks are processsed in tick order.
// In order to keep track of how long notes are to play, we have to incrementally
// accumulate the duration of all playing notes every time the tempo changes, and
// then one final time when the "stop note" event occurs.
@ -889,6 +911,7 @@ struct noteinfo { // everything we might care about as a note
int track, channel, note, instrument, volume; // all the nitty-gritty about it
};
struct tonegen_status { // current status of a tone generator
bool playing; // is it playing?
bool stopnote_pending; // are we due to issue a stop note command?
@ -899,7 +922,7 @@ struct track_status { // current status of a MIDI track
uint8_t *trkptr; // ptr to the next event we care about
uint8_t *trkend; // ptr just past the end of the track
unsigned long time; // what time we're at in the score, in ticks
unsigned long tempo; // the last tempo set on this track
unsigned long tempo; // the last tempo set by this track
int preferred_tonegen; // for strategy2: try to use this generator
byte cmd; // next CMD_xxxx event coming up
byte chan, note, volume; // if it is CMD_PLAYNOTE or CMD_STOPNOTE, the note info
@ -945,6 +968,7 @@ still plenty long.
struct queue_entry { // the format of each queue entry
byte cmd; // CMD_PLAY or CMD_STOP
byte delete; // delete this command due to "-noduplicates"?
struct noteinfo note; // info about the note, including the action time
} queue[QUEUE_SIZE];
@ -960,7 +984,7 @@ void show_queue(void) { // for debugging: dump the whole event queue
int ndx = queue_oldest_ndx;
if (queue_numitems > 0) while (1) {
struct queue_entry *q = &queue[ndx];
fprintf(fid, "%2d: %s %s\n", ndx, q->cmd == CMD_PLAYNOTE ? "PLAY" : "STOP", describe(&q->note));
fprintf(fid, "%2d: %s %s %s\n", ndx, q->cmd == CMD_PLAYNOTE ? "PLAY" : "STOP", q->delete ? "deleted" : "", describe(&q->note));
if (ndx == queue_newest_ndx) break;
if (++ndx >= QUEUE_SIZE) ndx = 0; } }
@ -984,7 +1008,8 @@ int find_idle_tgen(struct noteinfo *np) { // returns -1 if there isn't one
for (tgnum = 0; tgnum < num_tonegens; ++tgnum) { // first, is this note already playing on this channel?
tg = &tonegen[tgnum];
if (tg->playing
&& tg->note.note == np->note && tg->note.channel == np->channel) {
&& tg->note.note == np->note && tg->note.channel == np->channel
&& tg->note.track == np->track && tg->note.instrument == np->instrument) {
// this must be the start of the sustain phase of a playing note
++playnotes_without_stopnotes;
if (loggen) fprintf(logfile, " *** playnote without stopnote, tgen %d, %s\n",
@ -1014,8 +1039,9 @@ int find_idle_tgen(struct noteinfo *np) { // returns -1 if there isn't one
void remove_queue_entry(int ndx) { // remove the oldest queue entry
struct queue_entry *q = &queue[ndx];
if (q->delete) return; // if marked for deletion, just ignore it
if (q->cmd == CMD_STOPNOTE) {
if (loggen) fprintf(logfile, " dequeue stopnote for %s\n", describe(&q->note));
// find the tone generator playing this note, and record a pending stop
int tgnum;
for (tgnum = 0; tgnum < num_tonegens; ++tgnum) {
@ -1036,6 +1062,7 @@ void remove_queue_entry(int ndx) { // remove the oldest queue entry
else { // CMD_PLAYNOTE
assert(q->cmd == CMD_PLAYNOTE, "bad cmd in remove_queue_entry");
if (loggen) fprintf(logfile, " dequeue playnote for %s\n", describe(&q->note));
int tgnum = find_idle_tgen(&q->note);
struct tonegen_status *tg = &tonegen[tgnum];
if (tgnum >= 0) { // we found a tone generator we can use
@ -1120,7 +1147,7 @@ void pull_queue(void) {
--queue_numitems; }
while (queue_numitems > 0 && queue[queue_oldest_ndx].note.time_usec <= oldtime + (timestamp)delaymin_usec);
// do any "stop notes" still needed to be generated?
// do any "stop notes" still need to be generated?
for (int tgnum = 0; tgnum < num_tonegens; ++tgnum) {
struct tonegen_status *tg = &tonegen[tgnum];
if (tg->stopnote_pending) { // got one
@ -1138,12 +1165,57 @@ void pull_queue(void) {
void flush_queue(void) { // empty the queue
while (queue_numitems > 0) pull_queue(); }
// find the queue entry for the playnote matching the stopnote in queue[search_ndx]
int queue_find_playnote(int search_ndx) {
if (loggen) fprintf(logfile, " queue_find_playnote(%d), %s: ", search_ndx, describe(&queue[search_ndx].note));
for (int ndx = queue_newest_ndx;;) {
if (ndx != search_ndx // don't look at the entry we're trying to match
&& !queue[ndx].delete // don't re-find queue entries already deleted
&& queue[ndx].cmd == CMD_PLAYNOTE // must match playnote
&& queue[ndx].note.channel == queue[search_ndx].note.channel //also channel, track, note, and instrument
&& queue[ndx].note.track == queue[search_ndx].note.track
&& queue[ndx].note.note == queue[search_ndx].note.note
&& queue[ndx].note.instrument == queue[search_ndx].note.instrument ) {
if (loggen) fprintf(logfile, "found ndx %d\n", ndx);
return ndx; }
if (ndx == queue_oldest_ndx) break;
if (--ndx < 0) ndx = QUEUE_SIZE - 1; }
if (loggen) fprintf(logfile, "not found\n");
return -1; }
// find a queue entry for another stopnote matching the stopnote in queue[search_ndx]
int queue_find_stopnote(int search_ndx) {
if (loggen) fprintf(logfile, " queue_find_stopnote(%d), %s: ", search_ndx, describe(&queue[search_ndx].note));
for (int ndx = queue_newest_ndx;;) {
if (ndx != search_ndx // don't look at the entry we're trying to match
&& !queue[ndx].delete // don't re-find queue entries already deleted
&& queue[ndx].cmd == CMD_STOPNOTE // must match stopnote
&& queue[ndx].note.time_usec == queue[search_ndx].note.time_usec // also time, note, and instrument
&& queue[ndx].note.note == queue[search_ndx].note.note
&& queue[ndx].note.instrument == queue[search_ndx].note.instrument) {
if (loggen) fprintf(logfile, "found ndx %d\n", ndx);
return ndx; }
if (ndx == queue_oldest_ndx) break;
if (--ndx < 0) ndx = QUEUE_SIZE - 1; }
if (loggen) fprintf(logfile, "not found\n");
return -1; }
// For -noduplicates, mark for deletion any note start/stop identical to the CMD_STOPNOTE we just queued
void remove_queue_duplicates(int stop_ndx) {
int play_ndx, dup_play_ndx, dup_stop_ndx;
if ((play_ndx = queue_find_playnote(stop_ndx)) >= 0 // find its matching playnote
&& (dup_stop_ndx = queue_find_stopnote(stop_ndx)) >= 0 // find another stopnote for the same note at the same time
&& (dup_play_ndx = queue_find_playnote(dup_stop_ndx)) >= 0 // find its matching playnote
&& queue[dup_play_ndx].note.time_usec == queue[play_ndx].note.time_usec) { // if it starts at the same time, delete it
if (loggen) fprintf(logfile, " remove duplicate, ndxs %d, %d: %s\n", dup_play_ndx, dup_stop_ndx, describe(&queue[dup_play_ndx].note));
queue[dup_play_ndx].delete = queue[dup_stop_ndx].delete = true; } }
// queue a "note on" or "note off" command
void queue_cmd(byte cmd, struct noteinfo *np) {
if (loggen) fprintf(logfile, " queue %s %s\n",
cmd == CMD_PLAYNOTE ? "PLAY" : cmd == CMD_STOPNOTE ? "STOP" : "????",
describe(np));
if (queue_numitems == QUEUE_SIZE) pull_queue();
if (queue_numitems >= QUEUE_SIZE) pull_queue();
assert(queue_numitems < QUEUE_SIZE, "no room in queue");
timestamp horizon = output_usec + output_deficit_usec;
if (np->time_usec < horizon) { // don't allow revisionist history
@ -1175,9 +1247,10 @@ void queue_cmd(byte cmd, struct noteinfo *np) {
if (++ndx >= QUEUE_SIZE) ndx = 0; }
insert: // store the item at ndx
++queue_numitems;
queue[ndx].cmd = cmd; // fille in the queue entry
queue[ndx].cmd = cmd; // fill in the queue entry
queue[ndx].delete = false;
queue[ndx].note = *np; // structure copy of the note
}
if (noduplicates && cmd == CMD_STOPNOTE) remove_queue_duplicates(ndx); }
void show_queue_cmd(timestamp time_usec, byte cmd, int note) {
printf("debug queue %s note %02X at %6ld\n", cmd == CMD_PLAYNOTE ? "PLAY" : "STOP", note, time_usec);
@ -1364,7 +1437,7 @@ show_hex:
note_off: if (logparse) fprintf(logfile, "note %d (0x%02X) off, channel %d, volume %d\n", t->note, t->note, chan, t->volume);
if ((1 << chan) & channel_mask // we're processing this channel
&& (!percussion_ignore || chan != PERCUSSION_TRACK)) { // and not ignoring percussion
if (!instrumentoutput) t->chan = 0; // if no insruments, force all notes to channel 0
if (!instrumentoutput) t->chan = 0; // if no instruments, force all notes to channel 0
t->cmd = CMD_STOPNOTE; /* stop processing and return */
return; }
break;
@ -1376,7 +1449,7 @@ note_off: if (logparse) fprintf(logfile, "note %d (0x%02X) off, channel %d, vo
if (logparse) fprintf(logfile, "note %d (0x%02X) on, channel %d, volume %d\n", t->note, t->note, chan, t->volume);
if ((1 << chan) & channel_mask // we're processing this channel
&& (!percussion_ignore || chan != PERCUSSION_TRACK)) { // and not ignoring percussion
if (!instrumentoutput) t->chan = 0; // if no insruments, force all notes to channel 0
if (!instrumentoutput) t->chan = 0; // if no instruments, force all notes to channel 0
t->cmd = CMD_PLAYNOTE; /* stop processing and return */
return; }
break;
@ -1492,7 +1565,7 @@ void process_track_data(void) {
++noteinfo_notfound; // presumably the array overflowed on input
if (loggen) fprintf(logfile, " *** noteinfo slot not found to stop track %d note %d (%02X) channel %d\n",
tracknum, trk->note, trk->note, trk->chan); }
else {
else { // found the playing note for this stopnote
// Analyze the sustain and release parameters. We might generate another "note on"
// command with reduced volume, and/or move the stopnote command earlier than now.
struct noteinfo *np = &cp->notes_playing[ndx];
@ -1563,7 +1636,7 @@ int main (int argc, char *argv[]) {
#define MAXPATH 120
char filename[MAXPATH];
printf ("MIDITONES V%s, (C) 2011-2020 Len Shustek\n", VERSION);
printf ("MIDITONES V%s, (C) 2011-2021 Len Shustek\n", VERSION);
if (argc == 1) { // no arguments
SayUsage (argv[0]);
return 1; }
@ -1700,11 +1773,11 @@ int main (int argc, char *argv[]) {
// generate the ending commentary
if (!binaryoutput) {
fprintf(outfile, "\n// This score contains %ld bytes, and %d tone generator%s used.\n",
outfile_bytecount, num_tonegens_used,
num_tonegens_used == 1 ? " is" : "s are");
fprintf(outfile, "\n// This %ld byte score contains %d notes and uses %d tone generator%s\n",
outfile_bytecount, note_on_commands, num_tonegens_used,
num_tonegens_used == 1 ? "" : "s");
if (notes_skipped)
fprintf(outfile, "// %d notes had to be skipped.\n", notes_skipped); }
fprintf(outfile, "// %d notes had to be skipped\n", notes_skipped); }
printf(" %s %d tone generators were used.\n",
num_tonegens_used < num_tonegens ? "Only" : "All", num_tonegens_used);
if (notes_skipped)

Binary file not shown.

@ -43,12 +43,15 @@
*
* -x Show notes in hex instead of as octave-noteletter-sharp
*
* -n Don't show the bytestream data. (Ignored if -c is specified.)
*
* For source code to this and related programs, see
* www.github.com/LenShustek/miditones
* www.github.com/LenShustek/arduino-playtune
* www.github.com/LenShustek/Playtune_poll
* www.github.com/LenShustek/Playtune_poll
* www.github.com/LenShustek/Playtune_synth
* www.github.com/LenShustek/Playtune_Teensy
* www.github.com/LenShustek/ATtiny-playtune
*
*----------------------------------------------------------------------------------------
@ -112,9 +115,12 @@
* 23 April 2021, L. Shustek, V1.9
* - show a summary at the end of what instruments were used and how many times
* - show the lowest and highest volume used
* 5 May 2021, L. Shustek, V1.10
* - add -n option to not print the bytestream data
* - don't show instrument summary if instrument data wasn't in the bytestream
*/
#define VERSION "1.9"
#define VERSION "1.10"
#include <stdio.h>
#include <stdlib.h>
@ -153,6 +159,8 @@ bool codeoutput = false;
bool expect_volume = false;
bool ignore_volume = false;
bool showhex = false;
bool showbytestream = true;
bool got_instruments = false;
unsigned max_vol = 0, min_vol = 255;
struct file_hdr_t { /* what the optional file header looks like */
@ -235,6 +243,7 @@ void SayUsage (char *programName) {
" -vi expects and ignores volume information",
" -c creates an annotated C source file as <basefile>.c",
" -x show notes in hex instead of octave/note",
" -n don't show the bytestream data",
"" };
int i = 0;
while (usage[i][0] != '\0')
@ -260,6 +269,9 @@ int HandleOptions (int argc, char *argv[]) {
case 'X':
showhex = true;
break;
case 'N':
showbytestream = false;
break;
case 'T':
if (sscanf (&argv[i][2], "%d", &num_tonegens) != 1 || num_tonegens < 1
|| num_tonegens > MAX_TONEGENS)
@ -383,12 +395,13 @@ void print_status (void) {
else
fprintf (outfile, " v%-3d", gen_volume[gen]); }
// display the hex commands that created these changes
fprintf (outfile, "%3u.%03u %c%04X: ", delay/1000, delay%1000, warning ? '!' : ' ', (unsigned int) (lastbufptr - buffer));
fprintf (outfile, "%3u.%03u %c", delay/1000, delay%1000, warning ? '!' : ' ');
if (showbytestream) fprintf (outfile, "%04X: ", (unsigned int) (lastbufptr - buffer));
warning = false;
if (codeoutput)
fprintf (outfile, "*/ "); // end comment
for (; lastbufptr <= bufptr; ++lastbufptr)
fprintf (outfile, codeoutput ? "0x%02X," : "%02X ", *lastbufptr);
if (showbytestream) for (; lastbufptr <= bufptr; ++lastbufptr)
fprintf (outfile, codeoutput ? "0x%02X," : "%02X ", *lastbufptr);
fprintf (outfile, "\n");
lastbufptr = bufptr + 1; }
@ -413,6 +426,7 @@ int main (int argc, char *argv[]) {
return 1; }
argno = HandleOptions (argc, argv); /* process options */
if (codeoutput) showbytestream = true;
filebasename = argv[argno];
strlcpy (filename, filebasename, MAXPATH); // Open the input file
@ -487,7 +501,9 @@ int main (int argc, char *argv[]) {
fprintf (outfile, " time ");
for (unsigned i = 0; i < num_tonegens; ++i)
fprintf (outfile, expect_volume && !ignore_volume ? " gen%-5d" : " gen%-2d", i);
fprintf (outfile, "delay addr bytestream code\n\n");
fprintf(outfile, "delay");
if (showbytestream) fprintf(outfile, " addr bytestream code");
fprintf(outfile, "\n\n");
for (gen = 0; gen < num_tonegens; ++gen)
gen_note[gen] = SILENT;
@ -533,6 +549,7 @@ int main (int argc, char *argv[]) {
gen_note[gen] = SILENT;
gen_did_stopnote[gen] = true; }
else if (cmd == 0xc0) { /* change instrument */
got_instruments = true;
gen_instrument[gen] = *++bufptr & 0x7f;
gen_instrument_changed[gen] = true; }
else {
@ -562,10 +579,11 @@ int main (int argc, char *argv[]) {
fprintf (infofile, "%u stopnote commands were unnecessary.\n", stopnotes_before_startnote);
fprintf (infofile, "%u consecutive delays could have been merged.\n", consecutive_delays);
if (stopnotes_before_startnote + consecutive_delays > 0) fprintf (infofile, "(Those locations are marked with \"!\")\n");
fprintf(infofile, "instruments used:\n");
for (int i = 0; i < 128; ++i)
if (instrument_count[i]) {
fprintf(infofile, " %s (%3d, 0x%02X) %7d\n", instrumentname[i], i, i, instrument_count[i]); }
if (got_instruments) {
fprintf(infofile, "instruments used:\n");
for (int i = 0; i < 128; ++i)
if (instrument_count[i]) {
fprintf(infofile, " %s (%3d, 0x%02X) %7d\n", instrumentname[i], i, i, instrument_count[i]); } }
if (expect_volume)
fprintf(infofile, "volume ranged from %d to %d\n", min_vol, max_vol);
printf ("Done.\n"); }

Binary file not shown.
Loading…
Cancel
Save