1391 lines
46 KiB
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(µwire, 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;
|
|
}
|