psxsdk/libf3m/f3m.c

1069 lines
24 KiB
C
Executable File

// made in 2015 by GreaseMonkey - Public Domain
// modified in 2016 by nextvolume for inclusion in PSXSDK
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <psx.h>
#include "f3m.h"
typedef struct ins
{
uint8_t typ;
uint8_t fname[12];
uint8_t dat_para_h;
uint16_t dat_para;
uint32_t len, lpbeg, lpend;
uint8_t vol, rsv1, pack, flags;
uint32_t c4freq;
uint8_t rsv2[12];
uint8_t name[28];
uint8_t magic[4];
} __attribute__((__packed__)) ins_s;
extern mod_s fsys_s3m_test[];
const uint32_t period_amiclk = 8363*1712-400;
const uint16_t period_table[12] = {1712,1616,1524,1440,1356,1280,1208,1140,1076,1016,960,907};
// from ITTECH.TXT
static const int8_t f3m_sintab[64] = {
0, 6, 12, 19, 24, 30, 36, 41,
45, 49, 53, 56, 59, 61, 63, 64,
64, 64, 63, 61, 59, 56, 53, 49,
45, 41, 36, 30, 24, 19, 12, 6,
};
mod_s *f3m_mod_load(uint32_t *d)
{
return (mod_s*)d;
}
mod_s *f3m_mod_load_filename(const char *fname)
{
FILE *fp;
int filesize;
char *buf;
mod_s *m;
fp = fopen(fname, "rb");
if(!fp)
return NULL;
fseek(fp, 0, SEEK_END);
filesize = ftell(fp);
fseek(fp, 0, SEEK_SET);
buf = malloc(filesize);
fread(buf, sizeof(char), filesize, fp);
fclose(fp);
m = f3m_mod_load((uint32_t*)buf);
if(!m)
free(buf);
return m;
}
void f3m_mod_free(mod_s *mod)
{
free(mod);
}
static uint16_t f3m_get_para(const uint16_t *p)
{
const uint8_t *p2 = (const uint8_t *)p;
uint16_t v0 = p2[0];
uint16_t v1 = p2[1];
return (v1<<8)|v0;
}
static int32_t f3m_calc_tempo_samples(int32_t tempo)
{
return (F3M_FREQ*10)/(tempo*4);
}
static int32_t f3m_calc_freq(int32_t freq)
{
#if F3M_FREQ == 32768
freq <<= 1;
#else
#if F3M_FREQ == 16384
freq <<= 2;
#else
freq = (freq << 10) / (F3M_FREQ >> 6);
#endif
#endif
return freq;
}
static int32_t f3m_calc_period_freq(int32_t period)
{
int32_t freq = period_amiclk / period;
return f3m_calc_freq(freq);
}
int f3m_set_mono_mode(player_s *player, int onoff)
{
int old = player->monomode;
player->monomode = onoff;
return old;
}
uint16_t f3m_set_max_volume(player_s *player, uint16_t maxvolume)
{
uint16_t old = player->maxvolume;
if(maxvolume > SPU_MAXVOL)
player->maxvolume = SPU_MAXVOL;
else
player->maxvolume = maxvolume;
return old;
}
unsigned int f3m_player_init(player_s *player, mod_s *mod)
{
return f3m_player_init_ex(player, mod, 0, SPU_DATA_BASE_ADDR);
}
unsigned int f3m_player_init_ex(player_s *player, mod_s *mod, int basevoice, unsigned int baseaddr)
{
int i;
// Optional callback
//update_music_status(0, mod->ins_num);
player->mod = mod;
player->modbase = (const void *)mod;
player->ord_list = (const uint8_t *)(((const char *)(mod+1)));
player->ins_para = (const uint16_t *)(((const char *)(mod+1)) + mod->ord_num);
player->pat_para = (const uint16_t *)(((const char *)(mod+1)) + mod->ord_num + mod->ins_num*2);
player->speed = mod->ispeed;
player->tempo = mod->itempo;
player->gvol = mod->gvol;
player->ctick = player->speed;
player->tempo_samples = f3m_calc_tempo_samples(player->tempo);
player->tempo_wait = 0;
player->cord = 0-1;
player->cpat = 0;
player->crow = 64;
player->patptr = NULL;
player->patptr_next = NULL;
player->sfxoffs = 0;
player->ccount = 16;
player->repeat_row = 0;
player->repeat_count = 0;
player->baseaddr = baseaddr;
player->basevoice = basevoice;
player->monomode = 0;
player->maxvolume = SPU_MAXVOL;
for(i = 0; i < F3M_VCHNS; i++)
{
vchn_s *vchn = &(player->vchn[i]);
vchn->spu_data = 0;
vchn->len = 0;
vchn->len_loop = 0;
vchn->freq = 0;
vchn->offs = 0;
vchn->suboffs = 0;
vchn->priority = (i < player->ccount ? F3M_PRIO_MUSIC_OFF : 0);
vchn->gxx_period = 0;
vchn->period = 0;
vchn->insvol = 0;
vchn->midvol = 0;
vchn->outvol = 0;
vchn->pan = ((mod->mvol&0x80)==0)
? 0x8
: (mod->defpanFC == 0xFC
? ((const uint8_t *)(player->pat_para + mod->pat_num))[i] & 0xF
: ((i&1)?0xC:0x3));
vchn->vib_offs = 0;
vchn->tre_offs = 0;
vchn->rtg_count = 0;
vchn->eft = 0;
vchn->efp = 0;
vchn->lefp = 0;
vchn->last_note = 0;
vchn->lins = 0;
vchn->mem_gxx = 0;
vchn->mem_hxx = 0;
vchn->mem_oxx = 0;
}
int j, k;
// load samples
for(i = 0; i < 99; i++)
{
player->psx_spu_offset[i] = 0;
player->psx_spu_offset_lpbeg[i] = 0;
}
uint16_t spu_offs = baseaddr >> 3;
static uint16_t smp_data_buf[8];
static int smp_src_buf[28];
int smp_data_last = 0;
for(i = 0; i < 99 && i < mod->ins_num; i++)
{
//update_music_status(i, mod->ins_num);
// TODO: subtly adjust samples so loops work properly
// Get instrument + check if valid
const ins_s *ins = ((void *)mod) + (((uint32_t)(f3m_get_para(&player->ins_para[i])))*16);
uint32_t para = (((uint32_t)(ins->dat_para_h))<<16)|((uint32_t)(ins->dat_para));
if(ins->len == 0 || para == 0)
continue;
int lpbeg = (((ins->flags & 0x01) != 0) ? ins->lpbeg : ins->len + 64);
int lpend = (((ins->flags & 0x01) != 0) ? ins->lpend+1 : ins->len + 64);
// Ensure the loop actually fires
// Not sure if the assurance is really that good here!
if((ins->flags & 0x01) != 0 && lpend > ((int)ins->len)-14)
lpend = ins->len-14;
const uint8_t *data = ((void *)mod) + (para*16);
player->psx_spu_offset[i] = spu_offs;
player->psx_spu_offset_lpbeg[i] = spu_offs;
for(j = 0; j < 64000 && j < (int)ins->len; j += 28, data += 28, spu_offs += (0x10>>3))
{
// Load data
int src_min = smp_data_last;
int src_max = smp_data_last;
for(k = 0; k < 28; k++)
{
int v = (j+k >= (int)ins->len ? 0 : (((int)(data[k]))-0x80)<<8);
if(v < src_min) src_min = v;
if(v > src_max) src_max = v;
smp_src_buf[k] = v;
}
// Calculate shift
int src_range = src_max - src_min;
int shift = 0;
while(src_range >= 16 && shift < 12)
{
shift++;
src_range >>= 1;
}
// Clear old buffer
for(k = 0; k < 8; k++)
smp_data_buf[k] = 0;
// Set header
// applying filter 1 so we get a delta + soft LPF
smp_data_buf[0] = (12-shift) | (1<<4) | ((0x00)<<8);
if(j+14 >= lpbeg && j-14 < lpbeg)
{
smp_data_buf[0] |= 0x0400;
player->psx_spu_offset_lpbeg[i] = spu_offs;
}
if(j+14 >= lpend && j-14 < lpend)
smp_data_buf[0] |= 0x0300;
// Add data
for(k = 0; k < 28; k++)
{
int v = smp_src_buf[k];
v -= smp_data_last;
v = (v + (1<<(shift-1)))>>shift;
if(v < -8) v = -8;
if(v > 7) v = 7;
smp_data_buf[1+(k>>2)] |= ((v&15)<<((k&3)<<2));
smp_data_last += (v<<shift);
}
// Upload data
SsUpload(smp_data_buf, 16, spu_offs << 3);
}
// Upload silence
smp_data_buf[0] = 0x0500;
for(k = 1; k < 8; k++)
smp_data_buf[k] = 0x0000;
SsUpload(smp_data_buf, 16, spu_offs << 3);
spu_offs += 0x10>>3;
}
return spu_offs << 3;
}
static void f3m_update_outvol(player_s *player, vchn_s *vchn)
{
vchn->outvol = (vchn->midvol * player->gvol) >> 6;
}
static void f3m_player_eff_slide_vol(player_s *player, vchn_s *vchn, int isfirst)
{
(void)player; // "player" is only there to check the fast slide flag (TODO!)
uint8_t lefp = vchn->lefp;
int samt = 0;
if((lefp & 0xF0) == 0x00)
{
if((!isfirst) || lefp == 0x0F) samt = -(lefp & 0x0F);
} else if((lefp & 0x0F) == 0x00) {
if((!isfirst) || lefp == 0xF0) samt = lefp >> 4;
} else if((lefp & 0x0F) == 0x0F) {
if(isfirst) samt = lefp >> 4;
} else if((lefp & 0xF0) == 0xF0) {
if(isfirst) samt = -(lefp & 0x0F);
} else {
// default: slide down on nonzero ticks
// SATELL.s3m relies on this
if(!isfirst) samt = -(lefp & 0x0F);
}
if(samt > 0)
{
vchn->midvol += samt;
if(vchn->midvol > 63) vchn->midvol = 63;
} else if(samt < 0) {
if(vchn->midvol < (uint8_t)-samt) vchn->midvol = 0;
else vchn->midvol += samt;
}
f3m_update_outvol(player, vchn);
}
static void f3m_player_eff_slide_period(vchn_s *vchn, int amt)
{
vchn->period += amt;
vchn->freq = f3m_calc_period_freq(vchn->period);
}
static void f3m_player_eff_vibrato(vchn_s *vchn, int lefp, int shift)
{
vchn->freq = f3m_calc_period_freq(vchn->period);
int vspeed = (lefp>>4);
int vdepth = (lefp&15)<<shift;
// TODO: support other waveforms
// TODO: find rounding + direction
int vval = f3m_sintab[vchn->vib_offs&31];
if(vchn->vib_offs & 32) vval = -vval;
vval *= vdepth;
vval += (1<<(5-1));
vval >>= 5;
vchn->freq = f3m_calc_period_freq(vchn->period + vval);
vchn->vib_offs += vspeed;
}
static void f3m_note_retrig(player_s *player, vchn_s *vchn)
{
int iidx = vchn->lins;
const ins_s *ins = player->modbase + (((uint32_t)(f3m_get_para(&player->ins_para[iidx-1])))*16);
int note = vchn->last_note;
vchn->gxx_period = ((8363 * 16 * period_table[note&15]) / ins->c4freq)
>> (note>>4);
vchn->spu_data = player->psx_spu_offset[iidx-1];
vchn->spu_data_lpbeg = player->psx_spu_offset_lpbeg[iidx-1];
vchn->priority = F3M_PRIO_MUSIC;
vchn->len = (((ins->flags & 0x01) != 0) && ins->lpend < ins->len
? ins->lpend
: ins->len);
vchn->len_loop = (((ins->flags & 0x01) != 0) && ins->lpbeg < ins->len
? vchn->len - ins->lpbeg
: 0);
// TODO: verify if this is the case wrt note-end
if(vchn->spu_data == 0 || (vchn->eft != ('G'-'A'+1) && vchn->eft != ('L'-'A'+1)))
{
vchn->period = vchn->gxx_period;
vchn->freq = f3m_calc_period_freq(vchn->period);
vchn->offs = 0;
if(vchn->eft == ('O'-'A'+1))
{
vchn->eft = 0;
int lefp = (vchn->efp != 0 ? vchn->efp : vchn->mem_oxx);
vchn->mem_oxx = lefp;
lefp <<= 8;
if(lefp < vchn->len)
vchn->offs = lefp;
}
vchn->vib_offs = 0; // TODO: find correct retrig point
vchn->tre_offs = 0; // TODO: find correct retrig point
}
}
static void f3m_jump_to_row(player_s *player, int nrow)
{
int i;
// Ensure in range
// TODO: work out correct behaviour of out of range values
if(nrow < 0 || nrow >= 64)
{
return;
}
// Get patptr
const uint8_t *p = player->modbase + (((uint32_t)(f3m_get_para(&player->pat_para[player->cpat])))*16);
p += 2;
// Walk some number of rows
for(i = 0; i < nrow; i++)
{
for(;;)
{
uint8_t v = *(p++);
if(v == 0) break;
if((v & 0x80) != 0) p += 2;
if((v & 0x40) != 0) p += 1;
if((v & 0x20) != 0) p += 2;
}
}
// Update next patptr + values
player->patptr_next = p;
player->crow = nrow-1;
}
void f3m_effect_nop(player_s *player, vchn_s *vchn, int tick, int pefp, int lefp)
{
(void)player; (void)vchn; (void)tick; (void)pefp; (void)lefp;
}
void f3m_effect_Axx(player_s *player, vchn_s *vchn, int tick, int pefp, int lefp)
{
(void)player; (void)vchn; (void)tick; (void)pefp; (void)lefp;
if(tick == 0 && pefp >= 1)
player->speed = pefp;
}
void f3m_effect_Bxx(player_s *player, vchn_s *vchn, int tick, int pefp, int lefp)
{
(void)player; (void)vchn; (void)tick; (void)pefp; (void)lefp;
if(tick == 0)
{
// TODO: handle Bxx/Cxx combined
player->cord = pefp-1;
player->crow = 64;
}
}
void f3m_effect_Cxx(player_s *player, vchn_s *vchn, int tick, int pefp, int lefp)
{
(void)player; (void)vchn; (void)tick; (void)pefp; (void)lefp;
if(tick == 0)
{
if(player->patptr_next == NULL)
{
// TODO: actually look up the jump value
player->crow = 64;
}
}
}
void f3m_effect_Dxx(player_s *player, vchn_s *vchn, int tick, int pefp, int lefp)
{
(void)player; (void)vchn; (void)tick; (void)pefp; (void)lefp;
f3m_player_eff_slide_vol(player, vchn, tick == 0);
}
void f3m_effect_Exx(player_s *player, vchn_s *vchn, int tick, int pefp, int lefp)
{
(void)player; (void)vchn; (void)tick; (void)pefp; (void)lefp;
if(tick == 0)
{
if(lefp >= 0xF0)
{
f3m_player_eff_slide_period(vchn, ((lefp & 0x0F)<<2));
} else if(lefp >= 0xE0) {
f3m_player_eff_slide_period(vchn, (lefp & 0x0F));
}
} else {
if(lefp < 0xE0)
{
f3m_player_eff_slide_period(vchn, (lefp<<2));
}
}
}
void f3m_effect_Fxx(player_s *player, vchn_s *vchn, int tick, int pefp, int lefp)
{
(void)player; (void)vchn; (void)tick; (void)pefp; (void)lefp;
if(tick == 0)
{
if(lefp >= 0xF0)
{
f3m_player_eff_slide_period(vchn, -((lefp & 0x0F)<<2));
} else if(lefp >= 0xE0) {
f3m_player_eff_slide_period(vchn, -(lefp & 0x0F));
}
} else {
if(lefp < 0xE0)
{
f3m_player_eff_slide_period(vchn, -(lefp<<2));
}
}
}
void f3m_effect_Gxx(player_s *player, vchn_s *vchn, int tick, int pefp, int lefp)
{
(void)player; (void)vchn; (void)tick; (void)pefp; (void)lefp;
if(tick == 0)
{
lefp = (pefp != 0 ? pefp : vchn->mem_gxx);
vchn->mem_gxx = lefp;
} else {
lefp = vchn->mem_gxx;
if(vchn->period < vchn->gxx_period)
{
vchn->period += lefp<<2;
if(vchn->period > vchn->gxx_period)
vchn->period = vchn->gxx_period;
vchn->freq = f3m_calc_period_freq(vchn->period);
} else if(vchn->period > vchn->gxx_period) {
vchn->period -= lefp<<2;
if(vchn->period < vchn->gxx_period)
vchn->period = vchn->gxx_period;
vchn->freq = f3m_calc_period_freq(vchn->period);
}
}
}
void f3m_effect_Hxx(player_s *player, vchn_s *vchn, int tick, int pefp, int lefp)
{
(void)player; (void)vchn; (void)tick; (void)pefp; (void)lefp;
if(tick == 0)
{
lefp = pefp;
if((lefp&0x0F) == 0) lefp |= vchn->mem_hxx&0x0F;
if((lefp&0xF0) == 0) lefp |= vchn->mem_hxx&0xF0;
vchn->mem_hxx = lefp;
} else {
lefp = vchn->mem_hxx;
f3m_player_eff_vibrato(vchn, lefp, 2);
}
}
void f3m_effect_Kxx(player_s *player, vchn_s *vchn, int tick, int pefp, int lefp)
{
(void)player; (void)vchn; (void)tick; (void)pefp; (void)lefp;
if(tick != 0)
{
f3m_effect_Hxx(player, vchn, tick, 0, 0);
f3m_effect_Dxx(player, vchn, tick, pefp, lefp);
}
}
void f3m_effect_Lxx(player_s *player, vchn_s *vchn, int tick, int pefp, int lefp)
{
(void)player; (void)vchn; (void)tick; (void)pefp; (void)lefp;
if(tick != 0)
{
f3m_effect_Gxx(player, vchn, tick, 0, 0);
f3m_effect_Dxx(player, vchn, tick, pefp, lefp);
}
}
void f3m_effect_Qxx(player_s *player, vchn_s *vchn, int tick, int pefp, int lefp)
{
(void)player; (void)vchn; (void)tick; (void)pefp; (void)lefp;
// Notes:
// 1. When effect is not Qxy, rtg_count is reset.
// 2. Current y (from lefp, not special mem) is used as a threshold.
// 3. When y is exceeded, change volume according to current x.
int voldrop = (lefp>>4);
int rtick = (lefp&15);
if(rtick != 0 && vchn->rtg_count >= rtick)
{
// Retrigger
// TODO: work out what happens when we've already done a period or volume slide
// TODO:
f3m_note_retrig(player, vchn);
if(voldrop < 8)
{
if(voldrop < 6)
{
vchn->midvol -= (1<<voldrop);
if(vchn->midvol < 0) vchn->midvol = 0;
} else if(voldrop == 6) {
// *2/3, which according to FC is exactly the same as 5/8
vchn->midvol = (vchn->midvol*5)>>3;
} else {
// *1/2
vchn->midvol = vchn->midvol>>1;
}
} else {
voldrop -= 8;
if(voldrop < 6)
{
vchn->midvol += (1<<voldrop);
} else if(voldrop == 6) {
// *3/2
vchn->midvol = (vchn->midvol*3)>>1;
} else {
// *2
vchn->midvol = vchn->midvol<<1;
}
// XXX: do we deal with the case where volume > 63 before doubling?
if(vchn->midvol > 63) vchn->midvol = 63;
}
vchn->rtg_count = 0;
f3m_update_outvol(player, vchn);
}
vchn->rtg_count++;
}
void f3m_effect_Rxx(player_s *player, vchn_s *vchn, int tick, int pefp, int lefp)
{
(void)player; (void)vchn; (void)tick; (void)pefp; (void)lefp;
// TODO: actual tremolo
if(tick != 0 && vchn->insvol != 0)
{
int vspeed = (lefp>>4);
int vdepth = (lefp&15);
// TODO: support other waveforms
// TODO: find rounding + direction
int vval = f3m_sintab[vchn->tre_offs&31];
if(vchn->tre_offs & 32) vval = -vval;
vval *= vdepth;
vval += (1<<(5-1));
vval >>= 5;
// TODO: get clamp range
vchn->midvol = vchn->insvol + vval;
if(vchn->midvol < 0) vchn->midvol = 0;
if(vchn->midvol > 63) vchn->midvol = 63;
f3m_update_outvol(player, vchn);
vchn->tre_offs += vspeed;
}
}
void f3m_effect_Sxx(player_s *player, vchn_s *vchn, int tick, int pefp, int lefp)
{
(void)player; (void)vchn; (void)tick; (void)pefp; (void)lefp;
switch(lefp>>4)
{
case 0x8:
if(tick == 0)
if((player->mod->mvol&0x80)!=0)
{
vchn->pan = lefp & 0x0F;
}
break;
case 0xB:
// TODO confirm SBx behaviour
if(tick == 0)
{
if((lefp & 0x0F) == 0)
{
player->repeat_row = player->crow;
} else {
if(player->repeat_count == 0)
{
player->repeat_count = lefp & 0x0F;
} else {
player->repeat_count--;
}
if(player->repeat_count != 0)
{
f3m_jump_to_row(player, player->repeat_row);
} else {
player->repeat_row = player->crow + 1;
}
}
}
break;
case 0xC:
// SC0 is ignored
// TODO: Make this work:
// "Playback is temporarily frozen and may be resumed by EFGHJKLU"
if(tick != 0 && (lefp&0x0F) == tick)
{
vchn->spu_data = 0;
vchn->priority = F3M_PRIO_MUSIC_OFF;
vchn->midvol = 0;
f3m_update_outvol(player, vchn);
}
break;
case 0xD:
// TODO confirm SD0 behaviour
if(tick != 0 && (lefp&0x0F) == tick)
{
f3m_note_retrig(player, vchn);
}
break;
}
}
void f3m_effect_Txx(player_s *player, vchn_s *vchn, int tick, int pefp, int lefp)
{
(void)player; (void)vchn; (void)tick; (void)pefp; (void)lefp;
if(pefp >= 33)
{
player->tempo = pefp;
player->tempo_samples = f3m_calc_tempo_samples(player->tempo);
}
}
void f3m_effect_Uxx(player_s *player, vchn_s *vchn, int tick, int pefp, int lefp)
{
(void)player; (void)vchn; (void)tick; (void)pefp; (void)lefp;
if(tick == 0)
{
lefp = pefp;
if((lefp&0x0F) == 0) lefp |= vchn->mem_hxx&0x0F;
if((lefp&0xF0) == 0) lefp |= vchn->mem_hxx&0xF0;
vchn->mem_hxx = lefp;
} else {
lefp = vchn->mem_hxx;
f3m_player_eff_vibrato(vchn, lefp, 0);
}
}
void f3m_effect_Vxx(player_s *player, vchn_s *vchn, int tick, int pefp, int lefp)
{
(void)player; (void)vchn; (void)tick; (void)pefp; (void)lefp;
if(tick != 0)
{
if(pefp >= 0x00 && pefp <= 0x40)
{
player->gvol = lefp;
}
}
}
void (*(f3m_effect_tab[32]))(player_s *player, vchn_s *vchn, int tick, int pefp, int lefp) = {
f3m_effect_nop, f3m_effect_Axx, f3m_effect_Bxx, f3m_effect_Cxx,
f3m_effect_Dxx, f3m_effect_Exx, f3m_effect_Fxx, f3m_effect_Gxx,
f3m_effect_Hxx, f3m_effect_nop, f3m_effect_nop, f3m_effect_Kxx,
f3m_effect_Lxx, f3m_effect_nop, f3m_effect_nop, f3m_effect_nop,
f3m_effect_nop, f3m_effect_Qxx, f3m_effect_Rxx, f3m_effect_Sxx,
f3m_effect_Txx, f3m_effect_Uxx, f3m_effect_Vxx, f3m_effect_nop,
f3m_effect_nop, f3m_effect_nop, f3m_effect_nop, f3m_effect_nop,
f3m_effect_nop, f3m_effect_nop, f3m_effect_nop, f3m_effect_nop,
};
static void f3m_player_play_newnote(player_s *player)
{
int i;
// Advance row
player->crow++;
if(player->crow >= 64)
{
player->crow = 0;
// Advance order
player->cord++;
while(player->cord < player->mod->ord_num && player->ord_list[player->cord] == 0xFE)
player->cord++;
if(player->cord >= player->mod->ord_num || player->ord_list[player->cord] == 0xFF)
player->cord = 0;
while(player->cord < player->mod->ord_num && player->ord_list[player->cord] == 0xFE)
player->cord++;
player->cpat = player->ord_list[player->cord];
// assert(player->cpat < 200);
// assert(player->cpat < player->mod->pat_num);
// Get new pattern pointer
player->patptr = player->modbase + (((uint32_t)(f3m_get_para(&player->pat_para[player->cpat])))*16);
player->patptr += 2;
}
// Clear vchn pattern data
for(i = 0; i < F3M_VCHNS; i++)
{
vchn_s *vchn = &(player->vchn[i]);
vchn->eft = 0x00;
vchn->efp = 0x00;
}
// Read pattern data
if(player->patptr_next != NULL)
{
player->patptr = player->patptr_next;
player->patptr_next = NULL;
}
if(player->patptr == NULL)
return;
const uint8_t *p = player->patptr;
for(;;)
{
uint8_t cv = *(p++);
if(cv == 0) break;
vchn_s *vchn = &(player->vchn[cv&15]); // TODO proper channel map check?
uint8_t pnote = 0xFF;
uint8_t pins = 0x00;
uint8_t pvol = 0xFF;
uint8_t peft = 0x00;
uint8_t pefp = 0x00;
if((cv & 0x20) != 0)
{
pnote = *(p++);
pins = *(p++);
}
if((cv & 0x40) != 0)
{
pvol = *(p++);
}
if((cv & 0x80) != 0)
{
peft = *(p++);
pefp = *(p++);
peft &= 0x1F;
}
vchn->eft = peft;
vchn->efp = pefp;
if(pefp != 0) vchn->lefp = pefp;
uint8_t lefp = vchn->lefp;
// TODO: DO THIS PROPERLY
if(pnote == 0xFE)
{
vchn->spu_data = 0;
vchn->priority = F3M_PRIO_MUSIC_OFF;
} else if((pnote < 0x80 && (pins != 0 || vchn->lins != 0))
|| (pnote == 0xFF && pins != 0)) {
int iidx = (pins == 0 ? vchn->lins : pins);
vchn->lins = iidx;
const ins_s *ins = player->modbase + (((uint32_t)(f3m_get_para(&player->ins_para[iidx-1])))*16);
// TODO: work out correct rounding
if(pvol != 0xFF || pins != 0)
{
vchn->insvol = (pvol != 0xFF ? pvol
: pins != 0 ? ins->vol
: vchn->insvol);
if(vchn->insvol > 63) vchn->insvol = 63; // lesser-known quirk
vchn->midvol = vchn->insvol;
}
// TODO: work out what happens on note end when ins but no note
if(vchn->spu_data == 0 || pnote < 0x80)
{
int note = (pnote < 0x80 ? pnote : vchn->last_note);
vchn->last_note = note;
if(peft != ('S'-'A'+1) || (lefp&0xF0) != 0xD0)
f3m_note_retrig(player, vchn);
}
f3m_update_outvol(player, vchn);
}
if((peft == ('S'-'A'+1) && (lefp&0xF0) == 0xD0) && pnote >= 0x80)
{
// Cancel effect if no note to trigger (e.g. CLICK.S3M)
// TODO: Check if volume column has any effect
vchn->eft = 0;
}
if(pvol < 0x80)
{
vchn->insvol = pvol;
if(vchn->insvol > 63) vchn->insvol = 63;
vchn->midvol = vchn->insvol;
f3m_update_outvol(player, vchn);
}
if(peft != ('Q'-'A'+1))
{
vchn->rtg_count = 0;
}
f3m_effect_tab[peft](player, vchn, 0, pefp, lefp);
}
player->patptr = p;
}
void f3m_player_play_newtick(player_s *player)
{
int i;
player->ctick++;
if(player->ctick >= player->speed)
{
player->ctick = 0;
f3m_player_play_newnote(player);
} else {
for(i = 0; i < F3M_VCHNS; i++)
{
vchn_s *vchn = &(player->vchn[i]);
uint8_t peft = vchn->eft;
uint8_t pefp = vchn->efp;
uint8_t lefp = vchn->lefp;
f3m_effect_tab[peft&31](player, vchn, player->ctick, pefp, lefp);
}
}
}
void f3m_player_play(player_s *player)
{
int i;
const int blen = F3M_BUFLEN;
// Check if we have a new tick
while(player->tempo_wait < 0)
{
f3m_player_play_newtick(player);
player->tempo_wait += player->tempo_samples;
}
player->tempo_wait -= blen;
// TARGET_PSX.
// We need to use hardware channels for this.
uint32_t kon_mask = 0;
for(i = 0; i < F3M_VCHNS; i++)
{
vchn_s *vchn = &(player->vchn[i]);
// Channel enabled?
// TODO: handle note offs properly
if(vchn->spu_data == 0)
{
if((vchn->offs & 1) != 0)
{
vchn->offs &= ~1;
SsKeyOff(i+player->basevoice);
}
continue;
}
// Output sample
uint16_t spu_offs = vchn->spu_data;
uint16_t spu_offs_lpbeg = vchn->spu_data_lpbeg;
int32_t offs = vchn->offs;
int32_t lvol = vchn->outvol<<7;
int32_t rvol = vchn->outvol<<7;
if(vchn->pan < 0x8)
{
lvol = (lvol*(vchn->pan*2+1))/15;
} else {
rvol = (rvol*((15-vchn->pan)*2+1))/15;
}
const int32_t freq = vchn->freq;
if((vchn->offs & 1) == 0)
{
SsVoiceStartAddr(i+player->basevoice, (spu_offs + (((offs+14)/28)<<1)) << 3);
SsVoiceRepeatAddr(i+player->basevoice, spu_offs_lpbeg << 3);
SsVoiceADSRRaw(i+player->basevoice, 0x83FF, 0x9FC0);
kon_mask |= (1<<(i+player->basevoice));
vchn->offs |= 1;
}
if(player->maxvolume != SPU_MAXVOL)
{
lvol = (lvol * player->maxvolume) / SPU_MAXVOL;
rvol = (rvol * player->maxvolume) / SPU_MAXVOL;
}
if(player->monomode)
{
if(lvol > rvol)
rvol = lvol;
else
lvol = rvol;
}
SsVoiceVol(i+player->basevoice, lvol, rvol);
SsVoicePitch(i+player->basevoice, freq>>4);
}
SsKeyOnMask(kon_mask);
}
void f3m_player_stop(player_s *player)
{
int i;
for(i = 0; i < F3M_VCHNS; i++)
{
SsVoiceVol(i+player->basevoice, 0, 0);
SsVoiceADSRRaw(i+player->basevoice, 0, 0);
}
}