From f40bfc9267b13b54e6379dfe7539447662879d24 Mon Sep 17 00:00:00 2001 From: Sean Bartell Date: Sat, 25 Jun 2011 21:32:25 -0400 Subject: Add codecs to librbcodec. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change-Id: Id7f4717d51ed02d67cb9f9cb3c0ada4a81843f97 Reviewed-on: http://gerrit.rockbox.org/137 Reviewed-by: Nils Wallménius Tested-by: Nils Wallménius --- lib/rbcodec/codecs/sid.c | 1340 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1340 insertions(+) create mode 100644 lib/rbcodec/codecs/sid.c (limited to 'lib/rbcodec/codecs/sid.c') diff --git a/lib/rbcodec/codecs/sid.c b/lib/rbcodec/codecs/sid.c new file mode 100644 index 0000000000..9b19a20ba7 --- /dev/null +++ b/lib/rbcodec/codecs/sid.c @@ -0,0 +1,1340 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * SID Codec for rockbox based on the TinySID engine + * + * Written by Tammo Hinrichs (kb) and Rainer Sinsch in 1998-1999 + * Ported to rockbox on 14 April 2006 + * + * + * 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + + /***************************** + * kb explicitly points out that this emulation sounds crappy, though + * we decided to put it open source so everyone can enjoy sidmusic + * on rockbox + * + *****************************/ + + /********************* + * v1.1 + * Added 16-04-2006: Rainer Sinsch + * Removed all time critical floating point operations and + * replaced them with quick & dirty integer calculations + * + * Added 17-04-2006: Rainer Sinsch + * Improved quick & dirty integer calculations for the resonant filter + * Improved audio quality by 4 bits + * + * v1.2 + * Added 17-04-2006: Dave Chapman + * Improved file loading + * + * Added 17-04-2006: Rainer Sinsch + * Added sample routines + * Added cia timing routines + * Added fast forwarding capabilities + * Corrected bug in sid loading + * + * v1.2.1 + * Added 04-05-2006: Rainer Sinsch + * Implemented Marco Alanens suggestion for subsong selection: + * Select the subsong by seeking: Each second represents a subsong + * + **************************/ + +#define USE_FILTER + +#include "debug.h" +#include "codeclib.h" +#include + +CODEC_HEADER + +#define CHUNK_SIZE (1024*2) +#define SID_BUFFER_SIZE 0x10000 +#define SAMPLE_RATE 44100 + +/* This codec supports SID Files: + * + */ + +static int32_t samples[CHUNK_SIZE] IBSS_ATTR; /* The sample buffer */ + +void sidPoke(int reg, unsigned char val) ICODE_ATTR; + +#define FLAG_N 128 +#define FLAG_V 64 +#define FLAG_B 16 +#define FLAG_D 8 +#define FLAG_I 4 +#define FLAG_Z 2 +#define FLAG_C 1 + +#define imp 0 +#define imm 1 +#define _abs 2 +#define absx 3 +#define absy 4 +#define zp 6 +#define zpx 7 +#define zpy 8 +#define ind 9 +#define indx 10 +#define indy 11 +#define acc 12 +#define rel 13 + +enum { + adc, _and, asl, bcc, bcs, beq, bit, bmi, bne, bpl, _brk, bvc, bvs, clc, + cld, cli, clv, cmp, cpx, cpy, dec, dex, dey, eor, inc, inx, iny, jmp, + jsr, lda, ldx, ldy, lsr, _nop, ora, pha, php, pla, plp, rol, ror, rti, + rts, sbc, sec, sed, sei, sta, stx, sty, tax, tay, tsx, txa, txs, tya, + xxx +}; + +/* SID register definition */ +struct s6581 { + struct sidvoice { + unsigned short freq; + unsigned short pulse; + unsigned char wave; + unsigned char ad; + unsigned char sr; + } v[3]; + unsigned char ffreqlo; + unsigned char ffreqhi; + unsigned char res_ftv; + unsigned char ftp_vol; +}; + +/* internal oscillator def */ +struct sidosc { + unsigned long freq; + unsigned long pulse; + unsigned char wave; + unsigned char filter; + unsigned long attack; + unsigned long decay; + unsigned long sustain; + unsigned long release; + unsigned long counter; + signed long envval; + unsigned char envphase; + unsigned long noisepos; + unsigned long noiseval; + unsigned char noiseout; +}; + +/* internal filter def */ +struct sidflt { + int freq; + unsigned char l_ena; + unsigned char b_ena; + unsigned char h_ena; + unsigned char v3ena; + int vol; + int rez; + int h; + int b; + int l; +}; + +/* ------------------------ pseudo-constants (depending on mixing freq) */ +int mixing_frequency IDATA_ATTR; +unsigned long freqmul IDATA_ATTR; +int filtmul IDATA_ATTR; +#ifndef ROCKBOX +unsigned long attacks [16] IDATA_ATTR; +unsigned long releases[16] IDATA_ATTR; +#endif + +/* ------------------------------------------------------------ globals */ +struct s6581 sid IDATA_ATTR; +struct sidosc osc[3] IDATA_ATTR; +struct sidflt filter IDATA_ATTR; + +/* ------------------------------------------------------ C64 Emu Stuff */ +unsigned char bval IDATA_ATTR; +unsigned short wval IDATA_ATTR; +/* -------------------------------------------------- Register & memory */ +unsigned char a,x,y,s,p IDATA_ATTR; +unsigned short pc IDATA_ATTR; + +unsigned char memory[65536]; + +/* ----------------------------------------- Variables for sample stuff */ +static int sample_active IDATA_ATTR; +static int sample_position, sample_start, sample_end, sample_repeat_start IDATA_ATTR; +static int fracPos IDATA_ATTR; /* Fractal position of sample */ +static int sample_period IDATA_ATTR; +static int sample_repeats IDATA_ATTR; +static int sample_order IDATA_ATTR; +static int sample_nibble IDATA_ATTR; + +static int internal_period, internal_order, internal_start, internal_end, + internal_add, internal_repeat_times, internal_repeat_start IDATA_ATTR; + +/* ---------------------------------------------------------- constants */ +#ifndef ROCKBOX +static const float attackTimes[16] ICONST_ATTR = +{ + 0.0022528606, 0.0080099577, 0.0157696042, 0.0237795619, + 0.0372963655, 0.0550684591, 0.0668330845, 0.0783473987, + 0.0981219818, 0.244554021, 0.489108042, 0.782472742, + 0.977715461, 2.93364701, 4.88907793, 7.82272493 +}; +static const float decayReleaseTimes[16] ICONST_ATTR = +{ + 0.00891777693, 0.024594051, 0.0484185907, 0.0730116639, 0.114512475, + 0.169078356, 0.205199432, 0.240551975, 0.301266125, 0.750858245, + 1.50171551, 2.40243682, 3.00189298, 9.00721405, 15.010998, 24.0182111 +}; +#else +#define DIV(X) ((int)(0x1000000 / (X * SAMPLE_RATE))) +static const unsigned long attacks[16] ICONST_ATTR = +{ + DIV(0.0022528606), DIV(0.0080099577), DIV(0.0157696042), DIV(0.0237795619), + DIV(0.0372963655), DIV(0.0550684591), DIV(0.0668330845), DIV(0.0783473987), + DIV(0.0981219818), DIV(0.2445540210), DIV(0.4891080420), DIV(0.7824727420), + DIV(0.9777154610), DIV(2.9336470100), DIV(4.8890779300), DIV(7.8227249300) +}; +static const unsigned long releases[16] ICONST_ATTR = +{ + DIV(0.00891777693), DIV(0.0245940510), DIV(0.0484185907), DIV(0.0730116639), + DIV(0.11451247500), DIV(0.1690783560), DIV(0.2051994320), DIV(0.2405519750), + DIV(0.30126612500), DIV(0.7508582450), DIV(1.5017155100), DIV(2.4024368200), + DIV(3.00189298000), DIV(9.0072140500), DIV(15.010998000), DIV(24.018211100) +}; +#endif +static const int opcodes[256] ICONST_ATTR = { + _brk,ora,xxx,xxx,xxx,ora,asl,xxx,php,ora,asl,xxx,xxx,ora,asl,xxx, + bpl,ora,xxx,xxx,xxx,ora,asl,xxx,clc,ora,xxx,xxx,xxx,ora,asl,xxx, + jsr,_and,xxx,xxx,bit,_and,rol,xxx,plp,_and,rol,xxx,bit,_and,rol,xxx, + bmi,_and,xxx,xxx,xxx,_and,rol,xxx,sec,_and,xxx,xxx,xxx,_and,rol,xxx, + rti,eor,xxx,xxx,xxx,eor,lsr,xxx,pha,eor,lsr,xxx,jmp,eor,lsr,xxx, + bvc,eor,xxx,xxx,xxx,eor,lsr,xxx,cli,eor,xxx,xxx,xxx,eor,lsr,xxx, + rts,adc,xxx,xxx,xxx,adc,ror,xxx,pla,adc,ror,xxx,jmp,adc,ror,xxx, + bvs,adc,xxx,xxx,xxx,adc,ror,xxx,sei,adc,xxx,xxx,xxx,adc,ror,xxx, + xxx,sta,xxx,xxx,sty,sta,stx,xxx,dey,xxx,txa,xxx,sty,sta,stx,xxx, + bcc,sta,xxx,xxx,sty,sta,stx,xxx,tya,sta,txs,xxx,xxx,sta,xxx,xxx, + ldy,lda,ldx,xxx,ldy,lda,ldx,xxx,tay,lda,tax,xxx,ldy,lda,ldx,xxx, + bcs,lda,xxx,xxx,ldy,lda,ldx,xxx,clv,lda,tsx,xxx,ldy,lda,ldx,xxx, + cpy,cmp,xxx,xxx,cpy,cmp,dec,xxx,iny,cmp,dex,xxx,cpy,cmp,dec,xxx, + bne,cmp,xxx,xxx,xxx,cmp,dec,xxx,cld,cmp,xxx,xxx,xxx,cmp,dec,xxx, + cpx,sbc,xxx,xxx,cpx,sbc,inc,xxx,inx,sbc,_nop,xxx,cpx,sbc,inc,xxx, + beq,sbc,xxx,xxx,xxx,sbc,inc,xxx,sed,sbc,xxx,xxx,xxx,sbc,inc,xxx +}; + + +static const int modes[256] ICONST_ATTR = { + imp,indx,xxx,xxx,zp,zp,zp,xxx,imp,imm,acc,xxx,_abs,_abs,_abs,xxx, + rel,indy,xxx,xxx,xxx,zpx,zpx,xxx,imp,absy,xxx,xxx,xxx,absx,absx,xxx, + _abs,indx,xxx,xxx,zp,zp,zp,xxx,imp,imm,acc,xxx,_abs,_abs,_abs,xxx, + rel,indy,xxx,xxx,xxx,zpx,zpx,xxx,imp,absy,xxx,xxx,xxx,absx,absx,xxx, + imp,indx,xxx,xxx,zp,zp,zp,xxx,imp,imm,acc,xxx,_abs,_abs,_abs,xxx, + rel,indy,xxx,xxx,xxx,zpx,zpx,xxx,imp,absy,xxx,xxx,xxx,absx,absx,xxx, + imp,indx,xxx,xxx,zp,zp,zp,xxx,imp,imm,acc,xxx,ind,_abs,_abs,xxx, + rel,indy,xxx,xxx,xxx,zpx,zpx,xxx,imp,absy,xxx,xxx,xxx,absx,absx,xxx, + imm,indx,xxx,xxx,zp,zp,zp,xxx,imp,imm,acc,xxx,_abs,_abs,_abs,xxx, + rel,indy,xxx,xxx,zpx,zpx,zpy,xxx,imp,absy,acc,xxx,xxx,absx,absx,xxx, + imm,indx,imm,xxx,zp,zp,zp,xxx,imp,imm,acc,xxx,_abs,_abs,_abs,xxx, + rel,indy,xxx,xxx,zpx,zpx,zpy,xxx,imp,absy,acc,xxx,absx,absx,absy,xxx, + imm,indx,xxx,xxx,zp,zp,zp,xxx,imp,imm,acc,xxx,_abs,_abs,_abs,xxx, + rel,indy,xxx,xxx,zpx,zpx,zpx,xxx,imp,absy,acc,xxx,xxx,absx,absx,xxx, + imm,indx,xxx,xxx,zp,zp,zp,xxx,imp,imm,acc,xxx,_abs,_abs,_abs,xxx, + rel,indy,xxx,xxx,zpx,zpx,zpx,xxx,imp,absy,acc,xxx,xxx,absx,absx,xxx +}; + +/* Routines for quick & dirty float calculation */ + +static inline int quickfloat_ConvertFromInt(int i) +{ + return (i<<16); +} +#ifndef ROCKBOX +static inline int quickfloat_ConvertFromFloat(float f) +{ + return (int)(f*(1<<16)); +} +#else +#define quickfloat_ConvertFromFloat(X) (int)(X*(1<<16)) +#endif +static inline int quickfloat_Multiply(int a, int b) +{ + return (a>>8)*(b>>8); +} +static inline int quickfloat_ConvertToInt(int i) +{ + return (i>>16); +} + +/* Get the bit from an unsigned long at a specified position */ +static inline unsigned char get_bit(unsigned long val, unsigned char b) +{ + return (unsigned char) ((val >> b) & 1); +} + + +static inline int GenerateDigi(int sIn) +{ + static int sample = 0; + + if (!sample_active) return(sIn); + + if ((sample_position < sample_end) && (sample_position >= sample_start)) + { + sIn += sample; + + fracPos += 985248/sample_period; + + if (fracPos > mixing_frequency) + { + fracPos%=mixing_frequency; + + // N�hstes Samples holen + if (sample_order == 0) { + sample_nibble++; // Nähstes Sample-Nibble + if (sample_nibble==2) { + sample_nibble = 0; + sample_position++; + } + } + else { + sample_nibble--; + if (sample_nibble < 0) { + sample_nibble=1; + sample_position++; + } + } + if (sample_repeats) + { + if (sample_position > sample_end) + { + sample_repeats--; + sample_position = sample_repeat_start; + } + else sample_active = 0; + } + + sample = memory[sample_position&0xffff]; + if (sample_nibble==1) // Hi-Nibble holen? + sample = (sample & 0xf0)>>4; + else sample = sample & 0x0f; + + sample -= 7; + sample <<= 10; + } + } + + return (sIn); +} + +/* ------------------------------------------------------------- synthesis + initialize SID and frequency dependant values */ +void synth_init(unsigned long mixfrq) ICODE_ATTR; +void synth_init(unsigned long mixfrq) +{ +#ifndef ROCKBOX + int i; +#endif + mixing_frequency = mixfrq; + fracPos = 0; + freqmul = 15872000 / mixfrq; + filtmul = quickfloat_ConvertFromFloat(21.5332031f)/mixfrq; +#ifndef ROCKBOX + for (i=0;i<16;i++) { + attacks [i]=(int) (0x1000000 / (attackTimes[i]*mixfrq)); + releases[i]=(int) (0x1000000 / (decayReleaseTimes[i]*mixfrq)); + } +#endif + memset(&sid,0,sizeof(sid)); + memset(osc,0,sizeof(osc)); + memset(&filter,0,sizeof(filter)); + osc[0].noiseval = 0xffffff; + osc[1].noiseval = 0xffffff; + osc[2].noiseval = 0xffffff; +} + +/* render a buffer of n samples with the actual register contents */ +void synth_render (int32_t *buffer, unsigned long len) ICODE_ATTR; +void synth_render (int32_t *buffer, unsigned long len) +{ + unsigned long bp; + /* step 1: convert the not easily processable sid registers into some + more convenient and fast values (makes the thing much faster + if you process more than 1 sample value at once) */ + unsigned char v; + for (v=0;v<3;v++) { + osc[v].pulse = (sid.v[v].pulse & 0xfff) << 16; + osc[v].filter = get_bit(sid.res_ftv,v); + osc[v].attack = attacks[sid.v[v].ad >> 4]; + osc[v].decay = releases[sid.v[v].ad & 0xf]; + osc[v].sustain = sid.v[v].sr & 0xf0; + osc[v].release = releases[sid.v[v].sr & 0xf]; + osc[v].wave = sid.v[v].wave; + osc[v].freq = ((unsigned long)sid.v[v].freq)*freqmul; + } + +#ifdef USE_FILTER + filter.freq = (16*sid.ffreqhi + (sid.ffreqlo&0x7)) * filtmul; + + if (filter.freq>quickfloat_ConvertFromInt(1)) + filter.freq=quickfloat_ConvertFromInt(1); + /* the above line isnt correct at all - the problem is that the filter + works only up to rmxfreq/4 - this is sufficient for 44KHz but isnt + for 32KHz and lower - well, but sound quality is bad enough then to + neglect the fact that the filter doesnt come that high ;) */ + filter.l_ena = get_bit(sid.ftp_vol,4); + filter.b_ena = get_bit(sid.ftp_vol,5); + filter.h_ena = get_bit(sid.ftp_vol,6); + filter.v3ena = !get_bit(sid.ftp_vol,7); + filter.vol = (sid.ftp_vol & 0xf); + filter.rez = quickfloat_ConvertFromFloat(1.2f) - + quickfloat_ConvertFromFloat(0.04f)*(sid.res_ftv >> 4); + + /* We precalculate part of the quick float operation, saves time in loop later */ + filter.rez>>=8; +#endif + + + /* now render the buffer */ + for (bp=0;bp>19); + if (osc[v].counter>>27) + triout^=0xff; + unsigned char sawout = (unsigned char) (osc[v].counter >> 20); + unsigned char plsout = (unsigned char) ((osc[v].counter > osc[v].pulse)-1); + + /* generate noise waveform exactly as the SID does. */ + if (osc[v].noisepos!=(osc[v].counter>>23)) + { + osc[v].noisepos = osc[v].counter >> 23; + osc[v].noiseval = (osc[v].noiseval << 1) | + (get_bit(osc[v].noiseval,22) ^ get_bit(osc[v].noiseval,17)); + osc[v].noiseout = (get_bit(osc[v].noiseval,22) << 7) | + (get_bit(osc[v].noiseval,20) << 6) | + (get_bit(osc[v].noiseval,16) << 5) | + (get_bit(osc[v].noiseval,13) << 4) | + (get_bit(osc[v].noiseval,11) << 3) | + (get_bit(osc[v].noiseval, 7) << 2) | + (get_bit(osc[v].noiseval, 4) << 1) | + (get_bit(osc[v].noiseval, 2) << 0); + } + unsigned char nseout = osc[v].noiseout; + + /* modulate triangle wave if ringmod bit set */ + if (osc[v].wave & 0x04) + if (osc[refosc].counter < 0x8000000) + triout ^= 0xff; + + /* now mix the oscillators with an AND operation as stated in + the SID's reference manual - even if this is completely wrong. + well, at least, the $30 and $70 waveform sounds correct and there's + no real solution to do $50 and $60, so who cares. */ + + unsigned char outv=0xFF; + if (osc[v].wave & 0x10) outv &= triout; + if (osc[v].wave & 0x20) outv &= sawout; + if (osc[v].wave & 0x40) outv &= plsout; + if (osc[v].wave & 0x80) outv &= nseout; + + /* so now process the volume according to the phase and adsr values */ + switch (osc[v].envphase) { + case 0 : { /* Phase 0 : Attack */ + osc[v].envval+=osc[v].attack; + if (osc[v].envval >= 0xFFFFFF) + { + osc[v].envval = 0xFFFFFF; + osc[v].envphase = 1; + } + break; + } + case 1 : { /* Phase 1 : Decay */ + osc[v].envval-=osc[v].decay; + if ((signed int) osc[v].envval <= (signed int) (osc[v].sustain<<16)) + { + osc[v].envval = osc[v].sustain<<16; + osc[v].envphase = 2; + } + break; + } + case 2 : { /* Phase 2 : Sustain */ + if ((signed int) osc[v].envval != (signed int) (osc[v].sustain<<16)) + { + osc[v].envphase = 1; + } + /* :) yes, thats exactly how the SID works. and maybe + a music routine out there supports this, so better + let it in, thanks :) */ + break; + } + case 3 : { /* Phase 3 : Release */ + osc[v].envval-=osc[v].release; + if (osc[v].envval < 0x40000) osc[v].envval= 0x40000; + + /* the volume offset is because the SID does not + completely silence the voices when it should. most + emulators do so though and thats the main reason + why the sound of emulators is too, err... emulated :) */ + break; + } + } + +#ifdef USE_FILTER + + /* now route the voice output to either the non-filtered or the + filtered channel and dont forget to blank out osc3 if desired */ + + if (v<2 || filter.v3ena) + { + if (osc[v].filter) + outf+=(((int)(outv-0x80))*osc[v].envval)>>22; + else + outo+=(((int)(outv-0x80))*osc[v].envval)>>22; + } +#endif +#ifndef USE_FILTER + /* Don't use filters, just mix all voices together */ + outf+=((signed short)(outv-0x80)) * (osc[v].envval>>4); +#endif + } + + +#ifdef USE_FILTER + /* step 3 + * so, now theres finally time to apply the multi-mode resonant filter + * to the signal. The easiest thing ist just modelling a real electronic + * filter circuit instead of fiddling around with complex IIRs or even + * FIRs ... + * it sounds as good as them or maybe better and needs only 3 MULs and + * 4 ADDs for EVERYTHING. SIDPlay uses this kind of filter, too, but + * Mage messed the whole thing completely up - as the rest of the + * emulator. + * This filter sounds a lot like the 8580, as the low-quality, dirty + * sound of the 6581 is uuh too hard to achieve :) */ + + filter.h = quickfloat_ConvertFromInt(outf) - (filter.b>>8)*filter.rez - filter.l; + filter.b += quickfloat_Multiply(filter.freq, filter.h); + filter.l += quickfloat_Multiply(filter.freq, filter.b); + + outf = 0; + + if (filter.l_ena) outf+=quickfloat_ConvertToInt(filter.l); + if (filter.b_ena) outf+=quickfloat_ConvertToInt(filter.b); + if (filter.h_ena) outf+=quickfloat_ConvertToInt(filter.h); + + int final_sample = (filter.vol*(outo+outf)); + *(buffer+bp)= GenerateDigi(final_sample)<<13; +#endif +#ifndef USE_FILTER + *(buffer+bp) = GenerateDigi(outf)<<3; +#endif + } +} + + + +/* +* C64 Mem Routines +*/ +static inline unsigned char getmem(unsigned short addr) +{ + return memory[addr]; +} + +static inline void setmem(unsigned short addr, unsigned char value) +{ + if ((addr&0xfc00)==0xd400) + { + sidPoke(addr&0x1f,value); + /* New SID-Register */ + if (addr > 0xd418) + { + switch (addr) + { + case 0xd41f: /* Start-Hi */ + internal_start = (internal_start&0x00ff) | (value<<8); break; + case 0xd41e: /* Start-Lo */ + internal_start = (internal_start&0xff00) | (value); break; + case 0xd47f: /* Repeat-Hi */ + internal_repeat_start = (internal_repeat_start&0x00ff) | (value<<8); break; + case 0xd47e: /* Repeat-Lo */ + internal_repeat_start = (internal_repeat_start&0xff00) | (value); break; + case 0xd43e: /* End-Hi */ + internal_end = (internal_end&0x00ff) | (value<<8); break; + case 0xd43d: /* End-Lo */ + internal_end = (internal_end&0xff00) | (value); break; + case 0xd43f: /* Loop-Size */ + internal_repeat_times = value; break; + case 0xd45e: /* Period-Hi */ + internal_period = (internal_period&0x00ff) | (value<<8); break; + case 0xd45d: /* Period-Lo */ + internal_period = (internal_period&0xff00) | (value); break; + case 0xd47d: /* Sample Order */ + internal_order = value; break; + case 0xd45f: /* Sample Add */ + internal_add = value; break; + case 0xd41d: /* Start sampling */ + sample_repeats = internal_repeat_times; + sample_position = internal_start; + sample_start = internal_start; + sample_end = internal_end; + sample_repeat_start = internal_repeat_start; + sample_period = internal_period; + sample_order = internal_order; + switch (value) + { + case 0xfd: sample_active = 0; break; + case 0xfe: + case 0xff: sample_active = 1; break; + default: return; + } + break; + } + } + } + else memory[addr]=value; +} + +/* +* Poke a value into the sid register +*/ +void sidPoke(int reg, unsigned char val) ICODE_ATTR; +void sidPoke(int reg, unsigned char val) +{ + int voice=0; + + if ((reg >= 7) && (reg <=13)) {voice=1; reg-=7;} + else if ((reg >= 14) && (reg <=20)) {voice=2; reg-=14;} + + switch (reg) { + case 0: { /* Set frequency: Low byte */ + sid.v[voice].freq = (sid.v[voice].freq&0xff00)+val; + break; + } + case 1: { /* Set frequency: High byte */ + sid.v[voice].freq = (sid.v[voice].freq&0xff)+(val<<8); + break; + } + case 2: { /* Set pulse width: Low byte */ + sid.v[voice].pulse = (sid.v[voice].pulse&0xff00)+val; + break; + } + case 3: { /* Set pulse width: High byte */ + sid.v[voice].pulse = (sid.v[voice].pulse&0xff)+(val<<8); + break; + } + case 4: { sid.v[voice].wave = val; + /* Directly look at GATE-Bit! + * a change may happen twice or more often during one cpujsr + * Put the Envelope Generator into attack or release phase if desired + */ + if ((val & 0x01) == 0) osc[voice].envphase=3; + else if (osc[voice].envphase==3) osc[voice].envphase=0; + break; + } + + case 5: { sid.v[voice].ad = val; break;} + case 6: { sid.v[voice].sr = val; break;} + + case 21: { sid.ffreqlo = val; break; } + case 22: { sid.ffreqhi = val; break; } + case 23: { sid.res_ftv = val; break; } + case 24: { sid.ftp_vol = val; break;} + } + return; +} + +static inline unsigned char getaddr(int mode) +{ + unsigned short ad,ad2; + switch(mode) + { + case imp: + return 0; + case imm: + return getmem(pc++); + case _abs: + ad=getmem(pc++); + ad|=256*getmem(pc++); + return getmem(ad); + case absx: + ad=getmem(pc++); + ad|=256*getmem(pc++); + ad2=ad+x; + return getmem(ad2); + case absy: + ad=getmem(pc++); + ad|=256*getmem(pc++); + ad2=ad+y; + return getmem(ad2); + case zp: + ad=getmem(pc++); + return getmem(ad); + case zpx: + ad=getmem(pc++); + ad+=x; + return getmem(ad&0xff); + case zpy: + ad=getmem(pc++); + ad+=y; + return getmem(ad&0xff); + case indx: + ad=getmem(pc++); + ad+=x; + ad2=getmem(ad&0xff); + ad++; + ad2|=getmem(ad&0xff)<<8; + return getmem(ad2); + case indy: + ad=getmem(pc++); + ad2=getmem(ad); + ad2|=getmem((ad+1)&0xff)<<8; + ad=ad2+y; + return getmem(ad); + case acc: + return a; + } + return 0; +} + +static inline void setaddr(int mode, unsigned char val) +{ + unsigned short ad,ad2; + switch(mode) + { + case _abs: + ad=getmem(pc-2); + ad|=256*getmem(pc-1); + setmem(ad,val); + return; + case absx: + ad=getmem(pc-2); + ad|=256*getmem(pc-1); + ad2=ad+x; + setmem(ad2,val); + return; + case zp: + ad=getmem(pc-1); + setmem(ad,val); + return; + case zpx: + ad=getmem(pc-1); + ad+=x; + setmem(ad&0xff,val); + return; + case acc: + a=val; + return; + } +} + + +static inline void putaddr(int mode, unsigned char val) +{ + unsigned short ad,ad2; + switch(mode) + { + case _abs: + ad=getmem(pc++); + ad|=getmem(pc++)<<8; + setmem(ad,val); + return; + case absx: + ad=getmem(pc++); + ad|=getmem(pc++)<<8; + ad2=ad+x; + setmem(ad2,val); + return; + case absy: + ad=getmem(pc++); + ad|=getmem(pc++)<<8; + ad2=ad+y; + setmem(ad2,val); + return; + case zp: + ad=getmem(pc++); + setmem(ad,val); + return; + case zpx: + ad=getmem(pc++); + ad+=x; + setmem(ad&0xff,val); + return; + case zpy: + ad=getmem(pc++); + ad+=y; + setmem(ad&0xff,val); + return; + case indx: + ad=getmem(pc++); + ad+=x; + ad2=getmem(ad&0xff); + ad++; + ad2|=getmem(ad&0xff)<<8; + setmem(ad2,val); + return; + case indy: + ad=getmem(pc++); + ad2=getmem(ad); + ad2|=getmem((ad+1)&0xff)<<8; + ad=ad2+y; + setmem(ad,val); + return; + case acc: + a=val; + return; + } +} + + +static inline void setflags(int flag, int cond) +{ + if (cond) p|=flag; + else p&=~flag; +} + + +static inline void push(unsigned char val) +{ + setmem(0x100+s,val); + if (s) s--; +} + +static inline unsigned char pop(void) +{ + if (s<0xff) s++; + return getmem(0x100+s); +} + +static inline void branch(int flag) +{ + signed char dist; + dist=(signed char)getaddr(imm); + wval=pc+dist; + if (flag) pc=wval; +} + +void cpuReset(void) ICODE_ATTR; +void cpuReset(void) +{ + a=x=y=0; + p=0; + s=255; + pc=getaddr(0xfffc); +} + +void cpuResetTo(unsigned short npc, unsigned char na) ICODE_ATTR; +void cpuResetTo(unsigned short npc, unsigned char na) +{ + a=na; + x=0; + y=0; + p=0; + s=255; + pc=npc; +} + +static inline void cpuParse(void) +{ + unsigned char opc=getmem(pc++); + int cmd=opcodes[opc]; + int addr=modes[opc]; + int c; + switch (cmd) + { + case adc: + wval=(unsigned short)a+getaddr(addr)+((p&FLAG_C)?1:0); + setflags(FLAG_C, wval&0x100); + a=(unsigned char)wval; + setflags(FLAG_Z, !a); + setflags(FLAG_N, a&0x80); + setflags(FLAG_V, (!!(p&FLAG_C)) ^ (!!(p&FLAG_N))); + break; + case _and: + bval=getaddr(addr); + a&=bval; + setflags(FLAG_Z, !a); + setflags(FLAG_N, a&0x80); + break; + case asl: + wval=getaddr(addr); + wval<<=1; + setaddr(addr,(unsigned char)wval); + setflags(FLAG_Z,!wval); + setflags(FLAG_N,wval&0x80); + setflags(FLAG_C,wval&0x100); + break; + case bcc: + branch(!(p&FLAG_C)); + break; + case bcs: + branch(p&FLAG_C); + break; + case bne: + branch(!(p&FLAG_Z)); + break; + case beq: + branch(p&FLAG_Z); + break; + case bpl: + branch(!(p&FLAG_N)); + break; + case bmi: + branch(p&FLAG_N); + break; + case bvc: + branch(!(p&FLAG_V)); + break; + case bvs: + branch(p&FLAG_V); + break; + case bit: + bval=getaddr(addr); + setflags(FLAG_Z,!(a&bval)); + setflags(FLAG_N,bval&0x80); + setflags(FLAG_V,bval&0x40); + break; + case _brk: + pc=0; /* Just quit the emulation */ + break; + case clc: + setflags(FLAG_C,0); + break; + case cld: + setflags(FLAG_D,0); + break; + case cli: + setflags(FLAG_I,0); + break; + case clv: + setflags(FLAG_V,0); + break; + case cmp: + bval=getaddr(addr); + wval=(unsigned short)a-bval; + setflags(FLAG_Z,!wval); + setflags(FLAG_N,wval&0x80); + setflags(FLAG_C,a>=bval); + break; + case cpx: + bval=getaddr(addr); + wval=(unsigned short)x-bval; + setflags(FLAG_Z,!wval); + setflags(FLAG_N,wval&0x80); + setflags(FLAG_C,x>=bval); + break; + case cpy: + bval=getaddr(addr); + wval=(unsigned short)y-bval; + setflags(FLAG_Z,!wval); + setflags(FLAG_N,wval&0x80); + setflags(FLAG_C,y>=bval); + break; + case dec: + bval=getaddr(addr); + bval--; + setaddr(addr,bval); + setflags(FLAG_Z,!bval); + setflags(FLAG_N,bval&0x80); + break; + case dex: + x--; + setflags(FLAG_Z,!x); + setflags(FLAG_N,x&0x80); + break; + case dey: + y--; + setflags(FLAG_Z,!y); + setflags(FLAG_N,y&0x80); + break; + case eor: + bval=getaddr(addr); + a^=bval; + setflags(FLAG_Z,!a); + setflags(FLAG_N,a&0x80); + break; + case inc: + bval=getaddr(addr); + bval++; + setaddr(addr,bval); + setflags(FLAG_Z,!bval); + setflags(FLAG_N,bval&0x80); + break; + case inx: + x++; + setflags(FLAG_Z,!x); + setflags(FLAG_N,x&0x80); + break; + case iny: + y++; + setflags(FLAG_Z,!y); + setflags(FLAG_N,y&0x80); + break; + case jmp: + wval=getmem(pc++); + wval|=256*getmem(pc++); + switch (addr) + { + case _abs: + pc=wval; + break; + case ind: + pc=getmem(wval); + pc|=256*getmem(wval+1); + break; + } + break; + case jsr: + push((pc+1)>>8); + push((pc+1)); + wval=getmem(pc++); + wval|=256*getmem(pc++); + pc=wval; + break; + case lda: + a=getaddr(addr); + setflags(FLAG_Z,!a); + setflags(FLAG_N,a&0x80); + break; + case ldx: + x=getaddr(addr); + setflags(FLAG_Z,!x); + setflags(FLAG_N,x&0x80); + break; + case ldy: + y=getaddr(addr); + setflags(FLAG_Z,!y); + setflags(FLAG_N,y&0x80); + break; + case lsr: + bval=getaddr(addr); wval=(unsigned char)bval; + wval>>=1; + setaddr(addr,(unsigned char)wval); + setflags(FLAG_Z,!wval); + setflags(FLAG_N,wval&0x80); + setflags(FLAG_C,bval&1); + break; + case _nop: + break; + case ora: + bval=getaddr(addr); + a|=bval; + setflags(FLAG_Z,!a); + setflags(FLAG_N,a&0x80); + break; + case pha: + push(a); + break; + case php: + push(p); + break; + case pla: + a=pop(); + setflags(FLAG_Z,!a); + setflags(FLAG_N,a&0x80); + break; + case plp: + p=pop(); + break; + case rol: + bval=getaddr(addr); + c=!!(p&FLAG_C); + setflags(FLAG_C,bval&0x80); + bval<<=1; + bval|=c; + setaddr(addr,bval); + setflags(FLAG_N,bval&0x80); + setflags(FLAG_Z,!bval); + break; + case ror: + bval=getaddr(addr); + c=!!(p&FLAG_C); + setflags(FLAG_C,bval&1); + bval>>=1; + bval|=128*c; + setaddr(addr,bval); + setflags(FLAG_N,bval&0x80); + setflags(FLAG_Z,!bval); + break; + case rti: + /* Treat RTI like RTS */ + case rts: + wval=pop(); + wval|=pop()<<8; + pc=wval+1; + break; + case sbc: + bval=getaddr(addr)^0xff; + wval=(unsigned short)a+bval+((p&FLAG_C)?1:0); + setflags(FLAG_C, wval&0x100); + a=(unsigned char)wval; + setflags(FLAG_Z, !a); + setflags(FLAG_N, a>127); + setflags(FLAG_V, (!!(p&FLAG_C)) ^ (!!(p&FLAG_N))); + break; + case sec: + setflags(FLAG_C,1); + break; + case sed: + setflags(FLAG_D,1); + break; + case sei: + setflags(FLAG_I,1); + break; + case sta: + putaddr(addr,a); + break; + case stx: + putaddr(addr,x); + break; + case sty: + putaddr(addr,y); + break; + case tax: + x=a; + setflags(FLAG_Z, !x); + setflags(FLAG_N, x&0x80); + break; + case tay: + y=a; + setflags(FLAG_Z, !y); + setflags(FLAG_N, y&0x80); + break; + case tsx: + x=s; + setflags(FLAG_Z, !x); + setflags(FLAG_N, x&0x80); + break; + case txa: + a=x; + setflags(FLAG_Z, !a); + setflags(FLAG_N, a&0x80); + break; + case txs: + s=x; + break; + case tya: + a=y; + setflags(FLAG_Z, !a); + setflags(FLAG_N, a&0x80); + break; + } +} + +void cpuJSR(unsigned short npc, unsigned char na) ICODE_ATTR; +void cpuJSR(unsigned short npc, unsigned char na) +{ + a=na; + x=0; + y=0; + p=0; + s=255; + pc=npc; + push(0); + push(0); + + while (pc > 1) + cpuParse(); + +} + +void c64Init(int nSampleRate) ICODE_ATTR; +void c64Init(int nSampleRate) +{ + synth_init(nSampleRate); + memset(memory, 0, sizeof(memory)); + + cpuReset(); +} + + + +unsigned short LoadSIDFromMemory(void *pSidData, unsigned short *load_addr, + unsigned short *init_addr, unsigned short *play_addr, unsigned char *subsongs, unsigned char *startsong, unsigned char *speed, unsigned short size) ICODE_ATTR; +unsigned short LoadSIDFromMemory(void *pSidData, unsigned short *load_addr, + unsigned short *init_addr, unsigned short *play_addr, unsigned char *subsongs, unsigned char *startsong, unsigned char *speed, unsigned short size) +{ + unsigned char *pData; + unsigned char data_file_offset; + + pData = (unsigned char*)pSidData; + data_file_offset = pData[7]; + + *load_addr = pData[8]<<8; + *load_addr|= pData[9]; + + *init_addr = pData[10]<<8; + *init_addr|= pData[11]; + + *play_addr = pData[12]<<8; + *play_addr|= pData[13]; + + *subsongs = pData[0xf]-1; + *startsong = pData[0x11]-1; + + *load_addr = pData[data_file_offset]; + *load_addr|= pData[data_file_offset+1]<<8; + + *speed = pData[0x15]; + + memset(memory, 0, sizeof(memory)); + memcpy(&memory[*load_addr], &pData[data_file_offset+2], size-(data_file_offset+2)); + + if (*play_addr == 0) + { + cpuJSR(*init_addr, 0); + *play_addr = (memory[0x0315]<<8)+memory[0x0314]; + } + + return *load_addr; +} + +static int nSamplesRendered = 0; +static int nSamplesPerCall = 882; /* This is PAL SID single speed (44100/50Hz) */ +static int nSamplesToRender = 0; + +/* this is the codec entry point */ +enum codec_status codec_main(enum codec_entry_call_reason reason) +{ + if (reason == CODEC_LOAD) { + /* Make use of 44.1khz */ + ci->configure(DSP_SWITCH_FREQUENCY, SAMPLE_RATE); + /* Sample depth is 28 bit host endian */ + ci->configure(DSP_SET_SAMPLE_DEPTH, 28); + /* Mono output */ + ci->configure(DSP_SET_STEREO_MODE, STEREO_MONO); + } + + return CODEC_OK; +} + +/* this is called for each file to process */ +enum codec_status codec_run(void) +{ + size_t filesize; + unsigned short load_addr, init_addr, play_addr; + unsigned char subSongsMax, subSong, song_speed; + unsigned char *sidfile = NULL; + intptr_t param; + + if (codec_init()) { + return CODEC_ERROR; + } + + codec_set_replaygain(ci->id3); + + /* Load SID file the read_filebuf callback will return the full requested + * size if at all possible, so there is no need to loop */ + ci->seek_buffer(0); + sidfile = ci->request_buffer(&filesize, SID_BUFFER_SIZE); + + if (filesize == 0) { + return CODEC_ERROR; + } + + c64Init(SAMPLE_RATE); + LoadSIDFromMemory(sidfile, &load_addr, &init_addr, &play_addr, + &subSongsMax, &subSong, &song_speed, (unsigned short)filesize); + sidPoke(24, 15); /* Turn on full volume */ + cpuJSR(init_addr, subSong); /* Start the song initialize */ + + + /* Set the elapsed time to the current subsong (in seconds) */ + ci->set_elapsed(subSong*1000); + + /* The main decoder loop */ + while (1) { + enum codec_command_action action = ci->get_command(¶m); + + if (action == CODEC_ACTION_HALT) + break; + + if (action == CODEC_ACTION_SEEK_TIME) { + /* New time is ready in param */ + + /* Start playing from scratch */ + c64Init(SAMPLE_RATE); + LoadSIDFromMemory(sidfile, &load_addr, &init_addr, &play_addr, + &subSongsMax, &subSong, &song_speed, (unsigned short)filesize); + sidPoke(24, 15); /* Turn on full volume */ + subSong = param / 1000; /* Now use the current seek time in seconds as subsong */ + cpuJSR(init_addr, subSong); /* Start the song initialize */ + nSamplesToRender = 0; /* Start the rendering from scratch */ + + /* Set the elapsed time to the current subsong (in seconds) */ + ci->set_elapsed(subSong*1000); + ci->seek_complete(); + } + + nSamplesRendered = 0; + while (nSamplesRendered < CHUNK_SIZE) + { + if (nSamplesToRender == 0) + { + cpuJSR(play_addr, 0); + + /* Find out if cia timing is used and how many samples + have to be calculated for each cpujsr */ + int nRefreshCIA = (int)(20000*(memory[0xdc04]|(memory[0xdc05]<<8))/0x4c00); + if ((nRefreshCIA==0) || (song_speed == 0)) + nRefreshCIA = 20000; + nSamplesPerCall = mixing_frequency*nRefreshCIA/1000000; + + nSamplesToRender = nSamplesPerCall; + } + if (nSamplesRendered + nSamplesToRender > CHUNK_SIZE) + { + synth_render(samples+nSamplesRendered, CHUNK_SIZE-nSamplesRendered); + nSamplesToRender -= CHUNK_SIZE-nSamplesRendered; + nSamplesRendered = CHUNK_SIZE; + } + else + { + synth_render(samples+nSamplesRendered, nSamplesToRender); + nSamplesRendered += nSamplesToRender; + nSamplesToRender = 0; + } + } + + ci->pcmbuf_insert(samples, NULL, CHUNK_SIZE); + } + + return CODEC_OK; +} -- cgit v1.2.3