From f742d2f5618a70dffe604b9e87caf437f20a13c2 Mon Sep 17 00:00:00 2001 From: bruce Date: Thu, 14 Nov 2013 20:08:45 +0800 Subject: [PATCH] Original DRO2MIDI 1.5 code. --- dro2midi/dro2midi.cpp | 1286 +++++++++++++++++++++ dro2midi/gen_test_midi.cpp | 74 ++ dro2midi/make | 21 + dro2midi/midiio.cpp | 2161 ++++++++++++++++++++++++++++++++++++ dro2midi/midiio.hpp | 402 +++++++ 5 files changed, 3944 insertions(+) create mode 100644 dro2midi/dro2midi.cpp create mode 100644 dro2midi/gen_test_midi.cpp create mode 100644 dro2midi/make create mode 100644 dro2midi/midiio.cpp create mode 100644 dro2midi/midiio.hpp diff --git a/dro2midi/dro2midi.cpp b/dro2midi/dro2midi.cpp new file mode 100644 index 0000000..2b77021 --- /dev/null +++ b/dro2midi/dro2midi.cpp @@ -0,0 +1,1286 @@ +// +// 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. +// + +#define VERSION "1.5" +#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) logbase(x, 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 + +// 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.\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" + "\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); + } +} + +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] + ); + 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, "-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 new file mode 100644 index 0000000..99eb97d --- /dev/null +++ b/dro2midi/gen_test_midi.cpp @@ -0,0 +1,74 @@ +// +// 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 new file mode 100644 index 0000000..a452b9f --- /dev/null +++ b/dro2midi/make @@ -0,0 +1,21 @@ +#!/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 new file mode 100644 index 0000000..46bda29 --- /dev/null +++ b/dro2midi/midiio.cpp @@ -0,0 +1,2161 @@ +// 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 new file mode 100644 index 0000000..bc88e10 --- /dev/null +++ b/dro2midi/midiio.hpp @@ -0,0 +1,402 @@ +#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