diff --git a/LICENSE.txt b/LICENSE.txt index e6f88c6..34a2d76 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2016, Len Shustek +Copyright (c) 2016,2021 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 diff --git a/README.txt b/README.txt index 48ffd8c..7ae092d 100644 --- a/README.txt +++ b/README.txt @@ -44,6 +44,15 @@ (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. + It uses less CPU time than the polling version, but is limited to 4 notes at a time. + (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 @@ -93,6 +102,9 @@ The is the base name, without an extension, for the input and output files. It can contain directory path information, or not. + If the user specifies the full .mid filename, the .mid or .MID extension + will be dropped and the remaining name will be used as . + The input file is .mid, and the output filename(s) are the base file name with .c, .h, .bin, and/or .log extensions. @@ -152,6 +164,9 @@ -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. + -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, @@ -208,7 +223,7 @@ F0 End of score; stop playing. - E0 End of score, but start playing again from the beginning. This is + E0 End of score, but start playing again from the beginning. This is generated by the -r option. If the high-order bit of the byte is 0, it is a command to delay for a while until @@ -238,4 +253,4 @@ Any subsequent header bytes covered by the count, if present, are currently undefined and should be ignored by players. - Len Shustek, 2011 to 2019; see the change log. + Len Shustek, 2011 to 2021; see the change log. \ No newline at end of file diff --git a/miditones.c b/miditones.c index d2d99dc..2dc4c22 100644 --- a/miditones.c +++ b/miditones.c @@ -46,6 +46,15 @@ (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. + It uses less CPU time than the polling version, but is limited to 4 notes at a time. + (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 @@ -157,6 +166,9 @@ -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. + -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, @@ -213,7 +225,7 @@ F0 End of score; stop playing. - E0 End of score, but start playing again from the beginning. This is + E0 End of score, but start playing again from the beginning. This is generated by the -r option. If the high-order bit of the byte is 0, it is a command to delay for a while until @@ -243,11 +255,11 @@ Any subsequent header bytes covered by the count, if present, are currently undefined and should be ignored by players. - Len Shustek, 2011 to 2019; see the change log. + Len Shustek, 2011 to 2021; see the change log. *---------------------------------------------------------------------------------------- * The MIT License (MIT) -* Copyright (c) 2011,2013,2015,2016,2019 Len Shustek +* Copyright (c) 2011,2013,2015,2016,2019,2021 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 @@ -367,14 +379,17 @@ -Let user supply full filename to MIDI file on command line to be friendlier to users using shell autocompletion. If a .mid or .MID file is provided, the extension will be dropped to generate the base filename. + 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. future version ideas -Perhaps elide "note off/note on" event sequences for the same note that become adjacent because of -delaymin. Does that happen much, or at all? - -Allow the flexibility to specify note timing on a track-by-track or - channel-by-channel basis, by using a .cfg file which has + -Allow the flexibility to specify note timing on a track-by-track or + channel-by-channel basis, by using a .cfg file which has commands like these: options track 1 // melody @@ -382,7 +397,7 @@ future version ideas channel 8 // organ options -attacktime=1000 -sustainlevel=80% -releasetime=100 -notemin=200 */ -#define VERSION "2.1" +#define VERSION "2.2" /*-------------------------------------------------------------------------------------------- @@ -465,7 +480,7 @@ See https://stackoverflow.com/questions/1080297/how-does-midi-tempo-message-appl /*--------------- 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. +on counting "ticks", but our queueing is based on real-time seconds. The number of ticks per second changes with the tempo. noteinfo @@ -518,7 +533,7 @@ queue command output queue entries at the oldest time static output time, time deficit if time has advanced - output DELAY + output DELAY for all entries at the same oldest time if STOP find tgen matching channel and note @@ -571,7 +586,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; + gen_restart, scorename, showskipped; FILE *infile, *outfile, *logfile; uint8_t *buffer, *hdrptr; unsigned long buflen; @@ -703,6 +718,7 @@ void SayUsage(char *programName) { " -r terminate output file with \"restart\" instead of \"stop\" command", " -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", " -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", @@ -752,6 +768,7 @@ int HandleOptions (int argc, char *argv[]) { else if (opt_key(arg, "s1")) strategy1 = true; 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_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; @@ -898,8 +915,11 @@ struct channel_status { // current status of a channel char *describe(struct noteinfo *np) { // create a description of a note // WARNING: returns a pointer to a static string, so only call once per line, in a printf! static char notedescription[100]; - sprintf(notedescription, "at %lu.%03lu msec, note %d (0x%02X) track %d channel %d volume %d instrument %d", - np->time_usec / 1000, np->time_usec % 1000, np->note, np->note, + int secs = np->time_usec / 1000000; + sprintf(notedescription, "at %3lu.%06lu sec (%d:%02d), note %d (0x%02X) track %d channel %d volume %d instrument %d", + secs, np->time_usec % 1000000, + secs/60, secs % 60, + np->note, np->note, np->track, np->channel, np->volume, np->instrument); return notedescription; } @@ -1001,7 +1021,7 @@ void remove_queue_entry(int ndx) { // remove the oldest queue entry for (tgnum = 0; tgnum < num_tonegens; ++tgnum) { struct tonegen_status *tg = &tonegen[tgnum]; if (tg->playing - && tg->note.note == q->note.note + && tg->note.note == q->note.note && tg->note.channel == q->note.channel) { // found the note tg->stopnote_pending = true; // "stop note needed unless another start note follows" tg->playing = false; // free the tg to be reallocated, but note the stop time in case @@ -1056,7 +1076,8 @@ void remove_queue_entry(int ndx) { // remove the oldest queue entry else { if (loggen) fprintf(logfile, " *** at %lu.%03lu msec no free generator; skipping %s\n", output_usec / 1000, output_usec % 1000, describe(&q->note)); - ++notes_skipped; } } } + if (showskipped) printf(" *** no free generator %s\n", + describe(&q->note)); ++notes_skipped; } } } void generate_delay(unsigned long delta_msec) { // output a delay command if (delta_msec > 0) { @@ -1557,10 +1578,9 @@ int main (int argc, char *argv[]) { // strip off trailing .mid or .MID extension if provided by user basenamelen = strlength(filebasename); if (basenamelen > 4 && - (charcmp (filebasename + basenamelen - 4, ".mid") || - charcmp (filebasename + basenamelen - 4, ".MID"))) { - filebasename[basenamelen - 4] = 0; - } + (charcmp (filebasename + basenamelen - 4, ".mid") || + charcmp (filebasename + basenamelen - 4, ".MID"))) { + filebasename[basenamelen - 4] = 0; } if (logparse || loggen) { // open the log file miditones_strlcpy (filename, filebasename, MAXPATH); diff --git a/miditones.exe b/miditones.exe index c082d38..ba15dfc 100644 Binary files a/miditones.exe and b/miditones.exe differ diff --git a/miditones_scroll.c b/miditones_scroll.c index 1931238..e92f06e 100644 --- a/miditones_scroll.c +++ b/miditones_scroll.c @@ -53,7 +53,7 @@ * *---------------------------------------------------------------------------------------- * The MIT License (MIT) -* Copyright (c) 2011,2013,2015,2016,2019, Len Shustek +* Copyright (c) 2011,2013,2015,2016,2019,2021 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 @@ -109,9 +109,12 @@ * - Write the output to .txt, if not -c * - Decode the header flags * - Reformat to condense the code +* 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 */ -#define VERSION "1.8" +#define VERSION "1.9" #include #include @@ -150,6 +153,7 @@ bool codeoutput = false; bool expect_volume = false; bool ignore_volume = false; bool showhex = false; +unsigned max_vol = 0, min_vol = 255; struct file_hdr_t { /* what the optional file header looks like */ char id1; // 'P' @@ -216,6 +220,7 @@ static char *instrumentname[128] = { /* maximum 6 characters */ "Sitar ", "Banjo ", "Shamis", "Koto ", "Kalimb", "Bagpip", "Fiddle", "Shanai", "TnkBel", "Agogo ", "StDrum", "WdBlok", "TaiDrm", "MelTom", "SynDrm", "RevCym", "GuitFr", "Breath", "Seashr", "BirdTw", "Phone ", "Copter", "Claps ", "Guns " }; +int instrument_count[128] = { 0 }; /************** command-line processing *******************/ @@ -450,8 +455,7 @@ int main (int argc, char *argv[]) { for (int i = 0; i < argc; i++) fprintf(outfile, "%s ", argv[i]); fprintf(outfile, "\n"); fprintf(outfile, "reading %s.bin with %ld bytes\n", filebasename, buflen); - if (num_tonegens < MAX_TONEGENS) fprintf(outfile, "displaying only %d tone generators.\n", num_tonegens); - } + if (num_tonegens < MAX_TONEGENS) fprintf(outfile, "displaying only %d tone generators.\n", num_tonegens); } else { fprintf (outfile, "// Playtune bytestream for file \"%s.bin\"", filebasename); fprintf (outfile, " created by MIDITONES_SCROLL V%s on %s\n", VERSION, @@ -513,11 +517,14 @@ int main (int argc, char *argv[]) { if (cmd == 0x90) { /* note on */ gen_note[gen] = *++bufptr; // note number tonegens_used |= 1 << gen; // record that we used this generator at least once + ++instrument_count[gen_instrument[gen]]; // count a use of this instrument if (gen_did_stopnote[gen]) { // unnecesary stop note ++stopnotes_before_startnote; warning = true; } - if (expect_volume) - gen_volume[gen] = *++bufptr; // volume + if (expect_volume) { + unsigned volume = gen_volume[gen] = *++bufptr; // volume + if (volume > max_vol) max_vol = volume; + if (volume < min_vol) min_vol = volume; } if (gen >= num_tonegens) ++notes_skipped; // won't be displaying this note } else if (cmd == 0x80) { /* note off */ @@ -555,4 +562,10 @@ 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 (expect_volume) + fprintf(infofile, "volume ranged from %d to %d\n", min_vol, max_vol); printf ("Done.\n"); } diff --git a/miditones_scroll.exe b/miditones_scroll.exe index ab64fa8..8235666 100644 Binary files a/miditones_scroll.exe and b/miditones_scroll.exe differ