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