// made in 2015 by GreaseMonkey - Public Domain // modified in 2016 by nextvolume for inclusion in PSXSDK #include #include #include #include #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<>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)<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<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<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); } }