add minor new features

master
Len Shustek 3 years ago
parent 446ebadf83
commit 3a0d14ce12
  1. 2
      LICENSE.txt
  2. 19
      README.txt
  3. 54
      miditones.c
  4. BIN
      miditones.exe
  5. 25
      miditones_scroll.c
  6. BIN
      miditones_scroll.exe

@ -1,6 +1,6 @@
The MIT License (MIT) 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

@ -44,6 +44,15 @@
(from MIDI channel 10) are generated from longer sampled waveforms of a complete (from MIDI channel 10) are generated from longer sampled waveforms of a complete
instrument strike. Each generator's volume is independently adjusted according to instrument strike. Each generator's volume is independently adjusted according to
the MIDI velocity of the note being played before all channels are mixed. 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 www.github.com/LenShustek/ATtiny-playtune
This is a much simplified version that fits, with a small song, into an ATtiny This is a much simplified version that fits, with a small song, into an ATtiny
@ -93,6 +102,9 @@
The <basefilename> is the base name, without an extension, for the input and The <basefilename> is the base name, without an extension, for the input and
output files. It can contain directory path information, or not. 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 <basefilename>.
The input file is <basefilename>.mid, and the output filename(s) The input file is <basefilename>.mid, and the output filename(s)
are the base file name with .c, .h, .bin, and/or .log extensions. are the base file name with .c, .h, .bin, and/or .log extensions.
@ -152,6 +164,9 @@
-h Give command-line help. -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 -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 of "delay" commands and thus make the bytestream smaller, at the expense of
moving notes slightly. The deficits are accumulated and eventually used, moving notes slightly. The deficits are accumulated and eventually used,
@ -208,7 +223,7 @@
F0 End of score; stop playing. 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. 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 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 Any subsequent header bytes covered by the count, if present, are currently undefined
and should be ignored by players. and should be ignored by players.
Len Shustek, 2011 to 2019; see the change log. Len Shustek, 2011 to 2021; see the change log.

@ -46,6 +46,15 @@
(from MIDI channel 10) are generated from longer sampled waveforms of a complete (from MIDI channel 10) are generated from longer sampled waveforms of a complete
instrument strike. Each generator's volume is independently adjusted according to instrument strike. Each generator's volume is independently adjusted according to
the MIDI velocity of the note being played before all channels are mixed. 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 www.github.com/LenShustek/ATtiny-playtune
This is a much simplified version that fits, with a small song, into an ATtiny This is a much simplified version that fits, with a small song, into an ATtiny
@ -157,6 +166,9 @@
-h Give command-line help. -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 -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 of "delay" commands and thus make the bytestream smaller, at the expense of
moving notes slightly. The deficits are accumulated and eventually used, moving notes slightly. The deficits are accumulated and eventually used,
@ -213,7 +225,7 @@
F0 End of score; stop playing. 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. 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 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 Any subsequent header bytes covered by the count, if present, are currently undefined
and should be ignored by players. 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) * 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 * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * 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 -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, to users using shell autocompletion. If a .mid or .MID file is provided,
the extension will be dropped to generate the base filename. 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 future version ideas
-Perhaps elide "note off/note on" event sequences for the same note that -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? 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 -Allow the flexibility to specify note timing on a track-by-track or
channel-by-channel basis, by using a <basefile>.cfg file which has channel-by-channel basis, by using a <basefile>.cfg file which has
commands like these: commands like these:
options <global options> options <global options>
track 1 // melody track 1 // melody
@ -382,7 +397,7 @@ future version ideas
channel 8 // organ channel 8 // organ
options -attacktime=1000 -sustainlevel=80% -releasetime=100 -notemin=200 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 ----------------------------------- /*--------------- processing outline -----------------------------------
Lots of details are omitted. Note that MIDI track parsing is based 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. The number of ticks per second changes with the tempo.
noteinfo noteinfo
@ -518,7 +533,7 @@ queue command
output queue entries at the oldest time output queue entries at the oldest time
static output time, time deficit static output time, time deficit
if time has advanced if time has advanced
output DELAY output DELAY
for all entries at the same oldest time for all entries at the same oldest time
if STOP if STOP
find tgen matching channel and note find tgen matching channel and note
@ -571,7 +586,7 @@ struct track_header {
bool loggen, logparse, parseonly, strategy1, strategy2, binaryoutput, define_progmem, bool loggen, logparse, parseonly, strategy1, strategy2, binaryoutput, define_progmem,
volume_output, instrumentoutput, percussion_ignore, percussion_translate, do_header, volume_output, instrumentoutput, percussion_ignore, percussion_translate, do_header,
gen_restart, scorename; gen_restart, scorename, showskipped;
FILE *infile, *outfile, *logfile; FILE *infile, *outfile, *logfile;
uint8_t *buffer, *hdrptr; uint8_t *buffer, *hdrptr;
unsigned long buflen; unsigned long buflen;
@ -703,6 +718,7 @@ void SayUsage(char *programName) {
" -r terminate output file with \"restart\" instead of \"stop\" command", " -r terminate output file with \"restart\" instead of \"stop\" command",
" -s1 strategy 1: favor track 1", " -s1 strategy 1: favor track 1",
" -s2 strategy 2: try to assign tracks to specific tone generators", " -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", " -delaymin=x minimum delay is x msec, to save bytestream space",
" -attacktime=x the high volume attack phase lasts x msec", " -attacktime=x the high volume attack phase lasts x msec",
" -attacknotemax=x notes over x msec don't use the attack/sustain profile", " -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, "s1")) strategy1 = true;
else if (opt_key(arg, "s2")) strategy2 = true; else if (opt_key(arg, "s2")) strategy2 = true;
else if (opt_key(arg, "scorename")) scorename = 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)) else if (opt_int(arg, "t", &num_tonegens, 1, MAX_TONEGENS))
printf("Using %d tone generators\n", num_tonegens); printf("Using %d tone generators\n", num_tonegens);
else if (opt_key(arg, "v")) volume_output = true; 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 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! // WARNING: returns a pointer to a static string, so only call once per line, in a printf!
static char notedescription[100]; static char notedescription[100];
sprintf(notedescription, "at %lu.%03lu msec, note %d (0x%02X) track %d channel %d volume %d instrument %d", int secs = np->time_usec / 1000000;
np->time_usec / 1000, np->time_usec % 1000, np->note, np->note, 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); np->track, np->channel, np->volume, np->instrument);
return notedescription; } return notedescription; }
@ -1001,7 +1021,7 @@ void remove_queue_entry(int ndx) { // remove the oldest queue entry
for (tgnum = 0; tgnum < num_tonegens; ++tgnum) { for (tgnum = 0; tgnum < num_tonegens; ++tgnum) {
struct tonegen_status *tg = &tonegen[tgnum]; struct tonegen_status *tg = &tonegen[tgnum];
if (tg->playing 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->note.channel == q->note.channel) { // found the note
tg->stopnote_pending = true; // "stop note needed unless another start note follows" 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 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 { else {
if (loggen) fprintf(logfile, " *** at %lu.%03lu msec no free generator; skipping %s\n", if (loggen) fprintf(logfile, " *** at %lu.%03lu msec no free generator; skipping %s\n",
output_usec / 1000, output_usec % 1000, describe(&q->note)); 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 void generate_delay(unsigned long delta_msec) { // output a delay command
if (delta_msec > 0) { 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 // strip off trailing .mid or .MID extension if provided by user
basenamelen = strlength(filebasename); basenamelen = strlength(filebasename);
if (basenamelen > 4 && if (basenamelen > 4 &&
(charcmp (filebasename + basenamelen - 4, ".mid") || (charcmp (filebasename + basenamelen - 4, ".mid") ||
charcmp (filebasename + basenamelen - 4, ".MID"))) { charcmp (filebasename + basenamelen - 4, ".MID"))) {
filebasename[basenamelen - 4] = 0; filebasename[basenamelen - 4] = 0; }
}
if (logparse || loggen) { // open the log file if (logparse || loggen) { // open the log file
miditones_strlcpy (filename, filebasename, MAXPATH); miditones_strlcpy (filename, filebasename, MAXPATH);

Binary file not shown.

@ -53,7 +53,7 @@
* *
*---------------------------------------------------------------------------------------- *----------------------------------------------------------------------------------------
* The MIT License (MIT) * 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 * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@ -109,9 +109,12 @@
* - Write the output to <basefilename>.txt, if not -c * - Write the output to <basefilename>.txt, if not -c
* - Decode the header flags * - Decode the header flags
* - Reformat to condense the code * - 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 <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@ -150,6 +153,7 @@ bool codeoutput = false;
bool expect_volume = false; bool expect_volume = false;
bool ignore_volume = false; bool ignore_volume = false;
bool showhex = false; bool showhex = false;
unsigned max_vol = 0, min_vol = 255;
struct file_hdr_t { /* what the optional file header looks like */ struct file_hdr_t { /* what the optional file header looks like */
char id1; // 'P' char id1; // 'P'
@ -216,6 +220,7 @@ static char *instrumentname[128] = { /* maximum 6 characters */
"Sitar ", "Banjo ", "Shamis", "Koto ", "Kalimb", "Bagpip", "Fiddle", "Shanai", "Sitar ", "Banjo ", "Shamis", "Koto ", "Kalimb", "Bagpip", "Fiddle", "Shanai",
"TnkBel", "Agogo ", "StDrum", "WdBlok", "TaiDrm", "MelTom", "SynDrm", "RevCym", "TnkBel", "Agogo ", "StDrum", "WdBlok", "TaiDrm", "MelTom", "SynDrm", "RevCym",
"GuitFr", "Breath", "Seashr", "BirdTw", "Phone ", "Copter", "Claps ", "Guns " }; "GuitFr", "Breath", "Seashr", "BirdTw", "Phone ", "Copter", "Claps ", "Guns " };
int instrument_count[128] = { 0 };
/************** command-line processing *******************/ /************** 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]); for (int i = 0; i < argc; i++) fprintf(outfile, "%s ", argv[i]);
fprintf(outfile, "\n"); fprintf(outfile, "\n");
fprintf(outfile, "reading %s.bin with %ld bytes\n", filebasename, buflen); 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 { else {
fprintf (outfile, "// Playtune bytestream for file \"%s.bin\"", filebasename); fprintf (outfile, "// Playtune bytestream for file \"%s.bin\"", filebasename);
fprintf (outfile, " created by MIDITONES_SCROLL V%s on %s\n", VERSION, 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 */ if (cmd == 0x90) { /* note on */
gen_note[gen] = *++bufptr; // note number gen_note[gen] = *++bufptr; // note number
tonegens_used |= 1 << gen; // record that we used this generator at least once 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 if (gen_did_stopnote[gen]) { // unnecesary stop note
++stopnotes_before_startnote; ++stopnotes_before_startnote;
warning = true; } warning = true; }
if (expect_volume) if (expect_volume) {
gen_volume[gen] = *++bufptr; // 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 if (gen >= num_tonegens) ++notes_skipped; // won't be displaying this note
} }
else if (cmd == 0x80) { /* note off */ 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 stopnote commands were unnecessary.\n", stopnotes_before_startnote);
fprintf (infofile, "%u consecutive delays could have been merged.\n", consecutive_delays); 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"); 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"); } printf ("Done.\n"); }

Binary file not shown.
Loading…
Cancel
Save