mirror of
https://github.com/RaySollium99/picodrive.git
synced 2025-09-07 08:08:04 -04:00
core, some updates for Pico adpcm
This commit is contained in:
parent
a914c976f8
commit
492c47c4b8
1 changed files with 50 additions and 23 deletions
|
@ -7,6 +7,17 @@
|
||||||
* See COPYING file in the top-level directory.
|
* See COPYING file in the top-level directory.
|
||||||
*
|
*
|
||||||
* The following ADPCM algorithm was derived from MAME upd7759 driver.
|
* The following ADPCM algorithm was derived from MAME upd7759 driver.
|
||||||
|
*
|
||||||
|
* The Pico is using this chip in slave mode. In this mode there are no ROM
|
||||||
|
* headers, but the first byte sent to the chip is used to start the ADPCM
|
||||||
|
* engine. This byte is discarded, i.e. not processed by the engine.
|
||||||
|
*
|
||||||
|
* Data is fed into the chip through a FIFO. An Interrupt is created if the
|
||||||
|
* FIFO has been drained below the low water mark.
|
||||||
|
*
|
||||||
|
* The Pico has 2 extensions to the standard upd7759 chip:
|
||||||
|
* - gain control, used to control the volume of the ADPCM output
|
||||||
|
* - filtering, used to remove (some of) the ADPCM compression artifacts
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
@ -42,21 +53,24 @@ static const int step_deltas[16][16] =
|
||||||
|
|
||||||
static const int state_deltas[16] = { -1, -1, 0, 0, 1, 2, 2, 3, -1, -1, 0, 0, 1, 2, 2, 3 };
|
static const int state_deltas[16] = { -1, -1, 0, 0, 1, 2, 2, 3, -1, -1, 0, 0, 1, 2, 2, 3 };
|
||||||
|
|
||||||
s32 stepsamples = (44100LL<<16)/ADPCM_CLOCK;
|
static s32 stepsamples; // ratio as Q16, host sound rate / chip sample rate
|
||||||
|
|
||||||
static struct xpcm_state {
|
static struct xpcm_state {
|
||||||
s32 samplepos; // leftover duration for current sample wrt sndrate, Q16
|
s32 samplepos; // leftover duration for current sample wrt sndrate, Q16
|
||||||
int sample; // current sample
|
int sample; // current sample
|
||||||
short state; // ADPCM engine state
|
short state; // ADPCM decoder state
|
||||||
short samplegain; // programmable gain
|
short samplegain; // programmable gain
|
||||||
|
|
||||||
char startpin; // value on the !START pin
|
char startpin; // value on the !START pin
|
||||||
char irqenable; // IRQ enabled?
|
char irqenable; // IRQ enabled?
|
||||||
|
|
||||||
char portstate; // data stream state
|
char portstate; // ADPCM stream state
|
||||||
short silence; // silence blocks still to be played
|
short silence; // silence blocks still to be played
|
||||||
short rate, nibbles; // ADPCM nibbles still to be played
|
short rate, nibbles; // ADPCM nibbles still to be played
|
||||||
unsigned char highlow, cache; // nibble selector and cache
|
unsigned char highlow, cache; // nibble selector and cache
|
||||||
|
|
||||||
|
char filter; // filter selector
|
||||||
|
s32 x[3], y[3]; // filter history
|
||||||
} xpcm;
|
} xpcm;
|
||||||
enum { RESET, START, HDR, COUNT }; // portstate
|
enum { RESET, START, HDR, COUNT }; // portstate
|
||||||
|
|
||||||
|
@ -66,15 +80,15 @@ enum { RESET, START, HDR, COUNT }; // portstate
|
||||||
#define QB 16 // mantissa bits
|
#define QB 16 // mantissa bits
|
||||||
#define FP(f) (int)((f)*(1<<QB)) // convert to fixpoint
|
#define FP(f) (int)((f)*(1<<QB)) // convert to fixpoint
|
||||||
|
|
||||||
static struct iir2 { // 2nd order IIR
|
static struct iir2 { // 2nd order Butterworth IIR coefficients
|
||||||
s32 a[2], gain; // coefficients
|
s32 a[2], gain; // coefficients
|
||||||
s32 x[3], y[3]; // filter history
|
|
||||||
} filters[4];
|
} filters[4];
|
||||||
static struct iir2 *filter;
|
static struct iir2 *filter; // currently selected filter
|
||||||
|
|
||||||
|
|
||||||
static void PicoPicoFilterCoeff(struct iir2 *iir, int cutoff, int rate)
|
static void PicoPicoFilterCoeff(struct iir2 *iir, int cutoff, int rate)
|
||||||
{
|
{
|
||||||
|
// no filter if the cutoff is above the Nyquist frequency
|
||||||
if (cutoff >= rate/2) {
|
if (cutoff >= rate/2) {
|
||||||
memset(iir, 0, sizeof(*iir));
|
memset(iir, 0, sizeof(*iir));
|
||||||
return;
|
return;
|
||||||
|
@ -95,12 +109,12 @@ static int PicoPicoFilterApply(struct iir2 *iir, int sample)
|
||||||
return sample;
|
return sample;
|
||||||
|
|
||||||
// NB Butterworth specific!
|
// NB Butterworth specific!
|
||||||
iir->x[0] = iir->x[1]; iir->x[1] = iir->x[2];
|
xpcm.x[0] = xpcm.x[1]; xpcm.x[1] = xpcm.x[2];
|
||||||
iir->x[2] = sample * iir->gain; // Qb
|
xpcm.x[2] = sample * iir->gain; // Qb
|
||||||
iir->y[0] = iir->y[1]; iir->y[1] = iir->y[2];
|
xpcm.y[0] = xpcm.y[1]; xpcm.y[1] = xpcm.y[2];
|
||||||
iir->y[2] = (iir->x[0] + 2*iir->x[1] + iir->x[2]
|
xpcm.y[2] = (xpcm.x[0] + 2*xpcm.x[1] + xpcm.x[2]
|
||||||
+ iir->y[0]*iir->a[1] + iir->y[1]*iir->a[0]) >> QB;
|
+ xpcm.y[0]*iir->a[1] + xpcm.y[1]*iir->a[0]) >> QB;
|
||||||
return iir->y[2];
|
return xpcm.y[2];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -131,13 +145,23 @@ PICO_INTERNAL int PicoPicoPCMBusyN(void)
|
||||||
|
|
||||||
PICO_INTERNAL void PicoPicoPCMRerate(void)
|
PICO_INTERNAL void PicoPicoPCMRerate(void)
|
||||||
{
|
{
|
||||||
|
s32 nextstep = ((u64)PicoIn.sndRate<<16)/ADPCM_CLOCK;
|
||||||
|
|
||||||
|
// if the sound rate changes, erase filter history to avoid freak behaviour
|
||||||
|
if (stepsamples != nextstep) {
|
||||||
|
memset(xpcm.x, 0, sizeof(xpcm.x));
|
||||||
|
memset(xpcm.y, 0, sizeof(xpcm.y));
|
||||||
|
}
|
||||||
|
|
||||||
// output samples per chip clock
|
// output samples per chip clock
|
||||||
stepsamples = ((u64)PicoIn.sndRate<<16)/ADPCM_CLOCK;
|
stepsamples = nextstep;
|
||||||
|
|
||||||
// compute filter coefficients, cutoff at half the ADPCM sample rate
|
// compute filter coefficients, cutoff at half the ADPCM sample rate
|
||||||
PicoPicoFilterCoeff(&filters[1], 6000/2, PicoIn.sndRate); // 5-6 KHz
|
PicoPicoFilterCoeff(&filters[1], 6000/2, PicoIn.sndRate); // 5-6 KHz
|
||||||
PicoPicoFilterCoeff(&filters[2], 9000/2, PicoIn.sndRate); // 8-12 KHz
|
PicoPicoFilterCoeff(&filters[2], 9000/2, PicoIn.sndRate); // 8-12 KHz
|
||||||
PicoPicoFilterCoeff(&filters[3], 15000/2, PicoIn.sndRate); // 14-16 KHz
|
PicoPicoFilterCoeff(&filters[3], 15000/2, PicoIn.sndRate); // 14-16 KHz
|
||||||
|
|
||||||
|
PicoPicoPCMFilter(xpcm.filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
PICO_INTERNAL void PicoPicoPCMGain(int gain)
|
PICO_INTERNAL void PicoPicoPCMGain(int gain)
|
||||||
|
@ -147,6 +171,13 @@ PICO_INTERNAL void PicoPicoPCMGain(int gain)
|
||||||
|
|
||||||
PICO_INTERNAL void PicoPicoPCMFilter(int index)
|
PICO_INTERNAL void PicoPicoPCMFilter(int index)
|
||||||
{
|
{
|
||||||
|
// if the filter changes, erase the history to avoid freak behaviour
|
||||||
|
if (index != xpcm.filter) {
|
||||||
|
memset(xpcm.x, 0, sizeof(xpcm.x));
|
||||||
|
memset(xpcm.y, 0, sizeof(xpcm.y));
|
||||||
|
}
|
||||||
|
|
||||||
|
xpcm.filter = index;
|
||||||
filter = filters+index;
|
filter = filters+index;
|
||||||
if (filter->a[0] == 0)
|
if (filter->a[0] == 0)
|
||||||
filter = NULL;
|
filter = NULL;
|
||||||
|
@ -204,12 +235,13 @@ PICO_INTERNAL void PicoPicoPCMUpdate(short *buffer, int length, int stereo)
|
||||||
// loop over FIFO data, generating ADPCM samples
|
// loop over FIFO data, generating ADPCM samples
|
||||||
while (length > 0 && src < lim)
|
while (length > 0 && src < lim)
|
||||||
{
|
{
|
||||||
if (xpcm.silence > 0) {
|
// ADPCM state engine
|
||||||
|
if (xpcm.silence > 0) { // generate silence
|
||||||
xpcm.silence --;
|
xpcm.silence --;
|
||||||
xpcm.sample = 0;
|
xpcm.sample = 0;
|
||||||
xpcm.samplepos += stepsamples*256;
|
xpcm.samplepos += stepsamples*256;
|
||||||
|
|
||||||
} else if (xpcm.nibbles > 0) {
|
} else if (xpcm.nibbles > 0) { // produce samples
|
||||||
xpcm.nibbles --;
|
xpcm.nibbles --;
|
||||||
|
|
||||||
if (xpcm.highlow)
|
if (xpcm.highlow)
|
||||||
|
@ -221,7 +253,7 @@ PICO_INTERNAL void PicoPicoPCMUpdate(short *buffer, int length, int stereo)
|
||||||
do_sample((xpcm.cache & 0xf0) >> 4);
|
do_sample((xpcm.cache & 0xf0) >> 4);
|
||||||
xpcm.samplepos += stepsamples*xpcm.rate;
|
xpcm.samplepos += stepsamples*xpcm.rate;
|
||||||
|
|
||||||
} else switch (xpcm.portstate) {
|
} else switch (xpcm.portstate) { // handle stream headers
|
||||||
case RESET:
|
case RESET:
|
||||||
xpcm.sample = 0;
|
xpcm.sample = 0;
|
||||||
xpcm.samplepos += length<<16;
|
xpcm.samplepos += length<<16;
|
||||||
|
@ -301,15 +333,13 @@ PICO_INTERNAL int PicoPicoPCMSave(void *buffer, int length)
|
||||||
{
|
{
|
||||||
u8 *bp = buffer;
|
u8 *bp = buffer;
|
||||||
|
|
||||||
if (length < sizeof(xpcm) + sizeof(filters)) {
|
if (length < sizeof(xpcm)) {
|
||||||
elprintf(EL_ANOMALY, "save buffer too small?");
|
elprintf(EL_ANOMALY, "save buffer too small?");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
memcpy(bp, &xpcm, sizeof(xpcm));
|
memcpy(bp, &xpcm, sizeof(xpcm));
|
||||||
bp += sizeof(xpcm);
|
bp += sizeof(xpcm);
|
||||||
memcpy(bp, filters, sizeof(filters));
|
|
||||||
bp += sizeof(filters);
|
|
||||||
return (bp - (u8*)buffer);
|
return (bp - (u8*)buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -320,7 +350,4 @@ PICO_INTERNAL void PicoPicoPCMLoad(void *buffer, int length)
|
||||||
if (length >= sizeof(xpcm))
|
if (length >= sizeof(xpcm))
|
||||||
memcpy(&xpcm, bp, sizeof(xpcm));
|
memcpy(&xpcm, bp, sizeof(xpcm));
|
||||||
bp += sizeof(xpcm);
|
bp += sizeof(xpcm);
|
||||||
if (length >= sizeof(xpcm) + sizeof(filters))
|
|
||||||
memcpy(filters, bp, sizeof(filters));
|
|
||||||
bp += sizeof(filters);
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue