HatariWii/src/dmaSnd.c

1391 lines
46 KiB
C

/*
Hatari - dmaSnd.c
This file is distributed under the GNU General Public License, version 2
or at your option any later version. Read the file gpl.txt for details.
STE DMA sound emulation. Does not seem to be very hard at first glance,
but since the DMA sound has to be mixed together with the PSG sound and
the output frequency of the host computer differs from the DMA sound
frequency, the copy function is a little bit complicated.
The update function also triggers the ST interrupts (Timer A and MFP-i7)
which are often used in ST programs for setting a new sound frame after
the old one has finished.
To support programs that write into the frame buffer while it's played,
we should update dma sound on each video HBL.
This is also how it works on a real STE : bytes are read by the DMA
at the end of each HBL and stored in a small FIFO (8 bytes) that is sent
to the DAC depending on the chosen DMA output freq.
Falcon sound emulation is all taken into account in crossbar.c
Hardware I/O registers:
$FF8900 (word) : DMA sound control register
$FF8903 (byte) : Frame Start Hi
$FF8905 (byte) : Frame Start Mi
$FF8907 (byte) : Frame Start Lo
$FF8909 (byte) : Frame Count Hi
$FF890B (byte) : Frame Count Mi
$FF890D (byte) : Frame Count Lo
$FF890F (byte) : Frame End Hi
$FF8911 (byte) : Frame End Mi
$FF8913 (byte) : Frame End Lo
$FF8920 (word) : Sound Mode Control (frequency, mono/stereo)
$FF8922 (byte) : Microwire Data Register
$FF8924 (byte) : Microwire Mask Register
The Microwire and LMC 1992 commands :
a command looks like: 10 CCC DDD DDD
chipset address : 10
command :
000 XXX XDD Mixing
00 : DMA sound only
01 : DMA sound + input 1 (YM2149 + AUDIOI, full frequency range)
10 : DMA sound + input 2 (YM2149 + AUDIOI, Low Pass Filter) -> DMA sound only
11 : DMA sound + input 3 (not connected) -> DMA sound only
001 XXD DDD Bass
0 000 : -12 dB
0 110 : 0 dB
1 100 : +12 dB
002 XXD DDD Treble
0 000 : -12 dB
0 110 : 0 dB
1 100 : +12 dB
003 DDD DDD Master volume
000 000 : -80 dB
010 100 : -40 dB
101 XXX : 0 dB
004 XDD DDD Right channel volume
00 000 : -40 dB
01 010 : -20 dB
10 1XX : 0 dB
005 XDD DDD Left channel volume
00 000 : -40 dB
01 010 : -20 dB
10 1XX : 0 dB
Other : undefined
LMC1992 IIR code Copyright by David Savinkoff 2010
A first order bass filter is multiplied with a
first order treble filter to make a single
second order IIR shelf filter.
Sound is stereo filtered by Boosting or Cutting
the Bass and Treble by +/-12dB in 2dB steps.
This filter sounds exactly as the Atari TT or STE.
Sampling frequency = selectable
Bass turnover = 118.276Hz (8.2nF on LM1992 bass)
Treble turnover = 8438.756Hz (8.2nF on LM1992 treble)
*/
const char DmaSnd_fileid[] = "Hatari dmaSnd.c : " __DATE__ " " __TIME__;
#include "main.h"
#include "audio.h"
#include "configuration.h"
#include "dmaSnd.h"
#include "cycInt.h"
#include "ioMem.h"
#include "log.h"
#include "memorySnapShot.h"
#include "mfp.h"
#include "sound.h"
#include "stMemory.h"
#include "crossbar.h"
#include "screen.h"
#include "video.h"
#include "m68000.h"
#define TONE_STEPS 13
#define DMASND_FIFO_SIZE 8 /* 8 bytes : size of the DMA Audio's FIFO, filled on every HBL */
#define DMASND_FIFO_SIZE_MASK (DMASND_FIFO_SIZE-1) /* mask to keep FIFO_pos in 0-7 range */
/* Global variables that can be changed/read from other parts of Hatari */
static void DmaSnd_Apply_LMC(int nMixBufIdx, int nSamplesToGenerate);
static void DmaSnd_Set_Tone_Level(int set_bass, int set_treb);
static float DmaSnd_IIRfilterL(float xn);
static float DmaSnd_IIRfilterR(float xn);
static struct first_order_s *DmaSnd_Treble_Shelf(float g, float fc, float Fs);
static struct first_order_s *DmaSnd_Bass_Shelf(float g, float fc, float Fs);
static Sint16 DmaSnd_LowPassFilterLeft(Sint16 in);
static Sint16 DmaSnd_LowPassFilterRight(Sint16 in);
static bool DmaSnd_LowPass;
Uint16 nDmaSoundControl; /* Sound control register */
struct first_order_s { float a1, b0, b1; };
struct second_order_s { float a1, a2, b0, b1, b2; };
struct dma_s {
Uint16 soundMode; /* Sound mode register */
Uint32 frameStartAddr; /* Sound frame start */
Uint32 frameEndAddr; /* Sound frame end */
Uint32 frameCounterAddr; /* Sound frame current address counter */
/* Internal 8 byte FIFO */
Sint8 FIFO[ DMASND_FIFO_SIZE ];
Uint16 FIFO_Pos; /* from 0 to DMASND_FIFO_SIZE-1 */
Uint16 FIFO_NbBytes; /* from 0 to DMASND_FIFO_SIZE */
Sint16 FrameLeft; /* latest values read from the FIFO */
Sint16 FrameRight;
};
static Sint64 frameCounter_float = 0;
static bool DmaInitSample = false;
struct microwire_s {
Uint16 data; /* Microwire Data register */
Uint16 mask; /* Microwire Mask register */
Uint16 mwTransferSteps; /* Microwire shifting counter */
Uint16 pendingCyclesOver; /* Number of delayed cycles for the interrupt */
Uint16 mixing; /* Mixing command */
Uint16 bass; /* Bass command */
Uint16 treble; /* Treble command */
Uint16 masterVolume; /* Master volume command */
Uint16 leftVolume; /* Left channel volume command */
Uint16 rightVolume; /* Right channel volume command */
};
struct lmc1992_s {
struct first_order_s bass_table[TONE_STEPS];
struct first_order_s treb_table[TONE_STEPS];
float coef[5]; /* IIR coefficients */
float left_gain;
float right_gain;
};
static struct dma_s dma;
static struct microwire_s microwire;
static struct lmc1992_s lmc1992;
/* dB = 20log(gain) : gain = antilog(dB/20) */
/* Table gain values = (int)(powf(10.0, dB/20.0)*65536.0 + 0.5) 2dB steps */
/* Values for LMC1992 Master volume control (*65536) */
static const Uint16 LMC1992_Master_Volume_Table[64] =
{
7, 8, 10, 13, 16, 21, 26, 33, 41, 52, /* -80dB */
66, 83, 104, 131, 165, 207, 261, 328, 414, 521, /* -60dB */
655, 825, 1039, 1308, 1646, 2072, 2609, 3285, 4135, 5206, /* -40dB */
6554, 8250, 10387, 13076, 16462, 20724, 26090, 32846, 41350, 52057, /* -20dB */
65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, /* 0dB */
65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, /* 0dB */
65535, 65535, 65535, 65535 /* 0dB */
};
/* Values for LMC1992 Left and right volume control (*65536) */
static const Uint16 LMC1992_LeftRight_Volume_Table[32] =
{
655, 825, 1039, 1308, 1646, 2072, 2609, 3285, 4135, 5206, /* -40dB */
6554, 8250, 10387, 13076, 16462, 20724, 26090, 32846, 41350, 52057, /* -20dB */
65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, /* 0dB */
65535, 65535 /* 0dB */
};
/* Values for LMC1992 BASS and TREBLE */
static const Sint16 LMC1992_Bass_Treble_Table[16] =
{
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 12, 12, 12
};
static const int DmaSndSampleRates[4] =
{
6258, 12517, 25033, 50066
};
/*--------------------------------------------------------------*/
/* Local functions prototypes */
/*--------------------------------------------------------------*/
static void DmaSnd_FIFO_Refill(void);
static Sint8 DmaSnd_FIFO_PullByte(void);
static void DmaSnd_FIFO_SetStereo(void);
static int DmaSnd_DetectSampleRate(void);
static void DmaSnd_StartNewFrame(void);
static inline int DmaSnd_EndOfFrameReached(void);
/**
* Reset DMA sound variables.
*/
void DmaSnd_Reset(bool bCold)
{
nDmaSoundControl = 0;
dma.soundMode = 0;
/* [NP] Set start/end to 0 even on warm reset ? (fix 'Brace' by Diamond Design) */
IoMem[0xff8903] = 0; /* frame start addr = 0 */
IoMem[0xff8905] = 0;
IoMem[0xff8907] = 0;
IoMem[0xff890f] = 0; /* frame end addr = 0 */
IoMem[0xff8911] = 0;
IoMem[0xff8913] = 0;
dma.FIFO_Pos = 0;
dma.FIFO_NbBytes = 0;
dma.FrameLeft = 0;
dma.FrameRight = 0;
if ( bCold )
{
/* Microwire has no reset signal, it will keep its values on warm reset */
microwire.masterVolume = 7; /* -80 dB ; TOS 1.62 will put 0x28 (ie 65535) = 0 dB (max volume) */
microwire.leftVolume = 655; /* -40 dB ; TOS 1.62 will put 0x14 (ie 65535) = 0 dB (max volume) */
microwire.rightVolume = 655; /* -40 db ; TOS 1.62 will put 0x14 (ie 65535) = 0 dB (max volume) */
microwire.mixing = 0;
microwire.bass = 6; /* 0 dB (flat) */
microwire.treble = 6; /* 0 dB (flat) */
}
/* Initialise microwire LMC1992 IIR filter parameters */
DmaSnd_Init_Bass_and_Treble_Tables();
microwire.mwTransferSteps = 0;
microwire.pendingCyclesOver = 8;
}
/*-----------------------------------------------------------------------*/
/**
* Save/Restore snapshot of local variables ('MemorySnapShot_Store' handles type)
*/
void DmaSnd_MemorySnapShot_Capture(bool bSave)
{
/* Save/Restore details */
MemorySnapShot_Store(&nDmaSoundControl, sizeof(nDmaSoundControl));
MemorySnapShot_Store(&dma, sizeof(dma));
MemorySnapShot_Store(&microwire, sizeof(microwire));
MemorySnapShot_Store(&lmc1992, sizeof(lmc1992));
}
/*-----------------------------------------------------------------------*/
/**
* This function is called on every HBL to ensure the DMA Audio's FIFO
* is kept full.
* In Hatari, the FIFO is handled like a ring buffer (to avoid memcopying bytes
* inside the FIFO when a byte is pushed/pulled).
* Note that the DMA fetches words, not bytes, so we read new data only
* when 2 bytes or more are missing.
* When end of frame is reached, we continue with a new frame if loop mode
* is on, else we stop DMA Audio.
*/
static void DmaSnd_FIFO_Refill(void)
{
/* If DMA sound is OFF, don't update the FIFO */
if ( ( nDmaSoundControl & DMASNDCTRL_PLAY ) == 0)
return;
/* If End Address == Start Address, don't update the FIFO */
if (dma.frameEndAddr == dma.frameStartAddr)
{
DmaSnd_EndOfFrameReached(); /* Stop dma audio if loop mode is off */
return;
}
/* Refill the whole FIFO */
while ( DMASND_FIFO_SIZE - dma.FIFO_NbBytes >= 2 )
{
/* Add one word to the FIFO */
LOG_TRACE(TRACE_DMASND, "DMA snd fifo refill adr=%x pos %d nb %d %x %x\n", dma.frameCounterAddr , dma.FIFO_Pos , dma.FIFO_NbBytes ,
STRam[ dma.frameCounterAddr ] , STRam[ dma.frameCounterAddr+1 ] );
dma.FIFO[ ( dma.FIFO_Pos+dma.FIFO_NbBytes+0 ) & DMASND_FIFO_SIZE_MASK ] = (Sint8)STRam[ dma.frameCounterAddr ]; /* add upper byte of the word */
dma.FIFO[ ( dma.FIFO_Pos+dma.FIFO_NbBytes+1 ) & DMASND_FIFO_SIZE_MASK ] = (Sint8)STRam[ dma.frameCounterAddr+1 ]; /* add lower byte of the word */
dma.FIFO_NbBytes += 2; /* One word more in the FIFO */
/* Increase current frame address and check if we reached frame's end */
dma.frameCounterAddr += 2;
if ( dma.frameCounterAddr == dma.frameEndAddr ) /* end of frame reached, should we loop or stop dma ? */
{
if ( DmaSnd_EndOfFrameReached() )
break; /* Loop mode off, dma audio is now turned off */
}
}
}
/*-----------------------------------------------------------------------*/
/**
* Pull one sample/byte from the DMA Audio's FIFO and decrease the number of
* remaining bytes.
* If the FIFO is empty, return 0 (empty sample)
* Note : on a real STE, the 8 bytes FIFO is refilled on each HBL, which gives
* a total of 313*8*VBL_PER_SEC=125326 bytes per sec read by the DMA. As the max freq
* is 50066 Hz, the STE can play 100132 bytes per sec in stereo ; so on a real STE
* the FIFO can never be empty while DMA is ON.
* But on Hatari, if the user chooses an audio's output frequency that is much
* lower than the current DMA freq, audio will be updated less frequently than
* on each HBL and it could require to process more than DMASND_FIFO_SIZE in one
* call to DmaSnd_GenerateSamples(). This is why we allow DmaSnd_FIFO_Refill()
* to be called if FIFO is empty but DMA sound is still ON.
* This way, sound remains correct even if the user uses very low output freq.
*/
static Sint8 DmaSnd_FIFO_PullByte(void)
{
Sint8 sample;
if ( dma.FIFO_NbBytes == 0 )
{
DmaSnd_FIFO_Refill();
if ( dma.FIFO_NbBytes == 0 ) /* Refill didn't add any new bytes */
{
LOG_TRACE(TRACE_DMASND, "DMA snd fifo empty for pull\n" );
return 0;
}
}
LOG_TRACE(TRACE_DMASND, "DMA snd fifo pull pos %d nb %d %02x\n", dma.FIFO_Pos , dma.FIFO_NbBytes , (Uint8)dma.FIFO[ dma.FIFO_Pos ] );
sample = dma.FIFO[ dma.FIFO_Pos ]; /* Get oldest byte from the FIFO */
dma.FIFO_Pos = (dma.FIFO_Pos+1) & DMASND_FIFO_SIZE_MASK;/* Pos to be pulled on next call */
dma.FIFO_NbBytes--; /* One byte less in the FIFO */
return sample;
}
/*-----------------------------------------------------------------------*/
/**
* In case a program switches from mono to stereo, we must ensure that
* FIFO_pos is on even boundary to keep Left/Right bytes in the correct
* order (Left byte should be on even addresses and Right byte on odd ones).
* If this is not the case, we skip one byte.
*/
static void DmaSnd_FIFO_SetStereo(void)
{
Uint16 NewPos;
if ( dma.FIFO_Pos & 1 )
{
NewPos = (dma.FIFO_Pos+1) & DMASND_FIFO_SIZE_MASK; /* skip the byte on odd address */
if ( nDmaSoundControl & DMASNDCTRL_PLAY ) /* print a log if we change while playing */
{ LOG_TRACE(TRACE_DMASND, "DMA snd switching to stereo mode while playing mono FIFO_pos %d->%d\n", dma.FIFO_Pos , NewPos ); }
else
{ LOG_TRACE(TRACE_DMASND, "DMA snd switching to stereo mode FIFO_pos %d->%d\n", dma.FIFO_Pos , NewPos ); }
dma.FIFO_Pos = NewPos;
if ( dma.FIFO_NbBytes > 0 )
dma.FIFO_NbBytes--; /* remove one byte if FIFO was not already empty */
}
}
/*-----------------------------------------------------------------------*/
/**
* Returns the frequency corresponding to the 2 lower bits of dma.soundMode
*/
static int DmaSnd_DetectSampleRate(void)
{
return DmaSndSampleRates[dma.soundMode & 3];
}
/*-----------------------------------------------------------------------*/
/**
* This function is called when a new sound frame is started.
* It copies the start and end address from the I/O registers and set
* the frame counter addr to the start of this new frame.
*/
static void DmaSnd_StartNewFrame(void)
{
dma.frameStartAddr = (IoMem[0xff8903] << 16) | (IoMem[0xff8905] << 8) | (IoMem[0xff8907] & ~1);
dma.frameEndAddr = (IoMem[0xff890f] << 16) | (IoMem[0xff8911] << 8) | (IoMem[0xff8913] & ~1);
dma.frameCounterAddr = dma.frameStartAddr;
LOG_TRACE(TRACE_DMASND, "DMA snd new frame start=%x end=%x\n", dma.frameStartAddr, dma.frameEndAddr);
}
/*-----------------------------------------------------------------------*/
/**
* End-of-frame has been reached. Raise interrupts if needed.
* Returns true if DMA sound processing should be stopped now and false
* if it continues (DMA PLAYLOOP mode).
*/
static inline int DmaSnd_EndOfFrameReached(void)
{
LOG_TRACE(TRACE_DMASND, "DMA snd end of frame\n");
/* Raise end-of-frame interrupts (MFP-i7 and Time-A) */
MFP_InputOnChannel ( MFP_INT_GPIP7 , 0 );
if (MFP_TACR == 0x08) /* Is timer A in Event Count mode? */
MFP_TimerA_EventCount_Interrupt();
if (nDmaSoundControl & DMASNDCTRL_PLAYLOOP)
{
DmaSnd_StartNewFrame();
}
else
{
nDmaSoundControl &= ~DMASNDCTRL_PLAY;
return true;
}
return false;
}
/*-----------------------------------------------------------------------*/
/**
* Mix DMA sound sample with the normal PSG sound samples.
* Note: We adjust the volume level of the 8-bit DMA samples to factor
* 0.75 compared to the PSG sound samples.
*
* The following formula: -((256*3/4)/4)/4
*
* Multiply by 256 to convert 8 to 16 bits;
* DMA sound is 3/4 level of YM sound;
* Divide by 4 to account for the STe YM volume table level;
* ( STe sound at 1/2 amplitude to avoid overflow. )
* ( lmc1992.right_gain and lmc1992.left_gain are )
* ( doubled to compensate. )
* Divide by 4 to account for DmaSnd_LowPassFilter;
* Multiply DMA sound by -1 because the LMC1992 inverts the signal
* ( YM sign is +1 :: -1(op-amp) * -1(Lmc1992) ).
*/
void DmaSnd_GenerateSamples(int nMixBufIdx, int nSamplesToGenerate)
{
int i;
int nBufIdx;
Sint8 MonoByte , LeftByte , RightByte;
unsigned n;
Sint64 FreqRatio;
/* DMA Audio OFF and FIFO empty : process YM2149's output */
if ( !(nDmaSoundControl & DMASNDCTRL_PLAY) && ( dma.FIFO_NbBytes == 0 ) )
{
for (i = 0; i < nSamplesToGenerate; i++)
{
nBufIdx = (nMixBufIdx + i) % MIXBUFFER_SIZE;
switch (microwire.mixing) {
case 1:
/* DMA and YM2149 mixing */
MixBuffer[nBufIdx][0] = MixBuffer[nBufIdx][0] + dma.FrameLeft * -((256*3/4)/4)/4;
MixBuffer[nBufIdx][1] = MixBuffer[nBufIdx][1] + dma.FrameRight * -((256*3/4)/4)/4;
break;
default:
/* mixing=0 DMA only */
/* mixing=2 DMA and input 2 (YM2149 LPF) -> DMA */
/* mixing=3 DMA and input 3 -> DMA */
MixBuffer[nBufIdx][0] = dma.FrameLeft * -((256*3/4)/4)/4;
MixBuffer[nBufIdx][1] = dma.FrameRight * -((256*3/4)/4)/4;
break;
}
}
/* Apply LMC1992 sound modifications (Bass and Treble) */
DmaSnd_Apply_LMC ( nMixBufIdx , nSamplesToGenerate );
return;
}
/* DMA Audio ON or FIFO not empty yet */
/* Compute ratio between DMA's sound frequency and host computer's sound frequency, */
/* use << 32 to simulate floating point precision */
FreqRatio = ( ((Sint64)DmaSnd_DetectSampleRate()) << 32 ) / nAudioFrequency;
if (dma.soundMode & DMASNDMODE_MONO)
{
/* Mono 8-bit */
for (i = 0; i < nSamplesToGenerate; i++)
{
if ( DmaInitSample )
{
MonoByte = DmaSnd_FIFO_PullByte ();
dma.FrameLeft = DmaSnd_LowPassFilterLeft( (Sint16)MonoByte );
dma.FrameRight = DmaSnd_LowPassFilterRight( (Sint16)MonoByte );
DmaInitSample = false;
}
nBufIdx = (nMixBufIdx + i) % MIXBUFFER_SIZE;
switch (microwire.mixing) {
case 1:
/* DMA and YM2149 mixing */
MixBuffer[nBufIdx][0] = MixBuffer[nBufIdx][0] + dma.FrameLeft * -((256*3/4)/4)/4;
break;
default:
/* mixing=0 DMA only */
/* mixing=2 DMA and input 2 (YM2149 LPF) -> DMA */
/* mixing=3 DMA and input 3 -> DMA */
MixBuffer[nBufIdx][0] = dma.FrameLeft * -((256*3/4)/4)/4;
break;
}
MixBuffer[nBufIdx][1] = MixBuffer[nBufIdx][0]; /* right = left */
/* Increase freq counter */
frameCounter_float += FreqRatio;
n = frameCounter_float >> 32; /* number of samples to skip */
while ( n > 0 ) /* pull as many bytes from the FIFO as needed */
{
MonoByte = DmaSnd_FIFO_PullByte ();
dma.FrameLeft = DmaSnd_LowPassFilterLeft( (Sint16)MonoByte );
dma.FrameRight = DmaSnd_LowPassFilterRight( (Sint16)MonoByte );
n--;
}
frameCounter_float &= 0xffffffff; /* only keep the fractional part */
}
}
else
{
/* Stereo 8-bit */
for (i = 0; i < nSamplesToGenerate; i++)
{
if ( DmaInitSample )
{
LeftByte = DmaSnd_FIFO_PullByte ();
RightByte = DmaSnd_FIFO_PullByte ();
dma.FrameLeft = DmaSnd_LowPassFilterLeft( (Sint16)LeftByte );
dma.FrameRight = DmaSnd_LowPassFilterRight( (Sint16)RightByte );
DmaInitSample = false;
}
nBufIdx = (nMixBufIdx + i) % MIXBUFFER_SIZE;
switch (microwire.mixing) {
case 1:
/* DMA and YM2149 mixing */
MixBuffer[nBufIdx][0] = MixBuffer[nBufIdx][0] + dma.FrameLeft * -((256*3/4)/4)/4;
MixBuffer[nBufIdx][1] = MixBuffer[nBufIdx][1] + dma.FrameRight * -((256*3/4)/4)/4;
break;
default:
/* mixing=0 DMA only */
/* mixing=2 DMA and input 2 (YM2149 LPF) -> DMA */
/* mixing=3 DMA and input 3 -> DMA */
MixBuffer[nBufIdx][0] = dma.FrameLeft * -((256*3/4)/4)/4;
MixBuffer[nBufIdx][1] = dma.FrameRight * -((256*3/4)/4)/4;
break;
}
/* Increase freq counter */
frameCounter_float += FreqRatio;
n = frameCounter_float >> 32; /* number of samples to skip */
while ( n > 0 ) /* pull as many bytes from the FIFO as needed */
{
LeftByte = DmaSnd_FIFO_PullByte ();
RightByte = DmaSnd_FIFO_PullByte ();
dma.FrameLeft = DmaSnd_LowPassFilterLeft( (Sint16)LeftByte );
dma.FrameRight = DmaSnd_LowPassFilterRight( (Sint16)RightByte );
n--;
}
frameCounter_float &= 0xffffffff; /* only keep the fractional part */
}
}
/* Apply LMC1992 sound modifications (Bass and Treble) */
DmaSnd_Apply_LMC ( nMixBufIdx , nSamplesToGenerate );
}
/*-----------------------------------------------------------------------*/
/**
* Apply LMC1992 sound modifications (Bass and Treble)
* The Bass and Treble get samples at nAudioFrequency rate.
* The tone control's sampling frequency must be at least 22050 Hz to sound good.
*/
static void DmaSnd_Apply_LMC(int nMixBufIdx, int nSamplesToGenerate)
{
int nBufIdx;
int i;
Sint32 sample;
/* Apply LMC1992 sound modifications (Left, Right and Master Volume) */
for (i = 0; i < nSamplesToGenerate; i++) {
nBufIdx = (nMixBufIdx + i) % MIXBUFFER_SIZE;
sample = DmaSnd_IIRfilterL( Subsonic_IIR_HPF_Left( MixBuffer[nBufIdx][0]));
if (sample<-32767) /* check for overflow to clip waveform */
sample = -32767;
else if (sample>32767)
sample = 32767;
MixBuffer[nBufIdx][0] = sample;
sample = DmaSnd_IIRfilterR( Subsonic_IIR_HPF_Right(MixBuffer[nBufIdx][1]));
if (sample<-32767) /* check for overflow to clip waveform */
sample = -32767;
else if (sample>32767)
sample = 32767;
MixBuffer[nBufIdx][1] = sample;
}
}
/*-----------------------------------------------------------------------*/
/**
* STE DMA sound is using an 8 bytes FIFO that is checked and filled on each HBL
* (at 50066 Hz 8 bit stereo, the DMA requires approx 6.5 new bytes per HBL)
* Calling Sound_Update on each HBL allows to emulate some programs that modify
* the data between FrameStart and FrameEnd while DMA sound is ON
* (eg the demo 'Mental Hangover' or the game 'Power Up Plus')
* We first check if the FIFO needs to be refilled, then we call Sound_Update.
* This function should be called from the HBL's handler (in video.c)
*/
void DmaSnd_STE_HBL_Update(void)
{
if ( ( ConfigureParams.System.nMachineType != MACHINE_STE )
&& ( ConfigureParams.System.nMachineType != MACHINE_MEGA_STE ) )
return;
/* The DMA starts refilling the FIFO when display is OFF (eg cycle 376 in low res 50 Hz) */
DmaSnd_FIFO_Refill ();
/* If DMA sound is ON or FIFO is not empty, update sound */
if ( (nDmaSoundControl & DMASNDCTRL_PLAY) || ( dma.FIFO_NbBytes > 0 ) )
Sound_Update(false);
/* As long as display is OFF, the DMA will refill the FIFO after playing some samples during the HBL */
DmaSnd_FIFO_Refill ();
}
/*-----------------------------------------------------------------------*/
/**
* Return current frame counter address (value is always even)
*/
static Uint32 DmaSnd_GetFrameCount(void)
{
Uint32 nActCount;
/* Update sound to get the current DMA frame address */
Sound_Update(false);
if (nDmaSoundControl & DMASNDCTRL_PLAY)
nActCount = dma.frameCounterAddr;
else
nActCount = (IoMem[0xff8903] << 16) | (IoMem[0xff8905] << 8) | (IoMem[0xff8907] & ~1);
return nActCount;
}
/*-----------------------------------------------------------------------*/
/**
* Read word from sound control register (0xff8900).
*/
void DmaSnd_SoundControl_ReadWord(void)
{
IoMem_WriteWord(0xff8900, nDmaSoundControl);
if(LOG_TRACE_LEVEL(TRACE_DMASND))
{
int FrameCycles, HblCounterVideo, LineCycles;
Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles );
LOG_TRACE_PRINT("DMA snd control read: 0x%04x video_cyc=%d %d@%d pc=%x instr_cycle %d\n",
nDmaSoundControl,
FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles);
}
}
/*-----------------------------------------------------------------------*/
/**
* Write word to sound control register (0xff8900).
*/
void DmaSnd_SoundControl_WriteWord(void)
{
Uint16 nNewSndCtrl;
if(LOG_TRACE_LEVEL(TRACE_DMASND))
{
int FrameCycles, HblCounterVideo, LineCycles;
Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles );
LOG_TRACE_PRINT("DMA snd control write: 0x%04x video_cyc=%d %d@%d pc=%x instr_cycle %d\n",
IoMem_ReadWord(0xff8900),
FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles);
}
/* Before starting/stopping DMA sound, create samples up until this point with current values */
Sound_Update(false);
nNewSndCtrl = IoMem_ReadWord(0xff8900) & 3;
if (!(nDmaSoundControl & DMASNDCTRL_PLAY) && (nNewSndCtrl & DMASNDCTRL_PLAY))
{
LOG_TRACE(TRACE_DMASND, "DMA snd control write: starting dma sound output\n");
DmaInitSample = true;
frameCounter_float = 0;
DmaSnd_StartNewFrame();
}
else if ((nDmaSoundControl & DMASNDCTRL_PLAY) && !(nNewSndCtrl & DMASNDCTRL_PLAY))
{
LOG_TRACE(TRACE_DMASND, "DMA snd control write: stopping dma sound output\n");
}
nDmaSoundControl = nNewSndCtrl;
}
/*-----------------------------------------------------------------------*/
/**
* Read word from sound frame count high register (0xff8909).
*/
void DmaSnd_FrameCountHigh_ReadByte(void)
{
IoMem_WriteByte(0xff8909, DmaSnd_GetFrameCount() >> 16);
}
/*-----------------------------------------------------------------------*/
/**
* Read word from sound frame count medium register (0xff890b).
*/
void DmaSnd_FrameCountMed_ReadByte(void)
{
IoMem_WriteByte(0xff890b, DmaSnd_GetFrameCount() >> 8);
}
/*-----------------------------------------------------------------------*/
/**
* Read word from sound frame count low register (0xff890d).
*/
void DmaSnd_FrameCountLow_ReadByte(void)
{
IoMem_WriteByte(0xff890d, DmaSnd_GetFrameCount());
}
/*-----------------------------------------------------------------------*/
/**
* Write bytes to various registers with no action.
*/
void DmaSnd_FrameStartHigh_WriteByte(void)
{
if(LOG_TRACE_LEVEL(TRACE_DMASND))
{
int FrameCycles, HblCounterVideo, LineCycles;
Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles );
LOG_TRACE_PRINT("DMA snd frame start high: 0x%02x at pos %d/%d video_cyc=%d %d@%d pc=%x instr_cycle %d\n",
IoMem_ReadByte(0xff8903) , dma.frameCounterAddr - dma.frameStartAddr , dma.frameEndAddr - dma.frameStartAddr ,
FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles);
}
}
void DmaSnd_FrameStartMed_WriteByte(void)
{
if(LOG_TRACE_LEVEL(TRACE_DMASND))
{
int FrameCycles, HblCounterVideo, LineCycles;
Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles );
LOG_TRACE_PRINT("DMA snd frame start med: 0x%02x at pos %d/%d video_cyc=%d %d@%d pc=%x instr_cycle %d\n",
IoMem_ReadByte(0xff8905) , dma.frameCounterAddr - dma.frameStartAddr , dma.frameEndAddr - dma.frameStartAddr ,
FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles);
}
}
void DmaSnd_FrameStartLow_WriteByte(void)
{
if(LOG_TRACE_LEVEL(TRACE_DMASND))
{
int FrameCycles, HblCounterVideo, LineCycles;
Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles );
LOG_TRACE_PRINT("DMA snd frame start low: 0x%02x at pos %d/%d video_cyc=%d %d@%d pc=%x instr_cycle %d\n",
IoMem_ReadByte(0xff8907) , dma.frameCounterAddr - dma.frameStartAddr , dma.frameEndAddr - dma.frameStartAddr ,
FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles);
}
}
void DmaSnd_FrameCountHigh_WriteByte(void)
{
if(LOG_TRACE_LEVEL(TRACE_DMASND))
{
int FrameCycles, HblCounterVideo, LineCycles;
Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles );
LOG_TRACE_PRINT("DMA snd frame count high: 0x%02x at pos %d/%d video_cyc=%d %d@%d pc=%x instr_cycle %d\n",
IoMem_ReadByte(0xff8909) , dma.frameCounterAddr - dma.frameStartAddr , dma.frameEndAddr - dma.frameStartAddr ,
FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles);
}
}
void DmaSnd_FrameCountMed_WriteByte(void)
{
if(LOG_TRACE_LEVEL(TRACE_DMASND))
{
int FrameCycles, HblCounterVideo, LineCycles;
Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles );
LOG_TRACE_PRINT("DMA snd frame count med: 0x%02x at pos %d/%d video_cyc=%d %d@%d pc=%x instr_cycle %d\n",
IoMem_ReadByte(0xff890b) , dma.frameCounterAddr - dma.frameStartAddr , dma.frameEndAddr - dma.frameStartAddr ,
FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles);
}
}
void DmaSnd_FrameCountLow_WriteByte(void)
{
if(LOG_TRACE_LEVEL(TRACE_DMASND))
{
int FrameCycles, HblCounterVideo, LineCycles;
Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles );
LOG_TRACE_PRINT("DMA snd frame count low: 0x%02x at pos %d/%d video_cyc=%d %d@%d pc=%x instr_cycle %d\n",
IoMem_ReadByte(0xff890d) , dma.frameCounterAddr - dma.frameStartAddr , dma.frameEndAddr - dma.frameStartAddr ,
FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles);
}
}
void DmaSnd_FrameEndHigh_WriteByte(void)
{
if(LOG_TRACE_LEVEL(TRACE_DMASND))
{
int FrameCycles, HblCounterVideo, LineCycles;
Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles );
LOG_TRACE_PRINT("DMA snd frame end high: 0x%02x at pos %d/%d video_cyc=%d %d@%d pc=%x instr_cycle %d\n",
IoMem_ReadByte(0xff890f) , dma.frameCounterAddr - dma.frameStartAddr , dma.frameEndAddr - dma.frameStartAddr ,
FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles);
}
}
void DmaSnd_FrameEndMed_WriteByte(void)
{
if(LOG_TRACE_LEVEL(TRACE_DMASND))
{
int FrameCycles, HblCounterVideo, LineCycles;
Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles );
LOG_TRACE_PRINT("DMA snd frame end med: 0x%02x at pos %d/%d video_cyc=%d %d@%d pc=%x instr_cycle %d\n",
IoMem_ReadByte(0xff8911) , dma.frameCounterAddr - dma.frameStartAddr , dma.frameEndAddr - dma.frameStartAddr ,
FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles);
}
}
void DmaSnd_FrameEndLow_WriteByte(void)
{
if(LOG_TRACE_LEVEL(TRACE_DMASND))
{
int FrameCycles, HblCounterVideo, LineCycles;
Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles );
LOG_TRACE_PRINT("DMA snd frame end low: 0x%02x at pos %d/%d video_cyc=%d %d@%d pc=%x instr_cycle %d\n",
IoMem_ReadByte(0xff8913) , dma.frameCounterAddr - dma.frameStartAddr , dma.frameEndAddr - dma.frameStartAddr ,
FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles);
}
}
/*-----------------------------------------------------------------------*/
/**
* Read word from sound mode register (0xff8921).
*/
void DmaSnd_SoundModeCtrl_ReadByte(void)
{
IoMem_WriteByte(0xff8921, dma.soundMode);
if(LOG_TRACE_LEVEL(TRACE_DMASND))
{
int FrameCycles, HblCounterVideo, LineCycles;
Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles );
LOG_TRACE_PRINT("DMA snd mode read: 0x%02x video_cyc=%d %d@%d pc=%x instr_cycle %d\n", dma.soundMode,
FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles);
}
}
/*-----------------------------------------------------------------------*/
/**
* Write word to sound mode register (0xff8921).
*/
void DmaSnd_SoundModeCtrl_WriteByte(void)
{
Uint16 SoundModeNew;
SoundModeNew = IoMem_ReadByte(0xff8921);
if(LOG_TRACE_LEVEL(TRACE_DMASND))
{
int FrameCycles, HblCounterVideo, LineCycles;
Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles );
LOG_TRACE_PRINT("DMA snd mode write: 0x%02x mode=%s freq=%d video_cyc=%d %d@%d pc=%x instr_cycle %d\n",
SoundModeNew, SoundModeNew & DMASNDMODE_MONO ? "mono" : "stereo" , DmaSndSampleRates[ SoundModeNew & 3 ],
FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles);
}
/* We maskout to only bits that exist on a real STE */
SoundModeNew &= 0x8f;
/* Are we switching from mono to stereo ? */
if ( ( dma.soundMode & DMASNDMODE_MONO ) && ( ( SoundModeNew & DMASNDMODE_MONO ) == 0 ) )
DmaSnd_FIFO_SetStereo ();
dma.soundMode = SoundModeNew;
/* We also write the masked value back into the emulated hw registers so we have a correct value there */
IoMem_WriteByte(0xff8921, dma.soundMode);
}
/* ---------------------- Microwire / LMC 1992 ---------------------- */
/**
* Handle the shifting/rotating of the microwire registers
* The microwire regs should be done after 16 usec = 32 NOPs = 128 cycles.
* That means we have to shift 16 times with a delay of 8 cycles.
* Microwire uses the MWK clock signal at 1 Mhz
*/
void DmaSnd_InterruptHandler_Microwire(void)
{
int i;
Uint16 cmd;
int cmd_len;
/* If emulated computer is the Falcon, let's the crossbar Microwire code do the job. */
if (ConfigureParams.System.nMachineType == MACHINE_FALCON) {
Crossbar_InterruptHandler_Microwire();
return;
}
/* How many cycle was this sound interrupt delayed (>= 0) */
microwire.pendingCyclesOver += -INT_CONVERT_FROM_INTERNAL ( PendingInterruptCount , INT_CPU_CYCLE );
/* Remove this interrupt from list and re-order */
CycInt_AcknowledgeInterrupt();
/* Shift the mask and data according to the number of cycles (8 cycles for a shift) */
do
{
--microwire.mwTransferSteps;
/* Shift data register until it becomes zero. */
IoMem_WriteWord(0xff8922, microwire.data<<(16-microwire.mwTransferSteps));
/* Rotate mask register */
IoMem_WriteWord(0xff8924, (microwire.mask<<(16-microwire.mwTransferSteps))
|(microwire.mask>>microwire.mwTransferSteps));
/* 8 cycles for 1 shift */
microwire.pendingCyclesOver -= 8;
}
while ((microwire.mwTransferSteps != 0) && (microwire.pendingCyclesOver >= 8) );
/* Is the transfer finished ? */
if (microwire.mwTransferSteps > 0)
{
/* No ==> start a new internal interrupt to continue to transfer the data */
microwire.pendingCyclesOver = 8 - microwire.pendingCyclesOver;
CycInt_AddRelativeInterrupt(microwire.pendingCyclesOver, INT_CPU_CYCLE, INTERRUPT_DMASOUND_MICROWIRE);
}
else
{
/* Yes : decode the address + command word according to the binary mask */
/* According to LMC1992 doc, command starts with the first '1' bit in the mask */
/* and ends when a '0' bits is received in the mask */
/* If we get a bad command, we must scan the rest of the mask in case there's a valid */
/* command in the remaining bits */
/* TODO [NP] : to be really cycle accurate, we should decode the command at the same */
/* time as we rotate mask/data, instead of doing it when 16 rotations were made. */
/* But this would not be noticeable, so leave it like this for now */
cmd = 0;
cmd_len = 0;
for ( i=15 ; i>=0 ; i-- )
if ( microwire.mask & ( 1 << i ) )
{
/* Start of command found, wait for next '0' bit or end of mask */
do
{
cmd <<= 1;
cmd_len++;
if ( microwire.data & ( 1 << i ) )
cmd |= 1;
i--;
}
while ( ( i >= 0 ) && ( microwire.mask & ( 1 << i ) ) );
if ( ( cmd_len >= 11 )
&& ( ( cmd >> ( cmd_len-2 ) ) & 0x03 ) == 0x02 )
break; /* We found a valid command */
LOG_TRACE ( TRACE_DMASND, "Microwire bad command=0x%x len=%d ignored mask=0x%x data=0x%x\n", cmd , cmd_len , microwire.mask , microwire.data );
if ( i < 0 )
return; /* All bits were tested, stop here */
/* Check remaining bits for a possible command */
cmd = 0;
cmd_len = 0;
}
//fprintf ( stderr , "mwire cmd=%x len=%d mask=%x data=%x\n" , cmd , cmd_len , microwire.mask , microwire.data );
/* The LMC 1992 address (first 2 bits) should be "10", else we ignore the command */
/* The address should be followed by at least 9 bits ; if more bits were received, */
/* then only the latest 9 ones should be kept */
if ( ( cmd_len < 11 )
|| ( ( cmd >> ( cmd_len-2 ) ) & 0x03 ) != 0x02 )
{
LOG_TRACE ( TRACE_DMASND, "Microwire bad command=0x%x len=%d ignored mask=0x%x data=0x%x\n", cmd , cmd_len , microwire.mask , microwire.data );
return;
}
/* Update the LMC 1992 commands */
switch ( ( cmd >> 6 ) & 0x7 ) {
case 0:
/* Mixing command */
LOG_TRACE ( TRACE_DMASND, "Microwire new mixing=0x%x\n", cmd & 0x3 );
microwire.mixing = cmd & 0x3;
break;
case 1:
/* Bass command */
LOG_TRACE ( TRACE_DMASND, "Microwire new bass=0x%x\n", cmd & 0xf );
microwire.bass = cmd & 0xf;
DmaSnd_Set_Tone_Level(LMC1992_Bass_Treble_Table[microwire.bass],
LMC1992_Bass_Treble_Table[microwire.treble]);
break;
case 2:
/* Treble command */
LOG_TRACE ( TRACE_DMASND, "Microwire new trebble=0x%x\n", cmd & 0xf );
microwire.treble = cmd & 0xf;
DmaSnd_Set_Tone_Level(LMC1992_Bass_Treble_Table[microwire.bass],
LMC1992_Bass_Treble_Table[microwire.treble]);
break;
case 3:
/* Master volume command */
LOG_TRACE ( TRACE_DMASND, "Microwire new master volume=0x%x\n", cmd & 0x3f );
microwire.masterVolume = LMC1992_Master_Volume_Table[ cmd & 0x3f ];
lmc1992.left_gain = (microwire.leftVolume * (Uint32)microwire.masterVolume) * (2.0/(65536.0*65536.0));
lmc1992.right_gain = (microwire.rightVolume * (Uint32)microwire.masterVolume) * (2.0/(65536.0*65536.0));
break;
case 4:
/* Right channel volume */
LOG_TRACE ( TRACE_DMASND, "Microwire new right volume=0x%x\n", cmd & 0x1f );
microwire.rightVolume = LMC1992_LeftRight_Volume_Table[ cmd & 0x1f ];
lmc1992.right_gain = (microwire.rightVolume * (Uint32)microwire.masterVolume) * (2.0/(65536.0*65536.0));
break;
case 5:
/* Left channel volume */
LOG_TRACE ( TRACE_DMASND, "Microwire new left volume=0x%x\n", cmd & 0x1f );
microwire.leftVolume = LMC1992_LeftRight_Volume_Table[ cmd & 0x1f ];
lmc1992.left_gain = (microwire.leftVolume * (Uint32)microwire.masterVolume) * (2.0/(65536.0*65536.0));
break;
default:
/* Do nothing */
LOG_TRACE ( TRACE_DMASND, "Microwire unknown command=0x%x len=%d ignored mask=0x%x data=0x%x\n", cmd , cmd_len , microwire.mask , microwire.data );
break;
}
}
}
/**
* Read word from microwire data register (0xff8922).
*/
void DmaSnd_MicrowireData_ReadWord(void)
{
/* Shifting is done in DmaSnd_InterruptHandler_Microwire! */
if(LOG_TRACE_LEVEL(TRACE_DMASND))
{
int FrameCycles, HblCounterVideo, LineCycles;
Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles );
LOG_TRACE_PRINT("Microwire data read: 0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n",
IoMem_ReadWord(0xff8922),
FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles);
}
}
/**
* Write word to microwire data register (0xff8922).
*/
void DmaSnd_MicrowireData_WriteWord(void)
{
/* Only update, if no shift is in progress */
if (!microwire.mwTransferSteps)
{
microwire.data = IoMem_ReadWord(0xff8922);
/* Start shifting events to simulate a microwire transfer */
microwire.mwTransferSteps = 16;
microwire.pendingCyclesOver = 8;
CycInt_AddRelativeInterrupt(microwire.pendingCyclesOver, INT_CPU_CYCLE, INTERRUPT_DMASOUND_MICROWIRE);
}
if(LOG_TRACE_LEVEL(TRACE_DMASND))
{
int FrameCycles, HblCounterVideo, LineCycles;
Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles );
LOG_TRACE_PRINT("Microwire data write: 0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n",
IoMem_ReadWord(0xff8922),
FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles);
}
}
/**
* Read word from microwire mask register (0xff8924).
*/
void DmaSnd_MicrowireMask_ReadWord(void)
{
/* Same as with data register, but mask is rotated, not shifted. */
if(LOG_TRACE_LEVEL(TRACE_DMASND))
{
int FrameCycles, HblCounterVideo, LineCycles;
Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles );
LOG_TRACE_PRINT("Microwire mask read: 0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n",
IoMem_ReadWord(0xff8924),
FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles);
}
}
/**
* Write word to microwire mask register (0xff8924).
*/
void DmaSnd_MicrowireMask_WriteWord(void)
{
/* Only update, if no shift is in progress */
if (!microwire.mwTransferSteps)
{
microwire.mask = IoMem_ReadWord(0xff8924);
}
if(LOG_TRACE_LEVEL(TRACE_DMASND))
{
int FrameCycles, HblCounterVideo, LineCycles;
Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles );
LOG_TRACE_PRINT("Microwire mask write: 0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n",
IoMem_ReadWord(0xff8924),
FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles);
}
}
/*-------------------Bass / Treble filter ---------------------------*/
/**
* Left voice Filter for Bass/Treble.
*/
static float DmaSnd_IIRfilterL(float xn)
{
static float data[2] = { 0.0, 0.0 };
float a, yn;
/* Input coefficients */
/* biquad1 Note: 'a' coefficients are subtracted */
a = lmc1992.left_gain * xn; /* a=g*xn; */
a -= lmc1992.coef[0] * data[0]; /* a1; wn-1 */
a -= lmc1992.coef[1] * data[1]; /* a2; wn-2 */
/* If coefficient scale */
/* factor = 0.5 then */
/* multiply by 2 */
/* Output coefficients */
yn = lmc1992.coef[2] * a; /* b0; */
yn += lmc1992.coef[3] * data[0]; /* b1; */
yn += lmc1992.coef[4] * data[1]; /* b2; */
data[1] = data[0]; /* wn-1 -> wn-2; */
data[0] = a; /* wn -> wn-1 */
return yn;
}
/**
* Right voice Filter for Bass/Treble.
*/
static float DmaSnd_IIRfilterR(float xn)
{
static float data[2] = { 0.0, 0.0 };
float a, yn;
/* Input coefficients */
/* biquad1 Note: 'a' coefficients are subtracted */
a = lmc1992.right_gain * xn; /* a=g*xn; */
a -= lmc1992.coef[0]*data[0]; /* a1; wn-1 */
a -= lmc1992.coef[1]*data[1]; /* a2; wn-2 */
/* If coefficient scale */
/* factor = 0.5 then */
/* multiply by 2 */
/* Output coefficients */
yn = lmc1992.coef[2]*a; /* b0; */
yn += lmc1992.coef[3]*data[0]; /* b1; */
yn += lmc1992.coef[4]*data[1]; /* b2; */
data[1] = data[0]; /* wn-1 -> wn-2; */
data[0] = a; /* wn -> wn-1 */
return yn;
}
/**
* LowPass Filter Left
*/
static Sint16 DmaSnd_LowPassFilterLeft(Sint16 in)
{
static Sint16 lowPassFilter[2] = { 0, 0 };
static Sint16 out = 0;
if (DmaSnd_LowPass)
{
out = lowPassFilter[0] + (lowPassFilter[1]<<1) + in;
lowPassFilter[0] = lowPassFilter[1];
lowPassFilter[1] = in;
return out; /* Filter Gain = 4 */
}else
{
return in << 2;
}
}
/**
* LowPass Filter Right
*/
static Sint16 DmaSnd_LowPassFilterRight(Sint16 in)
{
static Sint16 lowPassFilter[2] = { 0, 0 };
static Sint16 out = 0;
if (DmaSnd_LowPass)
{
out = lowPassFilter[0] + (lowPassFilter[1]<<1) + in;
lowPassFilter[0] = lowPassFilter[1];
lowPassFilter[1] = in;
return out; /* Filter Gain = 4 */
}else
{
return in << 2;
}
}
/**
* Set Bass and Treble tone level
*/
static void DmaSnd_Set_Tone_Level(int set_bass, int set_treb)
{
/* 13 levels; 0 through 12 correspond with -12dB to 12dB in 2dB steps */
lmc1992.coef[0] = lmc1992.treb_table[set_treb].a1 + lmc1992.bass_table[set_bass].a1;
lmc1992.coef[1] = lmc1992.treb_table[set_treb].a1 * lmc1992.bass_table[set_bass].a1;
lmc1992.coef[2] = lmc1992.treb_table[set_treb].b0 * lmc1992.bass_table[set_bass].b0;
lmc1992.coef[3] = lmc1992.treb_table[set_treb].b0 * lmc1992.bass_table[set_bass].b1 +
lmc1992.treb_table[set_treb].b1 * lmc1992.bass_table[set_bass].b0;
lmc1992.coef[4] = lmc1992.treb_table[set_treb].b1 * lmc1992.bass_table[set_bass].b1;
}
/**
* Compute the first order bass shelf
*/
static struct first_order_s *DmaSnd_Bass_Shelf(float g, float fc, float Fs)
{
static struct first_order_s bass;
float a1;
/* g, fc, Fs must be positve real numbers > 0.0 */
if (g < 1.0)
bass.a1 = a1 = (tanf(M_PI*fc/Fs) - g ) / (tanf(M_PI*fc/Fs) + g );
else
bass.a1 = a1 = (tanf(M_PI*fc/Fs) - 1.0) / (tanf(M_PI*fc/Fs) + 1.0);
bass.b0 = (1.0 + a1) * (g - 1.0) / 2.0 + 1.0;
bass.b1 = (1.0 + a1) * (g - 1.0) / 2.0 + a1;
return &bass;
}
/**
* Compute the first order treble shelf
*/
static struct first_order_s *DmaSnd_Treble_Shelf(float g, float fc, float Fs)
{
static struct first_order_s treb;
float a1;
/* g, fc, Fs must be positve real numbers > 0.0 */
if (g < 1.0)
treb.a1 = a1 = (g*tanf(M_PI*fc/Fs) - 1.0) / (g*tanf(M_PI*fc/Fs) + 1.0);
else
treb.a1 = a1 = (tanf(M_PI*fc/Fs) - 1.0) / (tanf(M_PI*fc/Fs) + 1.0);
treb.b0 = 1.0 + (1.0 - a1) * (g - 1.0) / 2.0;
treb.b1 = a1 + (a1 - 1.0) * (g - 1.0) / 2.0;
return &treb;
}
/**
* Compute the bass and treble tables (nAudioFrequency)
*/
void DmaSnd_Init_Bass_and_Treble_Tables(void)
{
struct first_order_s *bass;
struct first_order_s *treb;
float dB_adjusted, dB, g, fc_bt, fc_tt, Fs;
int n;
fc_bt = 118.2763;
fc_tt = 8438.756;
Fs = (float)nAudioFrequency;
if ((Fs < 8000.0) || (Fs > 96000.0))
Fs = 44100.0;
if (fc_tt > 0.5*0.8*Fs)
{
fc_tt = 0.5*0.8*Fs;
dB_adjusted = 2.0 * 0.5*0.8*Fs/fc_tt;
}else
{
dB_adjusted = 2.0;
}
for (dB = dB_adjusted*(TONE_STEPS-1)/2, n = TONE_STEPS; n--; dB -= dB_adjusted)
{
g = powf(10.0, dB/20.0); /* 12dB to -12dB */
treb = DmaSnd_Treble_Shelf(g, fc_tt, Fs);
lmc1992.treb_table[n].a1 = treb->a1;
lmc1992.treb_table[n].b0 = treb->b0;
lmc1992.treb_table[n].b1 = treb->b1;
}
for (dB = 12.0, n = TONE_STEPS; n--; dB -= 2.0)
{
g = powf(10.0, dB/20.0); /* 12dB to -12dB */
bass = DmaSnd_Bass_Shelf(g, fc_bt, Fs);
lmc1992.bass_table[n].a1 = bass->a1;
lmc1992.bass_table[n].b0 = bass->b0;
lmc1992.bass_table[n].b1 = bass->b1;
}
DmaSnd_Set_Tone_Level(LMC1992_Bass_Treble_Table[microwire.bass & 0xf],
LMC1992_Bass_Treble_Table[microwire.treble & 0xf]);
/* Initialize IIR Filter Gain and use as a Volume Control */
lmc1992.left_gain = (microwire.leftVolume * (Uint32)microwire.masterVolume) * (2.0/(65536.0*65536.0));
lmc1992.right_gain = (microwire.rightVolume * (Uint32)microwire.masterVolume) * (2.0/(65536.0*65536.0));
/* Anti-alias filter is not required when nAudioFrequency == 50066 Hz */
if (nAudioFrequency>50000 && nAudioFrequency<50100)
DmaSnd_LowPass = false;
else
DmaSnd_LowPass = true;
}