add minor new features

master
Len Shustek 3 years ago
parent 446ebadf83
commit 3a0d14ce12
  1. 2
      LICENSE.txt
  2. 17
      README.txt
  3. 42
      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

@ -45,6 +45,15 @@
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
processor with only 4K of flash memory. It also using polling with only one timer, processor with only 4K of flash memory. It also using polling with only one timer,
@ -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,
@ -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.

@ -47,6 +47,15 @@
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
processor with only 4K of flash memory. It also using polling with only one timer, processor with only 4K of flash memory. It also using polling with only one timer,
@ -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,
@ -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,6 +379,9 @@
-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
@ -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"
/*-------------------------------------------------------------------------------------------- /*--------------------------------------------------------------------------------------------
@ -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; }
@ -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