diff --git a/docs/rh-log.txt b/docs/rh-log.txt index 5f3617dd3c..b46cd7b500 100644 --- a/docs/rh-log.txt +++ b/docs/rh-log.txt @@ -1,9 +1,18 @@ +May 8, 2008 +- Reduced volume, expression, and panning controllers back to 7 bits. +- Added very basic Soundfont support to the internal TiMidity. Things missing: + filter, LFOs, modulation envelope, chorus, reverb, and modulators. May or + may not be compatible with TiMidity++'s soundfont extensions. + May 8, 2008 (Changes by Graf Zahl) - Changed all thing coordinates that were stored as shorts into fixed_t. - Separated mapthing2_t into mapthinghexen_t and the internal FMapThing so that it is easier to add new features in the UDMF map format. - Added some initial code to read UDMF maps. +May 6, 2008 +- Added support for quoted strings to the TiMidity config parser. + May 2, 2008 (Changes by Graf Zahl) - Split off the slope creation code from p_Setup.cpp into its own file. - Separated the linedef activation types into a bit mask that allows combination diff --git a/src/c_dispatch.cpp b/src/c_dispatch.cpp index 2d5abf050a..06770547fb 100644 --- a/src/c_dispatch.cpp +++ b/src/c_dispatch.cpp @@ -101,7 +101,7 @@ struct FActionMap // PRIVATE FUNCTION PROTOTYPES --------------------------------------------- -static long ParseCommandLine (const char *args, int *argc, char **argv); +static long ParseCommandLine (const char *args, int *argc, char **argv, bool no_escapes); static FConsoleCommand *FindNameInHashTable (FConsoleCommand **table, const char *name, size_t namelen); static FConsoleCommand *ScanChainForName (FConsoleCommand *start, const char *name, size_t namelen, FConsoleCommand **prev); @@ -724,7 +724,7 @@ void AddCommandString (char *cmd, int keynum) // \c becomes just TEXTCOLOR_ESCAPE // $ is replaced by the contents of -static long ParseCommandLine (const char *args, int *argc, char **argv) +static long ParseCommandLine (const char *args, int *argc, char **argv, bool no_escapes) { int count; char *buffplace; @@ -758,15 +758,15 @@ static long ParseCommandLine (const char *args, int *argc, char **argv) do { stuff = *args++; - if (stuff == '\\' && *args == '\"') + if (!no_escapes && stuff == '\\' && *args == '\"') { stuff = '\"', args++; } - else if (stuff == '\\' && *args == '\\') + else if (!no_escapes && stuff == '\\' && *args == '\\') { args++; } - else if (stuff == '\\' && *args == 'c') + else if (!no_escapes && stuff == '\\' && *args == 'c') { stuff = TEXTCOLOR_ESCAPE, args++; } @@ -824,11 +824,12 @@ static long ParseCommandLine (const char *args, int *argc, char **argv) return (long)(buffplace - (char *)0); } -FCommandLine::FCommandLine (const char *commandline) +FCommandLine::FCommandLine (const char *commandline, bool no_escapes) { cmd = commandline; _argc = -1; _argv = NULL; + noescapes = no_escapes; } FCommandLine::~FCommandLine () @@ -839,11 +840,20 @@ FCommandLine::~FCommandLine () } } +void FCommandLine::Shift() +{ + // Only valid after _argv has been filled. + for (int i = 1; i < _argc; ++i) + { + _argv[i - 1] = _argv[i]; + } +} + int FCommandLine::argc () { if (_argc == -1) { - argsize = ParseCommandLine (cmd, &_argc, NULL); + argsize = ParseCommandLine (cmd, &_argc, NULL, noescapes); } return _argc; } @@ -855,7 +865,7 @@ char *FCommandLine::operator[] (int i) int count = argc(); _argv = new char *[count + (argsize+sizeof(char*)-1)/sizeof(char*)]; _argv[0] = (char *)_argv + count*sizeof(char *); - ParseCommandLine (cmd, NULL, _argv); + ParseCommandLine (cmd, NULL, _argv, noescapes); } return _argv[i]; } diff --git a/src/c_dispatch.h b/src/c_dispatch.h index f05cfd718d..3fb14b2f77 100644 --- a/src/c_dispatch.h +++ b/src/c_dispatch.h @@ -64,17 +64,19 @@ FString BuildString (int argc, char **argv); class FCommandLine { public: - FCommandLine (const char *commandline); + FCommandLine (const char *commandline, bool no_escapes = false); ~FCommandLine (); int argc (); char *operator[] (int i); const char *args () { return cmd; } + void Shift(); private: const char *cmd; int _argc; char **_argv; long argsize; + bool noescapes; }; typedef void (*CCmdRun) (FCommandLine &argv, APlayerPawn *instigator, int key); diff --git a/src/sound/music_timidity_mididevice.cpp b/src/sound/music_timidity_mididevice.cpp index a740d40528..92a0d08c76 100644 --- a/src/sound/music_timidity_mididevice.cpp +++ b/src/sound/music_timidity_mididevice.cpp @@ -623,13 +623,13 @@ FString TimidityMIDIDevice::GetStats() { dots << TEXTCOLOR_GREEN; } - if (Renderer->voice[i].envelope_increment == 0) + if (!Renderer->voice[i].eg1.env.bUpdating) { dots << "+"; } else { - dots << ('0' + Renderer->voice[i].envelope_stage); + dots << ('0' + Renderer->voice[i].eg1.gf1.stage); } } } diff --git a/src/timidity/gf1patch.h b/src/timidity/gf1patch.h new file mode 100644 index 0000000000..0457082ed4 --- /dev/null +++ b/src/timidity/gf1patch.h @@ -0,0 +1,85 @@ +/* GF1 Patch definition: */ +enum +{ + HEADER_SIZE = 12, + ID_SIZE = 10, + DESC_SIZE = 60, + RESERVED_SIZE = 40, + PATCH_HEADER_RESERVED_SIZE = 36, + LAYER_RESERVED_SIZE = 40, + PATCH_DATA_RESERVED_SIZE = 36, + INST_NAME_SIZE = 16, + ENVELOPES = 6, + MAX_LAYERS = 4 +}; +#define GF1_HEADER_TEXT "GF1PATCH110" + +#ifdef _MSC_VER +#pragma pack(push, 1) +#define GCC_PACKED +#else +#define GCC_PACKED __attribute__((__packed__)) +#endif + +struct GF1PatchHeader +{ + char Header[HEADER_SIZE]; + char GravisID[ID_SIZE]; /* Id = "ID#000002" */ + char Description[DESC_SIZE]; + BYTE Instruments; + BYTE Voices; + BYTE Channels; + WORD WaveForms; + WORD MasterVolume; + DWORD DataSize; + BYTE Reserved[PATCH_HEADER_RESERVED_SIZE]; +} GCC_PACKED; + +struct GF1InstrumentData +{ + WORD Instrument; + char InstrumentName[INST_NAME_SIZE]; + int InstrumentSize; + BYTE Layers; + BYTE Reserved[RESERVED_SIZE]; +} GCC_PACKED; + +struct GF1LayerData +{ + BYTE LayerDuplicate; + BYTE Layer; + int LayerSize; + BYTE Samples; + BYTE Reserved[LAYER_RESERVED_SIZE]; +} GCC_PACKED; + +struct GF1PatchData +{ + char WaveName[7]; + BYTE Fractions; + int WaveSize; + int StartLoop; + int EndLoop; + WORD SampleRate; + int LowFrequency; + int HighFrequency; + int RootFrequency; + SWORD Tune; + BYTE Balance; + BYTE EnvelopeRate[ENVELOPES]; + BYTE EnvelopeOffset[ENVELOPES]; + BYTE TremoloSweep; + BYTE TremoloRate; + BYTE TremoloDepth; + BYTE VibratoSweep; + BYTE VibratoRate; + BYTE VibratoDepth; + BYTE Modes; + SWORD ScaleFrequency; + WORD ScaleFactor; /* From 0 to 2048 or 0 to 2 */ + BYTE Reserved[PATCH_DATA_RESERVED_SIZE]; +} GCC_PACKED; +#ifdef _MSC_VER +#pragma pack(pop) +#endif +#undef GCC_PACKED diff --git a/src/timidity/instrum.cpp b/src/timidity/instrum.cpp index 0532bd2228..2ca40c137d 100644 --- a/src/timidity/instrum.cpp +++ b/src/timidity/instrum.cpp @@ -32,16 +32,15 @@ #include "m_swap.h" #include "files.h" #include "templates.h" +#include "gf1patch.h" namespace Timidity { extern Instrument *load_instrument_dls(Renderer *song, int drum, int bank, int instrument); -extern int openmode; - Instrument::Instrument() -: type(INST_GUS), samples(0), sample(NULL) +: samples(0), sample(NULL) { } @@ -52,7 +51,7 @@ Instrument::~Instrument() for (i = samples, sp = &(sample[0]); i != 0; i--, sp++) { - if (sp->data != NULL) + if (sp->type == INST_GUS && sp->data != NULL) { free(sp->data); } @@ -82,28 +81,6 @@ ToneBank::~ToneBank() } } - -int convert_envelope_rate(Renderer *song, BYTE rate) -{ - int r; - - r = 3 - ((rate>>6) & 0x3); - r *= 3; - r = (int)(rate & 0x3f) << r; /* 6.9 fixed point */ - - /* 15.15 fixed point. */ - return int(((r * 44100) / song->rate) * song->control_ratio) << 9; -} - -int convert_envelope_offset(BYTE offset) -{ - /* This is not too good... Can anyone tell me what these values mean? - Are they GUS-style "exponential" volumes? And what does that mean? */ - - /* 15.15 fixed point */ - return offset << (7 + 15); -} - int convert_tremolo_sweep(Renderer *song, BYTE sweep) { if (sweep == 0) @@ -268,7 +245,6 @@ failread: ip->samples = layer_data.Samples; ip->sample = (Sample *)safe_malloc(sizeof(Sample) * layer_data.Samples); memset(ip->sample, 0, sizeof(Sample) * layer_data.Samples); - ip->type = INST_GUS; for (i = 0; i < layer_data.Samples; ++i) { if (sizeof(patch_data) != fp->Read(&patch_data, sizeof(patch_data))) @@ -286,22 +262,23 @@ fail: sp->loop_start = LittleLong(patch_data.StartLoop); sp->loop_end = LittleLong(patch_data.EndLoop); sp->sample_rate = LittleShort(patch_data.SampleRate); - sp->low_freq = LittleLong(patch_data.LowFrequency); - sp->high_freq = LittleLong(patch_data.HighFrequency); - sp->root_freq = LittleLong(patch_data.RootFrequency); + sp->low_freq = float(LittleLong(patch_data.LowFrequency)); + sp->high_freq = float(LittleLong(patch_data.HighFrequency)) + 0.9999f; + sp->root_freq = float(LittleLong(patch_data.RootFrequency)); sp->high_vel = 127; + sp->velocity = -1; + sp->type = INST_GUS; + // Expand to SF2 range. if (panning == -1) { - sp->panning = patch_data.Balance & 0x0F; - sp->panning = (sp->panning << 3) | (sp->panning >> 1); + sp->panning = (patch_data.Balance & 0x0F) * 1000 / 15 - 500; } else { - sp->panning = panning & 0x7f; + sp->panning = (panning & 0x7f) * 1000 / 127 - 500; } - sp->panning |= sp->panning << 7; - song->compute_pan(sp->panning, sp->left_offset, sp->right_offset); + song->compute_pan((sp->panning + 500) / 1000.0, INST_GUS, sp->left_offset, sp->right_offset); /* tremolo */ if (patch_data.TremoloRate == 0 || patch_data.TremoloDepth == 0) @@ -353,6 +330,10 @@ fail: { sp->scale_factor *= 1024; } + else if (sp->scale_factor > 2048) + { + sp->scale_factor = 1024; + } if (sp->scale_factor != 1024) { cmsg(CMSG_INFO, VERB_DEBUG, " * Scale: note %d, factor %d\n", @@ -444,9 +425,9 @@ fail: for (j = 0; j < 6; j++) { - sp->envelope_rate[j] = convert_envelope_rate(song, patch_data.EnvelopeRate[j]); + sp->envelope.gf1.rate[j] = patch_data.EnvelopeRate[j]; /* [RH] GF1NEW clamps the offsets to the range [5,251], so we do too. */ - sp->envelope_offset[j] = convert_envelope_offset(clamp(patch_data.EnvelopeOffset[j], 5, 251)); + sp->envelope.gf1.offset[j] = clamp(patch_data.EnvelopeOffset[j], 5, 251); } /* Then read the sample data */ @@ -648,53 +629,62 @@ static int fill_bank(Renderer *song, int dr, int b) { if (bank->instrument[i] == MAGIC_LOAD_INSTRUMENT) { + bank->instrument[i] = NULL; bank->instrument[i] = load_instrument_dls(song, dr, b, i); if (bank->instrument[i] != NULL) { continue; } - if (bank->tone[i].name.IsEmpty()) + Instrument *ip; + ip = load_instrument_font_order(song, 0, dr, b, i); + if (ip == NULL) { - cmsg(CMSG_WARNING, (b != 0) ? VERB_VERBOSE : VERB_NORMAL, - "No instrument mapped to %s %d, program %d%s\n", - (dr) ? "drum set" : "tone bank", b, i, - (b != 0) ? "" : " - this instrument will not be heard"); + if (bank->tone[i].fontbank >= 0) + { + ip = load_instrument_font(song, bank->tone[i].name, dr, b, i); + } + else + { + ip = load_instrument(song, bank->tone[i].name, + (dr) ? 1 : 0, + bank->tone[i].pan, + bank->tone[i].amp, + (bank->tone[i].note != -1) ? bank->tone[i].note : ((dr) ? i : -1), + (bank->tone[i].strip_loop != -1) ? bank->tone[i].strip_loop : ((dr) ? 1 : -1), + (bank->tone[i].strip_envelope != -1) ? bank->tone[i].strip_envelope : ((dr) ? 1 : -1), + bank->tone[i].strip_tail); + } + if (ip == NULL) + { + ip = load_instrument_font_order(song, 1, dr, b, i); + } + } + bank->instrument[i] = ip; + if (ip == NULL) + { + if (bank->tone[i].name.IsEmpty()) + { + cmsg(CMSG_WARNING, (b != 0) ? VERB_VERBOSE : VERB_NORMAL, + "No instrument mapped to %s %d, program %d%s\n", + (dr) ? "drum set" : "tone bank", b, i, + (b != 0) ? "" : " - this instrument will not be heard"); + } + else + { + cmsg(CMSG_ERROR, VERB_NORMAL, + "Couldn't load instrument %s (%s %d, program %d)\n", + bank->tone[i].name.GetChars(), + (dr) ? "drum set" : "tone bank", b, i); + } if (b != 0) { /* Mark the corresponding instrument in the default bank / drumset for loading (if it isn't already) */ - if (!dr) + if (((dr) ? drumset[0] : tonebank[0])->instrument[i] != NULL) { - if (tonebank[0]->instrument[i] != NULL) - { - tonebank[0]->instrument[i] = MAGIC_LOAD_INSTRUMENT; - } - } - else - { - if (drumset[0]->instrument[i] != NULL) - { - drumset[0]->instrument[i] = MAGIC_LOAD_INSTRUMENT; - } + ((dr) ? drumset[0] : tonebank[0])->instrument[i] = MAGIC_LOAD_INSTRUMENT; } } - bank->instrument[i] = NULL; - errors++; - } - else if (!(bank->instrument[i] = - load_instrument(song, bank->tone[i].name, - (dr) ? 1 : 0, - bank->tone[i].pan, - bank->tone[i].amp, - (bank->tone[i].note != -1) ? bank->tone[i].note : ((dr) ? i : -1), - (bank->tone[i].strip_loop != -1) ? bank->tone[i].strip_loop : ((dr) ? 1 : -1), - (bank->tone[i].strip_envelope != -1) ? bank->tone[i].strip_envelope : ((dr) ? 1 : -1), - bank->tone[i].strip_tail))) - { - cmsg(CMSG_ERROR, VERB_NORMAL, - "Couldn't load instrument %s (%s %d, program %d)\n", - bank->tone[i].name.GetChars(), - (dr) ? "drum set" : "tone bank", b, i); errors++; } } diff --git a/src/timidity/instrum_dls.cpp b/src/timidity/instrum_dls.cpp index 7900a55d9f..13a0528c2e 100644 --- a/src/timidity/instrum_dls.cpp +++ b/src/timidity/instrum_dls.cpp @@ -1121,13 +1121,14 @@ static void load_region_dls(Renderer *song, Sample *sample, DLS_Instrument *ins, DLS_Region *rgn = &ins->regions[index]; DLS_Wave *wave = &song->patches->waveList[rgn->wlnk->ulTableIndex]; + sample->type = INST_DLS; sample->self_nonexclusive = !!(rgn->header->fusOptions & F_RGN_OPTION_SELFNONEXCLUSIVE); sample->key_group = (SBYTE)rgn->header->usKeyGroup; - sample->low_freq = SDWORD(note_to_freq(rgn->header->RangeKey.usLow)); - sample->high_freq = SDWORD(note_to_freq(rgn->header->RangeKey.usHigh)); - sample->root_freq = SDWORD(note_to_freq(rgn->wsmp->usUnityNote)); - sample->low_vel = rgn->header->RangeVelocity.usLow; - sample->high_vel = rgn->header->RangeVelocity.usHigh; + sample->low_freq = note_to_freq(rgn->header->RangeKey.usLow); + sample->high_freq = note_to_freq(rgn->header->RangeKey.usHigh); + sample->root_freq = note_to_freq(rgn->wsmp->usUnityNote); + sample->low_vel = (BYTE)rgn->header->RangeVelocity.usLow; + sample->high_vel = (BYTE)rgn->header->RangeVelocity.usHigh; sample->modes = wave->format->wBitsPerSample == 8 ? PATCH_UNSIGNED : PATCH_16; sample->sample_rate = wave->format->dwSamplesPerSec; @@ -1174,25 +1175,12 @@ static void load_region_dls(Renderer *song, Sample *sample, DLS_Instrument *ins, printf("%d, Rate=%d LV=%d HV=%d Low=%d Hi=%d Root=%d Pan=%d Attack=%f Hold=%f Sustain=%d Decay=%f Release=%f\n", index, sample->sample_rate, rgn->header->RangeVelocity.usLow, rgn->header->RangeVelocity.usHigh, sample->low_freq, sample->high_freq, sample->root_freq, sample->panning, attack, hold, sustain, decay, release); */ - sample->envelope_offset[ATTACK] = to_offset(255); - sample->envelope_rate[ATTACK] = calc_rate(song, 255, sample->sample_rate, attack); - - sample->envelope_offset[HOLD] = to_offset(250); - sample->envelope_rate[HOLD] = calc_rate(song, 5, sample->sample_rate, hold); - - sample->envelope_offset[DECAY] = to_offset(sustain); - sample->envelope_rate[DECAY] = calc_rate(song, 255 - sustain, sample->sample_rate, decay); - - sample->envelope_offset[RELEASE] = to_offset(0); - sample->envelope_rate[RELEASE] = calc_rate(song, 5 + sustain, sample->sample_rate, release); - - sample->envelope_offset[RELEASEB] = to_offset(0); - sample->envelope_rate[RELEASEB] = to_offset(1); - - sample->envelope_offset[RELEASEC] = to_offset(0); - sample->envelope_rate[RELEASEC] = to_offset(1); - - sample->modes |= PATCH_NO_SRELEASE; + sample->envelope.sf2.decay_vol = 0; + sample->envelope.sf2.attack_vol = (short)attack; + sample->envelope.sf2.hold_vol = (short)hold; + sample->envelope.sf2.decay_vol = (short)decay; + sample->envelope.sf2.release_vol = (short)release; + sample->envelope.sf2.sustain_vol = (short)sustain; } sample->data_length <<= FRACTION_BITS; @@ -1236,7 +1224,6 @@ Instrument *load_instrument_dls(Renderer *song, int drum, int bank, int instrume } inst = (Instrument *)safe_malloc(sizeof(Instrument)); - inst->type = INST_DLS; inst->samples = dls_ins->header->cRegions; inst->sample = (Sample *)safe_malloc(inst->samples * sizeof(Sample)); memset(inst->sample, 0, inst->samples * sizeof(Sample)); diff --git a/src/timidity/instrum_font.cpp b/src/timidity/instrum_font.cpp new file mode 100644 index 0000000000..63410ea00e --- /dev/null +++ b/src/timidity/instrum_font.cpp @@ -0,0 +1,131 @@ +#include +#include +#include + +#include "doomdef.h" +#include "m_swap.h" +#include "templates.h" +#include "timidity.h" +#include "sf2.h" + +namespace Timidity +{ + +FontFile *Fonts; + +FontFile *ReadDLS(const char *filename, FileReader *f) +{ + return NULL; +} + +void font_freeall() +{ + FontFile *font, *next; + + for (font = Fonts; font != NULL; font = next) + { + next = font->Next; + delete font; + } + Fonts = NULL; +} + +FontFile *font_find(const char *filename) +{ + for (FontFile *font = Fonts; font != NULL; font = font->Next) + { + if (stricmp(filename, font->Filename) == 0) + { + return font; + } + } + return NULL; +} + +void font_add(const char *filename, int load_order) +{ + FontFile *font; + + font = font_find(filename); + if (font != NULL) + { + font->SetAllOrders(load_order); + } + else + { + FileReader *fp = open_filereader(filename, openmode, NULL); + + if (fp != NULL) + { + if ((font = ReadSF2(filename, fp)) || (font = ReadDLS(filename, fp))) + { + font->SetAllOrders(load_order); + } + } + } +} + +void font_remove(const char *filename) +{ + FontFile *font; + + font = font_find(filename); + if (font != NULL) + { + // Don't actually remove the font from the list, because instruments + // from it might be loaded using the %font extension. + font->SetAllOrders(255); + } +} + +void font_order(int order, int bank, int preset, int keynote) +{ + for (FontFile *font = Fonts; font != NULL; font = font->Next) + { + font->SetOrder(order, bank, preset, keynote); + } +} + +Instrument *load_instrument_font(struct Renderer *song, const char *font, int drum, int bank, int instr) +{ + FontFile *fontfile = font_find(font); + if (fontfile != NULL) + { + return fontfile->LoadInstrument(song, drum, bank, instr); + } + return NULL; +} + +Instrument *load_instrument_font_order(struct Renderer *song, int order, int drum, int bank, int instr) +{ + for (FontFile *font = Fonts; font != NULL; font = font->Next) + { + Instrument *ip = font->LoadInstrument(song, drum, bank, instr); + if (ip != NULL) + { + return ip; + } + } + return NULL; +} + +FontFile::FontFile(FString filename) +: Filename(filename) +{ + Next = Fonts; + Fonts = this; +} + +FontFile::~FontFile() +{ + for (FontFile **probe = &Fonts; *probe != NULL; probe = &(*probe)->Next) + { + if (*probe == this) + { + *probe = Next; + break; + } + } +} + +} diff --git a/src/timidity/instrum_sf2.cpp b/src/timidity/instrum_sf2.cpp new file mode 100644 index 0000000000..79a46f9117 --- /dev/null +++ b/src/timidity/instrum_sf2.cpp @@ -0,0 +1,1533 @@ +#include +#include +#include +#include + +#include "doomdef.h" +#include "m_swap.h" +#include "templates.h" +#include "timidity.h" +#include "sf2.h" +#include "files.h" + +using namespace Timidity; + +#define cindex(identifier) (BYTE)(((size_t)&((SFGenComposite *)1)->identifier - 1) / 2) + +class CIOErr {}; +class CBadForm {}; +class CBadVer {}; + +struct ListHandler +{ + DWORD ID; + void (*Parser)(SFFile *sf2, FileReader *f, DWORD chunkid, DWORD chunklen); +}; + +enum +{ + GENF_InstrOnly = 1, // Only valid at intstrument level + GENF_PresetOnly = 2, // Only valid at preset level + GENF_Range = 4, // Value is a range, + GENF_Index = 8, // Value is an index (aka unsigned) + GENF_32768_Ok = 16, // -32768 is a valid value +}; +struct GenDef +{ + short Min; + short Max; + BYTE StructIndex; + BYTE Flags; +}; + +static const GenDef GenDefs[] = +{ + /* 0 */ 0, 32767, cindex(startAddrsOffset), GENF_InstrOnly, + /* 1 */ -32768, 0, cindex(endAddrsOffset), GENF_InstrOnly, + /* 2 */ -32768, 32767, cindex(startLoopAddrsOffset), GENF_InstrOnly, + /* 3 */ -32768, 32767, cindex(endLoopAddrsOffset), GENF_InstrOnly, + /* 4 */ 0, 32767, cindex(startAddrsCoarseOffset), GENF_InstrOnly, + /* 5 */ -12000, 12000, cindex(modLfoToPitch), 0, + /* 6 */ -12000, 12000, cindex(vibLfoToPitch), 0, + /* 7 */ -12000, 12000, cindex(modEnvToPitch), 0, + /* 8 */ 1500, 13500, cindex(initialFilterFc), 0, + /* 9 */ 0, 960, cindex(initialFilterQ), 0, + /* 10 */ -12000, 12000, cindex(modLfoToFilterFc), 0, + /* 11 */ -12000, 12000, cindex(modEnvToFilterFc), 0, + /* 12 */ -32768, 0, cindex(endAddrsCoarseOffset), 0, + /* 13 */ -960, 960, cindex(modLfoToVolume), 0, + /* 14 */ 0, 0, 255 /* unused1 */, 0, + /* 15 */ 0, 1000, cindex(chorusEffectsSend), 0, + /* 16 */ 0, 1000, cindex(reverbEffectsSend), 0, + /* 17 */ -500, 500, cindex(pan), 0, + /* 18 */ 0, 0, 255 /* unused2 */, 0, + /* 19 */ 0, 0, 255 /* unused3 */, 0, + /* 20 */ 0, 0, 255 /* unused4 */, 0, + /* 21 */ -12000, 5000, cindex(delayModLFO), GENF_32768_Ok, + /* 22 */ -16000, 4500, cindex(freqModLFO), 0, + /* 23 */ -12000, 5000, cindex(delayVibLFO), GENF_32768_Ok, + /* 24 */ -16000, 4500, cindex(freqVibLFO), 0, + /* 25 */ -12000, 5000, cindex(delayModEnv), GENF_32768_Ok, + /* 26 */ -12000, 8000, cindex(attackModEnv), GENF_32768_Ok, + /* 27 */ -12000, 5000, cindex(holdModEnv), GENF_32768_Ok, + /* 28 */ -12000, 8000, cindex(decayModEnv), 0, + /* 29 */ 0, 1000, cindex(sustainModEnv), 0, + /* 30 */ -12000, 8000, cindex(releaseModEnv), 0, + /* 31 */ -1200, 1200, cindex(keynumToModEnvHold), 0, + /* 32 */ -1200, 1200, cindex(keynumToModEnvDecay), 0, + /* 33 */ -12000, 5000, cindex(delayVolEnv), GENF_32768_Ok, + /* 34 */ -12000, 8000, cindex(attackVolEnv), GENF_32768_Ok, + /* 35 */ -12000, 5000, cindex(holdVolEnv), GENF_32768_Ok, + /* 36 */ -12000, 5000, cindex(decayVolEnv), 0, + /* 37 */ 0, 1440, cindex(sustainVolEnv), 0, + /* 38 */ -12000, 8000, cindex(releaseVolEnv), 0, + /* 39 */ -1200, 1200, cindex(keynumToVolEnvHold), 0, + /* 40 */ -1200, 1200, cindex(keynumToVolEnvDecay), 0, + /* 41 */ -32768, 32767, 255 /* instrument */, GENF_Index | GENF_PresetOnly, + /* 42 */ 0, 0, 255 /* reserved1 */, 0, + /* 43 */ 0, 127, 255 /* keyRange */, GENF_Range, + /* 44 */ 0, 127, 255 /* velRange */, GENF_Range, + /* 45 */ -32768, 32767, cindex(startLoopAddrsCoarseOffset), GENF_InstrOnly, + /* 46 */ 0, 127, cindex(keynum), GENF_InstrOnly, + /* 47 */ 1, 127, cindex(velocity), GENF_InstrOnly, + /* 48 */ 0, 1440, cindex(initialAttenuation), 0, + /* 49 */ 0, 0, 255 /* reserved2 */, 0, + /* 50 */ -32768, 32767, cindex(endLoopAddrsCoarseOffset), GENF_InstrOnly, + /* 51 */ -120, 120, cindex(coarseTune), 0, + /* 52 */ -99, 99, cindex(fineTune), 0, + /* 53 */ -32768, 32767, 255 /* sampleID */, GENF_Index | GENF_InstrOnly, + /* 54 */ -32768, 32767, cindex(sampleModes), GENF_InstrOnly, + /* 55 */ 0, 0, 255 /* reserved3 */, 0, + /* 56 */ 0, 1200, cindex(scaleTuning), 0, + /* 57 */ 1, 127, cindex(exclusiveClass), GENF_InstrOnly, + /* 58 */ 0, 127, cindex(overridingRootKey), GENF_InstrOnly, +}; + +static const SFGenComposite DefaultGenerators = +{ + { 0, 127 }, // keyRange + { 0, 127 }, // velRange + { 0 }, // instrument/sampleID + 0, // modLfoToPitch + 0, // vibLfoToPitch + 0, // modEnvToPitch + 13500, // initialFilterFc + 0, // initialFilterQ + 0, // modLfoToFilterFc + 0, // modEnvToFilterFc + 0, // modLfoToVolume + 0, // chorusEffectsSend + 0, // reverbEffectsSend + 0, // pan + -12000, // delayModLFO + 0, // freqModLFO + -12000, // delayVibLFO + 0, // freqVibLFO + -12000, // delayModEnv + -12000, // attackModEnv + -12000, // holdModEnv + -12000, // decayModEnv + 0, // sustainModEnv + -12000, // releaseModEnv + 0, // keynumToModEnvHold + 0, // keynumToModEnvDecay + -12000, // delayVolEnv + -12000, // attackVolEnv + -12000, // holdVolEnv + -12000, // decayVolEnv + 0, // sustainVolEnv + -12000, // releaseVolEnv + 0, // keynumToVolEnvHold + 0, // keynumToVolEnvDecay + 0, // initialAttenuation + 0, // coarseTune + 0, // fineTune + 100, // scaleTuning + + 0, 0, // startAddrs(Coarse)Offset + 0, 0, // endAddrs(Coarse)Offset + 0, 0, // startLoop(Coarse)Offset + 0, 0, // endLoop(Coarse)Offset + -1, // keynum + -1, // velocity + 0, // sampleModes + 0, // exclusiveClass + -1 // overridingRootKey +}; + +static void ParseIfil(SFFile *sf2, FileReader *f, DWORD chunkid, DWORD chunklen); +static void ParseSmpl(SFFile *sf2, FileReader *f, DWORD chunkid, DWORD chunklen); +static void ParseSm24(SFFile *sf2, FileReader *f, DWORD chunkid, DWORD chunklen); +static void ParsePhdr(SFFile *sf2, FileReader *f, DWORD chunkid, DWORD chunklen); +static void ParseBag(SFFile *sf2, FileReader *f, DWORD chunkid, DWORD chunklen); +static void ParseMod(SFFile *sf2, FileReader *f, DWORD chunkid, DWORD chunklen); +static void ParseGen(SFFile *sf2, FileReader *f, DWORD chunkid, DWORD chunklen); +static void ParseInst(SFFile *sf2, FileReader *f, DWORD chunkid, DWORD chunklen); +static void ParseShdr(SFFile *sf2, FileReader *f, DWORD chunkid, DWORD chunklen); + +ListHandler INFOHandlers[] = +{ + { ID_ifil, ParseIfil }, + { 0 } +}; + +ListHandler SdtaHandlers[] = +{ + { ID_smpl, ParseSmpl }, + { ID_sm24, ParseSm24 }, + { 0 } +}; + +ListHandler PdtaHandlers[] = +{ + { ID_phdr, ParsePhdr }, + { ID_pbag, ParseBag }, + { ID_pmod, ParseMod }, + { ID_pgen, ParseGen }, + { ID_inst, ParseInst }, + { ID_ibag, ParseBag }, + { ID_imod, ParseMod }, + { ID_igen, ParseGen }, + { ID_shdr, ParseShdr }, + { 0 } +}; + +static double timecent_to_sec(SWORD timecent) +{ + if (timecent == -32768) + return 0; + return pow(2.0, timecent / 1200.0); +} + +static SDWORD to_offset(int offset) +{ + return (SDWORD)offset << (7+15); +} + +static SDWORD calc_rate(Renderer *song, int diff, double sec) +{ + double rate; + + if(sec < 0.006) + sec = 0.006; + if(diff == 0) + diff = 255; + diff <<= (7+15); + rate = ((double)diff / song->rate) * song->control_ratio / sec; + return (SDWORD)rate; +} + + +static inline DWORD read_id(FileReader *f) +{ + DWORD id; + if (f->Read(&id, 4) != 4) + { + throw CIOErr(); + } + return id; +} + +static inline int read_byte(FileReader *f) +{ + BYTE x; + if (f->Read(&x, 1) != 1) + { + throw CIOErr(); + } + return x; +} + +static inline int read_char(FileReader *f) +{ + SBYTE x; + if (f->Read(&x, 1) != 1) + { + throw CIOErr(); + } + return x; +} + +static inline int read_uword(FileReader *f) +{ + WORD x; + if (f->Read(&x, 2) != 2) + { + throw CIOErr(); + } + return LittleShort(x); +} + +static inline int read_sword(FileReader *f) +{ + SWORD x; + if (f->Read(&x, 2) != 2) + { + throw CIOErr(); + } + return LittleShort(x); +} + +static inline DWORD read_dword(FileReader *f) +{ + DWORD x; + if (f->Read(&x, 4) != 4) + { + throw CIOErr(); + } + return LittleLong(x); +} + +static inline void read_name(FileReader *f, char name[21]) +{ + if (f->Read(name, 20) != 20) + { + throw CIOErr(); + } + name[20] = 0; +} + +static inline void skip_chunk(FileReader *f, DWORD len) +{ + // RIFF, like IFF, adds an extra pad byte to the end of + // odd-sized chunks so that new chunks are always on even + // byte boundaries. + if (f->Seek(len + (len & 1), SEEK_CUR) != 0) + { + throw CIOErr(); + } +} + +static void check_list(FileReader *f, DWORD id, DWORD filelen, DWORD &chunklen) +{ + if (read_id(f) != ID_LIST) + { + throw CBadForm(); + } + chunklen = read_dword(f); + if (chunklen + 8 > filelen) + { + throw CBadForm(); + } + if (read_id(f) != id) + { + throw CBadForm(); + } +} + +static void ParseIfil(SFFile *sf2, FileReader *f, DWORD chunkid, DWORD chunklen) +{ + WORD major, minor; + + if (chunklen != 4) + { + throw CBadForm(); + } + + major = read_uword(f); + minor = read_uword(f); + + if (major != 2) + { + throw CBadVer(); + } + sf2->MinorVersion = minor; +} + +static void ParseLIST(SFFile *sf2, FileReader *f, DWORD chunklen, ListHandler *handlers) +{ + ListHandler *handler; + DWORD id; + DWORD len; + + chunklen -= 4; + while (chunklen > 0) + { + id = read_id(f); + len = read_dword(f); + + if (len + 8 > chunklen) + { + throw CBadForm(); + } + chunklen -= len + (len & 1) + 8; + + for (handler = handlers; handler->ID != 0; ++handler) + { + if (handler->ID == id && handler->Parser != NULL) + { + handler->Parser(sf2, f, id, len); + break; + } + } + if (handler->ID == 0) + { + // Skip unknown chunks + skip_chunk(f, len); + } + } +} + +static void ParseINFO(SFFile *sf2, FileReader *f, DWORD chunklen) +{ + sf2->MinorVersion = -1; + + ParseLIST(sf2, f, chunklen, INFOHandlers); + + if (sf2->MinorVersion < 0) + { // The ifil chunk must be present. + throw CBadForm(); + } +} + +static void ParseSdta(SFFile *sf2, FileReader *f, DWORD chunklen) +{ + ParseLIST(sf2, f, chunklen, SdtaHandlers); + if (sf2->SampleDataOffset == 0) + { + throw CBadForm(); + } + // Section 6.2, page 20: It is not clear if the extra pad byte for an + // odd chunk is supposed to be included in the chunk length field. + if (sf2->SizeSampleDataLSB != sf2->SizeSampleData && + sf2->SizeSampleDataLSB != sf2->SizeSampleData + (sf2->SizeSampleData & 1)) + { + sf2->SampleDataLSBOffset = 0; + sf2->SizeSampleDataLSB = 0; + } +} + +static void ParseSmpl(SFFile *sf2, FileReader *f, DWORD chunkid, DWORD chunklen) +{ + // Only use the first smpl chunk. (Or should we reject files with more than one?) + if (sf2->SampleDataOffset == 0) + { + if (chunklen & 1) + { // Chunk must be an even number of bytes. + throw CBadForm(); + } + sf2->SampleDataOffset = f->Tell(); + sf2->SizeSampleData = chunklen >> 1; + } + skip_chunk(f, chunklen); +} + +static void ParseSm24(SFFile *sf2, FileReader *f, DWORD chunkid, DWORD chunklen) +{ + // The sm24 chunk is ignored if the file version is < 2.04 + if (sf2->MinorVersion >= 4) + { + // Only use the first sm24 chunk. (Or should we reject files with more than one?) + if (sf2->SampleDataLSBOffset == 0) + { + sf2->SampleDataLSBOffset = f->Tell(); + sf2->SizeSampleDataLSB = chunklen; + } + } + skip_chunk(f, chunklen); +} + +static void ParsePdta(SFFile *sf2, FileReader *f, DWORD chunklen) +{ + ParseLIST(sf2, f, chunklen, PdtaHandlers); +} + +static void ParsePhdr(SFFile *sf2, FileReader *f, DWORD chunkid, DWORD chunklen) +{ + SFPreset *preset; + + // Section 7.2, page 22: + // If the PHDR sub-chunk is missing, or contains fewer than two records, + // or its size is not a multiple of 38 bytes, the file should be rejected + // as structurally unsound. + if (chunklen < 38*2 || chunklen % 38 != 0) + { + throw CBadForm(); + } + + sf2->NumPresets = chunklen / 38; + sf2->Presets = new SFPreset[sf2->NumPresets]; + preset = sf2->Presets; + + for (int i = sf2->NumPresets; i != 0; --i, ++preset) + { + read_name(f, preset->Name); + preset->Program = read_uword(f); + preset->Bank = read_uword(f); + preset->BagIndex = read_uword(f); + skip_chunk(f, 4*3); // Skip library, genre, and morphology + + // Section 7.2, page 22: + // The preset bag indices will be monotonically increasing with + // increasing preset headers. + if (preset != sf2->Presets) + { + if (preset->BagIndex < (preset - 1)->BagIndex) + { + throw CBadForm(); + } + } + } +} + +static void ParseBag(SFFile *sf2, FileReader *f, DWORD chunkid, DWORD chunklen) +{ + SFBag *bags, *bag; + WORD prev_mod = 0; + int numbags; + int i; + + // Section 7.3, page 22: + // It is always a multiple of four bytes in length, and contains one + // record for each preset zone plus one record for a terminal zone. ... + // If the PBAG sub-chunk is missing, or its size is not a multiple of + // four bytes, the file should be rejected as structurally unsound. + // Section 7.7: IBAG is the same, but substitute "instrument" for "preset". + if (chunklen & 3) + { + throw CBadForm(); + } + + numbags = chunklen >> 2; + + if (chunkid == ID_pbag) + { + if (numbags != sf2->Presets[sf2->NumPresets - 1].BagIndex + 1) + { + throw CBadForm(); + } + sf2->PresetBags = bags = new SFBag[numbags]; + sf2->NumPresetBags = numbags; + } + else + { + assert(chunkid == ID_ibag); + if (numbags != sf2->Instruments[sf2->NumInstruments - 1].BagIndex + 1) + { + throw CBadForm(); + } + sf2->InstrBags = bags = new SFBag[numbags]; + sf2->NumInstrBags = numbags; + } + + for (bag = bags, i = numbags; i != 0; --i, ++bag) + { + bag->GenIndex = read_uword(f); + WORD mod = read_uword(f); + // Section 7.3, page 22: + // If the generator or modulator indices are non-monotonic or do not + // match the size of the respective PGEN or PMOD sub-chunks, the file + // is structurally defective and should be rejected at load time. + if (bag != bags) + { + if (bag->GenIndex < (bag - 1)->GenIndex || mod < prev_mod) + { + throw CBadForm(); + } + } + prev_mod = mod; + bag->KeyRange.Lo = bag->VelRange.Lo = 0; + bag->KeyRange.Hi = bag->VelRange.Hi = 127; + bag->Target = -1; + } +} + +static void ParseMod(SFFile *sf2, FileReader *f, DWORD chunkid, DWORD chunklen) +{ + // Section 7.4, page 23: + // It [the PMOD sub-chunk] is always a multiple of ten bytes in length, + // and contains zero or more modulators plus a terminal record + if (chunklen % 10 != 0) + { + throw CBadForm(); + } + // We've checked the length, now ignore the chunk. + skip_chunk(f, chunklen); +} + +static void ParseGen(SFFile *sf2, FileReader *f, DWORD chunkid, DWORD chunklen) +{ + SFGenList *gens, *gen; + int numgens; + int i; + + // Section 7.5, page 24: + // If the PGEN sub-chunk is missing, or its size is not a multiple of + // four bytes, the file should be rejected as structurally unsound. + if (chunklen & 3) + { + throw CBadForm(); + } + numgens = chunklen >> 2; + + if (chunkid == ID_pgen) + { + // Section 7.3, page 22: + // the size of the PGEN sub-chunk in bytes will be equal to four + // times the terminal preset’s wGenNdx plus four. + if (numgens != sf2->PresetBags[sf2->NumPresetBags - 1].GenIndex + 1) + { + throw CBadForm(); + } + sf2->PresetGenerators = gens = new SFGenList[numgens]; + sf2->NumPresetGenerators = numgens; + } + else + { + assert(chunkid == ID_igen); + if (numgens != sf2->InstrBags[sf2->NumInstrBags - 1].GenIndex + 1) + { + throw CBadForm(); + } + sf2->InstrGenerators = gens = new SFGenList[numgens]; + sf2->NumInstrGenerators = numgens; + } + + for (i = numgens, gen = gens; i != 0; --i, ++gen) + { + gen->Oper = read_uword(f); + gen->uAmount = read_uword(f); +#ifdef WORDS_BIGENDIAN + if (gen->Oper == GEN_keyRange || gen->Oper == GEN_velRange) + { + // Reswap range generators + gen->uAmount = LittleShort(gen->uAmount); + } +#endif + } +} + +static void ParseInst(SFFile *sf2, FileReader *f, DWORD chunkid, DWORD chunklen) +{ + int i; + SFInst *inst; + + // Section 7.6, page 25: + // If the INST sub-chunk is missing, contains fewer than two records, or its + // size is not a multiple of 22 bytes, the file should be rejected as + // structurally unsound. + if (chunklen < 22*2 || chunklen % 22 != 0) + { + throw CBadForm(); + } + + sf2->NumInstruments = chunklen / 22; + sf2->Instruments = inst = new SFInst[sf2->NumInstruments]; + for (i = sf2->NumInstruments; i != 0; --i, ++inst) + { + read_name(f, inst->Name); + inst->BagIndex = read_uword(f); + + // Section 7.6, page 25: + // If the instrument bag indices are non-monotonic or if the terminal + // instrument’s wInstBagNdx does not match the IBAG sub-chunk size, the + // file is structurally defective and should be rejected at load time. + if (inst != sf2->Instruments) + { + if (inst->BagIndex < (inst - 1)->BagIndex) + { + throw CBadForm(); + } + } + } +} + +static void ParseShdr(SFFile *sf2, FileReader *f, DWORD chunkid, DWORD chunklen) +{ + int i; + SFSample *sample; + + // Section 7.10, page 29: + // If the SHDR sub-chunk is missing, or its is size is not a multiple of 46 + // bytes the file should be rejected as structurally unsound. + if (chunklen % 46 != 0) + { + throw CBadForm(); + } + + sf2->NumSamples = chunklen / 46; + sf2->Samples = sample = new SFSample[sf2->NumSamples]; + for (i = sf2->NumSamples; i != 0; --i, ++sample) + { + sample->InMemoryData = NULL; + read_name(f, sample->Name); + sample->Start = read_dword(f); + sample->End = read_dword(f); + sample->StartLoop = read_dword(f); + sample->EndLoop = read_dword(f); + sample->SampleRate = read_dword(f); + sample->OriginalPitch = read_byte(f); + sample->PitchCorrection = read_char(f); + sample->SampleLink = read_uword(f); + sample->SampleType = read_uword(f); + + if (sample->SampleRate == 0) + { + // Section 7.10, page 29: + // A value of zero is illegal. If an illegal or impractical value is + // encountered, the nearest practical value should be used. + sample->SampleRate = 400; + } + if (sample->OriginalPitch > 127) + { + // Section 7.10, page 29: + // For unpitched sounds, a conventional value of 255 should be used + // Values between 128 and 254 are illegal. Whenever an illegal value + // or a value of 255 is encountered, the value 60 should be used. + sample->OriginalPitch = 60; + } + + // Clamp sample positions to the available sample data. + sample->Start = MIN(sample->Start, sf2->SizeSampleData - 1); + sample->End = MIN(sample->End, sf2->SizeSampleData - 1); + sample->StartLoop = MIN(sample->StartLoop, sf2->SizeSampleData - 1); + sample->EndLoop = MIN(sample->EndLoop, sf2->SizeSampleData - 1); + + if (sample->Start >= sample->End) + { + sample->SampleType |= SFST_Bad; + } + } +} + + +SFFile *ReadSF2(const char *filename, FileReader *f) +{ + SFFile *sf2 = NULL; + DWORD filelen; + DWORD chunklen; + + try + { + // Read RIFF sfbk header + if (read_id(f) != ID_RIFF) + { + return NULL; + } + filelen = read_dword(f); + if (read_id(f) != ID_sfbk) + { + return NULL; + } + filelen -= 4; + + // First chunk must be an INFO LIST + check_list(f, ID_INFO, filelen, chunklen); + + sf2 = new SFFile(filename); + + ParseINFO(sf2, f, chunklen); + filelen -= chunklen + 8; + + // Second chunk must be a sdta LIST + check_list(f, ID_sdta, filelen, chunklen); + ParseSdta(sf2, f, chunklen); + + // Third chunk must be a pdta LIST + check_list(f, ID_pdta, filelen, chunklen); + ParsePdta(sf2, f, chunklen); + + // There should be no more chunks. If there are, we'll just ignore them rather than reject the file. + if (!sf2->FinalStructureTest()) + { + throw CBadForm(); + } + + sf2->CheckBags(); + sf2->TranslatePercussions(); + + return sf2; + } + catch (CIOErr) + { + Printf("Error reading %s: %s\n", filename, strerror(errno)); + } + catch (CBadForm) + { + Printf("%s is corrupted.\n", filename); + } + catch (CBadVer) + { + Printf("%s is not a SoundFont version 2 file.\n", filename); + } + if (sf2 != NULL) + { + delete sf2; + } + return NULL; +} + +SFFile::SFFile(FString filename) +: FontFile(filename) +{ + Presets = NULL; + PresetBags = NULL; + PresetGenerators = NULL; + Instruments = NULL; + InstrBags = NULL; + InstrGenerators = NULL; + Samples = NULL; + MinorVersion = 0; + SampleDataOffset = 0; + SampleDataLSBOffset = 0; + SizeSampleData = 0; + SizeSampleDataLSB = 0; + NumPresets = 0; + NumPresetBags = 0; + NumPresetGenerators = 0; + NumInstruments = 0; + NumInstrBags = 0; + NumInstrGenerators = 0; + NumSamples = 0; +} + +SFFile::~SFFile() +{ + if (Presets != NULL) + { + delete[] Presets; + } + if (PresetBags != NULL) + { + delete[] PresetBags; + } + if (PresetGenerators != NULL) + { + delete[] PresetGenerators; + } + if (Instruments != NULL) + { + delete[] Instruments; + } + if (InstrBags != NULL) + { + delete[] InstrBags; + } + if (InstrGenerators != NULL) + { + delete[] InstrGenerators; + } + if (Samples != NULL) + { + for (int i = 0; i < NumSamples; ++i) + { + if (Samples[i].InMemoryData != NULL) + { + delete[] Samples[i].InMemoryData; + } + } + } +} + +bool SFFile::FinalStructureTest() +{ + // All required chunks must be present. + if (Presets == NULL || PresetBags == NULL || PresetGenerators == NULL || + Instruments == NULL || InstrBags == NULL || InstrGenerators == NULL || + Samples == NULL) + { + return false; + } + // What good is it if it has no sample data? + if (SizeSampleData == 0) + { + return false; + } + return true; +} + +void SFFile::SetOrder(int order, int drum, int bank, int program) +{ + if (drum) + { + for (int i = 0; i < NumPresets; ++i) + { + if (Percussion[i].Generators.drumset == bank && Percussion[i].Generators.key == program) + { + Percussion[i].LoadOrder = order; + } + } + } + else + { + for (int i = 0; i < NumPresets; ++i) + { + if (Presets[i].Program == program && Presets[i].Bank == bank) + { + Presets[i].LoadOrder = order; + } + } + } +} + +void SFFile::SetAllOrders(int order) +{ + for (int i = 0; i < NumPresets; ++i) + { + Presets[i].LoadOrder = order; + } + for (unsigned int i = 0; i < Percussion.Size(); ++i) + { + Percussion[i].LoadOrder = order; + } +} + +Instrument *SFFile::LoadInstrument(Renderer *song, int drum, int bank, int program) +{ + return LoadInstrumentOrder(song, -1, drum, bank, program); +} + +Instrument *SFFile::LoadInstrumentOrder(Renderer *song, int order, int drum, int bank, int program) +{ + if (drum) + { + for (unsigned int i = 0; i < Percussion.Size(); ++i) + { + if ((order < 0 || Percussion[i].LoadOrder == order) && + Percussion[i].Generators.drumset == bank && + Percussion[i].Generators.key == program) + { + return LoadPercussion(song, &Percussion[i]); + } + } + } + else + { + for (int i = 0; i < NumPresets - 1; ++i) + { + if ((order < 0 || Presets[i].LoadOrder == order) && + Presets[i].Bank == bank && + Presets[i].Program == program) + { + return LoadPreset(song, &Presets[i]); + } + } + } + return NULL; +} + +//=========================================================================== +// +// SFFile :: CheckBags +// +// For all preset and instrument zones, extract the velocity and key ranges +// and instrument and sample targets. +// +//=========================================================================== + +void SFFile::CheckBags() +{ + int i; + + for (i = 0; i < NumPresets - 1; ++i) + { + if (Presets[i].BagIndex >= Presets[i + 1].BagIndex) + { // Preset is empty. + Presets[i].Bank = ~0; + } + else + { + CheckZones(Presets[i].BagIndex, Presets[i + 1].BagIndex, 0); + Presets[i].bHasGlobalZone = PresetBags[Presets[i].BagIndex].Target < 0; + } + } + for (i = 0; i < NumInstruments - 1; ++i) + { + if (Instruments[i].BagIndex >= Instruments[i + 1].BagIndex) + { // Instrument is empty. + } + else + { + CheckZones(Instruments[i].BagIndex, Instruments[i + 1].BagIndex, 1); + Instruments[i].bHasGlobalZone = InstrBags[Instruments[i].BagIndex].Target < 0; + } + } +} + +//=========================================================================== +// +// SFFile :: CheckZones +// +// For every zone in the bag, extract the velocity and key ranges and +// instrument and sample targets. +// +//=========================================================================== + +void SFFile::CheckZones(int start, int stop, bool instr) +{ + SFBag *bag; + SFGenList *gens; + SFGenerator terminal_gen; + int zone_start, zone_stop; + int i, j; + + if (!instr) + { + bag = PresetBags; + gens = PresetGenerators; + terminal_gen = GEN_instrument; + } + else + { + bag = InstrBags; + gens = InstrGenerators; + terminal_gen = GEN_sampleID; + } + for (i = start; i < stop; ++i) + { + zone_start = bag[i].GenIndex; + zone_stop = bag[i + 1].GenIndex; + + if (zone_start > zone_stop) + { + // Skip empty zones, and mark them inaccessible. + bag[i].KeyRange.Lo = 255; + bag[i].KeyRange.Hi = 255; + bag[i].VelRange.Lo = 255; + bag[i].VelRange.Hi = 255; + continue; + } + + // According to the specs, if keyRange is present, it must be the first generator. + // If velRange is present, it may only be preceded by keyRange. In real life, there + // exist Soundfonts that violate this rule, so we need to scan every generator. + + // Preload ranges from the global zone. + if (i != start && bag[start].Target < 0) + { + bag[i].KeyRange = bag[start].KeyRange; + bag[i].VelRange = bag[start].VelRange; + } + for (j = zone_start; j < zone_stop; ++j) + { + if (gens[j].Oper == GEN_keyRange) + { + bag[i].KeyRange = gens[j].Range; + } + else if (gens[j].Oper == GEN_velRange) + { + bag[i].VelRange = gens[j].Range; + } + else if (gens[j].Oper == terminal_gen) + { + if (terminal_gen == GEN_instrument && gens[j].uAmount < NumInstruments - 1) + { + bag[i].Target = gens[j].uAmount; + } + else if (terminal_gen == GEN_sampleID && gens[j].uAmount < NumSamples - 1) + { + bag[i].Target = gens[j].uAmount; + } + break; + } + } + if (bag[i].Target < 0 && i != start) + { + // Only the first zone may be targetless. If any other zones are, + // make them inaccessible. + bag[i].KeyRange.Lo = 255; + bag[i].KeyRange.Hi = 255; + bag[i].VelRange.Lo = 255; + bag[i].VelRange.Hi = 255; + } + + // Check for swapped ranges. (Should we fix them or ignore them?) + if (bag[i].KeyRange.Lo > bag[i].KeyRange.Hi) + { + swap(bag[i].KeyRange.Lo, bag[i].KeyRange.Hi); + } + if (bag[i].VelRange.Lo > bag[i].VelRange.Hi) + { + swap(bag[i].VelRange.Lo, bag[i].VelRange.Hi); + } + } +} + +//=========================================================================== +// +// SFFile :: TranslatePercussions +// +// For every percussion instrument, compile a set of composite generators +// for each key, to make creating TiMidity instruments for individual +// percussion parts easier. +// +//=========================================================================== + +void SFFile::TranslatePercussions() +{ + for (int i = 0; i < NumPresets - 1; ++i) + { + if (Presets[i].Bank == 128 && Presets[i].Program < 128) + { + TranslatePercussionPreset(&Presets[i]); + } + } +} + +//=========================================================================== +// +// SFFile :: TranslatePercussionPreset +// +// Compile a set of composite generators for each key of this percussion +// instrument. Note that one instrument is actually an entire drumset. +// +//=========================================================================== + +void SFFile::TranslatePercussionPreset(SFPreset *preset) +{ + SFPerc perc; + int i; + bool has_global; + + perc.LoadOrder = preset->LoadOrder; + i = preset->BagIndex; + has_global = false; + + for (i = preset->BagIndex; i < (preset + 1)->BagIndex; ++i) + { + if (PresetBags[i].Target < 0) + { // This preset zone has no instrument. + continue; + } + if (PresetBags[i].KeyRange.Lo > 127 || PresetBags[i].VelRange.Lo > 127) + { // This preset zone is inaccesible. + continue; + } + TranslatePercussionPresetZone(preset, &PresetBags[i]); + } +} + +//=========================================================================== +// +// SFFile :: TranslatePercussionPresetZone +// +// Create a composite generator set for all keys and velocity ranges in this +// preset zone that intersect with this zone's instrument. +// +//=========================================================================== + +void SFFile::TranslatePercussionPresetZone(SFPreset *preset, SFBag *pzone) +{ + int key, i; + + for (key = pzone->KeyRange.Lo; key <= pzone->KeyRange.Hi; ++key) + { + SFInst *inst = &Instruments[pzone->Target]; + for (i = inst->BagIndex; i < (inst + 1)->BagIndex; ++i) + { + if (InstrBags[i].Target < 0) + { // This instrument zone has no sample. + continue; + } + if (InstrBags[i].KeyRange.Lo > key || InstrBags[i].KeyRange.Hi < key) + { // This instrument zone does not contain the key we want. + continue; + } + if (InstrBags[i].VelRange.Lo > pzone->VelRange.Hi || + InstrBags[i].VelRange.Hi < pzone->VelRange.Lo) + { // This instrument zone does not intersect the current velocity range. + continue; + } + // An intersection! Add the composite generator for this key and velocity range. + SFPerc perc; + perc.LoadOrder = preset->LoadOrder; + perc.Preset = preset; + perc.Generators = DefaultGenerators; + if (inst->bHasGlobalZone) + { + SetInstrumentGenerators(&perc.Generators, InstrBags[inst->BagIndex].GenIndex, InstrBags[inst->BagIndex + 1].GenIndex); + } + SetInstrumentGenerators(&perc.Generators, InstrBags[i].GenIndex, InstrBags[i + 1].GenIndex); + AddPresetGenerators(&perc.Generators, pzone->GenIndex, (pzone + 1)->GenIndex, preset); + perc.Generators.drumset = (BYTE)preset->Program; + perc.Generators.key = key; + perc.Generators.velRange.Lo = MAX(pzone->VelRange.Lo, InstrBags[i].VelRange.Lo); + perc.Generators.velRange.Hi = MIN(pzone->VelRange.Hi, InstrBags[i].VelRange.Hi); + perc.Generators.sampleID = InstrBags[i].Target; + Percussion.Push(perc); + } + } +} + +void SFFile::SetInstrumentGenerators(SFGenComposite *composite, int start, int stop) +{ + // Proceed from first to last; later generators override earlier ones. + SFGenList *gen = &InstrGenerators[start]; + for (int i = stop - start; i != 0; --i, ++gen) + { + if (gen->Oper >= GEN_NumGenerators) + { // Unknown generator. + continue; + } + if (GenDefs[gen->Oper].StructIndex >= sizeof(SFGenComposite)/2) + { // Generator is either unused or ignored. + continue; + } + // Set the generator + ((WORD *)composite)[GenDefs[gen->Oper].StructIndex] = gen->uAmount; + if (gen->Oper == GEN_sampleID) + { // Anything past sampleID is ignored. + break; + } + } +} + +void SFFile::AddPresetGenerators(SFGenComposite *composite, int start, int stop, SFPreset *preset) +{ + bool gen_set[GEN_NumGenerators] = { false, }; + AddPresetGenerators(composite, start, stop, gen_set); + if (preset->bHasGlobalZone) + { + AddPresetGenerators(composite, PresetBags[preset->BagIndex].GenIndex, PresetBags[preset->BagIndex + 1].GenIndex, gen_set); + } +} + +void SFFile::AddPresetGenerators(SFGenComposite *composite, int start, int stop, bool gen_set[GEN_NumGenerators]) +{ + // Proceed from last to first; later generators override earlier ones. + SFGenList *gen = &PresetGenerators[stop - 1]; + const GenDef *def; + + for (int i = stop - start; i != 0; --i, --gen) + { + if (gen->Oper >= GEN_NumGenerators) + { // Unknown generator. + continue; + } + if (gen_set[gen->Oper]) + { // Generator was already set. + continue; + } + def = &GenDefs[gen->Oper]; + if (def->StructIndex >= sizeof(SFGenComposite)/2) + { // Generator is either unused or ignored. + continue; + } + if (def->Flags & GENF_InstrOnly) + { // Generator is not valid at the preset level. + continue; + } + // Add to instrument/default generator. + int added = ((SWORD *)composite)[def->StructIndex] + gen->Amount; + // Clamp to proper range. + if (added <= -32768 && def->Flags & GENF_32768_Ok) + { + added = -32768; + } + else + { + added = clamp(added, def->Min, def->Max); + } + ((SWORD *)composite)[def->StructIndex] = added; + gen_set[gen->Oper] = true; + if (gen->Oper == GEN_instrument) + { // Anything past the instrument generator is ignored. + break; + } + } +} + +Instrument *SFFile::LoadPercussion(Renderer *song, SFPerc *perc) +{ + unsigned int i; + int drumkey; + int drumset; + int j; + + Instrument *ip = new Instrument; + ip->samples = 0; + drumkey = perc->Generators.key; + drumset = perc->Generators.drumset; + + // Count all percussion composites that match this one's key and set. + for (i = 0; i < Percussion.Size(); ++i) + { + if (Percussion[i].Generators.key == drumkey && + Percussion[i].Generators.drumset == drumset && + Percussion[i].Generators.sampleID < NumSamples) + { + SFSample *sfsamp = &Samples[Percussion[i].Generators.sampleID]; + if (sfsamp->InMemoryData == NULL) + { + LoadSample(sfsamp); + } + if (sfsamp->InMemoryData != NULL) + { + ip->samples++; + } + } + } + if (ip->samples == 0) + { // Nothing here to play. + delete ip; + return NULL; + } + ip->sample = (Sample *)safe_malloc(sizeof(Sample) * ip->samples); + memset(ip->sample, 0, sizeof(Sample) * ip->samples); + + // Fill in Sample structure for each composite. + for (j = 0, i = 0; i < Percussion.Size(); ++i) + { + SFPerc *zone = &Percussion[i]; + SFGenComposite *gen = &zone->Generators; + if (gen->key != drumkey || + gen->drumset != drumset || + gen->sampleID >= NumSamples) + { + continue; + } + SFSample *sfsamp = &Samples[gen->sampleID]; + if (sfsamp->InMemoryData == NULL) + { + continue; + } + Sample *sp = ip->sample + j++; + + // Set velocity range + sp->low_vel = gen->velRange.Lo; + sp->high_vel = gen->velRange.Hi; + + // Set frequency range + sp->low_freq = note_to_freq(gen->key); + sp->high_freq = sp->low_freq; + + ApplyGeneratorsToRegion(gen, sfsamp, song, sp); + } + assert(j == ip->samples); + return ip; +} + +//=========================================================================== +// +// SFFile :: LoadPreset +// +//=========================================================================== + +Instrument *SFFile::LoadPreset(Renderer *song, SFPreset *preset) +{ + SFInst *inst; + SFSample *sfsamp; + SFGenComposite gen; + int i, j, k; + + Instrument *ip = new Instrument; + ip->samples = 0; + + // Count the number of regions we'll need. + for (i = preset->BagIndex; i < (preset + 1)->BagIndex; ++i) + { + if (PresetBags[i].Target < 0) + { // Preset zone has no instrument. + continue; + } + inst = &Instruments[PresetBags[i].Target]; + for (j = inst->BagIndex; j < (inst + 1)->BagIndex; ++j) + { + if (InstrBags[j].Target < 0) + { // Instrument zone has no sample. + continue; + } + if (InstrBags[j].KeyRange.Lo <= PresetBags[i].KeyRange.Hi && + InstrBags[j].KeyRange.Hi >= PresetBags[i].KeyRange.Lo && + InstrBags[j].VelRange.Lo <= PresetBags[i].VelRange.Hi && + InstrBags[j].VelRange.Hi >= PresetBags[i].VelRange.Lo) + { // The preset and instrument zones intersect! + sfsamp = &Samples[InstrBags[j].Target]; + if (sfsamp->InMemoryData == NULL) + { + LoadSample(sfsamp); + } + if (sfsamp->InMemoryData != NULL) + { + ip->samples++; + } + } + } + } + if (ip->samples == 0) + { // Nothing here to play. + delete ip; + return NULL; + } + // Allocate the regions and define them + ip->sample = (Sample *)safe_malloc(sizeof(Sample) * ip->samples); + memset(ip->sample, 0, sizeof(Sample) * ip->samples); + k = 0; + for (i = preset->BagIndex; i < (preset + 1)->BagIndex; ++i) + { + if (PresetBags[i].Target < 0) + { // Preset zone has no instrument. + continue; + } + inst = &Instruments[PresetBags[i].Target]; + for (j = inst->BagIndex; j < (inst + 1)->BagIndex; ++j) + { + if (InstrBags[j].Target < 0) + { // Instrument zone has no sample. + continue; + } + if (InstrBags[j].KeyRange.Lo <= PresetBags[i].KeyRange.Hi && + InstrBags[j].KeyRange.Hi >= PresetBags[i].KeyRange.Lo && + InstrBags[j].VelRange.Lo <= PresetBags[i].VelRange.Hi && + InstrBags[j].VelRange.Hi >= PresetBags[i].VelRange.Lo) + { // The preset and instrument zones intersect! + sfsamp = &Samples[InstrBags[j].Target]; + if (sfsamp->InMemoryData == NULL) + { + continue; + } + Sample *sp = ip->sample + k++; + + // Set velocity range + sp->low_vel = MAX(InstrBags[j].VelRange.Lo, PresetBags[i].VelRange.Lo); + sp->high_vel = MIN(InstrBags[j].VelRange.Hi, PresetBags[i].VelRange.Hi); + + // Set frequency range + sp->low_freq = note_to_freq(MAX(InstrBags[j].KeyRange.Lo, PresetBags[i].KeyRange.Lo)); + sp->high_freq = note_to_freq(MIN(InstrBags[j].KeyRange.Hi, PresetBags[i].KeyRange.Hi)); + + gen = DefaultGenerators; + if (inst->bHasGlobalZone) + { + SetInstrumentGenerators(&gen, InstrBags[inst->BagIndex].GenIndex, InstrBags[inst->BagIndex + 1].GenIndex); + } + SetInstrumentGenerators(&gen, InstrBags[j].GenIndex, InstrBags[j + 1].GenIndex); + AddPresetGenerators(&gen, PresetBags[i].GenIndex, PresetBags[i + 1].GenIndex, preset); + ApplyGeneratorsToRegion(&gen, sfsamp, song, sp); + } + } + } + assert(k == ip->samples); + return ip; +} + +//=========================================================================== +// +// SFFile :: ApplyGeneratorsToRegion +// +// The caller must set the key and velocity ranges. Other information for +// the TiMidity sample will be filled in using the generators given. +// +// FIXME: At least try to do something useful with every parameter. +// +//=========================================================================== + +void SFFile::ApplyGeneratorsToRegion(SFGenComposite *gen, SFSample *sfsamp, Renderer *song, Sample *sp) +{ + sp->type = INST_SF2; + + // Set loop and sample points + int start, end; + start = gen->startAddrsOffset + gen->startAddrsCoarseOffset * 32768; + end = gen->endAddrsOffset + gen->endAddrsCoarseOffset * 32768; + start = MAX(sfsamp->Start, sfsamp->Start + start); + end = MIN(sfsamp->End, sfsamp->End + end); + sp->loop_start = MAX(start, sfsamp->StartLoop + gen->startLoopAddrsOffset + gen->startLoopAddrsCoarseOffset * 32768); + sp->loop_end = MIN(end, sfsamp->EndLoop + gen->endLoopAddrsOffset + gen->endLoopAddrsCoarseOffset * 32768); + + sp->loop_start = (sp->loop_start - start) << FRACTION_BITS; + sp->loop_end = (sp->loop_end - start) << FRACTION_BITS; + sp->data_length = (end - start) << FRACTION_BITS; + sp->data = sfsamp->InMemoryData + start - sfsamp->Start; + if (gen->overridingRootKey >= 0 && gen->overridingRootKey <= 127) + { + sp->scale_note = gen->overridingRootKey; + } + else + { + sp->scale_note = sfsamp->OriginalPitch; + } + sp->root_freq = note_to_freq(sp->scale_note); + sp->sample_rate = sfsamp->SampleRate; + sp->key_group = gen->exclusiveClass; + sp->volume = 1; + + // Set key scaling + if (gen->keynum >= 0 && gen->keynum <= 127) + { + sp->scale_note = gen->keynum; + sp->scale_factor = 0; + } + else if (gen->scaleTuning >= 0) + { + sp->scale_factor = gen->scaleTuning * 1024 / 100; + // Does the root key also serve as the scale key? Assuming it does here. + } + else + { + sp->scale_factor = 1024; + sp->scale_note = 60; + } + + // Set panning + sp->panning = gen->pan; + + // Set volume envelope + sp->envelope.sf2.delay_vol = gen->delayVolEnv; + sp->envelope.sf2.attack_vol = gen->attackVolEnv; + sp->envelope.sf2.hold_vol = gen->holdVolEnv; + sp->envelope.sf2.decay_vol = gen->decayVolEnv; + sp->envelope.sf2.sustain_vol = gen->sustainVolEnv; + sp->envelope.sf2.release_vol = gen->releaseVolEnv; + + // Set sample modes + if (gen->sampleModes == 1) + { + sp->modes = PATCH_LOOPEN | PATCH_SUSTAIN | PATCH_NO_SRELEASE; + } + else if (gen->sampleModes == 3) + { + sp->modes = PATCH_LOOPEN | PATCH_SUSTAIN; + } + else + { + sp->modes = PATCH_SUSTAIN; + } + + // Set tuning (in cents) + sp->tune = gen->coarseTune * 100 + gen->fineTune; + + sp->velocity = (SBYTE)gen->velocity; + sp->initial_attenuation = gen->initialAttenuation; +} + +//=========================================================================== +// +// SFFile :: LoadSample +// +// Loads a sample's data and converts it from 16/24-bit to floating point. +// +//=========================================================================== + +void SFFile::LoadSample(SFSample *sample) +{ + FileReader *fp = open_filereader(Filename, openmode, NULL); + DWORD i; + + if (fp == NULL) + { + return; + } + sample->InMemoryData = new float[sample->End - sample->Start + 1]; + fp->Seek(SampleDataOffset + sample->Start * 2, SEEK_SET); + // Load 16-bit sample data. + for (i = 0; i < sample->End - sample->Start; ++i) + { + SWORD samp; + *fp >> samp; + sample->InMemoryData[i] = samp / 32768.f; + } + if (SampleDataLSBOffset != 0) + { // Load lower 8 bits of 24-bit sample data. + fp->Seek(SampleDataLSBOffset + sample->Start, SEEK_SET); + for (i = 0; i < sample->End - sample->Start; ++i) + { + BYTE samp; + *fp >> samp; + sample->InMemoryData[i] = ((((SDWORD(sample->InMemoryData[i] * 32768) << 8) | samp) << 8) >> 8) / 8388608.f; + } + } + // Final 0 byte is for interpolation. + sample->InMemoryData[i] = 0; + delete fp; +} diff --git a/src/timidity/mix.cpp b/src/timidity/mix.cpp index cd0443a7ae..e79dc5f071 100644 --- a/src/timidity/mix.cpp +++ b/src/timidity/mix.cpp @@ -31,68 +31,117 @@ namespace Timidity { -/* Returns 1 if envelope runs out */ -int recompute_envelope(Voice *v) +static int convert_envelope_rate(Renderer *song, BYTE rate) { - int stage; + int r; - stage = v->envelope_stage; + r = 3 - ((rate>>6) & 0x3); + r *= 3; + r = (int)(rate & 0x3f) << r; /* 6.9 fixed point */ - if (stage >= ENVELOPES) + /* 15.15 fixed point. */ + return int(((r * 44100) / song->rate) * song->control_ratio) << 9; +} + +void Envelope::Init(Renderer *song, Voice *v) +{ + Type = v->sample->type; + env.bUpdating = true; + if (Type == INST_GUS) + { + gf1.Init(song, v); + gf1.ApplyToAmp(v); + } + else + { + sf2.Init(song, v); + sf2.ApplyToAmp(v); + } +} + +void GF1Envelope::Init(Renderer *song, Voice *v) +{ + /* Ramp up from 0 */ + stage = 0; + volume = 0; + + for (int i = 0; i < 6; ++i) + { + offset[i] = v->sample->envelope.gf1.offset[i] << (7 + 15); + rate[i] = convert_envelope_rate(song, v->sample->envelope.gf1.rate[i]); + } + Recompute(v); +} + +void GF1Envelope::Release(Voice *v) +{ + if (!(v->sample->modes & PATCH_NO_SRELEASE) || (v->sample->modes & PATCH_FAST_REL)) + { + /* ramp out to minimum volume with rate from final release stage */ + stage = GF1_RELEASEC+1; + target = 0; + increment = -rate[GF1_RELEASEC]; + } + else if (v->sample->modes & PATCH_SUSTAIN) + { + if (stage < GF1_RELEASE) + { + stage = GF1_RELEASE; + } + Recompute(v); + } + bUpdating = true; +} + +/* Returns 1 if envelope runs out */ +bool GF1Envelope::Recompute(Voice *v) +{ + int oldstage; + + oldstage = stage; + + if (oldstage > GF1_RELEASEC) { /* Envelope ran out. */ /* play sampled release */ v->status &= ~(VOICE_SUSTAINING | VOICE_LPE); v->status |= VOICE_RELEASING; - v->envelope_increment = 0; + increment = 0; + bUpdating = false; return 0; } - if (stage == RELEASE && !(v->status & VOICE_RELEASING) && (v->sample->modes & PATCH_SUSTAIN)) + if (oldstage == GF1_RELEASE && !(v->status & VOICE_RELEASING) && (v->sample->modes & PATCH_SUSTAIN)) { v->status |= VOICE_SUSTAINING; /* Freeze envelope until note turns off. Trumpets want this. */ - v->envelope_increment = 0; + increment = 0; + bUpdating = false; } else { - v->envelope_stage = stage + 1; + stage = oldstage + 1; - if (v->envelope_volume == v->sample->envelope_offset[stage]) + if (volume == offset[oldstage]) { - return recompute_envelope(v); + return Recompute(v); } - v->envelope_target = v->sample->envelope_offset[stage]; - v->envelope_increment = v->sample->envelope_rate[stage]; - if (v->envelope_target < v->envelope_volume) - v->envelope_increment = -v->envelope_increment; + target = offset[oldstage]; + increment = rate[oldstage]; + if (target < volume) + increment = -increment; } return 0; } -void apply_envelope_to_amp(Voice *v) +bool GF1Envelope::Update(Voice *v) { - float env_vol = v->attenuation; - float final_amp = v->sample->volume * FINAL_MIX_SCALE; - if (v->tremolo_phase_increment != 0) + volume += increment; + if (((increment < 0) && (volume <= target)) || ((increment > 0) && (volume >= target))) { - env_vol *= v->tremolo_volume; - } - env_vol *= v->envelope_volume / float(1 << 30); - // Note: The pan offsets are negative. - v->left_mix = MAX(0.f, (float)calc_gf1_amp(env_vol + v->left_offset) * final_amp); - v->right_mix = MAX(0.f, (float)calc_gf1_amp(env_vol + v->right_offset) * final_amp); -} - -static int update_envelope(Voice *v) -{ - v->envelope_volume += v->envelope_increment; - if (((v->envelope_increment < 0) && (v->envelope_volume <= v->envelope_target)) || - ((v->envelope_increment > 0) && (v->envelope_volume >= v->envelope_target))) - { - v->envelope_volume = v->envelope_target; - if (recompute_envelope(v)) + volume = target; + if (Recompute(v)) { return 1; } @@ -100,6 +149,214 @@ static int update_envelope(Voice *v) return 0; } +void GF1Envelope::ApplyToAmp(Voice *v) +{ + double env_vol = v->attenuation; + double final_amp = v->sample->volume * FINAL_MIX_SCALE; + if (v->tremolo_phase_increment != 0) + { // [RH] FIXME: This is wrong. Tremolo should offset the + // envelope volume, not scale it. + env_vol *= v->tremolo_volume; + } + env_vol *= volume / float(1 << 30); + env_vol = calc_gf1_amp(env_vol) * final_amp; + v->left_mix = float(env_vol * v->left_offset); + v->right_mix = float(env_vol * v->right_offset); +} + +void SF2Envelope::Init(Renderer *song, Voice *v) +{ + stage = 0; + volume = 0; + DelayTime = v->sample->envelope.sf2.delay_vol; + AttackTime = v->sample->envelope.sf2.attack_vol; + HoldTime = v->sample->envelope.sf2.hold_vol; + DecayTime = v->sample->envelope.sf2.decay_vol; + SustainLevel = v->sample->envelope.sf2.sustain_vol; + ReleaseTime = v->sample->envelope.sf2.release_vol; + SampleRate = song->rate; + HoldStart = 0; + RateMul = song->control_ratio / song->rate; + RateMul_cB = RateMul * 960; + bUpdating = true; +} + +void SF2Envelope::Release(Voice *v) +{ + if (stage == SF2_ATTACK) + { + // The attack stage does not use an attenuation in cB like all the rest. + volume = log10(volume) * -200; + } + stage = SF2_RELEASE; + bUpdating = true; +} + +static double timecent_to_sec(float timecent) +{ + if (timecent == -32768) + return 0; + return pow(2.0, timecent / 1200.0); +} + +static double calc_rate(double ratemul, double sec) +{ + if (sec < 0.006) + sec = 0.006; + return ratemul / sec; +} + +static void shutoff_voice(Voice *v) +{ + v->status &= ~(VOICE_SUSTAINING | VOICE_LPE); + v->status |= VOICE_RELEASING | VOICE_STOPPING; +} + +static bool check_release(double RateMul, double sec) +{ + double rate = calc_rate(960 * RateMul, sec); + + // Is release rate very fast? If so, don't do the release, but do + // the voice off ramp instead. + return (rate < 960/20); +} + +/* Returns 1 if envelope runs out */ +bool SF2Envelope::Update(Voice *v) +{ + double sec; + double newvolume; + + switch (stage) + { + case SF2_DELAY: + if (v->sample_count >= timecent_to_sec(DelayTime) * SampleRate) + { + stage = SF2_ATTACK; + return Update(v); + } + return 0; + + case SF2_ATTACK: + sec = timecent_to_sec(AttackTime); + if (sec <= 0) + { // instantaneous attack + newvolume = 1; + } + else + { + newvolume = volume + calc_rate(RateMul, sec); + } + if (newvolume >= 1) + { + volume = 0; + HoldStart = v->sample_count; + if (HoldTime <= -32768) + { // hold time is 0, so skip right to decay + stage = SF2_DECAY; + } + else + { + stage = SF2_HOLD; + } + return Update(v); + } + break; + + case SF2_HOLD: + if (v->sample_count - HoldStart >= timecent_to_sec(HoldTime) * SampleRate) + { + stage = SF2_DECAY; + return Update(v); + } + return 0; + + case SF2_DECAY: + sec = timecent_to_sec(DecayTime); + if (sec <= 0) + { // instantaneous decay + newvolume = SustainLevel; + } + else + { + newvolume = volume + calc_rate(RateMul_cB, sec); + } + if (newvolume >= SustainLevel) + { + newvolume = SustainLevel; + stage = SF2_SUSTAIN; + bUpdating = false; + if (!(v->status & VOICE_RELEASING)) + { + v->status |= VOICE_SUSTAINING; + } + } + break; + + case SF2_SUSTAIN: + // Stay here until released. + return 0; + + case SF2_RELEASE: + sec = timecent_to_sec(ReleaseTime); + if (sec <= 0) + { // instantaneous release + newvolume = 1000; + } + else + { + newvolume = volume + calc_rate(RateMul_cB, sec); + } + if (newvolume >= 960) + { + stage = SF2_FINISHED; + shutoff_voice(v); + bUpdating = false; + return 1; + } + break; + + case SF2_FINISHED: + return 1; + } + volume = (float)newvolume; + return 0; +} + +/* EMU 8k/10k don't follow spec in regards to volume attenuation. + * This factor is used in the equation pow (10.0, cb / FLUID_ATTEN_POWER_FACTOR). + * By the standard this should be -200.0. */ +#define FLUID_ATTEN_POWER_FACTOR (-531.509) +#define atten2amp(x) pow(10.0, (x) / FLUID_ATTEN_POWER_FACTOR) + +void SF2Envelope::ApplyToAmp(Voice *v) +{ + double amp; + + if (stage == SF2_DELAY) + { + v->left_mix = 0; + v->right_mix = 0; + return; + } + else if (stage == SF2_ATTACK) + { + amp = atten2amp(v->attenuation) * volume; + } + else + { + amp = atten2amp(v->attenuation) * cb_to_amp(volume); + } + amp *= FINAL_MIX_SCALE * 0.5; + v->left_mix = float(amp * v->left_offset); + v->right_mix = float(amp * v->right_offset); +} + +void apply_envelope_to_amp(Voice *v) +{ + v->eg1.ApplyToAmp(v); +} + static void update_tremolo(Voice *v) { int depth = v->sample->tremolo_depth << 7; @@ -136,7 +393,7 @@ static void update_tremolo(Voice *v) /* Returns 1 if the note died */ static int update_signal(Voice *v) { - if (v->envelope_increment != 0 && update_envelope(v)) + if (v->eg1.env.bUpdating && v->eg1.Update(v)) { return 1; } @@ -347,7 +604,7 @@ static void ramp_out(const sample_t *sp, float *lp, Voice *v, int c) /* printf("Ramping out: left=%d, c=%d, li=%d\n", left, c, li); */ - if (v->left_offset == 0) // All the way to the left + if (v->right_mix == 0) // All the way to the left { left = v->left_mix; li = -(left/c); @@ -362,7 +619,7 @@ static void ramp_out(const sample_t *sp, float *lp, Voice *v, int c) lp += 2; } } - else if (v->right_offset == 0) // All the way to the right + else if (v->left_mix == 0) // All the way to the right { right = v->right_mix; ri = -(right/c); @@ -441,7 +698,7 @@ void mix_voice(Renderer *song, float *buf, Voice *v, int c) } if (v->right_mix == 0) // All the way to the left { - if (v->envelope_increment != 0 || v->tremolo_phase_increment != 0) + if (v->eg1.env.bUpdating || v->tremolo_phase_increment != 0) { mix_single_left_signal(song->control_ratio, sp, buf, v, count); } @@ -452,7 +709,7 @@ void mix_voice(Renderer *song, float *buf, Voice *v, int c) } else if (v->left_mix == 0) // All the way to the right { - if (v->envelope_increment != 0 || v->tremolo_phase_increment != 0) + if (v->eg1.env.bUpdating || v->tremolo_phase_increment != 0) { mix_single_right_signal(song->control_ratio, sp, buf, v, count); } @@ -463,7 +720,7 @@ void mix_voice(Renderer *song, float *buf, Voice *v, int c) } else // Somewhere in the middle { - if (v->envelope_increment || v->tremolo_phase_increment) + if (v->eg1.env.bUpdating || v->tremolo_phase_increment) { mix_mystery_signal(song->control_ratio, sp, buf, v, count); } @@ -472,6 +729,7 @@ void mix_voice(Renderer *song, float *buf, Voice *v, int c) mix_mystery(song->control_ratio, sp, buf, v, count); } } + v->sample_count += count; } } diff --git a/src/timidity/playmidi.cpp b/src/timidity/playmidi.cpp index 9671e21e6c..e234fdde81 100644 --- a/src/timidity/playmidi.cpp +++ b/src/timidity/playmidi.cpp @@ -31,8 +31,6 @@ namespace Timidity { -static const double log_of_2 = 0.69314718055994529; - void Renderer::reset_voices() { for (int i = 0; i < voices; i++) @@ -44,8 +42,8 @@ void Renderer::reset_voices() /* Process the Reset All Controllers event */ void Renderer::reset_controllers(int c) { - channel[c].volume = (100 << 7) | 100; - channel[c].expression = 0x3fff; + channel[c].volume = 100; + channel[c].expression = 127; channel[c].sustain = 0; channel[c].pitchbend = 0x2000; channel[c].pitchfactor = 0; /* to be computed */ @@ -68,55 +66,6 @@ void Renderer::reset_midi() reset_voices(); } -void Renderer::select_sample(int v, Instrument *ip, int vel) -{ - double f, cdiff, diff; - int s, i; - Sample *sp, *closest; - - s = ip->samples; - sp = ip->sample; - - if (s == 1) - { - voice[v].sample = sp; - return; - } - - f = voice[v].orig_frequency; - for (i = 0; i < s; i++) - { - if (sp->low_vel <= vel && sp->high_vel >= vel && - sp->low_freq <= f && sp->high_freq >= f) - { - voice[v].sample = sp; - return; - } - sp++; - } - - /* - No suitable sample found! We'll select the sample whose root - frequency is closest to the one we want. (Actually we should - probably convert the low, high, and root frequencies to MIDI note - values and compare those.) */ - - cdiff = 1e10; - closest = sp = ip->sample; - for (i = 0; i < s; i++) - { - diff = fabs(sp->root_freq - f); - if (diff < cdiff) - { - cdiff = diff; - closest = sp; - } - sp++; - } - voice[v].sample = closest; - return; -} - void Renderer::recompute_freq(int v) { Channel *ch = &channel[voice[v].channel]; @@ -210,37 +159,59 @@ void Renderer::recompute_amp(Voice *v) int chanvol = chan->volume; int chanexpr = chan->expression; - v->attenuation = (vol_table[(chanvol * chanexpr) / 2113407] * vol_table[v->velocity]) * ((127 + 64) / 12419775.f); -} - -void Renderer::compute_pan(int panning, float &left_offset, float &right_offset) -{ - // Round the left- and right-most positions to their extremes, since - // most songs only do coarse panning. - if (panning < 128) + if (v->sample->type == INST_GUS) { - panning = 0; - } - else if (panning > 127*128) - { - panning = 32767; - } - - if (panning == 0) - { - left_offset = 0; - right_offset = (float)-HUGE_VAL; - } - else if (panning == 32767) - { - left_offset = (float)-HUGE_VAL; - right_offset = 0; + v->attenuation = (vol_table[(chanvol * chanexpr) / 127] * vol_table[v->velocity]) * ((127 + 64) / 12419775.f); } else { - double pan = panning * (1 / 32767.0); - right_offset = (float)(log(pan) * (1 / (log_of_2 * 32))); - left_offset = (float)(log(1 - pan) * (1 / (log_of_2 * 32))); + // Implicit modulators from SF2 spec + double velatten, cc7atten, cc11atten; + + velatten = log10(127.0 / v->velocity); + cc7atten = log10(127.0 / chanvol); + cc11atten = log10(127.0 / chanexpr); + v->attenuation = float(400 * (velatten + cc7atten + cc11atten)) + v->sample->initial_attenuation; + } +} + +// Pan must be in the range [0,1] +void Renderer::compute_pan(double pan, int type, float &left_offset, float &right_offset) +{ + if (pan <= 0) + { + left_offset = 1; + right_offset = 0; + } + else if (pan >= 127/128.0) + { + left_offset = 0; + right_offset = 1; + } + else + { + if (type == INST_GUS) + { + /* Original amp equation looks like this: + * calc_gf1_amp(atten + offset) + * which expands to: + * 2^(16*(atten + offset) - 16) + * Keeping in mind that 2^(x + y) == 2^x * 2^y, we can + * rewrite this to avoid doing two pows in GF1Envelope::ApplyToAmp(): + * 2^(16*atten + 16*offset - 16) + * 2^(16*atten - 16 + 16 * offset + 16 - 16) + * 2^(16*atten - 16) * 2^(16*offset + 16 - 16) + * 2^(16*atten - 16) * 2^(16*(offset + 1) - 16) + * calc_gf1_amp(atten) * calc_gf1_amp(offset + 1) + */ + right_offset = (float)calc_gf1_amp((log(pan) * (1 / (log_of_2 * 32))) + 1); + left_offset = (float)calc_gf1_amp((log(1 - pan) * (1 / (log_of_2 * 32))) + 1); + } + else + { + left_offset = (float)db_to_amp(-20 * log10(sqrt(1 - pan))); + right_offset = (float)db_to_amp(-20 * log10(sqrt(pan))); + } } } @@ -264,17 +235,112 @@ void Renderer::kill_key_group(int i) float Renderer::calculate_scaled_frequency(Sample *sp, int note) { - double scalednote = (note - sp->scale_note) * sp->scale_factor / 1024.0 + sp->scale_note; + double scalednote = (note - sp->scale_note) * sp->scale_factor / 1024.0 + sp->scale_note + sp->tune * 0.01; return (float)note_to_freq(scalednote); } -void Renderer::start_note(int chan, int note, int vel, int i) +bool Renderer::start_region(int chan, int note, int vel, Sample *sp, float f) +{ + int voicenum; + Voice *v; + + voicenum = allocate_voice(); + if (voicenum < 0) + { + return false; + } + v = &voice[voicenum]; + v->sample = sp; + + if (sp->type == INST_GUS) + { + v->orig_frequency = f; + } + else + { + if (sp->scale_factor != 1024) + { + v->orig_frequency = calculate_scaled_frequency(sp, note); + } + else if (sp->tune != 0) + { + v->orig_frequency = note_to_freq(note + sp->tune * 0.01); + } + else + { + v->orig_frequency = note_to_freq(note); + } + } + + v->status = VOICE_RUNNING; + v->channel = chan; + v->note = note; + v->velocity = vel; + v->sample_offset = 0; + v->sample_increment = 0; /* make sure it isn't negative */ + v->sample_count = 0; + + v->tremolo_phase = 0; + v->tremolo_phase_increment = v->sample->tremolo_phase_increment; + v->tremolo_sweep = v->sample->tremolo_sweep_increment; + v->tremolo_sweep_position = 0; + + v->vibrato_sweep = v->sample->vibrato_sweep_increment; + v->vibrato_sweep_position = 0; + v->vibrato_control_ratio = v->sample->vibrato_control_ratio; + v->vibrato_control_counter = v->vibrato_phase = 0; + + kill_key_group(voicenum); + + memset(v->vibrato_sample_increment, 0, sizeof(v->vibrato_sample_increment)); + + if (sp->type == INST_SF2) + { + // Channel pan is added to instrument pan. + double pan; + if (channel[chan].panning == NO_PANNING) + { + pan = (sp->panning + 500) / 1000.0; + } + else + { + pan = channel[chan].panning / 128.0 + sp->panning / 1000.0; + } + compute_pan(pan, sp->type, v->left_offset, v->right_offset); + } + else if (channel[chan].panning != NO_PANNING) + { + compute_pan(channel[chan].panning / 128.0, sp->type, v->left_offset, v->right_offset); + } + else + { + v->left_offset = v->sample->left_offset; + v->right_offset = v->sample->right_offset; + } + + recompute_freq(voicenum); + recompute_amp(v); + v->control_counter = 0; + + v->eg1.Init(this, v); + + if (v->sample->modes & PATCH_LOOPEN) + { + v->status |= VOICE_LPE; + } + return true; +} + +void Renderer::start_note(int chan, int note, int vel) { Instrument *ip; + Sample *sp; int bank = channel[chan].bank; int prog = channel[chan].program; - Voice *v = &voice[i]; + int i; + float f; + note &= 0x7f; if (ISDRUMCHANNEL(chan)) { if (NULL == drumset[bank] || NULL == (ip = drumset[bank]->instrument[note])) @@ -282,7 +348,12 @@ void Renderer::start_note(int chan, int note, int vel, int i) if (!(ip = drumset[0]->instrument[note])) return; /* No instrument? Then we can't play. */ } - if (ip->type == INST_GUS && ip->samples != 1) + assert(ip != MAGIC_LOAD_INSTRUMENT); + if (ip == MAGIC_LOAD_INSTRUMENT) + { + return; + } + if (ip->samples != 1 && ip->sample->type == INST_GUS) { cmsg(CMSG_WARNING, VERB_VERBOSE, "Strange: percussion instrument with %d samples!", ip->samples); @@ -299,62 +370,85 @@ void Renderer::start_note(int chan, int note, int vel, int i) if (NULL == (ip = tonebank[0]->instrument[prog])) return; /* No instrument? Then we can't play. */ } + assert(ip != MAGIC_LOAD_INSTRUMENT); + if (ip == MAGIC_LOAD_INSTRUMENT) + { + return; + } } - if (ip->sample->scale_factor != 1024) + + if (NULL == ip->sample || ip->samples == 0) + return; /* No samples? Then nothing to play. */ + + // For GF1 patches, scaling is based solely on the first + // waveform in this layer. + if (ip->sample->type == INST_GUS && ip->sample->scale_factor != 1024) { - v->orig_frequency = calculate_scaled_frequency(ip->sample, note & 0x7F); + f = calculate_scaled_frequency(ip->sample, note); } else { - v->orig_frequency = note_to_freq(note & 0x7F); + f = note_to_freq(note); } - select_sample(i, ip, vel); - v->status = VOICE_RUNNING; - v->channel = chan; - v->note = note; - v->velocity = vel; - v->sample_offset = 0; - v->sample_increment = 0; /* make sure it isn't negative */ - - v->tremolo_phase = 0; - v->tremolo_phase_increment = voice[i].sample->tremolo_phase_increment; - v->tremolo_sweep = voice[i].sample->tremolo_sweep_increment; - v->tremolo_sweep_position = 0; - - v->vibrato_sweep = voice[i].sample->vibrato_sweep_increment; - v->vibrato_sweep_position = 0; - v->vibrato_control_ratio = voice[i].sample->vibrato_control_ratio; - v->vibrato_control_counter = voice[i].vibrato_phase = 0; - - kill_key_group(i); - - memset(v->vibrato_sample_increment, 0, sizeof(v->vibrato_sample_increment)); - - if (channel[chan].panning != NO_PANNING) + if (ip->sample->type == INST_GUS) { - v->left_offset = channel[chan].left_offset; - v->right_offset = channel[chan].right_offset; + /* We're more lenient with matching ranges for GUS patches, since the + * official Gravis ones don't cover the full range of possible + * frequencies for every instrument. + */ + if (ip->samples == 1) + { // If there's only one sample, definitely play it. + start_region(chan, note, vel, ip->sample, f); + } + for (i = ip->samples, sp = ip->sample; i != 0; --i, ++sp) + { + // GUS patches don't have velocity ranges, so no need to compare against them. + if (sp->low_freq <= f && sp->high_freq >= f) + { + if (i > 1 && (sp + 1)->low_freq <= f && (sp + 1)->high_freq >= f) + { /* If there is a range of contiguous regions that match our + * desired frequency, the last one in that block is used. + */ + continue; + } + start_region(chan, note, vel, sp, f); + break; + } + } + if (i == 0) + { /* Found nothing. Try again, but look for the one with the closest root frequency. + * As per the suggestion in the original TiMidity function, this search uses + * note values rather than raw frequencies. + */ + double cdiff = 1e10; + double want_note = freq_to_note(f); + Sample *closest = sp = ip->sample; + for (i = ip->samples; i != 0; --i, ++sp) + { + double diff = fabs(freq_to_note(sp->root_freq) - want_note); + if (diff < cdiff) + { + cdiff = diff; + closest = sp; + } + } + start_region(chan, note, vel, closest, f); + } } else { - v->left_offset = v->sample->left_offset; - v->right_offset = v->sample->right_offset; - } - - recompute_freq(i); - recompute_amp(v); - - /* Ramp up from 0 */ - v->envelope_stage = ATTACK; - v->envelope_volume = 0; - v->control_counter = 0; - recompute_envelope(v); - apply_envelope_to_amp(v); - - if (v->sample->modes & PATCH_LOOPEN) - { - v->status |= VOICE_LPE; + for (i = ip->samples, sp = ip->sample; i != 0; --i, ++sp) + { + if ((sp->low_vel <= vel && sp->high_vel >= vel && + sp->low_freq <= f && sp->high_freq >= f)) + { + if (!start_region(chan, note, vel, sp, f)) + { // Ran out of voices + break; + } + } + } } } @@ -369,7 +463,53 @@ void Renderer::kill_note(int i) } } -/* Only one instance of a note can be playing on a single channel. */ +int Renderer::allocate_voice() +{ + int i, lowest; + float lv, v; + + for (i = 0; i < voices; ++i) + { + if (!(voice[i].status & VOICE_RUNNING)) + { + return i; /* Can't get a lower volume than silence */ + } + } + + /* Look for the decaying note with the lowest volume */ + lowest = -1; + lv = 1e10; + i = voices; + while (i--) + { + if ((voice[i].status & VOICE_RELEASING) && !(voice[i].status & VOICE_STOPPING)) + { + v = voice[i].attenuation; + if (v < lv) + { + lv = v; + lowest = i; + } + } + } + + if (lowest >= 0) + { + /* This can still cause a click, but if we had a free voice to + spare for ramping down this note, we wouldn't need to kill it + in the first place... Still, this needs to be fixed. Perhaps + we could use a reserve of voices to play dying notes only. */ + + cut_notes++; + voice[lowest].status = 0; + } + else + { + lost_notes++; + } + return lowest; +} + void Renderer::note_on(int chan, int note, int vel) { if (vel == 0) @@ -378,16 +518,12 @@ void Renderer::note_on(int chan, int note, int vel) return; } - int i = voices, lowest = -1; - float lv = 1e10, v; + int i = voices; + /* Only one instance of a note can be playing on a single channel. */ while (i--) { - if (!(voice[i].status & VOICE_RUNNING)) - { - lowest = i; /* Can't get a lower volume than silence */ - } - else if (voice[i].channel == chan && ((voice[i].note == note && !voice[i].sample->self_nonexclusive) || channel[chan].mono)) + if (voice[i].channel == chan && ((voice[i].note == note && !voice[i].sample->self_nonexclusive) || channel[chan].mono)) { if (channel[chan].mono) { @@ -400,46 +536,7 @@ void Renderer::note_on(int chan, int note, int vel) } } - if (lowest != -1) - { - /* Found a free voice. */ - start_note(chan, note, vel, lowest); - return; - } - - /* Look for the decaying note with the lowest volume */ - if (lowest == -1) - { - i = voices; - while (i--) - { - if ((voice[i].status & VOICE_RELEASING) && !(voice[i].status & VOICE_STOPPING)) - { - v = voice[i].attenuation; - if (v < lv) - { - lv = v; - lowest = i; - } - } - } - } - - if (lowest != -1) - { - /* This can still cause a click, but if we had a free voice to - spare for ramping down this note, we wouldn't need to kill it - in the first place... Still, this needs to be fixed. Perhaps - we could use a reserve of voices to play dying notes only. */ - - cut_notes++; - voice[lowest].status = 0; - start_note(chan, note, vel, lowest); - } - else - { - lost_notes++; - } + start_note(chan, note, vel); } void Renderer::finish_note(int i) @@ -455,23 +552,8 @@ void Renderer::finish_note(int i) { v->status &= ~VOICE_LPE; /* sampled release */ } - if (!(v->sample->modes & PATCH_NO_SRELEASE) || (v->sample->modes & PATCH_FAST_REL)) - { - /* ramp out to minimum volume with rate from final release stage */ - v->envelope_stage = RELEASEC; - recompute_envelope(v); - // Get rate from the final release ramp, but force the target to 0. - v->envelope_target = 0; - v->envelope_increment = -v->sample->envelope_rate[RELEASEC]; - } - else if (v->sample->modes & PATCH_SUSTAIN) - { - if (v->envelope_stage < RELEASE) - { - v->envelope_stage = RELEASE; - } - recompute_envelope(v); - } + v->eg1.Release(v); + v->eg2.Release(v); } } @@ -554,15 +636,19 @@ void Renderer::adjust_pressure(int chan, int note, int amount) void Renderer::adjust_panning(int chan) { Channel *chanp = &channel[chan]; - compute_pan(chanp->panning, chanp->left_offset, chanp->right_offset); int i = voices; while (i--) { - if ((voice[i].channel == chan) && (voice[i].status & VOICE_RUNNING)) + Voice *v = &voice[i]; + if ((v->channel == chan) && (v->status & VOICE_RUNNING)) { - voice[i].left_offset = chanp->left_offset; - voice[i].right_offset = chanp->right_offset; - apply_envelope_to_amp(&voice[i]); + double pan = chanp->panning / 128.0; + if (v->sample->type == INST_SF2) + { // Add instrument pan to channel pan. + pan += v->sample->panning / 500.0; + } + compute_pan(pan, v->sample->type, v->left_offset, v->right_offset); + apply_envelope_to_amp(v); } } } @@ -674,32 +760,17 @@ void Renderer::HandleController(int chan, int ctrl, int val) break; case CTRL_VOLUME: - channel[chan].volume = (channel[chan].volume & 0x007F) | (val << 7); - adjust_volume(chan); - break; - - case CTRL_VOLUME+32: - channel[chan].volume = (channel[chan].volume & 0x3F80) | (val); + channel[chan].volume = val; adjust_volume(chan); break; case CTRL_EXPRESSION: - channel[chan].expression = (channel[chan].expression & 0x007F) | (val << 7); - adjust_volume(chan); - break; - - case CTRL_EXPRESSION+32: - channel[chan].expression = (channel[chan].expression & 0x3F80) | (val); + channel[chan].expression = val; adjust_volume(chan); break; case CTRL_PAN: - channel[chan].panning = (channel[chan].panning & 0x007F) | (val << 7); - adjust_panning(chan); - break; - - case CTRL_PAN+32: - channel[chan].panning = (channel[chan].panning & 0x3F80) | (val); + channel[chan].panning = val; adjust_panning(chan); break; diff --git a/src/timidity/resample.cpp b/src/timidity/resample.cpp index 2b30dd55ce..22df72f529 100644 --- a/src/timidity/resample.cpp +++ b/src/timidity/resample.cpp @@ -509,6 +509,14 @@ sample_t *resample_voice(Renderer *song, Voice *vp, int *countptr) /* Need to resample. Use the proper function. */ modes = vp->sample->modes; + if (vp->status & VOICE_LPE) + { + if (vp->sample->loop_end - vp->sample->loop_start < 2) + { // Loop is too short; turn it off. + vp->status &= ~VOICE_LPE; + } + } + if (vp->vibrato_control_ratio) { if (vp->status & VOICE_LPE) diff --git a/src/timidity/sf2.h b/src/timidity/sf2.h new file mode 100644 index 0000000000..0036923dae --- /dev/null +++ b/src/timidity/sf2.h @@ -0,0 +1,318 @@ +typedef WORD SFGenerator; + +struct SFRange +{ + BYTE Lo; + BYTE Hi; +}; + +struct SFPreset +{ + char Name[21]; + BYTE LoadOrder:7; + BYTE bHasGlobalZone:1; + WORD Program; + WORD Bank; + WORD BagIndex; + /* Don't care about library, genre, and morphology */ +}; + +struct SFBag +{ + WORD GenIndex; +// WORD ModIndex; // If I am feeling ambitious, I might add support for modulators some day. + SFRange KeyRange; + SFRange VelRange; + int Target; // Either an instrument or sample index +}; + +struct SFInst +{ + char Name[21]; + BYTE Pad:7; + BYTE bHasGlobalZone:1; + WORD BagIndex; +}; + +struct SFSample +{ + float *InMemoryData; + DWORD Start; + DWORD End; + DWORD StartLoop; + DWORD EndLoop; + DWORD SampleRate; + BYTE OriginalPitch; + SBYTE PitchCorrection; + WORD SampleLink; + WORD SampleType; + char Name[21]; +}; + +// Sample type bit fields (all but ROM are mutually exclusive) +enum +{ + SFST_Mono = 1, + SFST_Right = 2, + SFST_Left = 4, + SFST_Linked = 8, /* SF2.04 defines this bit but not its function */ + SFST_Bad = 16384, /* Used internally */ + SFST_ROM = 32768 +}; + +// Generator definitions + +struct SFGenList +{ + SFGenerator Oper; + union + { + SFRange Range; + SWORD Amount; + WORD uAmount; + }; +}; + +enum +{ + GEN_startAddrsOffset, + GEN_endAddrsOffset, + GEN_startloopAddrsOffset, + GEN_endloopAddrsOffset, + GEN_startAddrsCoarseOffset, + GEN_modLfoToPitch, + GEN_vibLfoToPitch, + GEN_modEnvToPitch, + GEN_initialFilterFC, + GEN_initialFilterQ, + GEN_modLfoToFilterFc, + GEN_modEnvToFilterFc, + GEN_endAddrsCoarseOffset, + GEN_modLfoToVolume, + GEN_unused1, + GEN_chorusEffectsSend, + GEN_reverbEffectsSend, + GEN_pan, + GEN_unused2, + GEN_unused3, + GEN_unused4, + GEN_delayModLFO, + GEN_freqModLFO, + GEN_delayVibLFO, + GEN_freqVibLFO, + GEN_delayModEnv, + GEN_attackModEnv, + GEN_holdModEnv, + GEN_decayModEnv, + GEN_sustainModEnv, + GEN_releaseModEnv, + GEN_keynumToModEnvHold, + GEN_keynumToModEnvDecay, + GEN_delayVolEnv, + GEN_attackVolEnv, + GEN_holdVolEnv, + GEN_decayVolEnv, + GEN_sustainVolEnv, + GEN_releaseVolEnv, + GEN_keynumToVolEnvHold, + GEN_keynumToVolEnvDecay, + GEN_instrument, + GEN_reserved1, + GEN_keyRange, + GEN_velRange, + GEN_startloopAddrsCoarseOffset, + GEN_keynum, + GEN_velocity, + GEN_initialAttenuation, + GEN_reserved2, + GEN_endloopAddrsCoarseOffset, + GEN_coarseTune, + GEN_fineTune, + GEN_sampleID, + GEN_sampleModes, + GEN_reserved3, + GEN_scaleTuning, + GEN_exclusiveClass, + GEN_overridingRootKey, + + GEN_NumGenerators +}; + +// Modulator definitions + +struct SFModulator +{ + WORD Index:7; + WORD CC:1; + WORD Dir:1; /* 0 = min->max, 1 = max->min */ + WORD Polarity:1; /* 0 = unipolar, 1 = bipolar */ + WORD Type:6; +}; + +struct SFModList +{ + SFModulator SrcOper; + SFGenerator DestOper; + SWORD Amount; + SFModulator AmtSrcOper; + WORD Transform; +}; + +// Modulator sources when CC is 0 + +enum +{ + SFMod_One = 0, // Psuedo-controller that always has the value 1 + SFMod_NoteVelocity = 2, + SFMod_KeyNumber = 3, + SFMod_PolyPressure = 10, + SFMod_ChannelPressure = 13, + SFMod_PitchWheel = 14, + SFMod_PitchSens = 16, + SFMod_Link = 127 +}; + +// Modulator types + +enum +{ + SFModType_Linear, + SFModType_Concave, // log(fabs(value)/(max value)^2) + SFModType_Convex, + SFModType_Switch +}; + +// Modulator transforms + +enum +{ + SFModTrans_Linear = 0, + SFModTrans_Abs = 2 +}; + +// All possible generators in a single structure + +struct SFGenComposite +{ + union + { + SFRange keyRange; // For normal use + struct // For intermediate percussion use + { + BYTE drumset; + BYTE key; + }; + }; + SFRange velRange; + union + { + WORD instrument; // At preset level + WORD sampleID; // At instrument level + }; + SWORD modLfoToPitch; + SWORD vibLfoToPitch; + SWORD modEnvToPitch; + SWORD initialFilterFc; + SWORD initialFilterQ; + SWORD modLfoToFilterFc; + SWORD modEnvToFilterFc; + SWORD modLfoToVolume; + SWORD chorusEffectsSend; + SWORD reverbEffectsSend; + SWORD pan; + SWORD delayModLFO; + SWORD freqModLFO; + SWORD delayVibLFO; + SWORD freqVibLFO; + SWORD delayModEnv; + SWORD attackModEnv; + SWORD holdModEnv; + SWORD decayModEnv; + SWORD sustainModEnv; + SWORD releaseModEnv; + SWORD keynumToModEnvHold; + SWORD keynumToModEnvDecay; + SWORD delayVolEnv; + SWORD attackVolEnv; + SWORD holdVolEnv; + SWORD decayVolEnv; + SWORD sustainVolEnv; + SWORD releaseVolEnv; + SWORD keynumToVolEnvHold; + SWORD keynumToVolEnvDecay; + SWORD initialAttenuation; + SWORD coarseTune; + SWORD fineTune; + SWORD scaleTuning; + + // The following are only for instruments: + SWORD startAddrsOffset, startAddrsCoarseOffset; + SWORD endAddrsOffset, endAddrsCoarseOffset; + SWORD startLoopAddrsOffset, startLoopAddrsCoarseOffset; + SWORD endLoopAddrsOffset, endLoopAddrsCoarseOffset; + SWORD keynum; + SWORD velocity; + WORD sampleModes; + SWORD exclusiveClass; + SWORD overridingRootKey; +}; + +// Intermediate percussion representation + +struct SFPerc +{ + SFPreset *Preset; + SFGenComposite Generators; + BYTE LoadOrder; +}; + +// Container for all parameters from a SoundFont file + +struct SFFile : public Timidity::FontFile +{ + SFFile(FString filename); + ~SFFile(); + Timidity::Instrument *LoadInstrument(struct Timidity::Renderer *song, int drum, int bank, int program); + Timidity::Instrument *LoadInstrumentOrder(struct Timidity::Renderer *song, int order, int drum, int bank, int program); + void SetOrder(int order, int drum, int bank, int program); + void SetAllOrders(int order); + + bool FinalStructureTest(); + void CheckBags(); + void CheckZones(int start, int stop, bool instr); + void TranslatePercussions(); + void TranslatePercussionPreset(SFPreset *preset); + void TranslatePercussionPresetZone(SFPreset *preset, SFBag *zone); + + void SetInstrumentGenerators(SFGenComposite *composite, int start, int stop); + void AddPresetGenerators(SFGenComposite *composite, int start, int stop, SFPreset *preset); + void AddPresetGenerators(SFGenComposite *composite, int start, int stop, bool gen_set[GEN_NumGenerators]); + + Timidity::Instrument *LoadPercussion(Timidity::Renderer *song, SFPerc *perc); + Timidity::Instrument *LoadPreset(Timidity::Renderer *song, SFPreset *preset); + void LoadSample(SFSample *sample); + void ApplyGeneratorsToRegion(SFGenComposite *gen, SFSample *sfsamp, Timidity::Renderer *song, Timidity::Sample *sp); + + SFPreset *Presets; + SFBag *PresetBags; + SFGenList *PresetGenerators; + SFInst *Instruments; + SFBag *InstrBags; + SFGenList *InstrGenerators; + SFSample *Samples; + TArray Percussion; + int MinorVersion; + DWORD SampleDataOffset; + DWORD SampleDataLSBOffset; + DWORD SizeSampleData; + DWORD SizeSampleDataLSB; + int NumPresets; + int NumPresetBags; + int NumPresetGenerators; + int NumInstruments; + int NumInstrBags; + int NumInstrGenerators; + int NumSamples; +}; + +SFFile *ReadSF2(const char *filename, FileReader *f); diff --git a/src/timidity/timidity.cpp b/src/timidity/timidity.cpp index b1cd1cd5c4..66c891805a 100644 --- a/src/timidity/timidity.cpp +++ b/src/timidity/timidity.cpp @@ -28,6 +28,7 @@ #include "m_alloc.h" #include "cmdlib.h" #include "c_cvars.h" +#include "c_dispatch.h" #include "i_system.h" #include "files.h" @@ -42,13 +43,10 @@ ToneBank *tonebank[MAXBANK], *drumset[MAXBANK]; static FString def_instr_name; int openmode = OM_FILEORLUMP; - -#define MAXWORDS 10 - static int read_config_file(const char *name, bool ismain) { FileReader *fp; - char tmp[1024], *w[MAXWORDS], *cp; + char tmp[1024], *cp; ToneBank *bank = NULL; int i, j, k, line = 0, words; static int rcf_count = 0; @@ -81,17 +79,29 @@ static int read_config_file(const char *name, bool ismain) while (fp->Gets(tmp, sizeof(tmp))) { line++; - w[words = 0] = strtok(tmp, " \t\r\n\240"); - if (!w[0]) continue; + FCommandLine w(tmp, true); + words = w.argc(); + if (words == 0) continue; /* Originally the TiMidity++ extensions were prefixed like this */ if (strcmp(w[0], "#extension") == 0) - words = -1; + { + w.Shift(); + words--; + } else if (*w[0] == '#') + { continue; + } - while (w[words] && *w[words] != '#' && (words < MAXWORDS)) - w[++words] = strtok(0, " \t\r\n\240"); + for (i = 0; i < words; ++i) + { + if (*w[i] == '#') + { + words = i; + break; + } + } /* * TiMidity++ adds a number of extensions to the config file format. @@ -155,19 +165,93 @@ static int read_config_file(const char *name, bool ismain) */ Printf("FIXME: Implement \"altassign\" in TiMidity config.\n"); } - else if (!strcmp(w[0], "soundfont") || !strcmp(w[0], "font")) + else if (!strcmp(w[0], "soundfont")) { /* - * I can't find any documentation for these, but I guess they're - * an alternative way of loading/unloading instruments. - * * "soundfont" sf_file "remove" * "soundfont" sf_file ["order=" order] ["cutoff=" cutoff] * ["reso=" reso] ["amp=" amp] + */ + if (words < 2) + { + Printf("%s: line %d: No soundfont given\n", name, line); + delete fp; + return -2; + } + if (words > 2 && !strcmp(w[2], "remove")) + { + font_remove(w[1]); + } + else + { + int order = 0; + + for (i = 2; i < words; ++i) + { + if (!(cp = strchr(w[i], '='))) + { + Printf("%s: line %d: bad soundfont option %s\n", name, line, w[i]); + delete fp; + return -2; + } + } + font_add(w[1], order); + } + } + else if (!strcmp(w[0], "font")) + { + /* * "font" "exclude" bank preset keynote * "font" "order" order bank preset keynote */ - Printf("FIXME: Implmement \"%s\" in TiMidity config.\n", w[0]); + int order, drum = -1, bank = -1, instr = -1; + + if (words < 3) + { + Printf("%s: line %d: syntax error\n", name, line); + delete fp; + return -2; + } + + if (!strcmp(w[1], "exclude")) + { + order = 254; + i = 2; + } + else if (!strcmp(w[1], "order")) + { + order = atoi(w[2]); + i = 3; + } + else + { + Printf("%s: line %d: font subcommand must be 'order' or 'exclude'\n", name, line); + delete fp; + return -2; + } + if (i < words) + { + drum = atoi(w[i++]); + } + if (i < words) + { + bank = atoi(w[i++]); + } + if (i < words) + { + instr = atoi(w[i++]); + } + if (drum != 128) + { + instr = bank; + bank = drum; + drum = 0; + } + else + { + drum = 1; + } + font_order(order, drum, bank, instr); } else if (!strcmp(w[0], "progbase")) { @@ -289,12 +373,34 @@ static int read_config_file(const char *name, bool ismain) delete fp; return -2; } - bank->tone[i].name = w[1]; bank->tone[i].note = bank->tone[i].amp = bank->tone[i].pan = - bank->tone[i].strip_loop = bank->tone[i].strip_envelope = - bank->tone[i].strip_tail = -1; + bank->tone[i].fontbank = bank->tone[i].fontpreset = + bank->tone[i].fontnote = bank->tone[i].strip_loop = + bank->tone[i].strip_envelope = bank->tone[i].strip_tail = -1; - for (j = 2; jtone[i].name = w[2]; + bank->tone[i].fontbank = atoi(w[3]); + bank->tone[i].fontpreset = atoi(w[4]); + if (bank->tone[i].fontbank == 128 || (w[5][0] >= '0' && w[5][0] <= '9')) + { + bank->tone[i].fontnote = atoi(w[5]); + j = 6; + } + else + { + j = 5; + } + font_add(w[2], 254); + } + else + { + bank->tone[i].name = w[1]; + j = 2; + } + + for (; j - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - - - @@ -946,6 +936,16 @@ Outputs=""src/$(InputName).h"" /> + + + @@ -1540,16 +1540,6 @@ Outputs="$(IntDir)\$(InputName).obj" /> - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + + + + @@ -1920,14 +1928,6 @@ Outputs="$(IntDir)\$(InputName).obj" /> - - - + + + @@ -2794,14 +2802,6 @@ AdditionalIncludeDirectories="src\win32;$(NoInherit)" /> - - - @@ -2952,59 +2952,7 @@ Name="Timidity" > - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +