diff --git a/Media/icon.png b/Media/icon.png new file mode 100644 index 0000000..a1c3d43 Binary files /dev/null and b/Media/icon.png differ diff --git a/JuceOPLVSTi.jucer b/OPL.jucer similarity index 50% rename from JuceOPLVSTi.jucer rename to OPL.jucer index 6de47df..d09deb5 100644 --- a/JuceOPLVSTi.jucer +++ b/OPL.jucer @@ -1,147 +1,145 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cmd/release_win.cmd b/cmd/release_win.cmd deleted file mode 100644 index 18a2d92..0000000 --- a/cmd/release_win.cmd +++ /dev/null @@ -1,23 +0,0 @@ -@ECHO OFF -IF [%1]==[] GOTO usage - -ECHO Building Windows Release -SET RELEASE_VERSION=%1 - -MSBUILD Builds\VisualStudio2015\JuceOPLVSTi.vcxproj /p:Configuration="Release - 32-bit" /p:Platform="x86" -MSBUILD Builds\VisualStudio2015\JuceOPLVSTi.vcxproj /p:Configuration="Release - 64-bit" /p:Platform="x64" - -MKDIR %RELEASE_TEMP% -COPY "Builds\VisualStudio2015\Release - 32-bit\JuceOPLVSTi.dll" . -COPY "Builds\VisualStudio2015\x64\Release - 64-bit\JuceOPLVSTi x64.dll" . - -REM We can zip using jar from the JDK! -REM http://stackoverflow.com/a/29879102/1480560 -DEL JuceOPLVSTi_%RELEASE_VERSION%.zip -jar -cMf JuceOPLVSTi_%RELEASE_VERSION%.zip "JuceOPLVSTi x64.dll" JuceOPLVSTi.dll Instruments -DEL "JuceOPLVSTi x64.dll" JuceOPLVSTi.dll -GOTO :eof - -:usage -ECHO Specify version on command line (eg 0-14-1) - diff --git a/dro2midi/README b/dro2midi/README deleted file mode 100644 index b388fc1..0000000 --- a/dro2midi/README +++ /dev/null @@ -1,182 +0,0 @@ ---------------------------------------------------------------- - DRO2MIDI - version 1.6 (2013-11-29) - Written by malvineous@shikadi.net - Heavily based upon IMF2MIDI written by Guenter Nagler in 1996 - http://www.shikadi.net/utils/ ---------------------------------------------------------------- - -// What is it? -//////////////// - -DRO2MIDI converts Adlib music (in .dro, .imf or .raw format) into standard -MIDI files, so that it can be played or edited in most audio applications, or -so the notes can be extracted to be played on a real instrument. - -// Features -///////////// - - * Converts DOSBox .dro captures, id Software .imf songs and Rdos .raw captures - - * Converts OPL frequency changes into MIDI pitchbends - - * Tries to map Adlib instruments to MIDI instruments as best it can, but - these mappings can be easily added to and changed - - * Instruments can also be mapped to MIDI percussion - - * OPL rhythm-mode percussion is converted (v1.4 adds support for user-defined - mapping via new syntax in the mapping file) - - * The OPL conversion constant can be changed (see -c option) to more - accurately convert notes without excessive pitchbend events - -// Usage -////////// - - Linux: $ dro2midi file.dro file.mid - - Windows: C:\>dro2midi file.dro file.mid - -For a list of the command line options run dro2midi with no parameters or -see below for more details. For best results, all the .txt data files should -be in the current directory. - -Instrument mappings between Adlib registers and MIDI instruments are stored in -inst.txt. This file contains a number of existing mappings, but additional -mappings can easily be added. During conversion, if an exact match cannot -be found the mapping with the closest Adlib parameters will be used instead. -A message will be printed when this happens, along with a line that can be -copied into inst.txt to provide an exact match. This approximation can have -the unfortunate side effect of providing some odd conversions, such as -converting a bass-line into a monotonic drum. - -To get a perfect conversion you may wish to delete all but the first -"all-zero" instrument in inst.txt, which will cause all instruments to be -converted as a piano. You can then copy the definitions printed during -conversion one by one into inst.txt, to assign the best-sounding instrument -without worring about any default mappings taking over. Alternatively the -i -option can be used which will disable the closest-match algorithm, and only -exact matches will be used (again, anything that can't be exactly matched will -be mapped as a piano.) - -// Command-line options -///////////////////////// - --p disables generation of MIDI pitchbends. This results in a single note-on -when the instrument sounds, but no further pitch change results until the -note is switched off again. This can also be used to prevent the large number -of small pitchbends generated when the conversion constant is slightly off (but -see the -c option, which now provides a better way around this.) - --a will, if pitchbends are disabled with -p, approximate any pitchbend by -playing the nearest note to the new pitch at the time. This results in a -"hammering" of notes during a pitchbend, which while humourous, is probably -of limited use. - --r disables the conversion of OPL rhythm mode instruments. As OPL rhythm mode -conversion is now quite flexible, this option should rarely be needed. - --i will disable the approximation algorithm which selects similar instruments -when an exact match cannot be found in the mapping file (insts.txt). This is -useful when trying to create a perfect map for a single song, as it makes it -easier to pick out which instruments are being mapped. This option should not -be used for the final conversion however, as any instruments that haven't been -precisely mapped will come out (by default) as a grand piano. (This default -mapping is simply the first entry in insts.txt.) - --c changes the conversion constant used when converting OPL notes into their -MIDI equivalents. This is the heart of what DRO2MIDI does. Unfortunately -depending on which set of documents are available, those writing OPL *players* -are told the conversion constant is either 49716, or 50000. Thus half the -games out there use one, and half use the other. The result of using the wrong -constant in the DRO2MIDI conversion is a tiny difference in pitch when the song -is played (about 1/17th of a cent.) Most people will be unable to hear the -difference between these two values, however if the wrong constant is used by -DRO2MIDI during the conversion into MIDI, the resulting file will contain -thousands of small pitchbend events, as it tries to approximate the exact OPL -note played. Using -c to try a different constant should solve this problem. -The default constant is 49716, and "-c alt" will change the constant to 50000. -It is possible to specify an arbitrary constant like "-c 49999" however this -should be unnecessary unless the same nonstandard constant was used wherever -the song was originally played. Note that no error checking is performed here, -so out of range values could easily crash the program (not that that's a major -problem though...) After conversion the number of pitchbend events is -displayed at the end of the output, so it will be obvious when the correct -constant is in use as this number will be significantly smaller than with -any other constant. - --v disables the volume detection. Normally DRO2MIDI will take the OPL -carrier's "Level" amount and translate it to a MIDI note velocity. This will -result in the output MIDI file more accurately matching the loud and quiet -parts of the OPL song. Some songs (e.g. Stunts) somehow manage to work with -these volume levels set to zero, which results in very quiet MIDI files (there -is an internal limit as to how quiet a note can sound to prevent it being lost -entirely.) If your output MIDI file is much too quiet, this option will cause -all notes to be played at maximum velocity. - --s instructs dro2midi to write all detected instruments to a .sbi file. This -is a 52 byte binary instrument format for OPL chips created by Creative Labs. -It is supported by applications written to work with the OPL, such as Ad Lib -Tracker 2. - -// inst.txt -///////////// - -The instrument mappings are stored in inst.txt, in a format like this: - - NO 07-12/4F-00/F2-F2/60-72/08/00-00: patch=15 # Tubular bells - -These lines are printed automatically when an unknown instrument is encounted. -All you will need to do is copy and paste the line into insts.txt and choose -a MIDI instrument for it. The file itself is read in from the current -directory during conversion, so if you run DRO2MIDI in another folder remember -to copy the file across too or your mappings won't be used. - -The first two characters indicate what type of instrument it is. The -hexadecimal numbers that follow are the Adlib register values for that -instrument. "patch=15" assigns MIDI instrument #15 for this Adlib instrument. -For percussion, "drum=35" could be used instead. Anything after a # symbol is -treated as a comment. See the comments at the top of the file for more -detailed information. - -The instrument names (and values to supply to the patch= parameter) are stored -in patch.txt, and the drum names (and numbers) in drum.txt. You may find -these files helpful to reference when selecting instruments for conversion. -(These two files are read in from the current directory during conversion to -allow the display of instrument names in status messages instead of just -numbers.) - -Note that the parser for insts.txt file is quick and dirty, so it's easy to -get a syntax error - for example, an otherwise blank line with a single space -on it will cause an error (so if you get an error about a blank line, make -sure it really is blank!) - -// License -//////////// - -DRO2MIDI was based on IMF2MIDI by Guenter Nagler. DRO2MIDI is released under -the GPL license, except where it is incompatible with IMF2MIDI's original -license, in which case IMF2MIDI's license takes precedence. - ----- Begin IMF2MIDI license ---- - -IMF2MIDI (c) 1996 was created by Guenter Nagler. - -IMF2MIDI is free and may be used as you wish with this one exception: - - You may NOT charge any fee or derive any profit for distribution - of IMF2MIDI. Thus, you may NOT sell or bundle IMF2MIDI with any - product in a retail environment (shareware disk distribution, CD-ROM, - etc.) without permission of the author. - -You may give IMF2MIDI to your friends, upload it to a BBS, or ftp it to -another internet site, as long as you don't charge anything for it. - ----- End IMF2MIDI license ---- - -// Contact -//////////// - -Source code is available at http://www.shikadi.net/utils/ - -You can e-mail me at malvineous@shikadi.net diff --git a/dro2midi/build.bat b/dro2midi/build.bat deleted file mode 100644 index 2864811..0000000 --- a/dro2midi/build.bat +++ /dev/null @@ -1 +0,0 @@ -cl midiio.cpp dro2midi.cpp /link /OUT:dro2midi.exe diff --git a/dro2midi/dro2midi.cpp b/dro2midi/dro2midi.cpp deleted file mode 100644 index aa44e3d..0000000 --- a/dro2midi/dro2midi.cpp +++ /dev/null @@ -1,1328 +0,0 @@ -// -// DRO2MIDI - Convert DOSBox raw OPL captures (.dro), Rdos (.raw) and id -// Software (.imf, .wlf) into MIDI files (.mid) -// -// Created by malvineous@shikadi.net in June 2007. See README for license. -// Based on imf2midi v1.0 written by Guenter Nagler in 1996 (gnagler@ihm.tu-graz.ac.at) -// -// v1.0 / 2007-06-16 / malvineous@shikadi.net: Original release -// - imf2midi with .imf reader hacked to read .dro files instead -// -// v1.1 / 2007-07-28 / malvineous@shikadi.net: More file formats -// - Added .imf and .raw support. -// - Replaced Guenter's OPL -> MIDI frequency conversion algorithm (from a -// lookup table into a much more accurate formula), consequently was able -// to simplify pitchbend code (now conversions with pitchbends enabled -// sound quite good!) -// -// v1.2 / 2007-07-28 / malvineous@shikadi.net: Bugfix release -// - Fixed some file length calculations causing some files to be converted -// without any notes. -// - Added portamento-to-note for large (>2 semitone) pitchbends, but it -// doesn't seem to work when using Timidity. -// -// v1.3 / 2007-09-02 / malvineous@shikadi.net: New features -// - Fixed "tom tom" incorrectly called "bass drum" in output messages. -// - Fixed multi-note pitchbends by removing portamento-to-note and -// adjusting standard pitchbend range instead, thanks to a suggestion -// by Xky (xkyrauh2001@hotmail.com) -// - Implemented a better method for reading Adlib register -> MIDI patch -// mapping information (all stored in inst.txt now instead of having a -// seperate file for each instrument.) Also improved method for mapping -// instruments to percussion on MIDI channel 10. -// - Fixed OPL rhythm instrument conversion issue (a MIDI noteon was being -// generated too often - if the OPL instrument is on and we receive -// another keyon, it *shouldn't* generate a fresh MIDI keyon.) -// - Fixed IMF type-1 conversion issue where unsigned numbers were being -// read as signed, and the conversion was cutting off half way through. -// -// v1.4 / 2009-03-28 / malvineous@shikadi.net -// - Some code cleanup, fixed all the warnings in midiio.cpp. -// - Fixed a bunch of char/unsigned char issues, hopefully Win32 -// conversions will now be as reliable as under Linux. -// - Added line numbers to instrument names and mapping file error messages. -// - Added new instrument mapping entries for rhythm mode instruments -// (which previously were hard coded.) -// - Added transpose and mute options to instrument mapping file. -// - Added -c option to change OPL constant during conversion, thanks to a -// suggestion from Wraithverge (liam82067@yahoo.com) -// - Added -v option to disable note volume (helps with Stunts which -// otherwise comes out with no notes because they're all silent.) -// - Corrected OPL volume -> MIDI note velocity algorithm, and added hard -// limit to prevent notes from having a zero velocity (which stops them -// from being converted, like with Stunts.) -// - New instrument mappings (to be copied into insts.txt) are printed to -// stderr, so "dro2midi 2>> insts.txt" will conveniently append them all -// to the mapping file. Thanks to Wraithverge for the idea. -// - Replaced getop() and getchannel() lookup functions with GET_OP() and -// GET_CHANNEL() macro algorithms to calculate the values as required. -// - Included 128 standard GM instrument mappings extracted from Creative -// Labs' MIDI player (see gen_test_midi.cpp.) -// -// v1.5 / 2010-03-28 / Wraithverge (liam82067 at yahoo dot com): Changes -// - Added code for several MIDI Controller Events for output MID-files, -// and they are the following: -// Reset Controllers [121] -- At the head of the Event list. -// Balance (or Pan) [10] -- At the head of the Event list. -// Expression [11] -- At the head of the Event list. -// All Notes Off [123] -- At the foot of the Event list. -// Hopefully, other musicians will find these to be needed, as well. -// - Added detectors for the (at this time) two DRO formats, and a hint -// to let us know that the DRO v2.0 format is not supported. -// -// v1.6 / 2013-11-14 / bsa (http://bsutherland.github.io/JuceOPLVSTi) -// - Added -s switch to save detected instruments in Creative Sound -// Blaster Instrument format (.sbi) -// - -#define VERSION "1.6" -#define MAPPING_FILE "inst.txt" - -#define PATCH_NAME_FILE "patch.txt" -#define PERC_NAME_FILE "drum.txt" -#define NUM_MIDI_PATCHES 128 // 128 MIDI instruments -#define NUM_MIDI_PERC 128 // 46 MIDI percussive notes (channel 10), but 128 possible notes -#define INSTR_NAMELEN 32 // Maximum length of an instrument name - -//#define PITCHBEND_RANGE 12.0 // 12 == pitchbends can go up to a full octave -#define PITCHBEND_RANGE 24.0 // 24 == pitchbends can go up two full octaves -#define PITCHBEND_ONESEMITONE (8192.0 / PITCHBEND_RANGE) -const double pitchbend_center = 8192.0; - -#include "midiio.hpp" -#include -#include -#include -#include -#include -#include - -#define WRITE_BINARY "wb" -#define READ_TEXT "r" - -#ifdef _MSC_VER -// Keep MS VC++ happy -#define strncasecmp _strnicmp -#define strcasecmp _stricmp -#define snprintf _snprintf -#define log2(x) (log(x) / log(2.)) -inline double round( double d ) -{ -return floor( d + 0.5 ); -} -#endif - -bool bRhythm = true; // convert rhythm mode instruments (-r) -bool bUsePitchBends = true; // use pitch bends to better match MIDI note frequency with the OPL frequency (-p) -bool bApproximatePitchbends = false; // if pitchbends are disabled, should we approximate them by playing the nearest note when the pitch changes? -bool bPerfectMatchesOnly = false; // if true, only match perfect instruments -bool bEnableVolume = true; // enable note velocity based on OPL instrument volume -bool bWriteSbiInstruments = false; // write detected instruments to .SBI files - -// Rhythm instruments -enum RHYTHM_INSTRUMENT { - NormalInstrument, - BassDrum, - SnareDrum, - TomTom, - TopCymbal, - HiHat -}; - -// MIDI channels to use for these instruments when they are mapped as normal -// notes. Probably best not to use 1-10 as these are used by the rest of the -// OPL mapping code. Note these are zero-based, so the GM drum channel is -// channel 9 in this context. -#define CHAN_BASSDRUM 10 // Bass drum sits on OPL channel 7 -#define CHAN_SNAREDRUM 11 // OPL channel 8 carrier -#define CHAN_TOMTOM 12 // OPL channel 9 modulator -#define CHAN_TOPCYMBAL 13 // OPL channel 9 carrier -#define CHAN_HIHAT 14 // OPL channel 8 modulator - -char cPatchName[NUM_MIDI_PATCHES][INSTR_NAMELEN]; -char cPercName[NUM_MIDI_PERC][INSTR_NAMELEN]; - -char* input = 0; -char* output = 0; - -FILE* f = 0; // input file -MidiWrite* write = 0; - -//int resolution = 384; // 560Hz IMF -int resolution = 500; // 1000Hz DRO -int program = 0; -float tempo = 120.0; - -// Arrays of [9] refer to OPL channels, arrays of [16] also refer to OPL -// channels but use fake channels (11-15) for rhythm mode instruments (but -// only for elements like keyon and instrument mapping that can happen -// independently of the OPL channel in use. Things like OPL channel pitch -// which affect both rhythm mode instruments using that channel are not -// stored separately, i.e. they're in the [9] array.) -int mapchannel[16]; -int curfreq[9]; -bool keyAlreadyOn[16]; -int lastkey[16]; // last MIDI key pressed on this channel -int pitchbent[16]; -int transpose[16]; // used for instruments with mapped transpose values -int drumnote[16]; // note to play on MIDI channel 10 if Adlib channel has a - // percussive instrument assigned to it -int lastprog[16]; // last program/patch set on the MIDI channel -bool mute[16]; // true if the instrument on this channel is currently muted - -// Statistics -int iNotesActive = 0; -int iPitchbendCount = 0; -int iTotalNotes = 0; - -typedef struct -{ - /*unsigned char reg20[2]; - unsigned char reg40[2]; - unsigned char reg60[2]; - unsigned char reg80[2]; - unsigned char regC0; - unsigned char regE0[2];*/ - - unsigned int reg20[2]; - unsigned int reg40[2]; - unsigned int reg60[2]; - unsigned int reg80[2]; - unsigned int regC0; - unsigned int regE0[2]; - - // Is this a normal instrument or a mapping for a rhythm instrument? - RHYTHM_INSTRUMENT eRhythmInstrument; - - int prog; // MIDI patch (or -1 if a drum) - int isdrum; - int note; // note to play if drum - bool muted; // true if this instrument is muted - char name[128]; - - signed int iTranspose; // number of semitones to transpose this instrument - - // The instrument can be redirected to another. This is used when a new - // instrument is encountered - its info is printed to the screen then it's - // recorded (pointing to its best match) to prevent it appearing as a new - // instrument hundreds of times and cluttering the output. - int redirect; // if >= 0, use ::instr[redirect] instead of this one - - // When using this struct to cache current channel parms we need to - // remember the octave, so that when registers 0xA0-0xA8 are changed - // (to change note frequency) we can still pass the octave to the note - // conversion function (as the octave is otherwise only available when - // setting registers 0xB0-0xB8.) - int iOctave; -} INSTRUMENT; - -INSTRUMENT reg[9]; // current registers of channel - -#define MAXINSTR 2048 -int instrcnt = 0; -INSTRUMENT instr[MAXINSTR]; - -int iFormat = 0; // input format -#define FORMAT_IMF 1 -#define FORMAT_DRO 2 -#define FORMAT_RAW 3 -int iSpeed = 0; // clock speed (in Hz) -int iInitialSpeed = 0; // first iSpeed value written to MIDI header - -double dbConversionVal; -// Convert the given OPL F-num and octave values into a fractional MIDI note -// number (for the use of pitchbends.) -double freq2key(int freq, int octave) -{ - int iFNum = freq; - int iBlock = octave; - double dbOriginalFreq = dbConversionVal * (double)iFNum * pow(2, (double)(iBlock - 20)); - return 69.0 + 12.0 * log2(dbOriginalFreq / 440.0); -} - -void version() -{ - printf("DRO2MIDI v" VERSION " - Convert raw Adlib captures to General MIDI\n" - "Written by malvineous@shikadi.net in 2007\n" - "Heavily based upon IMF2MIDI written by Guenter Nagler in 1996\n" - "With contributions by Wraithverge (C) 2010, bsa in 2013\n" - "http://www.shikadi.net/utils/\n" - "\n" - ); - return; -} - -void usage() -{ - version(); - fprintf(stderr, - "Usage: dro2midi [-p [-a]] [-r] [-i] [-c alt|] [-v] input.dro output.mid\n" - "\n" - "Where:\n" - " -p Disable use of MIDI pitch bends\n" - " -a If pitchbends are disabled, approximate by playing the nearest note\n" - " -r Don't convert OPL rhythm-mode instruments\n" - " -i Only use instruments that match perfectly (default is 'close enough is\n" - " good enough.') Useful when guessing new patches. Instruments that can't\n" - " be matched use the first entry in " MAPPING_FILE " (piano by default)\n" - " -c Change the value used to convert OPL notes into MIDI notes from the\n" - " default 49716. Any non-zero value can be specified. The special word\n" - " \"alt\" means 50000, the other commonly used value. It is unlikely any\n" - " other values will need to be used, unless you get an excessive amount\n" - " of artificial pitchbends in the output MIDI.\n" - " -v Disable note velocity and play all notes as loudly as possible,\n" - " instead of trying to match the volume of the OPL note.\n" - " -s Write detected instruments to .sbi files\n" - " (Creative Sound Blaster Instrument).\n" - "\n" - "Supported input formats:\n" - " .raw Rdos RAW OPL capture\n" - " .dro DOSBox RAW OPL capture\n" - " .imf id Software Music Format (type-0 and type-1 at 560Hz)\n" - " .wlf id Software Music Format (type-0 and type-1 at 700Hz)\n" - "\n" - "Instrument definitions are read in from " MAPPING_FILE ". Instrument names\n" - "are read in from " PATCH_NAME_FILE " and " PERC_NAME_FILE ". See the README for more details.\n" - ); - exit(1); -} - -/* Channel 1 2 3 4 5 6 7 8 9 - * Operator 1 00 01 02 08 09 0A 10 11 12 } cells - * Operator 2 03 04 05 0B 0C 0D 13 14 15 } - */ -// Converts a cell into a channel -#define GET_CHANNEL(i) ((((i) / 8) * 3) + (((i) % 8) % 3)) - -// Converts a cell into 0 (for operator 1) or 1 (for operator 2) -#define GET_OP(i) (((i) % 8) / 3) - -// Helper functions to read data from files in a non-optimised but platform -// independent (little/big endian) way. -inline unsigned char readByte(FILE *f) -{ - unsigned char c; - fread(&c, 1, 1, f); - return c; -} -inline unsigned short readUINT16LE(FILE *f) -{ - unsigned char c[2]; - fread(&c, 1, 2, f); - return c[0] | (c[1] << 8L); -} -inline unsigned long readUINT32LE(FILE *f) -{ - unsigned char c[4]; - fread(&c, 1, 4, f); - return c[0] | (c[1] << 8L) | (c[2] << 16L) | (c[3] << 24L); -} - -bool loadInstruments(void) -{ - for (int i = 0; i < NUM_MIDI_PATCHES; i++) sprintf(cPatchName[i], "Patch #%d", i+1); - for (int i = 0; i < NUM_MIDI_PERC; i++) sprintf(cPercName[i], "Note #%d", i); - - char line[256]; - - FILE* p = fopen(PATCH_NAME_FILE, "r"); - if (!p) { - fprintf(stderr, "Warning: Unable to open file listing patch names (" - PATCH_NAME_FILE ")\nInstrument names will not be available.\n"); - } else { - while (fgets(line, sizeof(line)-1, p)) { - int iValue, iLen; - char *p = strpbrk(line, "\n\r"); - if (p) *p = '\0'; // terminate the string at the newline - if (sscanf(line, "%d=%n", &iValue, &iLen) == 1) { - assert(iValue <= NUM_MIDI_PATCHES); - snprintf(cPatchName[iValue-1], INSTR_NAMELEN, "%s [%d]", &line[iLen], iValue); - } else if ((line[0] != '#') && (line[0] != '\n')) { - fprintf(stderr, "Invalid line in " PATCH_NAME_FILE ": %s\n", line); - } - } - fclose(p); - } - - p = fopen(PERC_NAME_FILE, "r"); - if (!p) { - fprintf(stderr, "Warning: Unable to open file listing percussion note " - "names (" PERC_NAME_FILE ")\nPercussion names will not be available.\n"); - } else { - while (fgets(line, sizeof(line)-1, p)) { - int iValue, iLen; - char *p = strpbrk(line, "\n\r"); - if (p) *p = '\0'; // terminate the string at the newline - if (sscanf(line, "%d=%n", &iValue, &iLen) == 1) { - assert(iValue <= NUM_MIDI_PERC); - snprintf(cPercName[iValue], INSTR_NAMELEN, "%s [%d]", &line[iLen], iValue); - } else if ((line[0] != '#') && (line[0] != '\n')) { - fprintf(stderr, "Invalid line in " PERC_NAME_FILE ": %s\n", line); - } - } - fclose(p); - } - - FILE* f = fopen(MAPPING_FILE, "r"); - if (!f) { - fprintf(stderr, "Warning: Unable to open instrument mapping file " - MAPPING_FILE ", defaulting to a Grand Piano\nfor all instruments.\n"); - return true; - } - INSTRUMENT in; - memset(&in, 0, sizeof(in)); - in.redirect = -1; // none of these should redirect (but later automatic - // instruments will redirect to these ones) - - // Loop until we run out of lines in the data file or we hit the maximum - // number of instruments - char value[256]; - int iLineNum = 0; - for (::instrcnt = 0; fgets(line, sizeof(line)-1, f) && (instrcnt < MAXINSTR);) { - iLineNum++; - - // Ignore blank lines and comments - if ((line[0] == '#') || (line[0] == '\r') || (line[0] == '\n')) continue; - - // Figure out what type of rhythm mode instrument (if any) the first two - // chars are referring to. - char cInstType[3]; - int iNumFields = sscanf(line, "%2s ", cInstType); - if ((cInstType[0] == 'N') && (cInstType[1] == 'O')) in.eRhythmInstrument = NormalInstrument; - else if ((cInstType[0] == 'B') && (cInstType[1] == 'D')) in.eRhythmInstrument = BassDrum; - else if ((cInstType[0] == 'S') && (cInstType[1] == 'D')) in.eRhythmInstrument = SnareDrum; - else if ((cInstType[0] == 'T') && (cInstType[1] == 'T')) in.eRhythmInstrument = TomTom; - else if ((cInstType[0] == 'T') && (cInstType[1] == 'C')) in.eRhythmInstrument = TopCymbal; - else if ((cInstType[0] == 'H') && (cInstType[1] == 'H')) in.eRhythmInstrument = HiHat; - else { - fprintf(stderr, "Invalid instrument type \"%s\" on line %d:\n\n %s\n", - cInstType, iLineNum, line); - return false; - } - - switch (in.eRhythmInstrument) { - case NormalInstrument: - case BassDrum: - // Normal instrument or rhythm Bass Drum, read both - // operators + connection byte - iNumFields = sscanf(&line[3], "%02X-%02X/%02X-%02X/%02X-%02X/%02X-%02X" - "/%02X/%02X-%02X: %255c\n", - &in.reg20[0], &in.reg20[1], - &in.reg40[0], &in.reg40[1], - &in.reg60[0], &in.reg60[1], - &in.reg80[0], &in.reg80[1], - &in.regC0, - &in.regE0[0], &in.regE0[1], value); - if (iNumFields != 12) { - fprintf(stderr, "Unable to parse line %d: (expected 12 instrument " - "fields, got %d)\n\n%s\n", iLineNum, iNumFields, line); - return false; - } - break; - - case TomTom: - case HiHat: - // This instrument is one operator only, but it does use the connection - // byte (probably) - iNumFields = sscanf(&line[3], "%02X/%02X/%02X/%02X/%02X/%02X: %255c\n", - &in.reg20[0], - &in.reg40[0], - &in.reg60[0], - &in.reg80[0], - &in.regC0, - &in.regE0[0], value); - if (iNumFields != 7) { - fprintf(stderr, "Unable to parse line %d: (expected 7 instrument " - "fields, got %d)\n\n%s\n", iLineNum, iNumFields, line); - return false; - } - break; - - case SnareDrum: - case TopCymbal: - // This instrument does not uses the connection byte, so read in one byte - // less. Also read the values into the other operator. - iNumFields = sscanf(&line[3], "%02X/%02X/%02X/%02X/%02X: %255c\n", - &in.reg20[1], - &in.reg40[1], - &in.reg60[1], - &in.reg80[1], - &in.regE0[1], value); - if (iNumFields != 6) { - fprintf(stderr, "Unable to parse line %d: (expected 6 instrument " - "fields, got %d)\n\n%s\n", iLineNum, iNumFields, line); - return false; - } - break; - } - - // Default options - in.isdrum = 0; - in.prog = 1; - in.iTranspose = 0; - in.muted = false; - - // If we got this far it's a valid instrument - int iValue; - char nextopt[256]; - // We need to manually terminate the %255c in the sscanf() above, so do - // this by terminating it at the end-of-line char. Hopefully reading past - // the end of the input string (which causes this) won't crash things... - for (int i = 0; i < 256; i++) { - if (value[i] == '\n') value[i] = 0; - else if (value[i] == '\0') break; - } - value[255] = 0; - - const char *cList = value; - int iLen; - while (sscanf(cList, "%s%n", nextopt, &iLen) >= 1) { - cList += iLen; - if (nextopt[0] == '#') break; // reached an end of line comment - if (nextopt[0] == ' ') continue; // skip double spaces - if (sscanf(nextopt, "patch=%d", &iValue) == 1) { - // MIDI patch - in.isdrum = 0; - in.prog = iValue - 1; - if ((in.prog < 0) || (in.prog > 127)) { - fprintf(stderr, "ERROR: Instrument #%d (line %d) was set to " - "patch=%d, but this value must be between 1 and 128 inclusive.\n", - instrcnt, iLineNum, in.prog + 1); - return false; - } - } else if (sscanf(nextopt, "drum=%d", &iValue) == 1) { - // MIDI drum - in.isdrum = 1; - in.prog = -1; - in.note = iValue; - if ((in.note < 0) || (in.note > 127)) { - fprintf(stderr, "ERROR: Drum instrument #%d (line %d) was set to " - "drum=%d, but this value must be between 1 and 128 inclusive.\n", - instrcnt, iLineNum, in.note); - return false; - } - } else if (sscanf(nextopt, "transpose=%d", &iValue) == 1) { - // MIDI drum - in.iTranspose = iValue; - } else if (strcmp(nextopt, "mute") == 0) { - // Mute this instrument - in.muted = true; - } else { - fprintf(stderr, "Unknown instrument option on line %d: %s\n", - iLineNum, nextopt); - return false; - } - } - - char cInstTypeText[256]; - switch (in.eRhythmInstrument) { - case NormalInstrument: cInstTypeText[0] = '\0'; break; - case BassDrum: strcpy(cInstTypeText, "(OPL BD) "); break; - case TomTom: strcpy(cInstTypeText, "(OPL TT) "); break; - case HiHat: strcpy(cInstTypeText, "(OPL HH) "); break; - case SnareDrum: strcpy(cInstTypeText, "(OPL SD) "); break; - case TopCymbal: strcpy(cInstTypeText, "(OPL TC) "); break; - } - sprintf(in.name, "Inst#%03d %s@ line %3d%s: %s", instrcnt, - cInstTypeText, - iLineNum, - (in.isdrum) ? " (perc)" : "", - (in.isdrum) ? cPercName[in.note] : cPatchName[in.prog] - ); - if (in.iTranspose) { - char cTranspose[256]; - sprintf(cTranspose, " @ %+d semitones", in.iTranspose); - strcat(in.name, cTranspose); - } - if (in.muted) strcat(in.name, " {muted}"); - //printf("%s\n", in.name); - - // Add instrument - ::instr[instrcnt++] = in; - } - fclose(f); - return true; -} - -long difference(int a, int b, int importance = 1) -{ - long diff = a - b; - if (diff < 0) diff = -diff; - return diff * importance; -} - -long compareinstr(INSTRUMENT& a, INSTRUMENT& b, RHYTHM_INSTRUMENT ri) -{ - // Note that we're not using b.eRhythmInstrument below as "b" refers to the - // OPL channel, and this never has an instrument type set. The instrument - // type we want is passed in as "ri", so we compare against that instead. - //fprintf(stderr, "%d ", ri); - switch (ri) { - case NormalInstrument: - case BassDrum: - // Compare the full register set (both operators plus the connection - // byte) for normal instruments (which occupy a whole channel) and the - // one rhythm mode instrument which also occupies a whole channel. - return - difference(a.eRhythmInstrument, ri, 4) + - difference(a.reg20[0], b.reg20[0], 2) + - difference(a.reg20[1], b.reg20[1], 2) + - difference(a.reg40[0], b.reg40[0], 1) + - difference(a.reg40[1], b.reg40[1], 1) + - difference(a.reg60[0], b.reg60[0], 2) + - difference(a.reg60[1], b.reg60[1], 2) + - difference(a.reg80[0], b.reg80[0], 2) + - difference(a.reg80[1], b.reg80[1], 2) + - difference(a.regC0, b.regC0, 3) + - difference(a.regE0[0], b.regE0[0], 1) + - difference(a.regE0[1], b.regE0[1], 1); - case TomTom: - case HiHat: - // These two rhythm instruments only use one operator's settings, the - // settings of the other operator (should) be ignored. There is also - // only one Connection byte, but there is no documentation to say whether - // this applies to these modulator-only rhythm instruments or to the - // other carrier-only ones (below.) I'm guessing and putting it here. - return - difference(a.eRhythmInstrument, ri, 4) + - difference(a.reg20[0], b.reg20[0], 2) + - difference(a.reg40[0], b.reg40[0], 1) + - difference(a.reg60[0], b.reg60[0], 2) + - difference(a.reg80[0], b.reg80[0], 2) + - difference(a.regC0, b.regC0, 3) + - difference(a.regE0[0], b.regE0[0], 1); - case SnareDrum: - case TopCymbal: - // These instruments only use one operator - but the other one compared - // to the previous set above. They also don't use the single Connection - // byte (I think.) - return - difference(a.eRhythmInstrument, ri, 4) + - difference(a.reg20[1], b.reg20[1], 2) + - difference(a.reg40[1], b.reg40[1], 1) + - difference(a.reg60[1], b.reg60[1], 2) + - difference(a.reg80[1], b.reg80[1], 2) + - difference(a.regE0[1], b.regE0[1], 1); - } -} - -void writesbi(const char* filename, int instrno, int chanOPL) { - char fname[100]; - char title[32]; - snprintf(fname, 100, "%s_%03d.sbi", filename, instrno); - FILE* f_sbi = fopen(fname, WRITE_BINARY); - if (!f_sbi) { - fprintf(stderr, "Could not open instrument file %s for writing.\n", filename); - } else { - fwrite("SBI\x1a", sizeof(char), 4, f_sbi); - memset(title, 0, 32); - snprintf(title, 32, "dro2midi_%03d", instrno); - fwrite(title, sizeof(char), 32, f_sbi); - unsigned char instr[16]; - memset(instr, 0, 16); - instr[0] = (unsigned char)reg[chanOPL].reg20[0]; - instr[1] = (unsigned char)reg[chanOPL].reg20[1]; - instr[2] = (unsigned char)reg[chanOPL].reg40[0]; - instr[3] = (unsigned char)reg[chanOPL].reg40[1]; - instr[4] = (unsigned char)reg[chanOPL].reg60[0]; - instr[5] = (unsigned char)reg[chanOPL].reg60[1]; - instr[6] = (unsigned char)reg[chanOPL].reg80[0]; - instr[7] = (unsigned char)reg[chanOPL].reg80[1]; - instr[8] = (unsigned char)reg[chanOPL].regE0[0]; - instr[9] = (unsigned char)reg[chanOPL].regE0[1]; - instr[10] = (unsigned char)reg[chanOPL].regC0; - fwrite(instr, sizeof(char), 16, f_sbi); - fclose(f_sbi); - } -} - -int findinstr(int chanMIDI) -{ - assert((chanMIDI < 9) || ((chanMIDI >= CHAN_BASSDRUM) && (chanMIDI <= CHAN_HIHAT))); - - RHYTHM_INSTRUMENT ri; - int chanOPL; - switch (chanMIDI) { - case CHAN_BASSDRUM: ri = BassDrum; chanOPL = 6; break; - case CHAN_SNAREDRUM: ri = SnareDrum; chanOPL = 7; break; - case CHAN_TOMTOM: ri = TomTom; chanOPL = 8; break; - case CHAN_TOPCYMBAL: ri = TopCymbal; chanOPL = 8; break; - case CHAN_HIHAT: ri = HiHat; chanOPL = 7; break; - default: ri = NormalInstrument; chanOPL = chanMIDI; break; - } - - int besti = -1; - long bestdiff = -1; - for (int i = 0; i < instrcnt; i++) { - long diff = compareinstr(instr[i], reg[chanOPL], ri); - if (besti < 0 || diff < bestdiff) { - bestdiff = diff; - besti = i; - if (bestdiff == 0) break; - } - } - - if (besti >= 0) { // could be -1 if no instruments are loaded - while (instr[besti].redirect >= 0) { // Could have multiple redirects - // This instrument was an automatically generated one to avoid printing - // the instrument definition multiple times, so instead of using the auto - // one, use the one it originally matched against. - besti = instr[besti].redirect; - } - } - - if (bestdiff != 0) { - if (::bPerfectMatchesOnly) { - // User doesn't want "close enough is good enough" instrument guessing - besti = 0; // use first instrument - } - // Couldn't find an exact match, print the details - switch (ri) { - case NormalInstrument: - case BassDrum: - // Normal instrument or rhythm Bass Drum use both - // operators + connection byte - printf("** New instrument in use on channel %d\n** Copy this into " - MAPPING_FILE " to assign it a MIDI patch:\n", chanOPL); - fprintf(stderr, "%s %02X-%02X/%02X-%02X/%02X-%02X/%02X-%02X/%02X/" - "%02X-%02X: patch=?\n", - ((ri == BassDrum) ? "BD" : "NO"), - reg[chanOPL].reg20[0], reg[chanOPL].reg20[1], - reg[chanOPL].reg40[0], reg[chanOPL].reg40[1], - reg[chanOPL].reg60[0], reg[chanOPL].reg60[1], - reg[chanOPL].reg80[0], reg[chanOPL].reg80[1], - reg[chanOPL].regC0, - reg[chanOPL].regE0[0], reg[chanOPL].regE0[1] - ); - if (::bWriteSbiInstruments) { - writesbi(output, instrcnt, chanOPL); - } - break; - case TomTom: - case HiHat: - // This instrument is one operator only, but it does use the connection - // byte (probably) - printf("** New rhythm instrument in use on OPL channel %d modulator\n" - "** Copy this into " MAPPING_FILE " to assign it a MIDI patch:\n", - chanOPL); - fprintf(stderr, "%s %02X/%02X/%02X/%02X/%02X/%02X: " - "patch=?\n", - ((ri == TomTom) ? "TT" : "HH"), - reg[chanOPL].reg20[0], - reg[chanOPL].reg40[0], - reg[chanOPL].reg60[0], - reg[chanOPL].reg80[0], - reg[chanOPL].regC0, - reg[chanOPL].regE0[0] - ); - break; - case SnareDrum: - case TopCymbal: - // This instrument does not uses the connection byte, so read in one - // byte less. Also read the values into the other operator. - // This instrument is one operator only, but it does use the connection - // byte (probably) - printf("** New rhythm instrument in use on OPL channel %d carrier\n" - "** Copy this into " MAPPING_FILE " to assign it a MIDI patch:\n", - chanOPL); - fprintf(stderr, "%s %02X/%02X/%02X/%02X/%02X: " - "patch=?\n", - ((ri == SnareDrum) ? "SD" : "TC"), - reg[chanOPL].reg20[1], - reg[chanOPL].reg40[1], - reg[chanOPL].reg60[1], - reg[chanOPL].reg80[1], - reg[chanOPL].regE0[1] - ); - break; - } - - printf(">> Using similar match: %s\n", instr[besti].name); - // Save this unknown instrument as a known one, so the same registers don't get printed again -// reg[channel].prog = instr[besti].prog; // but keep the same patch that we've already assigned to the instrument, so it doesn't drop back to a piano for the rest of the song - // Maybe ^ isn't necessary if we're redirecting? - instr[instrcnt] = reg[chanOPL]; - instr[instrcnt].eRhythmInstrument = ri; - if (besti >= 0) { - instr[instrcnt].redirect = besti; // Next time this instrument is matched, use the original one instead - } else { - instr[instrcnt].redirect = -1; // Will only happen when no instruments are loaded - } - instrcnt++; - } - return besti; -} - -// Function for processing OPL note on and off events, and generating MIDI -// events in response. This function is also called when the pitch changes -// while a note is currently being played, causing it to generate MIDI -// pitchbends (if enabled) instead. -// Normally chanOPL and chanMIDI will be the same, except for rhythm mode -// instruments, which uses chanOPL for pitch and instrument patches, but -// chanMIDI for instrument mapping and note on/off events (since there will be -// two instrument maps and notes for a single OPL channel.) -void doNoteOnOff(bool bKeyOn, int chanOPL, int chanMIDI) -{ - double keyFrac = freq2key(curfreq[chanOPL], reg[chanOPL].iOctave); - int key = (int)round(keyFrac); - if ((key > 0) && (bKeyOn)) { - // This is set to true to forcibly stop a MIDI keyon being generated for - // this note. This is done when a pitchbend is deemed as having done the - // job properly. - bool bKeyonAgain = true; - - if (keyAlreadyOn[chanMIDI]) { - // There's already a note playing on this channel, just worry about the pitch of that - - if (mapchannel[chanMIDI] != gm_drumchannel) { - // We're using a normal instrument here - - if (::bUsePitchBends) { - // It's the same note, but the pitch is off just slightly, use a pitchbend - //double dbDiff = fabs(keyFrac - key); // should be between -0.9999 and 0.9999 - double dbDiff = keyFrac - (double)(lastkey[chanMIDI] - transpose[chanMIDI]); // hopefully between -PITCHBEND_RANGE and PITCHBEND_RANGE - - if (dbDiff > PITCHBEND_RANGE) { - fprintf(stderr, "Warning: This song wanted to pitchbend by %.2f notes, but the maximum is %.1f\n", dbDiff, PITCHBEND_RANGE); - - // Turn this note off - write->noteoff(mapchannel[chanMIDI], lastkey[chanMIDI]); - ::iNotesActive--; - lastkey[chanMIDI] = -1; - keyAlreadyOn[chanMIDI] = false; - // leave bKeyonAgain as true, so that a noteon will be played instead - } else { - int iNewBend = (int)(pitchbend_center + (PITCHBEND_ONESEMITONE * dbDiff)); - if (iNewBend != pitchbent[chanMIDI]) { - //printf("pitchbend to %d/%.2lf (center + %d) (%.2lf " - // "semitones)\n", iNewBend, (double)pitchbend_center*2, - // (int)(iNewBend - pitchbend_center), (double)dbDiff); - write->pitchbend(mapchannel[chanMIDI], iNewBend); -// ::iPitchbendCount++; - pitchbent[chanMIDI] = iNewBend; - } - // This pitchbend has done the job, don't play a noteon - bKeyonAgain = false; - } - } else { - // We're not using pitchbends, so just switch off the note if it's different (the next one will play below) - if ((::bApproximatePitchbends) && (key != (lastkey[chanMIDI] - transpose[chanMIDI]))) { - write->noteoff(mapchannel[chanMIDI], lastkey[chanMIDI]); - ::iNotesActive--; - lastkey[chanMIDI] = -1; - keyAlreadyOn[chanMIDI] = false; - //bKeyonAgain = true; - } else { - // Same note, different pitch, just pretend like it's not there - bKeyonAgain = false; - } - } - } else { - // This has mapped to a percussive MIDI note, so no pitchbends (or - // we'll bend all the percussive notes on the MIDI percussion channel.) - // But we don't want to play the note again, 'cos it's already on, so - // just ignore the keyon event. - // We'll also end up here if the instrument parameters are changed - // while the instrument is sounding, e.g. to change the characteristics - // of a hihat without sounding a new note. This won't be converted. - bKeyonAgain = false; - } - } // else this is a percussive instrument - - //} else { - //if ((!bDontKeyonAgain) && ((!keyAlreadyOn[channel]) || (::bUsePitchBends))) { // If *now* there's no note playing... (or we're using pitchbends, i.e. a portamento has been set up) - if (bKeyonAgain) { // If *now* there's no note playing... (or we're using pitchbends, i.e. a portamento has been set up) - // See if we need to update anything - - // See if the instrument needs to change - int i = findinstr(chanMIDI); - if ( - (i >= 0) && ( - (instr[i].prog != lastprog[chanMIDI]) || - ( - (instr[i].isdrum) && - (drumnote[chanMIDI] != instr[i].note) - ) || ( - // Same instrument mapping, but different mute setting? - (instr[i].muted != mute[chanMIDI]) - ) - ) - ) { - printf("// Ch%02d <- %s\n", chanMIDI, instr[i].name); - if (!instr[i].isdrum) { - // Normal instrument (not MIDI percussion) - assert(instr[i].prog >= 0); - - if (mapchannel[chanMIDI] == gm_drumchannel) { - // This was playing drums, now we're back to normal notes - - // make sure this sets things back to what they were in the init - // section in main() - mapchannel[chanMIDI] = chanMIDI; - drumnote[chanMIDI] = -1; // NOTE: This drumnote won't be reset if the drum instrument was muted! (As it then wouldn't have been assigned to gm_drumchannel) - } - - transpose[chanMIDI] = instr[i].iTranspose; - write->program(mapchannel[chanMIDI], lastprog[chanMIDI] = instr[i].prog); - } else { - // This new instrument is a drum - assert(instr[i].prog == -1); - - /*if (instr[i].muted) { - // This instrument is muted, which means whichever channel we - // assign it to will become muted. We can't therefore assign it to - // the drum channel as we normally would, otherwise all the MIDI - // percussion will become muted. So we assign it to its normal - // channel instead, like we would with a non-percussion instrument. - mapchannel[chanMIDI] = chanMIDI; - } else { - mapchannel[chanMIDI] = gm_drumchannel; - }*/ - mapchannel[chanMIDI] = gm_drumchannel; - drumnote[chanMIDI] = instr[i].note; - lastprog[chanMIDI] = instr[i].prog; - // drums don't use transpose values - } - mute[chanMIDI] = instr[i].muted; - } - - // Play the note - //if ((::bUsePitchBends) && (!keyAlreadyOn[channel])) { - if ((::bUsePitchBends) && (mapchannel[chanMIDI] != gm_drumchannel)) { // If pitchbends are enabled and this isn't a percussion instrument - double dbDiff = keyFrac - key; // should be between -0.9999 and 0.9999 - assert(dbDiff < PITCHBEND_RANGE); // not really necessary... - - int iNewBend = (int)(pitchbend_center + (PITCHBEND_ONESEMITONE * dbDiff)); - if (iNewBend != pitchbent[chanMIDI]) { - //printf("new note at pitchbend %d\n", iNewBend); - write->pitchbend(mapchannel[chanMIDI], iNewBend); // pitchbends are between 0x0000L and 0x2000L -// ::iPitchbendCount++; - pitchbent[chanMIDI] = iNewBend; - } - } - - int level; - if (!mute[chanMIDI]) { - if (::bEnableVolume) { - level = reg[chanOPL].reg40[1] & 0x3f; - if (level > 0x30) level = 0x30; // don't allow fully silent notes - } else level = 0; // 0 == loudest - } else { - level = 0x3f; // silent - } - - if (mapchannel[chanMIDI] != gm_drumchannel) { - // Normal note - lastkey[chanMIDI] = key + transpose[chanMIDI]; - } else { - // Percussion - //write->noteon(gm_drumchannel, drumnote[chanMIDI], (0x3f - level) << 1); - lastkey[chanMIDI] = drumnote[chanMIDI]; - } - - write->noteon(mapchannel[chanMIDI], lastkey[chanMIDI], (0x3f - level) << 1); - //printf("note on chan %d, mute is %s\n", chanMIDI, mute[chanMIDI] ? "true" : "false"); - ::iNotesActive++; - ::iTotalNotes++; - - // If this note went on with a pitchbend active on the channel, count it - if (pitchbent[chanMIDI] != pitchbend_center) ::iPitchbendCount++; - - keyAlreadyOn[chanMIDI] = true; - - } // if (not muted) - - } else { - // There's no note currently playing on this channel, so if we've still got - // one switch it off. - if (lastkey[chanMIDI] != -1) { - write->noteoff(mapchannel[chanMIDI], lastkey[chanMIDI]); - ::iNotesActive--; - lastkey[chanMIDI] = -1; - keyAlreadyOn[chanMIDI] = false; - } - } - - return; -} - -int main(int argc, char**argv) -{ - int c; - - // Defaults - ::dbConversionVal = 49716.0; - - argc--; argv++; - while (argc > 0 && **argv == '-') - { - if (strncasecmp(*argv, "-r", 2) == 0) { - ::bRhythm = false; - printf("Rhythm-mode instruments disabled.\n"); - } else if (strncasecmp(*argv, "-p", 2) == 0) { - ::bUsePitchBends = false; - printf("Pitchbends disabled.\n"); - } else if (strncasecmp(*argv, "-a", 2) == 0) { - ::bApproximatePitchbends = true; - } else if (strncasecmp(*argv, "-i", 2) == 0) { - ::bPerfectMatchesOnly = true; - printf("Only using exact instrument matches - approximations disabled!\n"); - } else if (strncasecmp(*argv, "-v", 2) == 0) { - ::bEnableVolume = false; - printf("Note velocity disabled, all notes will be played as loud as possible.\n"); - } else if (strncasecmp(*argv, "-s", 2) == 0) { - ::bWriteSbiInstruments = true; - } else if (strncasecmp(*argv, "-c", 2) == 0) { - argc--; argv++; - if (argc == 0) { - fprintf(stderr, "-c requires a parameter\n"); - usage(); - } - if (strncasecmp(*argv, "alt", 3) == 0) { - ::dbConversionVal = 50000.0; - } else { - // Use the given value - ::dbConversionVal = strtod(*argv, NULL); - if (::dbConversionVal == 0) { - fprintf(stderr, "-c requires a non-zero parameter\n"); - usage(); - } - } - } else if (strncasecmp(*argv, "--version", 9) == 0) { - version(); - return 0; - } else { - fprintf(stderr, "invalid option %s\n", *argv); - usage(); - } - argc--; argv++; - } - if (argc < 2) usage(); - - if ((::bUsePitchBends) && (::bApproximatePitchbends)) { - fprintf(stderr, "ERROR: Pitchbends can only be approximated (-a) if " - "proper MIDI pitchbends are disabled (-p)\n"); - return 1; - } - - input = argv[0]; - output = argv[1]; - if (strcmp(input, output) == 0) - { - fprintf(stderr, "cannot convert to same file\n"); - return 1; - } - - if (!loadInstruments()) return 1; - - - f = fopen(input, READ_BINARY); - if (!f) - { - perror(input); - return 1; - } - unsigned long imflen = 0; - - unsigned char cSig[9]; - fseek(f, 0, SEEK_SET); - //fgets(cSig, 9, f); - fread(cSig, 1, 9, f); - iSpeed = 0; - if (strncmp((char *)cSig, "DBRAWOPL", 8) == 0) { - ::iFormat = FORMAT_DRO; - fseek(f, 8, SEEK_SET); // Seek to "version" fields. - unsigned long version = readUINT32LE(f); - if (version == 0x10000) { - printf("Input file is in DOSBox DRO v1.0 format.\n"); - } else if (version == 0x2) { - printf("Input file is in DOSBox DRO v2.0 format, which is not supported.\n"); - return 2; - } else { - printf("Input file is in DOSBox DRO format, but an unknown version!\n"); - return 3; - } - ::iInitialSpeed = 1000; - - fseek(f, 16, SEEK_SET); // seek to "length in bytes" field - imflen = readUINT32LE(f); - } else if (strncmp((char *)cSig, "RAWADATA", 8) == 0) { - ::iFormat = FORMAT_RAW; - printf("Input file is in Rdos RAW format.\n"); - - // Read until EOF (0xFFFF is really the end but we'll check that during conversion) - fseek(f, 0, SEEK_END); - imflen = ftell(f); - - fseek(f, 8, SEEK_SET); // seek to "initial clock speed" field - ::iInitialSpeed = 1000; - int iClockSpeed = readUINT16LE(f); - if ((iClockSpeed == 0) || (iClockSpeed == 0xFFFF)) { - ::iSpeed = (int)18.2; // default to 18.2Hz...well, 18Hz thanks to rounding - } else { - ::iSpeed = (int)(1193180.0 / iClockSpeed); - } - } else { - ::iFormat = FORMAT_IMF; - if ((cSig[0] == 0) && (cSig[1] == 0)) { - printf("Input file appears to be in IMF type-0 format.\n"); - fseek(f, 0, SEEK_END); - imflen = ftell(f); - fseek(f, 0, SEEK_SET); - } else { - printf("Input file appears to be in IMF type-1 format.\n"); - imflen = cSig[0] + (cSig[1] << 8); - fseek(f, 2, SEEK_SET); // seek to start of actual OPL data - } - if (strcasecmp(&input[strlen(input)-3], "imf") == 0) { - printf("File extension is .imf - using 560Hz speed (rename to .wlf if " - "this is too slow)\n"); - ::iInitialSpeed = 560; - } else if (strcasecmp(&input[strlen(input)-3], "wlf") == 0) { - printf("File extension is .wlf - using 700Hz speed (rename to .imf if " - "this is too fast)\n"); - ::iInitialSpeed = 700; - } else { - printf("Unknown file extension - must be .imf or .wlf\n"); - return 3; - } - } - printf("Using conversion constant of %.1lf\n", ::dbConversionVal); - - write = new MidiWrite(output); - if (!write) { - fprintf(stderr, "out of memory\n"); - return 1; - } - if (!write->getf()) { - perror(output); - return 1; - } - if (iSpeed == 0) { - iSpeed = iInitialSpeed; - } - resolution = iInitialSpeed / 2; - write->head(/* version */ 0, /* track count updated later */0, resolution); - - write->track(); - write->tempo((long)(60000000.0 / tempo)); - write->tact(4,4,24,8); - - for (c = 0; c < 10; c++) { - mapchannel[c] = c; - write->resetctrlrs(mapchannel[c], 0); // Reset All Controllers (Ensures default settings upon every playback). - write->volume(mapchannel[c], 127); - write->balance(mapchannel[c], 64); // Usually called 'Pan'. - write->expression(mapchannel[c], 127); // Similar to 'Volume', but this is primarily used for volume damping. - } - - for (c = 0; c <= 8; c++) { - lastprog[c] = -1; - reg[c].iOctave = 0; - } - - int delay = 0; - int channel; - int code, param; - - for (c = 0; c < 9; c++) { - curfreq[c] = 0; - mapchannel[c] = c; // This can get reset when playing a drum and then a normal instrument on a channel - see instrument-change code below - keyAlreadyOn[c] = false; - lastkey[c] = -1; // last MIDI key pressed on this channel - pitchbent[c] = (int)pitchbend_center; - transpose[c] = 0; - drumnote[c] = 0; // probably not necessary... - mute[c] = false; - - if (::bUsePitchBends) { - write->control(mapchannel[c], 100, 0); // RPN LSB for "Pitch Bend Sensitivity" - write->control(mapchannel[c], 101, 0); // RPN MSB for "Pitch Bend Sensitivity" - write->control(mapchannel[c], 6, (int)PITCHBEND_RANGE); // Data for Pitch Bend Sensitivity (in semitones) - controller 38 can be used for cents in addition - write->control(mapchannel[c], 100, 0x7F); // RPN LSB for "Finished" - write->control(mapchannel[c], 101, 0x7F); // RPN MSB for "Finished" - } -// write->pitchbend(mapchannel[c], pitchbend_center); - } - // Rhythm-mode only channels - for (c = 10; c < 15; c++) { - keyAlreadyOn[c] = false; - mapchannel[c] = c; // as above, this will be changed later but must be - // eventually set back to this value here (or the instrument will jump - // channels unexpectedly.) - pitchbent[c] = (int)pitchbend_center; - lastkey[c] = -1; // last MIDI key pressed on this channel - transpose[c] = 0; - drumnote[c] = 0; // probably not necessary... - mute[c] = false; - } - - int iMinLen; // Minimum length for valid notes to still be present - switch (::iFormat) { - case FORMAT_IMF: iMinLen = 4; break; - case FORMAT_DRO: iMinLen = 2; break; - case FORMAT_RAW: iMinLen = 2; break; - } - - unsigned long iSize = imflen; // sometimes the counter wraps around, need this to stop it from happening - while ((imflen >= (unsigned long)iMinLen) && (imflen <= iSize)) { - - // Get the next OPL register and value from the input file - - switch (::iFormat) { - case FORMAT_IMF: - // Write the last iteration's delay (since the delay needs to come *after* the note) - write->time(delay); - - code = readByte(f); - param = readByte(f); - delay = readUINT16LE(f); - imflen -= 4; - break; - case FORMAT_DRO: - code = readByte(f); - imflen--; - switch (code) { - case 0x00: // delay (byte) - delay += 1 + readByte(f); - imflen--; - continue; - case 0x01: // delay (int) - delay += 1 + readUINT16LE(f); - imflen -= 2; - continue; - case 0x02: // use first OPL chip - case 0x03: // use second OPL chip - fprintf(stderr, "Warning: This song uses multiple OPL chips - this isn't yet supported!\n"); - continue; - case 0x04: // escape - code = readByte(f); - imflen--; - break; - } - param = readByte(f); - imflen--; - - // Write any delay (as this needs to come *before* the next note) - write->time(delay); - delay = 0; - break; - case FORMAT_RAW: - param = readByte(f); - code = readByte(f); - imflen -= 2; - switch (code) { - case 0x00: // delay - delay += param; - continue; - case 0x02: // control data - switch (param) { - case 0x00: { - if (delay != 0) { - // See below - we need to write out any delay at the old clock speed before we change it - write->time((delay * iInitialSpeed / ::iSpeed)); - delay = 0; - } - int iClockSpeed = readUINT16LE(f); - if ((iClockSpeed == 0) || (iClockSpeed == 0xFFFF)) { - printf("Speed set to invalid value, ignoring speed change.\n"); - } else { - ::iSpeed = (int)round(1193180.0 / iClockSpeed); - printf("Speed changed to %dHz\n", iSpeed); - } - imflen -= 2; - break; - } - case 0x01: - case 0x02: - printf("Switching OPL ports is not yet implemented!\n"); - break; - } - continue; - case 0xFF: - if (param == 0xFF) { - // End of song - imflen = 0; - continue; - } - break; - } - - // Write any delay (as this needs to come *before* the next note) - // Since our global clock speed is 1000Hz, we have to multiply this - // delay accordingly as the delay units are in the current clock speed. - // This calculation converts them into 1000Hz delay units regardless of - // the current clock speed. - if (delay != 0) write->time((delay * iInitialSpeed / ::iSpeed)); - //printf("delay is %d (ticks %d)\n", (delay * iInitialSpeed / ::iSpeed), delay); - delay = 0; - break; - - default: // should never happen - break; - - } // switch (::iFormat) - - // Convert the OPL register and value into a MIDI event - - if (code >= 0xa0 && code <= 0xa8) { // set freq bits 0-7 - channel = code-0xa0; - curfreq[channel] = (curfreq[channel] & 0xF00) + (param & 0xff); - if (keyAlreadyOn[channel]) { - param = 0x20; // bare noteon for code below - doNoteOnOff(true, channel, channel); - } - continue; - } else if (code >= 0xB0 && code <= 0xB8) { // set freq bits 8-9 and octave and on/off - channel = code - 0xb0; - curfreq[channel] = (curfreq[channel] & 0x0FF) + ((param & 0x03)<<8); - // save octave so we know what it is if we run 0xA0-0xA8 regs change code - // next (which doesn't have the octave) - reg[channel].iOctave = (param >> 2) & 7; - int keyon = (param >> 5) & 1; - doNoteOnOff(keyon, channel, channel); - } else if ((code == 0xBD) && (::bRhythm)) { - if ((param >> 5) & 1) { - // Bass Drum - doNoteOnOff((param >> 4) & 1, channel, CHAN_BASSDRUM); - doNoteOnOff((param >> 3) & 1, channel, CHAN_SNAREDRUM); - doNoteOnOff((param >> 2) & 1, channel, CHAN_TOMTOM); - doNoteOnOff((param >> 1) & 1, channel, CHAN_TOPCYMBAL); - doNoteOnOff( param & 1, channel, CHAN_HIHAT); - } - } else if (code >= 0x20 && code <= 0x35) { - channel = GET_CHANNEL(code-0x20); - reg[channel].reg20[GET_OP(code-0x20)] = param; - } else if (code >= 0x40 && code <= 0x55) { - channel = GET_CHANNEL(code-0x40); - reg[channel].reg40[GET_OP(code-0x40)] = param; - } else if (code >= 0x60 && code <= 0x75) { - channel = GET_CHANNEL(code-0x60); - reg[channel].reg60[GET_OP(code-0x60)] = param; - } else if (code >= 0x80 && code <= 0x95) { - channel = GET_CHANNEL(code-0x80); - reg[channel].reg80[GET_OP(code-0x80)] = param; - } else if (code >= 0xc0 && code <= 0xc8) { - channel = code-0xc0; - reg[channel].regC0 = param; - } else if (code >= 0xe0 && code <= 0xF5) { - channel = GET_CHANNEL(code-0xe0); - reg[channel].regE0[GET_OP(code-0xe0)] = param; - } - } - - for (c = 0; c < 10; c++) { - mapchannel[c] = c; - write->allnotesoff(mapchannel[c], 0); // All Notes Off (Ensures that even incomplete Notes will be switched-off per each MIDI channel at the end-of-playback). - } - - delete write; - fclose(f); - - // Display completion message and some stats - printf("\nConversion complete. Wrote %s\n\n Total pitchbent notes: %d\n" - " Total notes: %d\n Notes still active at end of song: %d\n\n", - output, ::iPitchbendCount, ::iTotalNotes, ::iNotesActive); - - return 0; -} diff --git a/dro2midi/gen_test_midi.cpp b/dro2midi/gen_test_midi.cpp deleted file mode 100644 index 99eb97d..0000000 --- a/dro2midi/gen_test_midi.cpp +++ /dev/null @@ -1,74 +0,0 @@ -// -// gen_test_midi.cpp - by malvineous@shikadi.net -// -// Generate a MIDI file that plays one note from all 128 GM instruments (in -// order), followed by all the percussion notes. -// -// When this file is played through an OPL synthesiser capable of MIDI -// playback, then captured through DOSBox, it will provide a complete set of -// OPL patches that can easily be associated back to MIDI instruments. -// - -#include "midiio.hpp" -#include -#include -#include -#include -#include -#include - -#define WRITE_BINARY "wb" -#define READ_TEXT "r" - -int main(int argc, char**argv) -{ - if (argc != 2) { - fprintf(stderr, "no output filename given\n"); - return 1; - } - - MidiWrite *write = new MidiWrite(argv[1]); - if (!write) { - fprintf(stderr, "out of memory\n"); - return 1; - } - if (!write->getf()) { - perror(argv[1]); - return 1; - } - - float tempo = 120.0; - int resolution = 280; - write->head(/* version */ 0, /* track count updated later */0, resolution); - - write->track(); - write->tempo((long)(60000000.0 / tempo)); - write->tact(4,4,24,8); - - write->volume(1, 127); - write->volume(9, 127); - for (int i = 0; i < 128; i++) { - write->program(1, i); - write->noteon(1, 60, 127); - write->time(50); - write->noteoff(1, 60); - write->time(50); - } -/* - // Need to play a normal note for percussion to get DOSBox to start capturing - // OPL data! (Why hasn't my patch to fix that been incorporated yet?!) - write->noteon(1, 60, 127); - write->time(50); - write->noteoff(1, 60); - write->time(200); - for (int i = 35; i < 82; i++) { - write->noteon(9, i, 127); - write->time(50); - write->noteoff(9, i); - write->time(50); - }*/ - - delete write; - - return 0; -} diff --git a/dro2midi/make b/dro2midi/make deleted file mode 100644 index a452b9f..0000000 --- a/dro2midi/make +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/sh - -# Usage: -# -# ./make -# -# or if you have a cross compiler for i586-pc-mingw32 installed -# -# ./make i586-pc-mingw32 .exe -# - -if [ "$1" != "" ]; then - PLATFORM="$1"- - TARGET="dro2midi$2" -else - PLATFORM="" - TARGET="dro2midi" -fi - -${PLATFORM}g++ -o ${TARGET} dro2midi.cpp midiio.cpp && - ${PLATFORM}strip ${TARGET} diff --git a/dro2midi/midiio.cpp b/dro2midi/midiio.cpp deleted file mode 100644 index 618e8ce..0000000 --- a/dro2midi/midiio.cpp +++ /dev/null @@ -1,2161 +0,0 @@ -// midiio.cpp written by Günter Nagler 1995 (gnagler@ihm.tu-graz.ac.at) -#include "midiio.hpp" -#include -#ifdef __MSDOS__ -#include -#endif -#include -#include - -static const char* copyright = "midiio v1.4 (c) 1995 by Günter Nagler (" __DATE__ ")"; - -int compress = 1; - -#define NOTREALISTIC_PAUSE 0x1000000UL - -// common sysex events -unsigned char sysex_gmreset[] = { 0xF0, 0x05, 0x7E, 0x7F, 0x09, 0x01, 0xF7 }; -unsigned char sysex_gsreset[] = { 0xF0, 0x0A, 0x41, 0x10, 0x42, 0x12, 0x40, 0x00, 0x7F, 0x00, 0x41, 0xF7 }; -unsigned char sysex_gsexit[] = { 0xF0, 0x0A, 0x41, 0x10, 0x42, 0x12, 0x40, 0x00, 0x7F, 0x7F, 0x42, 0xF7 }; -unsigned char sysex_xgreset[] = { 0xF0, 0x08, 0x43, 0x10, 0x4C, 0x00, 0x00, 0x7E, 0x00, 0xF7 }; - -static int issysex(const unsigned char* sysex, const unsigned char* sysdata, int syslen) -{ - if (*sysex == 0xF0) - sysex++; - while (syslen > 0) - { - if (*sysex != *sysdata) - return 0; - syslen--; - if (*sysex == 0xF7) - break; - sysex++; - sysdata++; - } - return (syslen != 0) ? 0 : 1; -} - -static int sysexlen(unsigned const char* sysex) -{ -int len = 0; - - len = 0; - while (*sysex != 0xF7) - { - sysex++; - len++; - } - return len+1; // incl. F7 -} - -// class MidiRead - -const char* MidiRead::copyright() -{ - return (const char*)::copyright; -} - -MidiRead::MidiRead(const char* filename, FILE* f) -{ - midiname_ = filename; - if (f) - { - f_ = f; - shouldclose_ = 0; - } - else - { - shouldclose_ = 1; - if (!filename) - f_ = 0; - else - f_ = fopen(filename, READ_BINARY); - } - - buflen_ = 0; - bufpos_ = 0; - curpos_ = 0; - pos_ = 0; - curchannel_ = NOCHANNEL; - curtime_ = 0; - options_ = 0; - if (f_) - { - fseek(f_, 0L, SEEK_END); - filesize_ = ftell(f_); - fseek(f_, 0L, SEEK_SET); - } - else - filesize_ = 0; - - version_ = tracks_ = clicks_ = trackno_ = 0; - tracklen_ = 0; -} - - -MidiRead::~MidiRead() -{ - if (f_ && shouldclose_) - fclose(f_); -} - -int MidiRead::runhead() -{ - if (!f_) - { - error("file not open"); - return 0; - } - seek(0); - if (getlong() != MThd) - { - error("missing midi header MThd"); - return 0; - } - - if (getlong() == 6) - { - version_ = getword(); - tracks_ = getword(); - clicks_ = getword(); - head(version_, tracks_, clicks_); - } - else - { - error("illegal midi header"); - return 0; - } - return 1; -} - -int MidiRead::run() -{ - pos_ = curpos_; - if (!runhead()) - return 0; - pos_ = curpos_; - for (trackno_ = 1; trackno_ <= tracks_; trackno_++) - if (!runtrack(trackno_)) - return 0; - if (curpos_ >= filesize_) - percent(percent_ = 100); - endmidi(); - return 1; -} - -int MidiRead::runevent(long trackend) -{ -int midicode; - - pos_ = curpos_; - - unsigned char *c = need(1); - if (!c || c[0] >= 0x80 || lastcode_ < 0) - midicode = getbyte(); - else - midicode = lastcode_; - - if (midicode < 0) - return 0; - - switch(midicode) - { - case 0xf0: // sysex - { - int syslen = scansysevent(trackend - curpos_); - if (!syslen) - { - error("end of sysex not found or sysex too large"); - return 0; - } - unsigned char* sysdata = get(syslen); - if ((options_ & OPTION_NOSYSEVENTS) == 0) - { - if (issysex(sysex_gmreset, sysdata, syslen)) - gmreset(); - else if (issysex(sysex_gsreset, sysdata, syslen)) - gsreset(); - else if (issysex(sysex_gsexit, sysdata, syslen)) - gsexit(); - else if (issysex(sysex_xgreset, sysdata, syslen)) - xgreset(); - else - sysex(syslen, sysdata); - } - else if ((options_ & OPTION_NOREALTIMEEVENTS) == 0) - sysex(syslen, sysdata); - else - event(0xf0, syslen, sysdata); - } - break; - case 0xf2: - { - c = get(2); if (!c) return 0; - if ((options_ & OPTION_NOREALTIMEEVENTS) == 0) - songpos((unsigned(c[1]) << 7) + unsigned(c[0])); - else - event(0xf2, 2, c); - break; - } - case 0xf3: - c = get(1); if (!c) return 0; - if ((options_ & OPTION_NOREALTIMEEVENTS) == 0) - songselect(*c); - else - event(0xf3, 1, c); - break; - case 0xf6: - if ((options_ & OPTION_NOREALTIMEEVENTS) == 0) - tunerequest(); - else - event(0xf6); - break; - case 0xf8: - if ((options_ & OPTION_NOREALTIMEEVENTS) == 0) - timingclock(); - else - event(0xf8); - break; - case 0xfa: - if ((options_ & OPTION_NOREALTIMEEVENTS) == 0) - start(); - else - event(0xfa); - break; - case 0xfb: - if ((options_ & OPTION_NOREALTIMEEVENTS) == 0) - cont(); - else - event(0xfb); - break; - case 0xfc: - if ((options_ & OPTION_NOREALTIMEEVENTS) == 0) - stop(); - else - event(0xfc); - break; - case 0xfe: - if ((options_ & OPTION_NOREALTIMEEVENTS) == 0) - activesense(); - else - event(0xfe); - break; - case 0xff: - { - int c; - unsigned long endpos; - int len; - - c = getbyte(); - len = (int)getdelta(); - endpos = curpos_ + len; - if (options_ & OPTION_NOREALTIMEEVENTS) - { - len += curdeltalen_ + 1; - seek(curpos_ - curdeltalen_ - 1); - event(0xff, len, get(len)); - } - else if (options_ & OPTION_NOMETAEVENTS) - meta(c, len, get(len)); - else - switch(c) - { - case 0: - if (len == 2) - seqnumber(getword()); - else - meta(c, len, get(len)); - break; - case meta_text: - text(c, len, "text", get(len)); break; - case meta_copyright: - text(c, len, "copyright", get(len)); break; - case meta_trackname: - text(c, len, "trackname", get(len)); break; - case meta_instrument: - text(c, len, "instrument", get(len)); break; - case meta_lyric: - text(c, len, "lyric", get(len)); break; - case meta_marker: - text(c, len, "marker", get(len)); break; - case meta_cuepoint: - text(c, len, "cuepoint", get(len)); break; - case 8: - case 9: - case 10: - case 11: - case 12: - case 13: - case 14: - case 15: - text(c, len, 0, get(len)); break; - case 0x20: - if (len == 1) - prefixchannel(getbyte()); - else - meta(c, len, get(len)); - break; - case 0x21: - if (len == 1) - prefixport(getbyte()); - else - meta(c, len, get(len)); - break; - case 0x2F: - end(); - break; - case 0x51: - tempo(gettri()); - break; - case 0x54: - if (len == 5) - { - unsigned char *s = get(len); - - smpteofs(s[0], s[1], s[2], s[3], s[4]); - } - else - meta(c, len, get(len)); - break; - case 0x58: - if (len == 4) - { - int nom, clicksperbeat, notes32perbeat, log2denom; - - nom = getbyte(); - log2denom = getbyte(); - - clicksperbeat = getbyte(); - notes32perbeat = getbyte(); - tact(nom, 1 << log2denom, clicksperbeat, notes32perbeat); - } - else - meta(c, len, get(len)); - break; - case 0x59: - if (len == 2) - { - signed char s[2]; - - s[0] = (signed char)getbyte(); // sf - s[1] = (signed char)getbyte(); // mi - if (s[0] >= -7 && s[0] <= +7 && s[1] >= 0 && s[1] <= 1) - key(s[0], s[1]); - else - meta(c, len, (unsigned char*)s); - } - else - meta(c, len, get(len)); - break; - default: - meta(c, len, get(len)); - break; - } - seek(endpos); - } - break; - default: - { - if (midicode < 0) - return 0; - if (midicode < 0x80) - { - char msg[30]; - - sprintf(msg, "illegal midi command %02X", midicode); - error(msg); - return 0; - } - int channel = midicode & 0x0F; - int cmd = midicode & 0xF0; - - switch(cmd) - { - case 0x80: - case 0x90: - { - unsigned char* c; - - lastcode_ = midicode; - c = get(2); if (!c) return 0; - if (options_ & OPTION_NONOTEEVENTS) - event(midicode, 2, c); - else - { - if (cmd == 0x80 || c[1] == 0) - noteoff(channel, c[0], c[1]); - else - noteon(channel, c[0], c[1]); - } - } - break; - case 0xA0: - { - unsigned char* p = get(2); if (!p) return 0; - - lastcode_ = midicode; - - if (options_ & OPTION_NOPOLYEVENTS) - event(midicode, 2, p); - else - { - polyaftertouch(channel, p[0], p[1]); - } - } - break; - case 0xB0: - { - unsigned char* p = get(2); if (!p) return 0; - - lastcode_ = midicode; - if (options_ & OPTION_NOCONTROLEVENTS) - event(midicode, 2, p); - else if (options_ & OPTION_NOCONTROLS) - control(channel, p[0], p[1]); - else - switch(p[0]) - { - case ctrl_highbank: highbank(channel, p[1]); break; - case ctrl_wheel: wheel(channel, p[1]); break; - case ctrl_breath: breath(channel, p[1]); break; - case ctrl_foot: foot(channel, p[1]); break; - case ctrl_portamentotime: portamentotime(channel, p[1]); break; - case ctrl_data: data(channel, p[1]); break; - case ctrl_volume: volume(channel, p[1]); break; - case ctrl_balance: balance(channel, p[1]); break; - case ctrl_expression: expression(channel, p[1]); break; - case ctrl_lowbank: lowbank(channel, p[1]); break; - case ctrl_hold: hold(channel, p[1]); break; - case ctrl_reverb: reverb(channel, p[1]); break; - case ctrl_chorus: chorus(channel, p[1]); break; - case ctrl_datainc: datainc(channel, p[1]); break; - case ctrl_datadec: datadec(channel, p[1]); break; - case ctrl_lowrpn: lowrpn(channel, p[1]); break; - case ctrl_highrpn: - case ctrl_resetctrlrs: resetctrlrs(channel, p[1]); break; - case ctrl_allnotesoff: allnotesoff(channel, p[1]); break; - { - unsigned char *c = need(8); - - if (c && - c[0] == 0 && c[1] == midicode && c[2] == ctrl_lowrpn && c[3] == 0 && - c[4] == 0 && c[5] == midicode && c[6] == ctrl_data) - { - c = get(8); - pitchbendrange(channel, c[7]); - } - else - highrpn(channel, p[1]); - break; - } - default: - control(channel, p[0], p[1]); - break; - } - break; - } - - case 0xC0: - { - unsigned char* p = get(1); if (!p) return 0; - - lastcode_ = midicode; - if (options_ & OPTION_NOPROGRAMEVENTS) - event(midicode, 1, p); - else - program(channel, p[0]); - } - break; - case 0xD0: - { - unsigned char* p = get(1); if (!p) return 0; - - lastcode_ = midicode; - if (options_ & OPTION_NOAFTERTOUCHEVENTS) - event(midicode, 1, p); - else - aftertouch(channel, p[0]); - } - break; - case 0xE0: - { - unsigned char* p = get(2); if (!p) return 0; - unsigned val = unsigned(p[0]) + (unsigned(p[1]) << 7); - - lastcode_ = midicode; - if (options_ & OPTION_NOPITCHBENDEVENTS) - event(midicode, 2, p); - else - pitchbend(channel, val); - } - break; - default: - { - char msg[30]; - - sprintf(msg, "unexpected command byte %02X", midicode); - error(msg); - return 0; - } - } - } - break; - } - return (int)(curpos_ - pos_); -} - -int MidiRead::runtrack(int trackno) -{ -unsigned long trackpos = curpos_, trackend; - - curtime_ = 0; - lastcode_ = -1; - pos_ = curpos_; - if (!f_) - { - error("file not open"); - return 0; - } - - if (getlong() != MTrk) - { - error("missing midi track MTrk"); - return 0; - } - tracklen_ = getlong(); - track(trackno, tracklen_, curchannel_ = scanchannel(tracklen_)); - trackpos = curpos_; - trackend = trackpos + tracklen_; - lastcode_ = -1; - if ((options_ & OPTION_NOEVENTS) == 0) - while (curpos_ < trackpos + tracklen_) - { - int newpercent = (int)((curpos_ * 100) / filesize_); - if (newpercent != percent_) - percent(percent_ = newpercent); - - unsigned long delta = getdelta(); - if ( delta >= NOTREALISTIC_PAUSE ) - warning("Unrealistic large pause found"); - time(delta); - curtime_ += delta; - if (runevent(trackend) <= 0) - return 0; - } - seek(trackend); - pos_ = curpos_; - endtrack(trackno); - return 1; -} - -void MidiRead::setchannel(int channel) -{ - assert(channel >= -1 && channel <= 15); - curchannel_ = channel; -} - -int MidiRead::scansysevent(unsigned long maxlen) -{ -int n = sizeof(buf_); -unsigned char* c, *p; -long savepos = curpos_; -int len; - - if (maxlen < n) - n = (int)maxlen; - c = need(n); - if (!c) - { - n = buflen_; - c = need(n); - if (!c) - return 0; - } - - if (c[0] < 0x80) - { - // short sysex 0..127 bytes - len = c[0]; - if (n >= len+1) - { - if (c[len] == 0xF7) - return len+1; - } - } - else if (n >= 2 && c[1] < 0x80) - { - // 128..16383 bytes - int len = (int(c[0] & 0x7f) << 7) + c[1]; - if (n >= len + 2) - { - if (c[len+1] == 0xF7) - return len+2; - } - } - // sysex events without length information? - p = (unsigned char*)memchr(c, 0xF7, n); - if (p) - return (int)(p - c + 1); - seek(savepos); - return 0; -} - -int MidiRead::scanchannel(unsigned long maxlen) -{ -int n = 512; -unsigned char* c; -int firstchannel = NOCHANNEL; -int channel, code; -long savepos = curpos_, endpos; -int lastcode = -1; - - if (maxlen < n) - n = (int)maxlen; - c = need(n); - if (!c) - return -1; - - endpos = curpos_ + n; - while (curpos_ < endpos) - { - channel = NOCHANNEL; - getdelta(); - c = need(1); - if (!c || *c >= 0x80 || lastcode < 0) - code = getbyte(); - else - code = lastcode; - switch(code & 0xF0) - { - case 0xF0: - switch(code) - { - case 0xFF: - getbyte(); - get((int)getdelta()); - break; - case 0xf0: // sysex - { - int len = scansysevent(endpos-curpos_); - if (!get(len)) - goto endscan; - } - break; - case 0xf2: get(2); break; - case 0xf3: getbyte(); break; - case 0xf6: - case 0xf8: - case 0xfa: - case 0xfb: - case 0xfc: - case 0xfe: - break; - default: - goto endscan; - } - break; - case 0x80: - case 0x90: - case 0xA0: - case 0xB0: - case 0xE0: - channel = code & 15; - get(2); - break; - case 0xC0: - case 0xD0: - channel = code & 15; - getbyte(); - break; - default: - goto endscan; - } - if (code < 0xf0) - lastcode = code; - if (channel >= 0) - { - if (firstchannel < 0) - firstchannel = channel; - else if (channel != firstchannel) - { - firstchannel = MULTICHANNEL; - break; - } - } - } -endscan: - seek(savepos); - return firstchannel; -} - -int MidiRead::getbyte() -{ -unsigned char* c = get(1); - - if (c) - return *c; - return -1; -} - -unsigned MidiRead::getword() -{ -unsigned char* c = get(2); -unsigned n = 0; - - if (c) - { - n = *c++; - n = (n << 8) + *c++; - } - return n; -} - -unsigned long MidiRead::gettri() -{ -unsigned char* c = get(3); -unsigned long n = 0; - - if (c) - { - n = *c++; - n = (n << 8) + *c++; - n = (n << 8) + *c++; - } - return n; -} - -unsigned long MidiRead::getlong() -{ -unsigned char* c = get(4); -unsigned long n = 0; - - if (c) - { - n = *c++; - n = (n << 8) + *c++; - n = (n << 8) + *c++; - n = (n << 8) + *c++; - } - return n; -} - -unsigned long MidiRead::getdelta() -{ -unsigned long n = 0; -int i = 0, c; - - curdeltalen_ = 0; - for (i = 0; i < 4; i++) - { - c = getbyte(); - if (c < 0) - { - error("unexpected end of file"); - return 0; - } - curdeltalen_++; - n = (n << 7) + (c & 0x7f); - if ((c & 0x80) == 0) - break; - } - return n; -} - -unsigned char* MidiRead::need(int n) -{ - assert(n >= 0); - if (n == 0) - return 0; - if (n > buflen_) - { - if (!f_) - return 0; - if (n > sizeof(buf_)) - return 0; - if (n > sizeof(buf_) - bufpos_) - { - // move to beginning of buf - memmove(buf_, buf_ + bufpos_, buflen_); - bufpos_ = 0; - } - // add new data at end - if (sizeof(buf_) - bufpos_ - buflen_ > 0) - { - fseek(f_, curpos_+buflen_, SEEK_SET); - int l = fread(buf_+bufpos_+buflen_, 1, sizeof(buf_) - bufpos_ - buflen_, f_); - if (l > 0) - buflen_ += l; - else if (l == 0) - ; - } - } - if (n <= buflen_) - return buf_ + bufpos_; - return 0; -} - -unsigned char* MidiRead::get(int n) -{ -unsigned char* s; - - s = need(n); - if (s) - { - buflen_ -= n; - bufpos_ += n; - curpos_ += n; - } - else if (n > sizeof(buf_)) - warning("midi event larger than internal bufsize ignored"); - else if (n > 0) - { - error("unexpected end of file"); - exit(1); - } - return s; -} - -void MidiRead::seek(long pos) -{ - if (pos == curpos_ || pos < 0) - return; - if (pos >= curpos_ - bufpos_ && pos < curpos_ - bufpos_ + buflen_) - { - int n = (int)(pos - curpos_ + bufpos_); - - if (n < bufpos_) - { - buflen_ += bufpos_ - n; - bufpos_ -= bufpos_ - n; - } - else - { - buflen_ -= n - bufpos_; - bufpos_ += n - bufpos_; - } - curpos_ = pos; - } - else - { - fseek(f_, curpos_ = pos, SEEK_SET); - bufpos_ = buflen_ = 0; - } -} - -unsigned long MidiRead::microsec(unsigned long units, unsigned long msperbeat) -{ - assert(clicks_ != 0); // call runhead() or run() first! - - if (units > msperbeat) - return (units / clicks_) * msperbeat; - else - return units * (msperbeat / clicks_); -} - - -long MidiRead::units(unsigned long microsec, unsigned long msperbeat) -{ - assert(clicks_ != 0); // call runhead() or run() first! - assert(msperbeat > 0); // invalid tempo! - int clicks = clicks_; - while ((msperbeat & 1) == 0) - { - if ((clicks & 1) == 0) - clicks >>= 1; - else if ((microsec & 1) == 0) - microsec >>= 1; - else - break; - msperbeat >>= 1; - } - if (microsec >= 0x10000L) - return (microsec / msperbeat) * clicks; - else - return (microsec * clicks) / msperbeat; -} - - -static const char* GMProg[128] = -{ - "Piano", "BritePiano", "HammerPiano", "HonkeyTonk", "NewTines", "DigiPiano", "Harpsicord", "Clav", - "Celesta", "Glocken", "MusicBox", "Vibes", "Marimba", "Xylophon", "Tubular", "Santur", - "FullOrgan", "PercOrgan", "BX-3Organ", "ChurchPipe", "Positive", "Musette", "Harmonica", "Tango", - "ClassicGtr", "A.Guitar", "JazzGuitar", "CleanGtr", "MuteGuitar", "OverDrive", "DistGuitar", "RockMonics", - "JazzBass", "DeepBass", "PickBass", "FretLess", "SlapBass1", "SlapBass2", "SynthBass1", "SynthBass2", - "Violin", "Viola", "Cello", "ContraBass", "TremoloStr", "Pizzicato", "Harp", "Timpani", - "Marcato", "SlowString", "AnalogPad", "StringPad", "Choir", "DooVoice", "Voices", "OrchHit", - "Trumpet", "Trombone", "Tuba", "MutedTrumpet", "FrenchHorn", "Brass", "SynBrass1", "SynBrass2", - "SopranoSax", "AltoSax", "TenorSax", "BariSax", "SweetOboe", "EnglishHorn", "BasoonOboe", "Clarinet", - "Piccolo", "Flute", "Recorder", "PanFlute", "Bottle", "Shakuhachi","Whistle", "Ocarina", - "SquareWave", "SawWave", "SynCalinope", "SynChiff", "Charang", "AirChorus", "Rezzo4ths", "Bass&Lead", - "Fantasia", "WarmPad", "PolyPad", "GhostPad", "BowedGlas", "MetalPad", "HaloPad", "Sweep", - "IceRain", "SoundTrack", "Crystal", "Atmosphere", "Brightness", "Goblin", "EchoDrop", "StarTheme", - "Sitar", "Banjo", "Shamisen", "Koto", "Kalimba","Scotland","Fiddle", "Shanai", - "MetalBell", "Agogo", "SteelDrums", "Woodblock", "Taiko", "Tom", "SynthTom", "RevCymbal", - "FretNoise", "NoiseChiff", "Seashore", "Birds", "Telephone", "Helicopter", "Stadium!!", "GunShot" -}; - -const char* MidiRead::notename(unsigned char note) -{ -static char name[5]; -char* s = name; - - switch(note % 12) - { - case 0: *s++ = 'c'; break; - case 1: *s++ = 'c'; *s++ = '#'; break; - case 2: *s++ = 'd'; break; - case 3: *s++ = 'd'; *s++ = '#'; break; - case 4: *s++ = 'e'; break; - case 5: *s++ = 'f'; break; - case 6: *s++ = 'f'; *s++ = '#'; break; - case 7: *s++ = 'g'; break; - case 8: *s++ = 'g'; *s++ = '#'; break; - case 9: *s++ = 'a'; break; - case 10: *s++ = 'a'; *s++ = '#'; break; - case 11: *s++ = 'b'; break; // former 'h': German language only - } - - sprintf(s, "%d", (note / 12)-1); // octave (assuming Piano C4 is 60) - return (const char*) name; -} - - -const char* MidiRead::progname(int n, int channel) -{ -static char defname[10] = ""; - - if (channel == 9) // drum programs - { - switch(n) - { - case 0: return "Dr1"; - case 0x10: return "Dr2"; - case 0x19: return "Dr3"; - case 0x20: return "Dr4"; - case 0x28: return "Dr5"; - case 0x40: return "Dr6"; - case 0x18: return "Dr7"; - case 0x30: return "Dr8"; - } - } - else if (n >= 0 && n <= 127) - return GMProg[n]; -def: - sprintf(defname, "%d", n); - return (const char*)defname; -} - -void MidiRead::head(unsigned version, unsigned tracks, unsigned clicksperquarter) -{ -} - -void MidiRead::track(int trackno, long length, int channel) -{ -} - -void MidiRead::endtrack(int trackno) -{ -} - -void MidiRead::event(int what, int len, unsigned char* data) -{ -} - -void MidiRead::seqnumber(unsigned int seqno) -{ -} - -void MidiRead::smpteofs(int hour, int min, int sec, int frame, int fracframe) -{ -} - -void MidiRead::key(int signature, int isminor) -{ -} - -void MidiRead::prefixchannel(unsigned char channel) -{ -} - -void MidiRead::prefixport(unsigned char port) -{ -} - -void MidiRead::text(int what, int len, const char* whattext, const unsigned char* txt) -{ -} - -void MidiRead::meta(int what, int len, const unsigned char* data) -{ -} - -void MidiRead::end() -{ -} - -void MidiRead::tact(int nom, int denom, int v1, int v2) -{ -} - -void MidiRead::tempo(unsigned long ticks) -{ -} - -void MidiRead::program(int channel, int program) -{ -} - -void MidiRead::control(int channel, int what, int value) -{ -} - -void MidiRead::highbank(int channel, int val) -{ - control(channel, ctrl_highbank, val); -} - -void MidiRead::wheel(int channel, int val) -{ - control(channel, ctrl_wheel, val); -} - -void MidiRead::breath(int channel, int val) -{ - control(channel, ctrl_breath, val); -} - -void MidiRead::foot(int channel, int val) -{ - control(channel, ctrl_foot, val); -} - -void MidiRead::portamentotime(int channel, int val) -{ - control(channel, ctrl_portamentotime, val); -} - -void MidiRead::data(int channel, int val) -{ - control(channel, ctrl_data, val); -} - -void MidiRead::volume(int channel, int val) -{ - control(channel, ctrl_volume, val); -} - -void MidiRead::balance(int channel, int val) -{ - control(channel, ctrl_balance, val); -} - -void MidiRead::expression(int channel, int val) -{ - control(channel, ctrl_expression, val); -} - -void MidiRead::lowbank(int channel, int val) -{ - control(channel, ctrl_lowbank, val); -} - -void MidiRead::hold(int channel, int val) -{ - control(channel, ctrl_hold, val); -} - -void MidiRead::reverb(int channel, int val) -{ - control(channel, ctrl_reverb, val); -} - -void MidiRead::chorus(int channel, int val) -{ - control(channel, ctrl_chorus, val); -} - -void MidiRead::datainc(int channel, int val) -{ - control(channel, ctrl_datainc, val); -} - -void MidiRead::datadec(int channel, int val) -{ - control(channel, ctrl_datadec, val); -} - -void MidiRead::lowrpn(int channel, int val) -{ - control(channel, ctrl_lowrpn, val); -} - -void MidiRead::highrpn(int channel, int val) -{ - control(channel, ctrl_highrpn, val); -} - -void MidiRead::resetctrlrs(int channel, int val) -{ - control(channel, ctrl_resetctrlrs, val); -} - -void MidiRead::allnotesoff(int channel, int val) -{ - control(channel, ctrl_allnotesoff, val); -} - -void MidiRead::pitchbendrange(int channel, int halfnotes) -{ - highrpn(channel, 0); - time(0); - lowrpn(channel, 0); - time(0); - data(channel, halfnotes); -} - -void MidiRead::noteon(int channel, int note, int vel) -{ -} - -void MidiRead::noteoff(int channel, int note, int vel) -{ -} - -void MidiRead::time(unsigned long ticks) -{ -} - -void MidiRead::pitchbend(int channel, int val) -{ -} - -void MidiRead::polyaftertouch(int channel, int note, int val) -{ -} - -void MidiRead::aftertouch(int channel, int val) -{ -} - -void MidiRead::songpos(unsigned pos) -{ -} - -void MidiRead::songselect(unsigned char song) -{ -} - -void MidiRead::tunerequest() -{ -} - -void MidiRead::timingclock() -{ -} - -void MidiRead::start() -{ -} - -void MidiRead::cont() -{ -} - -void MidiRead::stop() -{ -} - -void MidiRead::activesense() -{ -} - -void MidiRead::sysex(int syslen, const unsigned char* sysdata) -{ -} - -void MidiRead::gmreset() -{ -} - -void MidiRead::gsreset() -{ -} - -void MidiRead::gsexit() -{ -} - -void MidiRead::xgreset() -{ -} - -void MidiRead::endmidi() -{ -} - -void MidiRead::percent(int perc) -{ -} - -void MidiRead::error(const char* msg) -{ - fprintf(stderr, "error: %s\n", msg); -} - -void MidiRead::warning(const char* msg) -{ - fprintf(stderr, "warning: %s\n", msg); -} - -FILE* MidiRead::getf() -{ - return f_; -} - -// class MidiWrite - -const char* MidiWrite::copyright() -{ - return (const char*)::copyright; -} - -MidiWrite::MidiWrite(const char* filename) -{ - midiname_ = filename; - if (midiname_) - f_ = fopen(midiname_, WRITE_BINARY); - else - f_ = 0; - trackpos_ = -1; - curpos_ = 0; - trackchannel_ = -1; - - bufpos_ = 0; - buflen_ = 0; - filesize_ = 0; - trackcount_ = 0; - curtime_ = curdelta_ = 0; - lastcode_ = -1; - clicks_ = 0; -} - -MidiWrite::~MidiWrite() -{ - if (trackcount_ > 0) - { - seek(10); - putword(trackcount_); - } - if (trackpos_ > 0) - endtrack(); - flush(); - if (f_) - fclose(f_); -} - -void MidiWrite::head(int version, int tracks, unsigned clicksperquarter) -{ - seek(0); - putlong(MThd); - putlong(6); - putword(version); - putword(tracks); // unknown - putword(clicks_ = clicksperquarter); -} - -void MidiWrite::track() -{ - if (trackpos_ > 0) - endtrack(); - endtrack_ = 0; - lastcode_ = -1; - curtime_ = curdelta_ = 0; - seek(trackpos_ = filesize_); - putlong(MTrk); - putlong(0); // unknown yet - trackcount_++; -} - -void MidiWrite::endtrack() -{ - seek(filesize_); - if (!endtrack_) - end(); - if (trackpos_ <= 0) - return; - seek(trackpos_+4); - putlong(filesize_ - trackpos_ - 8); - trackpos_ = 0; -} - -void MidiWrite::event(int what, int len, const unsigned char* data) -{ - puttime(); - putcode(what); - put(len, data); -} - -void MidiWrite::prefixchannel(unsigned char channel) -{ - meta(0x20, 1, &channel); -} - -void MidiWrite::prefixport(unsigned char port) -{ - meta(0x21, 1, &port); -} - -void MidiWrite::text(int what, int len, const unsigned char* txt) -{ - if (len < 0) - len = strlen((char *)txt); - meta(what, len, txt); -} - -void MidiWrite::meta(int what, int len, const unsigned char* data) -{ - puttime(); - putcode(0xff); - putbyte(what); - putdelta(len); - put(len, data); -} - -void MidiWrite::end() -{ - if (endtrack_) // don't need end of track event twice - return; - endtrack_ = 1; - meta(0x2f, 0, 0); -} - -void MidiWrite::tact(int nom, int denom, int v1, int v2) -{ -int log2denom; - - switch(denom) - { - case 1: log2denom = 0; break; - case 2: log2denom = 1; break; - case 4: log2denom = 2; break; - case 8: log2denom = 3; break; - case 16: log2denom = 4; break; - case 32: log2denom = 5; break; - case 64: log2denom = 6; break; - case 128: log2denom = 7; break; - case 256: log2denom = 8; break; - default: - log2denom = 2; - assert((1 << log2denom) == denom); - } - puttime(); - putcode(0xff); - putbyte(0x58); - putbyte(4); - putbyte(nom); - putbyte(log2denom); - putbyte(v1); - putbyte(v2); -} - -void MidiWrite::seqnumber(unsigned int seqno) -{ - puttime(); - putcode(0xff); - putbyte(0x00); - putbyte(2); - putword(seqno); -} - -void MidiWrite::smpteofs(int hour, int min, int sec, int frame, int fracframe) -{ - puttime(); - putcode(0xff); - putbyte(0x54); - putbyte(5); - putbyte(hour); - putbyte(min); - putbyte(sec); - putbyte(frame); - putbyte(fracframe); -} - -void MidiWrite::key(int signature, int isminor) -{ - puttime(); - putcode(0xff); - putbyte(0x59); - putbyte(2); - putbyte((signed char)signature); - putbyte((signed char)isminor); -} - -void MidiWrite::tempo(unsigned long ticks) -{ - puttime(); - putcode(0xff); - putbyte(0x51); - putbyte(3); - puttri(ticks); -} - -void MidiWrite::program(int channel, int prg) -{ - assert(channel >= 0 && channel < 16); - puttime(); - putcode(0xC0 + channel); - putbyte(prg); -} - -void MidiWrite::control(int channel, int what, int val) -{ - assert(channel >= 0 && channel < 16); - puttime(); - putcode(0xB0 + channel); - putbyte(what); - putbyte(val); -} - -void MidiWrite::highbank(int channel, int val) -{ - control(channel, ctrl_highbank, val); -} - -void MidiWrite::wheel(int channel, int val) -{ - control(channel, ctrl_wheel, val); -} - -void MidiWrite::breath(int channel, int val) -{ - control(channel, ctrl_breath, val); -} - -void MidiWrite::foot(int channel, int val) -{ - control(channel, ctrl_foot, val); -} - -void MidiWrite::portamentotime(int channel, int val) -{ - control(channel, ctrl_portamentotime, val); -} - -void MidiWrite::data(int channel, int val) -{ - control(channel, ctrl_data, val); -} - -void MidiWrite::volume(int channel, int val) -{ - control(channel, ctrl_volume, val); -} - -void MidiWrite::balance(int channel, int val) -{ - control(channel, ctrl_balance, val); -} - -void MidiWrite::expression(int channel, int val) -{ - control(channel, ctrl_expression, val); -} - -void MidiWrite::lowbank(int channel, int val) -{ - control(channel, ctrl_lowbank, val); -} - -void MidiWrite::hold(int channel, int val) -{ - control(channel, ctrl_hold, val); -} - -void MidiWrite::reverb(int channel, int val) -{ - control(channel, ctrl_reverb, val); -} - -void MidiWrite::chorus(int channel, int val) -{ - control(channel, ctrl_chorus, val); -} - -void MidiWrite::datainc(int channel, int val) -{ - control(channel, ctrl_datainc, val); -} - -void MidiWrite::datadec(int channel, int val) -{ - control(channel, ctrl_datadec, val); -} - -void MidiWrite::lowrpn(int channel, int val) -{ - control(channel, ctrl_lowrpn, val); -} - -void MidiWrite::highrpn(int channel, int val) -{ - control(channel, ctrl_highrpn, val); -} - -void MidiWrite::resetctrlrs(int channel, int val) -{ - control(channel, ctrl_resetctrlrs, val); -} - -void MidiWrite::allnotesoff(int channel, int val) -{ - control(channel, ctrl_allnotesoff, val); -} - -void MidiWrite::noteon(int channel, int note, int vel) -{ - assert(channel >= 0 && channel < 16); - puttime(); - putcode(0x90+channel); - putbyte(note); - putbyte(vel); -} - -void MidiWrite::noteoff(int channel, int note, int vel) -{ - assert(channel >= 0 && channel < 16); - puttime(); - if (vel != 0 || lastcode_ < 0 || (lastcode_ & 0xF0) != 0x90) - putcode(0x80+channel); - else - putcode(0x90+channel); // vel == 0! - putbyte(note); - putbyte(vel); -} - -void MidiWrite::time(unsigned long ticks) -{ - if ( ticks >= NOTREALISTIC_PAUSE ) - warning("generating unrealistic large pause"); - curdelta_ += ticks; - curtime_ += ticks; -} - -void MidiWrite::cleardelta() -{ - curtime_ -= curdelta_; - curdelta_ = 0; -} - -void MidiWrite::pitchbend(int channel, int val) -{ - assert(channel >= 0 && channel < 16); - puttime(); - putcode(0xE0 + channel); - putbyte(val & 0x7F); - putbyte((val >> 7) & 0x7F); -} - -void MidiWrite::polyaftertouch(int channel, int note, int val) -{ - assert(channel >= 0 && channel < 16); - puttime(); - putcode(0xA0 + channel); - putbyte(note); - putbyte(val); -} - -void MidiWrite::aftertouch(int channel, int val) -{ - assert(channel >= 0 && channel < 16); - puttime(); - putcode(0xD0 + channel); - putbyte(val); -} - -void MidiWrite::songpos(unsigned pos) -{ - puttime(); - putcode(0xF2); - putbyte(pos & 0x7F); - putbyte((pos >> 7) & 0x7F); -} - -void MidiWrite::songselect(unsigned char song) -{ - puttime(); - putcode(0xF3); - putbyte(song); -} - -void MidiWrite::tunerequest() -{ - puttime(); - putcode(0xf6); -} - -void MidiWrite::timingclock() -{ - puttime(); - putcode(0xf8); -} - -void MidiWrite::start() -{ - puttime(); - putcode(0xfa); -} - -void MidiWrite::cont() -{ - puttime(); - putcode(0xfb); -} - -void MidiWrite::stop() -{ - puttime(); - putcode(0xfc); -} - -void MidiWrite::activesense() -{ - puttime(); - putcode(0xfe); -} - -void MidiWrite::sysex(int syslen, const unsigned char* sysdata) -{ - puttime(); - putcode(0xf0); - if (*sysdata == 0xF0) - { - syslen--; - sysdata++; - } - put(syslen, sysdata); -} - -void MidiWrite::gmreset() -{ - sysex(sysexlen(sysex_gmreset), sysex_gmreset); -} - -void MidiWrite::xgreset() -{ - sysex(sysexlen(sysex_xgreset), sysex_xgreset); -} - -void MidiWrite::gsreset() -{ - sysex(sysexlen(sysex_gsreset), sysex_gsreset); -} - -void MidiWrite::gsexit() -{ - sysex(sysexlen(sysex_gsexit), sysex_gsexit); -} - -void MidiWrite::pitchbendrange(int channel, int halfnotes) -{ - highrpn(channel, 0); - lowrpn(channel, 0); - data(channel, halfnotes); -} - -void MidiWrite::putbyte(unsigned char val) -{ - put(1, &val); -} - -void MidiWrite::putcode(unsigned char code) -{ -int put; - - assert(code >= 0x80); - - if (compress) - put = !(code == lastcode_ && code <= 0x9f); - else - put = 1; - if (put) - putbyte(code); - lastcode_ = code; -} - -static unsigned char c[4]; - -void MidiWrite::putword(unsigned val) -{ - c[1] = (unsigned char)(val & 0xff); val >>= 8; - c[0] = (unsigned char)(val & 0xff); - put(2, c); -} - -void MidiWrite::puttri(unsigned long val) -{ - c[2] = (unsigned char)(val & 0xff); val >>= 8; - c[1] = (unsigned char)(val & 0xff); val >>= 8; - c[0] = (unsigned char)(val & 0xff); - put(3, c); -} - -void MidiWrite::putlong(unsigned long val) -{ - c[3] = (unsigned char)(val & 0xff); val >>= 8; - c[2] = (unsigned char)(val & 0xff); val >>= 8; - c[1] = (unsigned char)(val & 0xff); val >>= 8; - c[0] = (unsigned char)(val & 0xff); - put(4, c); -} - -void MidiWrite::putdelta(unsigned long val) -{ - int i = 0, j = 3; - while (i < 4) - { - c[j] = val & 0x7F; - if (j < 3) - c[j] |= 0x80; - val >>= 7; - i++; - if (!val) - break; - j--; - } - put(i, c+j); -} - -void MidiWrite::puttime() -{ - putdelta(curdelta_); - curdelta_ = 0; -} - -void MidiWrite::flush() -{ - if (buflen_ > 0) - { - fseek(f_, curpos_ - bufpos_, SEEK_SET); - if (fwrite(buf_, buflen_, 1, f_) != 1) - error("write error (maybe disk full)"); - assert(ftell(f_) == curpos_ - bufpos_ + buflen_); - bufpos_ = buflen_ = 0; - } -} - -void MidiWrite::put(int len, const unsigned char* c) -{ - if (len <= 0) - return; - if (c == 0 || len > sizeof(buf_)) - return; - if (sizeof(buf_) - bufpos_ < len) - flush(); - memcpy(buf_+bufpos_, c, len); - bufpos_+=len; - if (bufpos_ > buflen_) - buflen_ = bufpos_; - curpos_+= len; - if (curpos_ > filesize_) - filesize_ = curpos_; -} - -void MidiWrite::seek(long pos) -{ - assert(pos >= 0 && pos <= filesize_); - if (curpos_ == pos) - return; - if (pos >= curpos_-bufpos_ && pos <= curpos_-bufpos_+buflen_) - { - bufpos_ = (int)(pos - (curpos_-bufpos_)); - curpos_ = pos; - return; - } - flush(); - curpos_ = pos; -} - -FILE* MidiWrite::getf() -{ - return f_; -} - -void MidiWrite::error(const char* msg) -{ - fprintf(stderr, "midi write error: %s\n", msg); -} - -void MidiWrite::warning(const char* msg) -{ - fprintf(stderr, "midi write warning: %s\n", msg); -} - -int MidiWrite::unitsperquarter() -{ - return clicks_; -} - -MidiCopy::MidiCopy(const char* filename, FILE* f) : MidiRead(filename, f) -{ - dest_ = NULL; - for (int c = 0; c < 16; c++) - mapchannel_[c] = c; // no change -} - -void MidiCopy::head(unsigned version, unsigned tracks, unsigned clicksperquarter) -{ - if (dest_) - dest_->head(version, 0, clicksperquarter); -} - -void MidiCopy::track(int trackno, long length, int channel) -{ - if (dest_) - dest_->track(); -} - -void MidiCopy::endtrack(int trackno) -{ - if (dest_) - dest_->endtrack(); -} - -void MidiCopy::event(int what, int len, unsigned char* data) -{ - if (dest_) - dest_->event(what, len, data); -} - - -void MidiCopy::seqnumber(unsigned int seqno) -{ - if (dest_) - dest_->seqnumber(seqno); -} - -void MidiCopy::smpteofs(int hour, int min, int sec, int frame, int fracframe) -{ - if (dest_) - dest_->smpteofs(hour, min, sec, frame, fracframe); -} - -void MidiCopy::key(int signature, int isminor) -{ - if (dest_) - dest_->key(signature, isminor); -} - -void MidiCopy::prefixchannel(unsigned char channel) -{ - if (dest_ && mapchannel_[channel] >= 0) - dest_->prefixchannel(mapchannel_[channel]); -} - -void MidiCopy::prefixport(unsigned char port) -{ - if (dest_) - dest_->prefixport(port); -} - -void MidiCopy::text(int what, int len, char* whattext, unsigned char* txt) -{ - if (dest_) - dest_->text(what, len, txt); -} - -void MidiCopy::meta(int what, int len, unsigned char* data) -{ - if (dest_) - dest_->meta(what, len, data); -} - -void MidiCopy::end() -{ - if (dest_) - dest_->end(); -} - -void MidiCopy::tact(int nom, int denom, int v1, int v2) -{ - if (dest_) - dest_->tact(nom, denom, v1,v2); -} - -void MidiCopy::tempo(unsigned long ticks) -{ - if (dest_) - dest_->tempo(ticks); -} - -void MidiCopy::program(int channel, int program) -{ - if (dest_ && mapchannel_[channel] >= 0) - dest_->program(mapchannel_[channel], program); -} - -void MidiCopy::control(int channel, int what, int value) -{ - if (dest_ && mapchannel_[channel] >= 0) - dest_->control(mapchannel_[channel], what, value); -} - -void MidiCopy::highbank(int channel, int val) -{ - if (dest_ && mapchannel_[channel] >= 0) - dest_->highbank(mapchannel_[channel], val); -} - -void MidiCopy::wheel(int channel, int val) -{ - if (dest_ && mapchannel_[channel] >= 0) - dest_->wheel(mapchannel_[channel], val); -} - -void MidiCopy::breath(int channel, int val) -{ - if (dest_ && mapchannel_[channel] >= 0) - dest_->wheel(mapchannel_[channel], val); -} - -void MidiCopy::foot(int channel, int val) -{ - if (dest_ && mapchannel_[channel] >= 0) - dest_->foot(mapchannel_[channel], val); -} - -void MidiCopy::portamentotime(int channel, int val) -{ - if (dest_ && mapchannel_[channel] >= 0) - dest_->portamentotime(mapchannel_[channel], val); -} - -void MidiCopy::data(int channel, int val) -{ - if (dest_ && mapchannel_[channel] >= 0) - dest_->data(mapchannel_[channel], val); -} - -void MidiCopy::volume(int channel, int val) -{ - if (dest_ && mapchannel_[channel] >= 0) - dest_->volume(mapchannel_[channel], val); -} - -void MidiCopy::balance(int channel, int val) -{ - if (dest_ && mapchannel_[channel] >= 0) - dest_->balance(mapchannel_[channel], val); -} - -void MidiCopy::expression(int channel, int val) -{ - if (dest_ && mapchannel_[channel] >= 0) - dest_->expression(mapchannel_[channel], val); -} - -void MidiCopy::lowbank(int channel, int val) -{ - if (dest_ && mapchannel_[channel] >= 0) - dest_->lowbank(mapchannel_[channel], val); -} - -void MidiCopy::hold(int channel, int val) -{ - if (dest_ && mapchannel_[channel] >= 0) - dest_->hold(mapchannel_[channel], val); -} - -void MidiCopy::reverb(int channel, int val) -{ - if (dest_ && mapchannel_[channel] >= 0) - dest_->reverb(mapchannel_[channel], val); -} - -void MidiCopy::chorus(int channel, int val) -{ - if (dest_ && mapchannel_[channel] >= 0) - dest_->chorus(mapchannel_[channel], val); -} - -void MidiCopy::datainc(int channel, int val) -{ - if (dest_ && mapchannel_[channel] >= 0) - dest_->datainc(mapchannel_[channel], val); -} - -void MidiCopy::datadec(int channel, int val) -{ - if (dest_ && mapchannel_[channel] >= 0) - dest_->datadec(mapchannel_[channel], val); -} - -void MidiCopy::lowrpn(int channel, int val) -{ - if (dest_ && mapchannel_[channel] >= 0) - dest_->lowrpn(mapchannel_[channel], val); -} - -void MidiCopy::highrpn(int channel, int val) -{ - if (dest_ && mapchannel_[channel] >= 0) - dest_->highrpn(mapchannel_[channel], val); -} - -void MidiCopy::resetctrlrs(int channel, int val) -{ - if (dest_ && mapchannel_[channel] >= 0) - dest_->resetctrlrs(mapchannel_[channel], val); -} - -void MidiCopy::allnotesoff(int channel, int val) -{ - if (dest_ && mapchannel_[channel] >= 0) - dest_->allnotesoff(mapchannel_[channel], val); -} - - -void MidiCopy::noteon(int channel, int note, int vel) -{ - if (dest_ && mapchannel_[channel] >= 0) - dest_->noteon(mapchannel_[channel], note, vel); -} - -void MidiCopy::noteoff(int channel, int note, int vel) -{ - if (dest_ && mapchannel_[channel] >= 0) - dest_->noteoff(mapchannel_[channel], note, vel); -} - -void MidiCopy::time(unsigned long ticks) -{ - if (dest_) - dest_->time(ticks); -} - -void MidiCopy::pitchbend(int channel, int val) -{ - if (dest_ && mapchannel_[channel] >= 0) - dest_->pitchbend(mapchannel_[channel],val); -} - -void MidiCopy::polyaftertouch(int channel, int note, int val) -{ - if (dest_ && mapchannel_[channel] >= 0) - dest_->polyaftertouch(mapchannel_[channel], note, val); -} - -void MidiCopy::aftertouch(int channel, int val) -{ - if (dest_ && mapchannel_[channel] >= 0) - dest_->aftertouch(mapchannel_[channel], val); -} - -void MidiCopy::songpos(unsigned pos) -{ - if (dest_) - dest_->songpos(pos); -} - -void MidiCopy::songselect(unsigned char song) -{ - if (dest_) - dest_->songselect(song); -} - -void MidiCopy::tunerequest() -{ - if (dest_) - dest_->tunerequest(); - -} - -void MidiCopy::timingclock() -{ - if (dest_) - dest_->timingclock(); -} - -void MidiCopy::start() -{ - if (dest_) - dest_->start(); -} - -void MidiCopy::cont() -{ - if (dest_) - dest_->cont(); -} - -void MidiCopy::stop() -{ - if (dest_) - dest_->stop(); -} - -void MidiCopy::activesense() -{ - if (dest_) - dest_->activesense(); -} - -void MidiCopy::sysex(int syslen, unsigned char* sysdata) -{ - if (dest_) - dest_->sysex(syslen, sysdata); -} - -void MidiCopy::xgreset() -{ - if (dest_) - dest_->xgreset(); -} - -void MidiCopy::gmreset() -{ - if (dest_) - dest_->gmreset(); -} - -void MidiCopy::gsreset() -{ - if (dest_) - dest_->gsreset(); -} - -void MidiCopy::gsexit() -{ - if (dest_) - dest_->gsexit(); -} - -void MidiCopy::setoutput(MidiWrite* dest) -{ - assert(dest != NULL && dest->getf() != NULL); // need a valid MidiWrite instance - dest_ = dest; -} - -void MidiCopy::stopoutput() -{ - dest_ = NULL; // owner is responsible for deleting the MidiWrite object -} - -MidiWrite* MidiCopy::getoutput() -{ - return dest_; -} - -void MidiCopy::mapchannel(int channel, int newchannel) -{ - assert(channel >= 0 && channel < 16); - assert(newchannel >= 0 && newchannel < 16); - mapchannel_[channel] = newchannel; -} - -void MidiCopy::ignorechannel(int channel) -{ - assert(channel >= 0 && channel < 16); - mapchannel_[channel] = -1; -} diff --git a/dro2midi/midiio.hpp b/dro2midi/midiio.hpp deleted file mode 100644 index bc88e10..0000000 --- a/dro2midi/midiio.hpp +++ /dev/null @@ -1,402 +0,0 @@ -#ifndef __MIDIIO__ -#define __MIDIIO__ - -#include - -#ifndef MIDI_BUFSIZE -#define MIDI_BUFSIZE 1024 -#endif - -// opening modes of a midi file -#define WRITE_BINARY "wb" -#define READ_BINARY "rb" - -const unsigned long MThd = 0x4D546864ul; -const unsigned long MTrk = 0x4D54726Bul; - -// different standard midi formats -#define VERSION_MULTICHANNEL 0 -#define VERSION_SINGLECHANNEL 1 -#define VERSION_MULTISONG 2 - -#define OPTION_NOCONTROLS 1 // no control details but general information -#define OPTION_NOEVENTS 2 // no track events at all -#define OPTION_NOMETAEVENTS 4 // no meta details but general information -#define OPTION_NOSYSEVENTS 8 // no sysex details but general information -#define OPTION_NONOTEEVENTS 16 // no note events (8x or 9x) -#define OPTION_NOPOLYEVENTS 32 // no poly aftertouch events (Ax) -#define OPTION_NOCONTROLEVENTS 64 // no control events at all (Bx) -#define OPTION_NOPROGRAMEVENTS 128 // no program change events (Cx) -#define OPTION_NOAFTERTOUCHEVENTS 256 // no aftertouch events (Dx) -#define OPTION_NOPITCHBENDEVENTS 512 // no pitchbend events (Ex) -#define OPTION_NOREALTIMEEVENTS 1024 // no realtime events (Fx) - -// getchannel delivers a valid channel or: -#define NOCHANNEL (-1) -#define MULTICHANNEL (-2) -#define VALIDCHANNEL(ch) ((ch) >= 0 && (ch) <= 15) -#define gm_drumchannel 9 -#define SAYCHANNEL(ch) ((ch) + 1) // 0..15 in midi format but spoken 1..16! - -// for use of param what in function text() -#define meta_text 1 -#define meta_copyright 2 -#define meta_trackname 3 -#define meta_instrument 4 -#define meta_lyric 5 -#define meta_marker 6 -#define meta_cuepoint 7 -#define meta_endtrack 0x2f - -#define ctrl_highbank 0 -#define ctrl_wheel 1 -#define ctrl_breath 2 -#define ctrl_foot 4 -#define ctrl_portamentotime 5 -#define ctrl_data 6 -#define ctrl_volume 7 -#define ctrl_balance 10 -#define ctrl_expression 11 -#define ctrl_lowbank 32 -#define ctrl_hold 64 -#define ctrl_reverb 91 -#define ctrl_chorus 93 -#define ctrl_datainc 96 -#define ctrl_datadec 97 -#define ctrl_lowrpn 100 -#define ctrl_highrpn 101 -#define ctrl_resetctrlrs 121 -#define ctrl_allnotesoff 123 - -#define balance_left 0 -#define balance_center 64 -#define balance_right 127 - -#define volume_mute 0 -#define volume_full 127 - -#define bpm(ticks) (60000000.0 / (ticks)) -#define ticks(beats) ((unsigned long)(60000000.0 / (beats))) - -#define tempo_60bpm (1000000L) -#define tempo_120bpm (500000L) -#define tempo_240bpm (250000L) - - -class MidiRead -{ -public: - static const char* copyright(); - - MidiRead(const char* filename, FILE* f = 0); - virtual ~MidiRead(); - - FILE* getf(); - - int run(); - int runhead(); - int runtrack(int trackno); - int runevent(long trackend); - - long getpos() { return pos_; } - long geteventpos() { return pos_; } - long getcurpos() { return curpos_; } - unsigned long getcurtime() { return curtime_; } // in midi units - - virtual void head(unsigned version, unsigned tracks, unsigned clicksperquarter); - virtual void track(int trackno, long length, int channel); - virtual void endtrack(int trackno); // closing track - - virtual void time(unsigned long ticks); // delay between events - - virtual void event(int what, int len = 0, unsigned char* data = 0); - - // these are event categories: - virtual void meta(int what, int len, const unsigned char* data); - // these are special meta events: - virtual void text(int what, int len, const char* whattext, const unsigned char* txt); - virtual void end(); // end of track command - virtual void prefixchannel(unsigned char channel); - virtual void prefixport(unsigned char port); - virtual void seqnumber(unsigned int seqno); - virtual void smpteofs(int hour, int min, int sec, int frame, int fracframe); - virtual void tact(int nom, int denom, int clicksperbeat, int notes32perbeat); - virtual void tempo(unsigned long microsecperbeat); - virtual void key(int signature, int isminor); // C=0, -7=7flats +7=7sharps - virtual void program(int channel, int prg); - virtual void control(int channel, int what, int val); // general controls - // special controls: - virtual void highbank(int channel, int val); - virtual void wheel(int channel, int val); - virtual void breath(int channel, int val); - virtual void foot(int channel, int val); - virtual void portamentotime(int channel, int val); - virtual void data(int channel, int val); - virtual void volume(int channel, int val); - virtual void balance(int channel, int val); - virtual void expression(int channel, int val); - virtual void lowbank(int channel, int val); - virtual void hold(int channel, int val); - virtual void reverb(int channel, int val); - virtual void chorus(int channel, int val); - virtual void datainc(int channel, int val); - virtual void datadec(int channel, int val); - virtual void lowrpn(int channel, int val); - virtual void highrpn(int channel, int val); - virtual void resetctrlrs(int channel, int val); - virtual void allnotesoff(int channel, int val); - virtual void pitchbendrange(int channel, int val); - virtual void noteon(int channel, int note, int vel); - virtual void noteoff(int channel, int note, int vel); - virtual void pitchbend(int channel, int val); - virtual void polyaftertouch(int channel, int note, int val); - virtual void aftertouch(int channel, int val); - virtual void songpos(unsigned pos); - virtual void songselect(unsigned char song); - virtual void tunerequest(); - virtual void timingclock(); - virtual void start(); - virtual void cont(); - virtual void stop(); - virtual void activesense(); - virtual void sysex(int syslen, const unsigned char* sysdata); - // these are special sysex events: - virtual void xgreset(); - virtual void gmreset(); - virtual void gsreset(); - virtual void gsexit(); - - virtual void endmidi(); // after last track - virtual void error(const char* msg); - virtual void warning(const char* msg); - - virtual void percent(int perc); - - int getchannel() { return curchannel_; } - void setchannel(int channel); - - static const char* progname(int n, int channel); - static const char* notename(unsigned char note); - - int options_; - - int version_, tracks_, clicks_, trackno_; - - void seek(long pos); - int getbyte(); - unsigned getword(); - unsigned long gettri(); - unsigned long getlong(); - unsigned long getdelta(); - unsigned char* get(int len); - unsigned char* need(int nbytes); - - unsigned long microsec(unsigned long units, unsigned long msperbeats); - long units(unsigned long microsec, unsigned long msperbeats); - - // use scanchannel only at start of track! - int scanchannel(unsigned long maxlen); // channel 0-15 or -1=no channel or -2=multichannels - - // use sysevent only directly after reading F0 or F7 - int scansysevent(unsigned long maxlen); - -protected: - const char *midiname_; - FILE* f_; - unsigned char shouldclose_; // 0=no, otherwise=yes - long filesize_; - unsigned char buf_[MIDI_BUFSIZE]; - int buflen_, bufpos_; - int curchannel_; - unsigned long curtime_; - int percent_; - int lastcode_; - unsigned long tracklen_; - - long pos_, curpos_; - unsigned char curdeltalen_; // number of bytes read by recent getdelta() call -}; - -class MidiWrite -{ -public: - static const char* copyright(); - - MidiWrite(const char* filename); - virtual ~MidiWrite(); - - FILE* getf(); - - long getcurpos() { return curpos_; } - long getcurtime() { return curtime_; } - void cleardelta(); - - void head(int version = 1, int tracks = 0, unsigned clicksperquarter = 192); - void track(); - void endtrack(); - - void event(int what, int len, const unsigned char* data); - - void text(int what, int len, const unsigned char* txt); - void meta(int what, int len, const unsigned char* data); // 0xff .... - virtual void prefixchannel(unsigned char channel); - virtual void prefixport(unsigned char port); - virtual void seqnumber(unsigned int seqno); - virtual void smpteofs(int hour, int min, int sec, int frame, int fracframe); - virtual void key(int signature, int isminor); - void tact(int nom, int denom, int v1, int v2); - void tempo(unsigned long microsecperbeat); - void end(); - void program(int channel, int prg); - void control(int channel, int what, int val); - // these are special controls - void highbank(int channel, int val); - void wheel(int channel, int val); - void breath(int channel, int val); - void foot(int channel, int val); - void portamentotime(int channel, int val); - void data(int channel, int val); - void volume(int channel, int val); - void balance(int channel, int val); - void expression(int channel, int val); - void lowbank(int channel, int val); - void hold(int channel, int val); - void reverb(int channel, int val); - void chorus(int channel, int val); - void datainc(int channel, int val); - void datadec(int channel, int val); - void lowrpn(int channel, int val); - void highrpn(int channel, int val); - void resetctrlrs(int channel, int val); - void allnotesoff(int channel, int val); - void pitchbendrange(int channel, int halfnotes); - - void noteon(int channel, int note, int vel); - void noteoff(int channel, int note, int vel = 0); - void time(unsigned long ticks); - void pitchbend(int channel, int val); - void polyaftertouch(int channel, int note, int val); - void aftertouch(int channel, int val); - void songpos(unsigned pos); - void songselect(unsigned char song); - void tunerequest(); - void timingclock(); - void start(); - void cont(); - void stop(); - void activesense(); - void sysex(int syslen, const unsigned char* sysdata); - void xgreset(); - void gmreset(); - void gsreset(); - void gsexit(); - - void putbyte(unsigned char val); - void putcode(unsigned char code); - void putword(unsigned val); - void puttri(unsigned long val); - void putlong(unsigned long val); - void putdelta(unsigned long val); - void puttime(); - void put(int len, const unsigned char* c); - void seek(long pos); - - virtual void error(const char* msg); - virtual void warning(const char* msg); - - int unitsperquarter(); - -protected: - const char *midiname_; - FILE* f_; - long trackpos_, curpos_, filesize_; - int trackchannel_, trackcount_, lastcode_, endtrack_; - - unsigned char buf_[MIDI_BUFSIZE]; - int bufpos_, buflen_; - - unsigned long curdelta_; - unsigned long curtime_; - - int clicks_; - - void flush(); -}; - - -class MidiCopy : public MidiRead -{ -public: - MidiCopy(const char* filename, FILE* f = 0); - - void setoutput(MidiWrite* dest); - void stopoutput(); - MidiWrite* getoutput(); - - void mapchannel(int channel, int newchannel); - void ignorechannel(int channel); - - virtual void head(unsigned version, unsigned tracks, unsigned clicksperquarter); - virtual void track(int trackno, long length, int channel); - virtual void endtrack(int trackno); - - virtual void time(unsigned long ticks); - - virtual void event(int what, int len = 0, unsigned char* data = 0); - - virtual void meta(int what, int len, unsigned char* data); - virtual void text(int what, int len, char* whattext, unsigned char* txt); - virtual void end(); - virtual void prefixchannel(unsigned char channel); - virtual void prefixport(unsigned char port); - virtual void seqnumber(unsigned int seqno); - virtual void smpteofs(int hour, int min, int sec, int frame, int fracframe); - virtual void tact(int nom, int denom, int clicksperbeat, int notes32perbeat); - virtual void tempo(unsigned long microsecperbeat); - virtual void key(int signature, int isminor); // C=0, -7=7flats +7=7sharps - virtual void program(int channel, int prg); - virtual void control(int channel, int what, int val); - virtual void highbank(int channel, int val); - virtual void wheel(int channel, int val); - virtual void breath(int channel, int val); - virtual void foot(int channel, int val); - virtual void portamentotime(int channel, int val); - virtual void data(int channel, int val); - virtual void volume(int channel, int val); - virtual void balance(int channel, int val); - virtual void expression(int channel, int val); - virtual void lowbank(int channel, int val); - virtual void hold(int channel, int val); - virtual void reverb(int channel, int val); - virtual void chorus(int channel, int val); - virtual void datainc(int channel, int val); - virtual void datadec(int channel, int val); - virtual void lowrpn(int channel, int val); - virtual void highrpn(int channel, int val); - virtual void resetctrlrs(int channel, int val); - virtual void allnotesoff(int channel, int val); - virtual void noteon(int channel, int note, int vel); - virtual void noteoff(int channel, int note, int vel); - virtual void pitchbend(int channel, int val); - virtual void polyaftertouch(int channel, int note, int val); - virtual void aftertouch(int channel, int val); - virtual void songpos(unsigned pos); - virtual void songselect(unsigned char song); - virtual void tunerequest(); - virtual void timingclock(); - virtual void start(); - virtual void cont(); - virtual void stop(); - virtual void activesense(); - virtual void sysex(int syslen, unsigned char* sysdata); - virtual void gmreset(); - virtual void gsreset(); - virtual void gsexit(); - virtual void xgreset(); - -protected: - MidiWrite* dest_; - int mapchannel_[16]; // channel 0-15 or events are ignored for invalid channel -}; - -#endif diff --git a/img/Two-OP AM.png b/img/Two-OP AM.png old mode 100644 new mode 100755 diff --git a/img/Two-OP FM.png b/img/Two-OP FM.png old mode 100644 new mode 100755 diff --git a/img/abs_sine.png b/img/abs_sine.png old mode 100644 new mode 100755 diff --git a/img/adlib.png b/img/adlib.png old mode 100644 new mode 100755 diff --git a/img/algo_switch_off.png b/img/algo_switch_off.png old mode 100644 new mode 100755 diff --git a/img/algo_switch_on.png b/img/algo_switch_on.png old mode 100644 new mode 100755 diff --git a/img/algo_switch_on2.png b/img/algo_switch_on2.png old mode 100644 new mode 100755 diff --git a/img/algo_switch_on3.png b/img/algo_switch_on3.png old mode 100644 new mode 100755 diff --git a/img/alternating_sine.png b/img/alternating_sine.png old mode 100644 new mode 100755 diff --git a/img/bassdrum.png b/img/bassdrum.png old mode 100644 new mode 100755 diff --git a/img/camel_sine.png b/img/camel_sine.png old mode 100644 new mode 100755 diff --git a/img/channeloff.png b/img/channeloff.png old mode 100644 new mode 100755 diff --git a/img/channelon.png b/img/channelon.png old mode 100644 new mode 100755 diff --git a/img/cymbal.png b/img/cymbal.png old mode 100644 new mode 100755 diff --git a/img/disabled.png b/img/disabled.png old mode 100644 new mode 100755 diff --git a/img/full_sine.png b/img/full_sine.png old mode 100644 new mode 100755 diff --git a/img/half_sine.png b/img/half_sine.png old mode 100644 new mode 100755 diff --git a/img/hihat.png b/img/hihat.png old mode 100644 new mode 100755 diff --git a/img/line_border_horiz.png b/img/line_border_horiz.png old mode 100644 new mode 100755 diff --git a/img/line_border_vert.png b/img/line_border_vert.png old mode 100644 new mode 100755 diff --git a/img/logarithmic_saw.png b/img/logarithmic_saw.png old mode 100644 new mode 100755 diff --git a/img/quarter_sine.png b/img/quarter_sine.png old mode 100644 new mode 100755 diff --git a/img/snare.png b/img/snare.png old mode 100644 new mode 100755 diff --git a/img/square.png b/img/square.png old mode 100644 new mode 100755 diff --git a/img/toggle_off_sq.png b/img/toggle_off_sq.png old mode 100644 new mode 100755 diff --git a/img/toggle_on_sq.png b/img/toggle_on_sq.png old mode 100644 new mode 100755 diff --git a/img/tom.png b/img/tom.png old mode 100644 new mode 100755 diff --git a/img/yamaha.png b/img/yamaha.png old mode 100644 new mode 100755 diff --git a/py/atten_levels.py b/py/atten_levels.py deleted file mode 100644 index e0ba7a9..0000000 --- a/py/atten_levels.py +++ /dev/null @@ -1,13 +0,0 @@ -lvls = [.75, 1.5, 3, 6, 12, 24] - -def get_db(i): - db = 0.0 - for d in lvls: - if i & 0x1: - db += d - i >>= 1 - return db - -for i in range(2**6): - db = get_db(i) - print '"%.2f dB",' % db, diff --git a/py/oplparse.py b/py/oplparse.py deleted file mode 100644 index 33b9d56..0000000 --- a/py/oplparse.py +++ /dev/null @@ -1,340 +0,0 @@ -import sys -import json - -REG_SCOPE = { - 0x00: 'Chip', - 0x20: '0x20', - 0x30: '0x20', - 0x40: '0x40', - 0x50: '0x40', - 0x60: 'Attack/decay', - 0x70: 'Attack/decay', - 0x80: 'Sustain/release', - 0x90: 'Sustain/release', - 0xA0: 'Frequency (L)', - 0xB0: 'Key on/off/Frequency(H)', - 0xE0: 'Wave select', - 0xF0: 'Wave select', -} - -# Map register ranges to channel + operator -OPERATOR_MAP = { - 0x00: (1, 1), - 0x01: (2, 1), - 0x02: (3, 1), - 0x03: (1, 2), - 0x04: (2, 2), - 0x05: (3, 2), - 0x08: (4, 1), - 0x09: (5, 1), - 0x0a: (6, 1), - 0x0b: (4, 2), - 0x0c: (4, 2), - 0x0d: (6, 2), - 0x10: (7, 1), - 0x11: (8, 1), - 0x12: (9, 1), - 0x13: (7, 2), - 0x14: (8, 2), - 0x15: (9, 2), -} -OSC_MAP = {1: 'mod', 2: 'car'} - -def reg_to_chan_and_op(reg): - # from REGOP in opl driver - #return ( ( reg >> 3) & 0x20 ) | ( reg & 0x1f ) - chan, op = OPERATOR_MAP.get(reg % 32, (-1, 1)) - print 'ch%02d.%s: ' % (chan, OSC_MAP[op]), - return chan, op - -def interpret_00(reg, val, ev): - regs = {1:'TEST', 2:'Timer 1', 3:'Timer 2', 4:'Timer Ctrl', 8:'CSM/keysplit'} - print '%02X (%s)' % (reg, regs.get(reg, '??')), - -def interpret_20(reg, val, ev): - ch, op = reg_to_chan_and_op(reg) - ev[2]['ch'] = ch - bitfields = {7: 'tre', 6: 'vib', 5: 'sus', 4:'ksr'} - bf = val & 0xf0 - opts = [] - for b, opt in bitfields.iteritems(): - optset = 'X' if (bf & (0x1 << b)) else '-' - opts.append('%s:%s' % (opt, optset)) - ev[op-1][opt]=optset - fm_mult = val & 0x0f - mults = {0:0.5, 11:10, 13:12, 14:15} - fm_mult = mults.get(fm_mult, fm_mult) - opts.append('fm_mult:x%d' % (fm_mult)) - ev[op-1]['fm_mult'] = fm_mult - print '; '.join(opts) , - -def _db_atten_to_factor(atten_db): - return (10 ** -(atten_db / 10)) - -def interpret_40(reg, val, ev): - ch, op = reg_to_chan_and_op(reg) - ev[2]['ch'] = ch - bit_db = {5:24, 4:12, 3:6, 2:3, 1:1.5, 0:0.75} - oct_db = {0:0, 0x40:1.5, 0x80:3, 0xC0:6} - lvl = 0.0 - for b, db in bit_db.iteritems(): - if (val & (0x1 << b)): - lvl += db - db_oct = oct_db[val & 0xC0] - oct_scale = '-%d dB/8ve' % db_oct - factor = _db_atten_to_factor(lvl) - ev[op-1]['db'] = lvl - ev[op-1]['db_oct'] = db_oct - ev[op-1]['scale'] = factor - print '%.2f dB; (x%.4f) %s' % (lvl, factor, oct_scale) , - -def interpret_60(reg, val, ev): - ch, op = reg_to_chan_and_op(reg) - ev[2]['ch'] = ch - a = val >> 4 - d = val & 0x0f - ev[op-1]['a'] = a - ev[op-1]['d'] = d - print 'Att: 0x%1x; Dec: 0x%1x' % (a, d) , - -def interpret_80(reg, val, ev): - ch, op = reg_to_chan_and_op(reg) - ev[2]['ch'] = ch - s = val >> 4 - r = val & 0x0f - ev[op-1]['s'] = s - ev[op-1]['r'] = r - print 'Sus: 0x%1x; Rel: 0x%1x' % (s, r) , - -def interpret_A0(reg, val, ev): - global freq_lsb - freq_lsb = val - chan = 1 + reg % 0xA0 - print 'ch%02d: Freq-LSB=%02X' % (chan, val) , - -def interpret_B0(reg, val, ev): - global freq_lsb - try: - freq_lsb - except NameError: - freq_lsb = 0 - oct = (val >> 2) & 0x7 - fnum = ((val & 0x3) << 8) + freq_lsb - chan = 1 + reg % 0xB0 - key = 'ON' if (1 << 5) & val else 'off' - frq = _fnum_to_hz(fnum, oct) - ev[1]['frq'] = frq - ev[2]['key'] = key - ev[2]['ch'] = chan - #ev[0]['gate'] = key - print 'ch%02d: Key-%s; f0x%03X oct:%d (%.4f Hz)' % (chan, key, fnum, oct, frq) , - -''' -A0-A8: Frequency Number: - - Determines the pitch of the note. Highest bits of F-Number are stored - in the register below. - - -B0-B8: Key On / Block Number / F-Number(hi bits): - - bit 5: KEY-ON. When 1, channel output is enabled. - bits 2-4: Block Number. Roughly determines the octave. - bits 0-1: Frequency Number. 2 highest bits of the above register. - - The following formula is used to determine F-Number and Block: - - F-Number = Music Frequency * 2^(20-Block) / 49716 Hz -''' - -def _fnum_to_hz(fnum, octave): - return (49716.0 * fnum) / (2 ** (21 - octave)) - -def interpret_BD(reg, val, ev): - print '* BD' , - -def interpret_C0(reg, val, ev): - alg = 'ADD' if val & 0x1 else 'MODULATE' - feedback = (val >> 1) & 0xc - ev[2]['alg'] = alg - ev[2]['feedback'] = feedback - print 'feedback: %d algo: %s' % (feedback, alg) , - -def interpret_E0(reg, val, ev): - ch, op = reg_to_chan_and_op(reg) - ev[2]['ch'] = ch - waves = {0: 'SIN', 1: 'HALFSIN', 2: 'ABSSIN', 3: 'QUARTSIN'} - wav = waves[val & 0x3] - ev[op-1]['wav'] = wav - print wav , - -def interpret_write(ts, reg, val, ev): - print '%10d : ' % ts, - if reg >= 0x01 and reg <= 0x08: - interpret_00(reg, val, ev) - elif reg >= 0x20 and reg <= 0x35: - interpret_20(reg, val, ev) - elif reg >= 0x40 and reg <= 0x55: - interpret_40(reg, val, ev) - elif reg >= 0x60 and reg <= 0x75: - interpret_60(reg, val, ev) - elif reg >= 0x80 and reg <= 0x95: - interpret_80(reg, val, ev) - elif reg >= 0xA0 and reg <= 0xA8: - interpret_A0(reg, val, ev) - elif reg >= 0xB0 and reg <= 0xB8: - interpret_B0(reg, val, ev) - elif reg == 0xBD: - interpret_BD(reg, val, ev) - elif reg >= 0xC0 and reg <= 0xC8: - interpret_C0(reg, val, ev) - elif reg >= 0xE0 and reg <= 0xF5: - interpret_E0(reg, val, ev) - else: - print '?????', - print - - #scope = REG_SCOPE.get(reg & 0xF0, '??') - #chan, operator = reg_to_chan_and_op(reg) - #aprint '%10f : %02d.%1d : %02x : %20s' % (float(ts)/1000, chan, operator, val, scope) - -def parse_opldump(stream): - events = dict() - t0 = tp = False - line = stream.readline() - while len(line) >= 1: - ts, opl2, op = line.split(':') - if 'OPL2' == opl2: - ts = int(ts) - if not t0: - t0 = ts - ts -= t0 - reg, val = op.split('=') - reg = int(reg, 16) - val = int(val, 16) - try: - ev = events[ts] - except KeyError: - ev = [dict(), dict(), dict()] - events[ts] = ev - interpret_write(ts, reg, val, ev) - line = stream.readline() - return events - -def get_javascript_for(event): - mod = event[0] - car = event[1] - anc = event[2] - indent = 8*' ' - lines = [] - if 'ch' in anc: - lines.append('var ch = opl2.channels[%d];\n' % (anc['ch'] - 1)) - if 'fm_mult' in car: - lines.append('ch.setModulatorMultiplier(%d); ch.setCarrierMultiplier(%d);\n' % (mod['fm_mult'], car['fm_mult'])) - if 'scale' in car: - lines.append('ch.setModulatorAttenuation(%f); ch.setCarrierAttenuation(%f);\n' % (mod['scale'], car['scale'])) - if 'a' in mod: - lines.append('ch.setEnvParams(ch.modEnv, 1/%d, 1/%d, 1/%d, 1/%d);\n' % (mod['a'], mod['d'], mod['s']+1, mod['r'])) - if 'a' in car: - lines.append('ch.setEnvParams(ch.carEnv, 1/%d, 1/%d, 1/%d, 1/%d);\n' % (car['a'], car['d'], car['s']+1, car['r'])) - if 'wav' in car: - lines.append('ch.setCarrierWaveform(ch.%s); ch.setModulatorWaveform(ch.%s);\n' % (car['wav'], mod['wav'])) - if 'key' in anc: - if 'ON' == anc['key'].upper(): - lines.append('ch.noteOn(%f);\n' % car['frq']) - elif 'OFF' == anc['key'].upper(): - lines.append('ch.noteOff();\n') - return indent + indent.join(lines) - - -def find_unique_instruments(events): - instruments = dict() - ilist = list() - timestamps = events.keys() - timestamps.sort() - for t in timestamps: - ev = events[t] - try: # get rid of fields which don't define uniqueness - del ev[2]['key'] - del ev[2]['ch'] - del ev[1]['frq'] - except KeyError: - pass - instr_json = json.dumps(ev, indent=2) - if instr_json not in instruments: - instruments[instr_json] = t - ilist.append(instr_json) - return ilist, instruments - -def print_instrument(json_i, ts): - print - print '@ %10d:' % ts - try: - d = json.loads(json_i) - print 'Waveforms: %10s%10s' % (d[0]['wav'], d[1]['wav']) - print 'Freq mult: %9dx%9dx' % (d[0]['fm_mult'], d[1]['fm_mult']) - print 'Levels: %7d dB%7d dB' % (-d[0]['db'], -d[1]['db']) - envs = (d[0]['a'], d[0]['d'], d[0]['s'], d[0]['r'], d[1]['a'], d[1]['d'], d[1]['s'], d[1]['r']) - print 'Envelopes: %1x %1x %1x %1x %1x %1x %1x %1x' % envs - except KeyError: - print 'incomplete instrument?' - -# Conversions mapping to VST floating point values -W2F = {'SIN':0, 'HALFSIN':.33, 'ABSSIN':.66, 'QUARTSIN':1} -def m2f(mult): - if mult < 1: # half frq - return 0 - else: - return float(mult)/15.0 - -def a2f(att_db): - a = float(att_db)/0.75 - return a/63.0 - -def b2f(bool_param): - if 'X' == bool_param: - return 1.0 - else: - return 0.0 -D2F = {0:0.0,1.5:0.33,3.0:0.66,6.0:1.0} - -def e2f(env_val): - return float(env_val)/15.0 - -def output_instrument_vst_program(json_i, ts): - try: - d = json.loads(json_i) - m=d[0]; c=d[1] - lines = [ - ' const float i_params_%d[] = {' % ts, - ' %.6ff, %.6ff, // waveforms' % (W2F[c['wav']], W2F[m['wav']]), - ' %.6ff, %.6ff, // frq multipliers' % (m2f(c['fm_mult']), m2f(m['fm_mult'])), - ' %.6ff, %.6ff, // attenuation' % (a2f(c['db']), a2f(m['db'])), - ' %.1ff, %.1ff, %.1ff, %.1ff, // tre / vib / sus / ks' % tuple([b2f(c[x]) for x in ['tre', 'vib', 'sus', 'ksr']]), - ' %.1ff, %.1ff, %.1ff, %.1ff, // tre / vib / sus / ks' % tuple([b2f(m[x]) for x in ['tre', 'vib', 'sus', 'ksr']]), - ' %.6ff, %.6ff, // KSR/8ve' % (D2F[c['db_oct']], D2F[m['db_oct']]), - ' %.6ff, // algorithm' % (1.0 if 'ADD'==d[2]['alg'] else 0.0), - ' %.6ff, // feedback' % (float(d[2]['feedback'])/7.0), - ' %.1ff, %.1ff, %.1ff, %.1ff, // adsr' % tuple([e2f(c[x]) for x in ['a', 'd', 's', 'r']]), - ' %.1ff, %.1ff, %.1ff, %.1ff, // adsr' % tuple([e2f(m[x]) for x in ['a', 'd', 's', 'r']]), - ' };', - ' std::vector v_i_params_%d (i_params_%d, i_params_%d + sizeof(i_params_%d) / sizeof(float));' % (ts,ts,ts,ts), - ' programs["Instr %d"] = std::vector(v_i_params_%d);' % (ts, ts), - ] - print - print '\n'.join(lines) - except KeyError: - pass - #print - #print '// incomplete instrument..' - -def main(argv): - events = parse_opldump(sys.stdin) - ilist, instruments = find_unique_instruments(events) - for i in ilist: - output_instrument_vst_program(i, instruments[i]) - #print i, instruments[i] - #print_instrument(i, instruments[i]) - -if '__main__' == __name__: - main(sys.argv) diff --git a/py/sbi_filter.py b/py/sbi_filter.py deleted file mode 100644 index 2a1f28d..0000000 --- a/py/sbi_filter.py +++ /dev/null @@ -1,45 +0,0 @@ -import glob -import os - -class SBI: - def __init__(self, sbi_data): - if sbi_data[0:3] != b'SBI': - raise ValueError('Invalid file header.') - if len(sbi_data) != 52: - raise ValueError('Invalid file size.') - self.register_values = sbi_data[36:] - - def __eq__(self, other): - return isinstance(other, SBI) and self.register_values == other.register_values - - def __hash__(self): - return hash(self.register_values) - - -# Requires Python 3.5+ -def find_distinct_sbis(root_dir): - sbis = {} - bad = [] - for filename in glob.iglob(os.path.join(root_dir, '**', '*.sbi'), recursive=True): - with open(filename, 'rb') as f: - try: - sbi = SBI(f.read()) - try: - dup_filename = sbis[sbi] - print('Duplicate SBI file: %s (duplicate of %s)' % (filename, dup_filename)) - bad.append(filename) - except KeyError: - sbis[sbi] = filename - except ValueError as e: - print('Invalid SBI file: %s (%s)' % (filename, e)) - bad.append(filename) - return sbis, bad - - -if __name__ == '__main__': - distinct_sbis, to_delete = find_distinct_sbis('.') - print('Found %d distinct SBI files' % len(distinct_sbis)) - print('Writing batch file..') - with open('delete_bad_sbis.cmd', 'w') as batch_file: - for delete in to_delete: - print('DEL %s' % delete, file=batch_file)