From 78f8fce7f286fd0c71774e2567404ed51f24fef3 Mon Sep 17 00:00:00 2001 From: Vito Caputo Date: Fri, 26 May 2017 21:51:04 -0700 Subject: *: initial commit of stripped schism stuff Forking schism tracker's IT playback stuff into a little playback library for embedding in demos. --- src/player/effects.c | 2070 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2070 insertions(+) create mode 100644 src/player/effects.c (limited to 'src/player/effects.c') 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 + * copyright (c) 2005-2008 Mrs. Brisby + * 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 + + +// 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; imaster_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; ilength) { + 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; jfadeout_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_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: // 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; nchann_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); + } +} -- cgit v1.2.3