7ad2cad173
buffer chunks. * Samples and position indication is closely associated with audio data instead of compensating by a latency constant. Alleviates problems with using the elapsed as a track indicator where it could be off by several steps. * Timing is accurate throughout track even if resampling for pitch shift, whereas before it updated during transition latency at the normal 1:1 rate. * Simpler PCM buffer with a constant chunk size, no linked lists. In converting crossfade, a minor change was made to not change the WPS until the fade-in of the incoming track, whereas before it would change upon the start of the fade-out of the outgoing track possibly having the WPS change with far too much lead time. Codec changes are to set elapsed times *before* writing next PCM frame because time and position data last set are saved in the next committed PCM chunk. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@30366 a1c6a512-1295-4272-9138-f99709370657
1340 lines
42 KiB
C
1340 lines
42 KiB
C
/***************************************************************************
|
||
* __________ __ ___.
|
||
* 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 <inttypes.h>
|
||
|
||
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<len;bp++) {
|
||
#ifdef USE_FILTER
|
||
int outo=0;
|
||
#endif
|
||
int outf=0;
|
||
/* step 2 : generate the two output signals (for filtered and non-
|
||
filtered) from the osc/eg sections */
|
||
for (v=0;v<3;v++) {
|
||
/* update wave counter */
|
||
osc[v].counter = (osc[v].counter+osc[v].freq) & 0xFFFFFFF;
|
||
/* reset counter / noise generator if reset get_bit set */
|
||
if (osc[v].wave & 0x08) {
|
||
osc[v].counter = 0;
|
||
osc[v].noisepos = 0;
|
||
osc[v].noiseval = 0xffffff;
|
||
}
|
||
unsigned char refosc = v?v-1:2; /* reference oscillator for sync/ring */
|
||
/* sync oscillator to refosc if sync bit set */
|
||
if (osc[v].wave & 0x02)
|
||
if (osc[refosc].counter < osc[refosc].freq)
|
||
osc[v].counter = osc[refosc].counter * osc[v].freq / osc[refosc].freq;
|
||
/* generate waveforms with really simple algorithms */
|
||
unsigned char triout = (unsigned char) (osc[v].counter>>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;
|
||
}
|