diff --git a/LICENSE.txt b/LICENSE.txt index 0cdd65f..e6f88c6 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,14 +1,21 @@ -* (C) Copyright 2011, 2013, 2015, Len Shustek -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of version 3 of the GNU General Public License as -* published by the Free Software Foundation at http://www.gnu.org/licenses, -* with Additional Permissions under term 7(b) that the original copyright -* notice and author attibution must be preserved and under term 7(c) that -* modified versions be marked as different from the original. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* \ No newline at end of file +The MIT License (MIT) + +Copyright (c) 2016, 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 +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.txt b/README.txt index 957db50..038613e 100644 --- a/README.txt +++ b/README.txt @@ -1,39 +1,42 @@ -/*-------------------------------------------------------------------------------- +/********************************************************************************************* * +* MIDITONES: Convert a MIDI file into a simple bytestream of notes * -* About MIDITONES * +* MIDITONES converts a MIDI music file into a much simplified stream of commands, so that +* the music can easily be played on a small microcontroller-based synthesizer that has +* only simple tone generators. This is on github at www.github.com/LenShustek/miditones. * -* MIDITONES converts a MIDI music file into a much simplified stream of commands, -* so that a version of the music can be played on a synthesizer having only -* tone generators without any volume or tone controls. +* Volume ("velocity") and instrument information in the MIDI file can either be +* discarded or kept. All the tracks are prcoessed and merged into a single time-ordered +* stream of "note on", "note off", "change instrument" and "delay" commands. * -* Volume ("velocity") and instrument specifications in the MIDI files are generally -* discarded. All the tracks are prcoessed and merged into a single time-ordered -* stream of "note on", "note off", and "delay" commands. -* -* This was written for the "Playtune" Arduino library, which plays polyphonic music -* using up to 6 tone generators run by the timers on the processor. See the separate -* documentation for Playtune. But MIDITONES may prove useful for other tone -* generating systems. +* This was written for the "Playtune" series of Arduino and Teensy microcontroller +* synthesizers. See the separate documentation for the various Playtune.players at +* www.github.com/LenShustek/arduino-playtune +* www.github.com/LenShustek/ATtiny-playtune +* www.github.com/LenShustek/Playtune_poll +* www.github.com/LenShustek/Playtune_samp +* MIDITONES may also prove useful for other simple music synthesizers.. * * The output can be either a C-language source code fragment that initializes an * array with the command bytestream, or a binary file with the bytestream itself. * -* MIDITONES is written in standard ANSI C (plus strlcpy and strlcat functions), and -* is meant to be executed from the command line. There is no GUI interface. +* MIDITONES is written in standard ANSI C and is meant to be executed from the +* command line. There is no GUI interface. +* +* The MIDI file format is complicated, and this has not been tested on all of its +* variations. In particular we have tested only format type "1", which seems +* to be what most of them are. Let me know if you find MIDI files that it +* won't digest and I'll see if I can fix it. * -* The MIDI file format is complicated, and this has not been tested on a very -* wide variety of file types. In particular, we have tested only format type "1", -* which seems to be what most of them are. Let me know if you find MIDI files -* that it won't digest and I'll see if I can fix it. - -* This has been tested only on a little-endian PC, but I think it should work on -* big-endian processors too. Note that the MIDI file format is inherently -* big-endian. +* There is a companion program in the same repository called Miditones_scroll +* that can convert the bytestream generated by MIDITONES into a piano-player +* like listing for debugging or annotation. See the documentation in the +* beginning of its source code. * * -* ***** The command line ***** +* ***** The MIDITONES command line ***** * * To convert a MIDI file called "chopin.mid" into a command bytestream, execute * @@ -45,41 +48,57 @@ * * The general form for command line execution is this: * -* miditones [-p] [-lg] [-lp] [-s1] [-tn] [-b] [-cn] [-kn] [-v] +* miditones * * The 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. +* +* The input file is .mid The output filename(s) +* are the base file name with .c, .bin, and/or .log extensions. +* +* +* The following commonly-used command-line options can be specified: +* +* -v Add velocity (volume) information to the output bytestream +* +* -i Add instrument change commands to the output bytestream +* +* -pt Translate notes in the MIDI percussion track to note numbers 128..255 +* and assign them to a tone generator as usual. +* +* -d Generate a self-describing file header that says which optional bytestream +* fields are present. This is highly recommended if you are using later +* Playtune players that can check the header to know what data to expect. +* +* -b Generate a binary file with the name .bin, instead of a +* C-language source file with the name .c. +* +* -tn Generate the bytestream so that at most n tone generators are used. +* The default is 6 tone generators, and the maximum is 16. The program +* will report how many notes had to be discarded because there weren't +* enough tone generators. * -* The input file is the base name with the extension ".mid". The output filename(s) -* are the base name with ".c", ".bin", and/or ".log" extensions. * +* The best combination of options to use with the later Playtune music players is: +* -v -i -pt -d * -* The following command-line options can be specified: * -* -p Only parse the MIDI file; don't generate an output file. +* The following are lesser-used command-line options: +* +* -p Only parse the MIDI file, and don't generate an output file. * Tracks are processed sequentially instead of being merged into chronological order. -* This is mostly useful when generating a log to debug MIDI file parsing problems. +* This is mostly useful for debugging MIDI file parsing problems. * * -lp Log input file parsing information to the .log file * * -lg Log output bytestream generation information to the .log file * +* -nx Put about "x" items on each line of the C file output +* * -sn Use bytestream generation strategy "n". * Two strategies are currently implemented: -* 1: favor track 1 notes instead of all tracks equally -* 2: try to keep each track to its own tone generator -* -* -tn Generate the bytestream so that at most n tone generators are used. -* The default is 6 tone generators, and the maximum is 16. -* The program will report how many notes had to be discarded because there -* weren't enough tone generators. Note that for the Arduino Playtunes -* library, it's ok to have the bytestream use more tone genreators than -* exist on your processor because any extra notes will be ignored, although -* it does make the file bigger than necessary . Of course, too many ignored -* notes will make the music sound really strange! -* -* -b Generate a binary file with the name .bin, instead of a -* C-language source file with the name .c. +* 1:favor track 1 notes instead of all tracks equally +* 2:try to keep each track to its own tone generator * * -cn Only process the channel numbers whose bits are on in the number "n". * For example, -c3 means "only process channels 0 and 1" @@ -87,31 +106,39 @@ * -kn Change the musical key of the output by n chromatic notes. * -k-12 goes one octave down, -k12 goes one octave up, etc. * -* -v Add velocity information to output +* -pi Ignore notes in the MIDI percussion track 9 (also called 10 by some) +* +* -dp Generate IDE-dependent C code to define PROGMEM +* +* -h Give command-line help. * * * ***** The score bytestream ***** * -* The generated bytestream is a series of commands that turn notes on and off, and -* start delays until the next note change. Here are the details, with numbers -* shown in hexadecimal. +* The generated bytestream is a series of commands that turn notes on and off, +* maybe change instruments, and begin delays until the next note change. +* Here are the details, with numbers shown in hexadecimal. * * If the high-order bit of the byte is 1, then it is one of the following commands: * -* 9t nn [vv] Start playing note nn on tone generator t. Generators are numbered -* starting with 0. The notes numbers are the MIDI numbers for the chromatic +* 9t nn [vv] +* Start playing note nn on tone generator t. Generators are numbered +* starting with 0. The note numbers are the MIDI numbers for the chromatic * scale, with decimal 60 being Middle C, and decimal 69 being Middle A (440 Hz). -* If the -v option is enabled, a second byte is added to indicate velocity. +* If the -v option was given, a second byte is added to indicate note volume. * * 8t Stop playing the note on tone generator t. * -* F0 End of score: stop playing. +* Ct ii Change tone generator t to play instrument ii from now on. This will only +* be generated if the -i option was given. +* +* F0 End of score; stop playing. * -* E0 End of score: start playing again from the beginning. +* E0 End of score; start playing again from the beginning. * (Shown for completeness; MIDITONES won't generate this.) * * If the high-order bit of the byte is 0, it is a command to delay for a while until -* the next note change.. The other 7 bits and the 8 bits of the following byte are +* the next note change. The other 7 bits and the 8 bits of the following byte are * interpreted as a 15-bit big-endian integer that is the number of milliseconds to * wait before processing the next command. For example, * @@ -120,8 +147,22 @@ * would cause a delay of 0x07d0 = 2000 decimal millisconds, or 2 seconds. Any tones * that were playing before the delay command will continue to play. * +* If the -d option is specified, the bytestream begins with a little header that tells +* what optional information will be in the data. This makes the file more self-describing, +* and allows music players to adapt to different kinds of files. The later Playtune +* players do that. The header looks like this: * -* Len Shustek, 4 Feb 2011 and later +* 'Pt' Two ascii characters that signal the presence of the header +* nn The length (in one byte) of the entire header, 6..255 +* ff1 A byte of flag bits, three of which are currently defined: +* 80 velocity information is present +* 40 instrument change information is present +* 20 translated percussion notes are present +* ff2 Another byte of flags, currently undefined +* tt The number (in one byte) of tone generators actually used in this music. +* +* Any subsequent header bytes covered by the count, if present, are currently undefined +* and should be ignored by players. * -*----------------------------------------------------------------------------------*/ - +* Len Shustek, 4 Feb 2011 and later +*********************************************************************************************/ \ No newline at end of file diff --git a/miditones.c b/miditones.c index e662ebe..e35a440 100644 --- a/miditones.c +++ b/miditones.c @@ -1,103 +1,43 @@ -/********************************************************************************* -* -* MIDITONES -* -* Convert a MIDI file into a bytestream of notes -* -* -* (C) Copyright 2011,2013,2015,2016 Len Shustek -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of version 3 of the GNU General Public License as -* published by the Free Software Foundation at http://www.gnu.org/licenses, -* with Additional Permissions under term 7(b) that the original copyright -* notice and author attibution must be preserved and under term 7(c) that -* modified versions be marked as different from the original. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -***********************************************************************************/ -/* -* Change log -* 19 January 2011, L.Shustek, V1.0 -* -Initial release. -* 26 February 2011, L. Shustek, V1.1 -* -Expand the documentation generated in the output file. -* -End the binary output file with an "end of score" command. -* -Fix bug: Some "stop note" commands were generated too early. -* 04 March 2011, L. Shustek, V1.2 -* -Minor error message rewording. -* 13 June 2011, L. Shustek, V1.3 -* -Add -s2 strategy to try to keep each track on its own tone generator -* for when there are separate speakers. This obviously works only when -* each track is monophonic. (Suggested by Michal Pustejovsky) -* 20 November 2011, L. Shustek, V1.4 -* -Add -cn option to mask which channels (tracks) to process -* -Add -kn option to change key -* Both of these are in support of music-playing on my Tesla Coil. -* 05 December 2011, L. Shustek, V1.5 -* -Fix command line parsing error for option -s1 -* -Display the commandline in the C file output -* -Change to decimal instead of hex for note numbers in the C file output -* 06 August 2013, L. Shustek, V1.6 -* -Changed to allow compilation and execution in 64-bit environments -* by using C99 standard intN_t and uintN_t types for MIDI structures, -* and formatting specifications like "PRId32" instead of "ld". -* 04 April 2015, L. Shustek, V1.7 -* -Made friendlier to other compilers: import source of strlcpy and strlcat, -* fixed various type mismatches that the LCC compiler didn't fret about. -* Generate "const" for data initialization for compatibility with Arduino IDE v1.6.x. -* 23 January 2016, D. Blackketter, V1.8 -* -Fix warnings and errors building on Mac OS X via "gcc miditones.c" -* 25 January 2016, D. Blackketter, Paul Stoffregen, V1.9 -* -Merge in velocity output option from Arduino/Teensy Audio Library -* 26 June 2016, L. Shustek, V1.10 -* -Fix overflow problem in calculating long delays. (Thanks go to Tiago Rocha.) -* In the process I discover and work around an LCC 32-bit compiler bug. -*/ -#define VERSION "1.10" - - -/*-------------------------------------------------------------------------------- -* +/********************************************************************************************* * -* About MIDITONES +* MIDITONES: Convert a MIDI file into a simple bytestream of notes * * -* MIDITONES converts a MIDI music file into a much simplified stream of commands, -* so that a version of the music can be played on a synthesizer having only -* tone generators without any volume or tone controls. +* MIDITONES converts a MIDI music file into a much simplified stream of commands, so that +* the music can easily be played on a small microcontroller-based synthesizer that has +* only simple tone generators. This is on github at www.github.com/LenShustek/miditones. * -* Volume ("velocity") and instrument specifications in the MIDI files are generally -* discarded. All the tracks are prcoessed and merged into a single time-ordered -* stream of "note on", "note off", and "delay" commands. +* Volume ("velocity") and instrument information in the MIDI file can either be +* discarded or kept. All the tracks are prcoessed and merged into a single time-ordered +* stream of "note on", "note off", "change instrument" and "delay" commands. * -* This was written for the "Playtune" Arduino library, which plays polyphonic music -* using up to 6 tone generators run by the timers on the processor. See the separate -* documentation for Playtune. But MIDITONES may prove useful for other tone -* generating systems. +* This was written for the "Playtune" series of Arduino and Teensy microcontroller +* synthesizers. See the separate documentation for the various Playtune.players at +* www.github.com/LenShustek/arduino-playtune +* www.github.com/LenShustek/ATtiny-playtune +* www.github.com/LenShustek/Playtune_poll +* www.github.com/LenShustek/Playtune_samp +* MIDITONES may also prove useful for other simple music synthesizers.. * * The output can be either a C-language source code fragment that initializes an * array with the command bytestream, or a binary file with the bytestream itself. * -* MIDITONES is written in standard ANSI C (plus strlcpy and strlcat functions), and -* is meant to be executed from the command line. There is no GUI interface. +* MIDITONES is written in standard ANSI C and is meant to be executed from the +* command line. There is no GUI interface. * -* The MIDI file format is complicated, and this has not been tested on a very -* wide variety of file types. In particular, we have tested only format type "1", -* which seems to be what most of them are. Let me know if you find MIDI files -* that it won't digest and I'll see if I can fix it. - -* This has been tested only on a little-endian PC, but I think it should work on -* big-endian processors too. Note that the MIDI file format is inherently -* big-endian. +* The MIDI file format is complicated, and this has not been tested on all of its +* variations. In particular we have tested only format type "1", which seems +* to be what most of them are. Let me know if you find MIDI files that it +* won't digest and I'll see if I can fix it. +* +* There is a companion program in the same repository called Miditones_scroll +* that can convert the bytestream generated by MIDITONES into a piano-player +* like listing for debugging or annotation. See the documentation in the +* beginning of its source code. * * -* ***** The command line ***** +* ***** The MIDITONES command line ***** * * To convert a MIDI file called "chopin.mid" into a command bytestream, execute * @@ -109,41 +49,57 @@ * * The general form for command line execution is this: * -* miditones [-p] [-lg] [-lp] [-s1] [-tn] [-b] [-cn] [-kn] [-v] +* miditones * * The 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. +* +* The input file is .mid The output filename(s) +* are the base file name with .c, .bin, and/or .log extensions. +* +* +* The following commonly-used command-line options can be specified: +* +* -v Add velocity (volume) information to the output bytestream +* +* -i Add instrument change commands to the output bytestream +* +* -pt Translate notes in the MIDI percussion track to note numbers 128..255 +* and assign them to a tone generator as usual. +* +* -d Generate a self-describing file header that says which optional bytestream +* fields are present. This is highly recommended if you are using later +* Playtune players that can check the header to know what data to expect. +* +* -b Generate a binary file with the name .bin, instead of a +* C-language source file with the name .c. * -* The input file is the base name with the extension ".mid". The output filename(s) -* are the base name with ".c", ".bin", and/or ".log" extensions. +* -tn Generate the bytestream so that at most n tone generators are used. +* The default is 6 tone generators, and the maximum is 16. The program +* will report how many notes had to be discarded because there weren't +* enough tone generators. +* +* +* The best combination of options to use with the later Playtune music players is: +* -v -i -pt -d * * -* The following command-line options can be specified: +* The following are lesser-used command-line options: * -* -p Only parse the MIDI file; don't generate an output file. +* -p Only parse the MIDI file, and don't generate an output file. * Tracks are processed sequentially instead of being merged into chronological order. -* This is mostly useful when generating a log to debug MIDI file parsing problems. +* This is mostly useful for debugging MIDI file parsing problems. * * -lp Log input file parsing information to the .log file * * -lg Log output bytestream generation information to the .log file * +* -nx Put about "x" items on each line of the C file output +* * -sn Use bytestream generation strategy "n". * Two strategies are currently implemented: -* 1: favor track 1 notes instead of all tracks equally -* 2: try to keep each track to its own tone generator -* -* -tn Generate the bytestream so that at most n tone generators are used. -* The default is 6 tone generators, and the maximum is 16. -* The program will report how many notes had to be discarded because there -* weren't enough tone generators. Note that for the Arduino Playtunes -* library, it's ok to have the bytestream use more tone genreators than -* exist on your processor because any extra notes will be ignored, although -* it does make the file bigger than necessary . Of course, too many ignored -* notes will make the music sound really strange! -* -* -b Generate a binary file with the name .bin, instead of a -* C-language source file with the name .c. +* 1:favor track 1 notes instead of all tracks equally +* 2:try to keep each track to its own tone generator * * -cn Only process the channel numbers whose bits are on in the number "n". * For example, -c3 means "only process channels 0 and 1" @@ -151,31 +107,39 @@ * -kn Change the musical key of the output by n chromatic notes. * -k-12 goes one octave down, -k12 goes one octave up, etc. * -* -v Add velocity information to output +* -pi Ignore notes in the MIDI percussion track 9 (also called 10 by some) +* +* -dp Generate IDE-dependent C code to define PROGMEM +* +* -h Give command-line help. * * * ***** The score bytestream ***** * -* The generated bytestream is a series of commands that turn notes on and off, and -* start delays until the next note change. Here are the details, with numbers -* shown in hexadecimal. +* The generated bytestream is a series of commands that turn notes on and off, +* maybe change instruments, and begin delays until the next note change. +* Here are the details, with numbers shown in hexadecimal. * * If the high-order bit of the byte is 1, then it is one of the following commands: * -* 9t nn [vv] Start playing note nn on tone generator t. Generators are numbered -* starting with 0. The notes numbers are the MIDI numbers for the chromatic +* 9t nn [vv] +* Start playing note nn on tone generator t. Generators are numbered +* starting with 0. The note numbers are the MIDI numbers for the chromatic * scale, with decimal 60 being Middle C, and decimal 69 being Middle A (440 Hz). -* If the -v option is enabled, a second byte is added to indicate velocity. +* If the -v option was given, a second byte is added to indicate note volume. * * 8t Stop playing the note on tone generator t. * -* F0 End of score: stop playing. +* Ct ii Change tone generator t to play instrument ii from now on. This will only +* be generated if the -i option was given. +* +* F0 End of score; stop playing. * -* E0 End of score: start playing again from the beginning. +* E0 End of score; start playing again from the beginning. * (Shown for completeness; MIDITONES won't generate this.) * * If the high-order bit of the byte is 0, it is a command to delay for a while until -* the next note change.. The other 7 bits and the 8 bits of the following byte are +* the next note change. The other 7 bits and the 8 bits of the following byte are * interpreted as a 15-bit big-endian integer that is the number of milliseconds to * wait before processing the next command. For example, * @@ -184,15 +148,176 @@ * would cause a delay of 0x07d0 = 2000 decimal millisconds, or 2 seconds. Any tones * that were playing before the delay command will continue to play. * +* If the -d option is specified, the bytestream begins with a little header that tells +* what optional information will be in the data. This makes the file more self-describing, +* and allows music players to adapt to different kinds of files. The later Playtune +* players do that. The header looks like this: +* +* 'Pt' Two ascii characters that signal the presence of the header +* nn The length (in one byte) of the entire header, 6..255 +* ff1 A byte of flag bits, three of which are currently defined: +* 80 velocity information is present +* 40 instrument change information is present +* 20 translated percussion notes are present +* ff2 Another byte of flags, currently undefined +* tt The number (in one byte) of tone generators actually used in this music. +* +* Any subsequent header bytes covered by the count, if present, are currently undefined +* and should be ignored by players. * * Len Shustek, 4 Feb 2011 and later * -*----------------------------------------------------------------------------------*/ +*---------------------------------------------------------------------------------------- +* The MIT License (MIT) +* Copyright (c) 2011,2013,2015,2016, 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 +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR +* IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*********************************************************************************************/ +// formatted with: indent miditones.c -br -brf -brs -ce -npsl -nut -i3 -l100 -lc100 + +/* +* Change log +* 19 January 2011, L.Shustek, V1.0 +* -Initial release. +* 26 February 2011, L. Shustek, V1.1 +* -Expand the documentation generated in the output file. +* -End the binary output file with an "end of score" command. +* -Fix bug: Some "stop note" commands were generated too early. +* 04 March 2011, L. Shustek, V1.2 +* -Minor error message rewording. +* 13 June 2011, L. Shustek, V1.3 +* -Add -s2 strategy to try to keep each track on its own tone generator +* for when there are separate speakers. This obviously works only when +* each track is monophonic. (Suggested by Michal Pustejovsky) +* 20 November 2011, L. Shustek, V1.4 +* -Add -cn option to mask which channels (tracks) to process +* -Add -kn option to change key +* Both of these are in support of music-playing on my Tesla Coil. +* 05 December 2011, L. Shustek, V1.5 +* -Fix command line parsing error for option -s1 +* -Display the commandline in the C file output +* -Change to decimal instead of hex for note numbers in the C file output +* 06 August 2013, L. Shustek, V1.6 +* -Changed to allow compilation and execution in 64-bit environments +* by using C99 standard intN_t and uintN_t types for MIDI structures, +* and formatting specifications like "PRId32" instead of "ld". +* 04 April 2015, L. Shustek, V1.7 +* -Made friendlier to other compilers: import source of strlcpy and strlcat, +* fixed various type mismatches that the LCC compiler didn't fret about. +* Generate "const" for data initialization for compatibility with Arduino IDE v1.6.x. +* 23 January 2016, D. Blackketter, V1.8 +* -Fix warnings and errors building on Mac OS X via "gcc miditones.c" +* 25 January 2016, D. Blackketter, Paul Stoffregen, V1.9 +* -Merge in velocity output option from Arduino/Teensy Audio Library +* 26 June 2016, L. Shustek, V1.10 +* -Fix overflow problem in calculating long delays. (Thanks go to Tiago Rocha.) +* In the process I discover and work around an LCC 32-bit compiler bug. +* 14 August 2016: L. Shustek, V1.11 +* -Fix our interpretation of MIDI "running status": it applies only to MIDI events +* (8x through Ex), not, as we thought, also to Sysex (Fx) or Meta (FF) events. +* -Improve parsing of text events for the log. +* -Change log file note and patch numbers, etc., to decimal. +* -Document a summary of the MIDI file format so I don't have to keep looking it up. +* -Add -pi and -pt options to ignore or translate the MIDI percussion track 9. +* -Remove string.h for more portability; add strlength(). +* -Add -i option for recording instrument types.in the bytestream. +* -Add -d option for generting a file description header. +* -Add -dp option to make generating the PROGMEM definition optional +* -Add -n option to specify number of items per output line +* -Do better error checking on options +* -Reformat option help +* +*/ +#define VERSION "1.11" + +/*-------------------------------------------------------------------------------------------- + +A CONCISE SUMMARY OF MIDI FILE FORMAT +L. Shustek, 16 July 2016. +Gleaned from http://www.music.mcgill.ca/~ich/classes/mumt306/StandardMIDIfileformat.html + +Notation: + is 1-4 bytes of 7-bit data, concatenated into one 7- to 28-bit number. The high bit of the last byte is 0. +lower case letters are hex digits. If preceeded by 0, only low 7 bits are used. +"xx" are ascii text characters +{xxx}... means indefinite repeat of xxx + +A MIDI file is: header_chunk {track_chunk}... + +header_chunk +"MThd" 00000006 ffff nnnn dddd + +track_chunk + +"MTrk" llllllll { track_event}... + +"running status" track_event + +0x to 7x: assume a missing 8n to En event code which is the same as the last MIDI-event track_event + +MIDI-event track_event + +8n 0kk 0vv note off, channel n, note kk, velocity vv +9n 0kk 0vv note on, channel n, note kk, velocity vv +An 0kk 0vv key pressure, channel n, note kk, pressure vv +Bn 0cc 0vv control value change, channel n, controller cc, new value vv +Cn 0pp program patch (instrument) change, channel n, new program pp +Dn 0vv channel pressure, channel n, pressure vv +En 0ll 0mm pitch wheel change, value llmm + +Note that channel 9 (called 10 by some programs) is used for percussion, particularly notes 35 to 81. + +Sysex event track_event + +F0 0ii {0dd}... F7 system-dependent data for manufacture ii. See www.gweep.net/~prefect/eng/reference/protocol/midispec.html +F2 0ll 0mm song position pointer +F3 0ss song select +F6 tune request +F7 end of system-dependent data +F8 timing clock sync +FA start playing +FB continue playing +FC stop playing +FE active sensing (hearbeat) + +Meta event track_event + +FF 00 02 ssss specify sequence number +FF 01 "xx"... arbitrary text +FF 02 "xx"... copyright notice +FF 03 "xx"... sequence or track name +FF 04 "xx"... instrument name +FF 05 "xx"... lyric to be sung +FF 06 "xx"... name of marked point in the score +FF 07 "xx"... description of cue point in the score +FF 20 01 0c default channel for subsequent events without a channel is c +FF 2F 00 end of track +FF 51 03 tttttt set tempo in microseconds per quarter-note +FF 54 05 hhmmssfrff set SMPTE time to start the track +FF 58 04 nnddccbb set time signature +FF 59 02 sfmi set key signature +FF 7F data sequencer-specific data + +--------------------------------------------------------------------------------------------*/ #include #include -#include #include #include #include @@ -202,346 +327,452 @@ /*********** MIDI file header formats *****************/ struct midi_header { - int8_t MThd[4]; - uint32_t header_size; - uint16_t format_type; - uint16_t number_of_tracks; - uint16_t time_division; + int8_t MThd[4]; + uint32_t header_size; + uint16_t format_type; + uint16_t number_of_tracks; + uint16_t time_division; }; struct track_header { - int8_t MTrk[4]; - uint32_t track_size; + int8_t MTrk[4]; + uint32_t track_size; }; /*********** Global variables ******************/ -#define MAX_TONEGENS 16 /* max tone generators: tones we can play simultaneously */ -#define DEFAULT_TONEGENS 6 /* default number of tone generators */ -#define MAX_TRACKS 24 /* max number of MIDI tracks we will process */ +#define MAX_TONEGENS 16 /* max tone generators: tones we can play simultaneously */ +#define DEFAULT_TONEGENS 6 /* default number of tone generators */ +#define MAX_TRACKS 24 /* max number of MIDI tracks we will process */ +#define PERCUSSION_TRACK 9 /* the track MIDI uses for percussion sounds */ -bool loggen, logparse, parseonly, strategy1, strategy2, binaryoutput, velocityoutput; +bool loggen, logparse, parseonly, strategy1, strategy2, binaryoutput, define_progmem, + velocityoutput, instrumentoutput, percussion_ignore, percussion_translate, do_header; FILE *infile, *outfile, *logfile; uint8_t *buffer, *hdrptr; unsigned long buflen; int num_tracks; int tracks_done = 0; +int outfile_maxitems = 25; int outfile_itemcount = 0; int num_tonegens = DEFAULT_TONEGENS; int num_tonegens_used = 0; +int instrument_changes = 0; +int note_on_commands = 0; unsigned channel_mask = 0xffff; // bit mask of channels to process -int keyshift = 0; // optional chromatic note shift for output file +int keyshift = 0; // optional chromatic note shift for output file long int outfile_bytecount = 0; unsigned int ticks_per_beat = 240; unsigned long timenow = 0; -unsigned long tempo; /* current tempo in usec/qnote */ +unsigned long tempo; /* current tempo in usec/qnote */ + +struct tonegen_status { /* current status of a tone generator */ + bool playing; /* is it playing? */ + int track; /* if so, which track is the note from? */ + int note; /* what note is playing? */ + int instrument; /* what instrument? */ +} tonegen[MAX_TONEGENS] = { + { + 0} +}; -struct tonegen_status { /* current status of a tone generator */ - bool playing; /* is it playing? */ - int track; /* if so, which track is the note from? */ - int note; /* what note is playing? */ -} -tonegen [MAX_TONEGENS] = { - {0}}; - -struct track_status { /* current processing point of a MIDI track */ - uint8_t *trkptr; /* ptr to the next note change */ - uint8_t *trkend; /* ptr past the end of the track */ - unsigned long time; /* what time we're at in the score */ - unsigned long tempo; /* the tempo last set, in usec/qnote */ - unsigned int preferred_tonegen; /* for strategy2: try to use this generator */ - unsigned char cmd; /* CMD_xxxx next to do */ - unsigned char note; /* for which note */ - unsigned char velocity; - unsigned char last_event; /* the last event, for MIDI's "running status" */ - bool tonegens[MAX_TONEGENS];/* which tone generators our notes are playing on */ -} -track[MAX_TRACKS] = { - {0}}; +struct track_status { /* current processing point of a MIDI track */ + uint8_t *trkptr; /* ptr to the next note change */ + uint8_t *trkend; /* ptr past the end of the track */ + unsigned long time; /* what time we're at in the score */ + unsigned long tempo; /* the tempo last set, in usec per qnote */ + unsigned int preferred_tonegen; /* for strategy2, try to use this generator */ + unsigned char cmd; /* CMD_xxxx next to do */ + unsigned char note; /* for which note */ + unsigned char chan; /* from which channel it was */ + unsigned char velocity; + unsigned char last_event; /* the last event, for MIDI's "running status" */ + bool tonegens[MAX_TONEGENS]; /* which tone generators our notes are playing on */ +} track[MAX_TRACKS] = { + { + 0} +}; +int midi_chan_instrument[16] = { + 0 +}; /* which instrument is currently being played on each channel */ /* output bytestream commands, which are also stored in track_status.cmd */ -#define CMD_PLAYNOTE 0x90 /* play a note: low nibble is generator #, note is next byte */ -#define CMD_STOPNOTE 0x80 /* stop a note: low nibble is generator # */ -#define CMD_RESTART 0xe0 /* restart the score from the beginning */ -#define CMD_STOP 0xf0 /* stop playing */ +#define CMD_PLAYNOTE 0x90 /* play a note: low nibble is generator #, note is next byte */ +#define CMD_STOPNOTE 0x80 /* stop a note: low nibble is generator # */ +#define CMD_INSTRUMENT 0xc0 /* change instrument; low nibble is generator #, instrument is next byte */ +#define CMD_RESTART 0xe0 /* restart the score from the beginning */ +#define CMD_STOP 0xf0 /* stop playing */ /* if CMD < 0x80, then the other 7 bits and the next byte are a 15-bit number of msec to delay */ /* these other commands stored in the track_status.com */ -#define CMD_TEMPO 0xFE /* tempo in usec per quarter note ("beat") */ -#define CMD_TRACKDONE 0xFF /* no more data left in this track */ +#define CMD_TEMPO 0xFE /* tempo in usec per quarter note ("beat") */ +#define CMD_TRACKDONE 0xFF /* no more data left in this track */ + + +struct file_hdr_t { /* what the optional file header looks like */ + char id1; // 'P' + char id2; // 't' + unsigned char hdr_length; // length of whole file header + unsigned char f1; // flag byte 1 + unsigned char f2; // flag byte 2 + unsigned char num_tgens; // how many tone generators are used by this score +} file_header = { +'P', 't', sizeof (struct file_hdr_t), 0, 0, MAX_TONEGENS}; +#define HDR_F1_VOLUME_PRESENT 0x80 +#define HDR_F1_INSTRUMENTS_PRESENT 0x40 +#define HDR_F1_PERCUSSION_PRESENT 0x20 +long int file_header_num_tgens_position; + /************** command-line processing *******************/ -void SayUsage(char *programName){ - static char *usage[] = { - "Convert MIDI files to an Arduino PLAYTUNE bytestream", - "miditones [-p] [-lg] [-lp] [-s1] [-tn] [-v] ", - " -p parse only, don't generate bytestream", - " -lp log input parsing", - " -lg log output generation", - " -s1 strategy 1: favor track 1", - " -s2 strategy 2: try to assign tracks to specific tone generators", - " -tn use at most n tone generators (default is 6, max is 16)", - " -b binary file output instead of C source text", - " -cn mask for which tracks to process, e.g. -c3 for only 0 and 1", - " -kn key shift in chromatic notes, positive or negative", - " -v include velocity data in play note commands", - "input file: .mid", - "output file: .bin or .c", - "log file: .log", - "" }; - int i=0; - while (usage[i][0] != '\0') fprintf(stderr, "%s\n", usage[i++]); +void SayUsage (char *programName) { + static char *usage[] = { + "Convert MIDI files to an Arduino PLAYTUNE bytestream", + "", + "Use: miditones ", + " input file will be .mid", + " output file will be .bin or .c", + " log file will be .log", + "", + "Commonly-used options:", + " -v include velocity data", + " -i include instrument change commands", + " -pt translate notes in the percussion track to notes 129 to 255", + " -d include a self-describing file header", + " -b generate binary file output instead of C source text", + " -tn use at most n tone generators (default is 6, max is 16)", + "", + " The best options for later Playtune music players are: -v -i -pt -d", + "", + "Lesser-used command-line options:", + " -p parse only, don't generate bytestream", + " -lp log input parsing", + " -lg log output generation", + " -nx Put about x items on each line of the C file output", + " -s1 strategy 1: favor track 1", + " -s2 strategy 2: try to assign tracks to specific tone generators", + " -cn mask for which tracks to process, e.g. -c3 for only 0 and 1", + " -kn key shift in chromatic notes, positive or negative", + " -pi ignore notes in the percussion track (9)", + " -dp define PROGMEM in output C code", + NULL + }; + int i = 0; + while (usage[i] != NULL) + fprintf (stderr, "%s\n", usage[i++]); } -int HandleOptions(int argc,char *argv[]) { - /* returns the index of the first argument that is not an option; i.e. - does not start with a dash or a slash*/ - - int i,firstnonoption=0; - - /* --- The following skeleton comes from C:\lcc\lib\wizard\textmode.tpl. */ - for (i=1; i< argc;i++) { - if (argv[i][0] == '/' || argv[i][0] == '-') { - switch (toupper(argv[i][1])) { - case 'H': - case '?': - SayUsage(argv[0]); - exit(1); - case 'L': - if (toupper(argv[i][2]) == 'G') loggen = true; - else if (toupper(argv[i][2]) == 'P') logparse = true; - else goto opterror; - break; - case 'P': - parseonly = true; - break; - case 'B': - binaryoutput = true; - break; - case 'V': - velocityoutput = true; - break; - case 'S': - if (argv[i][2] == '1') strategy1 = true; - else if (argv[i][2] == '2') strategy2 = true; - else goto opterror; - break; - case 'T': - if (sscanf(&argv[i][2],"%d",&num_tonegens) != 1 || num_tonegens <1 || num_tonegens > MAX_TONEGENS) goto opterror; - printf("Using %d tone generators.\n", num_tonegens); - break; - case 'C': - if (sscanf(&argv[i][2],"%d",&channel_mask) != 1 || channel_mask > 0xffff) goto opterror; - printf("Channel (track) mask is %04X.\n", channel_mask); - break; - case 'K': - if (sscanf(&argv[i][2],"%d",&keyshift) != 1 || keyshift < -100 || keyshift > 100) goto opterror; - printf("Using keyshift %d.\n", keyshift); - break; - - /* add more option switches here */ -opterror: - default: - fprintf(stderr,"unknown option: %s\n",argv[i]); - SayUsage(argv[0]); - exit(4); + +int HandleOptions (int argc, char *argv[]) { +/* returns the index of the first argument that is not an option; i.e. +does not start with a dash or a slash*/ + + int i, nch, firstnonoption = 0; + +/* --- The following skeleton comes from C:\lcc\lib\wizard\textmode.tpl. */ + for (i = 1; i < argc; i++) { + if (argv[i][0] == '/' || argv[i][0] == '-') { + switch (toupper (argv[i][1])) { + case 'H': + case '?': + SayUsage (argv[0]); + exit (1); + case 'L': + if (toupper (argv[i][2]) == 'G') + loggen = true; + else if (toupper (argv[i][2]) == 'P') + logparse = true; + else + goto opterror; + if (argv[i][3] != '\0') + goto opterror; + break; + case 'P': + if (argv[i][2] == '\0') + parseonly = true; + else if (toupper (argv[i][2]) == 'I') + percussion_ignore = true; + else if (toupper (argv[i][2]) == 'T') + percussion_translate = true; + else + goto opterror; + if (argv[i][3] != '\0') + goto opterror; + break; + case 'B': + binaryoutput = true; + if (argv[i][2] != '\0') + goto opterror; + break; + case 'V': + velocityoutput = true; + if (argv[i][2] != '\0') + goto opterror; + break; + case 'I': + instrumentoutput = true; + if (argv[i][2] != '\0') + goto opterror; + break; + case 'S': + if (argv[i][2] == '1') + strategy1 = true; + else if (argv[i][2] == '2') + strategy2 = true; + else + goto opterror; + if (argv[i][3] != '\0') + goto opterror; + break; + case 'T': + if (sscanf (&argv[i][2], "%d%n", &num_tonegens, &nch) != 1 + || num_tonegens < 1 || num_tonegens > MAX_TONEGENS) + goto opterror; + printf ("Using %d tone generators.\n", num_tonegens); + if (argv[i][2 + nch] != '\0') + goto opterror; + break; + case 'N': + if (sscanf (&argv[i][2], "%d%n", &outfile_maxitems, &nch) != 1 || outfile_maxitems < 1) + goto opterror; + if (argv[i][2 + nch] != '\0') + goto opterror; + break; + case 'C': + if (sscanf (&argv[i][2], "%d%n", &channel_mask, &nch) != 1 || channel_mask > 0xffff) + goto opterror; + printf ("Channel (track) mask is %04X.\n", channel_mask); + if (argv[i][2 + nch] != '\0') + goto opterror; + break; + case 'K': + if (sscanf (&argv[i][2], "%d%n", &keyshift, &nch) != 1 || keyshift < -100 + || keyshift > 100) + goto opterror; + printf ("Using keyshift %d.\n", keyshift); + if (argv[i][2 + nch] != '\0') + goto opterror; + break; + case 'D': + if (argv[i][2] == '\0') { + do_header = true; + break; } - } - else { - firstnonoption = i; + if (toupper (argv[i][2]) == 'P') + define_progmem = true; + else + goto opterror; + if (argv[i][3] != '\0') + goto opterror; break; - } - } - return firstnonoption; + /* add more option switches here */ + opterror: + default: + fprintf (stderr, "\n*** unknown option: %s\n\n", argv[i]); + SayUsage (argv[0]); + exit (4); + } + } else { + firstnonoption = i; + break; + } + } + return firstnonoption; } -void print_command_line (int argc,char *argv[]) { - int i; - fprintf(outfile, "// command line: "); - for (i=0; i< argc; i++) fprintf(outfile,"%s ",argv[i]); - fprintf(outfile, "\n"); +void print_command_line (int argc, char *argv[]) { + int i; + fprintf (outfile, "// command line: "); + for (i = 0; i < argc; i++) + fprintf (outfile, "%s ", argv[i]); + fprintf (outfile, "\n"); } /**************** utility routines **********************/ +/* portable string length */ +int strlength (const char *str) { + int i; + for (i = 0; str[i] != '\0'; ++i); + return i; +} /* safe string copy */ -size_t miditones_strlcpy(char *dst, const char *src, size_t siz) { - char *d = dst; - const char *s = src; - size_t n = siz; - /* Copy as many bytes as will fit */ - if (n != 0) - { - while (--n != 0) - { - if ((*d++ = *s++) == '\0') - break; - } - } - /* Not enough room in dst, add NUL and traverse rest of src */ - if (n == 0) - { - if (siz != 0) - *d = '\0'; /* NUL-terminate dst */ - while (*s++) - ; - } - return (s - src - 1); /* count does not include NUL */ +size_t miditones_strlcpy (char *dst, const char *src, size_t siz) { + char *d = dst; + const char *s = src; + size_t n = siz; +/* Copy as many bytes as will fit */ + if (n != 0) { + while (--n != 0) { + if ((*d++ = *s++) == '\0') + break; + } + } +/* Not enough room in dst, add NUL and traverse rest of src */ + if (n == 0) { + if (siz != 0) + *d = '\0'; /* NUL-terminate dst */ + while (*s++); + } + return (s - src - 1); /* count does not include NUL */ } /* safe string concatenation */ -size_t miditones_strlcat(char *dst, const char *src, size_t siz) { - char *d = dst; - const char *s = src; - size_t n = siz; - size_t dlen; - /* Find the end of dst and adjust bytes left but don't go past end */ - while (n-- != 0 && *d != '\0') - d++; - dlen = d - dst; - n = siz - dlen; - if (n == 0) - return (dlen + strlen(s)); - while (*s != '\0') - { - if (n != 1) - { - *d++ = *s; - n--; - } - s++; - } - *d = '\0'; - return (dlen + (s - src)); /* count does not include NUL */ +size_t miditones_strlcat (char *dst, const char *src, size_t siz) { + char *d = dst; + const char *s = src; + size_t n = siz; + size_t dlen; +/* Find the end of dst and adjust bytes left but don't go past end */ + while (n-- != 0 && *d != '\0') + d++; + dlen = d - dst; + n = siz - dlen; + if (n == 0) + return (dlen + strlength (s)); + while (*s != '\0') { + if (n != 1) { + *d++ = *s; + n--; + } + s++; + } + *d = '\0'; + return (dlen + (s - src)); /* count does not include NUL */ } /* match a constant character sequence */ int charcmp (const char *buf, const char *match) { - int len, i; - len = strlen (match); - for (i=0; i MIDI file error at position %04X (%d): %s\n", (uint16_t)(bufptr-buffer), (uint16_t)(bufptr-buffer), msg); - /* print some bytes surrounding the error */ - ptr = bufptr - 16; - if (ptr < buffer) ptr = buffer; - for (; ptr <= bufptr+16 && ptr < buffer+buflen; ++ptr) fprintf (stderr, ptr==bufptr ? " [%02X] ":"%02X ", *ptr); - fprintf(stderr, "\n"); - exit(8); + unsigned char *ptr; + fprintf (stderr, "---> MIDI file error at position %04X (%d): %s\n", + (uint16_t) (bufptr - buffer), (uint16_t) (bufptr - buffer), msg); +/* print some bytes surrounding the error */ + ptr = bufptr - 16; + if (ptr < buffer) + ptr = buffer; + for (; ptr <= bufptr + 16 && ptr < buffer + buflen; ++ptr) + fprintf (stderr, ptr == bufptr ? " [%02X] " : "%02X ", *ptr); + fprintf (stderr, "\n"); + exit (8); } /* check that we have a specified number of bytes left in the buffer */ -void chk_bufdata(unsigned char *ptr, unsigned long int len) { - if ((unsigned)(ptr + len - buffer) > buflen) midi_error("data missing", ptr); +void chk_bufdata (unsigned char *ptr, unsigned long int len) { + if ((unsigned) (ptr + len - buffer) > buflen) + midi_error ("data missing", ptr); } - /* fetch big-endian numbers */ uint16_t rev_short (uint16_t val) { - return ((val&0xff)<<8) | ((val>>8)&0xff); + return ((val & 0xff) << 8) | ((val >> 8) & 0xff); } -uint32_t rev_long (uint32_t val){ - return (((rev_short((uint16_t)val) & 0xffff) << 16) | - (rev_short((uint16_t)(val >> 16)) & 0xffff)); +uint32_t rev_long (uint32_t val) { + return (((rev_short ((uint16_t) val) & 0xffff) << 16) | + (rev_short ((uint16_t) (val >> 16)) & 0xffff)); } /* account for new items in the non-binary output file and generate a newline every so often. */ void outfile_items (int n) { - outfile_bytecount += n; - outfile_itemcount += n; - if (!binaryoutput && outfile_itemcount > 20) { - fprintf (outfile, "\n"); - outfile_itemcount = 0; - } + outfile_bytecount += n; + outfile_itemcount += n; + if (!binaryoutput && outfile_itemcount > outfile_maxitems) { + fprintf (outfile, "\n"); + outfile_itemcount = 0; + } } /************** process the MIDI file header *****************/ void process_header (void) { - struct midi_header *hdr; - unsigned int time_division; - - chk_bufdata(hdrptr, sizeof(struct midi_header)); - hdr = (struct midi_header *) hdrptr; - if (!charcmp((char*)hdr->MThd,"MThd")) midi_error("Missing 'MThd'", hdrptr); - - num_tracks = rev_short(hdr->number_of_tracks); - - time_division = rev_short(hdr->time_division); - if (time_division < 0x8000) ticks_per_beat = time_division; - else ticks_per_beat = ((time_division >> 8) & 0x7f) /* SMTE frames/sec */ * (time_division & 0xff); /* ticks/SMTE frame */ - - if (logparse) { - fprintf (logfile, "Header size %" PRId32 "\n", rev_long(hdr->header_size)); - fprintf (logfile, "Format type %d\n", rev_short(hdr->format_type)); - fprintf (logfile, "Number of tracks %d\n", num_tracks); - fprintf (logfile, "Time division %04X\n", time_division); - fprintf (logfile, "Ticks/beat = %d\n", ticks_per_beat); - - } - hdrptr += rev_long(hdr->header_size) + 8; /* point past header to track header, presumably. */ - return; + struct midi_header *hdr; + unsigned int time_division; + + chk_bufdata (hdrptr, sizeof (struct midi_header)); + hdr = (struct midi_header *) hdrptr; + if (!charcmp ((char *) hdr->MThd, "MThd")) + midi_error ("Missing 'MThd'", hdrptr); + num_tracks = rev_short (hdr->number_of_tracks); + time_division = rev_short (hdr->time_division); + if (time_division < 0x8000) + ticks_per_beat = time_division; + else + ticks_per_beat = ((time_division >> 8) & 0x7f) /* SMTE frames/sec */ *(time_division & 0xff); /* ticks/SMTE frame */ + if (logparse) { + fprintf (logfile, "Header size %" PRId32 "\n", rev_long (hdr->header_size)); + fprintf (logfile, "Format type %d\n", rev_short (hdr->format_type)); + fprintf (logfile, "Number of tracks %d\n", num_tracks); + fprintf (logfile, "Time division %04X\n", time_division); + fprintf (logfile, "Ticks/beat = %d\n", ticks_per_beat); + } + hdrptr += rev_long (hdr->header_size) + 8; /* point past header to track header, presumably. */ + return; } /**************** Process a MIDI track header *******************/ void start_track (int tracknum) { - struct track_header *hdr; - unsigned long tracklen; - - chk_bufdata(hdrptr, sizeof(struct track_header)); - hdr = (struct track_header *) hdrptr; - if (!charcmp((char *)(hdr->MTrk),"MTrk")) midi_error("Missing 'MTrk'", hdrptr); - - tracklen = rev_long(hdr->track_size); - if (logparse) fprintf (logfile, "\nTrack %d length %ld\n", tracknum, tracklen); - hdrptr += sizeof (struct track_header); /* point past header */ - chk_bufdata(hdrptr, tracklen); - track[tracknum].trkptr = hdrptr; - hdrptr += tracklen; /* point to the start of the next track */ - track[tracknum].trkend = hdrptr; /* the point past the end of the track */ + struct track_header *hdr; + unsigned long tracklen; + + chk_bufdata (hdrptr, sizeof (struct track_header)); + hdr = (struct track_header *) hdrptr; + if (!charcmp ((char *) (hdr->MTrk), "MTrk")) + midi_error ("Missing 'MTrk'", hdrptr); + tracklen = rev_long (hdr->track_size); + if (logparse) + fprintf (logfile, "\nTrack %d length %ld\n", tracknum, tracklen); + hdrptr += sizeof (struct track_header); /* point past header */ + chk_bufdata (hdrptr, tracklen); + track[tracknum].trkptr = hdrptr; + hdrptr += tracklen; /* point to the start of the next track */ + track[tracknum].trkend = hdrptr; /* the point past the end of the track */ } /* Get a MIDI-style variable-length integer */ -unsigned long get_varlen (uint8_t **ptr) { - /* Get a 1-4 byte variable-length value and adjust the pointer past it. - These are a succession of 7-bit values with a MSB bit of zero marking the end */ - - unsigned long val; - int i, byte; - - val = 0; - for (i=0; i<4; ++i){ - byte = *(*ptr)++; - val = (val<<7) | (byte&0x7f); - if (!(byte&0x80)) return val; - } - return val; +unsigned long get_varlen (uint8_t ** ptr) { +/* Get a 1-4 byte variable-length value and adjust the pointer past it. +These are a succession of 7-bit values with a MSB bit of zero marking the end */ + + unsigned long val; + int i, byte; + + val = 0; + for (i = 0; i < 4; ++i) { + byte = *(*ptr)++; + val = (val << 7) | (byte & 0x7f); + if (!(byte & 0x80)) + return val; + } + return val; } @@ -551,446 +782,591 @@ unsigned long get_varlen (uint8_t **ptr) { then record that information in the track status block and return. */ void find_note (int tracknum) { - unsigned long int delta_time; - int event, chan; - int i; - int note, velocity; - int meta_cmd, meta_length; - unsigned long int sysex_length; - struct track_status *t; - - /* process events */ - - t = &track[tracknum]; /* our track status structure */ - while (t->trkptr < t->trkend) { - - delta_time = get_varlen(&t->trkptr); - if (logparse) { - fprintf (logfile, "trk %d ", tracknum); - if (delta_time) { - fprintf (logfile, "delta time %4ld, ", delta_time); - } else { - fprintf (logfile, " "); + unsigned long int delta_time; + int event, chan; + int i; + int note, velocity, controller, pressure, pitchbend, instrument; + int meta_cmd, meta_length; + unsigned long int sysex_length; + struct track_status *t; + char *tag; + +/* process events */ + + t = &track[tracknum]; /* our track status structure */ + while (t->trkptr < t->trkend) { + + delta_time = get_varlen (&t->trkptr); + if (logparse) { + fprintf (logfile, "trk %d ", tracknum); + if (delta_time) { + fprintf (logfile, "delta time %4ld, ", delta_time); + } else { + fprintf (logfile, " "); + } + } + t->time += delta_time; + if (*t->trkptr < 0x80) + event = t->last_event; /* using "running status": same event as before */ + else { /* otherwise get new "status" (event type) */ + event = *t->trkptr++; + } + if (event == 0xff) { /* meta-event */ + meta_cmd = *t->trkptr++; + meta_length = *t->trkptr++; + switch (meta_cmd) { + case 0x00: + if (logparse) + fprintf (logfile, "sequence number %d\n", rev_short (*(unsigned short *) t->trkptr)); + break; + case 0x01: + tag = "description"; + goto show_text; + case 0x02: + tag = "copyright"; + goto show_text; + case 0x03: + tag = "track name"; + if (tracknum == 0 && !parseonly && !binaryoutput) { + /* Incredibly, MIDI has no standard for recording the name of the piece! + Track 0's "trackname" is often used for that so we output it to the C file as documentation. */ + fprintf (outfile, "// "); + for (i = 0; i < meta_length; ++i) { + int ch = t->trkptr[i]; + fprintf (outfile, "%c", isprint (ch) ? ch : '?'); + } + fprintf (outfile, "\n"); } - } - t->time += delta_time; - - if (*t->trkptr < 0x80) /* "running status" */ event = t->last_event;/* means same event as before */ - else { /* new "status" (event type) */ - event = *t->trkptr++; - t->last_event = event; - } - - if (event == 0xff) { /* meta-event */ - meta_cmd = *t->trkptr++; - meta_length = *t->trkptr++; - switch (meta_cmd) { - case 0x2f: - if (logparse) fprintf(logfile, "end of track\n"); - break; - case 0x00: - if (logparse) fprintf(logfile, "sequence number %d\n", rev_short(*(unsigned short *)t->trkptr)); - break; - case 0x20: - if (logparse) fprintf(logfile, "channel prefix %d\n", *t->trkptr); - break; - case 0x51: /* tempo: 3 byte big-endian integer! */ - t->cmd = CMD_TEMPO; - t->tempo = rev_long(*(unsigned long *)(t->trkptr-1)) & 0xffffffL; - if (logparse) fprintf(logfile, "set tempo %ld usec/qnote\n", t->tempo); - t->trkptr += meta_length; - return; - case 0x54: - if (logparse) fprintf(logfile, "SMPTE offset %08" PRIx32 "\n", rev_long(*(unsigned long *)t->trkptr)); - break; - case 0x58: - if (logparse) fprintf(logfile, "time signature %08" PRIx32 "\n", rev_long(*(unsigned long *)t->trkptr)); - break; - case 0x59: - if (logparse) fprintf(logfile, "key signature %04X\n", rev_short(*(unsigned short *)t->trkptr)); - break; - default: /* assume it is a string */ - if (logparse) { - fprintf(logfile, "meta cmd %02X, length %d, \"", meta_cmd, meta_length); - for (i=0; itrkptr[i]; - fprintf(logfile, "%c", isprint(ch) ? ch : '?'); - } - fprintf(logfile, "\"\n"); - } - if (tracknum==0 && meta_cmd==0x03 && !parseonly && !binaryoutput) { - /* Incredibly, MIDI has no standard for recording the name of the piece! - Track 0's "trackname" (meta 0x03) is sometimes used for that, so - we output it to the C file as documentation. */ - fprintf(outfile, "// "); - for (i=0; itrkptr[i]; - fprintf(outfile, "%c", isprint(ch) ? ch : '?'); - } - fprintf(outfile, "\n"); - } - break; + goto show_text; + case 0x04: + tag = "instrument name"; + goto show_text; + case 0x05: + tag = "lyric"; + goto show_text; + case 0x06: + tag = "marked point"; + goto show_text; + case 0x07: + tag = "cue point"; + show_text: + if (logparse) { + fprintf (logfile, "meta cmd %02X, length %d, %s: \"", meta_cmd, meta_length, tag); + for (i = 0; i < meta_length; ++i) { + int ch = t->trkptr[i]; + fprintf (logfile, "%c", isprint (ch) ? ch : '?'); + } + fprintf (logfile, "\"\n"); } + break; + case 0x20: + if (logparse) + fprintf (logfile, "channel prefix %d\n", *t->trkptr); + break; + case 0x2f: + if (logparse) + fprintf (logfile, "end of track\n"); + break; + case 0x51: /* tempo: 3 byte big-endian integer! */ + t->cmd = CMD_TEMPO; + t->tempo = rev_long (*(unsigned long *) (t->trkptr - 1)) & 0xffffffL; + if (logparse) + fprintf (logfile, "set tempo %ld usec/qnote\n", t->tempo); t->trkptr += meta_length; - } - - else if (event <0x80) midi_error("Unknown MIDI event type", t->trkptr); - - else { - chan = event & 0xf; - switch (event>>4) { - case 0x8: - t->note = *t->trkptr++; - velocity = *t->trkptr++; -note_off: - if (logparse) fprintf (logfile, "note %02X off, chan %d, velocity %d\n", t->note, chan, velocity); - if ((1<cmd = CMD_STOPNOTE; - return; /* stop processing and return */ - } - break; // else keep looking - case 0x9: - t->note = *t->trkptr++; - velocity = *t->trkptr++; - if (velocity == 0) /* some scores use note-on with zero velocity for off! */ goto note_off; - t->velocity = velocity; - if (logparse) fprintf (logfile, "note %02X on, chan %d, velocity %d\n", t->note, chan, velocity); - if ((1<cmd = CMD_PLAYNOTE; - return; /* stop processing and return */ - } - break; // else keep looking - case 0xa: - note = *t->trkptr++; - velocity = *t->trkptr++; - if (logparse) fprintf (logfile, "after-touch %02X, %02X\n", note, velocity); - break; - case 0xb: - note = *t->trkptr++; - velocity = *t->trkptr++; - if (logparse) fprintf (logfile, "control change %02X, %02X\n", note, velocity); - break; - case 0xc: - note = *t->trkptr++; - if (logparse) fprintf(logfile, "program patch %02X\n", note); - break; - case 0xd: - chan = *t->trkptr++; - if (logparse) fprintf(logfile, "channel after-touch %02X\n", chan); - break; - case 0xe: - note = *t->trkptr++; - velocity = *t->trkptr++; - if (logparse) fprintf(logfile, "pitch wheel change %02X, %02X\n", note, velocity); - break; - case 0xf: - sysex_length = get_varlen(&t->trkptr); - if (logparse) fprintf(logfile, "SysEx event %02X, %ld bytes\n", event, sysex_length); - t->trkptr += sysex_length; - break; - default: - midi_error("Unknown MIDI command", t->trkptr); + return; + case 0x54: + if (logparse) + fprintf (logfile, "SMPTE offset %08" PRIx32 "\n", + rev_long (*(unsigned long *) t->trkptr)); + break; + case 0x58: + if (logparse) + fprintf (logfile, "time signature %08" PRIx32 "\n", + rev_long (*(unsigned long *) t->trkptr)); + break; + case 0x59: + if (logparse) + fprintf (logfile, "key signature %04X\n", rev_short (*(unsigned short *) t->trkptr)); + break; + case 0x7f: + tag = "sequencer data"; + goto show_hex; + default: /* unknown meta command */ + tag = "???"; + show_hex: + if (logparse) { + fprintf (logfile, "meta cmd %02X, length %d, %s: ", meta_cmd, meta_length, tag); + for (i = 0; i < meta_length; ++i) + fprintf (logfile, "%02X ", t->trkptr[i]); + fprintf (logfile, "\n"); + } + + break; + } + t->trkptr += meta_length; + } + + else if (event < 0x80) + midi_error ("Unknown MIDI event type", t->trkptr); + + else { + if (event < 0xf0) + t->last_event = event; // remember "running status" if not meta or sysex event + chan = event & 0xf; + t->chan = chan; + switch (event >> 4) { + case 0x8: + t->note = *t->trkptr++; + velocity = *t->trkptr++; + note_off: + if (logparse) + fprintf (logfile, "note %d off, chan %d, velocity %d\n", t->note, chan, velocity); + if ((1 << chan) & channel_mask) { /* if we're processing this channel */ + t->cmd = CMD_STOPNOTE; + return; /* stop processing and return */ } - } - } - t->cmd = CMD_TRACKDONE; /* no more notes to process */ - ++tracks_done; + break; // else keep looking + case 0x9: + t->note = *t->trkptr++; + velocity = *t->trkptr++; + if (velocity == 0) /* some scores use note-on with zero velocity for off! */ + goto note_off; + t->velocity = velocity; + if (logparse) + fprintf (logfile, "note %d on, chan %d, velocity %d\n", t->note, chan, velocity); + if ((1 << chan) & channel_mask) { /* if we're processing this channel */ + t->cmd = CMD_PLAYNOTE; + return; /* stop processing and return */ + } + break; // else keep looking + case 0xa: + note = *t->trkptr++; + velocity = *t->trkptr++; + if (logparse) + fprintf (logfile, "after-touch %d, %d\n", note, velocity); + break; + case 0xb: + controller = *t->trkptr++; + velocity = *t->trkptr++; + if (logparse) + fprintf (logfile, "control change %d, %d\n", controller, velocity); + break; + case 0xc: + instrument = *t->trkptr++; + midi_chan_instrument[chan] = instrument; // record new instrument for this channel + if (logparse) + fprintf (logfile, "program patch %d\n", instrument); + break; + case 0xd: + pressure = *t->trkptr++; + if (logparse) + fprintf (logfile, "channel after-touch %d\n", pressure); + break; + case 0xe: + pitchbend = *t->trkptr++ | (*t->trkptr++ << 7); + if (logparse) + fprintf (logfile, "pitch wheel change %d\n", pitchbend); + break; + case 0xf: + sysex_length = get_varlen (&t->trkptr); + if (logparse) + fprintf (logfile, "SysEx event %d, %ld bytes\n", event, sysex_length); + t->trkptr += sysex_length; + break; + default: + midi_error ("Unknown MIDI command", t->trkptr); + } + } + } + t->cmd = CMD_TRACKDONE; /* no more notes to process */ + ++tracks_done; } /********************* main ****************************/ -int main(int argc,char *argv[]) { - int argno; - char *filebasename; +int main (int argc, char *argv[]) { + int argno; + char *filebasename; #define MAXPATH 120 - char filename[MAXPATH]; - - int tracknum; - int earliest_tracknum; - unsigned long earliest_time; - int notes_skipped = 0; - - printf("MIDITONES V%s, (C) 2011,2015,2016 Len Shustek\n", VERSION); - printf("See the source code for license information.\n\n"); - if (argc == 1) { /* no arguments */ - SayUsage(argv[0]); - return 1; - } - - /* process options */ - - argno = HandleOptions(argc,argv); - filebasename = argv[argno]; - - /* Open the log file */ - - if (logparse || loggen) { - miditones_strlcpy(filename, filebasename, MAXPATH); - miditones_strlcat(filename, ".log", MAXPATH); - logfile = fopen(filename, "w"); - if (!logfile) { - fprintf(stderr, "Unable to open log file %s", filename); - return 1; - } - } - - /* Open the input file */ - - miditones_strlcpy(filename, filebasename, MAXPATH); - miditones_strlcat(filename, ".mid", MAXPATH); - infile = fopen(filename, "rb"); - if (!infile) { - fprintf(stderr, "Unable to open input file %s", filename); - return 1; - } - - /* Read the whole input file into memory */ - - fseek(infile, 0, SEEK_END); /* find file size */ - buflen = ftell(infile); - fseek(infile, 0, SEEK_SET); - - buffer = (unsigned char *) malloc (buflen+1); - if (!buffer) { - fprintf(stderr, "Unable to allocate %ld bytes for the file", buflen); - return 1; - } - - fread(buffer, buflen, 1, infile); - fclose(infile); - if (logparse) fprintf(logfile, "Processing %s, %ld bytes\n", filename, buflen); - - /* Create the output file */ - - if (!parseonly) { - miditones_strlcpy(filename, filebasename, MAXPATH); - if (binaryoutput) { - miditones_strlcat(filename, ".bin", MAXPATH); - outfile = fopen(filename, "wb"); - } - else { - miditones_strlcat(filename, ".c", MAXPATH); - outfile = fopen(filename, "w"); - } - if (!outfile) { - fprintf(stderr, "Unable to open output file %s", filename); - return 1; - } - if (!binaryoutput) { /* create header of C file that initializes score data */ - time_t rawtime; - time (&rawtime); - fprintf(outfile, "// Playtune bytestream for file \"%s.mid\" ", filebasename); - fprintf(outfile, "created by MIDITONES V%s on %s", VERSION, asctime(localtime(&rawtime))); - print_command_line(argc,argv); - if (channel_mask != 0xffff) - fprintf(outfile, "// Only the masked channels were processed: %04X\n", channel_mask); - if (keyshift != 0) - fprintf(outfile, "// Keyshift was %d chromatic notes\n", keyshift); - fprintf(outfile, "#ifdef __AVR__\n"); - fprintf(outfile, "#include \n"); - fprintf(outfile, "#else\n"); - fprintf(outfile, "#define PROGMEM\n"); - fprintf(outfile, "#endif\n"); - fprintf(outfile, "const unsigned char PROGMEM score [] = {\n"); - } - } - - /* process the MIDI file header */ - - hdrptr = buffer; /* pointer to file and track headers */ - process_header (); - printf (" Processing %d tracks.\n", num_tracks); - if (num_tracks > MAX_TRACKS) midi_error ("Too many tracks", buffer); - - /* initialize processing of all the tracks */ - - for (tracknum=0; tracknum < num_tracks; ++tracknum) { - start_track (tracknum); /* process the track header */ - find_note (tracknum); /* position to the first note on/off */ - /* if we are in "parse only" mode, do the whole track, - so we do them one at a time instead of time-synchronized. */ - if (parseonly) while (track[tracknum].cmd != CMD_TRACKDONE) find_note(tracknum); - } - - /* Continue processing all tracks, in an order based on the simulated time. - This is not unlike multiway merging used for tape sorting algoritms in the 50's! */ - - tracknum = 0; - if (!parseonly) do { /* while there are still track notes to process */ - struct track_status *trk; - struct tonegen_status *tg; - int tgnum; - int count_tracks; - unsigned long delta_time, delta_msec; - - /* Find the track with the earliest event time, - and output a delay command if time has advanced. - - A potential improvement: If there are multiple tracks with the same time, - first do the ones with STOPNOTE as the next command, if any. That would - help avoid running out of tone generators. In practice, though, most MIDI - files do all the STOPNOTEs first anyway, so it won't have much effect. - */ - - earliest_time = 0x7fffffff; - - /* Usually we start with the track after the one we did last time (tracknum), - so that if we run out of tone generators, we have been fair to all the tracks. - The alternate "strategy1" says we always start with track 0, which means - that we favor early tracks over later ones when there aren't enough tone generators. - */ - - count_tracks = num_tracks; - if (strategy1) tracknum = num_tracks; /* beyond the end, so we start with track 0 */ - do { - if (++tracknum >= num_tracks) tracknum=0; + char filename[MAXPATH]; + int tracknum; + int earliest_tracknum; + unsigned long earliest_time; + int notes_skipped = 0; + + printf ("MIDITONES V%s, (C) 2011-2016 Len Shustek\n", VERSION); + if (argc == 1) { /* no arguments */ + SayUsage (argv[0]); + return 1; + } + +/* process options */ + + argno = HandleOptions (argc, argv); + filebasename = argv[argno]; + +/* Open the log file */ + + if (logparse || loggen) { + miditones_strlcpy (filename, filebasename, MAXPATH); + miditones_strlcat (filename, ".log", MAXPATH); + logfile = fopen (filename, "w"); + if (!logfile) { + fprintf (stderr, "Unable to open log file %s", filename); + return 1; + } + fprintf (logfile, "MIDITONES V%s log file\n", VERSION); + } + +/* Open the input file */ + + miditones_strlcpy (filename, filebasename, MAXPATH); + miditones_strlcat (filename, ".mid", MAXPATH); + infile = fopen (filename, "rb"); + if (!infile) { + fprintf (stderr, "Unable to open input file %s", filename); + return 1; + } + +/* Read the whole input file into memory */ + + fseek (infile, 0, SEEK_END); /* find file size */ + buflen = ftell (infile); + fseek (infile, 0, SEEK_SET); + buffer = (unsigned char *) malloc (buflen + 1); + if (!buffer) { + fprintf (stderr, "Unable to allocate %ld bytes for the file", buflen); + return 1; + } + fread (buffer, buflen, 1, infile); + fclose (infile); + if (logparse) + fprintf (logfile, "Processing %s, %ld bytes\n", filename, buflen); + +/* Create the output file */ + + if (!parseonly) { + miditones_strlcpy (filename, filebasename, MAXPATH); + if (binaryoutput) { + miditones_strlcat (filename, ".bin", MAXPATH); + outfile = fopen (filename, "wb"); + } else { + miditones_strlcat (filename, ".c", MAXPATH); + outfile = fopen (filename, "w"); + } + if (!outfile) { + fprintf (stderr, "Unable to open output file %s", filename); + return 1; + } + file_header.f1 = (velocityoutput ? HDR_F1_VOLUME_PRESENT : 0) + | (instrumentoutput ? HDR_F1_INSTRUMENTS_PRESENT : 0) + | (percussion_translate ? HDR_F1_PERCUSSION_PRESENT : 0); + file_header.num_tgens = num_tonegens; + if (!binaryoutput) { /* create header of C file that initializes score data */ + time_t rawtime; + time (&rawtime); + fprintf (outfile, "// Playtune bytestream for file \"%s.mid\" ", filebasename); + fprintf (outfile, "created by MIDITONES V%s on %s", VERSION, + asctime (localtime (&rawtime))); + print_command_line (argc, argv); + if (channel_mask != 0xffff) + fprintf (outfile, "// Only the masked channels were processed: %04X\n", channel_mask); + if (keyshift != 0) + fprintf (outfile, "// Keyshift was %d chromatic notes\n", keyshift); + if (define_progmem) { + fprintf (outfile, "#ifdef __AVR__\n"); + fprintf (outfile, "#include \n"); + fprintf (outfile, "#else\n"); + fprintf (outfile, "#define PROGMEM\n"); + fprintf (outfile, "#endif\n"); + } + fprintf (outfile, "const unsigned char PROGMEM score [] = {\n"); + if (do_header) { // write the C initialization for the file header + fprintf (outfile, "'P','t', 6, 0x%02X, 0x%02X, ", file_header.f1, file_header.f2); + fflush (outfile); + file_header_num_tgens_position = ftell (outfile); // remember where the number of tone generators is + fprintf (outfile, "%2d, // (Playtune file header)\n", file_header.num_tgens); + } + } else if (do_header) { // write the binary file header + for (int i = 0; i < sizeof (file_header); ++i) + fputc (((unsigned char *) &file_header)[i], outfile); + file_header_num_tgens_position = (char *) &file_header.num_tgens - (char *) &file_header; + } + } + +/* process the MIDI file header */ + + hdrptr = buffer; /* pointer to file and track headers */ + process_header (); + printf (" Processing %d tracks.\n", num_tracks); + if (num_tracks > MAX_TRACKS) + midi_error ("Too many tracks", buffer); + +/* initialize processing of all the tracks */ + + for (tracknum = 0; tracknum < num_tracks; ++tracknum) { + start_track (tracknum); /* process the track header */ + find_note (tracknum); /* position to the first note on/off */ + /* if we are in "parse only" mode, do the whole track, + so we do them one at a time instead of time-synchronized. */ + if (parseonly) + while (track[tracknum].cmd != CMD_TRACKDONE) + find_note (tracknum); + } + +/* Continue processing all tracks, in an order based on the simulated time. +This is not unlike multiway merging used for tape sorting algoritms in the 50's! */ + + tracknum = 0; + if (!parseonly) + do { /* while there are still track notes to process */ + struct track_status *trk; + struct tonegen_status *tg; + int tgnum; + int count_tracks; + unsigned long delta_time, delta_msec; + + /* Find the track with the earliest event time, + and output a delay command if time has advanced. + + A potential improvement: If there are multiple tracks with the same time, + first do the ones with STOPNOTE as the next command, if any. That would + help avoid running out of tone generators. In practice, though, most MIDI + files do all the STOPNOTEs first anyway, so it won't have much effect. + */ + + earliest_time = 0x7fffffff; + + /* Usually we start with the track after the one we did last time (tracknum), + so that if we run out of tone generators, we have been fair to all the tracks. + The alternate "strategy1" says we always start with track 0, which means + that we favor early tracks over later ones when there aren't enough tone generators. + */ + + count_tracks = num_tracks; + if (strategy1) + tracknum = num_tracks; /* beyond the end, so we start with track 0 */ + do { + if (++tracknum >= num_tracks) + tracknum = 0; trk = &track[tracknum]; if (trk->cmd != CMD_TRACKDONE && trk->time < earliest_time) { - earliest_time = trk->time; - earliest_tracknum = tracknum; + earliest_time = trk->time; + earliest_tracknum = tracknum; } - } - while (--count_tracks); - - tracknum = earliest_tracknum; /* the track we picked */ - trk = &track[tracknum]; + } + while (--count_tracks); - if (loggen) fprintf (logfile, "Earliest time is trk %d, time %ld\n", tracknum, earliest_time); - if (earliest_time < timenow) midi_error ("INTERNAL: time went backwards", trk->trkptr); + tracknum = earliest_tracknum; /* the track we picked */ + trk = &track[tracknum]; + if (loggen) + fprintf (logfile, "Earliest time is trk %d, time %ld\n", tracknum, earliest_time); + if (earliest_time < timenow) + midi_error ("INTERNAL: time went backwards", trk->trkptr); - /* If time has advanced, output a "delay" command */ + /* If time has advanced, output a "delay" command */ - delta_time = earliest_time - timenow; - if (delta_time) { + delta_time = earliest_time - timenow; + if (delta_time) { /* Convert ticks to milliseconds based on the current tempo */ unsigned long long temp; temp = ((unsigned long long) delta_time * tempo) / ticks_per_beat; - delta_msec = temp / 1000; // get around LCC compiler bug - if (loggen) fprintf (logfile, "->Delay %ld msec (%ld ticks)\n", delta_msec, delta_time); - if (delta_msec > 0x7fff) midi_error ("INTERNAL: time delta too big", trk->trkptr); + delta_msec = temp / 1000; // get around LCC compiler bug + if (loggen) + fprintf (logfile, "->Delay %ld msec (%ld ticks)\n", delta_msec, delta_time); + if (delta_msec > 0x7fff) + midi_error ("INTERNAL: time delta too big", trk->trkptr); /* output a 15-bit delay in big-endian format */ if (binaryoutput) { - putc ((unsigned char) (delta_msec >> 8), outfile); - putc ((unsigned char) (delta_msec & 0xff), outfile); - outfile_bytecount += 2; - } - else { - fprintf (outfile, "%ld,%ld, ", delta_msec >> 8, delta_msec & 0xff); - outfile_items(2); + putc ((unsigned char) (delta_msec >> 8), outfile); + putc ((unsigned char) (delta_msec & 0xff), outfile); + outfile_bytecount += 2; + } else { + fprintf (outfile, "%ld,%ld, ", delta_msec >> 8, delta_msec & 0xff); + outfile_items (2); } - } - timenow = earliest_time; + } + timenow = earliest_time; - /* If this track event is "set tempo", just change the global tempo. - That affects how we generate "delay" commands. */ + /* If this track event is "set tempo", just change the global tempo. + That affects how we generate "delay" commands. */ - if (trk->cmd == CMD_TEMPO) { + if (trk->cmd == CMD_TEMPO) { tempo = trk->tempo; - if (loggen) fprintf (logfile, "Tempo changed to %ld usec/qnote\n", tempo); + if (loggen) + fprintf (logfile, "Tempo changed to %ld usec/qnote\n", tempo); find_note (tracknum); - } - - /* If this track event is "stop note", process it and all subsequent "stop notes" for this track - that are happening at the same time. Doing so frees up as many tone generators as possible. */ - - else if (trk->cmd == CMD_STOPNOTE) do { - - // stop a note - for (tgnum=0; tgnum < num_tonegens; ++tgnum) { /* find which generator is playing it */ - tg = &tonegen[tgnum]; - if (tg->playing && tg->track == tracknum && tg->note == trk->note) { - if (loggen) fprintf (logfile, "->Stop note %02X, generator %d, track %d\n", tg->note, tgnum, tracknum); - if (binaryoutput) { - putc (CMD_STOPNOTE | tgnum, outfile); - outfile_bytecount += 1; - } - else { - fprintf (outfile, "0x%02X, ", CMD_STOPNOTE | tgnum); - outfile_items (1); - } - tg->playing = false; - trk->tonegens[tgnum] = false; - } - } - find_note (tracknum); // use up the note - } - while (trk->cmd == CMD_STOPNOTE && trk->time == timenow); - - /* If this track event is "start note", process only it. - Don't do more than one, so we allow other tracks their chance at grabbing tone generators. */ - - else if (trk->cmd == CMD_PLAYNOTE) { - bool foundgen = false; - - if (strategy2) { /* try to use the same tone generator this track used last time */ - tg = &tonegen [trk->preferred_tonegen]; - if (!tg->playing) { - tgnum = trk->preferred_tonegen; - foundgen = true; - } - } - if (!foundgen) for (tgnum=0; tgnum < num_tonegens; ++tgnum) { /* search for a free tone generator */ - tg = &tonegen[tgnum]; - if (!tg->playing) { - foundgen = true; - break; - } - } - if (foundgen) { - int shifted_note; - if (tgnum+1 > num_tonegens_used) num_tonegens_used = tgnum+1; - tg->playing = true; - tg->track = tracknum; - tg->note = trk->note; - trk->tonegens[tgnum] = true; - trk->preferred_tonegen = tgnum; - if (loggen) fprintf (logfile, "->Start note %02X, generator %d, track %d\n", trk->note, tgnum, tracknum); - shifted_note = trk->note + keyshift; - if (shifted_note < 0) shifted_note = 0; - if (shifted_note > 127) shifted_note = 127; - if (binaryoutput) { - putc (CMD_PLAYNOTE | tgnum, outfile); - putc (shifted_note, outfile); - outfile_bytecount += 2; - if (velocityoutput) { - putc (trk->velocity, outfile); - outfile_bytecount++; - } - } - else { - if (velocityoutput == 0) { - fprintf (outfile, "0x%02X,%d, ", CMD_PLAYNOTE | tgnum, shifted_note); - outfile_items(2); + } + + /* If this track event is "stop note", process it and all subsequent "stop notes" for this track + that are happening at the same time. Doing so frees up as many tone generators as possible. */ + + else if (trk->cmd == CMD_STOPNOTE) + do { + // stop a note + if (!percussion_ignore || trk->chan != PERCUSSION_TRACK) /* if we didn't ignore it as percussion */ + for (tgnum = 0; tgnum < num_tonegens; ++tgnum) { /* find which generator is playing it */ + tg = &tonegen[tgnum]; + if (tg->playing && tg->track == tracknum && tg->note == trk->note) { + if (loggen) + fprintf (logfile, + "->Stop note %d, generator %d, track %d\n", + tg->note, tgnum, tracknum); + if (binaryoutput) { + putc (CMD_STOPNOTE | tgnum, outfile); + outfile_bytecount += 1; } else { - fprintf (outfile, "0x%02X,%d,%d, ", CMD_PLAYNOTE | tgnum, shifted_note, trk->velocity); - outfile_items(3); + fprintf (outfile, "0x%02X, ", CMD_STOPNOTE | tgnum); + outfile_items (1); } - } + tg->playing = false; + trk->tonegens[tgnum] = false; + } + } + find_note (tracknum); // use up the note } - else { - if (loggen) fprintf (logfile, "----> No free generator, skipping note %02X, track %d\n", trk->note, tracknum); - ++notes_skipped; + while (trk->cmd == CMD_STOPNOTE && trk->time == timenow); + + /* If this track event is "start note", process only it. + Don't do more than one, so we allow other tracks their chance at grabbing tone generators. */ + + else if (trk->cmd == CMD_PLAYNOTE) { + if (!percussion_ignore || trk->chan != PERCUSSION_TRACK) { /* ignore percussion track notes if asked to */ + bool foundgen = false; + /* maybe try to use the same tone generator that this track used last time */ + if (strategy2) { + tg = &tonegen[trk->preferred_tonegen]; + if (!tg->playing) { + tgnum = trk->preferred_tonegen; + foundgen = true; + } + } + /* if not, then try for a free tone generator that had been playing the same instrument we need */ + if (!foundgen) + for (tgnum = 0; tgnum < num_tonegens; ++tgnum) { + tg = &tonegen[tgnum]; + if (!tg->playing && tg->instrument == midi_chan_instrument[trk->chan]) { + foundgen = true; + break; + } + } + /* if not, then try for any free tone generator */ + if (!foundgen) + for (tgnum = 0; tgnum < num_tonegens; ++tgnum) { + tg = &tonegen[tgnum]; + if (!tg->playing) { + foundgen = true; + break; + } + } + if (foundgen) { + int shifted_note; + if (tgnum + 1 > num_tonegens_used) + num_tonegens_used = tgnum + 1; + tg->playing = true; + tg->track = tracknum; + tg->note = trk->note; + trk->tonegens[tgnum] = true; + trk->preferred_tonegen = tgnum; + ++note_on_commands; + if (tg->instrument != midi_chan_instrument[trk->chan]) { /* new instrument for this generator */ + tg->instrument = midi_chan_instrument[trk->chan]; + ++instrument_changes; + if (loggen) + fprintf (logfile, + "gen %d changed to instrument %d\n", tgnum, tg->instrument); + if (instrumentoutput) { /* output a "change instrument" command */ + if (binaryoutput) { + putc (CMD_INSTRUMENT | tgnum, outfile); + putc (tg->instrument, outfile); + } else { + fprintf (outfile, "0x%02X,%d, ", CMD_INSTRUMENT | tgnum, tg->instrument); + outfile_items (2); + } + } + } + if (loggen) + fprintf (logfile, + "->Start note %d, generator %d, instrument %d, track %d\n", + trk->note, tgnum, tg->instrument, tracknum); + if (percussion_translate && trk->chan == PERCUSSION_TRACK) { /* if requested, */ + shifted_note = trk->note + 128; // shift percussion notes up to 128..255 + } else { /* shift notes as requested */ + shifted_note = trk->note + keyshift; + if (shifted_note < 0) + shifted_note = 0; + if (shifted_note > 127) + shifted_note = 127; + } + if (binaryoutput) { + putc (CMD_PLAYNOTE | tgnum, outfile); + putc (shifted_note, outfile); + outfile_bytecount += 2; + if (velocityoutput) { + putc (trk->velocity, outfile); + outfile_bytecount++; + } + } else { + if (velocityoutput == 0) { + fprintf (outfile, "0x%02X,%d, ", CMD_PLAYNOTE | tgnum, shifted_note); + outfile_items (2); + } else { + fprintf (outfile, "0x%02X,%d,%d, ", + CMD_PLAYNOTE | tgnum, shifted_note, trk->velocity); + outfile_items (3); + } + } + } else { + if (loggen) + fprintf (logfile, + "----> No free generator, skipping note %d, track %d\n", + trk->note, tracknum); + ++notes_skipped; + } } - find_note (tracknum); // use up the note - } - - } /* !parseonly do */ - while (tracks_done < num_tracks); - - if (!parseonly) { - // generate the end-of-score command and some commentary - if(binaryoutput) putc(CMD_STOP, outfile); - else { - fprintf(outfile, "0x%02x};\n// This score contains %ld bytes, and %d tone generator%s used.\n", CMD_STOP, outfile_bytecount, num_tonegens_used, num_tonegens_used == 1 ? " is" : "s are"); - if (notes_skipped) fprintf(outfile, "// %d notes had to be skipped.\n", notes_skipped); - } - printf (" %s %d tone generators were used.\n", num_tonegens_used < num_tonegens ? "Only":"All", num_tonegens_used); - if (notes_skipped) printf(" %d notes were skipped because there weren't enough tone generators.\n", notes_skipped); - printf (" %ld bytes of score data were generated.\n", outfile_bytecount); - } - - - printf (" Done.\n"); - return 0; + find_note (tracknum); // use up the note + } + + } /* !parseonly do */ + while (tracks_done < num_tracks); + + if (!parseonly) { + // generate the end-of-score command and some commentary + if (binaryoutput) + putc (CMD_STOP, outfile); + else { + fprintf (outfile, + "0x%02x};\n// This score contains %ld bytes, and %d tone generator%s used.\n", + CMD_STOP, outfile_bytecount, num_tonegens_used, + num_tonegens_used == 1 ? " is" : "s are"); + if (notes_skipped) + fprintf (outfile, "// %d notes had to be skipped.\n", notes_skipped); + } + printf (" %s %d tone generators were used.\n", + num_tonegens_used < num_tonegens ? "Only" : "All", num_tonegens_used); + if (notes_skipped) + printf + (" %d notes were skipped because there weren't enough tone generators.\n", + notes_skipped); + printf (" %ld bytes of score data were generated.\n", outfile_bytecount); + if (loggen) + fprintf (logfile, "%d note-on commands, %d instrument changes.\n", + note_on_commands, instrument_changes); + } + if (do_header) { // rewrite the file header with the actual number of tone generators used + if (fseek (outfile, file_header_num_tgens_position, SEEK_SET) != 0) + fprintf (stderr, "Can't seek to number of tone generators in the header"); + else { + if (binaryoutput) + putc (num_tonegens_used, outfile); + else + fprintf (outfile, "%2d", num_tonegens_used); + } + } + fclose (outfile); + if (loggen || logparse) + fclose (logfile); + printf (" Done.\n"); + return 0; } diff --git a/miditones.exe b/miditones.exe index 658b6a2..7a2bdda 100644 Binary files a/miditones.exe and b/miditones.exe differ diff --git a/miditones32.exe b/miditones32.exe deleted file mode 100644 index b3d6c31..0000000 Binary files a/miditones32.exe and /dev/null differ diff --git a/miditones_scroll.c b/miditones_scroll.c index b576dd9..77068ac 100644 --- a/miditones_scroll.c +++ b/miditones_scroll.c @@ -1,4 +1,4 @@ -/********************************************************************************* +/*************************************************************************************** * * MIDITONES_SCROLL * @@ -50,27 +50,36 @@ * -vi says that we expect volume information to be in the file, but we * should ignore it when creating the display. * +* -ii says we should not display instrument information we find +* * * For source code to this and related programs, see -* https://github.com/LenShustek/arduino-playtune -* https://github.com/LenShustek/miditones +* www.github.com/LenShustek/miditones +* www.github.com/LenShustek/arduino-playtune * -*---------------------------------------------------------------------------------- -* (C) Copyright 2011,2013,2016 Len Shustek +*---------------------------------------------------------------------------------------- +* The MIT License (MIT) +* Copyright (c) 2011,2013,2015,2016, Len Shustek * -* This program is free software. You can redistribute it and/or modify -* it under the terms of version 3 of the GNU General Public License as -* published by the Free Software Foundation at www.gnu.org/licenses, -* with Additional Permissions under term 7(b) that the original copyright -* notice and author attibution must be preserved and under term 7(c) that -* modified versions be marked as different from the original. +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: * -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY,without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. * -***********************************************************************************/ +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR +* IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +******************************************************************************************/ +// formatted with: indent miditones_scroll.c -br -brf -brs -ce -npsl -nut -i3 -l100 -lc100 + /* * Change log * @@ -80,7 +89,7 @@ * - Add a "-c" option to create C code output. * Thanks go to mats.engstrom for the idea. * 04 April 2015, L. Shustek, V1.3 -* - Made friendlier to other compilers: import source of strlcpy and strlcat, +* - Made friendlier to other compilers,import source of strlcpy and strlcat, * and fix various type mismatches that the LCC compiler didn't fret about. * Generate "const" data initialization for compatibility with Arduino IDE v1.6.x. * 5 August 2016, L. Shustek, V1.4 @@ -90,11 +99,14 @@ * - Make the number of generators be 16 maximum, but the number actually displayed * can be specified by the -tn command-line option. * - Remove string.h because OS X has macros for strlcpy; thus had to add strlen(). -* (It's tough to write even non-GUI code for many environments!) +* It's tough to write even non-GUI code for many environments! * - Add casts for where address subtraction is a long, like OS X. +* 6 August 2016, L. Shustek, V1.5 +* - Handle optional instrument change information. +* - Look for the optional self-describing file header. */ -#define VERSION "1.4" +#define VERSION "1.5" #include #include @@ -106,16 +118,19 @@ /*********** Global variables ******************/ -#define MAX_TONEGENS 16 /* max tone generators we could display */ +#define MAX_TONEGENS 16 /* max tone generators we could display */ + +#define SILENT -1 /* "silent note" code */ -int gen_note[MAX_TONEGENS]; // the note we're playing, or SILENT -#define SILENT -1 -int gen_volume[MAX_TONEGENS]; // the volume we're playing +int gen_note[MAX_TONEGENS]; // the note we're playing, or SILENT +int gen_volume[MAX_TONEGENS]; // the volume we're playing +int gen_instrument[MAX_TONEGENS]; // the instrument we're playing +bool gen_instrument_changed[MAX_TONEGENS]; FILE *infile, *outfile; unsigned char *buffer, *bufptr; unsigned long buflen; -unsigned int num_tonegens = 6; // default number of generators +unsigned int num_tonegens = 6; // default number of generators unsigned int max_tonegen_found = 0; unsigned int notes_skipped; @@ -126,324 +141,419 @@ unsigned delay; bool codeoutput = false; bool expect_volume = false; bool ignore_volume = false; +bool ignore_instruments = false; + +struct file_hdr_t { /* what the optional file header looks like */ + char id1; // 'P' + char id2; // 't' + unsigned char hdr_length; // length of whole file header + unsigned char f1; // flag byte 1 + unsigned char f2; // flag byte 2 + unsigned char num_tgens; // how many tone generators are used by this score +}; +#define HDR_F1_VOLUME_PRESENT 0x80 +#define HDR_F1_INSTRUMENTS_PRESENT 0x40 +#define HDR_F1_PERCUSSION_PRESENT 0x20 + + +static char *notename[256] = { /* maximum 5 characters */ + + // map from MIDI note number to octave and note name, + + "-1C ", "-1C#", "-1D ", "-1D#", "-1E ", "-1F ", "-1F#", "-1G ", "-1G#", "-1A ", "-1A#", "-1B ", + " 0C ", " 0C#", " 0D ", " 0D#", " 0E ", " 0F ", " 0F#", " 0G ", " 0G#", " 0A ", " 0A#", " 0B ", + " 1C ", " 1C#", " 1D ", " 1D#", " 1E ", " 1F ", " 1F#", " 1G ", " 1G#", " 1A ", " 1A#", " 1B ", + " 2C ", " 2C#", " 2D ", " 2D#", " 2E ", " 2F ", " 2F#", " 2G ", " 2G#", " 2A ", " 2A#", " 2B ", + " 3C ", " 3C#", " 3D ", " 3D#", " 3E ", " 3F ", " 3F#", " 3G ", " 3G#", " 3A ", " 3A#", " 3B ", + " 4C ", " 4C#", " 4D ", " 4D#", " 4E ", " 4F ", " 4F#", " 4G ", " 4G#", " 4A ", " 4A#", " 4B ", + " 5C ", " 5C#", " 5D ", " 5D#", " 5E ", " 5F ", " 5F#", " 5G ", " 5G#", " 5A ", " 5A#", " 5B ", + " 6C ", " 6C#", " 6D ", " 6D#", " 6E ", " 6F ", " 6F#", " 6G ", " 6G#", " 6A ", " 6A#", " 6B ", + " 7C ", " 7C#", " 7D ", " 7D#", " 7E ", " 7F ", " 7F#", " 7G ", " 7G#", " 7A ", " 7A#", " 7B ", + " 8C ", " 8C#", " 8D ", " 8D#", " 8E ", " 8F ", " 8F#", " 8G ", " 8G#", " 8A ", " 8A#", " 8B ", + " 9C ", " 9C#", " 9D ", " 9D#", " 9E ", " 9F ", " 9F#", " 9G ", + + // or to channel 9 percussion notes as relocated by Miditones to notes 128..255 + + "P000 ", "P001 ", "P002 ", "P003 ", "P004 ", "P005 ", "P006 ", "P007 ", + "P008 ", "P009 ", "P010 ", "P011 ", "P012 ", "P013 ", "P014 ", "P015 ", + "P016 ", "P017 ", "P018 ", "P019 ", "P020 ", "P021 ", "P022 ", "P023 ", + "P024 ", "P025 ", "P026 ", "Laser", "Whip ", "ScrPu", "ScrPl", "Stick", + "MetCk", "P033 ", "MetBl", "BassD", "KickD", "SnaSt", "SnaD ", "Clap ", + "ESnaD", "FTom2", "HHatC", "FTom1", "HHatF", "LTom ", "HHatO", "LMTom", + "HMTom", "CrCym", "HTom ", "RiCym", "ChCym", "RiBel", "Tamb ", "SpCym", + "CowBl", "CrCym", "VSlap", "RiCym", "HBong", "LBong", "CongD", "Conga", + "Tumba", "HTimb", "LTimb", "HAgog", "LAgog", "Cabas", "Marac", "SWhis", + "LWhis", "SGuir", "LGuir", "Clave", "HWood", "LWood", "HCuic", "LCuic", + "MTria", "OTria", "Shakr", "Sleig", "BelTr", "Casta", "SirdD", "Sirdu", + "P088 ", "P089 ", "P090 ", "SnDmR", "OcDrm", "SmDrB", "P094 ", "P095 ", + "P096 ", "P097 ", "P098 ", "P099 ", "P100 ", "P101 ", "P102 ", "P103 ", + "P104 ", "P105 ", "P106 ", "P107 ", "P108 ", "P109 ", "P110 ", "P111 ", + "P112 ", "P113 ", "P114 ", "P115 ", "P116 ", "P117 ", "P118 ", "P119 ", + "P120 ", "P121 ", "P122 ", "P123 ", "P124 ", "P125 ", "P126 ", "P127" +}; -static char *notename[256] = { - - // map from MIDI note number to octave and note name, - - "-1C ","-1C#","-1D ","-1D#","-1E ","-1F ","-1F#","-1G ","-1G#","-1A ","-1A#","-1B ", - " 0C "," 0C#"," 0D "," 0D#"," 0E "," 0F "," 0F#"," 0G "," 0G#"," 0A "," 0A#"," 0B ", - " 1C "," 1C#"," 1D "," 1D#"," 1E "," 1F "," 1F#"," 1G "," 1G#"," 1A "," 1A#"," 1B ", - " 2C "," 2C#"," 2D "," 2D#"," 2E "," 2F "," 2F#"," 2G "," 2G#"," 2A "," 2A#"," 2B ", - " 3C "," 3C#"," 3D "," 3D#"," 3E "," 3F "," 3F#"," 3G "," 3G#"," 3A "," 3A#"," 3B ", - " 4C "," 4C#"," 4D "," 4D#"," 4E "," 4F "," 4F#"," 4G "," 4G#"," 4A "," 4A#"," 4B ", - " 5C "," 5C#"," 5D "," 5D#"," 5E "," 5F "," 5F#"," 5G "," 5G#"," 5A "," 5A#"," 5B ", - " 6C "," 6C#"," 6D "," 6D#"," 6E "," 6F "," 6F#"," 6G "," 6G#"," 6A "," 6A#"," 6B ", - " 7C "," 7C#"," 7D "," 7D#"," 7E "," 7F "," 7F#"," 7G "," 7G#"," 7A "," 7A#"," 7B ", - " 8C "," 8C#"," 8D "," 8D#"," 8E "," 8F "," 8F#"," 8G "," 8G#"," 8A "," 8A#"," 8B ", - " 9C "," 9C#"," 9D "," 9D#"," 9E "," 9F "," 9F#"," 9G ", - - // or to channel 9 percussion notes as relocated by Miditones to notes 128..255 - - "P000 ", "P001 ", "P002 ", "P003 ", "P004 ", "P005 ", "P006 ", "P007 ", - "P008 ", "P009 ", "P010 ", "P011 ", "P012 ", "P013 ", "P014 ", "P015 ", - "P016 ", "P017 ", "P018 ", "P019 ", "P020 ", "P021 ", "P022 ", "P023 ", - "P024 ", "P025 ", "P026 ", "Laser", "Whip ", "ScrPu", "ScrPl", "Stick", - "MetCk", "P033 ", "MetBl", "BassD", "KickD", "SnaSt", "SnaD ", "Clap ", - "ESnaD", "FTom2", "HHatC", "FTom1", "HHatF", "LTom ", "HHatO", "LMTom", - "HMTom", "CrCym", "HTom ", "RiCym", "ChCym", "RiBel", "Tamb ", "SpCym", - "CowBl", "CrCym", "VSlap", "RiCym", "HBong", "LBong", "CongD", "Conga", - "Tumba", "HTimb", "LTimb", "HAgog", "LAgog", "Cabas", "Marac", "SWhis", - "LWhis", "SGuir", "LGuir", "Clave", "HWood", "LWood", "HCuic", "LCuic", - "MTria", "OTria", "Shakr", "Sleig", "BelTr", "Casta", "SirdD", "Sirdu", - "P088 ", "P089 ", "P090 ", "SnDmR", "OcDrm", "SmDrB", "P094 ", "P095 ", - "P096 ", "P097 ", "P098 ", "P099 ", "P100 ", "P101 ", "P102 ", "P103 ", - "P104 ", "P105 ", "P106 ", "P107 ", "P108 ", "P109 ", "P110 ", "P111 ", - "P112 ", "P113 ", "P114 ", "P115 ", "P116 ", "P117 ", "P118 ", "P119 ", - "P120 ", "P121 ", "P122 ", "P123 ", "P124 ", "P125 ", "P126 ", "P127" +static char *instrumentname[128] = { /* maximum 6 characters */ + "APiano", "BPiano", "EPiano", "HPiano", "E1Pian", "E2Pian", "Harpsi", "Clavic", + "Celest", "Glockn", "MusBox", "Vibrap", "Marimb", "Xyloph", "TubBel", "Dulcim", + "DOrgan", "POrgan", "ROrgan", "COrgan", "dOrgan", "Accord", "Harmon", "TAccor", + "NyGuit", "StGuit", "JzGuit", "ClGuit", "MuGuit", "OvGuit", "DsGuit", "HaGuit", + "AcBass", "FiBass", "PiBass", "FrBass", "S1Bass", "S2Bass", "y1Bass", "y2Bass", + "Violin", "Viola ", "Cello ", "CnBass", "TrStng", "PzStng", "OrHarp", "Timpan", + "S1Ensb", "S1Ensb", "y1Strg", "y2Strg", "ChAhhs", "VcOohs", "SyVoic", "OrcHit", + "Trumpt", "Trombn", "Tuba ", "MuTrum", "FrHorn", "Brass ", "y1Bras", "y2Bras", + "SopSax", "AltSax", "TenSax", "BarSax", "Oboe ", "EnHorn", "Basson", "Clarin", + "Piccol", "Flute ", "Record", "PFlute", "BlBotl", "Shakuh", "Whistl", "Ocarin", + "Square", "Sawtoo", "Callip", "Chiff ", "Charag", "Voice ", "Fifths", "BassLd", + "Pad1 ", "Pad2 ", "Pad3 ", "Pad4 ", "Pad5 ", "Pad6 ", "Pad7 ", "Pad 8 ", + "FX1 ", "FX2 ", "FX3 ", "FX4 ", "FX5 ", "FX6 ", "FX7 ", "FX8 ", + "Sitar ", "Banjo ", "Shamis", "Koto ", "Kalimb", "Bagpip", "Fiddle", "Shanai", + "TnkBel", "Agogo ", "StDrum", "WdBlok", "TaiDrm", "MelTom", "SynDrm", "RevCym", + "GuitFr", "Breath", "Seashr", "BirdTw", "Phone ", "Copter", "Claps ", "Guns " }; /************** command-line processing *******************/ -void SayUsage(char *programName){ - static char *usage[] = { - "Display a MIDITONES bytestream", - "Usage: miditones_scroll ", - " reads .bin", - " -tn displays up to n tone generators", - " -v expects and displays volume information", - " -vi expects and ignores volume information", - " -c option creates an annotated C source file as .c", - "" }; - int i=0; - while (usage[i][0] != '\0') fprintf(stderr, "%s\n", usage[i++]); +void SayUsage (char *programName) { + static char *usage[] = { + "Display a MIDITONES bytestream", + "Usage: miditones_scroll ", + " reads .bin", + " -tn displays up to n tone generators", + " -v expects and displays volume information", + " -vi expects and ignores volume information", + " -c option creates an annotated C source file as .c", + "" + }; + int i = 0; + while (usage[i][0] != '\0') + fprintf (stderr, "%s\n", usage[i++]); } -int HandleOptions(int argc,char *argv[]) { - /* returns the index of the first argument that is not an option; i.e. - does not start with a dash or a slash*/ - - int i,firstnonoption=0; - - /* --- The following skeleton comes from C:\lcc\lib\wizard\textmode.tpl. */ - for (i=1; i< argc;i++) { - if (argv[i][0] == '/' || argv[i][0] == '-') { - switch (toupper(argv[i][1])) { - case 'H': - case '?': - SayUsage(argv[0]); - exit(1); - case 'C': - codeoutput = true; - break; - case 'T': - if (sscanf(&argv[i][2],"%d",&num_tonegens) != 1 || num_tonegens <1 || num_tonegens > MAX_TONEGENS) goto opterror; - printf("Displaying %d tone generators.\n", num_tonegens); - break; - case 'V': - expect_volume = true; - if (argv[i][2] == '\0') break; - if (toupper(argv[i][2]) == 'I') ignore_volume = true; - else goto opterror; - break; - - /* add more option switches here */ -opterror: - default: - fprintf(stderr,"unknown option: %s\n",argv[i]); - SayUsage(argv[0]); - exit(4); - } - } - else { - firstnonoption = i; +int HandleOptions (int argc, char *argv[]) { + /* returns the index of the first argument that is not an option; i.e. + does not start with a dash or a slash */ + + int i, firstnonoption = 0; + + /* --- The following skeleton comes from C:\lcc\lib\wizard\textmode.tpl. */ + for (i = 1; i < argc; i++) { + if (argv[i][0] == '/' || argv[i][0] == '-') { + switch (toupper (argv[i][1])) { + case 'H': + case '?': + SayUsage (argv[0]); + exit (1); + case 'C': + codeoutput = true; + break; + case 'T': + if (sscanf (&argv[i][2], "%d", &num_tonegens) != 1 || num_tonegens < 1 + || num_tonegens > MAX_TONEGENS) + goto opterror; + printf ("Displaying %d tone generators.\n", num_tonegens); break; - } - } - return firstnonoption; + case 'V': + expect_volume = true; + if (argv[i][2] == '\0') + break; + if (toupper (argv[i][2]) == 'I') + ignore_volume = true; + else + goto opterror; + break; + + /* add more option switches here */ + opterror: + default: + fprintf (stderr, "unknown option: %s\n", argv[i]); + SayUsage (argv[0]); + exit (4); + } + } else { + firstnonoption = i; + break; + } + } + return firstnonoption; } /*************** portable string length *****************/ -int strlength(const char *str) { - int i; - for (i=0; str[i] != '\0'; ++i) ; - return i; +int strlength (const char *str) { + int i; + for (i = 0; str[i] != '\0'; ++i); + return i; } /*************** safe string copy *****************/ -unsigned int strlcpy(char *dst, const char *src, unsigned int siz) { - char *d = dst; - const char *s = src; - unsigned int n = siz; - /* Copy as many bytes as will fit */ - if (n != 0) { - while (--n != 0) { - if ((*d++ = *s++) == '\0') break; - } - } - /* Not enough room in dst, add NUL and traverse rest of src */ - if (n == 0) { - if (siz != 0) *d = '\0'; /* NUL-terminate dst */ - while (*s++) ; - } - return (s - src - 1); /* count does not include NUL */ +unsigned int strlcpy (char *dst, const char *src, unsigned int siz) { + char *d = dst; + const char *s = src; + unsigned int n = siz; + /* Copy as many bytes as will fit */ + if (n != 0) { + while (--n != 0) { + if ((*d++ = *s++) == '\0') + break; + } + } + /* Not enough room in dst, add NUL and traverse rest of src */ + if (n == 0) { + if (siz != 0) + *d = '\0'; /* NUL-terminate dst */ + while (*s++); + } + return (s - src - 1); /* count does not include NUL */ } /*************** safe string concatenation *****************/ -unsigned int strlcat(char *dst, const char *src, unsigned int siz) { - char *d = dst; - const char *s = src; - unsigned int n = siz; - unsigned int dlen; - /* Find the end of dst and adjust bytes left but don't go past end */ - while (n-- != 0 && *d != '\0') d++; - dlen = d - dst; - n = siz - dlen; - if (n == 0) return (dlen + strlength(s)); - while (*s != '\0') { - if (n != 1) { - *d++ = *s; - n--; - } - s++; - } - *d = '\0'; - return (dlen + (s - src)); /* count does not include NUL */ +unsigned int strlcat (char *dst, const char *src, unsigned int siz) { + char *d = dst; + const char *s = src; + unsigned int n = siz; + unsigned int dlen; + /* Find the end of dst and adjust bytes left but don't go past end */ + while (n-- != 0 && *d != '\0') + d++; + dlen = d - dst; + n = siz - dlen; + if (n == 0) + return (dlen + strlength (s)); + while (*s != '\0') { + if (n != 1) { + *d++ = *s; + n--; + } + s++; + } + *d = '\0'; + return (dlen + (s - src)); /* count does not include NUL */ } /*************** Found a fatal input file format error ************************/ void file_error (char *msg, unsigned char *bufptr) { - unsigned char *ptr; - fprintf(stderr, "\n---> file format error at position %04X (%d): %s\n", - (unsigned int)(bufptr-buffer), (unsigned int)(bufptr-buffer), msg); - /* print some bytes surrounding the error */ - ptr = bufptr - 16; - if (ptr < buffer) ptr = buffer; - for (; ptr <= bufptr+16 && ptr < buffer+buflen; ++ptr) fprintf (stderr, ptr==bufptr ? " [%02X] ":"%02X ", *ptr); - fprintf(stderr, "\n"); - exit(8); + unsigned char *ptr; + fprintf (stderr, "\n---> file format error at position %04X (%d): %s\n", + (unsigned int) (bufptr - buffer), (unsigned int) (bufptr - buffer), msg); + /* print some bytes surrounding the error */ + ptr = bufptr - 16; + if (ptr < buffer) + ptr = buffer; + for (; ptr <= bufptr + 16 && ptr < buffer + buflen; ++ptr) + fprintf (stderr, ptr == bufptr ? " [%02X] " : "%02X ", *ptr); + fprintf (stderr, "\n"); + exit (8); } /************** Output a line for the current status as we start a delay **************/ // show the current time, status of all the tone generators, and the bytestream data that got us here -void print_status(void) { - if (codeoutput) fprintf (outfile, "/*"); // start comment - // print the current timestamp - fprintf (outfile, "%5d %7d.%03d ", delay, timenow/1000, timenow%1000); - // print the current status of all tone generators - for (gen=0; gen>= 1) - count += bitmap & 1; - return count; + int count; + for (count = 0; bitmap; bitmap >>= 1) + count += bitmap & 1; + return count; } /********************* main loop ****************************/ -int main(int argc,char *argv[]) { - int argno, i; - char *filebasename; +int main (int argc, char *argv[]) { + int argno, i; + char *filebasename; #define MAXPATH 80 - char filename[MAXPATH]; - unsigned int tonegens_used; // bitmap of tone generators used - unsigned int num_tonegens_used; // count of tone generators used - - printf("MIDITONES_SCROLL V%s, (C) 2011,2016 Len Shustek\n", VERSION); - printf("See the source code for license information.\n\n"); - if (argc == 1) { /* no arguments */ - SayUsage(argv[0]); - return 1; - } - - /* process options */ - - argno = HandleOptions(argc,argv); - filebasename = argv[argno]; - - /* Open the input file */ - - strlcpy(filename, filebasename, MAXPATH); - strlcat(filename, ".bin", MAXPATH); - infile = fopen(filename, "rb"); - if (!infile) { - fprintf(stderr, "Unable to open input file %s", filename); - return 1; - } - - /* Create the output file */ - - if (codeoutput) { - strlcpy(filename, filebasename, MAXPATH); - strlcat(filename, ".c", MAXPATH); - outfile = fopen(filename, "w"); - if (!infile) { - fprintf(stderr, "Unable to open output file %s", filename); - return 1; - } - } - else outfile = stdout; - - /* Read the whole input file into memory */ - - fseek(infile, 0, SEEK_END); /* find file size */ - buflen = ftell(infile); - fseek(infile, 0, SEEK_SET); - buffer = (unsigned char *) malloc (buflen+1); - if (!buffer) { - fprintf(stderr, "Unable to allocate %ld bytes for the file", buflen); - return 1; - } - fread(buffer, buflen, 1, infile); - fclose(infile); - printf("Processing %s.bin, %ld bytes\n", filebasename, buflen); - if (codeoutput) { - time_t rawtime; - time (&rawtime); - fprintf(outfile, "// Playtune bytestream for file \"%s.bin\"", filebasename); - fprintf(outfile, " created by MIDITONES_SCROLL V%s on %s\n", VERSION, asctime(localtime(&rawtime))); - fprintf(outfile, "const byte PROGMEM score [] = {\n"); - } - - /* Do the titles */ - - fprintf(outfile, "\n"); - if (codeoutput) fprintf(outfile, "//"); - fprintf(outfile, "duration time "); - for (i=0; i< num_tonegens; ++i) - fprintf(outfile, expect_volume && !ignore_volume ? " gen%-5d" : " gen%-2d", i); - fprintf(outfile," bytestream code\n\n"); - for (gen=0; gen max_tonegen_found) max_tonegen_found = gen; - cmd = cmd & 0xf0; - if (cmd == 0x90) { //****** note on - gen_note[gen] = *++bufptr; // note number - tonegens_used |= 1<= num_tonegens) ++notes_skipped; // won't be displaying this note - } - else if (cmd == 0x80) { //****** note off - - if (gen_note[gen] == SILENT) file_error ("tone generator not on", bufptr); - gen_note[gen] = SILENT; - } - else file_error ("unknown command", bufptr); - } - } - - - /* Do the final cleanup */ - - delay = 0; - --bufptr; - if (codeoutput) --bufptr; //don't do 0xF0 for code, because we don't want the trailing comma - print_status(); // print final status - if (codeoutput) { - fprintf(outfile, " 0xf0};\n"); - num_tonegens_used = countbits(tonegens_used); - fprintf(outfile, "// This score contains %ld bytes, and %d tone generator%s used.\n", - buflen, num_tonegens_used, num_tonegens_used == 1 ? " is" : "s are"); - } - printf("\nAt most %u tone generators were used.\n", max_tonegen_found+1); - if (notes_skipped) printf("%u notes were not displayed because we were told to show only %u generators.\n", notes_skipped, num_tonegens); - printf ("Done.\n"); + char filename[MAXPATH]; + unsigned int tonegens_used; // bitmap of tone generators used + unsigned int num_tonegens_used; // count of tone generators used + + printf ("MIDITONES_SCROLL V%s, (C) 2011,2016 Len Shustek\n", VERSION); + if (argc == 1) { /* no arguments */ + SayUsage (argv[0]); + return 1; + } + + /* process options */ + + argno = HandleOptions (argc, argv); + filebasename = argv[argno]; + + /* Open the input file */ + + strlcpy (filename, filebasename, MAXPATH); + strlcat (filename, ".bin", MAXPATH); + infile = fopen (filename, "rb"); + if (!infile) { + fprintf (stderr, "Unable to open input file %s", filename); + return 1; + } + + /* Create the output file */ + + if (codeoutput) { + strlcpy (filename, filebasename, MAXPATH); + strlcat (filename, ".c", MAXPATH); + outfile = fopen (filename, "w"); + if (!infile) { + fprintf (stderr, "Unable to open output file %s", filename); + return 1; + } + } else + outfile = stdout; + + /* Read the whole input file into memory */ + + fseek (infile, 0, SEEK_END); /* find file size */ + buflen = ftell (infile); + fseek (infile, 0, SEEK_SET); + buffer = (unsigned char *) malloc (buflen + 1); + if (!buffer) { + fprintf (stderr, "Unable to allocate %ld bytes for the file", buflen); + return 1; + } + fread (buffer, buflen, 1, infile); + fclose (infile); + printf ("Processing %s.bin, %ld bytes.\n", filebasename, buflen); + if (codeoutput) { + time_t rawtime; + time (&rawtime); + fprintf (outfile, "// Playtune bytestream for file \"%s.bin\"", filebasename); + fprintf (outfile, " created by MIDITONES_SCROLL V%s on %s\n", VERSION, + asctime (localtime (&rawtime))); + fprintf (outfile, "const byte PROGMEM score [] = {\n"); + } + + /* Check for the optional self-describing file header */ + + bufptr = buffer; + { + struct file_hdr_t *hdrptr = (struct file_hdr_t *) buffer; + if (buflen > sizeof (struct file_hdr_t) && hdrptr->id1 == 'P' && hdrptr->id2 == 't') { + printf ("Found Pt self-describing file header with flags %02X %02X, # tone gens = %d\n", + hdrptr->f1, hdrptr->f2, hdrptr->num_tgens); + expect_volume = hdrptr->f1 & HDR_F1_VOLUME_PRESENT; + bufptr += hdrptr->hdr_length; + if (codeoutput) { + fprintf (outfile, "'P','t', 6, 0x%02X, 0x%02X, %2d, // (Playtune file header)\n", + hdrptr->f1, hdrptr->f2, hdrptr->num_tgens); + } + } + } + + /* Do the titles */ + + fprintf (outfile, "\n"); + if (codeoutput) + fprintf (outfile, "//"); + fprintf (outfile, "duration time "); + for (i = 0; i < num_tonegens; ++i) + fprintf (outfile, expect_volume && !ignore_volume ? " gen%-5d" : " gen%-2d", i); + fprintf (outfile, " bytestream code\n\n"); + for (gen = 0; gen < num_tonegens; ++gen) + gen_note[gen] = SILENT; + tonegens_used = 0; + lastbufptr = buffer; + + /* Process the commmands sequentially */ + + for (; bufptr < buffer + buflen; ++bufptr) { + cmd = *bufptr; + if (cmd < 0x80) { /* delay */ + delay = ((unsigned int) cmd << 8) + *++bufptr; + print_status (); // tone generator status now + timenow += delay; // advance time + } else if (cmd != 0xf0) { /* a command */ + gen = cmd & 0x0f; + if (gen > max_tonegen_found) + max_tonegen_found = gen; + cmd = cmd & 0xf0; + if (cmd == 0x90) { /* note on */ + gen_note[gen] = *++bufptr; // note number + tonegens_used |= 1 << gen; // record that we used this generator at least once + if (expect_volume) + gen_volume[gen] = *++bufptr; // volume + if (gen >= num_tonegens) + ++notes_skipped; // won't be displaying this note + } else if (cmd == 0x80) { /* note off */ + if (gen_note[gen] == SILENT) + file_error ("tone generator not on", bufptr); + gen_note[gen] = SILENT; + } else if (cmd == 0xc0) { /* change instrument */ + gen_instrument[gen] = *++bufptr & 0x7f; + gen_instrument_changed[gen] = true; + } else + file_error ("unknown command", bufptr); + } + } + + + /* Do the final cleanup */ + + delay = 0; + --bufptr; + if (codeoutput) + --bufptr; //don't do 0xf0 for code, because we don't want the trailing comma + print_status (); // print final status + if (codeoutput) { + fprintf (outfile, " 0xf0};\n"); + num_tonegens_used = countbits (tonegens_used); + fprintf (outfile, "// This score contains %ld bytes, and %d tone generator%s used.\n", + buflen, num_tonegens_used, num_tonegens_used == 1 ? " is" : "s are"); + } else + fprintf (outfile, "\n"); + printf ("At most %u tone generators were used.\n", max_tonegen_found + 1); + if (notes_skipped) + printf ("%u notes were not displayed because we were told to show only %u generators.\n", + notes_skipped, num_tonegens); + printf ("Done.\n"); } - diff --git a/miditones_scroll.exe b/miditones_scroll.exe index aba14d1..33b39eb 100644 Binary files a/miditones_scroll.exe and b/miditones_scroll.exe differ