diff options
| author | Xavi Del Campo <xavi.dcr@tutanota.com> | 2020-01-31 10:32:23 +0100 |
|---|---|---|
| committer | Xavi Del Campo <xavi.dcr@tutanota.com> | 2020-01-31 10:32:23 +0100 |
| commit | 7c24e9a9b02b04dcaf9507acb94091ea70a2c02d (patch) | |
| tree | c28d0748652ad4b4222309e46e6cfc82c0906220 /libf3m | |
| parent | a2b7b6bb1cc2f4a3258b7b2dbc92399d151f864d (diff) | |
| download | psxsdk-7c24e9a9b02b04dcaf9507acb94091ea70a2c02d.tar.gz | |
Imported pristine psxsdk-20190410 from official repo
Diffstat (limited to 'libf3m')
| -rw-r--r-- | libf3m/Makefile | 20 | ||||
| -rwxr-xr-x | libf3m/f3m.c | 1068 | ||||
| -rw-r--r-- | libf3m/f3m.h | 172 |
3 files changed, 1260 insertions, 0 deletions
diff --git a/libf3m/Makefile b/libf3m/Makefile new file mode 100644 index 0000000..4ecd4e7 --- /dev/null +++ b/libf3m/Makefile @@ -0,0 +1,20 @@ +# Makefile for libf3m + +include ../Makefile.cfg + +all: libf3m.a + +f3m.o: f3m.c + $(CC) $(CFLAGS) -c f3m.c + +libf3m.a: f3m.o + rm -f libf3m.a + $(AR) r libf3m.a f3m.o + $(RANLIB) libf3m.a + +install: all + cp libf3m.a $(TOOLCHAIN_PREFIX)/lib + cp f3m.h $(TOOLCHAIN_PREFIX)/include + +clean: + rm -f *.o *.a
\ No newline at end of file diff --git a/libf3m/f3m.c b/libf3m/f3m.c new file mode 100755 index 0000000..a6bcab5 --- /dev/null +++ b/libf3m/f3m.c @@ -0,0 +1,1068 @@ +// 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); + } +} diff --git a/libf3m/f3m.h b/libf3m/f3m.h new file mode 100644 index 0000000..4ae4e35 --- /dev/null +++ b/libf3m/f3m.h @@ -0,0 +1,172 @@ +#ifndef _F3M_H +#define _F3M_H + +#define F3M_FREQ 44100 +#define F3M_BUFLEN 882 +#define F3M_CHNS 2 + +#define F3M_VCHNS 20 +#define F3M_PRIO_NORMAL 50 +#define F3M_PRIO_MUSIC_OFF 100 +#define F3M_PRIO_MUSIC 0x7FFF + +typedef struct +{ + uint8_t name[28]; + uint8_t magic[4]; + uint16_t ord_num, ins_num, pat_num; + uint16_t flags, ver, smptyp; + uint8_t magic_scrm[4]; + uint8_t gvol, ispeed, itempo, mvol; + uint8_t uclick, defpanFC; + uint8_t unused1[8]; + uint16_t special; + uint8_t cset[32]; + uint8_t extra[]; +}__attribute__((__packed__)) mod_s; + +typedef struct +{ + uint16_t spu_data; + uint16_t spu_data_lpbeg; + + int32_t len; + int32_t len_loop; + + int32_t period; + int32_t gxx_period; + + int32_t freq; + int32_t offs; + uint16_t suboffs; + int16_t priority; + + int8_t insvol; // assigned on note start + int8_t midvol; // changed on slides + int8_t outvol; // actual output + uint8_t pan; + + uint8_t vib_offs; + uint8_t tre_offs; + uint8_t rtg_count; + + uint8_t eft, efp, lefp, last_note; + uint8_t lins; + uint8_t mem_gxx, mem_hxx, mem_oxx; +} vchn_s; + +typedef struct +{ + const mod_s *mod; + const void *modbase; + const uint16_t *ins_para; + const uint16_t *pat_para; + const uint8_t *ord_list; + + int32_t speed, tempo; + int32_t gvol; + int32_t ctick, tempo_samples, tempo_wait; + int32_t cord, cpat, crow; + const uint8_t *patptr; + const uint8_t *patptr_next; + int32_t repeat_row, repeat_count; + + int sfxoffs; + int ccount; + + uint16_t psx_spu_offset[99]; + uint16_t psx_spu_offset_lpbeg[99]; + + uint32_t baseaddr; + int basevoice; + int monomode; + uint16_t maxvolume; + + vchn_s vchn[F3M_VCHNS]; +} player_s; + +/** + * Create a module file structure from a data buffer. + * The data buffer must contain a valid S3M music module file. + * @param d Pointer to buffer memory + * @return Module file structure (NULL on error) + */ + +mod_s *f3m_mod_load(uint32_t *d); + +/** + * Create a module file structure from a file with the given filename, + * The file must be a valid S3M music module file. + * @param fname Module file filename + * @return Module file structure (NULL on error) + */ + +mod_s *f3m_mod_load_filename(const char *fname); + +/** + * Free the memory associated to a module file structure + * @param mod Pointer to module file structure + */ + +void f3m_mod_free(mod_s *mod); + +/** + * Initialize a music player from a module file structure. + * This is just like calling f3m_player_init_ex(player, mod, 0, SPU_DATA_BASE_ADDR) + * @param player Pointer to memory of music player structure + * @param mod Pointer to module file structure + * @return Address in SPU memory after uploaded samples + */ + +unsigned int f3m_player_init(player_s *player, mod_s *mod); + +/** + * Initialize a music player from a module file structure. + * @param player Pointer to memory of music player structure + * @param mod Pointer to module file structure + * @param basevoice Base voice to use to reproduce samples. Valid range is from 0 to (24 - F3M_VCHNS) + * @param baseaddr Base address in SPU memory for uploading samples. + * @return Address in SPU memory after uploaded samples + */ + +unsigned int f3m_player_init_ex(player_s *player, mod_s *mod, int basevoice, unsigned int baseaddr); + + +/** + * Make a music player play. + * This should be called roughly every 1/50th of a second on PAL systems, + * or every 1/60th of a second on NTSC systems; you can set a flag in a VBlank + * interrupt handler (see SetVBlankHandler()) to do this. + * This function is probably not re-entrant, so it is not recommended to call it + * directly from the VBlank handler. + * @param player Pointer to music player structure + */ + +void f3m_player_play(player_s *player); + +/** + * Stop a music player. + * @param player Pointer to music player structure + */ + +void f3m_player_stop(player_s *player); + +/** + * Set a music player to mono mode. Music players are by default in stereo mode. + * @param player Pointer to music player structure + * @param onoff Non-zero for mono mode, zero for stereo mode + * @return Previous status of mono mode flag. + */ + +int f3m_set_mono_mode(player_s *player, int onoff); + +/** + * Set maximum volume for a music player. + * Default volume for a music player is the maximum allowed by the SPU. + * @param player Pointer to music player structure + * @return Previous maximum volume. + */ + +uint16_t f3m_set_max_volume(player_s *player, uint16_t maxvolume); + +#endif |
