code:
/* Extended Module Player
 * Copyright (C) 1996-2021 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.
 */
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

#include "common.h"
#include "period.h"

#include <math.h>

#ifdef LIBXMP_PAULA_SIMULATOR
/*
 * Period table from the Protracker V2.1A play routine
 */
static uint16 pt_period_table[16][36] = {
	/* Tuning 0, Normal */
	{
		856,808,762,720,678,640,604,570,538,508,480,453,
		428,404,381,360,339,320,302,285,269,254,240,226,
		214,202,190,180,170,160,151,143,135,127,120,113
	},
	/* Tuning 1 */
	{
		850,802,757,715,674,637,601,567,535,505,477,450,
		425,401,379,357,337,318,300,284,268,253,239,225,
		213,201,189,179,169,159,150,142,134,126,119,113
	},
	/* Tuning 2 */
	{
		844,796,752,709,670,632,597,563,532,502,474,447,
		422,398,376,355,335,316,298,282,266,251,237,224,
		211,199,188,177,167,158,149,141,133,125,118,112
	},
	/* Tuning 3 */
	{
		838,791,746,704,665,628,592,559,528,498,470,444,
		419,395,373,352,332,314,296,280,264,249,235,222,
		209,198,187,176,166,157,148,140,132,125,118,111
	},
	/* Tuning 4 */
	{
		832,785,741,699,660,623,588,555,524,495,467,441,
		416,392,370,350,330,312,294,278,262,247,233,220,
		208,196,185,175,165,156,147,139,131,124,117,110
	},
	/* Tuning 5 */
	{
		826,779,736,694,655,619,584,551,520,491,463,437,
		413,390,368,347,328,309,292,276,260,245,232,219,
		206,195,184,174,164,155,146,138,130,123,116,109
	},
	/* Tuning 6 */
	{
		820,774,730,689,651,614,580,547,516,487,460,434,
		410,387,365,345,325,307,290,274,258,244,230,217,
		205,193,183,172,163,154,145,137,129,122,115,109
	},
	/* Tuning 7 */
	{
		814,768,725,684,646,610,575,543,513,484,457,431,
		407,384,363,342,323,305,288,272,256,242,228,216,
		204,192,181,171,161,152,144,136,128,121,114,108
	},
	/* Tuning -8 */
	{
		907,856,808,762,720,678,640,604,570,538,508,480,
		453,428,404,381,360,339,320,302,285,269,254,240,
		226,214,202,190,180,170,160,151,143,135,127,120
	},
	/* Tuning -7 */
	{
		900,850,802,757,715,675,636,601,567,535,505,477,
		450,425,401,379,357,337,318,300,284,268,253,238,
		225,212,200,189,179,169,159,150,142,134,126,119
	},
	/* Tuning -6 */
	{
		894,844,796,752,709,670,632,597,563,532,502,474,
		447,422,398,376,355,335,316,298,282,266,251,237,
		223,211,199,188,177,167,158,149,141,133,125,118
	},
	/* Tuning -5 */
	{
		887,838,791,746,704,665,628,592,559,528,498,470,
		444,419,395,373,352,332,314,296,280,264,249,235,
		222,209,198,187,176,166,157,148,140,132,125,118
	},
	/* Tuning -4 */
	{
		881,832,785,741,699,660,623,588,555,524,494,467,
		441,416,392,370,350,330,312,294,278,262,247,233,
		220,208,196,185,175,165,156,147,139,131,123,117
	},
	/* Tuning -3 */
	{
		875,826,779,736,694,655,619,584,551,520,491,463,
		437,413,390,368,347,328,309,292,276,260,245,232,
		219,206,195,184,174,164,155,146,138,130,123,116
	},
	/* Tuning -2 */
	{
		868,820,774,730,689,651,614,580,547,516,487,460,
		434,410,387,365,345,325,307,290,274,258,244,230,
		217,205,193,183,172,163,154,145,137,129,122,115
	},
	/* Tuning -1 */
	{
		862,814,768,725,684,646,610,575,543,513,484,457,
		431,407,384,363,342,323,305,288,272,256,242,228,
		216,203,192,181,171,161,152,144,136,128,121,114
	}
};
#endif

#ifndef M_LN2
#define M_LN2	0.69314718055994530942
#endif

static inline double libxmp_round(double val)
{
	return (val >= 0.0)? floor(val + 0.5) : ceil(val - 0.5);
}

#ifdef LIBXMP_PAULA_SIMULATOR
/* Get period from note using Protracker tuning */
static inline int libxmp_note_to_period_pt(int n, int f)
{
	if (n < MIN_NOTE_MOD || n > MAX_NOTE_MOD) {
		return -1;
	}

	n -= 48;
	f >>= 4;
	if (f < -8 || f > 7) {
		return 0;
	}

	if (f < 0) {
		f += 16;
	}

	return (int)pt_period_table[f][n];
}
#endif

/* Get period from note */
double libxmp_note_to_period(struct context_data *ctx, int n, int f, double adj)
{
	double d, per;
	struct module_data *m = &ctx->m;
#ifdef LIBXMP_PAULA_SIMULATOR
	struct player_data *p = &ctx->p;

	/* If mod replayer, modrng and Amiga mixing are active */
	if (p->flags & XMP_FLAGS_A500) {
		if (IS_AMIGA_MOD()) {
			return libxmp_note_to_period_pt(n, f);
		}
	}
#endif

	d = (double)n + (double)f / 128;

	switch (m->period_type) {
	case PERIOD_LINEAR:
		per = (240.0 - d) * 16;			/* Linear */
		break;
	case PERIOD_CSPD:
		per = 8363.0 * pow(2, n / 12) / 32 + f;	/* Hz */
		break;
	default:
		per = PERIOD_BASE / pow(2, d / 12);	/* Amiga */
	}

#ifndef LIBXMP_CORE_PLAYER
	if (adj > 0.1) {
		per *= adj;
	}
#endif

	return per;
}

/* For the software mixer */
double libxmp_note_to_period_mix(int n, int b)
{
	double d = (double)n + (double)b / 12800;
	return PERIOD_BASE / pow(2, d / 12);
}

/* Get note from period */
/* This function is used only by the MOD loader */
int libxmp_period_to_note(int p)
{
	if (p <= 0) {
		return 0;
	}

	return libxmp_round(12.0 * log(PERIOD_BASE / p) / M_LN2) + 1;
}

/* Get pitchbend from base note and amiga period */
int libxmp_period_to_bend(struct context_data *ctx, double p, int n, double adj)
{
	struct module_data *m = &ctx->m;
	double d;

	if (n == 0) {
		return 0;
	}

	switch (m->period_type) {
	case PERIOD_LINEAR:
		return 100 * (8 * (((240 - n) << 4) - p));
	case PERIOD_CSPD:
		d = libxmp_note_to_period(ctx, n, 0, adj);
		return libxmp_round(100.0 * (1536.0 / M_LN2) * log(p / d));
	default:
		/* Amiga */
		d = libxmp_note_to_period(ctx, n, 0, adj);
		return libxmp_round(100.0 * (1536.0 / M_LN2) * log(d / p));
	}
}

/* Convert finetune = 1200 * log2(C2SPD/8363))
 *
 *      c = (1200.0 * log(c2spd) - 1200.0 * log(c4_rate)) / M_LN2;
 *      xpo = c/100;
 *      fin = 128 * (c%100) / 100;
 */
void libxmp_c2spd_to_note(int c2spd, int *n, int *f)
{
	int c;

	if (c2spd <= 0) {
		*n = *f = 0;
		return;
	}

	c = (int)(1536.0 * log((double)c2spd / 8363) / M_LN2);
	*n = c / 128;
	*f = c % 128;
}


code:
#ifndef LIBXMP_PERIOD_H
#define LIBXMP_PERIOD_H

#define PERIOD_BASE	13696.0		/* C0 period */

/* Macros for period conversion */
#define NOTE_B0		11
#define NOTE_Bb0	(NOTE_B0 + 1)
#define MAX_NOTE	(NOTE_B0 * 8)
#define MAX_PERIOD	0x1c56
#define MIN_PERIOD_A	0x0071
#define MAX_PERIOD_A	0x0358
#define MIN_PERIOD_L	0x0000
#define MAX_PERIOD_L	0x1e00
#define MIN_NOTE_MOD	48
#define MAX_NOTE_MOD	83

double	libxmp_note_to_period	(struct context_data *, int, int, double);
double	libxmp_note_to_period_mix (int, int);
int	libxmp_period_to_note	(int);
int	libxmp_period_to_bend	(struct context_data *, double, int, double);
void	libxmp_c2spd_to_note	(int, int *, int *);

#endif /* LIBXMP_PERIOD_H */

repo_name: orochi/nblood

license: mit


code:
/* Extended Module Player
 * Copyright (C) 1996-2021 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.
 */

/*
 * Sat, 18 Apr 1998 20:23:07 +0200  Frederic Bujon <lvdl@bigfoot.com>
 * Pan effect bug fixed: In Fastracker II the track panning effect erases
 * the instrument panning effect, and the same should happen in xmp.
 */

/*
 * Fri, 26 Jun 1998 13:29:25 -0400 (EDT)
 * Reported by Jared Spiegel <spieg@phair.csh.rit.edu>
 * when the volume envelope is not enabled (disabled) on a sample, and a
 * notoff is delivered to ft2 (via either a noteoff in the note column or
 * command Kxx [where xx is # of ticks into row to give a noteoff to the
 * sample]), ft2 will set the volume of playback of the sample to 00h.
 *
 * Claudio's fix: implementing effect K
 */

#include "virtual.h"
#include "period.h"
#include "effects.h"
#include "player.h"
#include "mixer.h"
#ifndef LIBXMP_CORE_PLAYER
#include "extras.h"
#endif

/* Values for multi-retrig */
static const struct retrig_control rval[] = {
	{   0,  1,  1 }, {  -1,  1,  1 }, {  -2,  1,  1 }, {  -4,  1,  1 },
	{  -8,  1,  1 }, { -16,  1,  1 }, {   0,  2,  3 }, {   0,  1,  2 },
	{   0,  1,  1 }, {   1,  1,  1 }, {   2,  1,  1 }, {   4,  1,  1 },
	{   8,  1,  1 }, {  16,  1,  1 }, {   0,  3,  2 }, {   0,  2,  1 },
	{   0,  0,  1 }		/* Note cut */

};

/*
 * "Anyway I think this is the most brilliant piece of crap we
 *  have managed to put up!"
 *			  -- Ice of FC about "Mental Surgery"
 */

/* Envelope */

static int check_envelope_end(struct xmp_envelope *env, int x)
{
	int16 *data = env->data;
	int idx;

	if (~env->flg & XMP_ENVELOPE_ON || env->npt <= 0)
		return 0;

	idx = (env->npt - 1) * 2;

	/* last node */
	if (x >= data[idx] || idx == 0) {
		if (~env->flg & XMP_ENVELOPE_LOOP) {
			return 1;
		}
	}

	return 0;
}

static int get_envelope(struct xmp_envelope *env, int x, int def)
{
	int x1, x2, y1, y2;
	int16 *data = env->data;
	int idx;

	if (x < 0 || ~env->flg & XMP_ENVELOPE_ON || env->npt <= 0)
		return def;

	idx = (env->npt - 1) * 2;

	x1 = data[idx]; /* last node */
	if (x >= x1 || idx == 0) {
		return data[idx + 1];
	}

	do {
		idx -= 2;
		x1 = data[idx];
	} while (idx > 0 && x1 > x);

	/* interpolate */
	y1 = data[idx + 1];
	x2 = data[idx + 2];
	y2 = data[idx + 3];

	return x2 == x1 ? y2 : ((y2 - y1) * (x - x1) / (x2 - x1)) + y1;
}

static int update_envelope_xm(struct xmp_envelope *env, int x, int release)
{
	int16 *data = env->data;
	int has_loop, has_sus;
	int lpe, lps, sus;

	has_loop = env->flg & XMP_ENVELOPE_LOOP;
	has_sus = env->flg & XMP_ENVELOPE_SUS;

	lps = env->lps << 1;
	lpe = env->lpe << 1;
	sus = env->sus << 1;

	/* FT2 and IT envelopes behave in a different way regarding loops,
	 * sustain and release. When the sustain point is at the end of the
	 * envelope loop end and the key is released, FT2 escapes the loop
	 * while IT runs another iteration. (See EnvLoops.xm in the OpenMPT
	 * test cases.)
	 */
	if (has_loop && has_sus && sus == lpe) {
		if (!release)
			has_sus = 0;
	}

	/* If the envelope point is set to somewhere after the sustain point
	 * or sustain loop, enable release to prevent the envelope point to
	 * return to the sustain point or loop start. (See Filip Skutela's
	 * farewell_tear.xm.)
	 */
	if (has_loop && x > data[lpe] + 1) {
		release = 1;
	} else if (has_sus && x > data[sus] + 1) {
		release = 1;
	}

	/* If enabled, stay at the sustain point */
	if (has_sus && !release) {
		if (x >= data[sus]) {
			x = data[sus];
		}
	}

	/* Envelope loops */
	if (has_loop && x >= data[lpe]) {
		if (!(release && has_sus && sus == lpe))
			x = data[lps];
	}

	return x;
}

#ifndef LIBXMP_CORE_DISABLE_IT

static int update_envelope_it(struct xmp_envelope *env, int x, int release, int key_off)
{
	int16 *data = env->data;
	int has_loop, has_sus;
	int lpe, lps, sus, sue;

	has_loop = env->flg & XMP_ENVELOPE_LOOP;
	has_sus = env->flg & XMP_ENVELOPE_SUS;

	lps = env->lps << 1;
	lpe = env->lpe << 1;
	sus = env->sus << 1;
	sue = env->sue << 1;

	/* Release at the end of a sustain loop, run another loop */
	if (has_sus && key_off && x == data[sue] + 1) {
		x = data[sus];
	} else
	/* If enabled, stay in the sustain loop */
	if (has_sus && !release) {
		if (x == data[sue] + 1) {
			x = data[sus];
		}
	} else
	/* Finally, execute the envelope loop */
	if (has_loop) {
		if (x > data[lpe]) {
			x = data[lps];
		}
	}

	return x;
}

#endif

static int update_envelope(struct xmp_envelope *env, int x, int release, int key_off, int it_env)
{
	if (x < 0xffff)	{	/* increment tick */
		x++;
	}

	if (x < 0) {
		return -1;
	}

	if (~env->flg & XMP_ENVELOPE_ON || env->npt <= 0) {
		return x;
	}

#ifndef LIBXMP_CORE_DISABLE_IT
	return it_env ?
		update_envelope_it(env, x, release, key_off) :
		update_envelope_xm(env, x, release);
#else
	return update_envelope_xm(env, x, release);
#endif
}

/* Returns: 0 if do nothing, <0 to reset channel, >0 if has fade */
static int check_envelope_fade(struct xmp_envelope *env, int x)
{
	int16 *data = env->data;
	int idx;

	if (~env->flg & XMP_ENVELOPE_ON)
		return 0;

	idx = (env->npt - 1) * 2;		/* last node */
	if (x > data[idx]) {
		if (data[idx + 1] == 0)
			return -1;
		else
			return 1;
	}

	return 0;
}

#ifndef LIBXMP_CORE_PLAYER

/* From http://www.un4seen.com/forum/?topic=7554.0
 *
 * "Invert loop" effect replaces (!) sample data bytes within loop with their
 * bitwise complement (NOT). The parameter sets speed of altering the samples.
 * This effectively trashes the sample data. Because of that this effect was
 * supposed to be removed in the very next ProTracker versions, but it was
 * never removed.
 *
 * Prior to [Protracker 1.1A] this effect is called "Funk Repeat" and it moves
 * loop of the instrument (just the loop information - sample data is not
 * altered). The parameter is the speed of moving the loop.
 */

static const int invloop_table[] = {
	0, 5, 6, 7, 8, 10, 11, 13, 16, 19, 22, 26, 32, 43, 64, 128
};

static void update_invloop(struct module_data *m, struct channel_data *xc)
{
	struct xmp_sample *xxs = &m->mod.xxs[xc->smp];
	int len;

	xc->invloop.count += invloop_table[xc->invloop.speed];

	if ((xxs->flg & XMP_SAMPLE_LOOP) && xc->invloop.count >= 128) {
		xc->invloop.count = 0;
		len = xxs->lpe - xxs->lps;

		if (++xc->invloop.pos > len) {
			xc->invloop.pos = 0;
		}

		if (~xxs->flg & XMP_SAMPLE_16BIT) {
			xxs->data[xxs->lps + xc->invloop.pos] ^= 0xff;
		}
	}
}

#endif

/*
 * From OpenMPT Arpeggio.xm test:
 *
 * "[FT2] Arpeggio behavior is very weird with more than 16 ticks per row. This
 *  comes from the fact that Fasttracker 2 uses a LUT for computing the arpeggio
 *  note (instead of doing something like tick%3 or similar). The LUT only has
 *  16 entries, so when there are more than 16 ticks, it reads beyond array
 *  boundaries. The vibrato table happens to be stored right after arpeggio
 *  table. The tables look like this in memory:
 *
 *    ArpTab: 0,1,2,0,1,2,0,1,2,0,1,2,0,1,2,0
 *    VibTab: 0,24,49,74,97,120,141,161,180,197,...
 *
 *  All values except for the first in the vibrato table are greater than 1, so
 *  they trigger the third arpeggio note. Keep in mind that Fasttracker 2 counts
 *  downwards, so the table has to be read from back to front, i.e. at 16 ticks
 *  per row, the 16th entry in the LUT is the first to be read. This is also the
 *  reason why Arpeggio is played 'backwards' in Fasttracker 2."
 */
static int ft2_arpeggio(struct context_data *ctx, struct channel_data *xc)
{
	struct player_data *p = &ctx->p;
	int i;

	if (xc->arpeggio.val[1] == 0 && xc->arpeggio.val[2] == 0) {
		return 0;
	}

	if (p->frame == 0) {
		return 0;
	}

	i = p->speed - (p->frame % p->speed);

	if (i == 16) {
		return 0;
	} else if (i > 16) {
		return xc->arpeggio.val[2];
	}

	return xc->arpeggio.val[i % 3];
}

static int arpeggio(struct context_data *ctx, struct channel_data *xc)
{
	struct module_data *m = &ctx->m;
	int arp;

	if (HAS_QUIRK(QUIRK_FT2BUGS)) {
		arp = ft2_arpeggio(ctx, xc);
	} else {
		arp = xc->arpeggio.val[xc->arpeggio.count];
	}

	xc->arpeggio.count++;
	xc->arpeggio.count %= xc->arpeggio.size;

	return arp;
}

static int is_first_frame(struct context_data *ctx)
{
	struct player_data *p = &ctx->p;
	struct module_data *m = &ctx->m;

	switch (m->read_event_type) {
#ifndef LIBXMP_CORE_DISABLE_IT
	case READ_EVENT_IT:
		/* fall through */
#endif
	case READ_EVENT_ST3:
		return p->frame % p->speed == 0;
	default:
		return p->frame == 0;
	}
}

static void reset_channels(struct context_data *ctx)
{
	struct player_data *p = &ctx->p;
	struct module_data *m = &ctx->m;
	struct xmp_module *mod = &m->mod;
	struct smix_data *smix = &ctx->smix;
	struct channel_data *xc;
	int i;

#ifndef LIBXMP_CORE_PLAYER
	for (i = 0; i < p->virt.virt_channels; i++) {
		void *extra;

		xc = &p->xc_data[i];
		extra = xc->extra;
		memset(xc, 0, sizeof (struct channel_data));
		xc->extra = extra;
		libxmp_reset_channel_extras(ctx, xc);
		xc->ins = -1;
		xc->old_ins = 1;	/* raw value */
		xc->key = -1;
		xc->volume = m->volbase;
	}
#else
	for (i = 0; i < p->virt.virt_channels; i++) {
		xc = &p->xc_data[i];
		memset(xc, 0, sizeof (struct channel_data));
		xc->ins = -1;
		xc->old_ins = 1;	/* raw value */
		xc->key = -1;
		xc->volume = m->volbase;
	}
#endif

	for (i = 0; i < p->virt.num_tracks; i++) {
		xc = &p->xc_data[i];

		if (i >= mod->chn && i < mod->chn + smix->chn) {
			xc->mastervol = 0x40;
			xc->pan.val = 0x80;
		} else {
			xc->mastervol = mod->xxc[i].vol;
			xc->pan.val = mod->xxc[i].pan;
		}

#ifndef LIBXMP_CORE_DISABLE_IT
		xc->filter.cutoff = 0xff;

		/* Amiga split channel */
		if (mod->xxc[i].flg & XMP_CHANNEL_SPLIT) {
			int j;

			xc->split = ((mod->xxc[i].flg & 0x30) >> 4) + 1;
			/* Connect split channel pairs */
			for (j = 0; j < i; j++) {
				if (mod->xxc[j].flg & XMP_CHANNEL_SPLIT) {
					if (p->xc_data[j].split == xc->split) {
						p->xc_data[j].pair = i;
						xc->pair = j;
					}
				}
			}
		} else {
			xc->split = 0;
		}
#endif

		/* Surround channel */
		if (mod->xxc[i].flg & XMP_CHANNEL_SURROUND) {
			xc->pan.surround = 1;
		}
	}
}

static int check_delay(struct context_data *ctx, struct xmp_event *e, int chn)
{
	struct player_data *p = &ctx->p;
	struct channel_data *xc = &p->xc_data[chn];
	struct module_data *m = &ctx->m;

	/* Tempo affects delay and must be computed first */
	if ((e->fxt == FX_SPEED && e->fxp < 0x20) || e->fxt == FX_S3M_SPEED) {
		if (e->fxp) {
			p->speed = e->fxp;
		}
	}
	if ((e->f2t == FX_SPEED && e->f2p < 0x20) || e->f2t == FX_S3M_SPEED) {
		if (e->f2p) {
			p->speed = e->f2p;
		}
	}

	/* Delay event read */
	if (e->fxt == FX_EXTENDED && MSN(e->fxp) == EX_DELAY && LSN(e->fxp)) {
		xc->delay = LSN(e->fxp) + 1;
		goto do_delay;
	}
	if (e->f2t == FX_EXTENDED && MSN(e->f2p) == EX_DELAY && LSN(e->f2p)) {
		xc->delay = LSN(e->f2p) + 1;
		goto do_delay;
	}

	return 0;

    do_delay:
	memcpy(&xc->delayed_event, e, sizeof (struct xmp_event));

	if (e->ins) {
		xc->delayed_ins = e->ins;
	}

	if (HAS_QUIRK(QUIRK_RTDELAY)) {
		if (e->vol == 0 && e->f2t == 0 && e->ins == 0 && e->note != XMP_KEY_OFF)
			xc->delayed_event.vol = xc->volume + 1;
		if (e->note == 0)
			xc->delayed_event.note = xc->key + 1;
		if (e->ins == 0)
			xc->delayed_event.ins = xc->old_ins;
	}

	return 1;
}

static inline void read_row(struct context_data *ctx, int pat, int row)
{
	int chn;
	struct module_data *m = &ctx->m;
	struct xmp_module *mod = &m->mod;
	struct player_data *p = &ctx->p;
	struct flow_control *f = &p->flow;
	struct xmp_event ev;

	for (chn = 0; chn < mod->chn; chn++) {
		const int num_rows = mod->xxt[TRACK_NUM(pat, chn)]->rows;
		if (row < num_rows) {
			memcpy(&ev, &EVENT(pat, chn, row), sizeof(ev));
		} else {
			memset(&ev, 0, sizeof(ev));
		}

		if (ev.note == XMP_KEY_OFF) {
			int env_on = 0;
			int ins = ev.ins - 1;

			if (IS_VALID_INSTRUMENT(ins) &&
			    (mod->xxi[ins].aei.flg & XMP_ENVELOPE_ON)) {
				env_on = 1;
			}

			if (ev.fxt == FX_EXTENDED && MSN(ev.fxp) == EX_DELAY) {
				if (ev.ins && (LSN(ev.fxp) || env_on)) {
					if (LSN(ev.fxp)) {
						ev.note = 0;
					}
					ev.fxp = ev.fxt = 0;
				}
			}
		}

		if (check_delay(ctx, &ev, chn) == 0) {
			/* rowdelay_set bit 1 is set only in the first tick of the row
			 * event if the delay causes the tick count resets to 0. We test
			 * it to read row events only in the start of the row. (see the
			 * OpenMPT test case FineVolColSlide.it)
			 */
			if (!f->rowdelay_set || ((f->rowdelay_set & ROWDELAY_FIRST_FRAME) && f->rowdelay > 0)) {
				libxmp_read_event(ctx, &ev, chn);
#ifndef LIBXMP_CORE_PLAYER
				libxmp_med_hold_hack(ctx, pat, chn, row);
#endif
			}
		} else {
			if (IS_PLAYER_MODE_IT()) {
				/* Reset flags. See SlideDelay.it */
				p->xc_data[chn].flags = 0;
			}
		}
	}
}

static inline int get_channel_vol(struct context_data *ctx, int chn)
{
	struct player_data *p = &ctx->p;
	int root;

	/* channel is a root channel */
	if (chn < p->virt.num_tracks)
		return p->channel_vol[chn];

	/* channel is invalid */
	if (chn >= p->virt.virt_channels)
		return 0;

	/* root is invalid */
	root = libxmp_virt_getroot(ctx, chn);
	if (root < 0)
		return 0;

	return p->channel_vol[root];
}

static int tremor_ft2(struct context_data *ctx, int chn, int finalvol)
{
	struct player_data *p = &ctx->p;
	struct channel_data *xc = &p->xc_data[chn];

	if (xc->tremor.count & 0x80) {
		if (TEST(TREMOR) && p->frame != 0) {
			xc->tremor.count &= ~0x20;
			if (xc->tremor.count == 0x80) {
				/* end of down cycle, set up counter for up  */
				xc->tremor.count = xc->tremor.up | 0xc0;
			} else if (xc->tremor.count == 0xc0) {
				/* end of up cycle, set up counter for down */
				xc->tremor.count = xc->tremor.down | 0x80;
			} else {
				xc->tremor.count--;
			}
		}

		if ((xc->tremor.count & 0xe0) == 0x80) {
			finalvol = 0;
		}
	}

	return finalvol;
}

static int tremor_s3m(struct context_data *ctx, int chn, int finalvol)
{
	struct player_data *p = &ctx->p;
	struct channel_data *xc = &p->xc_data[chn];

	if (TEST(TREMOR)) {
		if (xc->tremor.count == 0) {
			/* end of down cycle, set up counter for up  */
			xc->tremor.count = xc->tremor.up | 0x80;
		} else if (xc->tremor.count == 0x80) {
			/* end of up cycle, set up counter for down */
			xc->tremor.count = xc->tremor.down;
		}

		xc->tremor.count--;

		if (~xc->tremor.count & 0x80) {
			finalvol = 0;
		}
	}

	return finalvol;
}

/*
 * Update channel data
 */

#define DOENV_RELEASE ((TEST_NOTE(NOTE_RELEASE) || act == VIRT_ACTION_OFF))

static void process_volume(struct context_data *ctx, int chn, int act)
{
	struct player_data *p = &ctx->p;
	struct module_data *m = &ctx->m;
	struct channel_data *xc = &p->xc_data[chn];
	struct xmp_instrument *instrument;
	int finalvol;
	uint16 vol_envelope;
	int fade = 0;

	instrument = libxmp_get_instrument(ctx, xc->ins);

	/* Keyoff and fadeout */

	/* Keyoff event in IT doesn't reset fadeout (see jeff93.it)
	 * In XM it depends on envelope (see graff-strange_land.xm vs
	 * Decibelter - Cosmic 'Wegian Mamas.xm)
	 */
	if (HAS_QUIRK(QUIRK_KEYOFF)) {
		/* If IT, only apply fadeout on note release if we don't
		 * have envelope, or if we have envelope loop
		 */
		if (TEST_NOTE(NOTE_RELEASE) || act == VIRT_ACTION_OFF) {
			if ((~instrument->aei.flg & XMP_ENVELOPE_ON) ||
			    (instrument->aei.flg & XMP_ENVELOPE_LOOP)) {
				fade = 1;
			}
		}
	} else {
		if (~instrument->aei.flg & XMP_ENVELOPE_ON) {
			if (TEST_NOTE(NOTE_RELEASE)) {
				xc->fadeout = 0;
			}
		}

		if (TEST_NOTE(NOTE_RELEASE) || act == VIRT_ACTION_OFF) {
			fade = 1;
		}
	}

	if (!TEST_PER(VENV_PAUSE)) {
		xc->v_idx = update_envelope(&instrument->aei, xc->v_idx,
			DOENV_RELEASE, TEST(KEY_OFF), IS_PLAYER_MODE_IT());
	}

	vol_envelope = get_envelope(&instrument->aei, xc->v_idx, 64);
	if (check_envelope_end(&instrument->aei, xc->v_idx)) {
		if (vol_envelope == 0) {
			SET_NOTE(NOTE_END);
		}
		SET_NOTE(NOTE_ENV_END);
	}

	/* IT starts fadeout automatically at the end of the volume envelope. */
	switch (check_envelope_fade(&instrument->aei, xc->v_idx)) {
	case -1:
		SET_NOTE(NOTE_END);
		/* Don't reset channel, we may have a tone portamento later
		 * virt_resetchannel(ctx, chn);
		 */
		break;
	case 0:
		break;
	default:
		if (HAS_QUIRK(QUIRK_ENVFADE)) {
			SET_NOTE(NOTE_FADEOUT);
		}
	}

	/* IT envelope fadeout starts immediately after the envelope tick,
	 * so process fadeout after the volume envelope. */
	if (TEST_NOTE(NOTE_FADEOUT) || act == VIRT_ACTION_FADE) {
		fade = 1;
	}

	if (fade) {
		if (xc->fadeout > xc->ins_fade) {
			xc->fadeout -= xc->ins_fade;
		} else {
			xc->fadeout = 0;
			SET_NOTE(NOTE_END);
		}
	}

	/* If note ended in background channel, we can safely reset it */
	if (TEST_NOTE(NOTE_END) && chn >= p->virt.num_tracks) {
		libxmp_virt_resetchannel(ctx, chn);
		return;
	}

#ifndef LIBXMP_CORE_PLAYER
	finalvol = libxmp_extras_get_volume(ctx, xc);
#else
	finalvol = xc->volume;
#endif

	if (IS_PLAYER_MODE_IT()) {
		finalvol = xc->volume * (100 - xc->rvv) / 100;
	}

	if (TEST(TREMOLO)) {
		/* OpenMPT VibratoReset.mod */
		if (!is_first_frame(ctx) || !HAS_QUIRK(QUIRK_PROTRACK)) {
			finalvol += libxmp_lfo_get(ctx, &xc->tremolo.lfo, 0) / (1 << 6);
		}

		if (!is_first_frame(ctx) || HAS_QUIRK(QUIRK_VIBALL)) {
			libxmp_lfo_update(&xc->tremolo.lfo);
		}
	}

	CLAMP(finalvol, 0, m->volbase);

	finalvol = (finalvol * xc->fadeout) >> 6;	/* 16 bit output */

	finalvol = (uint32)(vol_envelope * p->gvol * xc->mastervol /
		m->gvolbase * ((int)finalvol * 0x40 / m->volbase)) >> 18;

	/* Apply channel volume */
	finalvol = finalvol * get_channel_vol(ctx, chn) / 100;

#ifndef LIBXMP_CORE_PLAYER
	/* Volume translation table (for PTM, ARCH, COCO) */
	if (m->vol_table) {
		finalvol = m->volbase == 0xff ?
		    m->vol_table[finalvol >> 2] << 2 :
		    m->vol_table[finalvol >> 4] << 4;
	}
#endif

	if (HAS_QUIRK(QUIRK_INSVOL)) {
		finalvol = (finalvol * instrument->vol * xc->gvl) >> 12;
	}

	if (IS_PLAYER_MODE_FT2()) {
		finalvol = tremor_ft2(ctx, chn, finalvol);
	} else {
		finalvol = tremor_s3m(ctx, chn, finalvol);
	}

	if (chn < m->mod.chn) {
		finalvol = finalvol * p->master_vol / 100;
	} else {
		finalvol = finalvol * p->smix_vol / 100;
	}

	xc->info_finalvol = TEST_NOTE(NOTE_SAMPLE_END) ? 0 : finalvol;

	libxmp_virt_setvol(ctx, chn, finalvol);

	/* Check Amiga split channel */
	if (xc->split) {
		libxmp_virt_setvol(ctx, xc->pair, finalvol);
	}
}

static void process_frequency(struct context_data *ctx, int chn, int act)
{
#ifndef LIBXMP_CORE_DISABLE_IT
	struct mixer_data *s = &ctx->s;
#endif
	struct player_data *p = &ctx->p;
	struct module_data *m = &ctx->m;
	struct channel_data *xc = &p->xc_data[chn];
	struct xmp_instrument *instrument;
	double period, vibrato;
	double final_period;
	int linear_bend;
	int frq_envelope;
	int arp;
#ifndef LIBXMP_CORE_DISABLE_IT
	int cutoff, resonance;
#endif

	instrument = libxmp_get_instrument(ctx, xc->ins);

	if (!TEST_PER(FENV_PAUSE)) {
		xc->f_idx = update_envelope(&instrument->fei, xc->f_idx,
			DOENV_RELEASE, TEST(KEY_OFF), IS_PLAYER_MODE_IT());
	}
	frq_envelope = get_envelope(&instrument->fei, xc->f_idx, 0);

#ifndef LIBXMP_CORE_PLAYER
	/* Do note slide */

	if (TEST(NOTE_SLIDE)) {
		if (xc->noteslide.count == 0) {
			xc->note += xc->noteslide.slide;
			xc->period = libxmp_note_to_period(ctx, xc->note,
					xc->finetune, xc->per_adj);
			xc->noteslide.count = xc->noteslide.speed;
		}
		xc->noteslide.count--;

		libxmp_virt_setnote(ctx, chn, xc->note);
	}
#endif

	/* Instrument vibrato */
	vibrato = 1.0 * libxmp_lfo_get(ctx, &xc->insvib.lfo, 1) /
				(4096 * (1 + xc->insvib.sweep));
	libxmp_lfo_update(&xc->insvib.lfo);
	if (xc->insvib.sweep > 1) {
		xc->insvib.sweep -= 2;
	} else {
		xc->insvib.sweep = 0;
	}

	/* Vibrato */
	if (TEST(VIBRATO) || TEST_PER(VIBRATO)) {
		/* OpenMPT VibratoReset.mod */
		if (!is_first_frame(ctx) || !HAS_QUIRK(QUIRK_PROTRACK)) {
			int shift = HAS_QUIRK(QUIRK_VIBHALF) ? 10 : 9;
			int vib = libxmp_lfo_get(ctx, &xc->vibrato.lfo, 1) / (1 << shift);

			if (HAS_QUIRK(QUIRK_VIBINV)) {
				vibrato -= vib;
			} else {
				vibrato += vib;
			}
		}

		if (!is_first_frame(ctx) || HAS_QUIRK(QUIRK_VIBALL)) {
			libxmp_lfo_update(&xc->vibrato.lfo);
		}
	}

	period = xc->period;
#ifndef LIBXMP_CORE_PLAYER
	period += libxmp_extras_get_period(ctx, xc);
#endif

	if (HAS_QUIRK(QUIRK_ST3BUGS)) {
		if (period < 0.25) {
			libxmp_virt_resetchannel(ctx, chn);
		}
	}
	/* Sanity check */
	if (period < 0.1) {
		period = 0.1;
	}

	/* Arpeggio */
	arp = arpeggio(ctx, xc);

	/* Pitch bend */

	linear_bend = libxmp_period_to_bend(ctx, period + vibrato, xc->note, xc->per_adj);

	if (TEST_NOTE(NOTE_GLISSANDO) && TEST(TONEPORTA)) {
		if (linear_bend > 0) {
			linear_bend = (linear_bend + 6400) / 12800 * 12800;
		} else if (linear_bend < 0) {
			linear_bend = (linear_bend - 6400) / 12800 * 12800;
		}
	}

	if (HAS_QUIRK(QUIRK_FT2BUGS)) {
		if  (arp) {
			/* OpenMPT ArpSlide.xm */
			linear_bend = linear_bend / 12800 * 12800 +
							xc->finetune * 100;

			/* OpenMPT ArpeggioClamp.xm */
			if (xc->note + arp > 107) {
				if (p->speed - (p->frame % p->speed) > 0) {
					arp = 108 - xc->note;
				}
			}
		}
	}

	/* Envelope */

	if (xc->f_idx >= 0 && (~instrument->fei.flg & XMP_ENVELOPE_FLT)) {
		/* IT pitch envelopes are always linear, even in Amiga period
		 * mode. Each unit in the envelope scale is 1/25 semitone.
		 */
		linear_bend += frq_envelope << 7;
	}

	/* Arpeggio */

	if (arp != 0) {
		linear_bend += (100 << 7) * arp;

		/* OpenMPT ArpWrapAround.mod */
		if (HAS_QUIRK(QUIRK_PROTRACK)) {
			if (xc->note + arp > MAX_NOTE_MOD + 1) {
				linear_bend -= 12800 * (3 * 12);
			} else if (xc->note + arp > MAX_NOTE_MOD) {
				libxmp_virt_setvol(ctx, chn, 0);
			}
		}
	}

#ifndef LIBXMP_CORE_PLAYER
	linear_bend += libxmp_extras_get_linear_bend(ctx, xc);
#endif

	final_period = libxmp_note_to_period_mix(xc->note, linear_bend);

	/* From OpenMPT PeriodLimit.s3m:
	 * "ScreamTracker 3 limits the final output period to be at least 64,
	 *  i.e. when playing a note that is too high or when sliding the
	 *  period lower than 64, the output period will simply be clamped to
	 *  64. However, when reaching a period of 0 through slides, the
	 *  output on the channel should be stopped."
	 */
	/* ST3 uses periods*4, so the limit is 16. Adjusted to the exact
	 * A6 value because we compute periods in floating point.
	 */
	if (HAS_QUIRK(QUIRK_ST3BUGS)) {
		if (final_period < 16.239270) {	/* A6 */
			final_period = 16.239270;
		}
	}

	libxmp_virt_setperiod(ctx, chn, final_period);

	/* For xmp_get_frame_info() */
	xc->info_pitchbend = linear_bend >> 7;
	xc->info_period = final_period * 4096;

	if (IS_PERIOD_MODRNG()) {
		CLAMP(xc->info_period,
			libxmp_note_to_period(ctx, MAX_NOTE_MOD, xc->finetune, 0) * 4096,
			libxmp_note_to_period(ctx, MIN_NOTE_MOD, xc->finetune, 0) * 4096);
	} else if (xc->info_period < (1 << 12)) {
		xc->info_period = (1 << 12);
	}

#ifndef LIBXMP_CORE_DISABLE_IT

	/* Process filter */

	if (!HAS_QUIRK(QUIRK_FILTER)) {
		return;
	}

	if (xc->f_idx >= 0 && (instrument->fei.flg & XMP_ENVELOPE_FLT)) {
		if (frq_envelope < 0xfe) {
			xc->filter.envelope = frq_envelope;
		}
		cutoff = xc->filter.cutoff * xc->filter.envelope >> 8;
	} else {
		cutoff = xc->filter.cutoff;
	}
	resonance = xc->filter.resonance;

	if (cutoff > 0xff) {
		cutoff = 0xff;
	} else if (cutoff < 0xff) {
		int a0, b0, b1;
		libxmp_filter_setup(s->freq, cutoff, resonance, &a0, &b0, &b1);
		libxmp_virt_seteffect(ctx, chn, DSP_EFFECT_FILTER_A0, a0);
		libxmp_virt_seteffect(ctx, chn, DSP_EFFECT_FILTER_B0, b0);
		libxmp_virt_seteffect(ctx, chn, DSP_EFFECT_FILTER_B1, b1);
		libxmp_virt_seteffect(ctx, chn, DSP_EFFECT_RESONANCE, resonance);
	}

	/* Always set cutoff */
	libxmp_virt_seteffect(ctx, chn, DSP_EFFECT_CUTOFF, cutoff);

#endif
}

static void process_pan(struct context_data *ctx, int chn, int act)
{
	struct player_data *p = &ctx->p;
	struct module_data *m = &ctx->m;
	struct mixer_data *s = &ctx->s;
	struct channel_data *xc = &p->xc_data[chn];
	struct xmp_instrument *instrument;
	int finalpan, panbrello = 0;
	int pan_envelope;
	int channel_pan;

	instrument = libxmp_get_instrument(ctx, xc->ins);

	if (!TEST_PER(PENV_PAUSE)) {
		xc->p_idx = update_envelope(&instrument->pei, xc->p_idx,
			DOENV_RELEASE, TEST(KEY_OFF), IS_PLAYER_MODE_IT());
	}
	pan_envelope = get_envelope(&instrument->pei, xc->p_idx, 32);

#ifndef LIBXMP_CORE_DISABLE_IT
	if (TEST(PANBRELLO)) {
		panbrello = libxmp_lfo_get(ctx, &xc->panbrello.lfo, 0) / 512;
		if (is_first_frame(ctx)) {
			libxmp_lfo_update(&xc->panbrello.lfo);
		}
	}
#endif

	channel_pan = xc->pan.val;

#if 0
#ifdef LIBXMP_PAULA_SIMULATOR
	/* Always use 100% pan separation in Amiga mode */
	if (p->flags & XMP_FLAGS_A500) {
		if (IS_AMIGA_MOD()) {
			channel_pan = channel_pan < 0x80 ? 0 : 0xff;
		}
	}
#endif
#endif

	finalpan = channel_pan + panbrello + (pan_envelope - 32) *
				(128 - abs(xc->pan.val - 128)) / 32;

	if (IS_PLAYER_MODE_IT()) {
		finalpan = finalpan + xc->rpv * 4;
	}

	CLAMP(finalpan, 0, 255);

	if (s->format & XMP_FORMAT_MONO || xc->pan.surround) {
		finalpan = 0;
	} else {
		finalpan = (finalpan - 0x80) * s->mix / 100;
	}

	xc->info_finalpan = finalpan + 0x80;

	if (xc->pan.surround) {
		libxmp_virt_setpan(ctx, chn, PAN_SURROUND);
	} else {
		libxmp_virt_setpan(ctx, chn, finalpan);
	}
}

static void update_volume(struct context_data *ctx, int chn)
{
	struct player_data *p = &ctx->p;
	struct module_data *m = &ctx->m;
#ifndef LIBXMP_CORE_DISABLE_IT
	struct flow_control *f = &p->flow;
#endif
	struct channel_data *xc = &p->xc_data[chn];

	/* Volume slides happen in all frames but the first, except when the
	 * "volume slide on all frames" flag is set.
	 */
	if (p->frame % p->speed != 0 || HAS_QUIRK(QUIRK_VSALL)) {
		if (TEST(GVOL_SLIDE)) {
			p->gvol += xc->gvol.slide;
		}

		if (TEST(VOL_SLIDE) || TEST_PER(VOL_SLIDE)) {
			xc->volume += xc->vol.slide;
		}

#ifndef LIBXMP_CORE_PLAYER
		if (TEST_PER(VOL_SLIDE)) {
			if (xc->vol.slide > 0 && xc->volume > m->volbase) {
				xc->volume = m->volbase;
				RESET_PER(VOL_SLIDE);
			}
			if (xc->vol.slide < 0 && xc->volume < 0) {
				xc->volume = 0;
				RESET_PER(VOL_SLIDE);
			}
		}
#endif

		if (TEST(VOL_SLIDE_2)) {
			xc->volume += xc->vol.slide2;
		}
		if (TEST(TRK_VSLIDE)) {
			xc->mastervol += xc->trackvol.slide;
		}
	}

	if (p->frame % p->speed == 0) {
		/* Process "fine" effects */
		if (TEST(FINE_VOLS)) {
			xc->volume += xc->vol.fslide;
		}

#ifndef LIBXMP_CORE_DISABLE_IT
		if (TEST(FINE_VOLS_2)) {
			/* OpenMPT FineVolColSlide.it:
			 * Unlike fine volume slides in the effect column,
			 * fine volume slides in the volume column are only
			 * ever executed on the first tick -- not on multiples
			 * of the first tick if there is a pattern delay.
			 */
			if (!f->rowdelay_set || f->rowdelay_set & ROWDELAY_FIRST_FRAME) {
				xc->volume += xc->vol.fslide2;
			}
		}
#endif

		if (TEST(TRK_FVSLIDE)) {
			xc->mastervol += xc->trackvol.fslide;
		}

		if (TEST(GVOL_SLIDE)) {
			p->gvol += xc->gvol.fslide;
		}
	}

	/* Clamp volumes */
	CLAMP(xc->volume, 0, m->volbase);
	CLAMP(p->gvol, 0, m->gvolbase);
	CLAMP(xc->mastervol, 0, m->volbase);

	if (xc->split) {
		p->xc_data[xc->pair].volume = xc->volume;
	}
}

static void update_frequency(struct context_data *ctx, int chn)
{
	struct player_data *p = &ctx->p;
	struct module_data *m = &ctx->m;
	struct channel_data *xc = &p->xc_data[chn];

	if (!is_first_frame(ctx) || HAS_QUIRK(QUIRK_PBALL)) {
		if (TEST(PITCHBEND) || TEST_PER(PITCHBEND)) {
			xc->period += xc->freq.slide;
			if (HAS_QUIRK(QUIRK_PROTRACK)) {
				xc->porta.target = xc->period;
			}
		}

		/* Do tone portamento */
		if (TEST(TONEPORTA) || TEST_PER(TONEPORTA)) {
			if (xc->porta.target > 0) {
				int end = 0;
				if (xc->porta.dir > 0) {
					xc->period += xc->porta.slide;
					if (xc->period >= xc->porta.target)
						end = 1;
				} else {
					xc->period -= xc->porta.slide;
					if (xc->period <= xc->porta.target)
						end = 1;
				}

				if (end) {
					/* reached end */
					xc->period = xc->porta.target;
					xc->porta.dir = 0;
					RESET(TONEPORTA);
					RESET_PER(TONEPORTA);

					if (HAS_QUIRK(QUIRK_PROTRACK)) {
						xc->porta.target = -1;
					}
				}
			}
		}
	}

	if (is_first_frame(ctx)) {
		if (TEST(FINE_BEND)) {
			xc->period += xc->freq.fslide;
		}

#ifndef LIBXMP_CORE_PLAYER
		if (TEST(FINE_NSLIDE)) {
			xc->note += xc->noteslide.fslide;
			xc->period = libxmp_note_to_period(ctx, xc->note,
				xc->finetune, xc->per_adj);
		}
#endif
	}

	switch (m->period_type) {
	case PERIOD_LINEAR:
		CLAMP(xc->period, MIN_PERIOD_L, MAX_PERIOD_L);
		break;
	case PERIOD_MODRNG:
		CLAMP(xc->period,
			libxmp_note_to_period(ctx, MAX_NOTE_MOD, xc->finetune, 0),
			libxmp_note_to_period(ctx, MIN_NOTE_MOD, xc->finetune, 0));
		break;
	}

	/* Check for invalid periods (from Toru Egashira's NSPmod)
	 * panic.s3m has negative periods
	 * ambio.it uses low (~8) period values
	 */
	if (xc->period < 0.25) {
		libxmp_virt_setvol(ctx, chn, 0);
	}
}

static void update_pan(struct context_data *ctx, int chn)
{
	struct player_data *p = &ctx->p;
	struct channel_data *xc = &p->xc_data[chn];

	if (TEST(PAN_SLIDE)) {
		if (is_first_frame(ctx)) {
			xc->pan.val += xc->pan.fslide;
		} else {
			xc->pan.val += xc->pan.slide;
		}

		if (xc->pan.val < 0) {
			xc->pan.val = 0;
		} else if (xc->pan.val > 0xff) {
			xc->pan.val = 0xff;
		}
	}
}

static void play_channel(struct context_data *ctx, 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];
	int act;

	xc->info_finalvol = 0;

#ifndef LIBXMP_CORE_DISABLE_IT
	/* IT tempo slide */
	if (!is_first_frame(ctx) && TEST(TEMPO_SLIDE)) {
		p->bpm += xc->tempo.slide;
		CLAMP(p->bpm, 0x20, 0xff);
	}
#endif

	/* Do delay */
	if (xc->delay > 0) {
		if (--xc->delay == 0) {
			libxmp_read_event(ctx, &xc->delayed_event, chn);
		}
	}

	act = libxmp_virt_cstat(ctx, chn);
	if (act == VIRT_INVALID) {
		/* We need this to keep processing global volume slides */
		update_volume(ctx, chn);
		return;
	}

	if (p->frame == 0 && act != VIRT_ACTIVE) {
		if (!IS_VALID_INSTRUMENT_OR_SFX(xc->ins) || act == VIRT_ACTION_CUT) {
			libxmp_virt_resetchannel(ctx, chn);
			return;
		}
	}

	if (!IS_VALID_INSTRUMENT_OR_SFX(xc->ins))
		return;

#ifndef LIBXMP_CORE_PLAYER
	libxmp_play_extras(ctx, xc, chn);
#endif

	/* Do cut/retrig */
	if (TEST(RETRIG)) {
		int cond = HAS_QUIRK(QUIRK_S3MRTG) ?
				--xc->retrig.count <= 0 :
				--xc->retrig.count == 0;

		if (cond) {
			if (xc->retrig.type < 0x10) {
				/* don't retrig on cut */
				libxmp_virt_voicepos(ctx, chn, 0);
			} else {
				SET_NOTE(NOTE_END);
			}
			xc->volume += rval[xc->retrig.type].s;
			xc->volume *= rval[xc->retrig.type].m;
			xc->volume /= rval[xc->retrig.type].d;
                	xc->retrig.count = LSN(xc->retrig.val);
		}
        }

	/* Do keyoff */
	if (xc->keyoff) {
		if (--xc->keyoff == 0)
			SET_NOTE(NOTE_RELEASE);
	}

	libxmp_virt_release(ctx, chn, TEST_NOTE(NOTE_RELEASE));

	update_volume(ctx, chn);
	update_frequency(ctx, chn);
	update_pan(ctx, chn);

	process_volume(ctx, chn, act);
	process_frequency(ctx, chn, act);
	process_pan(ctx, chn, act);

#ifndef LIBXMP_CORE_PLAYER
	if (HAS_QUIRK(QUIRK_PROTRACK) && xc->ins < mod->ins) {
		update_invloop(m, xc);
	}
#endif

	if (TEST_NOTE(NOTE_SUSEXIT)) {
		SET_NOTE(NOTE_RELEASE);
	}

	xc->info_position = libxmp_virt_getvoicepos(ctx, chn);
}

/*
 * Event injection
 */

static void inject_event(struct context_data *ctx)
{
	struct player_data *p = &ctx->p;
	struct module_data *m = &ctx->m;
	struct xmp_module *mod = &m->mod;
	struct smix_data *smix = &ctx->smix;
	int chn;

	for (chn = 0; chn < mod->chn + smix->chn; chn++) {
		struct xmp_event *e = &p->inject_event[chn];
		if (e->_flag > 0) {
			libxmp_read_event(ctx, e, chn);
			e->_flag = 0;
		}
	}
}

/*
 * Sequencing
 */

static void next_order(struct context_data *ctx)
{
	struct player_data *p = &ctx->p;
	struct flow_control *f = &p->flow;
	struct module_data *m = &ctx->m;
	struct xmp_module *mod = &m->mod;
	int mark;

	do {
    		p->ord++;

		/* Restart module */
		mark = HAS_QUIRK(QUIRK_MARKER) && mod->xxo[p->ord] == 0xff;
		if (p->ord >= mod->len || mark) {
			if (mod->rst > mod->len ||
			    mod->xxo[mod->rst] >= mod->pat ||
			    p->ord < m->seq_data[p->sequence].entry_point) {
				p->ord = m->seq_data[p->sequence].entry_point;
			} else {
				if (libxmp_get_sequence(ctx, mod->rst) == p->sequence) {
					p->ord = mod->rst;
				} else {
					p->ord = m->seq_data[p->sequence].entry_point;
				}
			}

			p->gvol = m->xxo_info[p->ord].gvl;
		}
	} while (mod->xxo[p->ord] >= mod->pat);

	p->current_time = m->xxo_info[p->ord].time;

	f->num_rows = mod->xxp[mod->xxo[p->ord]]->rows;
	if (f->jumpline >= f->num_rows)
		f->jumpline = 0;
	p->row = f->jumpline;
	f->jumpline = 0;

	p->pos = p->ord;
	p->frame = 0;

#ifndef LIBXMP_CORE_PLAYER
	/* Reset persistent effects at new pattern */
	if (HAS_QUIRK(QUIRK_PERPAT)) {
		int chn;
		for (chn = 0; chn < mod->chn; chn++) {
			p->xc_data[chn].per_flags = 0;
		}
	}
#endif
}

static void next_row(struct context_data *ctx)
{
	struct player_data *p = &ctx->p;
	struct flow_control *f = &p->flow;

	p->frame = 0;
	f->delay = 0;

	if (f->pbreak) {
		f->pbreak = 0;

		if (f->jump != -1) {
			p->ord = f->jump - 1;
			f->jump = -1;
		}

		next_order(ctx);
	} else {
		if (f->loop_chn) {
			p->row = f->loop[f->loop_chn - 1].start - 1;
			f->loop_chn = 0;
		}

		if (f->rowdelay == 0) {
			p->row++;
			f->rowdelay_set = 0;
		} else {
			f->rowdelay--;
		}

		/* check end of pattern */
		if (p->row >= f->num_rows) {
			next_order(ctx);
		}
	}
}

#ifndef LIBXMP_CORE_DISABLE_IT

/*
 * Set note action for libxmp_virt_pastnote
 */
void libxmp_player_set_release(struct context_data *ctx, int chn)
{
	struct player_data *p = &ctx->p;
	struct channel_data *xc = &p->xc_data[chn];

	SET_NOTE(NOTE_RELEASE);
}

void libxmp_player_set_fadeout(struct context_data *ctx, int chn)
{
	struct player_data *p = &ctx->p;
	struct channel_data *xc = &p->xc_data[chn];

	SET_NOTE(NOTE_FADEOUT);
}

#endif

static void update_from_ord_info(struct context_data *ctx)
{
	struct player_data *p = &ctx->p;
	struct module_data *m = &ctx->m;
	struct ord_data *oinfo = &m->xxo_info[p->ord];

	if (oinfo->speed)
		p->speed = oinfo->speed;
	p->bpm = oinfo->bpm;
	p->gvol = oinfo->gvl;
	p->current_time = oinfo->time;
	p->frame_time = m->time_factor * m->rrate / p->bpm;

#ifndef LIBXMP_CORE_PLAYER
	p->st26_speed = oinfo->st26_speed;
#endif
}

int xmp_start_player(xmp_context opaque, int rate, int format)
{
	struct context_data *ctx = (struct context_data *)opaque;
	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 flow_control *f = &p->flow;
	int i;
	int ret = 0;

	if (rate < XMP_MIN_SRATE || rate > XMP_MAX_SRATE)
		return -XMP_ERROR_INVALID;

	if (ctx->state < XMP_STATE_LOADED)
		return -XMP_ERROR_STATE;

	if (ctx->state > XMP_STATE_LOADED)
		xmp_end_player(opaque);

	if (libxmp_mixer_on(ctx, rate, format, m->c4rate) < 0)
		return -XMP_ERROR_INTERNAL;

	p->master_vol = 100;
	p->smix_vol = 100;
	p->gvol = m->volbase;
	p->pos = p->ord = 0;
	p->frame = -1;
	p->row = 0;
	p->current_time = 0;
	p->loop_count = 0;
	p->sequence = 0;

	/* Set default volume and mute status */
	for (i = 0; i < mod->chn; i++) {
		if (mod->xxc[i].flg & XMP_CHANNEL_MUTE)
			p->channel_mute[i] = 1;
		p->channel_vol[i] = 100;
	}
	for (i = mod->chn; i < XMP_MAX_CHANNELS; i++) {
		p->channel_mute[i] = 0;
		p->channel_vol[i] = 100;
	}

	/* Skip invalid patterns at start (the seventh laboratory.it) */
	while (p->ord < mod->len && mod->xxo[p->ord] >= mod->pat) {
		p->ord++;
	}
	/* Check if all positions skipped */
	if (p->ord >= mod->len) {
		mod->len = 0;
	}

	if (mod->len == 0 || mod->chn == 0) {
		/* set variables to sane state */
		p->ord = p->scan[0].ord = 0;
		p->row = p->scan[0].row = 0;
		f->end_point = 0;
		f->num_rows = 0;
	} else {
		f->num_rows = mod->xxp[mod->xxo[p->ord]]->rows;
		f->end_point = p->scan[0].num;
	}

	update_from_ord_info(ctx);

	if (libxmp_virt_on(ctx, mod->chn + smix->chn) != 0) {
		ret = -XMP_ERROR_INTERNAL;
		goto err;
	}

	f->delay = 0;
	f->jumpline = 0;
	f->jump = -1;
	f->pbreak = 0;
	f->rowdelay_set = 0;

	f->loop = (struct pattern_loop *)calloc(p->virt.virt_channels, sizeof(struct pattern_loop));
	if (f->loop == NULL) {
		ret = -XMP_ERROR_SYSTEM;
		goto err;
	}

	p->xc_data = (struct channel_data *)calloc(p->virt.virt_channels, sizeof(struct channel_data));
	if (p->xc_data == NULL) {
		ret = -XMP_ERROR_SYSTEM;
		goto err1;
	}

	/* Reset our buffer pointers */
	xmp_play_buffer(opaque, NULL, 0, 0);

#ifndef LIBXMP_CORE_PLAYER
	for (i = 0; i < p->virt.virt_channels; i++) {
		struct channel_data *xc = &p->xc_data[i];
		if (libxmp_new_channel_extras(ctx, xc) < 0)
			goto err2;
	}
#endif
	reset_channels(ctx);

	ctx->state = XMP_STATE_PLAYING;

	return 0;

#ifndef LIBXMP_CORE_PLAYER
    err2:
	free(p->xc_data);
	p->xc_data = NULL;
#endif
    err1:
	free(f->loop);
	f->loop = NULL;
    err:
	return ret;
}

static void check_end_of_module(struct context_data *ctx)
{
	struct player_data *p = &ctx->p;
	struct flow_control *f = &p->flow;

	/* check end of module */
	if (p->ord == p->scan[p->sequence].ord &&
			p->row == p->scan[p->sequence].row) {
		if (f->end_point == 0) {
			p->loop_count++;
			f->end_point = p->scan[p->sequence].num;
			/* return -1; */
		}
		f->end_point--;
	}
}

int xmp_play_frame(xmp_context opaque)
{
	struct context_data *ctx = (struct context_data *)opaque;
	struct player_data *p = &ctx->p;
	struct module_data *m = &ctx->m;
	struct xmp_module *mod = &m->mod;
	struct flow_control *f = &p->flow;
	int i;

	if (ctx->state < XMP_STATE_PLAYING)
		return -XMP_ERROR_STATE;

	if (mod->len <= 0) {
		return -XMP_END;
	}

	if (HAS_QUIRK(QUIRK_MARKER) && mod->xxo[p->ord] == 0xff) {
		return -XMP_END;
	}

	/* check reposition */
	if (p->ord != p->pos) {
		int start = m->seq_data[p->sequence].entry_point;

		if (p->pos == -2) {		/* set by xmp_module_stop */
			return -XMP_END;	/* that's all folks */
		}

		if (p->pos == -1) {
			/* restart sequence */
			p->pos = start;
		}

		if (p->pos == start) {
			f->end_point = p->scan[p->sequence].num;
		}

		/* Check if lands after a loop point */
		if (p->pos > p->scan[p->sequence].ord) {
			f->end_point = 0;
		}

		f->jumpline = 0;
		f->jump = -1;

		p->ord = p->pos - 1;

		/* Stay inside our subsong */
		if (p->ord < start) {
			p->ord = start - 1;
		}

		next_order(ctx);

		update_from_ord_info(ctx);

		libxmp_virt_reset(ctx);
		reset_channels(ctx);
	} else {
		p->frame++;
		if (p->frame >= (p->speed * (1 + f->delay))) {
			/* If break during pattern delay, next row is skipped.
			 * See corruption.mod order 1D (pattern 0D) last line:
			 * EE2 + D31 ignores D00 in order 1C line 31. Reported
			 * by The Welder <welder@majesty.net>, Jan 14 2012
			 */
			if (HAS_QUIRK(QUIRK_PROTRACK) && f->delay && f->pbreak)
			{
				next_row(ctx);
				check_end_of_module(ctx);
			}
			next_row(ctx);
		}
	}

	for (i = 0; i < mod->chn; i++) {
		struct channel_data *xc = &p->xc_data[i];
		RESET(KEY_OFF);
	}

	/* check new row */

	if (p->frame == 0) {			/* first frame in row */
		check_end_of_module(ctx);
		read_row(ctx, mod->xxo[p->ord], p->row);

#ifndef LIBXMP_CORE_PLAYER
		if (p->st26_speed) {
			if  (p->st26_speed & 0x10000) {
				p->speed = (p->st26_speed & 0xff00) >> 8;
			} else {
				p->speed = p->st26_speed & 0xff;
			}
			p->st26_speed ^= 0x10000;
		}
#endif
	}

	inject_event(ctx);

	/* play_frame */
	for (i = 0; i < p->virt.virt_channels; i++) {
		play_channel(ctx, i);
	}

	f->rowdelay_set &= ~ROWDELAY_FIRST_FRAME;

	p->frame_time = m->time_factor * m->rrate / p->bpm;
	p->current_time += p->frame_time;

	libxmp_mixer_softmixer(ctx);

	return 0;
}

int xmp_play_buffer(xmp_context opaque, void *out_buffer, int size, int loop)
{
	struct context_data *ctx = (struct context_data *)opaque;
	struct player_data *p = &ctx->p;
	int ret = 0, filled = 0, copy_size;
	struct xmp_frame_info fi;

	/* Reset internal state
	 * Syncs buffer start with frame start */
	if (out_buffer == NULL) {
		p->loop_count = 0;
		p->buffer_data.consumed = 0;
		p->buffer_data.in_size = 0;
		return 0;
	}

	if (ctx->state < XMP_STATE_PLAYING)
		return -XMP_ERROR_STATE;

	/* Fill buffer */
	while (filled < size) {
		/* Check if buffer full */
		if (p->buffer_data.consumed == p->buffer_data.in_size) {
			ret = xmp_play_frame(opaque);
			xmp_get_frame_info(opaque, &fi);

			/* Check end of module */
			if (ret < 0 || (loop > 0 && fi.loop_count >= loop)) {
				/* Start of frame, return end of replay */
				if (filled == 0) {
					p->buffer_data.consumed = 0;
					p->buffer_data.in_size = 0;
					return -1;
				}

				/* Fill remaining of this buffer */
				memset((char *)out_buffer + filled, 0, size - filled);
				return 0;
			}

			p->buffer_data.consumed = 0;
			p->buffer_data.in_buffer = (char *)fi.buffer;
			p->buffer_data.in_size = fi.buffer_size;
		}

		/* Copy frame data to user buffer */
		copy_size = MIN(size - filled, p->buffer_data.in_size -
					p->buffer_data.consumed);
		memcpy((char *)out_buffer + filled, p->buffer_data.in_buffer +
					p->buffer_data.consumed, copy_size);
		p->buffer_data.consumed += copy_size;
		filled += copy_size;
	}

	return ret;
}

void xmp_end_player(xmp_context opaque)
{
	struct context_data *ctx = (struct context_data *)opaque;
	struct player_data *p = &ctx->p;
	struct flow_control *f = &p->flow;
#ifndef LIBXMP_CORE_PLAYER
	struct channel_data *xc;
	int i;
#endif

	if (ctx->state < XMP_STATE_PLAYING)
		return;

	ctx->state = XMP_STATE_LOADED;

#ifndef LIBXMP_CORE_PLAYER
	/* Free channel extras */
	for (i = 0; i < p->virt.virt_channels; i++) {
		xc = &p->xc_data[i];
		libxmp_release_channel_extras(ctx, xc);
	}
#endif

	libxmp_virt_off(ctx);

	free(p->xc_data);
	free(f->loop);

	p->xc_data = NULL;
	f->loop = NULL;

	libxmp_mixer_off(ctx);
}

void xmp_get_module_info(xmp_context opaque, struct xmp_module_info *info)
{
	struct context_data *ctx = (struct context_data *)opaque;
	struct module_data *m = &ctx->m;
	struct xmp_module *mod = &m->mod;

	if (ctx->state < XMP_STATE_LOADED)
		return;

	memcpy(info->md5, m->md5, 16);
	info->mod = mod;
	info->comment = m->comment;
	info->num_sequences = m->num_sequences;
	info->seq_data = m->seq_data;
	info->vol_base = m->volbase;
}

void xmp_get_frame_info(xmp_context opaque, struct xmp_frame_info *info)
{
	struct context_data *ctx = (struct context_data *)opaque;
	struct player_data *p = &ctx->p;
	struct mixer_data *s = &ctx->s;
	struct module_data *m = &ctx->m;
	struct xmp_module *mod = &m->mod;
	int chn, i;

	if (ctx->state < XMP_STATE_LOADED)
		return;

	chn = mod->chn;

	if (p->pos >= 0 && p->pos < mod->len) {
		info->pos = p->pos;
	} else {
		info->pos = 0;
	}

	info->pattern = mod->xxo[info->pos];

	if (info->pattern < mod->pat) {
		info->num_rows = mod->xxp[info->pattern]->rows;
	} else {
		info->num_rows = 0;
	}

	info->row = p->row;
	info->frame = p->frame;
	info->speed = p->speed;
	info->bpm = p->bpm;
	info->total_time = p->scan[p->sequence].time;
	info->frame_time = p->frame_time * 1000;
	info->time = p->current_time;
	info->buffer = s->buffer;

	info->total_size = XMP_MAX_FRAMESIZE;
	info->buffer_size = s->ticksize;
	if (~s->format & XMP_FORMAT_MONO) {
		info->buffer_size *= 2;
	}
	if (~s->format & XMP_FORMAT_8BIT) {
		info->buffer_size *= 2;
	}

	info->volume = p->gvol;
	info->loop_count = p->loop_count;
	info->virt_channels = p->virt.virt_channels;
	info->virt_used = p->virt.virt_used;

	info->sequence = p->sequence;

	if (p->xc_data != NULL) {
		for (i = 0; i < chn; i++) {
			struct channel_data *c = &p->xc_data[i];
			struct xmp_channel_info *ci = &info->channel_info[i];
			struct xmp_track *track;
			struct xmp_event *event;
			int trk;

			ci->note = c->key;
			ci->pitchbend = c->info_pitchbend;
			ci->period = c->info_period;
			ci->position = c->info_position;
			ci->instrument = c->ins;
			ci->sample = c->smp;
			ci->volume = c->info_finalvol >> 4;
			ci->pan = c->info_finalpan;
			ci->reserved = 0;
			memset(&ci->event, 0, sizeof(*event));

			if (info->pattern < mod->pat && info->row < info->num_rows) {
				trk = mod->xxp[info->pattern]->index[i];
				track = mod->xxt[trk];
				if (info->row < track->rows) {
					event = &track->event[info->row];
					memcpy(&ci->event, event, sizeof(*event));
				}
			}
		}
	}
}


code:
#ifndef LIBXMP_PLAYER_H
#define LIBXMP_PLAYER_H

#include "lfo.h"

/* Quirk control */
#define HAS_QUIRK(x)	(m->quirk & (x))

/* Channel flag control */
#define SET(f)		SET_FLAG(xc->flags,(f))
#define RESET(f) 	RESET_FLAG(xc->flags,(f))
#define TEST(f)		TEST_FLAG(xc->flags,(f))

/* Persistent effect flag control */
#define SET_PER(f)	SET_FLAG(xc->per_flags,(f))
#define RESET_PER(f)	RESET_FLAG(xc->per_flags,(f))
#define TEST_PER(f)	TEST_FLAG(xc->per_flags,(f))

/* Note flag control */
#define SET_NOTE(f)	SET_FLAG(xc->note_flags,(f))
#define RESET_NOTE(f)	RESET_FLAG(xc->note_flags,(f))
#define TEST_NOTE(f)	TEST_FLAG(xc->note_flags,(f))

struct retrig_control {
	int s;
	int m;
	int d;
};

/* The following macros are used to set the flags for each channel */
#define VOL_SLIDE	(1 << 0)
#define PAN_SLIDE	(1 << 1)
#define TONEPORTA	(1 << 2)
#define PITCHBEND	(1 << 3)
#define VIBRATO		(1 << 4)
#define TREMOLO		(1 << 5)
#define FINE_VOLS	(1 << 6)
#define FINE_BEND	(1 << 7)
#define OFFSET		(1 << 8)
#define TRK_VSLIDE	(1 << 9)
#define TRK_FVSLIDE	(1 << 10)
#define NEW_INS		(1 << 11)
#define NEW_VOL		(1 << 12)
#define VOL_SLIDE_2	(1 << 13)
#define NOTE_SLIDE	(1 << 14)
#define FINE_NSLIDE	(1 << 15)
#define NEW_NOTE	(1 << 16)
#define FINE_TPORTA	(1 << 17)
#define RETRIG		(1 << 18)
#define PANBRELLO	(1 << 19)
#define GVOL_SLIDE	(1 << 20)
#define TEMPO_SLIDE	(1 << 21)
#define VENV_PAUSE	(1 << 22)
#define PENV_PAUSE	(1 << 23)
#define FENV_PAUSE	(1 << 24)
#define FINE_VOLS_2	(1 << 25)
#define KEY_OFF		(1 << 26)	/* for IT release on envloop end */
#define TREMOR		(1 << 27)	/* for XM tremor */

#define NOTE_FADEOUT	(1 << 0)
#define NOTE_RELEASE	(1 << 1)
#define NOTE_END	(1 << 2)
#define NOTE_CUT	(1 << 3)
#define NOTE_ENV_END	(1 << 4)
#define NOTE_SAMPLE_END	(1 << 5)
#define NOTE_SET	(1 << 6)	/* for IT portamento after keyoff */
#define NOTE_SUSEXIT	(1 << 7)	/* for delayed note release */
#define NOTE_KEY_CUT	(1 << 8)	/* note cut with XMP_KEY_CUT event */
#define NOTE_GLISSANDO	(1 << 9)

#define IS_VALID_INSTRUMENT(x) ((uint32)(x) < mod->ins && mod->xxi[(x)].nsm > 0)
#define IS_VALID_INSTRUMENT_OR_SFX(x) (((uint32)(x) < mod->ins && mod->xxi[(x)].nsm > 0) || (smix->ins > 0 && (uint32)(x) < mod->ins + smix->ins))

struct instrument_vibrato {
	int phase;
	int sweep;
};

struct channel_data {
	int flags;		/* Channel flags */
	int per_flags;		/* Persistent effect channel flags */
	int note_flags;		/* Note release, fadeout or end */
	int note;		/* Note number */
	int key;		/* Key number */
	double period;		/* Amiga or linear period */
	double per_adj;		/* MED period/pitch adjustment factor hack */
	int finetune;		/* Guess what */
	int ins;		/* Instrument number */
	int old_ins;		/* Last instruemnt */
	int smp;		/* Sample number */
	int mastervol;		/* Master vol -- for IT track vol effect */
	int delay;		/* Note delay in frames */
	int keyoff;		/* Key off counter */
	int fadeout;		/* Current fadeout (release) value */
	int ins_fade;		/* Instrument fadeout value */
	int volume;		/* Current volume */
	int gvl;		/* Global volume for instrument for IT */

	int rvv;		/* Random volume variation */
	int rpv;		/* Random pan variation */

	uint8 split;		/* Split channel */
	uint8 pair;		/* Split channel pair */

	int v_idx;		/* Volume envelope index */
	int p_idx;		/* Pan envelope index */
	int f_idx;		/* Freq envelope index */

	int key_porta;		/* Key number for portamento target
				 * -- needed to handle IT portamento xpo */
	struct {
		struct lfo lfo;
		int memory;
	} vibrato;

	struct {
		struct lfo lfo;
		int memory;
	} tremolo;

#ifndef LIBXMP_CORE_DISABLE_IT
	struct {
		struct lfo lfo;
		int memory;
	} panbrello;
#endif

	struct {
        	int8 val[16];	/* 16 for Smaksak MegaArps */
		int size;
		int count;
		int memory;
	} arpeggio;

	struct {
		struct lfo lfo;
		int sweep;
	} insvib;

	struct {
		int val;
		int val2;	/* For fx9 bug emulation */
		int memory;
	} offset;

	struct {
		int val;	/* Retrig value */
		int count;	/* Retrig counter */
		int type;	/* Retrig type */
	} retrig;

	struct {
		uint8 up,down;	/* Tremor value */
		uint8 count;	/* Tremor counter */
		uint8 memory;	/* Tremor memory */
	} tremor;

	struct {
		int slide;	/* Volume slide value */
		int fslide;	/* Fine volume slide value */
		int slide2;	/* Volume slide value */
		int memory;	/* Volume slide effect memory */
#ifndef LIBXMP_CORE_DISABLE_IT
		int fslide2;
		int memory2;	/* Volume slide effect memory */
#endif
	} vol;

	struct {
		int up_memory;	/* Fine volume slide up memory (XM) */
		int down_memory;/* Fine volume slide up memory (XM) */
	} fine_vol;

	struct {
		int slide;	/* Global volume slide value */
		int fslide;	/* Fine global volume slide value */
		int memory;	/* Global volume memory is saved per channel */
	} gvol;

	struct {
		int slide;	/* Track volume slide value */
		int fslide;	/* Track fine volume slide value */
		int memory;	/* Track volume slide effect memory */
	} trackvol;

	struct {
		int slide;	/* Frequency slide value */
		double fslide;	/* Fine frequency slide value */
		int memory;	/* Portamento effect memory */
	} freq;

	struct {
		double target;	/* Target period for tone portamento */
		int dir;	/* Tone portamento up/down directionh */
		int slide;	/* Delta for tone portamento */
		int memory;	/* Tone portamento effect memory */
	} porta;

	struct {
		int up_memory;	/* FT2 has separate memories for these */
		int down_memory;/* cases (see Porta-LinkMem.xm) */
	} fine_porta;

	struct {
		int val;	/* Current pan value */
		int slide;	/* Pan slide value */
		int fslide;	/* Pan fine slide value */
		int memory;	/* Pan slide effect memory */
		int surround;	/* Surround channel flag */
	} pan;

	struct {
		int speed;
		int count;
		int pos;
	} invloop;

#ifndef LIBXMP_CORE_DISABLE_IT
	struct {
		int slide;	/* IT tempo slide */
	} tempo;

	struct {
		int cutoff;	/* IT filter cutoff frequency */
		int resonance;	/* IT filter resonance */
		int envelope;	/* IT filter envelope */
	} filter;

#endif

#ifndef LIBXMP_CORE_PLAYER
	struct {
		int slide;	/* PTM note slide amount */
		int fslide;	/* OKT fine note slide amount */
		int speed;	/* PTM note slide speed */
		int count;	/* PTM note slide counter */
	} noteslide;

	void *extra;
#endif

	struct xmp_event delayed_event;
	int delayed_ins;	/* IT save instrument emulation */

	int info_period;	/* Period */
	int info_pitchbend;	/* Linear pitchbend */
	int info_position;	/* Position before mixing */
	int info_finalvol;	/* Final volume including envelopes */
	int info_finalpan;	/* Final pan including envelopes */
};

void	libxmp_process_fx	(struct context_data *, struct channel_data *,
				 int, struct xmp_event *, int);
void	libxmp_filter_setup	(int, int, int, int*, int*, int *);
int	libxmp_read_event	(struct context_data *, struct xmp_event *, int);

#endif /* LIBXMP_PLAYER_H */


code:
/* Extended Module Player
 * Copyright (C) 1996-2021 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 "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_envelope_volume(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;
}

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

		/* TODO: should probably expand the LFO period size instead
		 * of reducing the vibrato rate precision here.
		 */
		libxmp_lfo_set_depth(&xc->insvib.lfo, sub->vde);
		libxmp_lfo_set_rate(&xc->insvib.lfo, (sub->vra + 2) >> 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 && is_valid_note(e->note - 1)) {
			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) {
			/* Previous instrument */
			sub = get_subinstrument(ctx, xc->ins, xc->key);

			/* No note */
			if (sub != NULL) {
				int pan = mod->xxc[chn].pan - 128;
				xc->volume = sub->vol;

				if (!HAS_QUIRK(QUIRK_FTMOD)) {
					xc->pan.val = pan + ((sub->pan - 128) *
						(128 - abs(pan))) / 128 + 128;
				}

				xc->ins_fade = mod->xxi[xc->ins].rls;
				SET(NEW_VOL);
			}
		}
	}

	/* Do this regardless if the instrument is invalid or not -- unless
	 * XM keyoff is used. Fixes xyce-dans_la_rue.xm chn 0 patterns 0E/0F and
	 * chn 10 patterns 0D/0E, see https://github.com/libxmp/libxmp/issues/152
	 * for details.
         */
	if (ev.ins && key != XMP_KEY_FADE) {
		SET(NEW_INS);
		use_ins_vol = 1;
		xc->per_flags = 0;

		RESET_NOTE(NOTE_RELEASE|NOTE_SUSEXIT);
		if (!k00) {
			RESET_NOTE(NOTE_FADEOUT);
		}

		xc->fadeout = 0x10000;

		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) {
			/* 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 pan = mod->xxc[chn].pan - 128;
				xc->volume = sub->vol;

				if (!HAS_QUIRK(QUIRK_FTMOD)) {
					xc->pan.val = pan + ((sub->pan - 128) *
						(128 - abs(pan))) / 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 if (is_valid_note(e->note - 1)) {
			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);
			/* Use instrument volume if an instrument was explicitly
			 * provided on this row (see OpenMPT NoteOffInstr.it row 4).
			 * However, never reset the envelope (see OpenMPT wnoteoff.it).
			 */
			reset_env = 0;
			if (!ev.ins) {
				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
	 * Also, only reset the volume envelope. (it_fade_env_reset_carry.it)
	 */
	if (ev.ins && TEST_NOTE(NOTE_ENV_END)) {
		if (check_fadeout(ctx, xc, candidate_ins)) {
			reset_envelope_volume(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)) {
		/* Do this even for XMP_KEY_OFF (see OpenMPT NoteOffInstr.it row 4). */
		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) && is_valid_note(e->note - 1)) {
			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 {
		sub = is_valid_note(xc->key) ?
			get_subinstrument(ctx, xc->ins, xc->key) : NULL;
		if (sub == NULL) {
			return 0;
		}
		transp = mod->xxi[xc->ins].map[xc->key].xpo;
		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);
	}
}