From a0a722b729b9d627db0327409a0f2bd240a9d975 Mon Sep 17 00:00:00 2001 From: Thomas Neumann Date: Sun, 15 Jan 2023 23:55:02 +0300 Subject: [PATCH 1/2] Envelope fixes [1/2]: Prevent last used sampled in a channel to be played, if afterwards an illegal instrument is set. Now nothing is played. Combined with the next patch, these two fixes make Ebony Owl Netsuke.xm to play correctly. --- libmikmod/include/mikmod_internals.h | 9 +- libmikmod/playercode/mplayer.c | 326 +++++++++++++++------------ 2 files changed, 193 insertions(+), 142 deletions(-) diff --git a/libmikmod/include/mikmod_internals.h b/libmikmod/include/mikmod_internals.h index 18267969..1d5d1c29 100644 --- a/libmikmod/include/mikmod_internals.h +++ b/libmikmod/include/mikmod_internals.h @@ -465,18 +465,19 @@ typedef struct ENVPR { UBYTE pts; /* number of envelope points */ UBYTE susbeg; /* envelope sustain index begin */ UBYTE susend; /* envelope sustain index end */ + BOOL susactive;/* Indicate if sustain is active (no interpolation) */ UBYTE beg; /* envelope loop begin */ UBYTE end; /* envelope loop end */ SWORD p; /* current envelope counter */ - UWORD a; /* envelope index a */ - UWORD b; /* envelope index b */ + UWORD index; /* envelope index for the point after the current one */ + SWORD lastvalue;/* the last calculated value */ ENVPT* env; /* envelope points */ } ENVPR; typedef struct MP_CHANNEL { INSTRUMENT* i; SAMPLE *s; - UBYTE sample; /* which sample number */ + SBYTE sample; /* which sample number, -1 if illegal instrument has been set */ UBYTE note; /* the audible note as heard, direct rep of period */ SWORD outvolume; /* output volume (vol + sampcol + instvol) */ SBYTE chanvol; /* channel's "global" volume */ @@ -590,6 +591,8 @@ typedef struct MP_VOICE { ENVPR penv; ENVPR cenv; + SWORD envstartpos;/* Start position for envelopes set by XM effect L */ + UWORD avibpos; /* autovibrato pos */ UWORD aswppos; /* autovibrato sweep pos */ diff --git a/libmikmod/playercode/mplayer.c b/libmikmod/playercode/mplayer.c index d82023bb..d0e323e8 100644 --- a/libmikmod/playercode/mplayer.c +++ b/libmikmod/playercode/mplayer.c @@ -349,37 +349,72 @@ static SWORD DoPan(SWORD envpan,SWORD pan) return (newpanPAN_RIGHT?PAN_RIGHT:newpan); } -static SWORD StartEnvelope(ENVPR *t,UBYTE flg,UBYTE pts,UBYTE susbeg,UBYTE susend,UBYTE beg,UBYTE end,ENVPT *p,UBYTE keyoff) +static void StartEnvelope(ENVPR *t,UBYTE flg,UBYTE pts,UBYTE susbeg,UBYTE susend,UBYTE beg,UBYTE end,ENVPT *p,UBYTE keyoff,SWORD defaultvalue) { t->flg=flg; t->pts=pts; t->susbeg=susbeg; t->susend=susend; + t->susactive=0; t->beg=beg; t->end=end; t->env=p; t->p=0; - t->a=0; - t->b=((t->flg&EF_SUSTAIN)&&(!(keyoff&KEY_OFF)))?0:1; + t->index=1; if (!t->pts) { /* FIXME: bad/crafted file. better/more general solution? */ - t->b=0; - return t->env[0].val; - } + t->index=0; + t->lastvalue=defaultvalue; + } else { + /* Imago Orpheus sometimes stores an extra initial point in the envelope */ + if ((t->pts>=2)&&(t->env[0].pos==t->env[1].pos)) + t->index++; + + /* Fit in the envelope, still */ + if (t->index >= t->pts) + t->index = t->pts; - /* Imago Orpheus sometimes stores an extra initial point in the envelope */ - if ((t->pts>=2)&&(t->env[0].pos==t->env[1].pos)) { - t->a++; - t->b++; + t->lastvalue = t->env[t->index - 1].val; } +} + +/* Set envelope tick to the position given */ +static void SetEnvelopePosition(ENVPR *t, ENVPT *p, SWORD pos) +{ + if (t->pts > 0) { + BOOL found = 0; + UWORD i; + + for (i = 0; i < t->pts - 1; i++) { + + if ((pos >= p[i].pos) && (pos < p[i + 1].pos)) { + t->index = i + 1; + t->p = pos; + + found = 1; + break; + } + } + + /* If position is after the last envelope point, just set + it to the last one */ + if (!found) { + t->index = t->pts; + t->p = p[t->index - 1].pos; + } - /* Fit in the envelope, still */ - if (t->a >= t->pts) - t->a = t->pts - 1; - if (t->b >= t->pts) - t->b = t->pts-1; + t->lastvalue = InterpolateEnv(t->p, &t->env[t->index - 1], &t->env[t->index]); + } +} - return t->env[t->a].val; +/* Set the panning envelope tick to the position given */ +static void SetPanningEnvelopePosition(MODULE *mod, INSTRUMENT *i, ENVPR *t, ENVPT *p, SWORD pos) +{ + /* Because of a bug in FastTracker II, only the panning envelope + position is set if the volume sustain flag is set. Other players + may set the panning all the time */ + if (!(mod->flags & UF_FT2QUIRKS) || (i->volflg & EF_SUSTAIN)) + SetEnvelopePosition(t, p, pos); } /* This procedure processes all envelope types, include volume, pitch, and @@ -401,108 +436,102 @@ static SWORD StartEnvelope(ENVPR *t,UBYTE flg,UBYTE pts,UBYTE susbeg,UBYTE susen Sustain loops are loops that are only active as long as the keyoff flag is clear. When a volume envelope terminates, so does the current fadeout. */ -static SWORD ProcessEnvelope(MP_VOICE *aout, ENVPR *t, SWORD v) -{ - if (!t->pts) { /* happens with e.g. Vikings In The Hood!.xm */ - return v; - } - if (t->flg & EF_ON) { - UBYTE a, b; /* actual points in the envelope */ - UWORD p; /* the 'tick counter' - real point being played */ - - a = t->a; - b = t->b; - p = t->p; - - /* - * Sustain loop on one point (XM type). - * Not processed if KEYOFF. - * Don't move and don't interpolate when the point is reached - */ - if ((t->flg & EF_SUSTAIN) && t->susbeg == t->susend && - (!(aout->main.keyoff & KEY_OFF) && p == t->env[t->susbeg].pos)) { - v = t->env[t->susbeg].val; - } else { - /* - * All following situations will require interpolation between - * two envelope points. - */ - - /* - * Sustain loop between two points (IT type). - * Not processed if KEYOFF. - */ - /* if we were on a loop point, loop now */ - if ((t->flg & EF_SUSTAIN) && !(aout->main.keyoff & KEY_OFF) && - a >= t->susend) { - a = t->susbeg; - b = (t->susbeg==t->susend)?a:a+1; - p = t->env[a].pos; - v = t->env[a].val; - } else - /* - * Regular loop. - * Be sure to correctly handle single point loops. - */ - if ((t->flg & EF_LOOP) && a >= t->end) { - a = t->beg; - b = t->beg == t->end ? a : a + 1; - p = t->env[a].pos; - v = t->env[a].val; - } else - /* - * Non looping situations. - */ - if (a != b) - v = InterpolateEnv(p, &t->env[a], &t->env[b]); - else - v = t->env[a].val; - - /* - * Start to fade if the volume envelope is finished. - */ - if (p >= t->env[t->pts - 1].pos) { - if (t->flg & EF_VOLENV) { - aout->main.keyoff |= KEY_FADE; - if (!v) - aout->main.fadevol = 0; - } - } else { +static SWORD ProcessEnvelope(MP_VOICE *aout, ENVPR *t) +{ + if (t->pts > 0) { /* e.g. Vikings In The Hood!.xm have 0 points here */ + if (t->flg & EF_ON) { + UWORD idx = t->index; /* actual points in the envelope */ + UWORD p = t->p; /* the 'tick counter' - real point being played */ + + if (idx < t->pts) { + /* Move position and check if we reached the end of the current envelope point */ p++; - /* did pointer reach point b? */ - if (p >= t->env[b].pos) - a = b++; /* shift points a and b */ - } - t->a = a; - t->b = b; - t->p = p; - } - } - return v; -} -/* Set envelope tick to the position given */ -static void SetEnvelopePosition(ENVPR *t, ENVPT *p, SWORD pos) -{ - if (t->pts > 0) { - UWORD i; + if (p >= t->env[idx].pos) { + SWORD v = t->env[idx].val; + + /* Shift to next point */ + idx++; + + /* + * Sustain loop on one point (XM type). + * Not processed if KEYOFF. + * Don't move and don't interpolate when the point is reached + */ + if ((t->flg & EF_SUSTAIN) && t->susbeg == t->susend && + (!(aout->main.keyoff & KEY_OFF) && p == t->env[t->susbeg].pos)) { + t->lastvalue = t->env[t->susbeg].val; + t->susactive = 1; + } else { + /* + * All following situations will require interpolation between + * two envelope points. + */ + + /* + * Sustain loop between two points (IT type). + * Not processed if KEYOFF. + */ + /* if we were on a loop point, loop now */ + if ((t->flg & EF_SUSTAIN) && !(aout->main.keyoff & KEY_OFF) && + idx > t->susend) { + idx = t->susbeg + 1; + p = t->env[t->susbeg].pos; + v = t->env[t->susbeg].val; + t->susactive = 1; + } else { + t->susactive = 0; + + /* + * Regular loop. + * Be sure to correctly handle single point loops. + */ + if ((t->flg & EF_LOOP) && (idx - 1) == t->end) { /* FastTracker II does only have equal here, so using Lxx to a point after the loop, no loop is made at all. See Ebony Owl Netsuke.xml for an example */ + idx = t->beg + 1; + p = t->env[t->beg].pos; + v = t->env[t->beg].val; + } else { + /* + * Non looping situations. + */ + if (idx < t->pts) + v = InterpolateEnv(p, &t->env[idx - 1], &t->env[idx]); + else + v = t->env[idx - 1].val; + } + } - for (i = 0; i < t->pts - 1; i++) { + /* + * Start to fade if the volume envelope is finished. + */ + if (p >= t->env[t->pts - 1].pos) { + if (t->flg & EF_VOLENV) { + aout->main.keyoff |= KEY_FADE; + if (!v) + aout->main.fadevol = 0; + } + } - if ((pos >= p[i].pos) && (pos < p[i + 1].pos)) { - t->a = i; - t->b = i + 1; - t->p = pos; - return; + t->lastvalue = v; + } + + if (idx < t->pts) + t->index = idx; + else + t->index = t->pts; + } + else { + /* Only interpolate if not in sustain mode */ + if (!t->susactive && (t->index < t->pts)) + t->lastvalue = InterpolateEnv(p, &t->env[idx - 1], &t->env[idx]); + } + + t->p = p; } } - - /* If position is after the last envelope point, just set - it to the last one */ - t->a = t->pts - 1; - t->b = t->pts; - t->p = p[t->a].pos; } + + return t->lastvalue; } /* XM linear period to frequency conversion */ @@ -1613,21 +1642,18 @@ static int DoXMEffectL(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWOR dat=UniGetByte(); if ((!tick)&&(a->main.i)) { INSTRUMENT *i=a->main.i; - MP_VOICE *aout; + MP_VOICE *aout = &mod->voice[channel]; - if ((aout=a->slave) != NULL) { - if (aout->venv.env) { - SetEnvelopePosition(&aout->venv, i->volenv, dat); - } - if (aout->penv.env) { - /* Because of a bug in FastTracker II, only the panning envelope - position is set if the volume sustain flag is set. Other players - may set the panning all the time */ - if (!(mod->flags & UF_FT2QUIRKS) || (i->volflg & EF_SUSTAIN)) { - SetEnvelopePosition(&aout->penv, i->panenv, dat); - } - } - } + /* We need to set this, in case if a note is played on + the same row, the calculated envelope position will + be overwritten in StartEnvelope() */ + aout->envstartpos = dat; + + if (aout->venv.env) + SetEnvelopePosition(&aout->venv, i->volenv, dat); + + if (aout->penv.env) + SetPanningEnvelopePosition(mod, i, &aout->penv, i->panenv, dat); } return 0; @@ -2806,29 +2832,43 @@ static void pt_UpdateVoices(MODULE *mod, int max_volume) envpan = PAN_CENTER; envpit = 32; if (i && ((aout->main.kick==KICK_NOTE)||(aout->main.kick==KICK_ENV))) { - if (aout->main.volflg & EF_ON) - envvol = StartEnvelope(&aout->venv,aout->main.volflg, + if (aout->main.volflg & EF_ON) { + StartEnvelope(&aout->venv,aout->main.volflg, i->volpts,i->volsusbeg,i->volsusend, - i->volbeg,i->volend,i->volenv,aout->main.keyoff); - if (aout->main.panflg & EF_ON) - envpan = StartEnvelope(&aout->penv,aout->main.panflg, + i->volbeg,i->volend,i->volenv,aout->main.keyoff,256); + if (aout->envstartpos != 0) + SetEnvelopePosition(&aout->venv,i->volenv,aout->envstartpos); + + envvol = aout->venv.lastvalue; + } + if (aout->main.panflg & EF_ON) { + StartEnvelope(&aout->penv,aout->main.panflg, i->panpts,i->pansusbeg,i->pansusend, - i->panbeg,i->panend,i->panenv,aout->main.keyoff); - if (aout->main.pitflg & EF_ON) - envpit = StartEnvelope(&aout->cenv,aout->main.pitflg, + i->panbeg,i->panend,i->panenv,aout->main.keyoff,PAN_CENTER); + if (aout->envstartpos != 0) + SetPanningEnvelopePosition(mod,i,&aout->penv,i->panenv,aout->envstartpos); + + envpan = aout->penv.lastvalue; + } + if (aout->main.pitflg & EF_ON) { + StartEnvelope(&aout->cenv,aout->main.pitflg, i->pitpts,i->pitsusbeg,i->pitsusend, - i->pitbeg,i->pitend,i->pitenv,aout->main.keyoff); + i->pitbeg,i->pitend,i->pitenv,aout->main.keyoff,32); + envpit = aout->cenv.lastvalue; + } if (aout->cenv.flg & EF_ON) aout->masterperiod=GetPeriod(mod->flags, (UWORD)aout->main.note<<1, aout->master->speed); + + aout->envstartpos = 0; } else { if (aout->main.volflg & EF_ON) - envvol = ProcessEnvelope(aout,&aout->venv,256); + envvol = ProcessEnvelope(aout,&aout->venv); if (aout->main.panflg & EF_ON) - envpan = ProcessEnvelope(aout,&aout->penv,PAN_CENTER); + envpan = ProcessEnvelope(aout,&aout->penv); if (aout->main.pitflg & EF_ON) - envpit = ProcessEnvelope(aout,&aout->cenv,32); + envpit = ProcessEnvelope(aout,&aout->cenv); } if (aout->main.kick == KICK_NOTE) { aout->main.kick_flag = 1; @@ -3015,13 +3055,18 @@ static void pt_Notes(MODULE *mod) break; case UNI_INSTRUMENT: inst=UniGetByte(); - if (inst>=mod->numins) break; /* safety valve */ + if (inst>=mod->numins) { + a->main.i=NULL; + a->main.s=NULL; + a->main.sample=-1; + break; /* safety valve */ + } funky|=2; a->main.i=(mod->flags & UF_INST)?&mod->instruments[inst]:NULL; a->retrig=0; a->s3mtremor=0; a->ultoffset=0; - a->main.sample=inst; + a->main.sample=(SBYTE)inst; break; default: UniSkipOpcode(); @@ -3037,6 +3082,9 @@ static void pt_Notes(MODULE *mod) s=&mod->samples[i->samplenumber[a->anote]]; a->main.note=i->samplenote[a->anote]; } else { + if (a->main.sample < 0) + continue; + a->main.note=a->anote; s=&mod->samples[a->main.sample]; } From 96759160262f9de0615fe5190a73ea76249f90ab Mon Sep 17 00:00:00 2001 From: Thomas Neumann Date: Sun, 15 Jan 2023 23:55:02 +0300 Subject: [PATCH 2/2] Envelope fixes [2/2]: Updated the envelopes, so there are two separate handlers. One for XM/IMF and one for IT. Almost totally rewrote the envelope handling, so it behaves like FastTracker II does, specially when used together with XM effect L. These two fixes make Ebony Owl Netsuke.xm to play correctly. --- libmikmod/include/mikmod_internals.h | 23 +- libmikmod/loaders/load_it.c | 4 +- libmikmod/playercode/mplayer.c | 329 ++++++++++++++++----------- 3 files changed, 212 insertions(+), 144 deletions(-) diff --git a/libmikmod/include/mikmod_internals.h b/libmikmod/include/mikmod_internals.h index 1d5d1c29..e3b3d6d1 100644 --- a/libmikmod/include/mikmod_internals.h +++ b/libmikmod/include/mikmod_internals.h @@ -424,6 +424,7 @@ typedef struct FILTER { #define EF_SUSTAIN 2 #define EF_LOOP 4 #define EF_VOLENV 8 +#define EF_ITMODE 16 /* New Note Action Flags */ #define NNA_CUT 0 @@ -461,17 +462,17 @@ typedef struct FILTER { #define LAST_PATTERN (UWORD)(-1) /* special ``end of song'' pattern */ typedef struct ENVPR { - UBYTE flg; /* envelope flag */ - UBYTE pts; /* number of envelope points */ - UBYTE susbeg; /* envelope sustain index begin */ - UBYTE susend; /* envelope sustain index end */ - BOOL susactive;/* Indicate if sustain is active (no interpolation) */ - UBYTE beg; /* envelope loop begin */ - UBYTE end; /* envelope loop end */ - SWORD p; /* current envelope counter */ - UWORD index; /* envelope index for the point after the current one */ - SWORD lastvalue;/* the last calculated value */ - ENVPT* env; /* envelope points */ + UBYTE flg; /* envelope flag */ + UBYTE pts; /* number of envelope points */ + UBYTE susbeg; /* envelope sustain index begin */ + UBYTE susend; /* envelope sustain index end */ + UBYTE loopbeg; /* envelope loop begin */ + UBYTE loopend; /* envelope loop end */ + SWORD tick; /* current envelope counter */ + UWORD index; /* envelope index for the point after the current one */ + BOOL interpolate; /* Indicate if interpolation should be done */ + SWORD lastvalue; /* the last calculated value */ + ENVPT* env; /* envelope points */ } ENVPR; typedef struct MP_CHANNEL { diff --git a/libmikmod/loaders/load_it.c b/libmikmod/loaders/load_it.c index 528e282c..951f3217 100644 --- a/libmikmod/loaders/load_it.c +++ b/libmikmod/loaders/load_it.c @@ -827,7 +827,9 @@ static BOOL IT_Load(BOOL curious) return 0; } - d->volflg|=EF_VOLENV; + d->volflg|=(EF_VOLENV | EF_ITMODE); + d->panflg|=EF_ITMODE; + d->pitflg|=EF_ITMODE; d->insname = DupStr(ih.name,26,0); d->nnatype = ih.nna & NNA_MASK; diff --git a/libmikmod/playercode/mplayer.c b/libmikmod/playercode/mplayer.c index d0e323e8..505aae9c 100644 --- a/libmikmod/playercode/mplayer.c +++ b/libmikmod/playercode/mplayer.c @@ -349,18 +349,17 @@ static SWORD DoPan(SWORD envpan,SWORD pan) return (newpanPAN_RIGHT?PAN_RIGHT:newpan); } -static void StartEnvelope(ENVPR *t,UBYTE flg,UBYTE pts,UBYTE susbeg,UBYTE susend,UBYTE beg,UBYTE end,ENVPT *p,UBYTE keyoff,SWORD defaultvalue) +static void StartEnvelope(ENVPR *t,UBYTE flg,UBYTE pts,UBYTE susbeg,UBYTE susend,UBYTE loopbeg,UBYTE loopend,ENVPT *p,SWORD defaultvalue) { t->flg=flg; t->pts=pts; t->susbeg=susbeg; t->susend=susend; - t->susactive=0; - t->beg=beg; - t->end=end; + t->loopbeg=loopbeg; + t->loopend=loopend; t->env=p; - t->p=0; - t->index=1; + t->tick=-1; + t->index=0; if (!t->pts) { /* FIXME: bad/crafted file. better/more general solution? */ t->index=0; @@ -373,37 +372,65 @@ static void StartEnvelope(ENVPR *t,UBYTE flg,UBYTE pts,UBYTE susbeg,UBYTE susend /* Fit in the envelope, still */ if (t->index >= t->pts) t->index = t->pts; - - t->lastvalue = t->env[t->index - 1].val; } } /* Set envelope tick to the position given */ static void SetEnvelopePosition(ENVPR *t, ENVPT *p, SWORD pos) { - if (t->pts > 0) { - BOOL found = 0; - UWORD i; + if (t->flg & EF_ON) { + SWORD idx = 0; + BOOL envUpdate = 1; + SWORD tick = pos; - for (i = 0; i < t->pts - 1; i++) { + t->tick = pos - 1; - if ((pos >= p[i].pos) && (pos < p[i + 1].pos)) { - t->index = i + 1; - t->p = pos; + if (t->pts > 1) { + UWORD i; + idx++; - found = 1; - break; + for (i = 0; i < t->pts - 1; i++) { + if (tick < p[idx].pos) { + idx--; + + tick -= p[idx].pos; + if (tick == 0) { + envUpdate = 0; + break; + } + + if (p[idx + 1].pos <= p[idx].pos) + break; + + t->lastvalue = InterpolateEnv(tick, &t->env[idx], &t->env[idx + 1]); + t->interpolate = 1; + + idx++; + envUpdate = 0; + break; + } + + idx++; } + + if (envUpdate) + idx--; + } + + if (envUpdate) { + t->interpolate = 0; + t->lastvalue = p[idx].val; } /* If position is after the last envelope point, just set it to the last one */ - if (!found) { - t->index = t->pts; - t->p = p[t->index - 1].pos; + if (idx >= t->pts) { + idx = t->pts - 1; + if (idx < 0) + idx = 0; } - t->lastvalue = InterpolateEnv(t->p, &t->env[t->index - 1], &t->env[t->index]); + t->index = idx; } } @@ -417,6 +444,136 @@ static void SetPanningEnvelopePosition(MODULE *mod, INSTRUMENT *i, ENVPR *t, ENV SetEnvelopePosition(t, p, pos); } +/* Calculates the next envelope value the XM way, based on the implementation from ft2-clone */ +static inline void ProcessEnvelopeXm(MP_VOICE *aout, ENVPR *t) +{ + UWORD idx = t->index; + const BOOL keyOff = aout->main.keyoff & KEY_OFF; + + if (keyOff) { + if (t->tick >= t->env[idx].pos) + t->tick = t->env[idx].pos - 1; + } + + // Move position and check if we reached the end of the current envelope point + t->tick++; + + if (t->tick == t->env[idx].pos) { + t->lastvalue = t->env[idx].val; + + // Shift to next point + idx++; + + // Is loop enabled, check if we need to do the looping + if (t->flg & EF_LOOP) { + // If the Lxx effect has been used to set to a point after the loop, no loop is made at all. See Ebony Owl Netsuke.xml for an example + if ((idx - 1) == t->loopend) + { + // Only one sustain point in XM, so SusBeg and SusEnd have the same value + if (!(t->flg & EF_SUSTAIN) || ((idx - 1) != t->susend) || !keyOff) { + idx = t->loopbeg; + t->tick = t->env[idx].pos; + t->lastvalue = t->env[idx].val; + idx++; + } + } + } + + if (idx < t->pts) { + BOOL interpolateFlag = 1; + if ((t->flg & EF_SUSTAIN) && !keyOff) { + if ((idx - 1) == t->susend) { + t->interpolate = 0; + interpolateFlag = 0; + } + } + + if (interpolateFlag) { + t->index = idx; + t->interpolate = 0; + + if (t->env[idx].pos > t->env[idx - 1].pos) + t->interpolate = 1; + } + } + else + t->interpolate = 0; + } + + if (t->interpolate) + t->lastvalue = InterpolateEnv(t->tick, &t->env[idx - 1], &t->env[idx]); +} + +/* Calculates the next envelope value the IT way, based on the implementation from Schismtracker */ +static inline void ProcessEnvelopeIt(MP_VOICE *aout, ENVPR *t) +{ + SWORD start, end; + const BOOL keyOff = aout->main.keyoff & KEY_OFF; + BOOL fadeFlag = 0; + SWORD pointPos, newPos; + SWORD tick; + UWORD i, idx; + + t->tick++; + + if ((t->flg & EF_SUSTAIN) && !keyOff) { + start = t->env[t->susbeg].pos; + end = t->env[t->susend].pos + 1; + } + else if (t->flg & EF_LOOP) { + start = t->env[t->loopbeg].pos; + end = t->env[t->loopend].pos + 1; + } + else { + // End of envelope + start = end = t->env[t->pts - 1].pos; + fadeFlag = 1; + } + + if (t->tick >= end) { + if (fadeFlag && (t->flg & EF_VOLENV)) { + aout->main.keyoff |= KEY_FADE; + + if (t->env[t->pts - 1].val == 0) + aout->main.fadevol = 0; + } + + t->tick = start; + } + + tick = t->tick; + idx = t->pts - 1; + + // Find the right point to use + for (i = 0; i < t->pts - 1; i++) { + if (tick <= t->env[i].pos) { + idx = i; + break; + } + } + + pointPos = t->env[idx].pos; + + if (tick >= pointPos) { + t->lastvalue = t->env[idx].val; + newPos = pointPos; + } + else if (idx != 0) { + t->lastvalue = t->env[idx - 1].val; + newPos = t->env[idx - 1].pos; + } + else { + t->lastvalue = 0; + newPos = 0; + } + + if (tick > pointPos) + tick = pointPos; + + if ((pointPos > newPos) && (tick > newPos)) + t->lastvalue += ((tick - newPos) * (t->env[idx].val - t->lastvalue)) / (pointPos - newPos); +} + /* This procedure processes all envelope types, include volume, pitch, and panning. Envelopes are defined by a set of points, each with a magnitude [relating either to volume, panning position, or pitch modifier] and a tick @@ -440,94 +597,14 @@ static SWORD ProcessEnvelope(MP_VOICE *aout, ENVPR *t) { if (t->pts > 0) { /* e.g. Vikings In The Hood!.xm have 0 points here */ if (t->flg & EF_ON) { - UWORD idx = t->index; /* actual points in the envelope */ - UWORD p = t->p; /* the 'tick counter' - real point being played */ - - if (idx < t->pts) { - /* Move position and check if we reached the end of the current envelope point */ - p++; - - if (p >= t->env[idx].pos) { - SWORD v = t->env[idx].val; - - /* Shift to next point */ - idx++; - - /* - * Sustain loop on one point (XM type). - * Not processed if KEYOFF. - * Don't move and don't interpolate when the point is reached - */ - if ((t->flg & EF_SUSTAIN) && t->susbeg == t->susend && - (!(aout->main.keyoff & KEY_OFF) && p == t->env[t->susbeg].pos)) { - t->lastvalue = t->env[t->susbeg].val; - t->susactive = 1; - } else { - /* - * All following situations will require interpolation between - * two envelope points. - */ - - /* - * Sustain loop between two points (IT type). - * Not processed if KEYOFF. - */ - /* if we were on a loop point, loop now */ - if ((t->flg & EF_SUSTAIN) && !(aout->main.keyoff & KEY_OFF) && - idx > t->susend) { - idx = t->susbeg + 1; - p = t->env[t->susbeg].pos; - v = t->env[t->susbeg].val; - t->susactive = 1; - } else { - t->susactive = 0; - - /* - * Regular loop. - * Be sure to correctly handle single point loops. - */ - if ((t->flg & EF_LOOP) && (idx - 1) == t->end) { /* FastTracker II does only have equal here, so using Lxx to a point after the loop, no loop is made at all. See Ebony Owl Netsuke.xml for an example */ - idx = t->beg + 1; - p = t->env[t->beg].pos; - v = t->env[t->beg].val; - } else { - /* - * Non looping situations. - */ - if (idx < t->pts) - v = InterpolateEnv(p, &t->env[idx - 1], &t->env[idx]); - else - v = t->env[idx - 1].val; - } - } - - /* - * Start to fade if the volume envelope is finished. - */ - if (p >= t->env[t->pts - 1].pos) { - if (t->flg & EF_VOLENV) { - aout->main.keyoff |= KEY_FADE; - if (!v) - aout->main.fadevol = 0; - } - } - - t->lastvalue = v; - } - - if (idx < t->pts) - t->index = idx; - else - t->index = t->pts; - } - else { - /* Only interpolate if not in sustain mode */ - if (!t->susactive && (t->index < t->pts)) - t->lastvalue = InterpolateEnv(p, &t->env[idx - 1], &t->env[idx]); - } - - t->p = p; - } + /* FastTracker II will first check for loop/sustain loops after a + point has been processed, while Impulse Tracker will check on + each tick + some other small differences + */ + if (t->flg & EF_ITMODE) + ProcessEnvelopeIt(aout, t); + else + ProcessEnvelopeXm(aout, t); } } @@ -2833,43 +2910,31 @@ static void pt_UpdateVoices(MODULE *mod, int max_volume) envpit = 32; if (i && ((aout->main.kick==KICK_NOTE)||(aout->main.kick==KICK_ENV))) { if (aout->main.volflg & EF_ON) { - StartEnvelope(&aout->venv,aout->main.volflg, - i->volpts,i->volsusbeg,i->volsusend, - i->volbeg,i->volend,i->volenv,aout->main.keyoff,256); + StartEnvelope(&aout->venv,aout->main.volflg, i->volpts,i->volsusbeg,i->volsusend,i->volbeg,i->volend,i->volenv,256); if (aout->envstartpos != 0) SetEnvelopePosition(&aout->venv,i->volenv,aout->envstartpos); - - envvol = aout->venv.lastvalue; } if (aout->main.panflg & EF_ON) { - StartEnvelope(&aout->penv,aout->main.panflg, - i->panpts,i->pansusbeg,i->pansusend, - i->panbeg,i->panend,i->panenv,aout->main.keyoff,PAN_CENTER); + StartEnvelope(&aout->penv,aout->main.panflg,i->panpts,i->pansusbeg,i->pansusend,i->panbeg,i->panend,i->panenv,PAN_CENTER); if (aout->envstartpos != 0) SetPanningEnvelopePosition(mod,i,&aout->penv,i->panenv,aout->envstartpos); - - envpan = aout->penv.lastvalue; - } - if (aout->main.pitflg & EF_ON) { - StartEnvelope(&aout->cenv,aout->main.pitflg, - i->pitpts,i->pitsusbeg,i->pitsusend, - i->pitbeg,i->pitend,i->pitenv,aout->main.keyoff,32); - envpit = aout->cenv.lastvalue; } + if (aout->main.pitflg & EF_ON) + StartEnvelope(&aout->cenv,aout->main.pitflg,i->pitpts,i->pitsusbeg,i->pitsusend,i->pitbeg,i->pitend,i->pitenv,32); if (aout->cenv.flg & EF_ON) aout->masterperiod=GetPeriod(mod->flags, (UWORD)aout->main.note<<1, aout->master->speed); aout->envstartpos = 0; - } else { - if (aout->main.volflg & EF_ON) - envvol = ProcessEnvelope(aout,&aout->venv); - if (aout->main.panflg & EF_ON) - envpan = ProcessEnvelope(aout,&aout->penv); - if (aout->main.pitflg & EF_ON) - envpit = ProcessEnvelope(aout,&aout->cenv); } + if (aout->main.volflg & EF_ON) + envvol = ProcessEnvelope(aout,&aout->venv); + if (aout->main.panflg & EF_ON) + envpan = ProcessEnvelope(aout,&aout->penv); + if (aout->main.pitflg & EF_ON) + envpit = ProcessEnvelope(aout,&aout->cenv); + if (aout->main.kick == KICK_NOTE) { aout->main.kick_flag = 1; }