mirror of
https://github.com/ZDoom/raze-gles.git
synced 2024-12-27 20:20:40 +00:00
d715918e45
git-svn-id: https://svn.eduke32.com/eduke32@6158 1a8010ca-5511-0410-912e-c29ae57300e0
1613 lines
36 KiB
C
1613 lines
36 KiB
C
/* Extended Module Player
|
|
* Copyright (C) 1996-2016 Claudio Matsuoka and Hipolito Carraro Jr
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include "common.h"
|
|
#include "player.h"
|
|
#include "effects.h"
|
|
#include "virtual.h"
|
|
#include "period.h"
|
|
|
|
#ifndef LIBXMP_CORE_PLAYER
|
|
#include "med_extras.h"
|
|
#endif
|
|
|
|
|
|
static inline int is_valid_note(int note)
|
|
{
|
|
return (note >= 0 && note < XMP_MAX_KEYS);
|
|
}
|
|
|
|
static struct xmp_subinstrument *get_subinstrument(struct context_data *ctx,
|
|
int ins, int key)
|
|
{
|
|
struct module_data *m = &ctx->m;
|
|
struct xmp_module *mod = &m->mod;
|
|
struct xmp_instrument *instrument;
|
|
|
|
if (IS_VALID_INSTRUMENT(ins)) {
|
|
instrument = &mod->xxi[ins];
|
|
if (is_valid_note(key)) {
|
|
int mapped = instrument->map[key].ins;
|
|
if (mapped != 0xff && mapped >= 0 && mapped < instrument->nsm)
|
|
return &instrument->sub[mapped];
|
|
} else {
|
|
if (mod->xxi[ins].nsm > 0) {
|
|
return &instrument->sub[0];
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void reset_envelopes(struct context_data *ctx, struct channel_data *xc)
|
|
{
|
|
struct module_data *m = &ctx->m;
|
|
struct xmp_module *mod = &m->mod;
|
|
|
|
if (!IS_VALID_INSTRUMENT(xc->ins))
|
|
return;
|
|
|
|
RESET_NOTE(NOTE_ENV_END);
|
|
|
|
xc->v_idx = -1;
|
|
xc->p_idx = -1;
|
|
xc->f_idx = -1;
|
|
}
|
|
|
|
#ifndef LIBXMP_CORE_DISABLE_IT
|
|
|
|
static void reset_envelopes_carry(struct context_data *ctx,
|
|
struct channel_data *xc)
|
|
{
|
|
struct module_data *m = &ctx->m;
|
|
struct xmp_module *mod = &m->mod;
|
|
struct xmp_instrument *xxi;
|
|
|
|
if (!IS_VALID_INSTRUMENT(xc->ins))
|
|
return;
|
|
|
|
RESET_NOTE(NOTE_ENV_END);
|
|
|
|
xxi = libxmp_get_instrument(ctx, xc->ins);
|
|
|
|
/* Reset envelope positions */
|
|
if (~xxi->aei.flg & XMP_ENVELOPE_CARRY) {
|
|
xc->v_idx = -1;
|
|
}
|
|
if (~xxi->pei.flg & XMP_ENVELOPE_CARRY) {
|
|
xc->p_idx = -1;
|
|
}
|
|
if (~xxi->fei.flg & XMP_ENVELOPE_CARRY) {
|
|
xc->f_idx = -1;
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
static void set_effect_defaults(struct context_data *ctx, int note,
|
|
struct xmp_subinstrument *sub,
|
|
struct channel_data *xc, int is_toneporta)
|
|
{
|
|
struct module_data *m = &ctx->m;
|
|
struct xmp_module *mod = &m->mod;
|
|
struct smix_data *smix = &ctx->smix;
|
|
|
|
if (sub != NULL && note >= 0) {
|
|
struct xmp_instrument *xxi;
|
|
|
|
if (xc->ins >= mod->ins) {
|
|
xxi = &smix->xxi[xc->ins - mod->ins];
|
|
} else {
|
|
xxi = &mod->xxi[xc->ins];
|
|
}
|
|
|
|
if (!HAS_QUIRK(QUIRK_PROTRACK)) {
|
|
xc->finetune = sub->fin;
|
|
}
|
|
xc->gvl = sub->gvl;
|
|
|
|
#ifndef LIBXMP_CORE_DISABLE_IT
|
|
if (sub->ifc & 0x80) {
|
|
xc->filter.cutoff = (sub->ifc - 0x80) * 2;
|
|
} else if (~xxi->fei.flg & XMP_ENVELOPE_FLT) {
|
|
xc->filter.cutoff = 0xff;
|
|
}
|
|
xc->filter.envelope = 0x100;
|
|
|
|
if (sub->ifr & 0x80) {
|
|
xc->filter.resonance = (sub->ifr - 0x80) * 2;
|
|
} /* else {
|
|
xc->filter.resonance = 0;
|
|
} */
|
|
#endif
|
|
|
|
libxmp_lfo_set_depth(&xc->insvib.lfo, sub->vde);
|
|
libxmp_lfo_set_rate(&xc->insvib.lfo, sub->vra >> 2);
|
|
libxmp_lfo_set_waveform(&xc->insvib.lfo, sub->vwf);
|
|
xc->insvib.sweep = sub->vsw;
|
|
|
|
libxmp_lfo_set_phase(&xc->vibrato.lfo, 0);
|
|
libxmp_lfo_set_phase(&xc->tremolo.lfo, 0);
|
|
}
|
|
|
|
xc->delay = 0;
|
|
xc->tremor.up = xc->tremor.down = 0;
|
|
|
|
/* Reset arpeggio */
|
|
xc->arpeggio.val[0] = 0;
|
|
xc->arpeggio.count = 0;
|
|
xc->arpeggio.size = 1;
|
|
}
|
|
|
|
/* From OpenMPT PortaTarget.mod:
|
|
* "A new note (with no portamento command next to it) does not reset the
|
|
* portamento target. That is, if a previous portamento has not finished yet,
|
|
* calling 3xx or 5xx after the new note will slide it towards the old target.
|
|
* Once the portamento target period is reached, the target is reset. This
|
|
* means that if the period is modified by another slide (e.g. 1xx or 2xx),
|
|
* a following 3xx will not slide back to the original target."
|
|
*/
|
|
static void set_period(struct context_data *ctx, int note,
|
|
struct xmp_subinstrument *sub,
|
|
struct channel_data *xc, int is_toneporta)
|
|
{
|
|
struct module_data *m = &ctx->m;
|
|
|
|
if (sub != NULL && note >= 0) {
|
|
double per = libxmp_note_to_period(ctx, note, xc->finetune,
|
|
xc->per_adj);
|
|
|
|
if (!HAS_QUIRK(QUIRK_PROTRACK) || (note > 0 && is_toneporta)) {
|
|
xc->porta.target = per;
|
|
}
|
|
|
|
if (xc->period < 1 || !is_toneporta) {
|
|
xc->period = per;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* From OpenMPT Porta-Pickup.xm:
|
|
* "An instrument number should not reset the current portamento target. The
|
|
* portamento target is valid until a new target is specified by combining a
|
|
* note and a portamento effect."
|
|
*/
|
|
static void set_period_ft2(struct context_data *ctx, int note,
|
|
struct xmp_subinstrument *sub,
|
|
struct channel_data *xc, int is_toneporta)
|
|
{
|
|
if (note > 0 && is_toneporta) {
|
|
xc->porta.target = libxmp_note_to_period(ctx, note, xc->finetune,
|
|
xc->per_adj);
|
|
}
|
|
if (sub != NULL && note >= 0) {
|
|
if (xc->period < 1 || !is_toneporta) {
|
|
xc->period = libxmp_note_to_period(ctx, note, xc->finetune,
|
|
xc->per_adj);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#ifndef LIBXMP_CORE_PLAYER
|
|
#define IS_SFX_PITCH(x) ((x) == FX_PITCH_ADD || (x) == FX_PITCH_SUB)
|
|
#define IS_TONEPORTA(x) ((x) == FX_TONEPORTA || (x) == FX_TONE_VSLIDE \
|
|
|| (x) == FX_PER_TPORTA)
|
|
#else
|
|
#define IS_TONEPORTA(x) ((x) == FX_TONEPORTA || (x) == FX_TONE_VSLIDE)
|
|
#endif
|
|
|
|
#define set_patch(ctx,chn,ins,smp,note) \
|
|
libxmp_virt_setpatch(ctx, chn, ins, smp, note, 0, 0, 0)
|
|
|
|
static int read_event_mod(struct context_data *ctx, struct xmp_event *e, int chn)
|
|
{
|
|
struct player_data *p = &ctx->p;
|
|
struct module_data *m = &ctx->m;
|
|
struct xmp_module *mod = &m->mod;
|
|
struct channel_data *xc = &p->xc_data[chn];
|
|
int note;
|
|
struct xmp_subinstrument *sub;
|
|
int new_invalid_ins = 0;
|
|
int is_toneporta;
|
|
int use_ins_vol;
|
|
|
|
xc->flags = 0;
|
|
note = -1;
|
|
is_toneporta = 0;
|
|
use_ins_vol = 0;
|
|
|
|
if (IS_TONEPORTA(e->fxt) || IS_TONEPORTA(e->f2t)) {
|
|
is_toneporta = 1;
|
|
}
|
|
|
|
/* Check instrument */
|
|
|
|
if (e->ins) {
|
|
int ins = e->ins - 1;
|
|
use_ins_vol = 1;
|
|
SET(NEW_INS);
|
|
xc->fadeout = 0x10000; /* for painlace.mod pat 0 ch 3 echo */
|
|
xc->per_flags = 0;
|
|
xc->offset.val = 0;
|
|
RESET_NOTE(NOTE_RELEASE|NOTE_FADEOUT);
|
|
|
|
if (IS_VALID_INSTRUMENT(ins)) {
|
|
sub = get_subinstrument(ctx, ins, e->note - 1);
|
|
|
|
if (is_toneporta) {
|
|
/* Get new instrument volume */
|
|
if (sub != NULL) {
|
|
/* Dennis Lindroos: instrument volume
|
|
* is not used on split channels
|
|
*/
|
|
if (!xc->split) {
|
|
xc->volume = sub->vol;
|
|
}
|
|
use_ins_vol = 0;
|
|
}
|
|
} else {
|
|
xc->ins = ins;
|
|
xc->ins_fade = mod->xxi[ins].rls;
|
|
|
|
if (sub != NULL) {
|
|
if (HAS_QUIRK(QUIRK_PROTRACK)) {
|
|
xc->finetune = sub->fin;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
new_invalid_ins = 1;
|
|
libxmp_virt_resetchannel(ctx, chn);
|
|
}
|
|
}
|
|
|
|
/* Check note */
|
|
|
|
if (e->note) {
|
|
SET(NEW_NOTE);
|
|
|
|
if (e->note == XMP_KEY_OFF) {
|
|
SET_NOTE(NOTE_RELEASE);
|
|
use_ins_vol = 0;
|
|
} else if (!is_toneporta) {
|
|
xc->key = e->note - 1;
|
|
RESET_NOTE(NOTE_END);
|
|
|
|
sub = get_subinstrument(ctx, xc->ins, xc->key);
|
|
|
|
if (!new_invalid_ins && sub != NULL) {
|
|
int transp = mod->xxi[xc->ins].map[xc->key].xpo;
|
|
int smp;
|
|
|
|
note = xc->key + sub->xpo + transp;
|
|
smp = sub->sid;
|
|
|
|
if (mod->xxs[smp].len == 0) {
|
|
smp = -1;
|
|
}
|
|
|
|
if (smp >= 0 && smp < mod->smp) {
|
|
set_patch(ctx, chn, xc->ins, smp, note);
|
|
xc->smp = smp;
|
|
}
|
|
} else {
|
|
xc->flags = 0;
|
|
use_ins_vol = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
sub = get_subinstrument(ctx, xc->ins, xc->key);
|
|
|
|
set_effect_defaults(ctx, note, sub, xc, is_toneporta);
|
|
if (e->ins && sub != NULL) {
|
|
reset_envelopes(ctx, xc);
|
|
}
|
|
|
|
/* Process new volume */
|
|
if (e->vol) {
|
|
xc->volume = e->vol - 1;
|
|
SET(NEW_VOL);
|
|
}
|
|
|
|
/* Secondary effect handled first */
|
|
libxmp_process_fx(ctx, xc, chn, e, 1);
|
|
libxmp_process_fx(ctx, xc, chn, e, 0);
|
|
|
|
#ifndef LIBXMP_CORE_PLAYER
|
|
if (IS_SFX_PITCH(e->fxt)) {
|
|
xc->period = libxmp_note_to_period(ctx, note, xc->finetune,
|
|
xc->per_adj);
|
|
} else
|
|
#endif
|
|
{
|
|
set_period(ctx, note, sub, xc, is_toneporta);
|
|
}
|
|
|
|
if (sub == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
if (note >= 0) {
|
|
xc->note = note;
|
|
libxmp_virt_voicepos(ctx, chn, xc->offset.val);
|
|
}
|
|
|
|
if (TEST(OFFSET)) {
|
|
if (HAS_QUIRK(QUIRK_PROTRACK) || p->flags & XMP_FLAGS_FX9BUG) {
|
|
xc->offset.val += xc->offset.val2;
|
|
}
|
|
RESET(OFFSET);
|
|
}
|
|
|
|
if (use_ins_vol && !TEST(NEW_VOL) && !xc->split) {
|
|
xc->volume = sub->vol;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sustain_check(struct xmp_envelope *env, int idx)
|
|
{
|
|
return (env &&
|
|
(env->flg & XMP_ENVELOPE_ON) &&
|
|
(~env->flg & XMP_ENVELOPE_LOOP) &&
|
|
idx == env->data[env->sus << 1]);
|
|
}
|
|
|
|
static int read_event_ft2(struct context_data *ctx, struct xmp_event *e, int chn)
|
|
{
|
|
struct player_data *p = &ctx->p;
|
|
struct module_data *m = &ctx->m;
|
|
struct xmp_module *mod = &m->mod;
|
|
struct channel_data *xc = &p->xc_data[chn];
|
|
int note, key, ins;
|
|
struct xmp_subinstrument *sub;
|
|
int new_invalid_ins;
|
|
int is_toneporta;
|
|
int use_ins_vol;
|
|
int k00 = 0;
|
|
struct xmp_event ev;
|
|
|
|
/* From the OpenMPT DelayCombination.xm test case:
|
|
* "Naturally, Fasttracker 2 ignores notes next to an out-of-range
|
|
* note delay. However, to check whether the delay is out of range,
|
|
* it is simply compared against the current song speed, not taking
|
|
* any pattern delays into account."
|
|
*/
|
|
if (p->frame >= p->speed) {
|
|
return 0;
|
|
}
|
|
|
|
memcpy(&ev, e, sizeof (struct xmp_event));
|
|
|
|
/* From OpenMPT TremorReset.xm test case:
|
|
* "Even if a tremor effect muted the sample on a previous row, volume
|
|
* commands should be able to override this effect."
|
|
*/
|
|
if (ev.vol) {
|
|
xc->tremor.count &= ~0x80;
|
|
}
|
|
|
|
xc->flags = 0;
|
|
note = -1;
|
|
key = ev.note;
|
|
ins = ev.ins;
|
|
new_invalid_ins = 0;
|
|
is_toneporta = 0;
|
|
use_ins_vol = 0;
|
|
|
|
/* From the OpenMPT key_off.xm test case:
|
|
* "Key off at tick 0 (K00) is very dodgy command. If there is a note
|
|
* next to it, the note is ignored. If there is a volume column
|
|
* command or instrument next to it and the current instrument has
|
|
* no volume envelope, the note is faded out instead of being cut."
|
|
*/
|
|
if (ev.fxt == FX_KEYOFF && ev.fxp == 0) {
|
|
k00 = 1;
|
|
key = 0;
|
|
|
|
if (ins || ev.vol || ev.f2t) {
|
|
if (IS_VALID_INSTRUMENT(xc->ins) &&
|
|
~mod->xxi[xc->ins].aei.flg & XMP_ENVELOPE_ON) {
|
|
SET_NOTE(NOTE_FADEOUT);
|
|
ev.fxt = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (IS_TONEPORTA(ev.fxt) || IS_TONEPORTA(ev.f2t)) {
|
|
is_toneporta = 1;
|
|
}
|
|
|
|
/* Check instrument */
|
|
|
|
/* Ignore invalid instruments. The last instrument, invalid or
|
|
* not, is preserved in channel data (see read_event() below).
|
|
* Fixes stray delayed notes in forgotten_city.xm.
|
|
*/
|
|
if (ins > 0 && !IS_VALID_INSTRUMENT(ins - 1)) {
|
|
ins = 0;
|
|
}
|
|
|
|
/* FT2: Retrieve old instrument volume */
|
|
if (ins) {
|
|
if (key == 0 || key >= XMP_KEY_OFF) {
|
|
struct xmp_subinstrument *sub;
|
|
|
|
/* Previous instrument */
|
|
sub = get_subinstrument(ctx, xc->ins, xc->key);
|
|
|
|
/* No note */
|
|
if (sub != NULL) {
|
|
int p = mod->xxc[chn].pan - 128;
|
|
xc->volume = sub->vol;
|
|
|
|
if (!HAS_QUIRK(QUIRK_FTMOD)) {
|
|
xc->pan.val = p + ((sub->pan - 128) *
|
|
(128 - abs(p))) / 128 + 128;
|
|
}
|
|
|
|
xc->ins_fade = mod->xxi[xc->ins].rls;
|
|
SET(NEW_VOL);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Do this regardless if the instrument is invalid or not */
|
|
if (ev.ins) {
|
|
SET(NEW_INS);
|
|
use_ins_vol = 1;
|
|
xc->fadeout = 0x10000;
|
|
xc->per_flags = 0;
|
|
RESET_NOTE(NOTE_RELEASE|NOTE_SUSEXIT);
|
|
if (!k00) {
|
|
RESET_NOTE(NOTE_FADEOUT);
|
|
}
|
|
|
|
if (IS_VALID_INSTRUMENT(ins - 1)) {
|
|
if (!is_toneporta)
|
|
xc->ins = ins - 1;
|
|
} else {
|
|
new_invalid_ins = 1;
|
|
|
|
/* If no note is set FT2 doesn't cut on invalid
|
|
* instruments (it keeps playing the previous one).
|
|
* If a note is set it cuts the current sample.
|
|
*/
|
|
xc->flags = 0;
|
|
|
|
if (is_toneporta) {
|
|
key = 0;
|
|
}
|
|
}
|
|
|
|
xc->tremor.count = 0x20;
|
|
}
|
|
|
|
/* Check note */
|
|
|
|
if (ins) {
|
|
if (key > 0 && key < XMP_KEY_OFF) {
|
|
struct xmp_subinstrument *sub;
|
|
|
|
/* Retrieve volume when we have note */
|
|
|
|
/* and only if we have instrument, otherwise we're in
|
|
* case 1: new note and no instrument
|
|
*/
|
|
|
|
/* Current instrument */
|
|
sub = get_subinstrument(ctx, xc->ins, key - 1);
|
|
if (sub != NULL) {
|
|
int p = mod->xxc[chn].pan - 128;
|
|
xc->volume = sub->vol;
|
|
|
|
if (!HAS_QUIRK(QUIRK_FTMOD)) {
|
|
xc->pan.val = p + ((sub->pan - 128) *
|
|
(128 - abs(p))) / 128 + 128;
|
|
}
|
|
|
|
xc->ins_fade = mod->xxi[xc->ins].rls;
|
|
} else {
|
|
xc->volume = 0;
|
|
}
|
|
SET(NEW_VOL);
|
|
}
|
|
}
|
|
|
|
if (key) {
|
|
SET(NEW_NOTE);
|
|
|
|
if (key == XMP_KEY_OFF) {
|
|
int env_on = 0;
|
|
int vol_set = ev.vol != 0 || ev.fxt == FX_VOLSET;
|
|
int delay_fx = ev.fxt == FX_EXTENDED && ev.fxp == 0xd0;
|
|
struct xmp_envelope *env = NULL;
|
|
|
|
/* OpenMPT NoteOffVolume.xm:
|
|
* "If an instrument has no volume envelope, a note-off
|
|
* command should cut the sample completely - unless
|
|
* there is a volume command next it. This applies to
|
|
* both volume commands (volume and effect column)."
|
|
*
|
|
* ...and unless we have a keyoff+delay without setting
|
|
* an instrument. See OffDelay.xm.
|
|
*/
|
|
if (IS_VALID_INSTRUMENT(xc->ins)) {
|
|
env = &mod->xxi[xc->ins].aei;
|
|
if (env->flg & XMP_ENVELOPE_ON) {
|
|
env_on = 1;
|
|
}
|
|
}
|
|
|
|
if (env_on || (!vol_set && (!ev.ins || !delay_fx))) {
|
|
if (sustain_check(env, xc->v_idx)) {
|
|
/* See OpenMPT EnvOff.xm. In certain
|
|
* cases a release event is effective
|
|
* only in the next frame
|
|
*/
|
|
SET_NOTE(NOTE_SUSEXIT);
|
|
} else {
|
|
SET_NOTE(NOTE_RELEASE);
|
|
}
|
|
use_ins_vol = 0;
|
|
} else {
|
|
SET_NOTE(NOTE_FADEOUT);
|
|
}
|
|
|
|
/* See OpenMPT keyoff+instr.xm, pattern 2 row 0x40 */
|
|
if (env_on && ev.fxt == FX_EXTENDED &&
|
|
(ev.fxp >> 4) == EX_DELAY) {
|
|
/* See OpenMPT OffDelay.xm test case */
|
|
if ((ev.fxp & 0xf) != 0) {
|
|
RESET_NOTE(NOTE_RELEASE|NOTE_SUSEXIT);
|
|
}
|
|
}
|
|
} else if (key == XMP_KEY_FADE) {
|
|
/* Handle keyoff + instrument case (NoteOff2.xm) */
|
|
SET_NOTE(NOTE_FADEOUT);
|
|
} else if (is_toneporta) {
|
|
/* set key to 0 so we can have the tone portamento from
|
|
* the original note (see funky_stars.xm pos 5 ch 9)
|
|
*/
|
|
key = 0;
|
|
|
|
/* And do the same if there's no keyoff (see comic
|
|
* bakery remix.xm pos 1 ch 3)
|
|
*/
|
|
}
|
|
|
|
if (ev.ins == 0 && !IS_VALID_INSTRUMENT(xc->old_ins - 1)) {
|
|
new_invalid_ins = 1;
|
|
}
|
|
|
|
if (new_invalid_ins) {
|
|
libxmp_virt_resetchannel(ctx, chn);
|
|
}
|
|
}
|
|
|
|
|
|
/* Check note range -- from the OpenMPT test NoteLimit.xm:
|
|
* "I think one of the first things Fasttracker 2 does when parsing a
|
|
* pattern cell is calculating the “real” note (i.e. pattern note +
|
|
* sample transpose), and if this “real” note falls out of its note
|
|
* range, it is ignored completely (wiped from its internal channel
|
|
* memory). The instrument number next it, however, is not affected
|
|
* and remains in the memory."
|
|
*/
|
|
sub = NULL;
|
|
if (is_valid_note(key - 1)) {
|
|
int k = key - 1;
|
|
sub = get_subinstrument(ctx, xc->ins, k);
|
|
if (!new_invalid_ins && sub != NULL) {
|
|
int transp = mod->xxi[xc->ins].map[k].xpo;
|
|
int k2 = k + sub->xpo + transp;
|
|
if (k2 < 12 || k2 > 130) {
|
|
key = 0;
|
|
RESET(NEW_NOTE);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (is_valid_note(key - 1)) {
|
|
xc->key = --key;
|
|
xc->fadeout = 0x10000;
|
|
RESET_NOTE(NOTE_END);
|
|
|
|
if (sub != NULL) {
|
|
if (~mod->xxi[xc->ins].aei.flg & XMP_ENVELOPE_ON) {
|
|
RESET_NOTE(NOTE_RELEASE|NOTE_FADEOUT);
|
|
}
|
|
}
|
|
|
|
if (!new_invalid_ins && sub != NULL) {
|
|
int transp = mod->xxi[xc->ins].map[key].xpo;
|
|
int smp;
|
|
|
|
note = key + sub->xpo + transp;
|
|
smp = sub->sid;
|
|
|
|
if (mod->xxs[smp].len == 0) {
|
|
smp = -1;
|
|
}
|
|
|
|
if (smp >= 0 && smp < mod->smp) {
|
|
set_patch(ctx, chn, xc->ins, smp, note);
|
|
xc->smp = smp;
|
|
}
|
|
} else {
|
|
xc->flags = 0;
|
|
use_ins_vol = 0;
|
|
}
|
|
}
|
|
|
|
sub = get_subinstrument(ctx, xc->ins, xc->key);
|
|
|
|
set_effect_defaults(ctx, note, sub, xc, is_toneporta);
|
|
|
|
if (ins && sub != NULL && !k00) {
|
|
/* Reset envelopes on new instrument, see olympic.xm pos 10
|
|
* But make sure we have an instrument set, see Letting go
|
|
* pos 4 chn 20
|
|
*/
|
|
reset_envelopes(ctx, xc);
|
|
}
|
|
|
|
/* Process new volume */
|
|
if (ev.vol) {
|
|
xc->volume = ev.vol - 1;
|
|
SET(NEW_VOL);
|
|
if (TEST_NOTE(NOTE_END)) { /* m5v-nine.xm */
|
|
xc->fadeout = 0x10000; /* OpenMPT NoteOff.xm */
|
|
RESET_NOTE(NOTE_RELEASE|NOTE_FADEOUT);
|
|
}
|
|
}
|
|
|
|
/* FT2: always reset sample offset */
|
|
xc->offset.val = 0;
|
|
|
|
/* Secondary effect handled first */
|
|
libxmp_process_fx(ctx, xc, chn, &ev, 1);
|
|
libxmp_process_fx(ctx, xc, chn, &ev, 0);
|
|
set_period_ft2(ctx, note, sub, xc, is_toneporta);
|
|
|
|
if (sub == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
if (note >= 0) {
|
|
xc->note = note;
|
|
|
|
/* From the OpenMPT test cases (3xx-no-old-samp.xm):
|
|
* "An offset effect that points beyond the sample end should
|
|
* stop playback on this channel."
|
|
*
|
|
* ... except in Skale Tracker (and possibly others), so make this a
|
|
* FastTracker2 quirk. See Armada Tanks game.it (actually an XM).
|
|
* Reported by Vladislav Suschikh.
|
|
*/
|
|
if (HAS_QUIRK(QUIRK_FT2BUGS) && xc->offset.val >= mod->xxs[sub->sid].len) {
|
|
libxmp_virt_resetchannel(ctx, chn);
|
|
} else {
|
|
|
|
/* (From Decibelter - Cosmic 'Wegian Mamas.xm p04 ch7)
|
|
* We retrigger the sample only if we have a new note
|
|
* without tone portamento, otherwise we won't play
|
|
* sweeps and loops correctly.
|
|
*/
|
|
libxmp_virt_voicepos(ctx, chn, xc->offset.val);
|
|
}
|
|
}
|
|
|
|
if (use_ins_vol && !TEST(NEW_VOL)) {
|
|
xc->volume = sub->vol;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int read_event_st3(struct context_data *ctx, struct xmp_event *e, int chn)
|
|
{
|
|
struct player_data *p = &ctx->p;
|
|
struct module_data *m = &ctx->m;
|
|
struct xmp_module *mod = &m->mod;
|
|
struct channel_data *xc = &p->xc_data[chn];
|
|
int note;
|
|
struct xmp_subinstrument *sub;
|
|
int not_same_ins;
|
|
int is_toneporta;
|
|
int use_ins_vol;
|
|
|
|
xc->flags = 0;
|
|
note = -1;
|
|
not_same_ins = 0;
|
|
is_toneporta = 0;
|
|
use_ins_vol = 0;
|
|
|
|
if (IS_TONEPORTA(e->fxt) || IS_TONEPORTA(e->f2t)) {
|
|
is_toneporta = 1;
|
|
}
|
|
|
|
if (libxmp_virt_mapchannel(ctx, chn) < 0 && xc->ins != e->ins - 1) {
|
|
is_toneporta = 0;
|
|
}
|
|
|
|
/* Check instrument */
|
|
|
|
if (e->ins) {
|
|
int ins = e->ins - 1;
|
|
SET(NEW_INS);
|
|
use_ins_vol = 1;
|
|
xc->fadeout = 0x10000;
|
|
xc->per_flags = 0;
|
|
xc->offset.val = 0;
|
|
RESET_NOTE(NOTE_RELEASE|NOTE_FADEOUT);
|
|
|
|
if (IS_VALID_INSTRUMENT(ins)) {
|
|
/* valid ins */
|
|
if (xc->ins != ins) {
|
|
not_same_ins = 1;
|
|
if (!is_toneporta) {
|
|
xc->ins = ins;
|
|
xc->ins_fade = mod->xxi[ins].rls;
|
|
} else {
|
|
/* Get new instrument volume */
|
|
sub = get_subinstrument(ctx, ins, e->note - 1);
|
|
if (sub != NULL) {
|
|
xc->volume = sub->vol;
|
|
use_ins_vol = 0;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
/* invalid ins */
|
|
|
|
/* Ignore invalid instruments */
|
|
xc->flags = 0;
|
|
use_ins_vol = 0;
|
|
}
|
|
}
|
|
|
|
/* Check note */
|
|
|
|
if (e->note) {
|
|
SET(NEW_NOTE);
|
|
|
|
if (e->note == XMP_KEY_OFF) {
|
|
SET_NOTE(NOTE_RELEASE);
|
|
use_ins_vol = 0;
|
|
} else if (is_toneporta) {
|
|
/* Always retrig in tone portamento: Fix portamento in
|
|
* 7spirits.s3m, mod.Biomechanoid
|
|
*/
|
|
if (not_same_ins) {
|
|
xc->offset.val = 0;
|
|
}
|
|
} else {
|
|
xc->key = e->note - 1;
|
|
RESET_NOTE(NOTE_END);
|
|
|
|
sub = get_subinstrument(ctx, xc->ins, xc->key);
|
|
|
|
if (sub != NULL) {
|
|
int transp = mod->xxi[xc->ins].map[xc->key].xpo;
|
|
int smp;
|
|
|
|
note = xc->key + sub->xpo + transp;
|
|
smp = sub->sid;
|
|
|
|
if (mod->xxs[smp].len == 0) {
|
|
smp = -1;
|
|
}
|
|
|
|
if (smp >= 0 && smp < mod->smp) {
|
|
set_patch(ctx, chn, xc->ins, smp, note);
|
|
xc->smp = smp;
|
|
}
|
|
} else {
|
|
xc->flags = 0;
|
|
use_ins_vol = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
sub = get_subinstrument(ctx, xc->ins, xc->key);
|
|
|
|
set_effect_defaults(ctx, note, sub, xc, is_toneporta);
|
|
if (e->ins && sub != NULL) {
|
|
reset_envelopes(ctx, xc);
|
|
}
|
|
|
|
/* Process new volume */
|
|
if (e->vol) {
|
|
xc->volume = e->vol - 1;
|
|
SET(NEW_VOL);
|
|
}
|
|
|
|
/* Secondary effect handled first */
|
|
libxmp_process_fx(ctx, xc, chn, e, 1);
|
|
libxmp_process_fx(ctx, xc, chn, e, 0);
|
|
set_period(ctx, note, sub, xc, is_toneporta);
|
|
|
|
if (sub == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
if (note >= 0) {
|
|
xc->note = note;
|
|
libxmp_virt_voicepos(ctx, chn, xc->offset.val);
|
|
}
|
|
|
|
if (use_ins_vol && !TEST(NEW_VOL)) {
|
|
xc->volume = sub->vol;
|
|
}
|
|
|
|
if (HAS_QUIRK(QUIRK_ST3BUGS) && TEST(NEW_VOL)) {
|
|
xc->volume = xc->volume * p->gvol / m->volbase;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifndef LIBXMP_CORE_DISABLE_IT
|
|
|
|
static inline void copy_channel(struct player_data *p, int to, int from)
|
|
{
|
|
if (to > 0 && to != from) {
|
|
memcpy(&p->xc_data[to], &p->xc_data[from],
|
|
sizeof (struct channel_data));
|
|
}
|
|
}
|
|
|
|
static inline int has_note_event(struct xmp_event *e)
|
|
{
|
|
return (e->note && e->note <= XMP_MAX_KEYS);
|
|
}
|
|
|
|
static int check_fadeout(struct context_data *ctx, struct channel_data *xc, int ins)
|
|
{
|
|
struct xmp_instrument *xxi = libxmp_get_instrument(ctx, ins);
|
|
|
|
if (xxi == NULL) {
|
|
return 1;
|
|
}
|
|
|
|
return (~xxi->aei.flg & XMP_ENVELOPE_ON ||
|
|
~xxi->aei.flg & XMP_ENVELOPE_CARRY ||
|
|
xc->ins_fade == 0 ||
|
|
xc->fadeout <= xc->ins_fade);
|
|
}
|
|
|
|
static int check_invalid_sample(struct context_data *ctx, int ins, int key)
|
|
{
|
|
struct module_data *m = &ctx->m;
|
|
struct xmp_module *mod = &m->mod;
|
|
|
|
if (ins < mod->ins) {
|
|
int smp = mod->xxi[ins].map[key].ins;
|
|
if (smp == 0xff || smp >= mod->smp) {
|
|
return 1;
|
|
};
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void fix_period(struct context_data *ctx, int chn, struct xmp_subinstrument *sub)
|
|
{
|
|
if (sub->nna == XMP_INST_NNA_CONT) {
|
|
struct player_data *p = &ctx->p;
|
|
struct channel_data *xc = &p->xc_data[chn];
|
|
struct xmp_instrument *xxi = libxmp_get_instrument(ctx, xc->ins);
|
|
|
|
xc->period = libxmp_note_to_period(ctx, xc->key + sub->xpo +
|
|
xxi->map[xc->key_porta].xpo, xc->finetune, xc->per_adj);
|
|
}
|
|
}
|
|
|
|
static int is_same_sid(struct context_data *ctx, int chn, int ins, int key)
|
|
{
|
|
struct player_data *p = &ctx->p;
|
|
struct channel_data *xc = &p->xc_data[chn];
|
|
struct xmp_subinstrument *s1, *s2;
|
|
|
|
s1 = get_subinstrument(ctx, ins, key);
|
|
s2 = get_subinstrument(ctx, xc->ins, xc->key);
|
|
|
|
return (s1 && s2 && s1->sid == s2->sid);
|
|
}
|
|
|
|
static int read_event_it(struct context_data *ctx, struct xmp_event *e, int chn)
|
|
{
|
|
struct player_data *p = &ctx->p;
|
|
struct module_data *m = &ctx->m;
|
|
struct xmp_module *mod = &m->mod;
|
|
struct channel_data *xc = &p->xc_data[chn];
|
|
int note, key;
|
|
struct xmp_subinstrument *sub;
|
|
int not_same_ins, not_same_smp;
|
|
int new_invalid_ins;
|
|
int is_toneporta, is_release;
|
|
int candidate_ins;
|
|
int reset_env;
|
|
int use_ins_vol;
|
|
int sample_mode;
|
|
int toneporta_offset;
|
|
int disabled_toneporta;
|
|
int retrig_ins;
|
|
struct xmp_event ev;
|
|
|
|
memcpy(&ev, e, sizeof (struct xmp_event));
|
|
|
|
/* Emulate Impulse Tracker "always read instrument" bug */
|
|
if (ev.ins) {
|
|
xc->delayed_ins = 0;
|
|
} else if (ev.note && xc->delayed_ins) {
|
|
ev.ins = xc->delayed_ins;
|
|
xc->delayed_ins = 0;
|
|
}
|
|
|
|
xc->flags = 0;
|
|
note = -1;
|
|
key = ev.note;
|
|
not_same_ins = 0;
|
|
not_same_smp = 0;
|
|
new_invalid_ins = 0;
|
|
is_toneporta = 0;
|
|
is_release = 0;
|
|
reset_env = 0;
|
|
use_ins_vol = 0;
|
|
candidate_ins = xc->ins;
|
|
sample_mode = !HAS_QUIRK(QUIRK_VIRTUAL);
|
|
toneporta_offset = 0;
|
|
disabled_toneporta = 0;
|
|
retrig_ins = 0;
|
|
|
|
/* Keyoff + instrument retrigs current instrument in old fx mode */
|
|
if (HAS_QUIRK(QUIRK_ITOLDFX)) {
|
|
if (ev.note == XMP_KEY_OFF && IS_VALID_INSTRUMENT(ev.ins -1)) {
|
|
retrig_ins = 1;
|
|
}
|
|
}
|
|
|
|
/* Notes with unmapped instruments are ignored */
|
|
if (ev.ins) {
|
|
if (ev.ins <= mod->ins && has_note_event(&ev)) {
|
|
int ins = ev.ins - 1;
|
|
if (check_invalid_sample(ctx, ins, ev.note - 1)) {
|
|
candidate_ins = ins;
|
|
memset(&ev, 0, sizeof (ev));
|
|
}
|
|
}
|
|
} else {
|
|
if (has_note_event(&ev)) {
|
|
int ins = xc->old_ins - 1;
|
|
if (!IS_VALID_INSTRUMENT(ins)) {
|
|
new_invalid_ins = 1;
|
|
} else if (check_invalid_sample(ctx, ins, ev.note - 1)) {
|
|
memset(&ev, 0, sizeof (ev));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (IS_TONEPORTA(ev.fxt) || IS_TONEPORTA(ev.f2t)) {
|
|
is_toneporta = 1;
|
|
}
|
|
|
|
if (TEST_NOTE(NOTE_RELEASE | NOTE_FADEOUT)) {
|
|
is_release = 1;
|
|
}
|
|
|
|
if (xc->period <= 0 || TEST_NOTE(NOTE_END)) {
|
|
is_toneporta = 0;
|
|
}
|
|
|
|
/* Off-Porta.it */
|
|
if (is_toneporta && ev.fxt == FX_OFFSET) {
|
|
disabled_toneporta = 1;
|
|
is_toneporta = 0;
|
|
if (!HAS_QUIRK(QUIRK_PRENV)) {
|
|
toneporta_offset = 1;
|
|
RESET_NOTE(NOTE_ENV_END);
|
|
}
|
|
}
|
|
|
|
/* Check instrument */
|
|
|
|
if (ev.ins) {
|
|
int ins = ev.ins - 1;
|
|
int set_new_ins = 1;
|
|
|
|
/* portamento_after_keyoff.it test case */
|
|
if (is_release && !key) {
|
|
if (is_toneporta) {
|
|
if (HAS_QUIRK(QUIRK_PRENV) || TEST_NOTE(NOTE_SET)) {
|
|
is_toneporta = 0;
|
|
reset_envelopes_carry(ctx, xc);
|
|
}
|
|
} else {
|
|
/* fixes OpenMPT wnoteoff.it */
|
|
reset_envelopes_carry(ctx, xc);
|
|
}
|
|
}
|
|
|
|
if (is_toneporta && xc->ins == ins) {
|
|
if (!HAS_QUIRK(QUIRK_PRENV)) {
|
|
if (is_same_sid(ctx, chn, ins, key - 1)) {
|
|
/* same instrument and same sample */
|
|
set_new_ins = !is_release;
|
|
} else {
|
|
/* same instrument, different sample */
|
|
not_same_ins = 1; /* need this too */
|
|
not_same_smp = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (set_new_ins) {
|
|
SET(NEW_INS);
|
|
use_ins_vol = 1;
|
|
reset_env = 1;
|
|
}
|
|
xc->per_flags = 0;
|
|
|
|
if (IS_VALID_INSTRUMENT(ins)) {
|
|
/* valid ins */
|
|
|
|
/* See OpenMPT StoppedInstrSwap.it for cut case */
|
|
if (!key && !TEST_NOTE(NOTE_KEY_CUT)) {
|
|
/* Retrig in new ins in sample mode */
|
|
if (sample_mode && TEST_NOTE(NOTE_END)) {
|
|
libxmp_virt_voicepos(ctx, chn, 0);
|
|
}
|
|
|
|
/* IT: Reset note for every new != ins */
|
|
if (xc->ins == ins) {
|
|
SET(NEW_INS);
|
|
use_ins_vol = 1;
|
|
} else {
|
|
key = xc->key + 1;
|
|
}
|
|
|
|
RESET_NOTE(NOTE_SET);
|
|
}
|
|
|
|
if (xc->ins != ins && (!is_toneporta || !HAS_QUIRK(QUIRK_PRENV))) {
|
|
candidate_ins = ins;
|
|
|
|
if (!is_same_sid(ctx, chn, ins, key - 1)) {
|
|
not_same_ins = 1;
|
|
if (is_toneporta) {
|
|
/* Get new instrument volume */
|
|
sub = get_subinstrument(ctx, ins, key);
|
|
if (sub != NULL) {
|
|
xc->volume = sub->vol;
|
|
use_ins_vol = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
/* In sample mode invalid ins cut previous ins */
|
|
if (sample_mode) {
|
|
xc->volume = 0;
|
|
}
|
|
|
|
/* Ignore invalid instruments */
|
|
new_invalid_ins = 1;
|
|
xc->flags = 0;
|
|
use_ins_vol = 0;
|
|
}
|
|
}
|
|
|
|
/* Check note */
|
|
|
|
if (key && !new_invalid_ins) {
|
|
SET(NEW_NOTE);
|
|
SET_NOTE(NOTE_SET);
|
|
|
|
if (key == XMP_KEY_FADE) {
|
|
SET_NOTE(NOTE_FADEOUT);
|
|
reset_env = 0;
|
|
use_ins_vol = 0;
|
|
} else if (key == XMP_KEY_CUT) {
|
|
SET_NOTE(NOTE_END | NOTE_CUT | NOTE_KEY_CUT);
|
|
xc->period = 0;
|
|
libxmp_virt_resetchannel(ctx, chn);
|
|
} else if (key == XMP_KEY_OFF) {
|
|
struct xmp_envelope *env = NULL;
|
|
if (IS_VALID_INSTRUMENT(xc->ins)) {
|
|
env = &mod->xxi[xc->ins].aei;
|
|
}
|
|
if (sustain_check(env, xc->v_idx)) {
|
|
SET_NOTE(NOTE_SUSEXIT);
|
|
} else {
|
|
SET_NOTE(NOTE_RELEASE);
|
|
}
|
|
SET(KEY_OFF);
|
|
reset_env = 0;
|
|
use_ins_vol = 0;
|
|
} else {
|
|
/* portamento_after_keyoff.it test case */
|
|
/* also see suburban_streets o13 c45 */
|
|
if (ev.ins || !is_toneporta) {
|
|
if (!disabled_toneporta) {
|
|
reset_env = 1;
|
|
}
|
|
}
|
|
|
|
if (is_toneporta) {
|
|
if (not_same_ins || TEST_NOTE(NOTE_END)) {
|
|
SET(NEW_INS);
|
|
RESET_NOTE(NOTE_RELEASE|NOTE_SUSEXIT|NOTE_FADEOUT);
|
|
} else {
|
|
if (is_valid_note(key - 1)) {
|
|
xc->key_porta = key - 1;
|
|
}
|
|
key = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (is_valid_note(key - 1) && !new_invalid_ins) {
|
|
if (TEST_NOTE(NOTE_CUT)) {
|
|
use_ins_vol = 1; /* See OpenMPT NoteOffInstr.it */
|
|
}
|
|
xc->key = --key;
|
|
RESET_NOTE(NOTE_END);
|
|
|
|
sub = get_subinstrument(ctx, candidate_ins, key);
|
|
|
|
if (sub != NULL) {
|
|
int transp = mod->xxi[candidate_ins].map[key].xpo;
|
|
int smp, to;
|
|
int rvv;
|
|
|
|
note = key + sub->xpo + transp;
|
|
smp = sub->sid;
|
|
if (smp >= mod->smp || mod->xxs[smp].len == 0) {
|
|
smp = -1;
|
|
}
|
|
|
|
if (not_same_smp) {
|
|
fix_period(ctx, chn, sub);
|
|
libxmp_virt_resetchannel(ctx, chn);
|
|
}
|
|
to = libxmp_virt_setpatch(ctx, chn, candidate_ins, smp,
|
|
note, sub->nna, sub->dct, sub->dca);
|
|
|
|
/* Random value for volume swing */
|
|
rvv = sub->rvv & 0xff;
|
|
if (rvv) {
|
|
CLAMP(rvv, 0, 100);
|
|
xc->rvv = rand() % (rvv + 1);
|
|
} else {
|
|
xc->rvv = 0;
|
|
}
|
|
|
|
/* Random value for pan swing */
|
|
rvv = (sub->rvv & 0xff00) >> 8;
|
|
if (rvv) {
|
|
CLAMP(rvv, 0, 64);
|
|
xc->rpv = rand() % (rvv + 1) - (rvv / 2);
|
|
} else {
|
|
xc->rpv = 0;
|
|
}
|
|
|
|
if (to < 0)
|
|
return -1;
|
|
if (to != chn) {
|
|
copy_channel(p, to, chn);
|
|
p->xc_data[to].flags = 0;
|
|
}
|
|
|
|
if (smp >= 0) { /* Not sure if needed */
|
|
xc->smp = smp;
|
|
}
|
|
} else {
|
|
xc->flags = 0;
|
|
use_ins_vol = 0;
|
|
}
|
|
}
|
|
|
|
/* Do after virtual channel copy */
|
|
if (is_toneporta || retrig_ins) {
|
|
if (HAS_QUIRK(QUIRK_PRENV) && ev.ins) {
|
|
reset_envelopes_carry(ctx, xc);
|
|
}
|
|
}
|
|
|
|
if (IS_VALID_INSTRUMENT(candidate_ins)) {
|
|
if (xc->ins != candidate_ins) {
|
|
/* Reset envelopes if instrument changes */
|
|
reset_envelopes(ctx, xc);
|
|
}
|
|
xc->ins = candidate_ins;
|
|
xc->ins_fade = mod->xxi[candidate_ins].rls;
|
|
}
|
|
|
|
/* Reset in case of new instrument and the previous envelope has
|
|
* finished (OpenMPT test EnvReset.it). This must take place after
|
|
* channel copies in case of NNA (see test/test.it)
|
|
* Also if we have envelope in carry mode, check fadeout
|
|
*/
|
|
if (ev.ins && TEST_NOTE(NOTE_ENV_END)) {
|
|
if (check_fadeout(ctx, xc, candidate_ins)) {
|
|
reset_envelopes(ctx, xc);
|
|
} else {
|
|
reset_env = 0;
|
|
}
|
|
}
|
|
|
|
if (reset_env) {
|
|
if (ev.note) {
|
|
RESET_NOTE(NOTE_RELEASE|NOTE_SUSEXIT|NOTE_FADEOUT);
|
|
}
|
|
/* Set after copying to new virtual channel (see ambio.it) */
|
|
xc->fadeout = 0x10000;
|
|
}
|
|
|
|
/* See OpenMPT wnoteoff.it vs noteoff3.it */
|
|
if (retrig_ins && not_same_ins) {
|
|
SET(NEW_INS);
|
|
libxmp_virt_voicepos(ctx, chn, 0);
|
|
xc->fadeout = 0x10000;
|
|
RESET_NOTE(NOTE_RELEASE|NOTE_SUSEXIT|NOTE_FADEOUT);
|
|
}
|
|
|
|
sub = get_subinstrument(ctx, xc->ins, xc->key);
|
|
|
|
set_effect_defaults(ctx, note, sub, xc, is_toneporta);
|
|
if (sub != NULL) {
|
|
if (note >= 0) {
|
|
/* Reset pan, see OpenMPT PanReset.it */
|
|
if (sub->pan >= 0) {
|
|
xc->pan.val = sub->pan;
|
|
xc->pan.surround = 0;
|
|
}
|
|
|
|
if (TEST_NOTE(NOTE_CUT)) {
|
|
reset_envelopes(ctx, xc);
|
|
} else if (!toneporta_offset) {
|
|
reset_envelopes_carry(ctx, xc);
|
|
}
|
|
RESET_NOTE(NOTE_CUT);
|
|
}
|
|
}
|
|
|
|
/* Process new volume */
|
|
if (ev.vol && (!TEST_NOTE(NOTE_CUT) || ev.ins != 0)) {
|
|
if (key != XMP_KEY_OFF) { /* See OpenMPT NoteOffInstr.it */
|
|
xc->volume = ev.vol - 1;
|
|
SET(NEW_VOL);
|
|
}
|
|
}
|
|
|
|
/* IT: always reset sample offset */
|
|
xc->offset.val &= ~0xffff;
|
|
|
|
/* According to Storlek test 25, Impulse Tracker handles the volume
|
|
* column effects after the standard effects.
|
|
*/
|
|
libxmp_process_fx(ctx, xc, chn, &ev, 0);
|
|
libxmp_process_fx(ctx, xc, chn, &ev, 1);
|
|
set_period(ctx, note, sub, xc, is_toneporta);
|
|
|
|
if (sub == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
if (note >= 0) {
|
|
xc->note = note;
|
|
libxmp_virt_voicepos(ctx, chn, xc->offset.val);
|
|
}
|
|
|
|
if (use_ins_vol && !TEST(NEW_VOL)) {
|
|
xc->volume = sub->vol;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#endif
|
|
|
|
#ifndef LIBXMP_CORE_PLAYER
|
|
|
|
static int read_event_med(struct context_data *ctx, struct xmp_event *e, int chn)
|
|
{
|
|
struct player_data *p = &ctx->p;
|
|
struct module_data *m = &ctx->m;
|
|
struct xmp_module *mod = &m->mod;
|
|
struct channel_data *xc = &p->xc_data[chn];
|
|
int note;
|
|
struct xmp_subinstrument *sub;
|
|
int new_invalid_ins = 0;
|
|
int is_toneporta;
|
|
int use_ins_vol;
|
|
int finetune;
|
|
|
|
xc->flags = 0;
|
|
note = -1;
|
|
is_toneporta = 0;
|
|
use_ins_vol = 0;
|
|
|
|
if (e->fxt == FX_TONEPORTA || e->fxt == FX_TONE_VSLIDE) {
|
|
is_toneporta = 1;
|
|
}
|
|
|
|
/* Check instrument */
|
|
|
|
if (e->ins && e->note) {
|
|
int ins = e->ins - 1;
|
|
use_ins_vol = 1;
|
|
SET(NEW_INS);
|
|
xc->fadeout = 0x10000;
|
|
xc->offset.val = 0;
|
|
RESET_NOTE(NOTE_RELEASE|NOTE_FADEOUT);
|
|
|
|
if (IS_VALID_INSTRUMENT(ins)) {
|
|
if (is_toneporta) {
|
|
/* Get new instrument volume */
|
|
sub = get_subinstrument(ctx, ins, e->note - 1);
|
|
if (sub != NULL) {
|
|
xc->volume = sub->vol;
|
|
use_ins_vol = 0;
|
|
}
|
|
} else {
|
|
xc->ins = ins;
|
|
xc->ins_fade = mod->xxi[ins].rls;
|
|
}
|
|
} else {
|
|
new_invalid_ins = 1;
|
|
libxmp_virt_resetchannel(ctx, chn);
|
|
}
|
|
|
|
MED_CHANNEL_EXTRAS(*xc)->arp = 0;
|
|
MED_CHANNEL_EXTRAS(*xc)->aidx = 0;
|
|
} else {
|
|
/* Hold */
|
|
if (e->ins && !e->note) {
|
|
use_ins_vol = 1;
|
|
}
|
|
}
|
|
|
|
/* Check note */
|
|
|
|
if (e->note) {
|
|
SET(NEW_NOTE);
|
|
|
|
if (e->note == XMP_KEY_OFF) {
|
|
SET_NOTE(NOTE_RELEASE);
|
|
use_ins_vol = 0;
|
|
} else if (e->note == XMP_KEY_CUT) {
|
|
SET_NOTE(NOTE_END);
|
|
xc->period = 0;
|
|
libxmp_virt_resetchannel(ctx, chn);
|
|
} else if (!is_toneporta && IS_VALID_INSTRUMENT(xc->ins)) {
|
|
struct xmp_instrument *xxi = &mod->xxi[xc->ins];
|
|
|
|
xc->key = e->note - 1;
|
|
RESET_NOTE(NOTE_END);
|
|
|
|
xc->per_adj = 0.0;
|
|
if (xxi->nsm > 1 && HAS_MED_INSTRUMENT_EXTRAS(*xxi)) {
|
|
/* synth or iffoct */
|
|
if (MED_INSTRUMENT_EXTRAS(*xxi)->vts == 0 &&
|
|
MED_INSTRUMENT_EXTRAS(*xxi)->wts == 0) {
|
|
/* iffoct */
|
|
xc->per_adj = 2.0;
|
|
}
|
|
}
|
|
|
|
sub = get_subinstrument(ctx, xc->ins, xc->key);
|
|
|
|
if (!new_invalid_ins && sub != NULL) {
|
|
int transp = xxi->map[xc->key].xpo;
|
|
int smp;
|
|
|
|
note = xc->key + sub->xpo + transp;
|
|
smp = sub->sid;
|
|
|
|
if (mod->xxs[smp].len == 0) {
|
|
smp = -1;
|
|
}
|
|
|
|
if (smp >= 0 && smp < mod->smp) {
|
|
set_patch(ctx, chn, xc->ins, smp, note);
|
|
xc->smp = smp;
|
|
}
|
|
} else {
|
|
xc->flags = 0;
|
|
use_ins_vol = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
sub = get_subinstrument(ctx, xc->ins, xc->key);
|
|
|
|
/* Keep effect-set finetune if no instrument set */
|
|
finetune = xc->finetune;
|
|
set_effect_defaults(ctx, note, sub, xc, is_toneporta);
|
|
if (!e->ins) {
|
|
xc->finetune = finetune;
|
|
}
|
|
|
|
if (e->ins && sub != NULL) {
|
|
reset_envelopes(ctx, xc);
|
|
}
|
|
|
|
/* Process new volume */
|
|
if (e->vol) {
|
|
xc->volume = e->vol - 1;
|
|
SET(NEW_VOL);
|
|
}
|
|
|
|
/* Secondary effect handled first */
|
|
libxmp_process_fx(ctx, xc, chn, e, 1);
|
|
libxmp_process_fx(ctx, xc, chn, e, 0);
|
|
set_period(ctx, note, sub, xc, is_toneporta);
|
|
|
|
if (sub == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
if (note >= 0) {
|
|
xc->note = note;
|
|
libxmp_virt_voicepos(ctx, chn, xc->offset.val);
|
|
}
|
|
|
|
if (use_ins_vol && !TEST(NEW_VOL)) {
|
|
xc->volume = sub->vol;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#endif
|
|
|
|
static int read_event_smix(struct context_data *ctx, struct xmp_event *e, int chn)
|
|
{
|
|
struct player_data *p = &ctx->p;
|
|
struct smix_data *smix = &ctx->smix;
|
|
struct module_data *m = &ctx->m;
|
|
struct xmp_module *mod = &m->mod;
|
|
struct channel_data *xc = &p->xc_data[chn];
|
|
struct xmp_subinstrument *sub;
|
|
int is_smix_ins;
|
|
int ins, note, transp, smp;
|
|
|
|
xc->flags = 0;
|
|
|
|
if (!e->ins)
|
|
return 0;
|
|
|
|
is_smix_ins = 0;
|
|
ins = e->ins - 1;
|
|
SET(NEW_INS);
|
|
xc->fadeout = 0x10000;
|
|
xc->per_flags = 0;
|
|
xc->offset.val = 0;
|
|
RESET_NOTE(NOTE_RELEASE);
|
|
|
|
xc->ins = ins;
|
|
|
|
if (ins >= mod->ins && ins < mod->ins + smix->ins) {
|
|
is_smix_ins = 1;
|
|
xc->ins_fade = smix->xxi[xc->ins - mod->ins].rls;
|
|
}
|
|
|
|
SET(NEW_NOTE);
|
|
|
|
if (e->note == XMP_KEY_OFF) {
|
|
SET_NOTE(NOTE_RELEASE);
|
|
return 0;
|
|
}
|
|
|
|
xc->key = e->note - 1;
|
|
RESET_NOTE(NOTE_END);
|
|
|
|
if (is_smix_ins) {
|
|
sub = &smix->xxi[xc->ins - mod->ins].sub[0];
|
|
if (sub == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
note = xc->key + sub->xpo;
|
|
smp = sub->sid;
|
|
if (smix->xxs[smp].len == 0)
|
|
smp = -1;
|
|
if (smp >= 0 && smp < smix->smp) {
|
|
smp += mod->smp;
|
|
set_patch(ctx, chn, xc->ins, smp, note);
|
|
xc->smp = smp;
|
|
}
|
|
} else {
|
|
transp = mod->xxi[xc->ins].map[xc->key].xpo;
|
|
sub = get_subinstrument(ctx, xc->ins, xc->key);
|
|
if (sub == NULL) {
|
|
return 0;
|
|
}
|
|
note = xc->key + sub->xpo + transp;
|
|
smp = sub->sid;
|
|
if (mod->xxs[smp].len == 0)
|
|
smp = -1;
|
|
if (smp >= 0 && smp < mod->smp) {
|
|
set_patch(ctx, chn, xc->ins, smp, note);
|
|
xc->smp = smp;
|
|
}
|
|
}
|
|
|
|
set_effect_defaults(ctx, note, sub, xc, 0);
|
|
set_period(ctx, note, sub, xc, 0);
|
|
|
|
if (e->ins) {
|
|
reset_envelopes(ctx, xc);
|
|
}
|
|
|
|
xc->volume = e->vol - 1;
|
|
|
|
xc->note = note;
|
|
libxmp_virt_voicepos(ctx, chn, xc->offset.val);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int libxmp_read_event(struct context_data *ctx, struct xmp_event *e, int chn)
|
|
{
|
|
struct player_data *p = &ctx->p;
|
|
struct module_data *m = &ctx->m;
|
|
struct channel_data *xc = &p->xc_data[chn];
|
|
|
|
if (e->ins != 0)
|
|
xc->old_ins = e->ins;
|
|
|
|
if (TEST_NOTE(NOTE_SAMPLE_END)) {
|
|
SET_NOTE(NOTE_END);
|
|
}
|
|
|
|
if (chn >= m->mod.chn) {
|
|
return read_event_smix(ctx, e, chn);
|
|
} else switch (m->read_event_type) {
|
|
case READ_EVENT_MOD:
|
|
return read_event_mod(ctx, e, chn);
|
|
case READ_EVENT_FT2:
|
|
return read_event_ft2(ctx, e, chn);
|
|
case READ_EVENT_ST3:
|
|
return read_event_st3(ctx, e, chn);
|
|
#ifndef LIBXMP_CORE_DISABLE_IT
|
|
case READ_EVENT_IT:
|
|
return read_event_it(ctx, e, chn);
|
|
#endif
|
|
#ifndef LIBXMP_CORE_PLAYER
|
|
case READ_EVENT_MED:
|
|
return read_event_med(ctx, e, chn);
|
|
#endif
|
|
default:
|
|
return read_event_mod(ctx, e, chn);
|
|
}
|
|
}
|