xmpnx/libxmp/src/far_extras.c

406 lines
12 KiB
C

/* Extended Module Player
* Copyright (C) 1996-2021 Claudio Matsuoka and Hipolito Carraro Jr
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "common.h"
#include "player.h"
#include "lfo.h"
#include "effects.h"
#include "period.h"
#include "far_extras.h"
#define FAR_GUS_CHANNELS 17
#define FAR_OLD_TEMPO_SHIFT 2 /* Power of multiplier for old tempo mode. */
/**
* The time factor needed to directly use FAR tempos is a little unintuitive.
*
* Generally: FAR tries to run 32/[coarse tempo] rows per second, which
* (usually, but not always) are subdivided into 4 "ticks". To achieve
* this, it measures tempos in the number of ticks that should play per second
* (see far_tempos below). Fine tempo is added or subtracted from this number.
* To time these ticks, FAR uses the programmable interval timer (PIT) to run a
* player interrupt.
*
* libxmp effectively uses a calculation of 10.0 * 0.25 / BPM to get the tick
* duration in seconds. A base time factor of 4.0 makes this 1 / BPM, turning
* BPM into the ticks/sec measure that FAR uses. This isn't completely
* accurate to FAR, though.
*
* The x86 PIT runs at a rate of 1193182 Hz, but FAR does something strange
* when calculating PIT divisors and uses a constant of 1197255 Hz instead.
* This means FAR tempo is slightly slower by a factor of around:
*
* floor(1197255 / 32) / floor(1193182 / 32) ~= 1.003439
*
* This still isn't perfect, but it gets the playback rate fairly close.
*/
/* tempo[0] = 256; tempo[i] = floor(128 / i). */
static const int far_tempos[16] =
{
256, 128, 64, 42, 32, 25, 21, 18, 16, 14, 12, 11, 10, 9, 9, 8
};
/**
* FAR tempo has some unusual requirements that don't really match any other
* format:
*
* 1) The coarse tempo is roughly equivalent to speed, but a value of 0 is
* supported, and FAR doesn't actually have a concept of ticks: it translates
* this value to tempo.
*
* 2) There is some very bizarre clamping behavior involving fine tempo slides
* that needs to be emulated.
*
* 3) Tempos can range from 1 to 356(!). FAR uses a fixed row subdivision size
* of 16, so just shift the tempo by 4 and hope libxmp doesn't change it.
*
* 4) There are two tempo modes, and they can be switched between arbitrarily...
*/
int libxmp_far_translate_tempo(int mode, int fine_change, int coarse,
int *fine, int *_speed, int *_bpm)
{
int speed, bpm;
if (coarse < 0 || coarse > 15 || mode < 0 || mode > 1)
return -1;
/* Compatibility for FAR's broken fine tempo "clamping". */
if (fine_change < 0 && far_tempos[coarse] + *fine <= 0) {
*fine = 0;
} else if (fine_change > 0 && far_tempos[coarse] + *fine >= 100) {
*fine = 100;
}
if (mode == 1) {
/* "New" FAR tempo
* Note that negative values are possible in Farandole Composer
* via changing fine tempo and then slowing coarse tempo.
* These result in very slow final tempos due to signed to
* unsigned conversion. Zero should just be ignored entirely. */
int tempo = far_tempos[coarse] + *fine;
uint32 divisor;
if (tempo == 0)
return -1;
divisor = 1197255 / tempo;
/* Coincidentally(?), the "new" FAR tempo algorithm actually
* prevents the BPM from dropping too far under XMP_MIN_BPM,
* which is what libxmp needs anyway. */
speed = 0;
while (divisor > 0xffff) {
divisor >>= 1;
tempo <<= 1;
speed++;
}
if (speed >= 2)
speed++;
speed += 3;
/* Add an extra tick because the FAR replayer checks the tick
* remaining count before decrementing it but after handling
* each tick, i.e. a count of "3" executes 4 ticks. */
speed++;
bpm = tempo;
} else {
/* "Old" FAR tempo
* This runs into the XMP_MIN_BPM limit, but nothing uses it anyway.
* Old tempo mode in the original FAR replayer has 32 ticks,
* but ignores all except every 8th. */
speed = 4 << FAR_OLD_TEMPO_SHIFT;
bpm = (far_tempos[coarse] + *fine * 2) << FAR_OLD_TEMPO_SHIFT;
}
if (bpm < XMP_MIN_BPM)
bpm = XMP_MIN_BPM;
*_speed = speed;
*_bpm = bpm;
return 0;
}
static void libxmp_far_update_tempo(struct context_data *ctx, int fine_change)
{
struct player_data *p = &ctx->p;
struct module_data *m = &ctx->m;
struct far_module_extras *me = (struct far_module_extras *)m->extra;
int speed, bpm;
if (libxmp_far_translate_tempo(me->tempo_mode, fine_change,
me->coarse_tempo, &me->fine_tempo, &speed, &bpm) == 0) {
p->speed = speed;
p->bpm = bpm;
p->frame_time = m->time_factor * m->rrate / p->bpm;
}
}
static void libxmp_far_update_vibrato(struct lfo *lfo, int rate, int depth)
{
libxmp_lfo_set_depth(lfo, libxmp_gus_frequency_steps(depth << 1, FAR_GUS_CHANNELS));
libxmp_lfo_set_rate(lfo, rate * 3);
}
/* Convoluted algorithm for delay times for retrigger and note offset effects. */
static int libxmp_far_retrigger_delay(struct far_module_extras *me, int param)
{
int delay;
if (me->coarse_tempo < 0 || me->coarse_tempo > 15 || param < 1)
return -1;
delay = (far_tempos[me->coarse_tempo] + me->fine_tempo) / param;
if (me->tempo_mode) {
/* Effects divide by 4, timer increments by 2 (round up). */
return ((delay >> 2) + 1) >> 1;
} else {
/* Effects divide by 2, timer increments by 2 (round up).
* Old tempo mode handles every 8th tick (<< FAR_OLD_TEMPO_SHIFT).
* Delay values >4 result in no retrigger. */
delay = (((delay >> 1) + 1) >> 1) << FAR_OLD_TEMPO_SHIFT;
if (delay >= 16)
return -1;
if (delay < (1 << FAR_OLD_TEMPO_SHIFT))
return (1 << FAR_OLD_TEMPO_SHIFT);
return delay;
}
}
void libxmp_far_play_extras(struct context_data *ctx, struct channel_data *xc, int chn)
{
struct far_module_extras *me = FAR_MODULE_EXTRAS(ctx->m);
struct far_channel_extras *ce = FAR_CHANNEL_EXTRAS(*xc);
/* FAR vibrato depth is global, even though rate isn't. This might have
* been changed by a different channel, so make sure it's applied. */
if (TEST(VIBRATO) || TEST_PER(VIBRATO))
libxmp_far_update_vibrato(&xc->vibrato.lfo, ce->vib_rate, me->vib_depth);
}
int libxmp_far_new_channel_extras(struct channel_data *xc)
{
xc->extra = calloc(1, sizeof(struct far_channel_extras));
if (xc->extra == NULL)
return -1;
FAR_CHANNEL_EXTRAS(*xc)->magic = FAR_EXTRAS_MAGIC;
return 0;
}
void libxmp_far_reset_channel_extras(struct channel_data *xc)
{
memset((char *)xc->extra + 4, 0, sizeof(struct far_channel_extras) - 4);
}
void libxmp_far_release_channel_extras(struct channel_data *xc)
{
free(xc->extra);
xc->extra = NULL;
}
int libxmp_far_new_module_extras(struct module_data *m)
{
m->extra = calloc(1, sizeof(struct far_module_extras));
if (m->extra == NULL)
return -1;
FAR_MODULE_EXTRAS(*m)->magic = FAR_EXTRAS_MAGIC;
FAR_MODULE_EXTRAS(*m)->vib_depth = 4;
return 0;
}
void libxmp_far_release_module_extras(struct module_data *m)
{
free(m->extra);
m->extra = NULL;
}
void libxmp_far_extras_process_fx(struct context_data *ctx, struct channel_data *xc,
int chn, uint8 note, uint8 fxt, uint8 fxp, int fnum)
{
struct xmp_module *mod = &ctx->m.mod;
struct far_module_extras *me = FAR_MODULE_EXTRAS(ctx->m);
struct far_channel_extras *ce = FAR_CHANNEL_EXTRAS(*xc);
int update_tempo = 0;
int update_vibrato = 0;
int fine_change = 0;
int delay, target, tempo;
int32 diff, step;
/* Tempo effects and vibrato are multiplexed to reduce the effects count.
*
* Misc. notes: FAR pitch offset effects can overflow/underflow GUS
* frequency, which isn't supported by libxmp (Haj/before.far).
*/
switch (fxt) {
case FX_FAR_PORTA_UP: /* FAR pitch offset up */
SET(FINE_BEND);
RESET_PER(TONEPORTA);
xc->freq.fslide = libxmp_gus_frequency_steps(fxp << 2, FAR_GUS_CHANNELS);
break;
case FX_FAR_PORTA_DN: /* FAR pitch offset down */
SET(FINE_BEND);
RESET_PER(TONEPORTA);
xc->freq.fslide = -libxmp_gus_frequency_steps(fxp << 2, FAR_GUS_CHANNELS);
break;
/* Despite some claims, this effect scales with tempo and only
* corresponds to (param) rows at tempo 4. See FORMATS.DOC.
*/
case FX_FAR_TPORTA: /* FAR persistent tone portamento */
if (!IS_VALID_INSTRUMENT(xc->ins))
break;
tempo = far_tempos[me->coarse_tempo] + me->fine_tempo;
SET_PER(TONEPORTA);
if (IS_VALID_NOTE(note - 1)) {
xc->porta.target = libxmp_note_to_period(ctx, note - 1, xc->finetune, xc->per_adj);
}
xc->porta.dir = xc->period < xc->porta.target ? 1 : -1;
/* Parameter of 0 is equivalent to 1. */
if (fxp < 1)
fxp = 1;
/* Tempos <=0 cause crashes and other weird behavior
* here in Farandole Composer, don't emulate that. */
if (tempo < 1)
tempo = 1;
diff = xc->porta.target - xc->period;
step = (diff > 0 ? diff : -diff) * 8 / (tempo * fxp);
xc->porta.slide = (step > 0) ? step : 1;
break;
/* Despite some claims, this effect scales with tempo and only
* corresponds to (param/2) rows at tempo 4. See FORMATS.DOC.
*/
case FX_FAR_SLIDEVOL: /* FAR persistent slide-to-volume */
tempo = far_tempos[me->coarse_tempo] + me->fine_tempo;
target = MSN(fxp) << 4;
fxp = LSN(fxp);
/* Parameter of 0 is equivalent to 1. */
if (fxp < 1)
fxp = 1;
/* Tempos <=0 cause crashes and other weird behavior
* here in Farandole Composer, don't emulate that. */
if (tempo < 1)
tempo = 1;
diff = target - xc->volume;
step = diff * 16 / (tempo * fxp);
if (step == 0)
step = (diff > 0) ? 1 : -1;
SET_PER(VOL_SLIDE);
xc->vol.slide = step;
xc->vol.target = target + 1;
break;
case FX_FAR_VIBDEPTH: /* FAR set vibrato depth */
me->vib_depth = LSN(fxp);
update_vibrato = 1;
break;
case FX_FAR_VIBRATO: /* FAR vibrato and sustained vibrato */
if (ce->vib_sustain == 0) {
/* With sustain, regular vibrato only sets the rate. */
ce->vib_sustain = MSN(fxp);
if (ce->vib_sustain == 0)
SET(VIBRATO);
}
ce->vib_rate = LSN(fxp);
update_vibrato = 1;
break;
/* Retrigger note param times at intervals that roughly evently
* divide the row. A param of 0 crashes Farandole Composer.
*/
case FX_FAR_RETRIG: /* FAR retrigger */
delay = libxmp_far_retrigger_delay(me, fxp);
if (note && fxp > 1 && delay >= 0 && delay <= ctx->p.speed) {
SET(RETRIG);
xc->retrig.val = delay ? delay : 1;
xc->retrig.count = delay + 1;
xc->retrig.type = 0;
xc->retrig.limit = fxp - 1;
}
break;
/* A better effect name would probably be "retrigger once".
* The description/intent seems to be that this is a delay
* effect, but an initial note always plays as well. The second
* note always plays on the (param)th tick due to player quirks,
* but it's supposed to be derived similar to retrigger.
* A param of zero works like effect 4F (bug?).
*/
case FX_FAR_DELAY: /* FAR note offset */
if (note) {
delay = me->tempo_mode ? fxp : fxp << FAR_OLD_TEMPO_SHIFT;
SET(RETRIG);
xc->retrig.val = delay ? delay : 1;
xc->retrig.count = delay + 1;
xc->retrig.type = 0;
xc->retrig.limit = fxp ? 1 : 0;
}
break;
case FX_FAR_TEMPO: /* FAR coarse tempo and tempo mode */
if (MSN(fxp)) {
me->tempo_mode = MSN(fxp) - 1;
} else {
me->coarse_tempo = LSN(fxp);
}
update_tempo = 1;
break;
case FX_FAR_F_TEMPO: /* FAR fine tempo slide up/down */
if (MSN(fxp)) {
me->fine_tempo += MSN(fxp);
fine_change = MSN(fxp);
} else if (LSN(fxp)) {
me->fine_tempo -= LSN(fxp);
fine_change = -LSN(fxp);
} else {
me->fine_tempo = 0;
}
update_tempo = 1;
break;
}
if (update_vibrato) {
if (ce->vib_rate != 0) {
if (ce->vib_sustain)
SET_PER(VIBRATO);
} else {
RESET_PER(VIBRATO);
ce->vib_sustain = 0;
}
libxmp_far_update_vibrato(&xc->vibrato.lfo, ce->vib_rate, me->vib_depth);
}
if (update_tempo)
libxmp_far_update_tempo(ctx, fine_change);
}