179 lines
6.2 KiB
C++
179 lines
6.2 KiB
C++
// ========================= Paula Class (Paula Emulator) =======================
|
|
|
|
class Paula
|
|
{
|
|
public:
|
|
static const sInt FIR_WIDTH = 512; // Finite Impulse Response (FIR) filter width
|
|
sF32 FIRMem[2 * FIR_WIDTH + 1]; // FIR memory (1025), one dimensional array
|
|
|
|
struct Voice // Start Voice Structure
|
|
{
|
|
private:
|
|
sInt Pos; // position ?
|
|
sInt PWMCnt, DivCnt; // Pulse Width Modulation, pwm division count?
|
|
sIntFlt Cur; // current ?
|
|
|
|
public:
|
|
sS8 *Sample; // audio channel data (sample) location
|
|
sInt SampleLen; // audio channel data (sample) length
|
|
sInt LoopLen; // loop length
|
|
sInt Period; // 124 .. 65535 (audio channel period (rate))
|
|
sInt Volume; // 0 .. 64 AUDxVOL
|
|
|
|
Voice ()
|
|
: Period (65535), Volume (0), Sample (0), Pos (0), PWMCnt (0), DivCnt (0), LoopLen (1)
|
|
{
|
|
Cur.F32 = 0;
|
|
} // voice constructor ( initailization list - zero everything)
|
|
|
|
public:
|
|
void Render (sF32 *buffer, sInt samples) // define render function
|
|
{
|
|
if (!Sample) // return if no samples... i think
|
|
return;
|
|
|
|
sU8 *smp = (sU8 *)Sample;
|
|
for (sInt i = 0; i < samples; i++)
|
|
{
|
|
if (!DivCnt)
|
|
{ // todo: use a fake d/a table for this
|
|
Cur.U32 = ((smp[Pos] ^ 0x80) << 15) | 0x40000000; // smp[pos] XOR 0x80 << 15 OR 4000 0000
|
|
Cur.F32 -= 3.0f;
|
|
if (++Pos == SampleLen)
|
|
Pos -= LoopLen;
|
|
DivCnt = Period;
|
|
}
|
|
if (PWMCnt < Volume)
|
|
buffer[i] += Cur.F32; // PWM counter
|
|
PWMCnt = (PWMCnt + 1) & 0x3f; // 0x3f = 63
|
|
DivCnt--;
|
|
}
|
|
} // end render function
|
|
public:
|
|
void Trigger (sS8 *smp, sInt sl, sInt ll, sInt offs = 0) // define trigger function (trigger voice data)
|
|
{
|
|
Sample = smp; // sample
|
|
SampleLen = sl; // sample length
|
|
LoopLen = ll; // looplength
|
|
Pos = sMin (offs, SampleLen - 1); // offset
|
|
} // end trigger function
|
|
// }; // end voice structure
|
|
// Voice V[4];
|
|
} V[4]; // create array of instance of voice structure
|
|
|
|
// --
|
|
// rendering in paula freq
|
|
static const sInt RBSIZE = 4096; // ring buffer (aka circular buffer) size
|
|
sF32 RingBuf[2 * RBSIZE];
|
|
sInt WritePos; // write position
|
|
sInt ReadPos; // read position
|
|
sF32 ReadFrac; // fraction?
|
|
public:
|
|
void CalcFrag (sF32 *out, sInt samples) // i believe this function transfers
|
|
// samples into ring buffer
|
|
{
|
|
sZeroMem (out, sizeof (sF32) * samples); // zero-out mem
|
|
sZeroMem (out + RBSIZE, sizeof (sF32) * samples);
|
|
for (sInt i = 0; i < 4; i++) // four voices(0 - 3)
|
|
{
|
|
if (i == 1 || i == 2)
|
|
V[i].Render (out + RBSIZE, samples);
|
|
else
|
|
V[i].Render (out, samples);
|
|
}
|
|
}
|
|
|
|
// =================================== Calc
|
|
public:
|
|
void Calc ()
|
|
{
|
|
sInt RealReadPos = ReadPos - FIR_WIDTH - 1;
|
|
sInt samples = (RealReadPos - WritePos) & (RBSIZE - 1);
|
|
|
|
sInt todo = sMin (samples, RBSIZE - WritePos);
|
|
CalcFrag (RingBuf + WritePos, todo);
|
|
if (todo < samples)
|
|
{
|
|
WritePos = 0;
|
|
todo = samples - todo;
|
|
CalcFrag (RingBuf, todo);
|
|
}
|
|
WritePos += todo;
|
|
}; // Calc end
|
|
|
|
// =================== rendering in output freq P->Render
|
|
public:
|
|
sF32 MasterVolume; // master volume
|
|
sF32 MasterSeparation; // master stereo separation
|
|
|
|
void Render (sF32 *outbuf, sInt samples) // iutput buffer
|
|
{
|
|
const sF32 step = sF32 (PAULARATE) / sF32 (OUTRATE);// ratio paula/output rate step (3740000/48000 = 77.92)
|
|
const sF32 pan = 0.5f + 0.5f * MasterSeparation; // audio panning (50% each left/right) (0.5 + 0.5 * 0.5 = 0.75)
|
|
const sF32 vm0 = MasterVolume * sFSqrt (pan); // master volume 0
|
|
const sF32 vm1 = MasterVolume * sFSqrt (1 - pan); // master volume 1
|
|
|
|
for (sInt s = 0; s < samples; s++)
|
|
{
|
|
sInt ReadEnd = ReadPos + FIR_WIDTH + 1;
|
|
if (WritePos < ReadPos)
|
|
ReadEnd -= RBSIZE;
|
|
if (ReadEnd > WritePos)
|
|
Calc (); // call calc() - render in paula rate
|
|
sF32 outl0 = 0, outl1 = 0; // out left
|
|
sF32 outr0 = 0, outr1 = 0; // out right
|
|
|
|
sInt offs
|
|
= (ReadPos - FIR_WIDTH - 1) & (RBSIZE - 1); // offset [this needs optimization. SSE would
|
|
// come to mind. (streaming SMID extensions)]
|
|
sF32 vl = RingBuf[offs];
|
|
sF32 vr = RingBuf[offs + RBSIZE];
|
|
for (sInt i = 1; i < 2 * FIR_WIDTH - 1; i++)
|
|
{
|
|
sF32 w = FIRMem[i]; // w = FIRMem[i]
|
|
outl0 += vl * w; // outl0 = outl0 + (vl * w)
|
|
outr0 += vr * w; // outr0 = outr0 + (vl * w)
|
|
offs = (offs + 1) & (RBSIZE - 1);
|
|
vl = RingBuf[offs];
|
|
vr = RingBuf[offs + RBSIZE];
|
|
outl1 += vl * w;
|
|
outr1 += vr * w;
|
|
}
|
|
sF32 outl = sLerp (outl0, outl1, ReadFrac); // output left
|
|
sF32 outr = sLerp (outr0, outr1, ReadFrac); // output right
|
|
*outbuf++ = vm0 * outl + vm1 * outr;
|
|
*outbuf++ = vm1 * outl + vm0 * outr;
|
|
|
|
ReadFrac += step;
|
|
sInt rfi = sInt (ReadFrac);
|
|
ReadPos = (ReadPos + rfi) & (RBSIZE - 1);
|
|
ReadFrac -= rfi;
|
|
}
|
|
} // Render end
|
|
|
|
// --
|
|
public:
|
|
Paula () // paula constructor
|
|
{
|
|
// make Finite Impulse Response (FIR) table (for low pass filter?)
|
|
sF32 *FIRTable = FIRMem + FIR_WIDTH; // FIR table size
|
|
sF32 yscale = sF32 (OUTRATE) / sF32 (PAULARATE); // Y scale
|
|
sF32 xscale = sFPi * yscale; // X scale
|
|
for (sInt i = -FIR_WIDTH; i <= FIR_WIDTH; i++) // windowed-sinc FIR filter (product of sinc & window function)
|
|
FIRTable[i]
|
|
= yscale * sFSinc (sF32 (i) * xscale)
|
|
* sFHamming (sF32 (i)
|
|
/ sF32 (FIR_WIDTH
|
|
- 1)); // Firtable = (yscale) * (sinc(i) * xscale) * hamming(i) / (fir_width-1)
|
|
|
|
sZeroMem (RingBuf, sizeof (RingBuf));
|
|
ReadPos = 0;
|
|
ReadFrac = 0;
|
|
WritePos = FIR_WIDTH; // reset ring buffer
|
|
|
|
MasterVolume = 0.66f; // master volume 66%
|
|
MasterSeparation = 0.5f; // stereo seperation 50:50
|
|
// FltBuf = 0;
|
|
} // Paula Constructor end
|
|
};
|