fix "running status" bug; add volume, instruments, percussion, file header, etc.

pull/9/head
Len Shustek 8 years ago
parent c3a2db9c01
commit 9a394d5a0b
  1. 35
      LICENSE.txt
  2. 155
      README.txt
  3. 1146
      miditones.c
  4. BIN
      miditones.exe
  5. BIN
      miditones32.exe
  6. 390
      miditones_scroll.c
  7. BIN
      miditones_scroll.exe

@ -1,14 +1,21 @@
* (C) Copyright 2011, 2013, 2015, Len Shustek The MIT License (MIT)
*
* This program is free software: you can redistribute it and/or modify Copyright (c) 2016, Len Shustek
* 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, Permission is hereby granted, free of charge, to any person obtaining a copy
* with Additional Permissions under term 7(b) that the original copyright of this software and associated documentation files (the "Software"), to deal
* notice and author attibution must be preserved and under term 7(c) that in the Software without restriction, including without limitation the rights
* modified versions be marked as different from the original. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* This program is distributed in the hope that it will be useful, furnished to do so, subject to the following conditions:
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the The above copyright notice and this permission notice shall be included in all
* GNU General Public License for more details. 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.

@ -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, * Volume ("velocity") and instrument information in the MIDI file can either be
* so that a version of the music can be played on a synthesizer having only * discarded or kept. All the tracks are prcoessed and merged into a single time-ordered
* tone generators without any volume or tone controls. * stream of "note on", "note off", "change instrument" and "delay" commands.
* *
* Volume ("velocity") and instrument specifications in the MIDI files are generally * This was written for the "Playtune" series of Arduino and Teensy microcontroller
* discarded. All the tracks are prcoessed and merged into a single time-ordered * synthesizers. See the separate documentation for the various Playtune.players at
* stream of "note on", "note off", and "delay" commands. * www.github.com/LenShustek/arduino-playtune
* * www.github.com/LenShustek/ATtiny-playtune
* This was written for the "Playtune" Arduino library, which plays polyphonic music * www.github.com/LenShustek/Playtune_poll
* using up to 6 tone generators run by the timers on the processor. See the separate * www.github.com/LenShustek/Playtune_samp
* documentation for Playtune. But MIDITONES may prove useful for other tone * MIDITONES may also prove useful for other simple music synthesizers..
* generating systems.
* *
* The output can be either a C-language source code fragment that initializes an * 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. * 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 * MIDITONES is written in standard ANSI C and is meant to be executed from the
* is meant to be executed from the command line. There is no GUI interface. * 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 * There is a companion program in the same repository called Miditones_scroll
* wide variety of file types. In particular, we have tested only format type "1", * that can convert the bytestream generated by MIDITONES into a piano-player
* which seems to be what most of them are. Let me know if you find MIDI files * like listing for debugging or annotation. See the documentation in the
* that it won't digest and I'll see if I can fix it. * beginning of its source code.
* 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 command line ***** * ***** The MIDITONES command line *****
* *
* To convert a MIDI file called "chopin.mid" into a command bytestream, execute * 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: * The general form for command line execution is this:
* *
* miditones [-p] [-lg] [-lp] [-s1] [-tn] [-b] [-cn] [-kn] [-v] <basefilename> * miditones <options> <basefilename>
* *
* The <basefilename> is the base name, without an extension, for the input and * The <basefilename> is the base name, without an extension, for the input and
* output files. It can contain directory path information, or not. * output files. It can contain directory path information, or not.
* *
* The input file is the base name with the extension ".mid". The output filename(s) * The input file is <basefilename>.mid The output filename(s)
* are the base name with ".c", ".bin", and/or ".log" extensions. * 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 <basefilename>.bin, instead of a
* C-language source file with the name <basefilename>.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 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. * 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 <basefilename>.log file * -lp Log input file parsing information to the <basefilename>.log file
* *
* -lg Log output bytestream generation information to the <basefilename>.log file * -lg Log output bytestream generation information to the <basefilename>.log file
* *
* -nx Put about "x" items on each line of the C file output
*
* -sn Use bytestream generation strategy "n". * -sn Use bytestream generation strategy "n".
* Two strategies are currently implemented: * Two strategies are currently implemented:
* 1: favor track 1 notes instead of all tracks equally * 1:favor track 1 notes instead of all tracks equally
* 2: try to keep each track to its own tone generator * 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 <basefilename>.bin, instead of a
* C-language source file with the name <basefilename>.c.
* *
* -cn Only process the channel numbers whose bits are on in the number "n". * -cn Only process the channel numbers whose bits are on in the number "n".
* For example, -c3 means "only process channels 0 and 1" * 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. * -kn Change the musical key of the output by n chromatic notes.
* -k-12 goes one octave down, -k12 goes one octave up, etc. * -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 score bytestream *****
* *
* The generated bytestream is a series of commands that turn notes on and off, and * The generated bytestream is a series of commands that turn notes on and off,
* start delays until the next note change. Here are the details, with numbers * maybe change instruments, and begin delays until the next note change.
* shown in hexadecimal. * 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: * 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 * 9t nn [vv]
* starting with 0. The notes numbers are the MIDI numbers for the chromatic * 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). * 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. * 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.) * (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 * 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 * interpreted as a 15-bit big-endian integer that is the number of milliseconds to
* wait before processing the next command. For example, * 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 * 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. * 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
*********************************************************************************************/

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

@ -1,4 +1,4 @@
/********************************************************************************* /***************************************************************************************
* *
* MIDITONES_SCROLL * MIDITONES_SCROLL
* *
@ -50,27 +50,36 @@
* -vi says that we expect volume information to be in the file, but we * -vi says that we expect volume information to be in the file, but we
* should ignore it when creating the display. * 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 * For source code to this and related programs, see
* https://github.com/LenShustek/arduino-playtune * www.github.com/LenShustek/miditones
* https://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 * Permission is hereby granted, free of charge, to any person obtaining a copy
* it under the terms of version 3 of the GNU General Public License as * of this software and associated documentation files (the "Software"), to deal
* published by the Free Software Foundation at www.gnu.org/licenses, * in the Software without restriction, including without limitation the rights
* with Additional Permissions under term 7(b) that the original copyright * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* notice and author attibution must be preserved and under term 7(c) that * copies of the Software, and to permit persons to whom the Software is
* modified versions be marked as different from the original. * furnished to do so, subject to the following conditions:
* *
* This program is distributed in the hope that it will be useful, * The above copyright notice and this permission notice shall be included in all
* but WITHOUT ANY WARRANTY,without even the implied warranty of * copies or substantial portions of the Software.
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* *
***********************************************************************************/ * 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 * Change log
* *
@ -80,7 +89,7 @@
* - Add a "-c" option to create C code output. * - Add a "-c" option to create C code output.
* Thanks go to mats.engstrom for the idea. * Thanks go to mats.engstrom for the idea.
* 04 April 2015, L. Shustek, V1.3 * 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. * 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. * Generate "const" data initialization for compatibility with Arduino IDE v1.6.x.
* 5 August 2016, L. Shustek, V1.4 * 5 August 2016, L. Shustek, V1.4
@ -90,11 +99,14 @@
* - Make the number of generators be 16 maximum, but the number actually displayed * - Make the number of generators be 16 maximum, but the number actually displayed
* can be specified by the -tn command-line option. * can be specified by the -tn command-line option.
* - Remove string.h because OS X has macros for strlcpy; thus had to add strlen(). * - 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. * - 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 <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@ -108,9 +120,12 @@
#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 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_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; FILE *infile, *outfile;
unsigned char *buffer, *bufptr; unsigned char *buffer, *bufptr;
@ -126,22 +141,36 @@ unsigned delay;
bool codeoutput = false; bool codeoutput = false;
bool expect_volume = false; bool expect_volume = false;
bool ignore_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] = {
static char *notename[256] = { /* maximum 5 characters */
// map from MIDI note number to octave and note name, // map from MIDI note number to octave and note name,
"-1C ","-1C#","-1D ","-1D#","-1E ","-1F ","-1F#","-1G ","-1G#","-1A ","-1A#","-1B ", "-1C ", "-1C#", "-1D ", "-1D#", "-1E ", "-1F ", "-1F#", "-1G ", "-1G#", "-1A ", "-1A#", "-1B ",
" 0C "," 0C#"," 0D "," 0D#"," 0E "," 0F "," 0F#"," 0G "," 0G#"," 0A "," 0A#"," 0B ", " 0C ", " 0C#", " 0D ", " 0D#", " 0E ", " 0F ", " 0F#", " 0G ", " 0G#", " 0A ", " 0A#", " 0B ",
" 1C "," 1C#"," 1D "," 1D#"," 1E "," 1F "," 1F#"," 1G "," 1G#"," 1A "," 1A#"," 1B ", " 1C ", " 1C#", " 1D ", " 1D#", " 1E ", " 1F ", " 1F#", " 1G ", " 1G#", " 1A ", " 1A#", " 1B ",
" 2C "," 2C#"," 2D "," 2D#"," 2E "," 2F "," 2F#"," 2G "," 2G#"," 2A "," 2A#"," 2B ", " 2C ", " 2C#", " 2D ", " 2D#", " 2E ", " 2F ", " 2F#", " 2G ", " 2G#", " 2A ", " 2A#", " 2B ",
" 3C "," 3C#"," 3D "," 3D#"," 3E "," 3F "," 3F#"," 3G "," 3G#"," 3A "," 3A#"," 3B ", " 3C ", " 3C#", " 3D ", " 3D#", " 3E ", " 3F ", " 3F#", " 3G ", " 3G#", " 3A ", " 3A#", " 3B ",
" 4C "," 4C#"," 4D "," 4D#"," 4E "," 4F "," 4F#"," 4G "," 4G#"," 4A "," 4A#"," 4B ", " 4C ", " 4C#", " 4D ", " 4D#", " 4E ", " 4F ", " 4F#", " 4G ", " 4G#", " 4A ", " 4A#", " 4B ",
" 5C "," 5C#"," 5D "," 5D#"," 5E "," 5F "," 5F#"," 5G "," 5G#"," 5A "," 5A#"," 5B ", " 5C ", " 5C#", " 5D ", " 5D#", " 5E ", " 5F ", " 5F#", " 5G ", " 5G#", " 5A ", " 5A#", " 5B ",
" 6C "," 6C#"," 6D "," 6D#"," 6E "," 6F "," 6F#"," 6G "," 6G#"," 6A "," 6A#"," 6B ", " 6C ", " 6C#", " 6D ", " 6D#", " 6E ", " 6F ", " 6F#", " 6G ", " 6G#", " 6A ", " 6A#", " 6B ",
" 7C "," 7C#"," 7D "," 7D#"," 7E "," 7F "," 7F#"," 7G "," 7G#"," 7A "," 7A#"," 7B ", " 7C ", " 7C#", " 7D ", " 7D#", " 7E ", " 7F ", " 7F#", " 7G ", " 7G#", " 7A ", " 7A#", " 7B ",
" 8C "," 8C#"," 8D "," 8D#"," 8E "," 8F "," 8F#"," 8G "," 8G#"," 8A "," 8A#"," 8B ", " 8C ", " 8C#", " 8D ", " 8D#", " 8E ", " 8F ", " 8F#", " 8G ", " 8G#", " 8A ", " 8A#", " 8B ",
" 9C "," 9C#"," 9D "," 9D#"," 9E "," 9F "," 9F#"," 9G ", " 9C ", " 9C#", " 9D ", " 9D#", " 9E ", " 9F ", " 9F#", " 9G ",
// or to channel 9 percussion notes as relocated by Miditones to notes 128..255 // or to channel 9 percussion notes as relocated by Miditones to notes 128..255
@ -163,10 +192,29 @@ static char *notename[256] = {
"P120 ", "P121 ", "P122 ", "P123 ", "P124 ", "P125 ", "P126 ", "P127" "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 *******************/ /************** command-line processing *******************/
void SayUsage(char *programName){ void SayUsage (char *programName) {
static char *usage[] = { static char *usage[] = {
"Display a MIDITONES bytestream", "Display a MIDITONES bytestream",
"Usage: miditones_scroll <basefilename>", "Usage: miditones_scroll <basefilename>",
@ -175,48 +223,54 @@ void SayUsage(char *programName){
" -v expects and displays volume information", " -v expects and displays volume information",
" -vi expects and ignores volume information", " -vi expects and ignores volume information",
" -c option creates an annotated C source file as <basefile>.c", " -c option creates an annotated C source file as <basefile>.c",
"" }; ""
int i=0; };
while (usage[i][0] != '\0') fprintf(stderr, "%s\n", usage[i++]); int i = 0;
while (usage[i][0] != '\0')
fprintf (stderr, "%s\n", usage[i++]);
} }
int HandleOptions(int argc,char *argv[]) { int HandleOptions (int argc, char *argv[]) {
/* returns the index of the first argument that is not an option; i.e. /* returns the index of the first argument that is not an option; i.e.
does not start with a dash or a slash*/ does not start with a dash or a slash */
int i,firstnonoption=0; int i, firstnonoption = 0;
/* --- The following skeleton comes from C:\lcc\lib\wizard\textmode.tpl. */ /* --- The following skeleton comes from C:\lcc\lib\wizard\textmode.tpl. */
for (i=1; i< argc;i++) { for (i = 1; i < argc; i++) {
if (argv[i][0] == '/' || argv[i][0] == '-') { if (argv[i][0] == '/' || argv[i][0] == '-') {
switch (toupper(argv[i][1])) { switch (toupper (argv[i][1])) {
case 'H': case 'H':
case '?': case '?':
SayUsage(argv[0]); SayUsage (argv[0]);
exit(1); exit (1);
case 'C': case 'C':
codeoutput = true; codeoutput = true;
break; break;
case 'T': case 'T':
if (sscanf(&argv[i][2],"%d",&num_tonegens) != 1 || num_tonegens <1 || num_tonegens > MAX_TONEGENS) goto opterror; if (sscanf (&argv[i][2], "%d", &num_tonegens) != 1 || num_tonegens < 1
printf("Displaying %d tone generators.\n", num_tonegens); || num_tonegens > MAX_TONEGENS)
goto opterror;
printf ("Displaying %d tone generators.\n", num_tonegens);
break; break;
case 'V': case 'V':
expect_volume = true; expect_volume = true;
if (argv[i][2] == '\0') break; if (argv[i][2] == '\0')
if (toupper(argv[i][2]) == 'I') ignore_volume = true; break;
else goto opterror; if (toupper (argv[i][2]) == 'I')
ignore_volume = true;
else
goto opterror;
break; break;
/* add more option switches here */ /* add more option switches here */
opterror: opterror:
default: default:
fprintf(stderr,"unknown option: %s\n",argv[i]); fprintf (stderr, "unknown option: %s\n", argv[i]);
SayUsage(argv[0]); SayUsage (argv[0]);
exit(4); exit (4);
} }
} } else {
else {
firstnonoption = i; firstnonoption = i;
break; break;
} }
@ -226,44 +280,48 @@ opterror:
/*************** portable string length *****************/ /*************** portable string length *****************/
int strlength(const char *str) { int strlength (const char *str) {
int i; int i;
for (i=0; str[i] != '\0'; ++i) ; for (i = 0; str[i] != '\0'; ++i);
return i; return i;
} }
/*************** safe string copy *****************/ /*************** safe string copy *****************/
unsigned int strlcpy(char *dst, const char *src, unsigned int siz) { unsigned int strlcpy (char *dst, const char *src, unsigned int siz) {
char *d = dst; char *d = dst;
const char *s = src; const char *s = src;
unsigned int n = siz; unsigned int n = siz;
/* Copy as many bytes as will fit */ /* Copy as many bytes as will fit */
if (n != 0) { if (n != 0) {
while (--n != 0) { while (--n != 0) {
if ((*d++ = *s++) == '\0') break; if ((*d++ = *s++) == '\0')
break;
} }
} }
/* Not enough room in dst, add NUL and traverse rest of src */ /* Not enough room in dst, add NUL and traverse rest of src */
if (n == 0) { if (n == 0) {
if (siz != 0) *d = '\0'; /* NUL-terminate dst */ if (siz != 0)
while (*s++) ; *d = '\0'; /* NUL-terminate dst */
while (*s++);
} }
return (s - src - 1); /* count does not include NUL */ return (s - src - 1); /* count does not include NUL */
} }
/*************** safe string concatenation *****************/ /*************** safe string concatenation *****************/
unsigned int strlcat(char *dst, const char *src, unsigned int siz) { unsigned int strlcat (char *dst, const char *src, unsigned int siz) {
char *d = dst; char *d = dst;
const char *s = src; const char *s = src;
unsigned int n = siz; unsigned int n = siz;
unsigned int dlen; unsigned int dlen;
/* Find the end of dst and adjust bytes left but don't go past end */ /* Find the end of dst and adjust bytes left but don't go past end */
while (n-- != 0 && *d != '\0') d++; while (n-- != 0 && *d != '\0')
d++;
dlen = d - dst; dlen = d - dst;
n = siz - dlen; n = siz - dlen;
if (n == 0) return (dlen + strlength(s)); if (n == 0)
return (dlen + strlength (s));
while (*s != '\0') { while (*s != '\0') {
if (n != 1) { if (n != 1) {
*d++ = *s; *d++ = *s;
@ -280,43 +338,70 @@ unsigned int strlcat(char *dst, const char *src, unsigned int siz) {
void file_error (char *msg, unsigned char *bufptr) { void file_error (char *msg, unsigned char *bufptr) {
unsigned char *ptr; unsigned char *ptr;
fprintf(stderr, "\n---> file format error at position %04X (%d): %s\n", fprintf (stderr, "\n---> file format error at position %04X (%d): %s\n",
(unsigned int)(bufptr-buffer), (unsigned int)(bufptr-buffer), msg); (unsigned int) (bufptr - buffer), (unsigned int) (bufptr - buffer), msg);
/* print some bytes surrounding the error */ /* print some bytes surrounding the error */
ptr = bufptr - 16; ptr = bufptr - 16;
if (ptr < buffer) ptr = buffer; if (ptr < buffer)
for (; ptr <= bufptr+16 && ptr < buffer+buflen; ++ptr) fprintf (stderr, ptr==bufptr ? " [%02X] ":"%02X ", *ptr); ptr = buffer;
fprintf(stderr, "\n"); for (; ptr <= bufptr + 16 && ptr < buffer + buflen; ++ptr)
exit(8); fprintf (stderr, ptr == bufptr ? " [%02X] " : "%02X ", *ptr);
fprintf (stderr, "\n");
exit (8);
} }
/************** Output a line for the current status as we start a delay **************/ /************** 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 // show the current time, status of all the tone generators, and the bytestream data that got us here
void print_status(void) { void print_status (void) {
if (codeoutput) fprintf (outfile, "/*"); // start comment int gen;
bool any_instr_changed = false;
for (gen = 0; gen < num_tonegens; ++gen)
any_instr_changed |= gen_instrument_changed[gen];
if (any_instr_changed) {
if (codeoutput)
fprintf (outfile, "//");
fprintf (outfile, "%21s", "");
for (gen = 0; gen < num_tonegens; ++gen) {
if (gen_instrument_changed[gen]) {
gen_instrument_changed[gen] = false;
fprintf (outfile, "%6s", instrumentname[gen_instrument[gen]]);
} else
fprintf (outfile, "%6s", "");
if (expect_volume && !ignore_volume)
fprintf (outfile, "%5s", "");
}
fprintf (outfile, "\n");
}
if (codeoutput)
fprintf (outfile, "/*"); // start comment
// print the current timestamp // print the current timestamp
fprintf (outfile, "%5d %7d.%03d ", delay, timenow/1000, timenow%1000); fprintf (outfile, "%5d %7d.%03d ", delay, timenow / 1000, timenow % 1000);
// print the current status of all tone generators // print the current status of all tone generators
for (gen=0; gen<num_tonegens; ++gen) { for (gen = 0; gen < num_tonegens; ++gen) {
fprintf (outfile, "%6s", gen_note[gen] == SILENT ? " " : notename[gen_note[gen]]); fprintf (outfile, "%6s", gen_note[gen] == SILENT ? " " : notename[gen_note[gen]]);
if (expect_volume && !ignore_volume) if (gen_note[gen] == SILENT) fprintf (outfile, " "); if (expect_volume && !ignore_volume)
else fprintf (outfile, " v%-3d", gen_volume[gen]); if (gen_note[gen] == SILENT)
fprintf (outfile, " ");
else
fprintf (outfile, " v%-3d", gen_volume[gen]);
} }
// display the hex commands that created these changes // display the hex commands that created these changes
fprintf (outfile, " %04X: ", (unsigned int)(lastbufptr-buffer)); // offset fprintf (outfile, " %04X: ", (unsigned int) (lastbufptr - buffer)); // offset
if (codeoutput) fprintf (outfile, "*/ "); // end comment if (codeoutput)
for (; lastbufptr <= bufptr; ++lastbufptr) fprintf (outfile, codeoutput ? "0x%02X," : "%02X ", *lastbufptr); fprintf (outfile, "*/ "); // end comment
for (; lastbufptr <= bufptr; ++lastbufptr)
fprintf (outfile, codeoutput ? "0x%02X," : "%02X ", *lastbufptr);
fprintf (outfile, "\n"); fprintf (outfile, "\n");
lastbufptr = bufptr+1; lastbufptr = bufptr + 1;
} }
int countbits (unsigned int bitmap) { int countbits (unsigned int bitmap) {
int count; int count;
for (count=0; bitmap; bitmap >>= 1) for (count = 0; bitmap; bitmap >>= 1)
count += bitmap & 1; count += bitmap & 1;
return count; return count;
} }
@ -324,7 +409,7 @@ int countbits (unsigned int bitmap) {
/********************* main loop ****************************/ /********************* main loop ****************************/
int main(int argc,char *argv[]) { int main (int argc, char *argv[]) {
int argno, i; int argno, i;
char *filebasename; char *filebasename;
#define MAXPATH 80 #define MAXPATH 80
@ -332,100 +417,122 @@ int main(int argc,char *argv[]) {
unsigned int tonegens_used; // bitmap of tone generators used unsigned int tonegens_used; // bitmap of tone generators used
unsigned int num_tonegens_used; // count 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 ("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 */ if (argc == 1) { /* no arguments */
SayUsage(argv[0]); SayUsage (argv[0]);
return 1; return 1;
} }
/* process options */ /* process options */
argno = HandleOptions(argc,argv); argno = HandleOptions (argc, argv);
filebasename = argv[argno]; filebasename = argv[argno];
/* Open the input file */ /* Open the input file */
strlcpy(filename, filebasename, MAXPATH); strlcpy (filename, filebasename, MAXPATH);
strlcat(filename, ".bin", MAXPATH); strlcat (filename, ".bin", MAXPATH);
infile = fopen(filename, "rb"); infile = fopen (filename, "rb");
if (!infile) { if (!infile) {
fprintf(stderr, "Unable to open input file %s", filename); fprintf (stderr, "Unable to open input file %s", filename);
return 1; return 1;
} }
/* Create the output file */ /* Create the output file */
if (codeoutput) { if (codeoutput) {
strlcpy(filename, filebasename, MAXPATH); strlcpy (filename, filebasename, MAXPATH);
strlcat(filename, ".c", MAXPATH); strlcat (filename, ".c", MAXPATH);
outfile = fopen(filename, "w"); outfile = fopen (filename, "w");
if (!infile) { if (!infile) {
fprintf(stderr, "Unable to open output file %s", filename); fprintf (stderr, "Unable to open output file %s", filename);
return 1; return 1;
} }
} } else
else outfile = stdout; outfile = stdout;
/* Read the whole input file into memory */ /* Read the whole input file into memory */
fseek(infile, 0, SEEK_END); /* find file size */ fseek (infile, 0, SEEK_END); /* find file size */
buflen = ftell(infile); buflen = ftell (infile);
fseek(infile, 0, SEEK_SET); fseek (infile, 0, SEEK_SET);
buffer = (unsigned char *) malloc (buflen+1); buffer = (unsigned char *) malloc (buflen + 1);
if (!buffer) { if (!buffer) {
fprintf(stderr, "Unable to allocate %ld bytes for the file", buflen); fprintf (stderr, "Unable to allocate %ld bytes for the file", buflen);
return 1; return 1;
} }
fread(buffer, buflen, 1, infile); fread (buffer, buflen, 1, infile);
fclose(infile); fclose (infile);
printf("Processing %s.bin, %ld bytes\n", filebasename, buflen); printf ("Processing %s.bin, %ld bytes.\n", filebasename, buflen);
if (codeoutput) { if (codeoutput) {
time_t rawtime; time_t rawtime;
time (&rawtime); time (&rawtime);
fprintf(outfile, "// Playtune bytestream for file \"%s.bin\"", filebasename); fprintf (outfile, "// Playtune bytestream for file \"%s.bin\"", filebasename);
fprintf(outfile, " created by MIDITONES_SCROLL V%s on %s\n", VERSION, asctime(localtime(&rawtime))); fprintf (outfile, " created by MIDITONES_SCROLL V%s on %s\n", VERSION,
fprintf(outfile, "const byte PROGMEM score [] = {\n"); 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 */ /* Do the titles */
fprintf(outfile, "\n"); fprintf (outfile, "\n");
if (codeoutput) fprintf(outfile, "//"); if (codeoutput)
fprintf(outfile, "duration time "); fprintf (outfile, "//");
for (i=0; i< num_tonegens; ++i) fprintf (outfile, "duration time ");
fprintf(outfile, expect_volume && !ignore_volume ? " gen%-5d" : " gen%-2d", i); for (i = 0; i < num_tonegens; ++i)
fprintf(outfile," bytestream code\n\n"); fprintf (outfile, expect_volume && !ignore_volume ? " gen%-5d" : " gen%-2d", i);
for (gen=0; gen<num_tonegens; ++gen) fprintf (outfile, " bytestream code\n\n");
for (gen = 0; gen < num_tonegens; ++gen)
gen_note[gen] = SILENT; gen_note[gen] = SILENT;
tonegens_used = 0; tonegens_used = 0;
lastbufptr = buffer; lastbufptr = buffer;
/* Process the commmands sequentially */ /* Process the commmands sequentially */
for (bufptr = buffer; bufptr < buffer+buflen; ++bufptr) { for (; bufptr < buffer + buflen; ++bufptr) {
cmd = *bufptr; cmd = *bufptr;
if (cmd < 0x80) { //***** delay if (cmd < 0x80) { /* delay */
delay = ((unsigned int)cmd << 8) + *++bufptr; delay = ((unsigned int) cmd << 8) + *++bufptr;
print_status(); // tone generator status now print_status (); // tone generator status now
timenow += delay; // advance time timenow += delay; // advance time
} } else if (cmd != 0xf0) { /* a command */
else if (cmd != 0xf0) { // a note command
gen = cmd & 0x0f; gen = cmd & 0x0f;
if (gen > max_tonegen_found) max_tonegen_found = gen; if (gen > max_tonegen_found)
max_tonegen_found = gen;
cmd = cmd & 0xf0; cmd = cmd & 0xf0;
if (cmd == 0x90) { //****** note on if (cmd == 0x90) { /* note on */
gen_note[gen] = *++bufptr; // note number gen_note[gen] = *++bufptr; // note number
tonegens_used |= 1<<gen; // record that we used this generator at least once tonegens_used |= 1 << gen; // record that we used this generator at least once
if (expect_volume) gen_volume[gen] = *++bufptr; // volume if (expect_volume)
if (gen >= num_tonegens) ++notes_skipped; // won't be displaying this note gen_volume[gen] = *++bufptr; // volume
} if (gen >= num_tonegens)
else if (cmd == 0x80) { //****** note off ++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); if (gen_note[gen] == SILENT)
file_error ("tone generator not on", bufptr);
gen_note[gen] = SILENT; gen_note[gen] = SILENT;
} } else if (cmd == 0xc0) { /* change instrument */
else file_error ("unknown command", bufptr); gen_instrument[gen] = *++bufptr & 0x7f;
gen_instrument_changed[gen] = true;
} else
file_error ("unknown command", bufptr);
} }
} }
@ -434,16 +541,19 @@ int main(int argc,char *argv[]) {
delay = 0; delay = 0;
--bufptr; --bufptr;
if (codeoutput) --bufptr; //don't do 0xF0 for code, because we don't want the trailing comma if (codeoutput)
print_status(); // print final status --bufptr; //don't do 0xf0 for code, because we don't want the trailing comma
print_status (); // print final status
if (codeoutput) { if (codeoutput) {
fprintf(outfile, " 0xf0};\n"); fprintf (outfile, " 0xf0};\n");
num_tonegens_used = countbits(tonegens_used); num_tonegens_used = countbits (tonegens_used);
fprintf(outfile, "// This score contains %ld bytes, and %d tone generator%s used.\n", 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"); buflen, num_tonegens_used, num_tonegens_used == 1 ? " is" : "s are");
} } else
printf("\nAt most %u tone generators were used.\n", max_tonegen_found+1); fprintf (outfile, "\n");
if (notes_skipped) printf("%u notes were not displayed because we were told to show only %u generators.\n", notes_skipped, num_tonegens); 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"); printf ("Done.\n");
} }

Binary file not shown.
Loading…
Cancel
Save