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)
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

@ -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 <basefilename> 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 <basefilename>.
The input file is <basefilename>.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.

@ -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 <basefile>.cfg file which has
-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
commands like these:
options <global 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);

Binary file not shown.

@ -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 <basefilename>.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 <stdio.h>
#include <stdlib.h>
@ -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"); }

Binary file not shown.
Loading…
Cancel
Save