diff --git a/docs/rh-log.txt b/docs/rh-log.txt index b46cd7b50..9cf68192c 100644 --- a/docs/rh-log.txt +++ b/docs/rh-log.txt @@ -1,3 +1,10 @@ +May 9, 2008 +- Added a new cvar: midi_timiditylike. This re-enables TiMidity handling of + GUS patch flags, envelopes, and volume levels, while trying to be closer + to TiMidity++ than original TiMidity. +- Renamed timidity_config and timidity_voices to midi_config and midi_voices + respectively. + May 8, 2008 - Reduced volume, expression, and panning controllers back to 7 bits. - Added very basic Soundfont support to the internal TiMidity. Things missing: diff --git a/src/timidity/instrum.cpp b/src/timidity/instrum.cpp index 2ca40c137..55683cb4e 100644 --- a/src/timidity/instrum.cpp +++ b/src/timidity/instrum.cpp @@ -362,53 +362,58 @@ fail: if (header.Description[j] != 0) break; } - if (j == DESC_SIZE) + /* Strip any loops and envelopes we're permitted to */ + /* [RH] (But PATCH_BACKWARD isn't a loop flag at all!) */ + if ((strip_loop == 1) && + (sp->modes & (PATCH_SUSTAIN | PATCH_LOOPEN | PATCH_BIDIR | PATCH_BACKWARD))) { - /* Strip any loops and envelopes we're permitted to */ - /* [RH] (But PATCH_BACKWARD isn't a loop flag at all!) */ - if ((strip_loop == 1) && - (sp->modes & (PATCH_SUSTAIN | PATCH_LOOPEN | PATCH_BIDIR | PATCH_BACKWARD))) + cmsg(CMSG_INFO, VERB_DEBUG, " - Removing loop and/or sustain\n"); + if (j == DESC_SIZE) { - cmsg(CMSG_INFO, VERB_DEBUG, " - Removing loop and/or sustain\n"); sp->modes &= ~(PATCH_SUSTAIN | PATCH_LOOPEN | PATCH_BIDIR | PATCH_BACKWARD); } + sp->modes |= PATCH_T_NO_LOOP; + } - if (strip_envelope == 1) + if (strip_envelope == 1) + { + cmsg(CMSG_INFO, VERB_DEBUG, " - Removing envelope\n"); + /* [RH] The envelope isn't really removed, but this is the way the standard + * Gravis patches get that effect: All rates at maximum, and all offsets at + * a constant level. + */ + if (j == DESC_SIZE) { - cmsg(CMSG_INFO, VERB_DEBUG, " - Removing envelope\n"); - /* [RH] The envelope isn't really removed, but this is the way the standard - * Gravis patches get that effect: All rates at maximum, and all offsets at - * a constant level. - */ - for (j = 1; j < ENVELOPES; ++j) + int k; + for (k = 1; k < ENVELOPES; ++k) { /* Find highest offset. */ - if (patch_data.EnvelopeOffset[j] > patch_data.EnvelopeOffset[0]) + if (patch_data.EnvelopeOffset[k] > patch_data.EnvelopeOffset[0]) { - patch_data.EnvelopeOffset[0] = patch_data.EnvelopeOffset[j]; + patch_data.EnvelopeOffset[0] = patch_data.EnvelopeOffset[k]; } } - for (j = 0; j < ENVELOPES; ++j) + for (k = 0; k < ENVELOPES; ++k) { - patch_data.EnvelopeRate[j] = 63; - patch_data.EnvelopeOffset[j] = patch_data.EnvelopeOffset[0]; + patch_data.EnvelopeRate[k] = 63; + patch_data.EnvelopeOffset[k] = patch_data.EnvelopeOffset[0]; } } + sp->modes |= PATCH_T_NO_ENVELOPE; } -#if 0 else if (strip_envelope != 0) { /* Have to make a guess. */ if (!(sp->modes & (PATCH_LOOPEN | PATCH_BIDIR | PATCH_BACKWARD))) { /* No loop? Then what's there to sustain? No envelope needed either... */ - sp->modes &= ~(PATCH_SUSTAIN | PATCH_NO_SRELEASE); + sp->modes |= PATCH_T_NO_ENVELOPE; cmsg(CMSG_INFO, VERB_DEBUG, " - No loop, removing sustain and envelope\n"); } - else if (memcmp(patch_data.EnvelopeRate, "??????", 6) == 0 || patch_data.EnvelopeOffset[RELEASEC] >= 100) + else if (memcmp(patch_data.EnvelopeRate, "??????", 6) == 0 || patch_data.EnvelopeOffset[GF1_RELEASEC] >= 100) { /* Envelope rates all maxed out? Envelope end at a high "offset"? That's a weird envelope. Take it out. */ - sp->modes &= ~PATCH_NO_SRELEASE; + sp->modes |= PATCH_T_NO_ENVELOPE; cmsg(CMSG_INFO, VERB_DEBUG, " - Weirdness, removing envelope\n"); } else if (!(sp->modes & PATCH_SUSTAIN)) @@ -417,11 +422,14 @@ fail: justified, but patches without sustain usually don't need the envelope either... at least the Gravis ones. They're mostly drums. I think. */ - sp->modes &= ~PATCH_NO_SRELEASE; + sp->modes |= PATCH_T_NO_ENVELOPE; cmsg(CMSG_INFO, VERB_DEBUG, " - No sustain, removing envelope\n"); } } -#endif + if (!(sp->modes & PATCH_NO_SRELEASE)) + { // TiMidity thinks that this is an envelope enable flag. + sp->modes |= PATCH_T_NO_ENVELOPE; + } for (j = 0; j < 6; j++) { @@ -468,10 +476,10 @@ fail: } else { -#if defined(ADJUST_SAMPLE_VOLUMES) /* Try to determine a volume scaling factor for the sample. - This is a very crude adjustment, but things sound more - balanced with it. Still, this should be a runtime option. */ + This is a very crude adjustment, but things sound more + balanced with it. Still, this should be a runtime option. + (This is ignored unless midi_timiditylike is turned on.) */ int i; sample_t maxamp = 0, a; sample_t *tmp; @@ -483,9 +491,6 @@ fail: } sp->volume = 1 / maxamp; cmsg(CMSG_INFO, VERB_DEBUG, " * volume comp: %f\n", sp->volume); -#else - sp->volume = 1; -#endif } /* Then fractional samples */ diff --git a/src/timidity/mix.cpp b/src/timidity/mix.cpp index e79dc5f07..e36f234d3 100644 --- a/src/timidity/mix.cpp +++ b/src/timidity/mix.cpp @@ -27,6 +27,9 @@ #include "timidity.h" #include "templates.h" +#include "c_cvars.h" + +EXTERN_CVAR(Bool, midi_timiditylike) namespace Timidity { @@ -75,7 +78,16 @@ void GF1Envelope::Init(Renderer *song, Voice *v) void GF1Envelope::Release(Voice *v) { - if (!(v->sample->modes & PATCH_NO_SRELEASE) || (v->sample->modes & PATCH_FAST_REL)) + if (midi_timiditylike) + { + if (!(v->sample->modes & PATCH_T_NO_ENVELOPE)) + { + stage = GF1_RELEASE; + Recompute(v); + } + // else ... loop was already turned off by the caller + } + else 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; @@ -96,22 +108,31 @@ void GF1Envelope::Release(Voice *v) /* Returns 1 if envelope runs out */ bool GF1Envelope::Recompute(Voice *v) { - int oldstage; + int newstage; - oldstage = stage; + newstage = stage; - if (oldstage > GF1_RELEASEC) + if (newstage > GF1_RELEASEC) { /* Envelope ran out. */ - /* play sampled release */ - v->status &= ~(VOICE_SUSTAINING | VOICE_LPE); - v->status |= VOICE_RELEASING; increment = 0; bUpdating = false; + v->status &= ~(VOICE_SUSTAINING | VOICE_LPE); + v->status |= VOICE_RELEASING; + if (midi_timiditylike) + { /* kill the voice */ + v->status |= VOICE_STOPPING; + return 1; + } + else + { /* play sampled release */ + } return 0; } - if (oldstage == GF1_RELEASE && !(v->status & VOICE_RELEASING) && (v->sample->modes & PATCH_SUSTAIN)) + if (newstage == GF1_RELEASE && !(v->status & VOICE_RELEASING) && + ((!midi_timiditylike && (v->sample->modes & PATCH_SUSTAIN)) || + (midi_timiditylike && !(v->sample->modes & PATCH_T_NO_ENVELOPE)))) { v->status |= VOICE_SUSTAINING; /* Freeze envelope until note turns off. Trumpets want this. */ @@ -120,14 +141,14 @@ bool GF1Envelope::Recompute(Voice *v) } else { - stage = oldstage + 1; + stage = newstage + 1; - if (volume == offset[oldstage]) + if (volume == offset[newstage]) { return Recompute(v); } - target = offset[oldstage]; - increment = rate[oldstage]; + target = offset[newstage]; + increment = rate[newstage]; if (target < volume) increment = -increment; } @@ -137,6 +158,10 @@ bool GF1Envelope::Recompute(Voice *v) bool GF1Envelope::Update(Voice *v) { + if (midi_timiditylike && (v->sample->modes & PATCH_T_NO_ENVELOPE)) + { + return 0; + } volume += increment; if (((increment < 0) && (volume <= target)) || ((increment > 0) && (volume >= target))) { @@ -152,14 +177,39 @@ bool GF1Envelope::Update(Voice *v) 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; + double final_amp; + + if (midi_timiditylike) + { + final_amp = v->sample->volume * FINAL_MIX_TIMIDITY_SCALE; + if (v->tremolo_phase_increment != 0) + { + env_vol *= v->tremolo_volume; + } + if (!(v->sample->modes & PATCH_T_NO_ENVELOPE)) + { + if (stage > GF1_ATTACK) + { + env_vol *= pow(2.0, volume * (6.0 / (1 << 30)) - 6.0); + } + else + { + env_vol *= volume / float(1 << 30); + } + } } - env_vol *= volume / float(1 << 30); - env_vol = calc_gf1_amp(env_vol) * final_amp; + else + { + final_amp = 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); + } + env_vol *= final_amp; v->left_mix = float(env_vol * v->left_offset); v->right_mix = float(env_vol * v->right_offset); } diff --git a/src/timidity/playmidi.cpp b/src/timidity/playmidi.cpp index e234fdde8..9c77e7c52 100644 --- a/src/timidity/playmidi.cpp +++ b/src/timidity/playmidi.cpp @@ -27,6 +27,9 @@ #include #include "timidity.h" +#include "c_cvars.h" + +EXTERN_CVAR(Bool, midi_timiditylike) namespace Timidity { @@ -161,7 +164,16 @@ void Renderer::recompute_amp(Voice *v) if (v->sample->type == INST_GUS) { - v->attenuation = (vol_table[(chanvol * chanexpr) / 127] * vol_table[v->velocity]) * ((127 + 64) / 12419775.f); + if (midi_timiditylike) + { + v->attenuation = float(timidityxx_perceived_vol(v->velocity / 127.0) * + timidityxx_perceived_vol(chanvol / 127.0) * + timidityxx_perceived_vol(chanexpr / 127.0)); + } + else + { + v->attenuation = (vol_table[(chanvol * chanexpr) / 127] * vol_table[v->velocity]) * ((127 + 64) / 12419775.f); + } } else { @@ -190,7 +202,7 @@ void Renderer::compute_pan(double pan, int type, float &left_offset, float &righ } else { - if (type == INST_GUS) + if (type == INST_GUS && !midi_timiditylike) { /* Original amp equation looks like this: * calc_gf1_amp(atten + offset) @@ -209,8 +221,12 @@ void Renderer::compute_pan(double pan, int type, float &left_offset, float &righ } else { - left_offset = (float)db_to_amp(-20 * log10(sqrt(1 - pan))); - right_offset = (float)db_to_amp(-20 * log10(sqrt(pan))); + /* I have no idea what equation, if any, will reproduce the sc_pan_table + * that TiMidity++ uses, so midi_timiditylike gets the same Equal Power + * Panning as SF2/DLS. + */ + left_offset = (float)sqrt(1 - pan); + right_offset = (float)sqrt(pan); } } } @@ -548,7 +564,7 @@ void Renderer::finish_note(int i) v->status &= ~VOICE_SUSTAINING; v->status |= VOICE_RELEASING; - if (!(v->sample->modes & PATCH_NO_SRELEASE)) + if (!(v->sample->modes & PATCH_NO_SRELEASE) || midi_timiditylike) { v->status &= ~VOICE_LPE; /* sampled release */ } diff --git a/src/timidity/resample.cpp b/src/timidity/resample.cpp index 22df72f52..ceb29c60f 100644 --- a/src/timidity/resample.cpp +++ b/src/timidity/resample.cpp @@ -26,6 +26,9 @@ #include #include "timidity.h" +#include "c_cvars.h" + +EXTERN_CVAR(Bool, midi_timiditylike) namespace Timidity { @@ -484,7 +487,7 @@ static sample_t *rs_vib_bidir(sample_t *resample_buffer, float rate, Voice *vp, sample_t *resample_voice(Renderer *song, Voice *vp, int *countptr) { int ofs; - BYTE modes; + WORD modes; if (vp->sample->sample_rate == 0) { @@ -519,7 +522,7 @@ sample_t *resample_voice(Renderer *song, Voice *vp, int *countptr) if (vp->vibrato_control_ratio) { - if (vp->status & VOICE_LPE) + if (vp->status & VOICE_LPE && !(midi_timiditylike && vp->sample->modes & PATCH_T_NO_LOOP)) { if (modes & PATCH_BIDIR) return rs_vib_bidir(song->resample_buffer, song->rate, vp, *countptr); @@ -533,7 +536,7 @@ sample_t *resample_voice(Renderer *song, Voice *vp, int *countptr) } else { - if (vp->status & VOICE_LPE) + if (vp->status & VOICE_LPE && !(midi_timiditylike && vp->sample->modes & PATCH_T_NO_LOOP)) { if (modes & PATCH_BIDIR) return rs_bidir(song->resample_buffer, vp, *countptr); diff --git a/src/timidity/timidity.cpp b/src/timidity/timidity.cpp index 66c891805..9f95f9000 100644 --- a/src/timidity/timidity.cpp +++ b/src/timidity/timidity.cpp @@ -32,8 +32,9 @@ #include "i_system.h" #include "files.h" -CVAR(String, timidity_config, CONFIG_FILE, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) -CVAR(Int, timidity_voices, 32, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +CVAR(String, midi_config, CONFIG_FILE, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +CVAR(Int, midi_voices, 32, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +CVAR(Bool, midi_timiditylike, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) namespace Timidity { @@ -551,7 +552,7 @@ int LoadConfig(const char *filename) int LoadConfig() { - return LoadConfig(timidity_config); + return LoadConfig(midi_config); } Renderer::Renderer(float sample_rate) @@ -573,7 +574,7 @@ Renderer::Renderer(float sample_rate) if (def_instr_name.IsNotEmpty()) set_default_instrument(def_instr_name); - voices = clamp(timidity_voices, 16, 256); + voices = clamp(midi_voices, 16, 256); voice = new Voice[voices]; drumchannels = DEFAULT_DRUMCHANNELS; } diff --git a/src/timidity/timidity.h b/src/timidity/timidity.h index 4d562d43c..ed5a2b2f8 100644 --- a/src/timidity/timidity.h +++ b/src/timidity/timidity.h @@ -53,7 +53,11 @@ config.h /* A scalar applied to the final mix to try and approximate the volume level of FMOD's built-in MIDI player. */ -#define FINAL_MIX_SCALE 0.5f +#define FINAL_MIX_SCALE 0.5 + +/* This value is used instead when midi_timiditylike is turned on, + because TiMidity++ is louder than a GUS. */ +#define FINAL_MIX_TIMIDITY_SCALE 0.3 /* How many bits to use for the fractional part of sample positions. This affects tonal accuracy. The entire position counter must fit @@ -214,7 +218,10 @@ enum PATCH_BACKWARD = (1<<4), PATCH_SUSTAIN = (1<<5), PATCH_NO_SRELEASE = (1<<6), - PATCH_FAST_REL = (1<<7) + PATCH_FAST_REL = (1<<7), + + PATCH_T_NO_ENVELOPE = (1<<8), + PATCH_T_NO_LOOP = (1<<9) }; struct Sample @@ -249,7 +256,9 @@ struct Sample BYTE tremolo_depth, vibrato_depth, low_vel, high_vel, - modes, type; + type; + WORD + modes; SWORD panning; WORD @@ -608,6 +617,7 @@ const double log_of_2 = 0.69314718055994529; #define calc_gf1_amp(x) (pow(2.0,((x)*16.0 - 16.0))) // Actual GUS equation #define cb_to_amp(x) (pow(10.0, (x) * (1 / -200.0))) // centibels to amp #define db_to_amp(x) (pow(10.0, (x) * (1 / -20.0))) // decibels to map +#define timidityxx_perceived_vol(x) (pow((x), 1.66096404744)) /* timidity.h