From ef1599ba56dfbb1bb520e44600188301a53c3667 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 sample in a channel to be played, if an illegal instrument is set afterwards. 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 | 328 +++++++++++++++------------ 2 files changed, 194 insertions(+), 143 deletions(-) diff --git a/libmikmod/include/mikmod_internals.h b/libmikmod/include/mikmod_internals.h index 1f355b87..9b59e24a 100644 --- a/libmikmod/include/mikmod_internals.h +++ b/libmikmod/include/mikmod_internals.h @@ -470,18 +470,19 @@ typedef struct ENVPR { UBYTE pts; /* number of envelope points */ UBYTE susbeg; /* envelope sustain index begin */ UBYTE susend; /* envelope sustain index end */ + BOOL susactive;/* whether 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 lastval; /* 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 */ @@ -595,6 +596,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 1aca8721..5e352542 100644 --- a/libmikmod/playercode/mplayer.c +++ b/libmikmod/playercode/mplayer.c @@ -349,43 +349,78 @@ 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->lastval=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; + + t->lastval = t->env[t->index - 1].val; } /* Ignore junk loops */ - if (beg > pts || beg > end) + if (loopbeg > pts || loopbeg > loopend) t->flg &= ~EF_LOOP; if (susbeg > pts || susbeg > susend) t->flg &= ~EF_SUSTAIN; +} - /* 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++; - } +/* 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->lastval = 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 @@ -407,108 +442,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->lastval = 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->lastval = 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->lastval = 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->lastval; } /* XM linear period to frequency conversion */ @@ -1619,21 +1648,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; @@ -2812,29 +2838,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.lastval; + } + 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.lastval; + } + 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.lastval; + } 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; @@ -3021,13 +3061,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(); @@ -3043,6 +3088,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 a660ca6fafef7f09752fa5ff76e96bfebb888fb7 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 | 9 +- libmikmod/loaders/load_it.c | 4 +- libmikmod/playercode/mplayer.c | 329 ++++++++++++++++----------- 3 files changed, 205 insertions(+), 137 deletions(-) diff --git a/libmikmod/include/mikmod_internals.h b/libmikmod/include/mikmod_internals.h index 9b59e24a..6ba807c7 100644 --- a/libmikmod/include/mikmod_internals.h +++ b/libmikmod/include/mikmod_internals.h @@ -429,6 +429,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 @@ -470,12 +471,12 @@ typedef struct ENVPR { UBYTE pts; /* number of envelope points */ UBYTE susbeg; /* envelope sustain index begin */ UBYTE susend; /* envelope sustain index end */ - BOOL susactive;/* whether sustain is active (no interpolation) */ - UBYTE beg; /* envelope loop begin */ - UBYTE end; /* envelope loop end */ - SWORD p; /* current envelope counter */ + 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 */ SWORD lastval; /* the last calculated value */ + BOOL interpolate;/* whether to interpolate */ ENVPT* env; /* envelope points */ } ENVPR; diff --git a/libmikmod/loaders/load_it.c b/libmikmod/loaders/load_it.c index 68dc619c..66075f2d 100644 --- a/libmikmod/loaders/load_it.c +++ b/libmikmod/loaders/load_it.c @@ -810,7 +810,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 5e352542..29a810da 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,8 +372,6 @@ 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->lastval = t->env[t->index - 1].val; } /* Ignore junk loops */ @@ -387,29 +384,59 @@ static void StartEnvelope(ENVPR *t,UBYTE flg,UBYTE pts,UBYTE susbeg,UBYTE susend /* 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->lastval = 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->lastval = 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->lastval = InterpolateEnv(t->p, &t->env[t->index - 1], &t->env[t->index]); + t->index = idx; } } @@ -423,6 +450,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->lastval = 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->lastval = 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->lastval = 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->lastval = t->env[idx].val; + newPos = pointPos; + } + else if (idx != 0) { + t->lastval = t->env[idx - 1].val; + newPos = t->env[idx - 1].pos; + } + else { + t->lastval = 0; + newPos = 0; + } + + if (tick > pointPos) + tick = pointPos; + + if ((pointPos > newPos) && (tick > newPos)) + t->lastval += ((tick - newPos) * (t->env[idx].val - t->lastval)) / (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 @@ -446,94 +603,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->lastval = 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->lastval = 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->lastval = 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); } } @@ -2839,43 +2916,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.lastval; } 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.lastval; - } - 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.lastval; } + 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; }