summaryrefslogtreecommitdiff
path: root/src/player/effects.c
diff options
context:
space:
mode:
authorVito Caputo <vcaputo@pengaru.com>2017-05-26 21:51:04 -0700
committerVito Caputo <vcaputo@pengaru.com>2017-05-26 22:48:09 -0700
commit78f8fce7f286fd0c71774e2567404ed51f24fef3 (patch)
treef3de4987f7a9fc1bc03331e97b65a851b041051a /src/player/effects.c
*: initial commit of stripped schism stuff
Forking schism tracker's IT playback stuff into a little playback library for embedding in demos.
Diffstat (limited to 'src/player/effects.c')
-rw-r--r--src/player/effects.c2070
1 files changed, 2070 insertions, 0 deletions
diff --git a/src/player/effects.c b/src/player/effects.c
new file mode 100644
index 0000000..93dc299
--- /dev/null
+++ b/src/player/effects.c
@@ -0,0 +1,2070 @@
+/*
+ * Schism Tracker - a cross-platform Impulse Tracker clone
+ * copyright (c) 2003-2005 Storlek <storlek@rigelseven.com>
+ * copyright (c) 2005-2008 Mrs. Brisby <mrs.brisby@nimh.org>
+ * copyright (c) 2009 Storlek & Mrs. Brisby
+ * copyright (c) 2010-2012 Storlek
+ * URL: http://schismtracker.org/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "sndfile.h"
+
+#include "cmixer.h"
+#include "snd_fm.h"
+#include "tables.h"
+
+#include "util.h" /* for clamp/min */
+
+#include <math.h>
+
+
+// see also csf_midi_out_note in sndmix.c
+void (*csf_midi_out_raw)(const unsigned char *,unsigned int, unsigned int) = NULL;
+
+/* --------------------------------------------------------------------------------------------------------- */
+/* note/freq/period conversion functions */
+
+int get_note_from_period(int period)
+{
+ int n;
+ if (!period)
+ return 0;
+ for (n = 0; n <= 120; n++) {
+ /* Essentially, this is just doing a note_to_period(n, 8363), but with less
+ computation since there's no c5speed to deal with. */
+ if (period >= (32 * period_table[n % 12] >> (n / 12)))
+ return n + 1;
+ }
+ return 120;
+}
+
+int get_period_from_note(int note, unsigned int c5speed, int linear)
+{
+ if (!note || note > 0xF0)
+ return 0;
+ note--;
+ if (linear)
+ return _muldiv(c5speed, linear_slide_up_table[(note % 12) * 16] << (note / 12), 65536 << 5);
+ else if (!c5speed)
+ return INT_MAX;
+ else
+ return _muldiv(8363, (period_table[note % 12] << 5), c5speed << (note / 12));
+}
+
+
+unsigned int transpose_to_frequency(int transp, int ftune)
+{
+ return (unsigned int) (8363.0 * pow(2, (transp * 128.0 + ftune) / 1536.0));
+}
+
+int frequency_to_transpose(unsigned int freq)
+{
+ return (int) (1536.0 * (log(freq / 8363.0) / log(2)));
+}
+
+
+unsigned long calc_halftone(unsigned long hz, int rel)
+{
+ return pow(2, rel / 12.0) * hz + 0.5;
+}
+
+/* --------------------------------------------------------------------------------------------------------- */
+/* the full content of snd_fx.cpp follows. */
+
+
+////////////////////////////////////////////////////////////
+// Channels effects
+
+void fx_note_cut(song_t *csf, uint32_t nchan, int clear_note)
+{
+ song_voice_t *chan = &csf->voices[nchan];
+ // stop the current note:
+ chan->flags |= CHN_FASTVOLRAMP;
+ chan->length = 0;
+ if (clear_note) {
+ // keep instrument numbers from picking up old notes
+ // (SCx doesn't do this)
+ chan->period = 0;
+ }
+ if (chan->flags & CHN_ADLIB) {
+ //Do this only if really an adlib chan. Important!
+ OPL_NoteOff(nchan);
+ OPL_Touch(nchan, NULL, 0);
+ }
+}
+
+void fx_key_off(song_t *csf, uint32_t nchan)
+{
+ song_voice_t *chan = &csf->voices[nchan];
+
+ /*fprintf(stderr, "KeyOff[%d] [ch%u]: flags=0x%X\n",
+ tick_count, (unsigned)nchan, chan->flags);*/
+ if (chan->flags & CHN_ADLIB) {
+ //Do this only if really an adlib chan. Important!
+ OPL_NoteOff(nchan);
+ }
+
+ song_instrument_t *penv = (csf->flags & SONG_INSTRUMENTMODE) ? chan->ptr_instrument : NULL;
+
+ /*if ((chan->flags & CHN_ADLIB)
+ || (penv && penv->midi_channel_mask))
+ {
+ // When in AdLib / MIDI mode, end the sample
+ chan->flags |= CHN_FASTVOLRAMP;
+ chan->length = 0;
+ chan->position = 0;
+ return;
+ }*/
+
+ chan->flags |= CHN_KEYOFF;
+ //if ((!chan->ptr_instrument) || (!(chan->flags & CHN_VOLENV)))
+ if ((csf->flags & SONG_INSTRUMENTMODE) && chan->ptr_instrument && !(chan->flags & CHN_VOLENV)) {
+ chan->flags |= CHN_NOTEFADE;
+ }
+ if (!chan->length)
+ return;
+ if ((chan->flags & CHN_SUSTAINLOOP) && chan->ptr_sample) {
+ song_sample_t *psmp = chan->ptr_sample;
+ if (psmp->flags & CHN_LOOP) {
+ if (psmp->flags & CHN_PINGPONGLOOP)
+ chan->flags |= CHN_PINGPONGLOOP;
+ else
+ chan->flags &= ~(CHN_PINGPONGLOOP|CHN_PINGPONGFLAG);
+ chan->flags |= CHN_LOOP;
+ chan->length = psmp->length;
+ chan->loop_start = psmp->loop_start;
+ chan->loop_end = psmp->loop_end;
+ if (chan->length > chan->loop_end) chan->length = chan->loop_end;
+ if (chan->position >= chan->length)
+ chan->position = chan->position - chan->length + chan->loop_start;
+ } else {
+ chan->flags &= ~(CHN_LOOP|CHN_PINGPONGLOOP|CHN_PINGPONGFLAG);
+ chan->length = psmp->length;
+ }
+ }
+ if (penv && penv->fadeout && (penv->flags & ENV_VOLLOOP))
+ chan->flags |= CHN_NOTEFADE;
+}
+
+
+// negative value for slide = up, positive = down
+static void fx_do_freq_slide(uint32_t flags, song_voice_t *chan, int32_t slide)
+{
+ // IT Linear slides
+ if (!chan->period) return;
+ if (flags & SONG_LINEARSLIDES) {
+ int32_t old_period = chan->period;
+ if (slide < 0) {
+ uint32_t n = (-slide) >> 2;
+ if (n > 255)
+ n = 255;
+ chan->period = _muldivr(chan->period, linear_slide_up_table[n], 65536);
+ if (old_period == chan->period)
+ chan->period++;
+ } else {
+ uint32_t n = (slide) >> 2;
+ if (n > 255)
+ n = 255;
+ chan->period = _muldivr(chan->period, linear_slide_down_table[n], 65536);
+ if (old_period == chan->period)
+ chan->period--;
+ }
+ } else {
+ chan->period += slide;
+ }
+}
+
+static void fx_fine_portamento_up(uint32_t flags, song_voice_t *chan, uint32_t param)
+{
+ if ((flags & SONG_FIRSTTICK) && chan->period && param) {
+ if (flags & SONG_LINEARSLIDES) {
+ chan->period = _muldivr(chan->period, linear_slide_up_table[param & 0x0F], 65536);
+ } else {
+ chan->period -= (int)(param * 4);
+ }
+ }
+}
+
+static void fx_fine_portamento_down(uint32_t flags, song_voice_t *chan, uint32_t param)
+{
+ if ((flags & SONG_FIRSTTICK) && chan->period && param) {
+ if (flags & SONG_LINEARSLIDES) {
+ chan->period = _muldivr(chan->period, linear_slide_down_table[param & 0x0F], 65536);
+ } else {
+ chan->period += (int)(param * 4);
+ }
+ }
+}
+
+static void fx_extra_fine_portamento_up(uint32_t flags, song_voice_t *chan, uint32_t param)
+{
+ if ((flags & SONG_FIRSTTICK) && chan->period && param) {
+ if (flags & SONG_LINEARSLIDES) {
+ chan->period = _muldivr(chan->period, fine_linear_slide_up_table[param & 0x0F], 65536);
+ } else {
+ chan->period -= (int)(param);
+ }
+ }
+}
+
+static void fx_extra_fine_portamento_down(uint32_t flags, song_voice_t *chan, uint32_t param)
+{
+ if ((flags & SONG_FIRSTTICK) && chan->period && param) {
+ if (flags & SONG_LINEARSLIDES) {
+ chan->period = _muldivr(chan->period, fine_linear_slide_down_table[param & 0x0F], 65536);
+ } else {
+ chan->period += (int)(param);
+ }
+ }
+}
+
+static void fx_reg_portamento_up(uint32_t flags, song_voice_t *chan, uint32_t param)
+{
+ if (!(flags & SONG_FIRSTTICK))
+ fx_do_freq_slide(flags, chan, -(int)(param * 4));
+}
+
+static void fx_reg_portamento_down(uint32_t flags, song_voice_t *chan, uint32_t param)
+{
+ if (!(flags & SONG_FIRSTTICK))
+ fx_do_freq_slide(flags, chan, (int)(param * 4));
+}
+
+
+static void fx_portamento_up(uint32_t flags, song_voice_t *chan, uint32_t param)
+{
+ if (!param)
+ param = chan->mem_pitchslide;
+
+ switch (param & 0xf0) {
+ case 0xe0:
+ fx_extra_fine_portamento_up(flags, chan, param & 0x0F);
+ break;
+ case 0xf0:
+ fx_fine_portamento_up(flags, chan, param & 0x0F);
+ break;
+ default:
+ fx_reg_portamento_up(flags, chan, param);
+ break;
+ }
+}
+
+static void fx_portamento_down(uint32_t flags, song_voice_t *chan, uint32_t param)
+{
+ if (!param)
+ param = chan->mem_pitchslide;
+
+ switch (param & 0xf0) {
+ case 0xe0:
+ fx_extra_fine_portamento_down(flags, chan, param & 0x0F);
+ break;
+ case 0xf0:
+ fx_fine_portamento_down(flags, chan, param & 0x0F);
+ break;
+ default:
+ fx_reg_portamento_down(flags, chan, param);
+ break;
+ }
+}
+
+static void fx_tone_portamento(uint32_t flags, song_voice_t *chan, uint32_t param)
+{
+ int delta;
+
+ if (!param)
+ param = chan->mem_portanote;
+
+ chan->flags |= CHN_PORTAMENTO;
+ if (chan->period && chan->portamento_target && !(flags & SONG_FIRSTTICK)) {
+ if (chan->period < chan->portamento_target) {
+ if (flags & SONG_LINEARSLIDES) {
+ uint32_t n = MIN(255, param);
+ delta = _muldivr(chan->period, linear_slide_up_table[n], 65536) - chan->period;
+ if (delta < 1) delta = 1;
+ } else {
+ delta = param * 4;
+ }
+ chan->period += delta;
+ if (chan->period > chan->portamento_target) {
+ chan->period = chan->portamento_target;
+ chan->portamento_target = 0;
+ }
+ } else if (chan->period > chan->portamento_target) {
+ if (flags & SONG_LINEARSLIDES) {
+ uint32_t n = MIN(255, param);
+ delta = _muldivr(chan->period, linear_slide_down_table[n], 65536) - chan->period;
+ if (delta > -1) delta = -1;
+ } else {
+ delta = -param * 4;
+ }
+ chan->period += delta;
+ if (chan->period < chan->portamento_target) {
+ chan->period = chan->portamento_target;
+ chan->portamento_target = 0;
+ }
+ }
+ }
+}
+
+// Implemented for IMF compatibility, can't actually save this in any formats
+// sign should be 1 (up) or -1 (down)
+static void fx_note_slide(uint32_t flags, song_voice_t *chan, uint32_t param, int sign)
+{
+ uint8_t x, y;
+ if (flags & SONG_FIRSTTICK) {
+ x = param & 0xf0;
+ if (x)
+ chan->note_slide_speed = (x >> 4);
+ y = param & 0xf;
+ if (y)
+ chan->note_slide_step = y;
+ chan->note_slide_counter = chan->note_slide_speed;
+ } else {
+ if (--chan->note_slide_counter == 0) {
+ chan->note_slide_counter = chan->note_slide_speed;
+ // update it
+ chan->period = get_period_from_note
+ (sign * chan->note_slide_step + get_note_from_period(chan->period),
+ 8363, 0);
+ }
+ }
+}
+
+
+
+static void fx_vibrato(song_voice_t *p, uint32_t param)
+{
+ if (param & 0x0F)
+ p->vibrato_depth = (param & 0x0F) * 4;
+ if (param & 0xF0)
+ p->vibrato_speed = (param >> 4) & 0x0F;
+ p->flags |= CHN_VIBRATO;
+}
+
+static void fx_fine_vibrato(song_voice_t *p, uint32_t param)
+{
+ if (param & 0x0F)
+ p->vibrato_depth = param & 0x0F;
+ if (param & 0xF0)
+ p->vibrato_speed = (param >> 4) & 0x0F;
+ p->flags |= CHN_VIBRATO;
+}
+
+
+static void fx_panbrello(song_voice_t *chan, uint32_t param)
+{
+ unsigned int panpos = chan->panbrello_position & 0xFF;
+ int pdelta;
+
+ if (param & 0x0F)
+ chan->panbrello_depth = param & 0x0F;
+ if (param & 0xF0)
+ chan->panbrello_speed = (param >> 4) & 0x0F;
+
+ switch (chan->panbrello_type) {
+ case VIB_SINE:
+ default:
+ pdelta = sine_table[panpos];
+ break;
+ case VIB_RAMP_DOWN:
+ pdelta = ramp_down_table[panpos];
+ break;
+ case VIB_SQUARE:
+ pdelta = square_table[panpos];
+ break;
+ case VIB_RANDOM:
+ pdelta = 128 * ((double) rand() / RAND_MAX) - 64;
+ break;
+ }
+
+ chan->panbrello_position += chan->panbrello_speed;
+ pdelta = ((pdelta * (int)chan->panbrello_depth) + 2) >> 3;
+ chan->panbrello_delta = pdelta;
+}
+
+
+static void fx_volume_up(song_voice_t *chan, uint32_t param)
+{
+ chan->volume += param * 4;
+ if (chan->volume > 256)
+ chan->volume = 256;
+}
+
+static void fx_volume_down(song_voice_t *chan, uint32_t param)
+{
+ chan->volume -= param * 4;
+ if (chan->volume < 0)
+ chan->volume = 0;
+}
+
+static void fx_volume_slide(uint32_t flags, song_voice_t *chan, uint32_t param)
+{
+ // Dxx Volume slide down
+ //
+ // if (xx == 0) then xx = last xx for (Dxx/Kxx/Lxx) for this channel.
+ if (param)
+ chan->mem_volslide = param;
+ else
+ param = chan->mem_volslide;
+
+ // Order of testing: Dx0, D0x, DxF, DFx
+ if (param == (param & 0xf0)) {
+ // Dx0 Set effect update for channel enabled if channel is ON.
+ // If x = F, then slide up volume by 15 straight away also (for S3M compat)
+ // Every update, add x to the volume, check and clip values > 64 to 64
+ param >>= 4;
+ if (param == 0xf || !(flags & SONG_FIRSTTICK))
+ fx_volume_up(chan, param);
+ } else if (param == (param & 0xf)) {
+ // D0x Set effect update for channel enabled if channel is ON.
+ // If x = F, then slide down volume by 15 straight away also (for S3M)
+ // Every update, subtract x from the volume, check and clip values < 0 to 0
+ if (param == 0xf || !(flags & SONG_FIRSTTICK))
+ fx_volume_down(chan, param);
+ } else if ((param & 0xf) == 0xf) {
+ // DxF Add x to volume straight away. Check and clip values > 64 to 64
+ param >>= 4;
+ if (flags & SONG_FIRSTTICK)
+ fx_volume_up(chan, param);
+ } else if ((param & 0xf0) == 0xf0) {
+ // DFx Subtract x from volume straight away. Check and clip values < 0 to 0
+ param &= 0xf;
+ if (flags & SONG_FIRSTTICK)
+ fx_volume_down(chan, param);
+ }
+}
+
+
+static void fx_panning_slide(uint32_t flags, song_voice_t *chan, uint32_t param)
+{
+ int32_t slide = 0;
+ if (param)
+ chan->mem_panslide = param;
+ else
+ param = chan->mem_panslide;
+ if ((param & 0x0F) == 0x0F && (param & 0xF0)) {
+ if (flags & SONG_FIRSTTICK) {
+ param = (param & 0xF0) >> 2;
+ slide = - (int)param;
+ }
+ } else if ((param & 0xF0) == 0xF0 && (param & 0x0F)) {
+ if (flags & SONG_FIRSTTICK) {
+ slide = (param & 0x0F) << 2;
+ }
+ } else {
+ if (!(flags & SONG_FIRSTTICK)) {
+ if (param & 0x0F)
+ slide = (int)((param & 0x0F) << 2);
+ else
+ slide = -(int)((param & 0xF0) >> 2);
+ }
+ }
+ if (slide) {
+ slide += chan->panning;
+ chan->panning = CLAMP(slide, 0, 256);
+ }
+ chan->flags &= ~CHN_SURROUND;
+ chan->panbrello_delta = 0;
+}
+
+
+static void fx_tremolo(uint32_t flags, song_voice_t *chan, uint32_t param)
+{
+ unsigned int trempos = chan->tremolo_position & 0xFF;
+ int tdelta;
+
+ if (param & 0x0F)
+ chan->tremolo_depth = (param & 0x0F) << 2;
+ if (param & 0xF0)
+ chan->tremolo_speed = (param >> 4) & 0x0F;
+
+ chan->flags |= CHN_TREMOLO;
+
+ // don't handle on first tick if old-effects mode
+ if ((flags & SONG_FIRSTTICK) && (flags & SONG_ITOLDEFFECTS))
+ return;
+
+ switch (chan->tremolo_type) {
+ case VIB_SINE:
+ default:
+ tdelta = sine_table[trempos];
+ break;
+ case VIB_RAMP_DOWN:
+ tdelta = ramp_down_table[trempos];
+ break;
+ case VIB_SQUARE:
+ tdelta = square_table[trempos];
+ break;
+ case VIB_RANDOM:
+ tdelta = 128 * ((double) rand() / RAND_MAX) - 64;
+ break;
+ }
+
+ chan->tremolo_position = (trempos + 4 * chan->tremolo_speed) & 0xFF;
+ tdelta = (tdelta * (int)chan->tremolo_depth) >> 5;
+ chan->tremolo_delta = tdelta;
+}
+
+
+static void fx_retrig_note(song_t *csf, uint32_t nchan, uint32_t param)
+{
+ song_voice_t *chan = &csf->voices[nchan];
+
+ //printf("Q%02X note=%02X tick%d %d\n", param, chan->row_note, tick_count, chan->cd_retrig);
+ if ((csf->flags & SONG_FIRSTTICK) && chan->row_note != NOTE_NONE) {
+ chan->cd_retrig = param & 0xf;
+ } else if (--chan->cd_retrig <= 0) {
+
+ // in Impulse Tracker, retrig only works if a sample is currently playing in the channel
+ if (chan->position == 0)
+ return;
+
+ chan->cd_retrig = param & 0xf;
+ param >>= 4;
+ if (param) {
+ int vol = chan->volume;
+ if (retrig_table_1[param])
+ vol = (vol * retrig_table_1[param]) >> 4;
+ else
+ vol += (retrig_table_2[param]) << 2;
+ chan->volume = CLAMP(vol, 0, 256);
+ chan->flags |= CHN_FASTVOLRAMP;
+ }
+
+ uint32_t note = chan->new_note;
+ int32_t period = chan->period;
+ if (NOTE_IS_NOTE(note) && chan->length)
+ csf_check_nna(csf, nchan, 0, note, 1);
+ csf_note_change(csf, nchan, note, 1, 1, 0);
+ if (period && chan->row_note == NOTE_NONE)
+ chan->period = period;
+ chan->position = chan->position_frac = 0;
+ }
+}
+
+
+static void fx_channel_vol_slide(uint32_t flags, song_voice_t *chan, uint32_t param)
+{
+ int32_t slide = 0;
+ if (param)
+ chan->mem_channel_volslide = param;
+ else
+ param = chan->mem_channel_volslide;
+ if ((param & 0x0F) == 0x0F && (param & 0xF0)) {
+ if (flags & SONG_FIRSTTICK)
+ slide = param >> 4;
+ } else if ((param & 0xF0) == 0xF0 && (param & 0x0F)) {
+ if (flags & SONG_FIRSTTICK)
+ slide = - (int)(param & 0x0F);
+ } else {
+ if (!(flags & SONG_FIRSTTICK)) {
+ if (param & 0x0F)
+ slide = -(int)(param & 0x0F);
+ else
+ slide = (int)((param & 0xF0) >> 4);
+ }
+ }
+ if (slide) {
+ slide += chan->global_volume;
+ chan->global_volume = CLAMP(slide, 0, 64);
+ }
+}
+
+
+static void fx_global_vol_slide(song_t *csf, song_voice_t *chan, uint32_t param)
+{
+ int32_t slide = 0;
+ if (param)
+ chan->mem_global_volslide = param;
+ else
+ param = chan->mem_global_volslide;
+ if ((param & 0x0F) == 0x0F && (param & 0xF0)) {
+ if (csf->flags & SONG_FIRSTTICK)
+ slide = param >> 4;
+ } else if ((param & 0xF0) == 0xF0 && (param & 0x0F)) {
+ if (csf->flags & SONG_FIRSTTICK)
+ slide = -(int)(param & 0x0F);
+ } else {
+ if (!(csf->flags & SONG_FIRSTTICK)) {
+ if (param & 0xF0)
+ slide = (int)((param & 0xF0) >> 4);
+ else
+ slide = -(int)(param & 0x0F);
+ }
+ }
+ if (slide) {
+ slide += csf->current_global_volume;
+ csf->current_global_volume = CLAMP(slide, 0, 128);
+ }
+}
+
+
+static void fx_pattern_loop(song_t *csf, song_voice_t *chan, uint32_t param)
+{
+ if (param) {
+ if (chan->cd_patloop) {
+ if (!--chan->cd_patloop) {
+ // this should get rid of that nasty infinite loop for cases like
+ // ... .. .. SB0
+ // ... .. .. SB1
+ // ... .. .. SB1
+ // it still doesn't work right in a few strange cases, but oh well :P
+ chan->patloop_row = csf->row + 1;
+ return; // don't loop!
+ }
+ } else {
+ chan->cd_patloop = param;
+ }
+ csf->process_row = chan->patloop_row - 1;
+ } else {
+ chan->patloop_row = csf->row;
+ }
+}
+
+
+static void fx_special(song_t *csf, uint32_t nchan, uint32_t param)
+{
+ song_voice_t *chan = &csf->voices[nchan];
+ uint32_t command = param & 0xF0;
+ param &= 0x0F;
+ switch(command) {
+ // S0x: Set Filter
+ // S1x: Set Glissando Control
+ case 0x10:
+ chan->flags &= ~CHN_GLISSANDO;
+ if (param) chan->flags |= CHN_GLISSANDO;
+ break;
+ // S2x: Set FineTune (no longer implemented)
+ // S3x: Set Vibrato WaveForm
+ case 0x30:
+ chan->vib_type = param;
+ break;
+ // S4x: Set Tremolo WaveForm
+ case 0x40:
+ chan->tremolo_type = param;
+ break;
+ // S5x: Set Panbrello WaveForm
+ case 0x50:
+ chan->panbrello_type = param;
+ break;
+ // S6x: Pattern Delay for x ticks
+ case 0x60:
+ if (csf->flags & SONG_FIRSTTICK)
+ csf->tick_count += param;
+ break;
+ // S7x: Envelope Control
+ case 0x70:
+ if (!(csf->flags & SONG_FIRSTTICK))
+ break;
+ switch(param) {
+ case 0:
+ case 1:
+ case 2:
+ {
+ song_voice_t *bkp = &csf->voices[MAX_CHANNELS];
+ for (uint32_t i=MAX_CHANNELS; i<MAX_VOICES; i++, bkp++) {
+ if (bkp->master_channel == nchan+1) {
+ if (param == 1) {
+ fx_key_off(csf, i);
+ } else if (param == 2) {
+ bkp->flags |= CHN_NOTEFADE;
+ } else {
+ bkp->flags |= CHN_NOTEFADE;
+ bkp->fadeout_volume = 0;
+ }
+ }
+ }
+ }
+ break;
+ case 3: chan->nna = NNA_NOTECUT; break;
+ case 4: chan->nna = NNA_CONTINUE; break;
+ case 5: chan->nna = NNA_NOTEOFF; break;
+ case 6: chan->nna = NNA_NOTEFADE; break;
+ case 7: chan->flags &= ~CHN_VOLENV; break;
+ case 8: chan->flags |= CHN_VOLENV; break;
+ case 9: chan->flags &= ~CHN_PANENV; break;
+ case 10: chan->flags |= CHN_PANENV; break;
+ case 11: chan->flags &= ~CHN_PITCHENV; break;
+ case 12: chan->flags |= CHN_PITCHENV; break;
+ }
+ break;
+ // S8x: Set 4-bit Panning
+ case 0x80:
+ if (csf->flags & SONG_FIRSTTICK) {
+ chan->flags &= ~CHN_SURROUND;
+ chan->panbrello_delta = 0;
+ chan->panning = (param << 4) + 8;
+ chan->flags |= CHN_FASTVOLRAMP;
+ chan->pan_swing = 0;
+ }
+ break;
+ // S9x: Set Surround
+ case 0x90:
+ if (param == 1 && (csf->flags & SONG_FIRSTTICK)) {
+ chan->flags |= CHN_SURROUND;
+ chan->panbrello_delta = 0;
+ chan->panning = 128;
+ }
+ break;
+ // SAx: Set 64k Offset
+ // Note: don't actually APPLY the offset, and don't clear the regular offset value, either.
+ case 0xA0:
+ if (csf->flags & SONG_FIRSTTICK) {
+ chan->mem_offset = (param << 16) | (chan->mem_offset & ~0xf0000);
+ }
+ break;
+ // SBx: Pattern Loop
+ case 0xB0:
+ if (csf->flags & SONG_FIRSTTICK)
+ fx_pattern_loop(csf, chan, param & 0x0F);
+ break;
+ // SCx: Note Cut
+ case 0xC0:
+ if (csf->flags & SONG_FIRSTTICK)
+ chan->cd_note_cut = param ?: 1;
+ else if (--chan->cd_note_cut == 0)
+ fx_note_cut(csf, nchan, 0);
+ break;
+ // SDx: Note Delay
+ // SEx: Pattern Delay for x rows
+ case 0xE0:
+ if (csf->flags & SONG_FIRSTTICK) {
+ if (!csf->row_count) // ugh!
+ csf->row_count = param + 1;
+ }
+ break;
+ // SFx: Set Active Midi Macro
+ case 0xF0:
+ chan->active_macro = param;
+ break;
+ }
+}
+
+
+// this is all brisby
+void csf_midi_send(song_t *csf, const unsigned char *data, unsigned int len, uint32_t nchan, int fake)
+{
+ song_voice_t *chan = &csf->voices[nchan];
+ int oldcutoff;
+ const unsigned char *idata = data;
+ unsigned int ilen = len;
+
+ while (ilen > 4 && idata[0] == 0xF0 && idata[1] == 0xF0) {
+ // impulse tracker filter control (mfg. 0xF0)
+ switch (idata[2]) {
+ case 0x00: // set cutoff
+ oldcutoff = chan->cutoff;
+ if (idata[3] < 0x80)
+ chan->cutoff = idata[3];
+ oldcutoff -= chan->cutoff;
+ if (oldcutoff < 0)
+ oldcutoff = -oldcutoff;
+ if (chan->volume > 0 || oldcutoff < 0x10
+ || !(chan->flags & CHN_FILTER)
+ || !(chan->left_volume|chan->right_volume)) {
+ setup_channel_filter(chan, !(chan->flags & CHN_FILTER), 256, csf->mix_frequency);
+ }
+ break;
+ case 0x01: // set resonance
+ if (idata[3] < 0x80)
+ chan->resonance = idata[3];
+ setup_channel_filter(chan, !(chan->flags & CHN_FILTER), 256, csf->mix_frequency);
+ break;
+ }
+ idata += 4;
+ ilen -= 4;
+ }
+
+ if (!fake && csf_midi_out_raw) {
+ /* okay, this is kind of how it works.
+ we pass buffer_count as here because while
+ 1000 * ((8((buffer_size/2) - buffer_count)) / sample_rate)
+ is the number of msec we need to delay by, libmodplug simply doesn't know
+ what the buffer size is at this point so buffer_count simply has no
+ frame of reference.
+
+ fortunately, schism does and can complete this (tags: _schism_midi_out_raw )
+
+ */
+ csf_midi_out_raw(data, len, csf->buffer_count);
+ }
+}
+
+
+static int _was_complete_midi(unsigned char *q, unsigned int len, int nextc)
+{
+ if (len == 0) return 0;
+ if (*q == 0xF0) return (q[len-1] == 0xF7 ? 1 : 0);
+ return ((nextc & 0x80) ? 1 : 0);
+}
+
+void csf_process_midi_macro(song_t *csf, uint32_t nchan, const char * macro, uint32_t param,
+ uint32_t note, uint32_t velocity, uint32_t use_instr)
+{
+/* this was all wrong. -mrsb */
+ song_voice_t *chan = &csf->voices[nchan];
+ song_instrument_t *penv = ((csf->flags & SONG_INSTRUMENTMODE)
+ && chan->last_instrument < MAX_INSTRUMENTS)
+ ? csf->instruments[use_instr ?: chan->last_instrument]
+ : NULL;
+ unsigned char outbuffer[64];
+ unsigned char cx;
+ int mc, fake = 0;
+ int saw_c;
+ int i, j, x;
+
+ saw_c = 0;
+ if (!penv || penv->midi_channel_mask == 0) {
+ /* okay, there _IS_ no real midi channel. forget this for now... */
+ mc = 15;
+ fake = 1;
+
+ } else if (penv->midi_channel_mask >= 0x10000) {
+ mc = (nchan-1) % 16;
+ } else {
+ mc = 0;
+ while(!(penv->midi_channel_mask & (1 << mc))) ++mc;
+ }
+
+ for (i = j = x = 0, cx =0; i <= 32 && macro[i]; i++) {
+ int c, cw;
+ if (macro[i] >= '0' && macro[i] <= '9') {
+ c = macro[i] - '0';
+ cw = 1;
+ } else if (macro[i] >= 'A' && macro[i] <= 'F') {
+ c = (macro[i] - 'A') + 10;
+ cw = 1;
+ } else if (macro[i] == 'c') {
+ c = mc;
+ cw = 1;
+ saw_c = 1;
+ } else if (macro[i] == 'n') {
+ c = (note-1);
+ cw = 2;
+ } else if (macro[i] == 'v') {
+ c = velocity;
+ cw = 2;
+ } else if (macro[i] == 'u') {
+ c = (chan->volume >> 1);
+ if (c > 127) c = 127;
+ cw = 2;
+ } else if (macro[i] == 'x') {
+ c = chan->panning;
+ if (c > 127) c = 127;
+ cw = 2;
+ } else if (macro[i] == 'y') {
+ c = chan->final_panning;
+ if (c > 127) c = 127;
+ cw = 2;
+ } else if (macro[i] == 'a') {
+ if (!penv)
+ c = 0;
+ else
+ c = (penv->midi_bank >> 7) & 127;
+ cw = 2;
+ } else if (macro[i] == 'b') {
+ if (!penv)
+ c = 0;
+ else
+ c = penv->midi_bank & 127;
+ cw = 2;
+ } else if (macro[i] == 'z' || macro[i] == 'p') {
+ c = param & 0x7F;
+ cw = 2;
+ } else {
+ continue;
+ }
+ if (j == 0 && cw == 1) {
+ cx = c;
+ j = 1;
+ continue;
+ } else if (j == 1 && cw == 1) {
+ cx = (cx << 4) | c;
+ j = 0;
+ } else if (j == 0) {
+ cx = c;
+ } else if (j == 1) {
+ outbuffer[x] = cx;
+ x++;
+
+ cx = c;
+ j = 0;
+ }
+ // start of midi message
+ if (_was_complete_midi(outbuffer, x, cx)) {
+ csf_midi_send(csf, outbuffer, x, nchan, saw_c && fake);
+ x = 0;
+ }
+ outbuffer[x] = cx;
+ x++;
+ }
+ if (j == 1) {
+ outbuffer[x] = cx;
+ x++;
+ }
+ if (x) {
+ // terminate sysex
+ if (!_was_complete_midi(outbuffer, x, 0xFF)) {
+ if (*outbuffer == 0xF0) {
+ outbuffer[x] = 0xF7;
+ x++;
+ }
+ }
+ csf_midi_send(csf, outbuffer, x, nchan, saw_c && fake);
+ }
+}
+
+
+////////////////////////////////////////////////////////////
+// Length
+
+#if MAX_CHANNELS != 64
+# error csf_get_length assumes 64 channels
+#endif
+
+unsigned int csf_get_length(song_t *csf)
+{
+ uint32_t elapsed = 0, row = 0, next_row = 0, cur_order = 0, next_order = 0, pat = csf->orderlist[0],
+ speed = csf->initial_speed, tempo = csf->initial_tempo, psize, n;
+ uint32_t patloop[MAX_CHANNELS] = {0};
+ uint8_t mem_tempo[MAX_CHANNELS] = {0};
+ uint64_t setloop = 0; // bitmask
+ const song_note_t *pdata;
+
+ for (;;) {
+ uint32_t speed_count = 0;
+ row = next_row;
+ cur_order = next_order;
+
+ // Check if pattern is valid
+ pat = csf->orderlist[cur_order];
+ while (pat >= MAX_PATTERNS) {
+ // End of song ?
+ if (pat == ORDER_LAST || cur_order >= MAX_ORDERS) {
+ pat = ORDER_LAST; // cause break from outer loop too
+ break;
+ } else {
+ cur_order++;
+ pat = (cur_order < MAX_ORDERS) ? csf->orderlist[cur_order] : ORDER_LAST;
+ }
+ next_order = cur_order;
+ }
+ // Weird stuff?
+ if (pat >= MAX_PATTERNS)
+ break;
+ pdata = csf->patterns[pat];
+ if (pdata) {
+ psize = csf->pattern_size[pat];
+ } else {
+ pdata = blank_pattern;
+ psize = 64;
+ }
+ // guard against Cxx to invalid row, etc.
+ if (row >= psize)
+ row = 0;
+ // Update next position
+ next_row = row + 1;
+ if (next_row >= psize) {
+ next_order = cur_order + 1;
+ next_row = 0;
+ }
+
+ /* muahahaha */
+ if (csf->stop_at_order > -1 && csf->stop_at_row > -1) {
+ if (csf->stop_at_order <= (signed) cur_order && csf->stop_at_row <= (signed) row)
+ break;
+ if (csf->stop_at_time > 0) {
+ /* stupid api decision */
+ if (((elapsed + 500) / 1000) >= csf->stop_at_time) {
+ csf->stop_at_order = cur_order;
+ csf->stop_at_row = row;
+ break;
+ }
+ }
+ }
+
+ /* This is nasty, but it fixes inaccuracies with SB0 SB1 SB1. (Simultaneous
+ loops in multiple channels are still wildly incorrect, though.) */
+ if (!row)
+ setloop = ~0;
+ if (setloop) {
+ for (n = 0; n < MAX_CHANNELS; n++)
+ if (setloop & (1 << n))
+ patloop[n] = elapsed;
+ setloop = 0;
+ }
+ const song_note_t *note = pdata + row * MAX_CHANNELS;
+ for (n = 0; n < MAX_CHANNELS; note++, n++) {
+ uint32_t param = note->param;
+ switch (note->effect) {
+ case FX_NONE:
+ break;
+ case FX_POSITIONJUMP:
+ next_order = param > cur_order ? param : cur_order + 1;
+ next_row = 0;
+ break;
+ case FX_PATTERNBREAK:
+ next_order = cur_order + 1;
+ next_row = param;
+ break;
+ case FX_SPEED:
+ if (param)
+ speed = param;
+ break;
+ case FX_TEMPO:
+ if (param)
+ mem_tempo[n] = param;
+ else
+ param = mem_tempo[n];
+ int d = (param & 0xf);
+ switch (param >> 4) {
+ default:
+ tempo = param;
+ break;
+ case 0:
+ d = -d;
+ case 1:
+ d = d * (speed - 1) + tempo;
+ tempo = CLAMP(d, 32, 255);
+ break;
+ }
+ break;
+ case FX_SPECIAL:
+ switch (param >> 4) {
+ case 0x6:
+ speed_count = param & 0x0F;
+ break;
+ case 0xb:
+ if (param & 0x0F) {
+ elapsed += (elapsed - patloop[n]) * (param & 0x0F);
+ patloop[n] = 0xffffffff;
+ setloop = 1;
+ } else {
+ patloop[n] = elapsed;
+ }
+ break;
+ case 0xe:
+ speed_count = (param & 0x0F) * speed;
+ break;
+ }
+ break;
+ }
+ }
+ // sec/tick = 5 / (2 * tempo)
+ // msec/tick = 5000 / (2 * tempo)
+ // = 2500 / tempo
+ elapsed += (speed + speed_count) * 2500 / tempo;
+ }
+
+ return (elapsed + 500) / 1000;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+// Effects
+
+song_sample_t *csf_translate_keyboard(song_t *csf, song_instrument_t *penv, uint32_t note, song_sample_t *def)
+{
+ uint32_t n = penv->sample_map[note - 1];
+ return (n && n < MAX_SAMPLES) ? &csf->samples[n] : def;
+}
+
+static void env_reset(song_voice_t *chan, int always)
+{
+ if (chan->ptr_instrument) {
+ chan->flags |= CHN_FASTVOLRAMP;
+ if (always) {
+ chan->vol_env_position = 0;
+ chan->pan_env_position = 0;
+ chan->pitch_env_position = 0;
+ } else {
+ /* only reset envelopes with carry off */
+ if (!(chan->ptr_instrument->flags & ENV_VOLCARRY))
+ chan->vol_env_position = 0;
+ if (!(chan->ptr_instrument->flags & ENV_PANCARRY))
+ chan->pan_env_position = 0;
+ if (!(chan->ptr_instrument->flags & ENV_PITCHCARRY))
+ chan->pitch_env_position = 0;
+ }
+ }
+
+ // this was migrated from csf_note_change, should it be here?
+ chan->flags &= ~CHN_NOTEFADE;
+ chan->fadeout_volume = 65536;
+}
+
+void csf_instrument_change(song_t *csf, song_voice_t *chan, uint32_t instr, int porta, int inst_column)
+{
+ int inst_changed = 0;
+
+ if (instr >= MAX_INSTRUMENTS) return;
+ song_instrument_t *penv = (csf->flags & SONG_INSTRUMENTMODE) ? csf->instruments[instr] : NULL;
+ song_sample_t *psmp = chan->ptr_sample;
+ uint32_t note = chan->new_note;
+
+ if (note == NOTE_NONE) {
+ /* nothing to see here */
+ } else if (NOTE_IS_CONTROL(note)) {
+ /* nothing here either */
+ } else if (penv) {
+ if (NOTE_IS_CONTROL(penv->note_map[note-1]))
+ return;
+ if (!(porta && penv == chan->ptr_instrument && chan->ptr_sample && chan->current_sample_data))
+ psmp = csf_translate_keyboard(csf, penv, note, NULL);
+ chan->flags &= ~CHN_SUSTAINLOOP; // turn off sustain
+ } else {
+ psmp = csf->samples + instr;
+ }
+
+ // Update Volume
+ if (inst_column && psmp) chan->volume = psmp->volume;
+ // inst_changed is used for IT carry-on env option
+ if (penv != chan->ptr_instrument || !chan->current_sample_data) {
+ inst_changed = 1;
+ chan->ptr_instrument = penv;
+ }
+
+ // Instrument adjust
+ chan->new_instrument = 0;
+ if (psmp) {
+ psmp->played = 1;
+ if (penv) {
+ penv->played = 1;
+ chan->instrument_volume = (psmp->global_volume * penv->global_volume) >> 7;
+ if (penv->flags & ENV_SETPANNING) {
+ chan->panning = penv->panning;
+ chan->flags &= ~CHN_SURROUND;
+ }
+ chan->nna = penv->nna;
+ } else {
+ chan->instrument_volume = psmp->global_volume;
+ }
+ if (psmp->flags & CHN_PANNING) {
+ chan->panning = psmp->panning;
+ chan->flags &= ~CHN_SURROUND;
+ }
+ }
+
+ // Reset envelopes
+
+ // Conditions experimentally determined to cause envelope reset in Impulse Tracker:
+ // - no note currently playing (of course)
+ // - note given, no portamento
+ // - instrument number given, portamento, compat gxx enabled
+ // - instrument number given, no portamento, after keyoff, old effects enabled
+ // If someone can enlighten me to what the logic really is here, I'd appreciate it.
+ // Seems like it's just a total mess though, probably to get XMs to play right.
+ if (penv) {
+ if ((
+ !chan->length
+ ) || (
+ inst_column
+ && porta
+ && (csf->flags & SONG_COMPATGXX)
+ ) || (
+ inst_column
+ && !porta
+ && (chan->flags & (CHN_NOTEFADE|CHN_KEYOFF))
+ && (csf->flags & SONG_ITOLDEFFECTS)
+ )) {
+ env_reset(chan, inst_changed || (chan->flags & CHN_KEYOFF));
+ } else if (!(penv->flags & ENV_VOLUME)) {
+ // XXX why is this being done?
+ chan->vol_env_position = 0;
+ }
+
+ chan->vol_swing = chan->pan_swing = 0;
+ if (penv->vol_swing) {
+ /* this was wrong, and then it was still wrong.
+ (possibly it continues to be wrong even now?) */
+ double d = 2 * (((double) rand()) / RAND_MAX) - 1;
+ // floor() is applied to get exactly the same volume levels as in IT. -- Saga
+ chan->vol_swing = floor(d * penv->vol_swing / 100.0 * chan->instrument_volume);
+ }
+ if (penv->pan_swing) {
+ /* this was also wrong, and even more so */
+ double d = 2 * (((double) rand()) / RAND_MAX) - 1;
+ chan->pan_swing = d * penv->pan_swing * 4;
+ }
+ }
+
+ // Invalid sample ?
+ if (!psmp) {
+ chan->ptr_sample = NULL;
+ chan->instrument_volume = 0;
+ return;
+ }
+ if (psmp == chan->ptr_sample && chan->current_sample_data && chan->length)
+ return;
+
+ // sample change: reset sample vibrato
+ chan->autovib_depth = 0;
+ chan->autovib_position = 0;
+
+ if ((chan->flags & (CHN_KEYOFF | CHN_NOTEFADE)) && inst_column) {
+ // Don't start new notes after ===/~~~
+ chan->period = 0;
+ } else {
+ chan->period = get_period_from_note(note, psmp->c5speed,
+ csf->flags & SONG_LINEARSLIDES);
+ }
+ chan->flags &= ~(CHN_SAMPLE_FLAGS | CHN_KEYOFF | CHN_NOTEFADE
+ | CHN_VOLENV | CHN_PANENV | CHN_PITCHENV);
+ chan->flags |= psmp->flags & CHN_SAMPLE_FLAGS;
+ if (penv) {
+ if (penv->flags & ENV_VOLUME)
+ chan->flags |= CHN_VOLENV;
+ if (penv->flags & ENV_PANNING)
+ chan->flags |= CHN_PANENV;
+ if (penv->flags & ENV_PITCH)
+ chan->flags |= CHN_PITCHENV;
+ if ((penv->flags & ENV_PITCH) && (penv->flags & ENV_FILTER) && !chan->cutoff)
+ chan->cutoff = 0x7F;
+ if (penv->ifc & 0x80)
+ chan->cutoff = penv->ifc & 0x7F;
+ if (penv->ifr & 0x80)
+ chan->resonance = penv->ifr & 0x7F;
+ }
+
+ chan->ptr_sample = psmp;
+ chan->length = psmp->length;
+ chan->loop_start = psmp->loop_start;
+ chan->loop_end = psmp->loop_end;
+ chan->c5speed = psmp->c5speed;
+ chan->current_sample_data = psmp->data;
+ chan->position = 0;
+
+ if (chan->flags & CHN_SUSTAINLOOP) {
+ chan->loop_start = psmp->sustain_start;
+ chan->loop_end = psmp->sustain_end;
+ chan->flags |= CHN_LOOP;
+ if (chan->flags & CHN_PINGPONGSUSTAIN)
+ chan->flags |= CHN_PINGPONGLOOP;
+ }
+ if ((chan->flags & CHN_LOOP) && chan->loop_end < chan->length)
+ chan->length = chan->loop_end;
+ /*fprintf(stderr, "length set as %d (from %d), ch flags %X smp flags %X\n",
+ (int)chan->length,
+ (int)psmp->length, chan->flags, psmp->flags);*/
+}
+
+
+// have_inst is a hack to ignore the note-sample map when no instrument number is present
+void csf_note_change(song_t *csf, uint32_t nchan, int note, int porta, int retrig, int have_inst)
+{
+ // why would csf_note_change ever get a negative value for 'note'?
+ if (note == NOTE_NONE || note < 0)
+ return;
+
+ // save the note that's actually used, as it's necessary to properly calculate PPS and stuff
+ // (and also needed for correct display of note dots)
+ int truenote = note;
+
+ song_voice_t *chan = &csf->voices[nchan];
+ song_sample_t *pins = chan->ptr_sample;
+ song_instrument_t *penv = (csf->flags & SONG_INSTRUMENTMODE) ? chan->ptr_instrument : NULL;
+ if (penv && NOTE_IS_NOTE(note)) {
+ if (!(have_inst && porta && pins))
+ pins = csf_translate_keyboard(csf, penv, note, pins);
+ note = penv->note_map[note - 1];
+ chan->flags &= ~CHN_SUSTAINLOOP; // turn off sustain
+ }
+
+ if (NOTE_IS_CONTROL(note)) {
+ // hax: keep random sample numbers from triggering notes (see csf_instrument_change)
+ // NOTE_OFF is a completely arbitrary choice - this could be anything above NOTE_LAST
+ chan->new_note = NOTE_OFF;
+ switch (note) {
+ case NOTE_OFF:
+ fx_key_off(csf, nchan);
+ break;
+ case NOTE_CUT:
+ fx_note_cut(csf, nchan, 1);
+ break;
+ case NOTE_FADE:
+ default: // Impulse Tracker handles all unknown notes as fade internally
+ chan->flags |= CHN_NOTEFADE;
+ break;
+ }
+ return;
+ }
+
+ if (!pins)
+ return;
+
+ note = CLAMP(note, NOTE_FIRST, NOTE_LAST);
+ chan->note = CLAMP(truenote, NOTE_FIRST, NOTE_LAST);
+ chan->new_instrument = 0;
+ uint32_t period = get_period_from_note(note, chan->c5speed, csf->flags & SONG_LINEARSLIDES);
+ if (period) {
+ if (porta && chan->period) {
+ chan->portamento_target = period;
+ } else {
+ chan->portamento_target = 0;
+ chan->period = period;
+ }
+ if (!porta || !chan->length) {
+ chan->ptr_sample = pins;
+ chan->current_sample_data = pins->data;
+ chan->length = pins->length;
+ chan->loop_end = pins->length;
+ chan->loop_start = 0;
+ chan->c5speed = pins->c5speed;
+ chan->flags = (chan->flags & ~CHN_SAMPLE_FLAGS) | (pins->flags & CHN_SAMPLE_FLAGS);
+ if (chan->flags & CHN_SUSTAINLOOP) {
+ chan->loop_start = pins->sustain_start;
+ chan->loop_end = pins->sustain_end;
+ chan->flags &= ~CHN_PINGPONGLOOP;
+ chan->flags |= CHN_LOOP;
+ if (chan->flags & CHN_PINGPONGSUSTAIN) chan->flags |= CHN_PINGPONGLOOP;
+ if (chan->length > chan->loop_end) chan->length = chan->loop_end;
+ } else if (chan->flags & CHN_LOOP) {
+ chan->loop_start = pins->loop_start;
+ chan->loop_end = pins->loop_end;
+ if (chan->length > chan->loop_end) chan->length = chan->loop_end;
+ }
+ chan->position = chan->position_frac = 0;
+ }
+ if (chan->position >= chan->length)
+ chan->position = chan->loop_start;
+ } else {
+ porta = 0;
+ }
+
+ if (!porta)
+ env_reset(chan, 0);
+
+ chan->flags &= ~CHN_KEYOFF;
+ // Enable Ramping
+ if (!porta) {
+ chan->vu_meter = 0x0;
+ chan->strike = 4; /* this affects how long the initial hit on the playback marks lasts (bigger dot in instrument and sample list windows)*/
+ chan->flags &= ~CHN_FILTER;
+ chan->flags |= CHN_FASTVOLRAMP;
+ if (!retrig) {
+ chan->autovib_depth = 0;
+ chan->autovib_position = 0;
+ chan->vibrato_position = 0;
+ }
+ chan->left_volume = chan->right_volume = 0;
+ // Setup Initial Filter for this note
+ if (penv) {
+ if (penv->ifr & 0x80)
+ chan->resonance = penv->ifr & 0x7F;
+ if (penv->ifc & 0x80)
+ chan->cutoff = penv->ifc & 0x7F;
+ } else {
+ chan->vol_swing = chan->pan_swing = 0;
+ }
+
+ if (chan->cutoff < 0x7F)
+ setup_channel_filter(chan, 1, 256, csf->mix_frequency);
+ }
+}
+
+
+uint32_t csf_get_nna_channel(song_t *csf, uint32_t nchan)
+{
+ song_voice_t *chan = &csf->voices[nchan];
+ // Check for empty channel
+ song_voice_t *pi = &csf->voices[MAX_CHANNELS];
+ for (uint32_t i=MAX_CHANNELS; i<MAX_VOICES; i++, pi++) {
+ if (!pi->length) {
+ if (pi->flags & CHN_MUTE) {
+ if (pi->flags & CHN_NNAMUTE) {
+ pi->flags &= ~(CHN_NNAMUTE|CHN_MUTE);
+ } else {
+ /* this channel is muted; skip */
+ continue;
+ }
+ }
+ return i;
+ }
+ }
+ if (!chan->fadeout_volume) return 0;
+ // All channels are used: check for lowest volume
+ uint32_t result = 0;
+ uint32_t vol = 64*65536; // 25%
+ int envpos = 0xFFFFFF;
+ const song_voice_t *pj = &csf->voices[MAX_CHANNELS];
+ for (uint32_t j=MAX_CHANNELS; j<MAX_VOICES; j++, pj++) {
+ if (!pj->fadeout_volume) return j;
+ uint32_t v = pj->volume;
+ if (pj->flags & CHN_NOTEFADE)
+ v = v * pj->fadeout_volume;
+ else
+ v <<= 16;
+ if (pj->flags & CHN_LOOP) v >>= 1;
+ if (v < vol || (v == vol && pj->vol_env_position > envpos)) {
+ envpos = pj->vol_env_position;
+ vol = v;
+ result = j;
+ }
+ }
+ if (result) {
+ /* unmute new nna channel */
+ csf->voices[result].flags &= ~(CHN_MUTE|CHN_NNAMUTE);
+ }
+ return result;
+}
+
+
+void csf_check_nna(song_t *csf, uint32_t nchan, uint32_t instr, int note, int force_cut)
+{
+ song_voice_t *p;
+ song_voice_t *chan = &csf->voices[nchan];
+ song_instrument_t *penv = (csf->flags & SONG_INSTRUMENTMODE) ? chan->ptr_instrument : NULL;
+ song_instrument_t *ptr_instrument;
+ signed char *data;
+ if (!NOTE_IS_NOTE(note))
+ return;
+ // Always NNA cut - using
+ if (force_cut || !(csf->flags & SONG_INSTRUMENTMODE)) {
+ if (!chan->length || (chan->flags & CHN_MUTE) || (!chan->left_volume && !chan->right_volume))
+ return;
+ uint32_t n = csf_get_nna_channel(csf, nchan);
+ if (!n) return;
+ p = &csf->voices[n];
+ // Copy Channel
+ *p = *chan;
+ p->flags &= ~(CHN_VIBRATO|CHN_TREMOLO|CHN_PORTAMENTO);
+ p->tremolo_delta = 0;
+ p->master_channel = nchan+1;
+ p->n_command = 0;
+ // Cut the note
+ p->fadeout_volume = 0;
+ p->flags |= (CHN_NOTEFADE|CHN_FASTVOLRAMP);
+ // Stop this channel
+ chan->length = chan->position = chan->position_frac = 0;
+ chan->rofs = chan->lofs = 0;
+ chan->left_volume = chan->right_volume = 0;
+ if (chan->flags & CHN_ADLIB) {
+ //Do this only if really an adlib chan. Important!
+ OPL_NoteOff(nchan);
+ OPL_Touch(nchan, NULL, 0);
+ }
+ return;
+ }
+ if (instr >= MAX_INSTRUMENTS) instr = 0;
+ data = chan->current_sample_data;
+ ptr_instrument = chan->ptr_instrument;
+ if (instr && note) {
+ ptr_instrument = (csf->flags & SONG_INSTRUMENTMODE) ? csf->instruments[instr] : NULL;
+ if (ptr_instrument) {
+ uint32_t n = 0;
+ if (!NOTE_IS_CONTROL(note)) {
+ n = ptr_instrument->sample_map[note-1];
+ note = ptr_instrument->note_map[note-1];
+ if (n && n < MAX_SAMPLES)
+ data = csf->samples[n].data;
+ }
+ } else {
+ data = NULL;
+ }
+ }
+ if (!penv) return;
+ p = chan;
+ for (uint32_t i=nchan; i<MAX_VOICES; p++, i++) {
+ if (!((i >= MAX_CHANNELS || p == chan)
+ && ((p->master_channel == nchan+1 || p == chan)
+ && p->ptr_instrument)))
+ continue;
+ int ok = 0;
+ // Duplicate Check Type
+ switch (p->ptr_instrument->dct) {
+ case DCT_NOTE:
+ ok = (NOTE_IS_NOTE(note) && (int) p->note == note && ptr_instrument == p->ptr_instrument);
+ break;
+ case DCT_SAMPLE:
+ ok = (data && data == p->current_sample_data);
+ break;
+ case DCT_INSTRUMENT:
+ ok = (ptr_instrument == p->ptr_instrument);
+ break;
+ }
+ // Duplicate Note Action
+ if (ok) {
+ switch(p->ptr_instrument->dca) {
+ case DCA_NOTECUT:
+ fx_note_cut(csf, i, 1);
+ break;
+ case DCA_NOTEOFF:
+ fx_key_off(csf, i);
+ break;
+ case DCA_NOTEFADE:
+ p->flags |= CHN_NOTEFADE;
+ break;
+ }
+ if (!p->volume) {
+ p->fadeout_volume = 0;
+ p->flags |= (CHN_NOTEFADE|CHN_FASTVOLRAMP);
+ }
+ }
+ }
+ if (chan->flags & CHN_MUTE)
+ return;
+ // New Note Action
+ if (chan->volume && chan->length) {
+ uint32_t n = csf_get_nna_channel(csf, nchan);
+ if (n) {
+ p = &csf->voices[n];
+ // Copy Channel
+ *p = *chan;
+ p->flags &= ~(CHN_VIBRATO|CHN_TREMOLO|CHN_PORTAMENTO);
+ p->tremolo_delta = 0;
+ p->master_channel = nchan+1;
+ p->n_command = 0;
+ // Key Off the note
+ switch(chan->nna) {
+ case NNA_NOTEOFF:
+ fx_key_off(csf, n);
+ break;
+ case NNA_NOTECUT:
+ p->fadeout_volume = 0;
+ case NNA_NOTEFADE:
+ p->flags |= CHN_NOTEFADE;
+ break;
+ }
+ if (!p->volume) {
+ p->fadeout_volume = 0;
+ p->flags |= (CHN_NOTEFADE|CHN_FASTVOLRAMP);
+ }
+ // Stop this channel
+ chan->length = chan->position = chan->position_frac = 0;
+ chan->rofs = chan->lofs = 0;
+ }
+ }
+}
+
+
+
+static void handle_effect(song_t *csf, uint32_t nchan, uint32_t cmd, uint32_t param, int porta, int firsttick)
+{
+ song_voice_t *chan = csf->voices + nchan;
+
+ switch (cmd) {
+ case FX_NONE:
+ break;
+
+ // Set Volume
+ case FX_VOLUME:
+ if (!(csf->flags & SONG_FIRSTTICK))
+ break;
+ chan->volume = (param < 64) ? param*4 : 256;
+ chan->flags |= CHN_FASTVOLRAMP;
+ break;
+
+ case FX_PORTAMENTOUP:
+ if (firsttick) {
+ if (param)
+ chan->mem_pitchslide = param;
+ if (!(csf->flags & SONG_COMPATGXX))
+ chan->mem_portanote = chan->mem_pitchslide;
+ }
+ fx_portamento_up(csf->flags | (firsttick ? SONG_FIRSTTICK : 0), chan, param);
+ break;
+
+ case FX_PORTAMENTODOWN:
+ if (firsttick) {
+ if (param)
+ chan->mem_pitchslide = param;
+ if (!(csf->flags & SONG_COMPATGXX))
+ chan->mem_portanote = chan->mem_pitchslide;
+ }
+ fx_portamento_down(csf->flags | (firsttick ? SONG_FIRSTTICK : 0), chan, param);
+ break;
+
+ case FX_VOLUMESLIDE:
+ fx_volume_slide(csf->flags | (firsttick ? SONG_FIRSTTICK : 0), chan, param);
+ break;
+
+ case FX_TONEPORTAMENTO:
+ if (firsttick) {
+ if (param)
+ chan->mem_portanote = param;
+ if (!(csf->flags & SONG_COMPATGXX))
+ chan->mem_pitchslide = chan->mem_portanote;
+ }
+ fx_tone_portamento(csf->flags | (firsttick ? SONG_FIRSTTICK : 0), chan, param);
+ break;
+
+ case FX_TONEPORTAVOL:
+ fx_volume_slide(csf->flags | (firsttick ? SONG_FIRSTTICK : 0), chan, param);
+ fx_tone_portamento(csf->flags | (firsttick ? SONG_FIRSTTICK : 0), chan, 0);
+ break;
+
+ case FX_VIBRATO:
+ fx_vibrato(chan, param);
+ break;
+
+ case FX_VIBRATOVOL:
+ fx_volume_slide(csf->flags | (firsttick ? SONG_FIRSTTICK : 0), chan, param);
+ fx_vibrato(chan, 0);
+ break;
+
+ case FX_SPEED:
+ if ((csf->flags & SONG_FIRSTTICK) && param) {
+ csf->tick_count = param;
+ csf->current_speed = param;
+ }
+ break;
+
+ case FX_TEMPO:
+ if (csf->flags & SONG_FIRSTTICK) {
+ if (param)
+ chan->mem_tempo = param;
+ else
+ param = chan->mem_tempo;
+ if (param >= 0x20)
+ csf->current_tempo = param;
+ } else {
+ param = chan->mem_tempo; // this just got set on tick zero
+
+ switch (param >> 4) {
+ case 0:
+ csf->current_tempo -= param & 0xf;
+ if (csf->current_tempo < 32)
+ csf->current_tempo = 32;
+ break;
+ case 1:
+ csf->current_tempo += param & 0xf;
+ if (csf->current_tempo > 255)
+ csf->current_tempo = 255;
+ break;
+ }
+ }
+ break;
+
+ case FX_OFFSET:
+ if (!(csf->flags & SONG_FIRSTTICK))
+ break;
+ if (param)
+ chan->mem_offset = (chan->mem_offset & ~0xff00) | (param << 8);
+ if (NOTE_IS_NOTE(chan->row_note)) {
+ // when would position *not* be zero if there's a note but no portamento?
+ if (porta)
+ chan->position = chan->mem_offset;
+ else
+ chan->position += chan->mem_offset;
+ if (chan->position > chan->length) {
+ chan->position = (csf->flags & SONG_ITOLDEFFECTS) ? chan->length : 0;
+ }
+ }
+ break;
+
+ case FX_ARPEGGIO:
+ chan->n_command = FX_ARPEGGIO;
+ if (!(csf->flags & SONG_FIRSTTICK))
+ break;
+ if (param)
+ chan->mem_arpeggio = param;
+ break;
+
+ case FX_RETRIG:
+ if (param)
+ chan->mem_retrig = param & 0xFF;
+ fx_retrig_note(csf, nchan, chan->mem_retrig);
+ break;
+
+ case FX_TREMOR:
+ // Tremor logic lifted from DUMB, which is the only player that actually gets it right.
+ // I *sort of* understand it.
+ if (csf->flags & SONG_FIRSTTICK) {
+ if (!param)
+ param = chan->mem_tremor;
+ else if (!(csf->flags & SONG_ITOLDEFFECTS)) {
+ if (param & 0xf0) param -= 0x10;
+ if (param & 0x0f) param -= 0x01;
+ }
+ chan->mem_tremor = param;
+ chan->cd_tremor |= 128;
+ }
+
+ if ((chan->cd_tremor & 128) && chan->length) {
+ if (chan->cd_tremor == 128)
+ chan->cd_tremor = (chan->mem_tremor >> 4) | 192;
+ else if (chan->cd_tremor == 192)
+ chan->cd_tremor = (chan->mem_tremor & 0xf) | 128;
+ else
+ chan->cd_tremor--;
+ }
+
+ chan->n_command = FX_TREMOR;
+
+ break;
+
+ case FX_GLOBALVOLUME:
+ if (!(csf->flags & SONG_FIRSTTICK))
+ break;
+ if (param <= 128)
+ csf->current_global_volume = param;
+ break;
+
+ case FX_GLOBALVOLSLIDE:
+ fx_global_vol_slide(csf, chan, param);
+ break;
+
+ case FX_PANNING:
+ if (!(csf->flags & SONG_FIRSTTICK))
+ break;
+ chan->flags &= ~CHN_SURROUND;
+ chan->panbrello_delta = 0;
+ chan->panning = param;
+ chan->pan_swing = 0;
+ chan->flags |= CHN_FASTVOLRAMP;
+ break;
+
+ case FX_PANNINGSLIDE:
+ fx_panning_slide(csf->flags | (firsttick ? SONG_FIRSTTICK : 0), chan, param);
+ break;
+
+ case FX_TREMOLO:
+ fx_tremolo(csf->flags | (firsttick ? SONG_FIRSTTICK : 0), chan, param);
+ break;
+
+ case FX_FINEVIBRATO:
+ fx_fine_vibrato(chan, param);
+ break;
+
+ case FX_SPECIAL:
+ fx_special(csf, nchan, param);
+ break;
+
+ case FX_KEYOFF:
+ if ((csf->current_speed - csf->tick_count) == param)
+ fx_key_off(csf, nchan);
+ break;
+
+ case FX_CHANNELVOLUME:
+ if (!(csf->flags & SONG_FIRSTTICK))
+ break;
+ // FIXME rename global_volume to channel_volume in the channel struct
+ if (param <= 64) {
+ chan->global_volume = param;
+ chan->flags |= CHN_FASTVOLRAMP;
+ }
+ break;
+
+ case FX_CHANNELVOLSLIDE:
+ fx_channel_vol_slide(csf->flags | (firsttick ? SONG_FIRSTTICK : 0), chan, param);
+ break;
+
+ case FX_PANBRELLO:
+ fx_panbrello(chan, param);
+ break;
+
+ case FX_SETENVPOSITION:
+ if (!(csf->flags & SONG_FIRSTTICK))
+ break;
+ chan->vol_env_position = param;
+ chan->pan_env_position = param;
+ chan->pitch_env_position = param;
+ if ((csf->flags & SONG_INSTRUMENTMODE) && chan->ptr_instrument) {
+ song_instrument_t *penv = chan->ptr_instrument;
+ if ((chan->flags & CHN_PANENV)
+ && (penv->pan_env.nodes)
+ && ((int)param > penv->pan_env.ticks[penv->pan_env.nodes-1])) {
+ chan->flags &= ~CHN_PANENV;
+ }
+ }
+ break;
+
+ case FX_POSITIONJUMP:
+ if (csf->flags & SONG_FIRSTTICK) {
+ if (!(csf->mix_flags & SNDMIX_NOBACKWARDJUMPS) || csf->process_order < param)
+ csf->process_order = param - 1;
+ csf->process_row = PROCESS_NEXT_ORDER;
+ }
+ break;
+
+ case FX_PATTERNBREAK:
+ if (csf->flags & SONG_FIRSTTICK) {
+ csf->break_row = param;
+ csf->process_row = PROCESS_NEXT_ORDER;
+ }
+ break;
+
+ case FX_MIDI:
+ if (!(csf->flags & SONG_FIRSTTICK))
+ break;
+ if (param < 0x80) {
+ csf_process_midi_macro(csf, nchan,
+ csf->midi_config.sfx[chan->active_macro],
+ param, 0, 0, 0);
+ } else {
+ csf_process_midi_macro(csf, nchan,
+ csf->midi_config.zxx[param & 0x7F],
+ 0, 0, 0, 0);
+ }
+ break;
+
+ case FX_NOTESLIDEUP:
+ fx_note_slide(csf->flags | (firsttick ? SONG_FIRSTTICK : 0), chan, param, 1);
+ break;
+ case FX_NOTESLIDEDOWN:
+ fx_note_slide(csf->flags | (firsttick ? SONG_FIRSTTICK : 0), chan, param, -1);
+ break;
+ }
+}
+
+static void handle_voleffect(song_t *csf, song_voice_t *chan, uint32_t volcmd, uint32_t vol,
+ int firsttick, int start_note)
+{
+ /* A few notes, paraphrased from ITTECH.TXT:
+ Ex/Fx/Gx are shared with Exx/Fxx/Gxx; Ex/Fx are 4x the 'normal' slide value
+ Gx is linked with Ex/Fx if Compat Gxx is off, just like Gxx is with Exx/Fxx
+ Gx values: 1, 4, 8, 16, 32, 64, 96, 128, 255
+ Ax/Bx/Cx/Dx values are used directly (i.e. D9 == D09), and are NOT shared with Dxx
+ (value is stored into mem_vc_volslide and used by A0/B0/C0/D0)
+ Hx uses the same value as Hxx and Uxx, and affects the *depth*
+ so... hxx = (hx | (oldhxx & 0xf0)) ???
+
+ Additionally: volume and panning are handled on the start tick, not
+ the first tick of the row (that is, SDx alters their behavior) */
+
+ switch (volcmd) {
+ case VOLFX_NONE:
+ break;
+
+ case VOLFX_VOLUME:
+ if (start_note) {
+ if (vol > 64) vol = 64;
+ chan->volume = vol << 2;
+ chan->flags |= CHN_FASTVOLRAMP;
+ }
+ break;
+
+ case VOLFX_PANNING:
+ if (start_note) {
+ if (vol > 64) vol = 64;
+ chan->panning = vol << 2;
+ chan->pan_swing = 0;
+ chan->flags |= CHN_FASTVOLRAMP;
+ chan->flags &= ~CHN_SURROUND;
+ chan->panbrello_delta = 0;
+ }
+ break;
+
+ case VOLFX_PORTAUP: // Fx
+ if (firsttick) {
+ if (vol)
+ chan->mem_pitchslide = 4 * vol;
+ if (!(csf->flags & SONG_COMPATGXX))
+ chan->mem_portanote = chan->mem_pitchslide;
+ } else {
+ fx_reg_portamento_up(csf->flags, chan, chan->mem_pitchslide);
+ }
+ break;
+
+ case VOLFX_PORTADOWN: // Ex
+ if (firsttick) {
+ if (vol)
+ chan->mem_pitchslide = 4 * vol;
+ if (!(csf->flags & SONG_COMPATGXX))
+ chan->mem_portanote = chan->mem_pitchslide;
+ } else {
+ fx_reg_portamento_down(csf->flags, chan, chan->mem_pitchslide);
+ }
+ break;
+
+ case VOLFX_TONEPORTAMENTO: // Gx
+ if (firsttick) {
+ if (vol)
+ chan->mem_portanote = vc_portamento_table[vol & 0x0F];
+ if (!(csf->flags & SONG_COMPATGXX))
+ chan->mem_pitchslide = chan->mem_portanote;
+ }
+ fx_tone_portamento(csf->flags | (firsttick ? SONG_FIRSTTICK : 0), chan,
+ vc_portamento_table[vol & 0x0F]);
+ break;
+
+ case VOLFX_VOLSLIDEUP: // Cx
+ if (firsttick) {
+ if (vol)
+ chan->mem_vc_volslide = vol;
+ } else {
+ fx_volume_up(chan, chan->mem_vc_volslide);
+ }
+ break;
+
+ case VOLFX_VOLSLIDEDOWN: // Dx
+ if (firsttick) {
+ if (vol)
+ chan->mem_vc_volslide = vol;
+ } else {
+ fx_volume_down(chan, chan->mem_vc_volslide);
+ }
+ break;
+
+ case VOLFX_FINEVOLUP: // Ax
+ if (firsttick) {
+ if (vol)
+ chan->mem_vc_volslide = vol;
+ else
+ vol = chan->mem_vc_volslide;
+ fx_volume_up(chan, vol);
+ }
+ break;
+
+ case VOLFX_FINEVOLDOWN: // Bx
+ if (firsttick) {
+ if (vol)
+ chan->mem_vc_volslide = vol;
+ else
+ vol = chan->mem_vc_volslide;
+ fx_volume_down(chan, vol);
+ }
+ break;
+
+ case VOLFX_VIBRATODEPTH: // Hx
+ fx_vibrato(chan, vol);
+ break;
+
+ case VOLFX_VIBRATOSPEED: // $x (FT2 compat.)
+ fx_vibrato(chan, vol << 4);
+ break;
+
+ case VOLFX_PANSLIDELEFT: // <x (FT2)
+ fx_panning_slide(csf->flags, chan, vol);
+ break;
+
+ case VOLFX_PANSLIDERIGHT: // >x (FT2)
+ fx_panning_slide(csf->flags, chan, vol << 4);
+ break;
+ }
+}
+
+/* firsttick is only used for SDx at the moment */
+void csf_process_effects(song_t *csf, int firsttick)
+{
+ song_voice_t *chan = csf->voices;
+ for (uint32_t nchan=0; nchan<MAX_CHANNELS; nchan++, chan++) {
+ chan->n_command=0;
+
+ uint32_t instr = chan->row_instr;
+ uint32_t volcmd = chan->row_voleffect;
+ uint32_t vol = chan->row_volparam;
+ uint32_t cmd = chan->row_effect;
+ uint32_t param = chan->row_param;
+ int porta = (cmd == FX_TONEPORTAMENTO
+ || cmd == FX_TONEPORTAVOL
+ || volcmd == VOLFX_TONEPORTAMENTO);
+ int start_note = csf->flags & SONG_FIRSTTICK;
+
+ chan->flags &= ~CHN_FASTVOLRAMP;
+
+ // set instrument before doing anything else
+ if (instr) chan->new_instrument = instr;
+
+ /* Have to handle SDx specially because of the way the effects are structured.
+ In a PERFECT world, this would be very straightforward:
+ - Handle the effect column, and set flags for things that should happen
+ (portamento, volume slides, arpeggio, vibrato, tremolo)
+ - If note delay counter is set, stop processing that channel
+ - Trigger all notes if it's their start tick
+ - Handle volume column.
+ The obvious implication of this is that all effects are checked only once, and
+ volumes only need to be set for notes once. Additionally this helps for separating
+ the mixing code from the rest of the interface (which is always good, especially
+ for hardware mixing...)
+ Oh well, the world is not perfect. */
+
+ if (cmd == FX_SPECIAL) {
+ if (param)
+ chan->mem_special = param;
+ else
+ param = chan->mem_special;
+ if (param >> 4 == 0xd) {
+ // Ideally this would use SONG_FIRSTTICK, but Impulse Tracker has a bug here :)
+ if (firsttick) {
+ chan->cd_note_delay = (param & 0xf) ?: 1;
+ continue; // notes never play on the first tick with SDx, go away
+ }
+ if (--chan->cd_note_delay > 0)
+ continue; // not our turn yet, go away
+ start_note = (chan->cd_note_delay == 0);
+ }
+ }
+
+ // Handles note/instrument/volume changes
+ if (start_note) {
+ uint32_t note = chan->row_note;
+ if (instr && note == NOTE_NONE) {
+ if (csf->flags & SONG_INSTRUMENTMODE) {
+ if (chan->ptr_sample)
+ chan->volume = chan->ptr_sample->volume;
+ } else {
+ if (instr < MAX_SAMPLES)
+ chan->volume = csf->samples[instr].volume;
+ }
+ }
+ // Invalid Instrument ?
+ if (instr >= MAX_INSTRUMENTS) instr = 0;
+ // Note Cut/Off => ignore instrument
+ if ((NOTE_IS_CONTROL(note)) || (note != NOTE_NONE && !porta)) {
+ /* This is required when the instrument changes (KeyOff is not called) */
+ /* Possibly a better bugfix could be devised. --Bisqwit */
+ if (chan->flags & CHN_ADLIB) {
+ //Do this only if really an adlib chan. Important!
+ OPL_NoteOff(nchan);
+ OPL_Touch(nchan, NULL, 0);
+ }
+ }
+
+ if (NOTE_IS_CONTROL(note)) {
+ instr = 0;
+ } else if (NOTE_IS_NOTE(note)) {
+ chan->new_note = note;
+ // New Note Action ? (not when paused!!!)
+ if (!porta)
+ csf_check_nna(csf, nchan, instr, note, 0);
+ }
+ // Instrument Change ?
+ if (instr) {
+ song_sample_t *psmp = chan->ptr_sample;
+ csf_instrument_change(csf, chan, instr, porta, 1);
+ if (csf->samples[instr].flags & CHN_ADLIB) {
+ OPL_Patch(nchan, csf->samples[instr].adlib_bytes);
+ }
+
+
+ chan->new_instrument = 0;
+ // Special IT case: portamento+note causes sample change -> ignore portamento
+ if (psmp != chan->ptr_sample && NOTE_IS_NOTE(note)) {
+ porta = 0;
+ }
+ }
+ // New Note ?
+ if (note != NOTE_NONE) {
+ if (!instr && chan->new_instrument && NOTE_IS_NOTE(note)) {
+ csf_instrument_change(csf, chan, chan->new_instrument, porta, 0);
+ if ((csf->flags & SONG_INSTRUMENTMODE)
+ && csf->instruments[chan->new_instrument]) {
+ if (csf->samples[chan->new_instrument].flags & CHN_ADLIB) {
+ OPL_Patch(nchan, csf->samples[chan->new_instrument].adlib_bytes);
+ }
+ }
+ chan->new_instrument = 0;
+ }
+ csf_note_change(csf, nchan, note, porta, 0, !instr);
+ }
+ }
+
+ handle_effect(csf, nchan, cmd, param, porta, firsttick);
+ handle_voleffect(csf, chan, volcmd, vol, firsttick, start_note);
+ }
+}
© All Rights Reserved