Merge pull request #5417 from Cacodemon345/cms-saasound

Switch to SAASound for CMS
This commit is contained in:
Miran Grča
2025-04-01 11:56:02 +02:00
committed by GitHub
26 changed files with 3006 additions and 113 deletions

View File

@@ -7,23 +7,20 @@
#define MASTER_CLOCK 7159090
typedef struct cms_t {
int addrs[2];
uint8_t regs[2][32];
uint16_t latch[2][6];
int freq[2][6];
float count[2][6];
int vol[2][6][2];
int stat[2][6];
uint16_t noise[2][2];
uint16_t noisefreq[2][2];
int noisecount[2][2];
int noisetype[2][2];
#ifdef SAASOUND_H_INCLUDED
SAASND saasound;
SAASND saasound2;
#else
void* saasound;
void* saasound2;
#endif
uint8_t latched_data;
int16_t buffer[SOUNDBUFLEN * 2];
int16_t buffer[WTBUFLEN * 2];
int16_t buffer2[WTBUFLEN * 2];
int pos;
int pos, pos2;
} cms_t;
extern void cms_update(cms_t *cms);

View File

@@ -180,6 +180,9 @@ endif()
add_subdirectory(ymfm)
target_link_libraries(86Box ymfm)
add_subdirectory(saasound)
target_link_libraries(86Box saasound)
if(GUSMAX)
target_compile_definitions(snd PRIVATE USE_GUSMAX)
endif()

View File

@@ -0,0 +1,16 @@
add_library(saasound OBJECT
SAAAmp.cpp
SAAAmp.h
SAADevice.cpp
SAADevice.h
SAAEnv.cpp
SAAEnv.h
SAAFreq.cpp
SAAFreq.h
SAAImpl.cpp
SAAImpl.h
SAANoise.cpp
SAANoise.h
SAASndC.cpp
SAASndC.h
SAASound.cpp)

203
src/sound/saasound/SAAAmp.cpp Executable file
View File

@@ -0,0 +1,203 @@
// Part of SAASound copyright 1998-2018 Dave Hooper <dave@beermex.com>
//
// SAAAmp.cpp: implementation of the CSAAAmp class.
// This class handles Tone/Noise mixing, Envelope application and
// amplification.
//
//////////////////////////////////////////////////////////////////////
#include "SAASound.h"
#include "types.h"
#include "SAANoise.h"
#include "SAAEnv.h"
#include "SAAFreq.h"
#include "SAAAmp.h"
#include "defns.h"
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
CSAAAmp::CSAAAmp(CSAAFreq * const ToneGenerator, const CSAANoise * const NoiseGenerator, const CSAAEnv * const EnvGenerator)
:
m_pcConnectedToneGenerator(ToneGenerator),
m_pcConnectedNoiseGenerator(NoiseGenerator),
m_pcConnectedEnvGenerator(EnvGenerator),
m_bUseEnvelope(EnvGenerator != NULL)
{
leftlevel = 0;
leftlevela0x0e = 0;
rightlevel = 0;
rightlevela0x0e = 0;
m_nMixMode = 0;
m_bMute=true;
m_bSync = false;
m_nOutputIntermediate=0;
last_level_byte=0;
SetAmpLevel(0x00);
}
CSAAAmp::~CSAAAmp()
{
// Nothing to do
}
void CSAAAmp::SetAmpLevel(BYTE level_byte)
{
// if level unchanged since last call then do nothing
if (level_byte != last_level_byte)
{
last_level_byte = level_byte;
leftlevel = level_byte & 0x0f;
leftlevela0x0e = leftlevel & 0x0e;
rightlevel = (level_byte >> 4) & 0x0f;
rightlevela0x0e = rightlevel & 0x0e;
}
}
void CSAAAmp::SetToneMixer(BYTE bEnabled)
{
if (bEnabled == 0)
{
// clear mixer bit
m_nMixMode &= ~(0x01);
}
else
{
// set mixer bit
m_nMixMode |= 0x01;
}
}
void CSAAAmp::SetNoiseMixer(BYTE bEnabled)
{
if (bEnabled == 0)
{
m_nMixMode &= ~(0x02);
}
else
{
m_nMixMode |= 0x02;
}
}
void CSAAAmp::Mute(bool bMute)
{
// m_bMute refers to the GLOBAL mute setting (register 28 bit 0)
// NOT the per-channel mixer settings !!
m_bMute = bMute;
}
void CSAAAmp::Sync(bool bSync)
{
// m_bSync refers to the GLOBAL sync setting (register 28 bit 1)
m_bSync = bSync;
}
void CSAAAmp::Tick(void)
{
// updates m_nOutputIntermediate to 0, 1 or 2
//
// connected oscillator always ticks (this isn't really connected to the amp)
int level = m_pcConnectedToneGenerator->Tick();
switch (m_nMixMode)
{
case 0:
// no tone or noise for this channel
m_nOutputIntermediate = 0;
break;
case 1:
// tone only for this channel
m_nOutputIntermediate = level * 2;
// NOTE: ConnectedToneGenerator returns either 0 or 1
break;
case 2:
// noise only for this channel
m_nOutputIntermediate = m_pcConnectedNoiseGenerator->Level() * 2;
// NOTE: ConnectedNoiseGenerator()->Level() returns either 0 or 1
break;
case 3:
// tone+noise for this channel ... mixing algorithm :
// tone noise output
// 0 0 0
// 1 0 2
// 0 1 0
// 1 1 1
// = 2 * tone - 1 * (tone & noise)
// = tone * (2 - noise)
m_nOutputIntermediate = level * (2 - m_pcConnectedNoiseGenerator->Level());
break;
}
// intermediate is between 0 and 2
}
inline int CSAAAmp::EffectiveAmplitude(int amp, int env) const
{
// Return the effective amplitude of the low-pass-filtered result of the logical
// AND of the amplitude PDM and envelope PDM patterns. This is a more accurate
// evaluation of the SAA than simply returning amp * env , based on how the SAA
// implements pulse-density modulation.
static const int pdm[16][16] = {
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,2,2,2,2,2,2,2,2,4,4,4,4},
{0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8},
{0,1,1,2,4,5,5,6,6,7,7,8,10,11,11,12},
{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15},
{0,1,2,3,6,7,8,9,10,11,12,13,16,17,18,19},
{0,2,3,5,6,8,9,11,12,14,15,17,18,20,21,23},
{0,2,3,5,8,10,11,13,14,16,17,19,22,24,25,27},
{0,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30},
{0,2,4,6,10,12,14,16,18,20,22,24,28,30,32,34},
{0,3,5,8,10,13,15,18,20,23,25,28,30,33,35,38},
{0,3,5,8,12,15,17,20,22,25,27,30,34,37,39,42},
{0,3,6,9,12,15,18,21,24,27,30,33,36,39,42,45},
{0,3,6,9,14,17,20,23,26,29,32,35,40,43,46,49},
{0,4,7,11,14,18,21,25,28,32,35,39,42,46,49,53},
{0,4,7,11,16,20,23,27,30,34,37,41,46,50,53,57}
};
return(pdm[amp][env] * 4);
}
void CSAAAmp::TickAndOutputStereo(unsigned int & left, unsigned int & right)
{
// This returns a value between 0 and 480 inclusive.
// This represents the full dynamic range of one output mixer (tone, or noise+tone, at full volume,
// without envelopes enabled). Note that, with envelopes enabled, the actual dynamic range
// is reduced on-chip to just over 88% of this (424), so the "loudest" output requires disabling envs.
// NB for 6 channels at full volume, with simple additive mixing, you would see a combined
// output of 2880, and a multiplier of 11 (=31680) fits comfortably within 16-bit signed output range.
if (m_bSync)
{
// TODO check this
left = right = 0;
return;
}
// first, do the Tick:
Tick();
// now calculate the returned amplitude for this sample:
////////////////////////////////////////////////////////
if (m_bMute)
{
left = right = 0;
}
else if (m_bUseEnvelope && m_pcConnectedEnvGenerator->IsActive())
{
left = EffectiveAmplitude(m_pcConnectedEnvGenerator->LeftLevel(), leftlevela0x0e) * (2 - m_nOutputIntermediate);
right = EffectiveAmplitude(m_pcConnectedEnvGenerator->RightLevel(), rightlevela0x0e) * (2 - m_nOutputIntermediate);
}
else
{
left = leftlevel * m_nOutputIntermediate * 16;
right = rightlevel * m_nOutputIntermediate * 16;
}
}

44
src/sound/saasound/SAAAmp.h Executable file
View File

@@ -0,0 +1,44 @@
// Part of SAASound copyright 1998-2018 Dave Hooper <dave@beermex.com>
//
// SAAAmp.h: interface for the CSAAAmp class.
// This class handles Tone/Noise mixing, Envelope application and
// amplification.
//
//////////////////////////////////////////////////////////////////////
#ifndef SAAAMP_H_INCLUDED
#define SAAAMP_H_INCLUDED
class CSAAAmp
{
private:
int leftlevel;
int leftlevela0x0e;
int rightlevel;
int rightlevela0x0e;
int m_nOutputIntermediate;
unsigned int m_nMixMode;
CSAAFreq * const m_pcConnectedToneGenerator; // not const because amp calls ->Tick()
const CSAANoise * const m_pcConnectedNoiseGenerator;
const CSAAEnv * const m_pcConnectedEnvGenerator;
const bool m_bUseEnvelope;
mutable bool m_bMute;
mutable bool m_bSync;
mutable BYTE last_level_byte;
int EffectiveAmplitude(int amp, int env) const;
public:
CSAAAmp(CSAAFreq * const ToneGenerator, const CSAANoise * const NoiseGenerator, const CSAAEnv * const EnvGenerator);
~CSAAAmp();
void SetAmpLevel(BYTE level_byte); // really just a BYTE
void SetToneMixer(BYTE bEnabled);
void SetNoiseMixer(BYTE bEnabled);
void Mute(bool bMute);
void Sync(bool bSync);
void Tick(void);
void TickAndOutputStereo(unsigned int & left, unsigned int & right);
};
#endif // SAAAMP_H_INCLUDED

View File

@@ -0,0 +1,41 @@
// Part of SAASound copyright 2020 Dave Hooper <dave@beermex.com>
//
// SAAConfig.h: configuration file handler class
//
//////////////////////////////////////////////////////////////////////
#include "defns.h"
#ifdef USE_CONFIG_FILE
#ifndef SAA_CONFIG_H_INCLUDED
#define SAA_CONFIG_H_INCLUDED
#define INI_READONLY
#define INI_ANSIONLY /*nb not really 'ANSI', this just forces all read/write to use 8-bit char*/
#include "minIni/minIni.h"
class SAAConfig
{
private:
minIni m_minIni;
bool m_bHasReadConfig;
public:
bool m_bGenerateRegisterLogs;
bool m_bGeneratePcmLogs;
bool m_bGeneratePcmSeparateChannels;
t_string m_strRegisterLogPath;
t_string m_strPcmOutputPath;
unsigned int m_nOversample;
bool m_bHighpass;
double m_nBoost;
SAAConfig();
void ReadConfig();
t_string getChannelPcmOutputPath(int);
};
#endif // SAA_CONFIG_H_INCLUDED
#endif // USE_CONFIG_FILE

View File

@@ -0,0 +1,392 @@
// Part of SAASound copyright 2020 Dave Hooper <dave@beermex.com>
//
// SAADevice.cpp: connecting the subcomponents of the SAA1099 together.
// This class handles device inputs and outputs (clocking, data and
// address bus, and simulated output)
//
//////////////////////////////////////////////////////////////////////
#include "SAASound.h"
#include "types.h"
#include "SAAEnv.h"
#include "SAANoise.h"
#include "SAAFreq.h"
#include "SAAAmp.h"
#include "SAASound.h"
#include "SAAImpl.h"
#include "defns.h"
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
CSAADevice::CSAADevice()
:
m_nCurrentSaaReg(0),
m_bOutputEnabled(false),
m_bSync(false),
m_bHighpass(true),
m_nOversample(0),
m_Noise0(0xffffffff),
m_Noise1(0xffffffff),
m_Env0(),
m_Env1(),
m_Osc0(&m_Noise0, NULL),
m_Osc1(NULL, &m_Env0),
m_Osc2(NULL, NULL),
m_Osc3(&m_Noise1, NULL),
m_Osc4(NULL, &m_Env1),
m_Osc5(NULL, NULL),
m_Amp0(&m_Osc0, &m_Noise0, NULL),
m_Amp1(&m_Osc1, &m_Noise0, NULL),
m_Amp2(&m_Osc2, &m_Noise0, &m_Env0),
m_Amp3(&m_Osc3, &m_Noise1, NULL),
m_Amp4(&m_Osc4, &m_Noise1, NULL),
m_Amp5(&m_Osc5, &m_Noise1, &m_Env1)
{
// Create and link up the objects that make up the emulator
Noise[0] = &m_Noise0;
Noise[1] = &m_Noise1;
Env[0] = &m_Env0;
Env[1] = &m_Env1;
// Create oscillators (tone generators) and link to noise generators and
// envelope controllers
Osc[0] = &m_Osc0;
Osc[1] = &m_Osc1;
Osc[2] = &m_Osc2;
Osc[3] = &m_Osc3;
Osc[4] = &m_Osc4;
Osc[5] = &m_Osc5;
// Create amplification/mixing stages and link to appropriate oscillators,
// noise generators and envelope controllers
Amp[0] = &m_Amp0;
Amp[1] = &m_Amp1;
Amp[2] = &m_Amp2;
Amp[3] = &m_Amp3;
Amp[4] = &m_Amp4;
Amp[5] = &m_Amp5;
_SetClockRate(EXTERNAL_CLK_HZ);
_SetOversample(DEFAULT_OVERSAMPLE);
}
CSAADevice::~CSAADevice()
{
}
//////////////////////////////////////////////////////////////////////
// CSAASound members
//////////////////////////////////////////////////////////////////////
void CSAADevice::_SetClockRate(unsigned int nClockRate)
{
m_Osc0._SetClockRate(nClockRate);
m_Osc1._SetClockRate(nClockRate);
m_Osc2._SetClockRate(nClockRate);
m_Osc3._SetClockRate(nClockRate);
m_Osc4._SetClockRate(nClockRate);
m_Osc5._SetClockRate(nClockRate);
m_Noise0._SetClockRate(nClockRate);
m_Noise1._SetClockRate(nClockRate);
}
void CSAADevice::_SetSampleRate(unsigned int nSampleRate)
{
m_Osc0._SetSampleRate(nSampleRate);
m_Osc1._SetSampleRate(nSampleRate);
m_Osc2._SetSampleRate(nSampleRate);
m_Osc3._SetSampleRate(nSampleRate);
m_Osc4._SetSampleRate(nSampleRate);
m_Osc5._SetSampleRate(nSampleRate);
m_Noise0._SetSampleRate(nSampleRate);
m_Noise1._SetSampleRate(nSampleRate);
}
void CSAADevice::_SetOversample(unsigned int nOversample)
{
if (nOversample != m_nOversample)
{
m_nOversample = nOversample;
m_Osc0._SetOversample(nOversample);
m_Osc1._SetOversample(nOversample);
m_Osc2._SetOversample(nOversample);
m_Osc3._SetOversample(nOversample);
m_Osc4._SetOversample(nOversample);
m_Osc5._SetOversample(nOversample);
m_Noise0._SetOversample(nOversample);
m_Noise1._SetOversample(nOversample);
}
}
void CSAADevice::_WriteData(BYTE nData)
{
#if defined(DEBUG) || defined(DEBUGSAA)
m_Reg[m_nCurrentSaaReg] = nData;
#endif
// route nData to the appropriate place
switch (m_nCurrentSaaReg)
{
// Amplitude data (==> Amp)
case 0:
m_Amp0.SetAmpLevel(nData);
break;
case 1:
m_Amp1.SetAmpLevel(nData);
break;
case 2:
m_Amp2.SetAmpLevel(nData);
break;
case 3:
m_Amp3.SetAmpLevel(nData);
break;
case 4:
m_Amp4.SetAmpLevel(nData);
break;
case 5:
m_Amp5.SetAmpLevel(nData);
break;
// Freq data (==> Osc)
case 8:
m_Osc0.SetFreqOffset(nData);
break;
case 9:
m_Osc1.SetFreqOffset(nData);
break;
case 10:
m_Osc2.SetFreqOffset(nData);
break;
case 11:
m_Osc3.SetFreqOffset(nData);
break;
case 12:
m_Osc4.SetFreqOffset(nData);
break;
case 13:
m_Osc5.SetFreqOffset(nData);
break;
// Freq octave data (==> Osc) for channels 0,1
case 16:
m_Osc0.SetFreqOctave(nData & 0x07);
m_Osc1.SetFreqOctave((nData >> 4) & 0x07);
break;
// Freq octave data (==> Osc) for channels 2,3
case 17:
m_Osc2.SetFreqOctave(nData & 0x07);
m_Osc3.SetFreqOctave((nData >> 4) & 0x07);
break;
// Freq octave data (==> Osc) for channels 4,5
case 18:
m_Osc4.SetFreqOctave(nData & 0x07);
m_Osc5.SetFreqOctave((nData >> 4) & 0x07);
break;
// Tone mixer control (==> Amp)
case 20:
m_Amp0.SetToneMixer(nData & 0x01);
m_Amp1.SetToneMixer(nData & 0x02);
m_Amp2.SetToneMixer(nData & 0x04);
m_Amp3.SetToneMixer(nData & 0x08);
m_Amp4.SetToneMixer(nData & 0x10);
m_Amp5.SetToneMixer(nData & 0x20);
break;
// Noise mixer control (==> Amp)
case 21:
m_Amp0.SetNoiseMixer(nData & 0x01);
m_Amp1.SetNoiseMixer(nData & 0x02);
m_Amp2.SetNoiseMixer(nData & 0x04);
m_Amp3.SetNoiseMixer(nData & 0x08);
m_Amp4.SetNoiseMixer(nData & 0x10);
m_Amp5.SetNoiseMixer(nData & 0x20);
break;
// Noise frequency/source control (==> Noise)
case 22:
m_Noise0.SetSource(nData & 0x03);
m_Noise1.SetSource((nData >> 4) & 0x03);
break;
// Envelope control data (==> Env) for envelope controller #0
case 24:
m_Env0.SetEnvControl(nData);
break;
// Envelope control data (==> Env) for envelope controller #1
case 25:
m_Env1.SetEnvControl(nData);
break;
// Global enable and reset (sync) controls
case 28:
{
// Reset (sync) bit
bool bSync = bool(nData & 0x02);
if (bSync != m_bSync)
{
// Sync all devices
// This amounts to telling them all to reset to a
// known state, which is also a state that doesn't change
// (i.e. no audio output, although there are some exceptions)
// bSync=true => all devices are sync (aka reset);
// bSync=false => all devices are allowed to run and generate changing output
m_Osc0.Sync(bSync);
m_Osc1.Sync(bSync);
m_Osc2.Sync(bSync);
m_Osc3.Sync(bSync);
m_Osc4.Sync(bSync);
m_Osc5.Sync(bSync);
m_Noise0.Sync(bSync);
m_Noise1.Sync(bSync);
m_Amp0.Sync(bSync);
m_Amp1.Sync(bSync);
m_Amp2.Sync(bSync);
m_Amp3.Sync(bSync);
m_Amp4.Sync(bSync);
m_Amp5.Sync(bSync);
m_bSync = bSync;
}
// Global mute bit
bool bOutputEnabled = bool(nData & 0x01);
if (bOutputEnabled != m_bOutputEnabled)
{
// unmute all amps - sound 'enabled'
m_Amp0.Mute(!bOutputEnabled);
m_Amp1.Mute(!bOutputEnabled);
m_Amp2.Mute(!bOutputEnabled);
m_Amp3.Mute(!bOutputEnabled);
m_Amp4.Mute(!bOutputEnabled);
m_Amp5.Mute(!bOutputEnabled);
m_bOutputEnabled = bOutputEnabled;
}
}
break;
default:
// anything else means data is being written to a register
// that is not used within the SAA-1099 architecture
// hence, we ignore it.
{}
}
}
void CSAADevice::_WriteAddress(BYTE nReg)
{
m_nCurrentSaaReg = nReg & 31;
if (m_nCurrentSaaReg == 24)
{
m_Env0.ExternalClock();
}
else if (m_nCurrentSaaReg == 25)
{
m_Env1.ExternalClock();
}
}
#if 1
BYTE CSAADevice::_ReadAddress(void)
{
// Not a real hardware function of the SAA-1099, which is write-only
// However, this is used by SAAImpl to generate debug logs (if enabled)
return(m_nCurrentSaaReg);
}
#endif
#if defined(DEBUG)
BYTE CSAADevice::_ReadData(void)
{
// Not a real hardware function of the SAA-1099, which is write-only
// This is only compiled for Debug builds
return(m_Reg[m_nCurrentSaaReg]);
}
#endif
void CSAADevice::_TickAndOutputStereo(unsigned int& left_mixed, unsigned int& right_mixed)
{
unsigned int temp_left, temp_right;
unsigned int accum_left = 0, accum_right = 0;
for (int i = 1 << m_nOversample; i > 0; i--)
{
m_Noise0.Tick();
m_Noise1.Tick();
m_Amp0.TickAndOutputStereo(temp_left, temp_right);
accum_left += temp_left;
accum_right += temp_right;
m_Amp1.TickAndOutputStereo(temp_left, temp_right);
accum_left += temp_left;
accum_right += temp_right;
m_Amp2.TickAndOutputStereo(temp_left, temp_right);
accum_left += temp_left;
accum_right += temp_right;
m_Amp3.TickAndOutputStereo(temp_left, temp_right);
accum_left += temp_left;
accum_right += temp_right;
m_Amp4.TickAndOutputStereo(temp_left, temp_right);
accum_left += temp_left;
accum_right += temp_right;
m_Amp5.TickAndOutputStereo(temp_left, temp_right);
accum_left += temp_left;
accum_right += temp_right;
}
left_mixed = accum_left;
right_mixed = accum_right;
}
void CSAADevice::_TickAndOutputSeparate(unsigned int& left_mixed, unsigned int& right_mixed,
unsigned int& left0, unsigned int& right0,
unsigned int& left1, unsigned int& right1,
unsigned int& left2, unsigned int& right2,
unsigned int& left3, unsigned int& right3,
unsigned int& left4, unsigned int& right4,
unsigned int& left5, unsigned int& right5
)
{
unsigned int temp_left, temp_right;
unsigned int accum_left = 0, accum_right = 0;
left0 = left1 = left2 = left3 = left4 = left5 = 0;
right0 = right1 = right2 = right3 = right4 = right5 = 0;
for (int i = 1 << m_nOversample; i > 0; i--)
{
m_Noise0.Tick();
m_Noise1.Tick();
m_Amp0.TickAndOutputStereo(temp_left, temp_right);
left0 += temp_left;
right0 += temp_right;
accum_left += temp_left;
accum_right += temp_right;
m_Amp1.TickAndOutputStereo(temp_left, temp_right);
left1 += temp_left;
right1 += temp_right;
accum_left += temp_left;
accum_right += temp_right;
m_Amp2.TickAndOutputStereo(temp_left, temp_right);
left2 += temp_left;
right2 += temp_right;
accum_left += temp_left;
accum_right += temp_right;
m_Amp3.TickAndOutputStereo(temp_left, temp_right);
left3 += temp_left;
right3 += temp_right;
accum_left += temp_left;
accum_right += temp_right;
m_Amp4.TickAndOutputStereo(temp_left, temp_right);
left4 += temp_left;
right4 += temp_right;
accum_left += temp_left;
accum_right += temp_right;
m_Amp5.TickAndOutputStereo(temp_left, temp_right);
left5 += temp_left;
right5 += temp_right;
accum_left += temp_left;
accum_right += temp_right;
}
left_mixed = accum_left;
right_mixed = accum_right;
}

View File

@@ -0,0 +1,69 @@
// Part of SAASound copyright 2020 Dave Hooper <dave@beermex.com>
//
// SAADevice.h: connecting the subcomponents of the SAA1099 together.
// This class handles device inputs and outputs (clocking, data and
// address bus, and simulated output)
//
//////////////////////////////////////////////////////////////////////
#ifndef SAADEVICE_H_INCLUDED
#define SAADEVICE_H_INCLUDED
#include "SAASound.h"
#include "SAANoise.h"
#include "SAAEnv.h"
#include "SAAFreq.h"
#include "SAAAmp.h"
class CSAADevice
{
private:
int m_nCurrentSaaReg;
bool m_bOutputEnabled;
bool m_bSync;
bool m_bHighpass;
int m_nOversample;
CSAANoise m_Noise0, m_Noise1;
CSAAEnv m_Env0, m_Env1;
CSAAFreq m_Osc0, m_Osc1, m_Osc2, m_Osc3, m_Osc4, m_Osc5;
CSAAAmp m_Amp0, m_Amp1, m_Amp2, m_Amp3, m_Amp4, m_Amp5;
CSAANoise* Noise[2];
CSAAEnv* Env[2];
CSAAFreq* Osc[6];
CSAAAmp* Amp[6];
#if defined(DEBUG) || defined(DEBUGSAA)
BYTE m_Reg[32];
#endif
public:
CSAADevice();
~CSAADevice();
void _WriteAddress(BYTE nReg);
void _WriteData(BYTE nData);
#if 1
BYTE _ReadAddress(void);
#endif
#if defined(DEBUG)
BYTE _ReadData(void);
#endif
void _SetClockRate(unsigned int nClockRate);
void _SetSampleRate(unsigned int nSampleRate);
void _SetOversample(unsigned int nOversample);
void _TickAndOutputStereo(unsigned int& left_mixed, unsigned int& right_mixed);
void _TickAndOutputSeparate(unsigned int& left_mixed, unsigned int& right_mixed,
unsigned int& left0, unsigned int& right0,
unsigned int& left1, unsigned int& right1,
unsigned int& left2, unsigned int& right2,
unsigned int& left3, unsigned int& right3,
unsigned int& left4, unsigned int& right4,
unsigned int& left5, unsigned int& right5
);
};
#endif // SAADEVICE_H_INCLUDED

380
src/sound/saasound/SAAEnv.cpp Executable file
View File

@@ -0,0 +1,380 @@
// Part of SAASound copyright 1998-2018 Dave Hooper <dave@beermex.com>
//
// SAAEnv.cpp: implementation of the CSAAEnv class.
//
//////////////////////////////////////////////////////////////////////
#include "SAASound.h"
#include "types.h"
#include "SAAEnv.h"
//////////////////////////////////////////////////////////////////////
// Static member initialisation
//////////////////////////////////////////////////////////////////////
const ENVDATA CSAAEnv::cs_EnvData[8] =
{
{1,false, { {{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}},
{{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}}}},
{1,true, { {{15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15},{15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15}},
{{14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14},{14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14}}}},
{1,false, { {{15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}},
{{14,14,12,12,10,10,8,8,6,6,4,4,2,2,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}}}},
{1,true, { {{15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}},
{{14,14,12,12,10,10,8,8,6,6,4,4,2,2,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}}}},
{2,false, { {{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}, {15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0}},
{{0,0,2,2,4,4,6,6,8,8,10,10,12,12,14,14}, {14,14,12,12,10,10,8,8,6,6,4,4,2,2,0,0}}}},
{2,true, { {{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}, {15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0}},
{{0,0,2,2,4,4,6,6,8,8,10,10,12,12,14,14}, {14,14,12,12,10,10,8,8,6,6,4,4,2,2,0,0}}}},
{1,false, { {{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}},
{{0,0,2,2,4,4,6,6,8,8,10,10,12,12,14,14}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}}}},
{1,true, { {{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}},
{{0,0,2,2,4,4,6,6,8,8,10,10,12,12,14,14}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}}}}
};
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
CSAAEnv::CSAAEnv()
:
m_bEnabled(false),
m_bNewData(false),
m_nNextData(0),
m_bEnvelopeEnded(true),
m_nPhase(0),
m_nPhasePosition(0),
m_nResolution(1)
{
// initialise itself with the value 'zero'
SetEnvControl(0);
}
CSAAEnv::~CSAAEnv()
{
// Nothing to do
}
void CSAAEnv::InternalClock(void)
{
// will only do something if envelope clock mode is set to internal
// and the env control is enabled
if (m_bEnabled && (!m_bClockExternally)) Tick();
}
void CSAAEnv::ExternalClock(void)
{
// will only do something if envelope clock mode is set to external
// and the env control is enabled
if (m_bClockExternally && m_bEnabled) Tick();
}
void CSAAEnv::SetEnvControl(int nData)
{
// process immediate stuff first:
// start with the Enabled flag. if env is disabled,
// there's not much to do
bool bEnabled = ((nData & 0x80)==0x80);
if (!bEnabled && !m_bEnabled)
return;
m_bEnabled = bEnabled;
if (!m_bEnabled)
{
// env control was enabled, and now disabled
// Any subsequent env control changes are immediate.
m_bEnvelopeEnded = true;
return;
}
// Resolution (3bit/4bit) is also immediately processed
int new_resolution = ((nData & 0x10) == 0x10) ? 2 : 1;
// NOTE: undocumented behaviour when changing resolution mid-waveform
// Empirically, the following matches observations:
// * When ticking the env generator with 4-bit resolution, the position += 1
// * When ticking the env generator with 3-bit resolution, the position += 2
// * When changing between 4-bit resolution and 3-bit resolution
// without ticking the env generator, the position is unchanged
// (although, effectively, the LSB is ignored. Purely as an implementation
// detail, I'm implementing this as clearing the LSB ie LSB=0; see next point)
// * When changing between 3-bit resolution and 4-bit resolution
// without ticking the env generator, the position LSB is set to 1
// See test case: envext_34b
//
if (m_nResolution == 1 && new_resolution == 2)
{
// change from 4-bit to 3-bit
m_nPhasePosition &= 0xe;
}
else if (m_nResolution == 2 && new_resolution == 1)
{
// change from 3-bit to 4-bit
m_nPhasePosition |= 0x1;
}
m_nResolution = new_resolution;
// now buffered stuff: but only if it's ok to, and only if the
// envgenerator is not disabled. otherwise it just stays buffered until
// the Tick() function sets m_bEnvelopeEnded to true and realises there is
// already some new data waiting
if (m_bEnvelopeEnded)
{
SetNewEnvData(nData); // also does the SetLevels() call for us.
m_bNewData=false;
}
else
{
// since the 'next resolution' changes arrive unbuffered, we
// may need to change the current level because of this:
SetLevels();
// store current new data, and set the newdata flag:
m_bNewData = true;
m_nNextData = nData;
}
}
int CSAAEnv::LeftLevel(void) const
{
return m_nLeftLevel;
}
int CSAAEnv::RightLevel(void) const
{
return m_nRightLevel;
}
inline void CSAAEnv::Tick(void)
{
// if disabled, do nothing
if (!m_bEnabled) // m_bEnabled is set directly, not buffered, so this is ok
{
// for sanity, reset stuff:
m_bEnvelopeEnded = true;
m_nPhase = 0;
m_nPhasePosition = 0;
return;
}
// else : m_bEnabled
if (m_bEnvelopeEnded)
{
// do nothing
// (specifically, don't change the values of m_bEnvelopeEnded,
// m_nPhase and m_nPhasePosition, as these will still be needed
// by SetLevels() should it be called again)
return;
}
// else : !m_bEnvelopeEnded
// Continue playing the same envelope ...
// increments the phaseposition within an envelope.
// also handles looping and resolution appropriately.
// Changes the level of the envelope accordingly
// through calling SetLevels() . This must be called after making
// any changes that will affect the output levels of the env controller!!
// SetLevels also handles left-right channel inverting
// increment phase position
m_nPhasePosition += m_nResolution;
// if this means we've gone past 16 (the end of a phase)
// then change phase, and if necessary, loop
// Refer to datasheet for meanings of (3) and (4) in following text
// w.r.t SAA1099 envelopes
// Note that we will always reach position (3) or (4), even if we keep toggling
// resolution from 4-bit to 3-bit and back, because the counter will always wrap to 0.
// In fact it's quite elegant:
// No matter how you increment and toggle and increment and toggle, the counter
// will at some point be either 0xe (either 4-bit mode or 3-bit mode) or 0xf (4-bit mode only).
// Depending on the mode, even if you change the mode, the next increment,
// or the one after it, will then take it to 0.
// 0xe + 2 (3bit mode) => 0x0
// 0xe + 1 (4bit mode) => 0xf
// 0xf + 1 (4bit mode) => 0x0
// 0xe -> (toggle 3bit mode to 4bit mode) => 0xf
// 0xe -> (toggle 4bit mode to 3bit mode) => 0xe
// 0xf -> (toggle 4bit mode to 3bit mode) => 0xe
//
// but there is a subtlety (of course), which is that any changes at point (3)
// can take place immediately you hit point (3), but changes at point (4) are actually
// only acted upon when the counter transitions from 0xe (or 0xf) to 0x0 (which also
// means that, for these looping envelopes, which are the ones that have a point(4),
// immediately after the counter wrapping to 0x0, a write to the env data register will
// NOT set the waveform and will NOT reset the phase/phaseposition (even though it
// will still let you toggle the 4bit/3bit mode, which will change the phaseposition LSB!)
// See test case: envext_34c
bool bProcessNewDataIfAvailable = false;
if (m_nPhasePosition >= 16)
{
m_nPhase++;
// if we should loop, then do so - and we've reached position (4)
// otherwise, if we shouldn't loop,
// then we've reached position (3) and so we say that
// we're ok for new data.
if (m_nPhase == m_nNumberOfPhases)
{
// at position (3) or (4)
if (!m_bLooping)
{
// position (3) only
// note that it seems that the sustain level is ALWAYS zero
// in the case of non-looping waveforms
m_bEnvelopeEnded = true;
bProcessNewDataIfAvailable = true;
}
else
{
// position (4) only
// note that any data already latched is ONLY acted upon
// at THIS point. If (after this Tick has completed) any new
// env data is written, it will NOT be acted upon, until
// we get back to position (4) again.
// this is why m_bEnvelopeEnded (which affects the behaviour
// of the SetEnvControl method) is FALSE here.
// See test case: envext_34c (as noted earlier)
m_bEnvelopeEnded = false;
// set phase pointer to start of envelope for loop
// and reset m_nPhasePosition
m_nPhase=0;
m_nPhasePosition -= 16;
bProcessNewDataIfAvailable = true;
}
}
else // (m_nPhase < m_nNumberOfPhases)
{
// not at position (3) or (4) ...
// (i.e., we're in the middle of an envelope with
// more than one phase. Specifically, we're in
// the middle of envelope 4 or 5 - the
// triangle envelopes - but that's not important)
// any commands sent to this envelope controller
// will be buffered. Set the flag to indicate this.
m_bEnvelopeEnded = false;
m_nPhasePosition -= 16;
}
}
else // (m_nPhasePosition < 16)
{
// still within the same phase;
// but, importantly, we are no longer at the start of the phase ...
// so new data cannot be acted on immediately, and must
// be buffered
m_bEnvelopeEnded = false;
// Phase and PhasePosition have already been updated.
// SetLevels() will need to be called to actually calculate
// the output 'level' of this envelope controller
}
// if we have new (buffered) data, now is the time to act on it
if (m_bNewData && bProcessNewDataIfAvailable)
{
m_bNewData = false;
SetNewEnvData(m_nNextData);
}
else
{
// ok, we didn't have any new buffered date to act on,
// so we just call SetLevels() to calculate the output level
// for whatever the current envelope is
SetLevels();
}
}
inline void CSAAEnv::SetLevels(void)
{
// sets m_nLeftLevel
// Also sets m_nRightLevel in terms of m_nLeftLevel
// and m_bInvertRightChannel
// m_nResolution: 1 means 4-bit resolution; 2 means 3-bit resolution. Resolution of envelope waveform.
// Note that this is handled 'immediately', and doesn't wait for synchronisation of
// the envelope waveform (this is important, see test case EnvExt_imm)
// It is therefore possible to switch between 4-bit and 3-bit resolution in the middle of
// an envelope waveform. if you are at an 'odd' phase position, you would be able to hear
// the difference. if you are at an 'even' phase position, the volume level for 4-bit
// and 3-bit would be the same.
// NOTE: additional test cases are required.
switch (m_nResolution)
{
case 1: // 4 bit res waveforms
default:
{
// special case: if envelope is not a looping one, and we're at the end
// then our level should be zero (all of the non-looping waveforms have
// a sustain level of zero):
if (m_bEnvelopeEnded && !m_bLooping)
m_nLeftLevel = 0;
else
m_nLeftLevel = m_pEnvData->nLevels[0][m_nPhase][m_nPhasePosition];
if (m_bInvertRightChannel)
m_nRightLevel = 15-m_nLeftLevel;
else
m_nRightLevel = m_nLeftLevel;
break;
}
case 2: // 3 bit res waveforms
{
// special case: if envelope is not a looping one, and we're at the end
// then our level should be zero (all of the non-looping waveforms have
// a sustain level of zero):
if (m_bEnvelopeEnded && !m_bLooping)
m_nLeftLevel = 0;
else
m_nLeftLevel = m_pEnvData->nLevels[1][m_nPhase][m_nPhasePosition];
if (m_bInvertRightChannel)
m_nRightLevel = 14-m_nLeftLevel;
else
m_nRightLevel = m_nLeftLevel;
break;
}
}
}
inline void CSAAEnv::SetNewEnvData(int nData)
{
// loads envgenerator's registers according to the bits set
// in nData
m_nPhase = 0;
m_nPhasePosition = 0;
m_pEnvData = &(cs_EnvData[(nData >> 1) & 0x07]);
m_bInvertRightChannel = ((nData & 0x01) == 0x01);
m_bClockExternally = ((nData & 0x20) == 0x20);
m_nNumberOfPhases = m_pEnvData->nNumberOfPhases;
m_bLooping = m_pEnvData->bLooping;
m_nResolution = (((nData & 0x10)==0x10) ? 2 : 1);
m_bEnabled = ((nData & 0x80) == 0x80);
if (m_bEnabled)
{
m_bEnvelopeEnded = false;
// is this right?
// YES. See test case EnvExt_34c (setting data multiple times
// when at a point (3) resets the waveform so you're no longer
// at a point (3).
}
else
{
// DISABLED - so set stuff accordingly
m_bEnvelopeEnded = true;
m_nPhase = 0;
m_nPhasePosition = 0;
}
SetLevels();
}

54
src/sound/saasound/SAAEnv.h Executable file
View File

@@ -0,0 +1,54 @@
// Part of SAASound copyright 1998-2018 Dave Hooper <dave@beermex.com>
//
// SAAEnv.h: interface for the CSAAEnv class.
//
//////////////////////////////////////////////////////////////////////
#ifndef SAAENV_H_INCLUDED
#define SAAENV_H_INCLUDED
class CSAAEnv
{
private:
int m_nLeftLevel, m_nRightLevel;
ENVDATA const * m_pEnvData;
bool m_bEnabled;
bool m_bInvertRightChannel;
BYTE m_nPhase;
BYTE m_nPhasePosition;
bool m_bEnvelopeEnded;
char m_nPhaseAdd[2];
char m_nCurrentPhaseAdd;
bool m_bLooping;
char m_nNumberOfPhases;
char m_nResolution;
char m_nInitialLevel;
bool m_bNewData;
BYTE m_nNextData;
bool m_bClockExternally;
static const ENVDATA cs_EnvData[8];
void Tick(void);
void SetLevels(void);
void SetNewEnvData(int nData);
public:
CSAAEnv();
~CSAAEnv();
void InternalClock(void);
void ExternalClock(void);
void SetEnvControl(int nData); // really just a BYTE
int LeftLevel(void) const;
int RightLevel(void) const;
bool IsActive(void) const;
};
inline bool CSAAEnv::IsActive(void) const
{
return m_bEnabled;
}
#endif // SAAENV_H_INCLUDED

280
src/sound/saasound/SAAFreq.cpp Executable file
View File

@@ -0,0 +1,280 @@
// Part of SAASound copyright 1998-2018 Dave Hooper <dave@beermex.com>
//
// SAAFreq.cpp: implementation of the CSAAFreq class.
// only 7-bit fractional accuracy on oscillator periods. I may consider fixing that.
//
//////////////////////////////////////////////////////////////////////
#include "SAASound.h"
#include "types.h"
#include "SAANoise.h"
#include "SAAEnv.h"
#include "SAAFreq.h"
#include "defns.h"
#ifdef SAAFREQ_FIXED_CLOCKRATE
// 'load in' the data for the static frequency lookup table
// precomputed for a fixed clockrate
// See: tools/freqdat.py
const unsigned long CSAAFreq::m_FreqTable[2048] = {
#include "SAAFreq.dat"
};
#else
unsigned long CSAAFreq::m_FreqTable[2048];
unsigned long CSAAFreq::m_nClockRate = 0;
#endif // SAAFREQ_FIXED_CLOCKRATE
const int INITIAL_LEVEL = 1;
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
CSAAFreq::CSAAFreq(CSAANoise * const NoiseGenerator, CSAAEnv * const EnvGenerator)
:
m_nCounter(0), m_nCounter_low(0), m_nAdd(0),
m_nLevel(INITIAL_LEVEL),
m_nOversample(0), m_nCounterLimit_low(1),
m_nCurrentOffset(0), m_nCurrentOctave(0), m_nNextOffset(0), m_nNextOctave(0),
m_bIgnoreOffsetData(false), m_bNewData(false),
m_bSync(false),
m_nSampleRate(SAMPLE_RATE_HZ),
m_pcConnectedNoiseGenerator(NoiseGenerator),
m_pcConnectedEnvGenerator(EnvGenerator),
m_nConnectedMode((NoiseGenerator == NULL) ? ((EnvGenerator == NULL) ? 0 : 1) : 2)
{
_SetClockRate(EXTERNAL_CLK_HZ);
SetAdd(); // current octave, current offset
}
CSAAFreq::~CSAAFreq()
{
// Nothing to do
}
void CSAAFreq::SetFreqOffset(BYTE nOffset)
{
// nOffset between 0 and 255
if (!m_bSync)
{
m_nNextOffset = nOffset;
m_bNewData=true;
if (m_nNextOctave==m_nCurrentOctave)
{
// According to Philips, if you send the SAA-1099
// new Octave data and then new Offset data in that
// order, on the next half-cycle of the current frequency
// generator, ONLY the octave data is acted upon.
// The offset data will be acted upon next time.
// ?? TEST CASE : if you set the octave and then the offset
// but the octave you set it to is the same one it already was.
// Will this ignore the offset data?
// Do you get the same behaviour if you set offset THEN octave
// even if you set octave to the same value it was before?
m_bIgnoreOffsetData=true;
}
}
else
{
// updates straightaway if m_bSync
m_bNewData=false;
m_bIgnoreOffsetData = false;
m_nCurrentOffset = nOffset;
m_nNextOffset = nOffset;
m_nCurrentOctave = m_nNextOctave;
SetAdd();
}
}
void CSAAFreq::SetFreqOctave(BYTE nOctave)
{
// nOctave between 0 and 7
if (!m_bSync)
{
m_nNextOctave = nOctave;
m_bNewData=true;
m_bIgnoreOffsetData = false;
}
else
{
// updates straightaway if m_bSync
m_bNewData=false;
m_bIgnoreOffsetData = false;
m_nCurrentOctave = nOctave;
m_nNextOctave = nOctave;
m_nCurrentOffset = m_nNextOffset;
SetAdd();
}
}
void CSAAFreq::UpdateOctaveOffsetData(void)
{
// loads the buffered new octave and new offset data into the current registers
// and sets up the new frequency for this frequency generator (i.e. sets up m_nAdd)
// - called during Sync, and called when waveform half-cycle completes
// How the SAA-1099 really treats new data:
// if only new octave data is present,
// then set new period based on just the octave data
// Otherwise, if only new offset data is present,
// then set new period based on just the offset data
// Otherwise, if new octave data is present, and new offset data is present,
// and the offset data was set BEFORE the octave data,
// then set new period based on both the octave and offset data
// Else, if the offset data came AFTER the new octave data
// then set new period based on JUST THE OCTAVE DATA, and continue
// signalling the offset data as 'new', so it will be acted upon
// next half-cycle
//
// Weird, I know. But that's how it works. Philips even documented as much.
if (!m_bNewData)
{
// optimise for the most common case! No new data!
return;
}
m_nCurrentOctave=m_nNextOctave;
if (!m_bIgnoreOffsetData)
{
m_nCurrentOffset=m_nNextOffset;
m_bNewData=false;
}
m_bIgnoreOffsetData=false;
SetAdd();
}
void CSAAFreq::_SetSampleRate(unsigned int nSampleRate)
{
m_nSampleRate = nSampleRate;
}
void CSAAFreq::_SetOversample(unsigned int oversample)
{
// oversample is a power of 2 i.e.
// if oversample == 2 then 4x oversample
// if oversample == 6 then 64x oversample
if (oversample < m_nOversample)
{
m_nCounter_low <<= (m_nOversample - oversample);
}
else
{
m_nCounter_low >>= (oversample - m_nOversample);
}
m_nCounterLimit_low = 1<<oversample;
m_nOversample = oversample;
}
#ifdef SAAFREQ_FIXED_CLOCKRATE
void CSAAFreq::_SetClockRate(int nClockRate)
{
// if SAAFREQ clock rate is hardcoded, then we don't support dynamically
// adjusting the SAA clock rate, so this is a no-op
}
#else
void CSAAFreq::_SetClockRate(int nClockRate)
{
// initialise the frequency table based on the SAA clockrate
// Each item in m_FreqTable corresponds to the frequency calculated by
// the standard formula (15625 << octave) / (511 - offset)
// then multiplied by 8192 (and represented as a long integer value).
// We are therefore using 12 bits (i.e. 2^12 = 4096) as fractional part.
// The reason we multiply by 8192, not 4096, is that we use this as a counter
// to toggle the oscillator state, so we need to count half-waves (i.e. twice
// the frequency)
//
// Finally, note that the standard formula corresponds to a 8MHz base clock
// so we rescale the final result by the ratio nClockRate/8000000
if (nClockRate != m_nClockRate)
{
m_nClockRate = nClockRate;
int ix = 0;
for (int nOctave = 0; nOctave < 8; nOctave++)
for (int nOffset = 0; nOffset < 256; nOffset++)
m_FreqTable[ix++] = (unsigned long)((8192.0 * 15625.0 * double(1 << nOctave) * (double(nClockRate) / 8000000.0)) / (511.0 - double(nOffset)));
}
}
#endif
int CSAAFreq::Tick(void)
{
// set to the absolute level (0 or 1)
if (m_bSync)
return 1;
m_nCounter += m_nAdd;
while (m_nCounter >= (m_nSampleRate<<12))
{
m_nCounter -= (m_nSampleRate<<12);
m_nCounter_low++;
if (m_nCounter_low >= m_nCounterLimit_low)
{
// period elapsed for (at least) one half-cycle of
// current frequency
m_nCounter_low = 0;
// flip state - from 0 to 1 or vice versa
m_nLevel = 1 - m_nLevel;
// trigger any connected devices
switch (m_nConnectedMode)
{
case 1:
// env trigger
m_pcConnectedEnvGenerator->InternalClock();
break;
case 2:
// noise trigger
m_pcConnectedNoiseGenerator->Trigger();
break;
default:
// do nothing
break;
}
// get new frequency (set period length m_nAdd) if new data is waiting:
UpdateOctaveOffsetData();
}
}
return m_nLevel;
}
void CSAAFreq::SetAdd(void)
{
// nOctave between 0 and 7; nOffset between 0 and 255
// Used to be:
// m_nAdd = (15625 << nOctave) / (511 - nOffset);
// Now just table lookup:
m_nAdd = m_FreqTable[m_nCurrentOctave<<8 | m_nCurrentOffset];
}
void CSAAFreq::Sync(bool bSync)
{
m_bSync = bSync;
// update straightaway if m_bSync
if (m_bSync)
{
m_nCounter = 0;
m_nCounter_low = 0;
// this seems to need to be required to make the Fred59 SPACE DEMO audio work correctly
m_nLevel = INITIAL_LEVEL;
m_nCurrentOctave=m_nNextOctave;
m_nCurrentOffset=m_nNextOffset;
SetAdd();
}
}

141
src/sound/saasound/SAAFreq.dat Executable file
View File

@@ -0,0 +1,141 @@
/*
// Part of SAASound copyright 1998-2018 Dave Hooper <dave@beermex.com>
//
// Precalculated oscillator frequency period steps
// Higher scaling for better accuracy.
//
// After construction, it's important to SetSampleRate before
// trying to use the generator.
// (Just because the CSAANoise object has a default samplerate
// doesn't mean you should rely on it)
//
//////////////////////////////////////////////////////////////////////
*/
250489 , 250980 , 251473 , 251969 , 252465 , 252964 , 253465 , 253968 , 254473 , 254980 , 255489 , 256000 , 256513 , 257028 , 257545 , 258065 ,
258586 , 259109 , 259635 , 260163 , 260692 , 261224 , 261759 , 262295 , 262834 , 263374 , 263918 , 264463 , 265010 , 265560 , 266112 , 266667 ,
267223 , 267782 , 268344 , 268908 , 269474 , 270042 , 270613 , 271186 , 271762 , 272340 , 272921 , 273504 , 274090 , 274678 , 275269 , 275862 ,
276458 , 277056 , 277657 , 278261 , 278867 , 279476 , 280088 , 280702 , 281319 , 281938 , 282561 , 283186 , 283814 , 284444 , 285078 , 285714 ,
286353 , 286996 , 287640 , 288288 , 288939 , 289593 , 290249 , 290909 , 291572 , 292237 , 292906 , 293578 , 294253 , 294931 , 295612 , 296296 ,
296984 , 297674 , 298368 , 299065 , 299766 , 300469 , 301176 , 301887 , 302600 , 303318 , 304038 , 304762 , 305489 , 306220 , 306954 , 307692 ,
308434 , 309179 , 309927 , 310680 , 311436 , 312195 , 312958 , 313725 , 314496 , 315271 , 316049 , 316832 , 317618 , 318408 , 319202 , 320000 ,
320802 , 321608 , 322418 , 323232 , 324051 , 324873 , 325700 , 326531 , 327366 , 328205 , 329049 , 329897 , 330749 , 331606 , 332468 , 333333 ,
334204 , 335079 , 335958 , 336842 , 337731 , 338624 , 339523 , 340426 , 341333 , 342246 , 343164 , 344086 , 345013 , 345946 , 346883 , 347826 ,
348774 , 349727 , 350685 , 351648 , 352617 , 353591 , 354571 , 355556 , 356546 , 357542 , 358543 , 359551 , 360563 , 361582 , 362606 , 363636 ,
364672 , 365714 , 366762 , 367816 , 368876 , 369942 , 371014 , 372093 , 373178 , 374269 , 375367 , 376471 , 377581 , 378698 , 379822 , 380952 ,
382090 , 383234 , 384384 , 385542 , 386707 , 387879 , 389058 , 390244 , 391437 , 392638 , 393846 , 395062 , 396285 , 397516 , 398754 , 400000 ,
401254 , 402516 , 403785 , 405063 , 406349 , 407643 , 408946 , 410256 , 411576 , 412903 , 414239 , 415584 , 416938 , 418301 , 419672 , 421053 ,
422442 , 423841 , 425249 , 426667 , 428094 , 429530 , 430976 , 432432 , 433898 , 435374 , 436860 , 438356 , 439863 , 441379 , 442907 , 444444 ,
445993 , 447552 , 449123 , 450704 , 452297 , 453901 , 455516 , 457143 , 458781 , 460432 , 462094 , 463768 , 465455 , 467153 , 468864 , 470588 ,
472325 , 474074 , 475836 , 477612 , 479401 , 481203 , 483019 , 484848 , 486692 , 488550 , 490421 , 492308 , 494208 , 496124 , 498054 , 500000 ,
500978 , 501961 , 502947 , 503937 , 504931 , 505929 , 506931 , 507937 , 508946 , 509960 , 510978 , 512000 , 513026 , 514056 , 515091 , 516129 ,
517172 , 518219 , 519270 , 520325 , 521385 , 522449 , 523517 , 524590 , 525667 , 526749 , 527835 , 528926 , 530021 , 531120 , 532225 , 533333 ,
534447 , 535565 , 536688 , 537815 , 538947 , 540084 , 541226 , 542373 , 543524 , 544681 , 545842 , 547009 , 548180 , 549356 , 550538 , 551724 ,
552916 , 554113 , 555315 , 556522 , 557734 , 558952 , 560175 , 561404 , 562637 , 563877 , 565121 , 566372 , 567627 , 568889 , 570156 , 571429 ,
572707 , 573991 , 575281 , 576577 , 577878 , 579186 , 580499 , 581818 , 583144 , 584475 , 585812 , 587156 , 588506 , 589862 , 591224 , 592593 ,
593968 , 595349 , 596737 , 598131 , 599532 , 600939 , 602353 , 603774 , 605201 , 606635 , 608076 , 609524 , 610979 , 612440 , 613909 , 615385 ,
616867 , 618357 , 619855 , 621359 , 622871 , 624390 , 625917 , 627451 , 628993 , 630542 , 632099 , 633663 , 635236 , 636816 , 638404 , 640000 ,
641604 , 643216 , 644836 , 646465 , 648101 , 649746 , 651399 , 653061 , 654731 , 656410 , 658098 , 659794 , 661499 , 663212 , 664935 , 666667 ,
668407 , 670157 , 671916 , 673684 , 675462 , 677249 , 679045 , 680851 , 682667 , 684492 , 686327 , 688172 , 690027 , 691892 , 693767 , 695652 ,
697548 , 699454 , 701370 , 703297 , 705234 , 707182 , 709141 , 711111 , 713092 , 715084 , 717087 , 719101 , 721127 , 723164 , 725212 , 727273 ,
729345 , 731429 , 733524 , 735632 , 737752 , 739884 , 742029 , 744186 , 746356 , 748538 , 750733 , 752941 , 755162 , 757396 , 759644 , 761905 ,
764179 , 766467 , 768769 , 771084 , 773414 , 775758 , 778116 , 780488 , 782875 , 785276 , 787692 , 790123 , 792570 , 795031 , 797508 , 800000 ,
802508 , 805031 , 807571 , 810127 , 812698 , 815287 , 817891 , 820513 , 823151 , 825806 , 828479 , 831169 , 833876 , 836601 , 839344 , 842105 ,
844884 , 847682 , 850498 , 853333 , 856187 , 859060 , 861953 , 864865 , 867797 , 870748 , 873720 , 876712 , 879725 , 882759 , 885813 , 888889 ,
891986 , 895105 , 898246 , 901408 , 904594 , 907801 , 911032 , 914286 , 917563 , 920863 , 924188 , 927536 , 930909 , 934307 , 937729 , 941176 ,
944649 , 948148 , 951673 , 955224 , 958801 , 962406 , 966038 , 969697 , 973384 , 977099 , 980843 , 984615 , 988417 , 992248 , 996109 , 1000000 ,
1001957 , 1003922 , 1005894 , 1007874 , 1009862 , 1011858 , 1013861 , 1015873 , 1017893 , 1019920 , 1021956 , 1024000 , 1026052 , 1028112 , 1030181 , 1032258 ,
1034343 , 1036437 , 1038540 , 1040650 , 1042770 , 1044898 , 1047035 , 1049180 , 1051335 , 1053498 , 1055670 , 1057851 , 1060041 , 1062241 , 1064449 , 1066667 ,
1068894 , 1071130 , 1073375 , 1075630 , 1077895 , 1080169 , 1082452 , 1084746 , 1087049 , 1089362 , 1091684 , 1094017 , 1096360 , 1098712 , 1101075 , 1103448 ,
1105832 , 1108225 , 1110629 , 1113043 , 1115468 , 1117904 , 1120350 , 1122807 , 1125275 , 1127753 , 1130243 , 1132743 , 1135255 , 1137778 , 1140312 , 1142857 ,
1145414 , 1147982 , 1150562 , 1153153 , 1155756 , 1158371 , 1160998 , 1163636 , 1166287 , 1168950 , 1171625 , 1174312 , 1177011 , 1179724 , 1182448 , 1185185 ,
1187935 , 1190698 , 1193473 , 1196262 , 1199063 , 1201878 , 1204706 , 1207547 , 1210402 , 1213270 , 1216152 , 1219048 , 1221957 , 1224880 , 1227818 , 1230769 ,
1233735 , 1236715 , 1239709 , 1242718 , 1245742 , 1248780 , 1251834 , 1254902 , 1257985 , 1261084 , 1264198 , 1267327 , 1270471 , 1273632 , 1276808 , 1280000 ,
1283208 , 1286432 , 1289673 , 1292929 , 1296203 , 1299492 , 1302799 , 1306122 , 1309463 , 1312821 , 1316195 , 1319588 , 1322997 , 1326425 , 1329870 , 1333333 ,
1336815 , 1340314 , 1343832 , 1347368 , 1350923 , 1354497 , 1358090 , 1361702 , 1365333 , 1368984 , 1372654 , 1376344 , 1380054 , 1383784 , 1387534 , 1391304 ,
1395095 , 1398907 , 1402740 , 1406593 , 1410468 , 1414365 , 1418283 , 1422222 , 1426184 , 1430168 , 1434174 , 1438202 , 1442254 , 1446328 , 1450425 , 1454545 ,
1458689 , 1462857 , 1467049 , 1471264 , 1475504 , 1479769 , 1484058 , 1488372 , 1492711 , 1497076 , 1501466 , 1505882 , 1510324 , 1514793 , 1519288 , 1523810 ,
1528358 , 1532934 , 1537538 , 1542169 , 1546828 , 1551515 , 1556231 , 1560976 , 1565749 , 1570552 , 1575385 , 1580247 , 1585139 , 1590062 , 1595016 , 1600000 ,
1605016 , 1610063 , 1615142 , 1620253 , 1625397 , 1630573 , 1635783 , 1641026 , 1646302 , 1651613 , 1656958 , 1662338 , 1667752 , 1673203 , 1678689 , 1684211 ,
1689769 , 1695364 , 1700997 , 1706667 , 1712375 , 1718121 , 1723906 , 1729730 , 1735593 , 1741497 , 1747440 , 1753425 , 1759450 , 1765517 , 1771626 , 1777778 ,
1783972 , 1790210 , 1796491 , 1802817 , 1809187 , 1815603 , 1822064 , 1828571 , 1835125 , 1841727 , 1848375 , 1855072 , 1861818 , 1868613 , 1875458 , 1882353 ,
1889299 , 1896296 , 1903346 , 1910448 , 1917603 , 1924812 , 1932075 , 1939394 , 1946768 , 1954198 , 1961686 , 1969231 , 1976834 , 1984496 , 1992218 , 2000000 ,
2003914 , 2007843 , 2011788 , 2015748 , 2019724 , 2023715 , 2027723 , 2031746 , 2035785 , 2039841 , 2043912 , 2048000 , 2052104 , 2056225 , 2060362 , 2064516 ,
2068687 , 2072874 , 2077079 , 2081301 , 2085540 , 2089796 , 2094070 , 2098361 , 2102669 , 2106996 , 2111340 , 2115702 , 2120083 , 2124481 , 2128898 , 2133333 ,
2137787 , 2142259 , 2146751 , 2151261 , 2155789 , 2160338 , 2164905 , 2169492 , 2174098 , 2178723 , 2183369 , 2188034 , 2192719 , 2197425 , 2202151 , 2206897 ,
2211663 , 2216450 , 2221258 , 2226087 , 2230937 , 2235808 , 2240700 , 2245614 , 2250549 , 2255507 , 2260486 , 2265487 , 2270510 , 2275556 , 2280624 , 2285714 ,
2290828 , 2295964 , 2301124 , 2306306 , 2311512 , 2316742 , 2321995 , 2327273 , 2332574 , 2337900 , 2343249 , 2348624 , 2354023 , 2359447 , 2364896 , 2370370 ,
2375870 , 2381395 , 2386946 , 2392523 , 2398126 , 2403756 , 2409412 , 2415094 , 2420804 , 2426540 , 2432304 , 2438095 , 2443914 , 2449761 , 2455635 , 2461538 ,
2467470 , 2473430 , 2479419 , 2485437 , 2491484 , 2497561 , 2503667 , 2509804 , 2515971 , 2522167 , 2528395 , 2534653 , 2540943 , 2547264 , 2553616 , 2560000 ,
2566416 , 2572864 , 2579345 , 2585859 , 2592405 , 2598985 , 2605598 , 2612245 , 2618926 , 2625641 , 2632391 , 2639175 , 2645995 , 2652850 , 2659740 , 2666667 ,
2673629 , 2680628 , 2687664 , 2694737 , 2701847 , 2708995 , 2716180 , 2723404 , 2730667 , 2737968 , 2745308 , 2752688 , 2760108 , 2767568 , 2775068 , 2782609 ,
2790191 , 2797814 , 2805479 , 2813187 , 2820937 , 2828729 , 2836565 , 2844444 , 2852368 , 2860335 , 2868347 , 2876404 , 2884507 , 2892655 , 2900850 , 2909091 ,
2917379 , 2925714 , 2934097 , 2942529 , 2951009 , 2959538 , 2968116 , 2976744 , 2985423 , 2994152 , 3002933 , 3011765 , 3020649 , 3029586 , 3038576 , 3047619 ,
3056716 , 3065868 , 3075075 , 3084337 , 3093656 , 3103030 , 3112462 , 3121951 , 3131498 , 3141104 , 3150769 , 3160494 , 3170279 , 3180124 , 3190031 , 3200000 ,
3210031 , 3220126 , 3230284 , 3240506 , 3250794 , 3261146 , 3271565 , 3282051 , 3292605 , 3303226 , 3313916 , 3324675 , 3335505 , 3346405 , 3357377 , 3368421 ,
3379538 , 3390728 , 3401993 , 3413333 , 3424749 , 3436242 , 3447811 , 3459459 , 3471186 , 3482993 , 3494881 , 3506849 , 3518900 , 3531034 , 3543253 , 3555556 ,
3567944 , 3580420 , 3592982 , 3605634 , 3618375 , 3631206 , 3644128 , 3657143 , 3670251 , 3683453 , 3696751 , 3710145 , 3723636 , 3737226 , 3750916 , 3764706 ,
3778598 , 3792593 , 3806691 , 3820896 , 3835206 , 3849624 , 3864151 , 3878788 , 3893536 , 3908397 , 3923372 , 3938462 , 3953668 , 3968992 , 3984436 , 4000000 ,
4007828 , 4015686 , 4023576 , 4031496 , 4039448 , 4047431 , 4055446 , 4063492 , 4071571 , 4079681 , 4087824 , 4096000 , 4104208 , 4112450 , 4120724 , 4129032 ,
4137374 , 4145749 , 4154158 , 4162602 , 4171079 , 4179592 , 4188139 , 4196721 , 4205339 , 4213992 , 4222680 , 4231405 , 4240166 , 4248963 , 4257796 , 4266667 ,
4275574 , 4284519 , 4293501 , 4302521 , 4311579 , 4320675 , 4329810 , 4338983 , 4348195 , 4357447 , 4366738 , 4376068 , 4385439 , 4394850 , 4404301 , 4413793 ,
4423326 , 4432900 , 4442516 , 4452174 , 4461874 , 4471616 , 4481400 , 4491228 , 4501099 , 4511013 , 4520971 , 4530973 , 4541020 , 4551111 , 4561247 , 4571429 ,
4581655 , 4591928 , 4602247 , 4612613 , 4623025 , 4633484 , 4643991 , 4654545 , 4665148 , 4675799 , 4686499 , 4697248 , 4708046 , 4718894 , 4729792 , 4740741 ,
4751740 , 4762791 , 4773893 , 4785047 , 4796253 , 4807512 , 4818824 , 4830189 , 4841608 , 4853081 , 4864608 , 4876190 , 4887828 , 4899522 , 4911271 , 4923077 ,
4934940 , 4946860 , 4958838 , 4970874 , 4982968 , 4995122 , 5007335 , 5019608 , 5031941 , 5044335 , 5056790 , 5069307 , 5081886 , 5094527 , 5107232 , 5120000 ,
5132832 , 5145729 , 5158690 , 5171717 , 5184810 , 5197970 , 5211196 , 5224490 , 5237852 , 5251282 , 5264781 , 5278351 , 5291990 , 5305699 , 5319481 , 5333333 ,
5347258 , 5361257 , 5375328 , 5389474 , 5403694 , 5417989 , 5432361 , 5446809 , 5461333 , 5475936 , 5490617 , 5505376 , 5520216 , 5535135 , 5550136 , 5565217 ,
5580381 , 5595628 , 5610959 , 5626374 , 5641873 , 5657459 , 5673130 , 5688889 , 5704735 , 5720670 , 5736695 , 5752809 , 5769014 , 5785311 , 5801700 , 5818182 ,
5834758 , 5851429 , 5868195 , 5885057 , 5902017 , 5919075 , 5936232 , 5953488 , 5970845 , 5988304 , 6005865 , 6023529 , 6041298 , 6059172 , 6077151 , 6095238 ,
6113433 , 6131737 , 6150150 , 6168675 , 6187311 , 6206061 , 6224924 , 6243902 , 6262997 , 6282209 , 6301538 , 6320988 , 6340557 , 6360248 , 6380062 , 6400000 ,
6420063 , 6440252 , 6460568 , 6481013 , 6501587 , 6522293 , 6543131 , 6564103 , 6585209 , 6606452 , 6627832 , 6649351 , 6671010 , 6692810 , 6714754 , 6736842 ,
6759076 , 6781457 , 6803987 , 6826667 , 6849498 , 6872483 , 6895623 , 6918919 , 6942373 , 6965986 , 6989761 , 7013699 , 7037801 , 7062069 , 7086505 , 7111111 ,
7135889 , 7160839 , 7185965 , 7211268 , 7236749 , 7262411 , 7288256 , 7314286 , 7340502 , 7366906 , 7393502 , 7420290 , 7447273 , 7474453 , 7501832 , 7529412 ,
7557196 , 7585185 , 7613383 , 7641791 , 7670412 , 7699248 , 7728302 , 7757576 , 7787072 , 7816794 , 7846743 , 7876923 , 7907336 , 7937984 , 7968872 , 8000000 ,
8015656 , 8031373 , 8047151 , 8062992 , 8078895 , 8094862 , 8110891 , 8126984 , 8143141 , 8159363 , 8175649 , 8192000 , 8208417 , 8224900 , 8241449 , 8258065 ,
8274747 , 8291498 , 8308316 , 8325203 , 8342159 , 8359184 , 8376278 , 8393443 , 8410678 , 8427984 , 8445361 , 8462810 , 8480331 , 8497925 , 8515593 , 8533333 ,
8551148 , 8569038 , 8587002 , 8605042 , 8623158 , 8641350 , 8659619 , 8677966 , 8696391 , 8714894 , 8733475 , 8752137 , 8770878 , 8789700 , 8808602 , 8827586 ,
8846652 , 8865801 , 8885033 , 8904348 , 8923747 , 8943231 , 8962801 , 8982456 , 9002198 , 9022026 , 9041943 , 9061947 , 9082040 , 9102222 , 9122494 , 9142857 ,
9163311 , 9183857 , 9204494 , 9225225 , 9246050 , 9266968 , 9287982 , 9309091 , 9330296 , 9351598 , 9372998 , 9394495 , 9416092 , 9437788 , 9459584 , 9481481 ,
9503480 , 9525581 , 9547786 , 9570093 , 9592506 , 9615023 , 9637647 , 9660377 , 9683215 , 9706161 , 9729216 , 9752381 , 9775656 , 9799043 , 9822542 , 9846154 ,
9869880 , 9893720 , 9917676 , 9941748 , 9965937 , 9990244 , 10014670 , 10039216 , 10063882 , 10088670 , 10113580 , 10138614 , 10163772 , 10189055 , 10214464 , 10240000 ,
10265664 , 10291457 , 10317380 , 10343434 , 10369620 , 10395939 , 10422392 , 10448980 , 10475703 , 10502564 , 10529563 , 10556701 , 10583979 , 10611399 , 10638961 , 10666667 ,
10694517 , 10722513 , 10750656 , 10778947 , 10807388 , 10835979 , 10864721 , 10893617 , 10922667 , 10951872 , 10981233 , 11010753 , 11040431 , 11070270 , 11100271 , 11130435 ,
11160763 , 11191257 , 11221918 , 11252747 , 11283747 , 11314917 , 11346260 , 11377778 , 11409471 , 11441341 , 11473389 , 11505618 , 11538028 , 11570621 , 11603399 , 11636364 ,
11669516 , 11702857 , 11736390 , 11770115 , 11804035 , 11838150 , 11872464 , 11906977 , 11941691 , 11976608 , 12011730 , 12047059 , 12082596 , 12118343 , 12154303 , 12190476 ,
12226866 , 12263473 , 12300300 , 12337349 , 12374622 , 12412121 , 12449848 , 12487805 , 12525994 , 12564417 , 12603077 , 12641975 , 12681115 , 12720497 , 12760125 , 12800000 ,
12840125 , 12880503 , 12921136 , 12962025 , 13003175 , 13044586 , 13086262 , 13128205 , 13170418 , 13212903 , 13255663 , 13298701 , 13342020 , 13385621 , 13429508 , 13473684 ,
13518152 , 13562914 , 13607973 , 13653333 , 13698997 , 13744966 , 13791246 , 13837838 , 13884746 , 13931973 , 13979522 , 14027397 , 14075601 , 14124138 , 14173010 , 14222222 ,
14271777 , 14321678 , 14371930 , 14422535 , 14473498 , 14524823 , 14576512 , 14628571 , 14681004 , 14733813 , 14787004 , 14840580 , 14894545 , 14948905 , 15003663 , 15058824 ,
15114391 , 15170370 , 15226766 , 15283582 , 15340824 , 15398496 , 15456604 , 15515152 , 15574144 , 15633588 , 15693487 , 15753846 , 15814672 , 15875969 , 15937743 , 16000000 ,
16031311 , 16062745 , 16094303 , 16125984 , 16157791 , 16189723 , 16221782 , 16253968 , 16286282 , 16318725 , 16351297 , 16384000 , 16416834 , 16449799 , 16482897 , 16516129 ,
16549495 , 16582996 , 16616633 , 16650407 , 16684318 , 16718367 , 16752556 , 16786885 , 16821355 , 16855967 , 16890722 , 16925620 , 16960663 , 16995851 , 17031185 , 17066667 ,
17102296 , 17138075 , 17174004 , 17210084 , 17246316 , 17282700 , 17319239 , 17355932 , 17392781 , 17429787 , 17466951 , 17504274 , 17541756 , 17579399 , 17617204 , 17655172 ,
17693305 , 17731602 , 17770065 , 17808696 , 17847495 , 17886463 , 17925602 , 17964912 , 18004396 , 18044053 , 18083885 , 18123894 , 18164080 , 18204444 , 18244989 , 18285714 ,
18326622 , 18367713 , 18408989 , 18450450 , 18492099 , 18533937 , 18575964 , 18618182 , 18660592 , 18703196 , 18745995 , 18788991 , 18832184 , 18875576 , 18919169 , 18962963 ,
19006961 , 19051163 , 19095571 , 19140187 , 19185012 , 19230047 , 19275294 , 19320755 , 19366430 , 19412322 , 19458432 , 19504762 , 19551313 , 19598086 , 19645084 , 19692308 ,
19739759 , 19787440 , 19835351 , 19883495 , 19931873 , 19980488 , 20029340 , 20078431 , 20127764 , 20177340 , 20227160 , 20277228 , 20327543 , 20378109 , 20428928 , 20480000 ,
20531328 , 20582915 , 20634761 , 20686869 , 20739241 , 20791878 , 20844784 , 20897959 , 20951407 , 21005128 , 21059126 , 21113402 , 21167959 , 21222798 , 21277922 , 21333333 ,
21389034 , 21445026 , 21501312 , 21557895 , 21614776 , 21671958 , 21729443 , 21787234 , 21845333 , 21903743 , 21962466 , 22021505 , 22080863 , 22140541 , 22200542 , 22260870 ,
22321526 , 22382514 , 22443836 , 22505495 , 22567493 , 22629834 , 22692521 , 22755556 , 22818942 , 22882682 , 22946779 , 23011236 , 23076056 , 23141243 , 23206799 , 23272727 ,
23339031 , 23405714 , 23472779 , 23540230 , 23608069 , 23676301 , 23744928 , 23813953 , 23883382 , 23953216 , 24023460 , 24094118 , 24165192 , 24236686 , 24308605 , 24380952 ,
24453731 , 24526946 , 24600601 , 24674699 , 24749245 , 24824242 , 24899696 , 24975610 , 25051988 , 25128834 , 25206154 , 25283951 , 25362229 , 25440994 , 25520249 , 25600000 ,
25680251 , 25761006 , 25842271 , 25924051 , 26006349 , 26089172 , 26172524 , 26256410 , 26340836 , 26425806 , 26511327 , 26597403 , 26684039 , 26771242 , 26859016 , 26947368 ,
27036304 , 27125828 , 27215947 , 27306667 , 27397993 , 27489933 , 27582492 , 27675676 , 27769492 , 27863946 , 27959044 , 28054795 , 28151203 , 28248276 , 28346021 , 28444444 ,
28543554 , 28643357 , 28743860 , 28845070 , 28946996 , 29049645 , 29153025 , 29257143 , 29362007 , 29467626 , 29574007 , 29681159 , 29789091 , 29897810 , 30007326 , 30117647 ,
30228782 , 30340741 , 30453532 , 30567164 , 30681648 , 30796992 , 30913208 , 31030303 , 31148289 , 31267176 , 31386973 , 31507692 , 31629344 , 31751938 , 31875486 , 32000000 ,
32062622 , 32125490 , 32188605 , 32251969 , 32315582 , 32379447 , 32443564 , 32507937 , 32572565 , 32637450 , 32702595 , 32768000 , 32833667 , 32899598 , 32965795 , 33032258 ,
33098990 , 33165992 , 33233266 , 33300813 , 33368635 , 33436735 , 33505112 , 33573770 , 33642710 , 33711934 , 33781443 , 33851240 , 33921325 , 33991701 , 34062370 , 34133333 ,
34204593 , 34276151 , 34348008 , 34420168 , 34492632 , 34565401 , 34638478 , 34711864 , 34785563 , 34859574 , 34933902 , 35008547 , 35083512 , 35158798 , 35234409 , 35310345 ,
35386609 , 35463203 , 35540130 , 35617391 , 35694989 , 35772926 , 35851204 , 35929825 , 36008791 , 36088106 , 36167770 , 36247788 , 36328160 , 36408889 , 36489978 , 36571429 ,
36653244 , 36735426 , 36817978 , 36900901 , 36984199 , 37067873 , 37151927 , 37236364 , 37321185 , 37406393 , 37491991 , 37577982 , 37664368 , 37751152 , 37838337 , 37925926 ,
38013921 , 38102326 , 38191142 , 38280374 , 38370023 , 38460094 , 38550588 , 38641509 , 38732861 , 38824645 , 38916865 , 39009524 , 39102625 , 39196172 , 39290168 , 39384615 ,
39479518 , 39574879 , 39670702 , 39766990 , 39863747 , 39960976 , 40058680 , 40156863 , 40255528 , 40354680 , 40454321 , 40554455 , 40655087 , 40756219 , 40857855 , 40960000 ,
41062657 , 41165829 , 41269521 , 41373737 , 41478481 , 41583756 , 41689567 , 41795918 , 41902813 , 42010256 , 42118252 , 42226804 , 42335917 , 42445596 , 42555844 , 42666667 ,
42778068 , 42890052 , 43002625 , 43115789 , 43229551 , 43343915 , 43458886 , 43574468 , 43690667 , 43807487 , 43924933 , 44043011 , 44161725 , 44281081 , 44401084 , 44521739 ,
44643052 , 44765027 , 44887671 , 45010989 , 45134986 , 45259669 , 45385042 , 45511111 , 45637883 , 45765363 , 45893557 , 46022472 , 46152113 , 46282486 , 46413598 , 46545455 ,
46678063 , 46811429 , 46945559 , 47080460 , 47216138 , 47352601 , 47489855 , 47627907 , 47766764 , 47906433 , 48046921 , 48188235 , 48330383 , 48473373 , 48617211 , 48761905 ,
48907463 , 49053892 , 49201201 , 49349398 , 49498489 , 49648485 , 49799392 , 49951220 , 50103976 , 50257669 , 50412308 , 50567901 , 50724458 , 50881988 , 51040498 , 51200000 ,
51360502 , 51522013 , 51684543 , 51848101 , 52012698 , 52178344 , 52345048 , 52512821 , 52681672 , 52851613 , 53022654 , 53194805 , 53368078 , 53542484 , 53718033 , 53894737 ,
54072607 , 54251656 , 54431894 , 54613333 , 54795987 , 54979866 , 55164983 , 55351351 , 55538983 , 55727891 , 55918089 , 56109589 , 56302405 , 56496552 , 56692042 , 56888889 ,
57087108 , 57286713 , 57487719 , 57690141 , 57893993 , 58099291 , 58306050 , 58514286 , 58724014 , 58935252 , 59148014 , 59362319 , 59578182 , 59795620 , 60014652 , 60235294 ,
60457565 , 60681481 , 60907063 , 61134328 , 61363296 , 61593985 , 61826415 , 62060606 , 62296578 , 62534351 , 62773946 , 63015385 , 63258687 , 63503876 , 63750973 , 64000000

72
src/sound/saasound/SAAFreq.h Executable file
View File

@@ -0,0 +1,72 @@
// Part of SAASound copyright 1998-2018 Dave Hooper <dave@beermex.com>
//
// SAAFreq.h: interface for the CSAAFreq class.
// Note about Samplerates: 0=44100, 1=22050; 2=11025
//
//////////////////////////////////////////////////////////////////////
#ifndef SAAFREQ_H_INCLUDE
#define SAAFREQ_H_INCLUDE
#include "defns.h"
class CSAAFreq
{
private:
#ifdef SAAFREQ_FIXED_CLOCKRATE
// 'load in' the data for the static frequency lookup table
// precomputed for a fixed clockrate
// See: tools/freqdat.py
const static unsigned long m_FreqTable[2048];
#else
// we'll calculate the frequency lookup table at runtime.
static unsigned long m_FreqTable[2048];
static unsigned long m_nClockRate;
#endif
unsigned long m_nCounter;
unsigned long m_nAdd;
unsigned long m_nCounter_low;
unsigned int m_nOversample;
unsigned long m_nCounterLimit_low;
int m_nLevel;
int m_nCurrentOffset;
int m_nCurrentOctave;
int m_nNextOffset;
int m_nNextOctave;
bool m_bIgnoreOffsetData;
bool m_bNewData;
bool m_bSync;
unsigned long m_nSampleRate;
CSAANoise * const m_pcConnectedNoiseGenerator;
CSAAEnv * const m_pcConnectedEnvGenerator;
const int m_nConnectedMode; // 0 = nothing; 1 = envgenerator; 2 = noisegenerator
void UpdateOctaveOffsetData(void);
void SetAdd(void);
public:
CSAAFreq(CSAANoise * const pcNoiseGenerator, CSAAEnv * const pcEnvGenerator);
~CSAAFreq();
void SetFreqOffset(BYTE nOffset);
void SetFreqOctave(BYTE nOctave);
void _SetSampleRate(unsigned int nSampleRate);
void _SetOversample(unsigned int oversample);
void _SetClockRate(int nClockRate);
void Sync(bool bSync);
int Tick(void);
int Level(void) const;
};
inline int CSAAFreq::Level(void) const
{
if (m_bSync)
return 1;
return m_nLevel;
}
#endif // SAAFREQ_H_INCLUDE

View File

@@ -0,0 +1,487 @@
// Part of SAASound copyright 1998-2018 Dave Hooper <dave@beermex.com>
//
// SAAImpl.cpp: implementation of the CSAASound class.
// the bones of the 'virtual SAA-1099' emulation
//
// the actual sound generation is carried out in the other classes;
// this class provides the output stage and the external interface only
//
//////////////////////////////////////////////////////////////////////
#include "SAASound.h"
#include "types.h"
#include "SAAImpl.h"
#include "defns.h"
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
CSAASoundInternal::CSAASoundInternal()
:
m_nClockRate(EXTERNAL_CLK_HZ),
m_bHighpass(false),
m_nSampleRate(SAMPLE_RATE_HZ),
m_nOversample(DEFAULT_OVERSAMPLE),
m_uParam(0),
m_uParamRate(0),
#if defined(DEBUGSAA) || defined(USE_CONFIG_FILE)
m_nDebugSample(0),
#endif
m_chip()
{
#ifdef USE_CONFIG_FILE
m_Config.ReadConfig();
#endif
#if defined(DEBUGSAA)
m_dbgfile.open(_T(DEBUG_SAA_REGISTER_LOG), std::ios_base::out);
m_pcmfile.open(_T(DEBUG_SAA_PCM_LOG), std::ios_base::out | std::ios_base::binary);
#elif defined(USE_CONFIG_FILE)
if (m_Config.m_bGenerateRegisterLogs)
m_dbgfile.open(m_Config.m_strRegisterLogPath, std::ios_base::out);
if (m_Config.m_bGeneratePcmLogs)
m_pcmfile.open(m_Config.m_strPcmOutputPath, std::ios_base::out | std::ios_base::binary);
if (m_Config.m_bGeneratePcmLogs && m_Config.m_bGeneratePcmSeparateChannels)
{
for (int i = 0; i < 6; i++)
{
m_channel_pcmfile[i].open(m_Config.getChannelPcmOutputPath(i), std::ios_base::out | std::ios_base::binary);
}
}
#endif
// set parameters
// TODO support defaults and overrides from config file
// m_chip.SetSoundParameters(SAAP_FILTER | SAAP_11025 | SAAP_8BIT | SAAP_MONO);
// reset the virtual SAA
// m_chip.Clear();
m_chip._SetClockRate(m_nClockRate);
m_chip._SetOversample(m_nOversample);
}
CSAASoundInternal::~CSAASoundInternal()
{
//
}
//////////////////////////////////////////////////////////////////////
// CSAASound members
//////////////////////////////////////////////////////////////////////
void CSAASoundInternal::SetClockRate(unsigned int nClockRate)
{
m_nClockRate = nClockRate;
m_chip._SetClockRate(m_nClockRate);
}
void CSAASoundInternal::Clear(void)
{
// reinitialises virtual SAA:
// sets reg 28 to 0x02; - sync and disabled
// sets regs 00-31 (except 28) to 0x00;
// sets reg 28 to 0x00;
// sets current reg to 0
WriteAddressData(28,2);
for (int i=31; i>=0; i--)
{
if (i!=28) WriteAddressData(i,0);
}
WriteAddressData(28,0);
WriteAddress(0);
}
void CSAASoundInternal::WriteData(BYTE nData)
{
// originated from an OUT 255,d call
m_chip._WriteData(nData);
#if defined(DEBUGSAA) || defined(USE_CONFIG_FILE)
#ifdef USE_CONFIG_FILE
if (m_Config.m_bGenerateRegisterLogs)
{
#endif
m_dbgfile << m_nDebugSample << " " << (int)m_chip._ReadAddress() << ":" << (int)nData << std::endl;
#ifdef USE_CONFIG_FILE
}
#endif
#endif
}
void CSAASoundInternal::WriteAddress(BYTE nReg)
{
// originated from an OUT 511,r call
m_chip._WriteAddress(nReg);
#if defined(DEBUGSAA) || defined(USE_CONFIG_FILE)
#ifdef USE_CONFIG_FILE
if (m_Config.m_bGenerateRegisterLogs)
{
#endif
m_dbgfile << m_nDebugSample << " " << (int)nReg << ":";
if (nReg==24)
{
m_dbgfile << "<!ENVO!>";
}
else if (nReg==25)
{
m_dbgfile << "<!ENV1!>";
}
m_dbgfile << std::endl;
#ifdef USE_CONFIG_FILE
}
#endif
#endif
}
void CSAASoundInternal::WriteAddressData(BYTE nReg, BYTE nData)
{
// performs WriteAddress(nReg) followed by WriteData(nData)
m_chip._WriteAddress(nReg);
m_chip._WriteData(nData);
}
#if 1
BYTE CSAASoundInternal::ReadAddress(void)
{
// Not a real hardware function of the SAA-1099, which is write-only
return(m_chip._ReadAddress());
}
#else
BYTE CSAASoundInternal::ReadAddress(void)
{
// Not a real hardware function of the SAA-1099, which is write-only
return(0);
}
#endif
void CSAASoundInternal::SetSoundParameters(SAAPARAM uParam)
{
// set samplerate properties from uParam (deprecated but still supported)
unsigned int nSampleRate = m_nSampleRate;
switch (uParam & SAAP_MASK_SAMPLERATE)
{
case SAAP_44100:
nSampleRate = 44100;
m_uParamRate = (m_uParamRate & ~SAAP_MASK_SAMPLERATE) | SAAP_44100;
break;
case SAAP_22050:
nSampleRate = 22050;
m_uParamRate = (m_uParamRate & ~SAAP_MASK_SAMPLERATE) | SAAP_22050;
break;
case SAAP_11025:
nSampleRate = 11025;
m_uParamRate = (m_uParamRate & ~SAAP_MASK_SAMPLERATE) | SAAP_11025;
break;
case 0:// change nothing!
default:
break;
}
if (nSampleRate != m_nSampleRate)
{
m_nSampleRate = nSampleRate;
m_chip._SetSampleRate(m_nSampleRate);
}
// set filter properties from uParam
m_uParam = (m_uParam & ~SAAP_MASK_FILTER) | (uParam & SAAP_MASK_FILTER);
m_bHighpass=true;
}
void CSAASoundInternal::SetSampleRate(unsigned int nSampleRate)
{
if (nSampleRate != m_nSampleRate)
{
m_nSampleRate = nSampleRate;
m_chip._SetSampleRate(m_nSampleRate);
}
}
void CSAASoundInternal::SetOversample(unsigned int nOversample)
{
if (nOversample != m_nOversample)
{
m_nOversample = nOversample;
m_chip._SetOversample(m_nOversample);
}
}
SAAPARAM CSAASoundInternal::GetCurrentSoundParameters(void)
{
return m_uParam | m_uParamRate;
}
unsigned short CSAASoundInternal::GetCurrentBytesPerSample(void)
{
// 16 bit stereo => 4 bytes per sample
return 4;
}
/*static*/ unsigned short CSAASound::GetBytesPerSample(SAAPARAM uParam)
{
// 16 bit stereo => 4 bytes per sample
switch (uParam & (SAAP_MASK_CHANNELS | SAAP_MASK_BITDEPTH))
{
case SAAP_STEREO | SAAP_16BIT:
return 4;
default:
return 0;
}
}
unsigned long CSAASoundInternal::GetCurrentSampleRate(void)
{
return CSAASound::GetSampleRate(m_uParamRate);
}
/*static*/ unsigned long CSAASound::GetSampleRate(SAAPARAM uParam) // static member function
{
switch (uParam & SAAP_MASK_SAMPLERATE)
{
case SAAP_11025:
return 11025;
case SAAP_22050:
return 22050;
case SAAP_44100:
return 44100;
default:
return 0;
}
}
#if defined(USE_CONFIG_FILE) || (defined(DEFAULT_BOOST) && DEFAULT_BOOST>1)
#define DO_BOOST
#endif
void scale_for_output(unsigned int left_input, unsigned int right_input,
double oversample_scalar, bool highpass, double boost,
double& filterout_z1_left, double& filterout_z1_right,
BYTE* &pBuffer)
{
double float_left = (double)left_input;
double float_right = (double)right_input;
float_left /= oversample_scalar;
float_right /= oversample_scalar;
// scale output into good range
float_left *= DEFAULT_UNBOOSTED_MULTIPLIER;
float_right *= DEFAULT_UNBOOSTED_MULTIPLIER;
if (highpass)
{
/* cutoff = 5 Hz (say)
const double b1 = exp(-2.0 * M_PI * (Fc/Fs))
const double a0 = 1.0 - b1;
*/
const double b1 = 0.99928787;
const double a0 = 1.0 - b1;
filterout_z1_left = float_left * a0 + filterout_z1_left * b1;
filterout_z1_right = float_right * a0 + filterout_z1_right * b1;
float_left -= filterout_z1_left;
float_right -= filterout_z1_right;
}
// multiply by boost, if defined
#if defined(DO_BOOST)
float_left *= boost;
float_right *= boost;
#endif
// convert to 16-bit signed range with hard clipping
signed short left_output = (signed short)(float_left > 32767 ? 32767 : float_left < -32768 ? -32768 : float_left);
signed short right_output = (signed short)(float_right > 32767 ? 32767 : float_right < -32768 ? -32768 : float_right);
*pBuffer++ = left_output & 0x00ff;
*pBuffer++ = (left_output >> 8) & 0x00ff;
*pBuffer++ = right_output & 0x00ff;
*pBuffer++ = (right_output >> 8) & 0x00ff;
}
void CSAASoundInternal::GenerateMany(BYTE* pBuffer, unsigned long nSamples)
{
unsigned int left_mixed, right_mixed;
static double filterout_z1_left_mixed = 0, filterout_z1_right_mixed = 0;
#if defined(DEBUGSAA) || defined(USE_CONFIG_FILE)
BYTE* pBufferStart = pBuffer;
unsigned long nTotalSamples = nSamples;
#endif
#if defined(DO_BOOST)
#if defined(USE_CONFIG_FILE)
double nBoost = m_Config.m_nBoost;
#else
double nBoost = DEFAULT_BOOST;
#endif
#else
double nBoost = 1.0;
#endif
double oversample = double(1 << m_nOversample);
#if defined(USE_CONFIG_FILE)
static double filterout_z1_left_0 = 0, filterout_z1_right_0 = 0;
static double filterout_z1_left_1 = 0, filterout_z1_right_1 = 0;
static double filterout_z1_left_2 = 0, filterout_z1_right_2 = 0;
static double filterout_z1_left_3 = 0, filterout_z1_right_3 = 0;
static double filterout_z1_left_4 = 0, filterout_z1_right_4 = 0;
static double filterout_z1_left_5 = 0, filterout_z1_right_5 = 0;
if (m_Config.m_bGeneratePcmLogs && m_Config.m_bGeneratePcmSeparateChannels)
{
unsigned int left0, right0, left1, right1, left2, right2, left3, right3, left4, right4, left5, right5;
BYTE* pChannelBufferPtr[6] = { m_pChannelBuffer[0], m_pChannelBuffer[1], m_pChannelBuffer[2], m_pChannelBuffer[3], m_pChannelBuffer[4], m_pChannelBuffer[5] };
while (nSamples--)
{
m_chip._TickAndOutputSeparate(left_mixed, right_mixed,
left0, right0,
left1, right1,
left2, right2,
left3, right3,
left4, right4,
left5, right5);
scale_for_output(left_mixed, right_mixed, oversample, m_bHighpass, nBoost, filterout_z1_left_mixed, filterout_z1_right_mixed, pBuffer);
// and the separate channels
scale_for_output(left0, right0, oversample, m_bHighpass, nBoost, filterout_z1_left_0, filterout_z1_right_0, pChannelBufferPtr[0]);
scale_for_output(left1, right1, oversample, m_bHighpass, nBoost, filterout_z1_left_1, filterout_z1_right_1, pChannelBufferPtr[1]);
scale_for_output(left2, right2, oversample, m_bHighpass, nBoost, filterout_z1_left_2, filterout_z1_right_2, pChannelBufferPtr[2]);
scale_for_output(left3, right3, oversample, m_bHighpass, nBoost, filterout_z1_left_3, filterout_z1_right_3, pChannelBufferPtr[3]);
scale_for_output(left4, right4, oversample, m_bHighpass, nBoost, filterout_z1_left_4, filterout_z1_right_4, pChannelBufferPtr[4]);
scale_for_output(left5, right5, oversample, m_bHighpass, nBoost, filterout_z1_left_5, filterout_z1_right_5, pChannelBufferPtr[5]);
// flush channel output PCM buffers when full
if (pChannelBufferPtr[0] >= m_pChannelBuffer[0] + CHANNEL_BUFFER_SIZE)
{
for (int i = 0; i < 6; i++)
{
m_channel_pcmfile[i].write((const char*)m_pChannelBuffer[i], CHANNEL_BUFFER_SIZE);
pChannelBufferPtr[i] = m_pChannelBuffer[i];
}
}
}
// flush remaining channel PCM output data
if (pChannelBufferPtr[0] >= m_pChannelBuffer[0])
{
for (int i = 0; i < 6; i++)
{
m_channel_pcmfile[i].write((const char*)m_pChannelBuffer[i], pChannelBufferPtr[i]-m_pChannelBuffer[i]);
}
}
}
else
{
#endif
while (nSamples--)
{
m_chip._TickAndOutputStereo(left_mixed, right_mixed);
scale_for_output(left_mixed, right_mixed, oversample, m_bHighpass, nBoost, filterout_z1_left_mixed, filterout_z1_right_mixed, pBuffer);
}
#if defined(USE_CONFIG_FILE)
}
#endif
#if defined(DEBUGSAA) || defined(USE_CONFIG_FILE)
#ifdef USE_CONFIG_FILE
if (m_Config.m_bGeneratePcmLogs)
{
#endif
m_pcmfile.write((const char *)pBufferStart, nTotalSamples * (unsigned long)GetCurrentBytesPerSample());
m_nDebugSample += nTotalSamples;
#ifdef USE_CONFIG_FILE
}
#endif
#endif
}
///////////////////////////////////////////////////////
LPCSAASOUND SAAAPI CreateCSAASound(void)
{
return (new CSAASoundInternal);
}
void SAAAPI DestroyCSAASound(LPCSAASOUND object)
{
delete (object);
}
/* thoughts on lowpass filtering as part of oversampling.
I tried this and really it didn't seem to make a lot of (audible) difference.
// lowpass oversample filter adds complexity and not particularly audibly better than simple averaging.
// use_lowpass_oversample_filter_average_output adds an additional averaging step to the output of the oversample
// filter. this seems critical, because without this, the raw output of the lowpass filter is full of aliases
// If use_lowpass_oversample_filter is False, then the _average_output flag is ignored.
// Default, use_lowpass_oversample_filter is False, it sounds just fine really.
//#define USE_LOWPASS_OVERSAMPLE_FILTER
#undef USE_LOWPASS_OVERSAMPLE_FILTER
//#define USE_LOWPASS_OVERSAMPLE_FILTER_AVERAGE_OUTPUT
#undef USE_LOWPASS_OVERSAMPLE_FILTER_AVERAGE_OUTPUT
#ifdef USE_LOWPASS_OVERSAMPLE_FILTER
static double oversample_lp_filterout_z1_left_stages[10] = { 0,0,0,0,0,0,0,0,0,0 };
static double oversample_lp_filterout_z1_right_stages[10] = { 0,0,0,0,0,0,0,0,0,0 };
double averaged_filterout_left = 0.0, averaged_filterout_right = 0.0;
const int nStages = 10;
for (int i = 0; i < 1 << m_nOversample; i++)
{
Noise[0]->Tick();
Noise[1]->Tick();
f_left = f_right = 0;
for (int c = 0; c < 6; c++)
{
Amp[c]->TickAndOutputStereo(temp_left, temp_right);
f_left += (double)temp_left;
f_right += (double)temp_right;
}
// apply lowpass here.
// HACK: ASSUME m_nOversample is 64 (I was experimenting only using the 64x oversample anyway)
// therefore Fs = 44100*64
// let's set Fc = 10kHz
// so Fc/Fs = 0.00354308390022675736961451247166
// const double b1 = exp(-2.0 * M_PI * (Fc/Fs))
// const double a0 = 1.0 - b1;
// const double b1 = 0.9779841137335348363722276130195;
const double b1 = 0.977;
const double a0 = 1.0 - b1;
oversample_lp_filterout_z1_left_stages[0] = f_left * a0 + oversample_lp_filterout_z1_left_stages[0] * b1;
for (int stage = 1; stage < nStages; stage++)
oversample_lp_filterout_z1_left_stages[stage] = oversample_lp_filterout_z1_left_stages[stage - 1] * a0 + oversample_lp_filterout_z1_left_stages[stage] * b1;
oversample_lp_filterout_z1_right_stages[0] = f_right * a0 + oversample_lp_filterout_z1_right_stages[0] * b1;
for (int stage = 1; stage < nStages; stage++)
oversample_lp_filterout_z1_right_stages[stage] = oversample_lp_filterout_z1_right_stages[stage - 1] * a0 + oversample_lp_filterout_z1_right_stages[stage] * b1;
#ifdef USE_LOWPASS_OVERSAMPLE_FILTER_AVERAGE_OUTPUT
averaged_filterout_left += oversample_lp_filterout_4z1_left;
averaged_filterout_right += oversample_lp_filterout_4z1_right;
#endif
}
// by the end of this loop we will have computed the oversample lowpass filter m_nOversample times
// and yielded exactly ONE sample output.
#ifdef USE_LOWPASS_OVERSAMPLE_FILTER_AVERAGE_OUTPUT
f_left = averaged_filterout_left / (1 << m_nOversample);
f_right = averaged_filterout_right / (1 << m_nOversample);
#else
f_left = oversample_lp_filterout_z1_left_stages[nStages - 1];
f_right = oversample_lp_filterout_z1_right_stages[nStages - 1];
#endif
#else
// do the simple 1/N averaging which is easier and sounds good enough
#endif
*/

75
src/sound/saasound/SAAImpl.h Executable file
View File

@@ -0,0 +1,75 @@
// Part of SAASound copyright 1998-2018 Dave Hooper <dave@beermex.com>
//
// This is the internal implementation (header file) of the SAASound object.
// This is done so that the external interface to the object always stays the same
// (SAASound.h) even though the internal object can change
// .. Meaning future releases don't require relinking everyone elses code against
// the updated saasound stuff
//
//////////////////////////////////////////////////////////////////////
#ifndef SAAIMPL_H_INCLUDED
#define SAAIMPL_H_INCLUDED
#include "SAASound.h"
#include "SAADevice.h"
#ifdef USE_CONFIG_FILE
#include "SAAConfig.h"
#endif
#if defined(DEBUGSAA) || defined(USE_CONFIG_FILE)
#include <ios>
#include <iostream>
#include <fstream>
#if defined(USE_CONFIG_FILE)
const int CHANNEL_BUFFER_SIZE=1024;
#endif
#endif
class CSAASoundInternal : public CSAASound
{
private:
CSAADevice m_chip;
int m_uParam, m_uParamRate;
unsigned int m_nClockRate;
unsigned int m_nSampleRate;
unsigned int m_nOversample;
bool m_bHighpass;
#ifdef USE_CONFIG_FILE
SAAConfig m_Config;
#endif
#if defined(DEBUGSAA) || defined(USE_CONFIG_FILE)
unsigned long m_nDebugSample;
std::ofstream m_dbgfile, m_pcmfile;
#if defined(USE_CONFIG_FILE)
std::ofstream m_channel_pcmfile[6];
BYTE m_pChannelBuffer[6][CHANNEL_BUFFER_SIZE];
#endif
#endif
public:
CSAASoundInternal();
~CSAASoundInternal();
void SetClockRate(unsigned int nClockRate);
void SetSampleRate(unsigned int nClockRate);
void SetOversample(unsigned int nOversample);
void SetSoundParameters(SAAPARAM uParam);
void WriteAddress(BYTE nReg);
void WriteData(BYTE nData);
void WriteAddressData(BYTE nReg, BYTE nData);
BYTE ReadAddress(void);
void Clear(void);
SAAPARAM GetCurrentSoundParameters(void);
unsigned long GetCurrentSampleRate(void);
static unsigned long GetSampleRate(SAAPARAM uParam);
unsigned short GetCurrentBytesPerSample(void);
static unsigned short GetBytesPerSample(SAAPARAM uParam);
void GenerateMany(BYTE * pBuffer, unsigned long nSamples);
};
#endif // SAAIMPL_H_INCLUDED

180
src/sound/saasound/SAANoise.cpp Executable file
View File

@@ -0,0 +1,180 @@
// Part of SAASound copyright 1998-2018 Dave Hooper <dave@beermex.com>
//
// SAANoise.cpp: implementation of the CSAANoise class.
// One noise generator
//
// After construction, it's important to SetSampleRate before
// trying to use the generator.
// (Just because the CSAANoise object has a default samplerate
// doesn't mean you should rely on it)
//
//////////////////////////////////////////////////////////////////////
#include "SAASound.h"
#include "types.h"
#include "SAANoise.h"
#include "defns.h"
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
CSAANoise::CSAANoise()
:
m_nCounter(0),
m_nCounter_low(0),
m_nCounterLimit_low(1),
m_nOversample(0),
m_bSync(false),
m_nSampleRate(SAMPLE_RATE_HZ),
m_nSourceMode(0),
m_nRand(1)
{
_SetClockRate(EXTERNAL_CLK_HZ);
m_nAdd = m_nAddBase;
}
CSAANoise::CSAANoise(unsigned long seed)
:
m_nCounter(0),
m_nCounter_low(0),
m_nCounterLimit_low(1),
m_nOversample(0),
m_bSync(false),
m_nSampleRate(SAMPLE_RATE_HZ),
m_nSourceMode(0),
m_nRand(seed)
{
_SetClockRate(EXTERNAL_CLK_HZ);
m_nAdd = m_nAddBase;
}
CSAANoise::~CSAANoise()
{
// Nothing to do
}
void CSAANoise::_SetClockRate(int nClockRate)
{
// at 8MHz the clock rate is 31.250kHZ
// This is simply the clock rate divided by 256 i.e. 2^8
// We then shift this by 2^12 (like the Freq) for better
// period accuracy. So that's the same as shifting by (12-8)
m_nAddBase = nClockRate << (12 - 8);
}
void CSAANoise::Seed(unsigned long seed)
{
m_nRand = seed;
}
void CSAANoise::SetSource(int nSource)
{
m_nSourceMode = nSource;
m_nAdd = m_nAddBase >> m_nSourceMode;
}
void CSAANoise::Trigger(void)
{
// Trigger only does anything useful when we're
// clocking from the frequency generator - i.e
// if bUseFreqGen = true (i.e. SourceMode = 3)
// So if we're clocking from the noise generator
// clock (ie, SourceMode = 0, 1 or 2) then do nothing
// No point actually checking m_bSync here ... because if sync is true,
// then frequency generators won't actually be generating Trigger pulses
// so we wouldn't even get here!
// EXCEPT - cool edge case: if sync is set, then actually the Noise Generator
// is triggered on EVERY CLOCK PULSE (i.e. 8MHz noise). So indeed it is correct
// to not check for sync here. NEEDS TEST CASE.
if (m_nSourceMode == 3)
{
ChangeLevel();
}
}
void CSAANoise::Tick(void)
{
// Tick only does anything useful when we're
// clocking from the noise generator clock
// (ie, SourceMode = 0, 1 or 2)
// So, if SourceMode = 3 (ie, we're clocking from a
// frequency generator ==> bUseFreqGen = true)
// then do nothing
if ( (!m_bSync) && (m_nSourceMode!=3) )
{
m_nCounter += m_nAdd;
while (m_nCounter >= (m_nSampleRate<<12))
{
m_nCounter -= (m_nSampleRate<<12);
m_nCounter_low++;
if (m_nCounter_low >= m_nCounterLimit_low)
{
m_nCounter_low = 0;
ChangeLevel();
}
}
}
}
void CSAANoise::Sync(bool bSync)
{
if (bSync)
{
m_nCounter = 0;
m_nCounter_low = 0;
}
m_bSync = bSync;
}
void CSAANoise::_SetSampleRate(int nSampleRate)
{
m_nSampleRate = nSampleRate;
}
void CSAANoise::_SetOversample(unsigned int oversample)
{
// oversample is a power of 2 i.e.
// if oversample == 2 then 4x oversample
// if oversample == 6 then 64x oversample
if (oversample < m_nOversample)
{
m_nCounter_low <<= (m_nOversample - oversample);
}
else
{
m_nCounter_low >>= (oversample - m_nOversample);
}
m_nCounterLimit_low = 1<<oversample;
m_nOversample = oversample;
}
inline void CSAANoise::ChangeLevel(void)
{
/*
https://www.vogons.org/viewtopic.php?f=9&t=51695
SAA1099P noise generator as documented by Jepael
18-bit Galois LFSR
Feedback polynomial = x^18 + x^11 + x^1
Period = 2^18-1 = 262143 bits
Verified to match recorded noise from my SAA1099P
*/
if (m_nRand & 1)
{
m_nRand = (m_nRand >> 1) ^ 0x20400;
}
else
{
m_nRand >>= 1;
}
}

54
src/sound/saasound/SAANoise.h Executable file
View File

@@ -0,0 +1,54 @@
// Part of SAASound copyright 1998-2018 Dave Hooper <dave@beermex.com>
//
// SAANoise.h: interface for the CSAANoise class.
//
//////////////////////////////////////////////////////////////////////
#ifndef SAANOISE_H_INCLUDED
#define SAANOISE_H_INCLUDED
class CSAANoise
{
private:
unsigned long m_nCounter;
unsigned long m_nAdd;
unsigned long m_nCounter_low;
unsigned int m_nOversample;
unsigned long m_nCounterLimit_low;
bool m_bSync; // see description of "SYNC" bit of register 28
unsigned long m_nSampleRate; // = 44100 when RateMode=0, for example
int m_nSourceMode;
unsigned long m_nAddBase; // nAdd for 31.25 kHz noise at 44.1 kHz samplerate
// pseudo-random number generator
unsigned long m_nRand;
void ChangeLevel(void);
public:
CSAANoise();
CSAANoise(unsigned long seed);
~CSAANoise();
void SetSource(int nSource);
void Trigger(void);
void _SetSampleRate(int nSampleRate);
void _SetOversample(unsigned int oversample);
void _SetClockRate(int nClockRate);
void Seed(unsigned long seed);
void Tick(void);
int Level(void) const;
void Sync(bool bSync);
};
inline int CSAANoise::Level(void) const
{
// returns 0 or 1
return (m_nRand & 0x00000001);
}
#endif // SAANOISE_H_INCLUDED

100
src/sound/saasound/SAASndC.cpp Executable file
View File

@@ -0,0 +1,100 @@
// Part of SAASound copyright 1998-2018 Dave Hooper <dave@beermex.com>
//
// Thanks to this file (and associated header file) you can now
// use CSAASound from within a standard 'C' program
//
//////////////////////////////////////////////////////////////////////
#include "SAASound.h"
#include "types.h"
#include "SAAEnv.h"
#include "SAANoise.h"
#include "SAAFreq.h"
#include "SAAAmp.h"
#include "SAASound.h"
#include "SAAImpl.h"
SAASND SAAAPI newSAASND(void)
{
return (SAASND)(new CSAASoundInternal());
}
void SAAAPI deleteSAASND(SAASND object)
{
delete (LPCSAASOUND)(object);
}
void SAAAPI SAASNDSetClockRate(SAASND object, unsigned int nClockRate)
{
((LPCSAASOUND)(object))->SetClockRate(nClockRate);
}
void SAAAPI SAASNDSetSoundParameters(SAASND object, SAAPARAM uParam)
{
((LPCSAASOUND)(object))->SetSoundParameters(uParam);
}
void SAAAPI SAASNDWriteAddress(SAASND object, BYTE nReg)
{
((LPCSAASOUND)(object))->WriteAddress(nReg);
}
void SAAAPI SAASNDWriteData(SAASND object, BYTE nData)
{
((LPCSAASOUND)(object))->WriteData(nData);
}
void SAAAPI SAASNDWriteAddressData(SAASND object, BYTE nReg, BYTE nData)
{
((LPCSAASOUND)(object))->WriteAddressData(nReg, nData);
}
void SAAAPI SAASNDClear(SAASND object)
{
((LPCSAASOUND)(object))->Clear();
}
SAAPARAM SAAAPI SAASNDGetCurrentSoundParameters(SAASND object)
{
return ((LPCSAASOUND)(object))->GetCurrentSoundParameters();
}
unsigned short SAAAPI SAASNDGetCurrentBytesPerSample(SAASND object)
{
return ((LPCSAASOUND)(object))->GetCurrentBytesPerSample();
}
unsigned short SAAAPI SAASNDGetBytesPerSample(SAAPARAM uParam)
{
return CSAASound::GetBytesPerSample(uParam);
}
unsigned long SAAAPI SAASNDGetCurrentSampleRate(SAASND object)
{
return ((LPCSAASOUND)(object))->GetCurrentSampleRate();
}
unsigned long SAAAPI SAASNDGetSampleRate(SAAPARAM uParam)
{
return CSAASound::GetSampleRate(uParam);
}
void SAAAPI SAASNDGenerateMany(SAASND object, BYTE * pBuffer, unsigned long nSamples)
{
((LPCSAASOUND)(object))->GenerateMany(pBuffer, nSamples);
}
void SAAAPI SAASNDSetSampleRate(SAASND object, unsigned int nSampleRate)
{
return ((LPCSAASOUND)(object))->SetSampleRate(nSampleRate);
}
void SAAAPI SAASNDSetOversample(SAASND object, unsigned int nOversample)
{
return ((LPCSAASOUND)(object))->SetOversample(nOversample);
}
BYTE SAAAPI SAASNDReadAddress(SAASND object)
{
return ((LPCSAASOUND)(object))->ReadAddress();
}

View File

@@ -0,0 +1,102 @@
// Part of SAASound copyright 1998-2018 Dave Hooper <dave@beermex.com>
//
// **********
// * PUBLIC *
// **********
//
// SAASndC.h: "C-style" interface for the CSAASound class.
//
//////////////////////////////////////////////////////////////////////
#ifndef SAASNDC_H_INCLUDED
#define SAASNDC_H_INCLUDED
#ifdef _MSC_VER
#if _MSC_VER >= 1000
#pragma once
#endif // _MSC_VER >= 1000
#endif
#ifndef SAASOUND_H_INCLUDED
// Parameters for use with SetSoundParameters, for example,
// SetSoundParameters(SAAP_NOFILTER | SAAP_44100 | SAAP_16BIT | SAAP_STEREO);
#define SAAP_FILTER_HIGHPASS_SIMPLE 0x00000400
#define SAAP_FILTER_OVERSAMPLE64x 0x00000300
#define SAAP_FILTER_OVERSAMPLE2x 0x00000200
#define SAAP_FILTER SAAP_FILTER_OVERSAMPLE2x
#define SAAP_NOFILTER 0x00000100
#define SAAP_44100 0x00000030
#define SAAP_22050 0x00000020
#define SAAP_11025 0x00000010
#define SAAP_16BIT 0x0000000c
#define SAAP_8BIT 0x00000004
#define SAAP_STEREO 0x00000003
#define SAAP_MONO 0x00000001
// Bitmasks for use with GetCurrentSoundParameters, for example,
// unsigned long CurrentSampleRateParameter = GetCurrentSoundParameters()
#define SAAP_MASK_FILTER 0x00000f00
#define SAAP_MASK_FILTER_HIGHPASS 0x00000c00
#define SAAP_MASK_FILTER_OVERSAMPLE 0x00000300
#define SAAP_MASK_SAMPLERATE 0x000000030
#define SAAP_MASK_BITDEPTH 0x0000000c
#define SAAP_MASK_CHANNELS 0x00000003
typedef unsigned long SAAPARAM;
#ifndef BYTE
#define BYTE unsigned char
#endif
#ifdef WIN32
#ifndef WINAPI
#define WINAPI __stdcall
#endif
#define EXTAPI __declspec(dllexport) WINAPI
#else // Win32
#ifndef WINAPI
#define WINAPI /**/
#endif
#define EXTAPI /**/
#endif // Win32
#endif // SAASOUND_H_INCLUDED
typedef void * SAASND;
// the following are implemented as calls, etc, to a class.
#ifdef __cplusplus
extern "C" {
#endif
SAASND EXTAPI newSAASND(void);
void EXTAPI deleteSAASND(SAASND object);
void EXTAPI SAASNDSetSoundParameters(SAASND object, SAAPARAM uParam);
void EXTAPI SAASNDWriteAddress(SAASND object, BYTE nReg);
void EXTAPI SAASNDWriteData(SAASND object, BYTE nData);
void EXTAPI SAASNDWriteAddressData(SAASND object, BYTE nReg, BYTE nData);
void EXTAPI SAASNDClear(SAASND object);
BYTE EXTAPI SAASNDReadAddress(SAASND object);
SAAPARAM EXTAPI SAASNDGetCurrentSoundParameters(SAASND object);
unsigned short EXTAPI SAASNDGetCurrentBytesPerSample(SAASND object);
unsigned short EXTAPI SAASNDGetBytesPerSample(SAAPARAM uParam);
unsigned long EXTAPI SAASNDGetCurrentSampleRate(SAASND object);
unsigned long EXTAPI SAASNDGetSampleRate(SAAPARAM uParam);
void EXTAPI SAASNDGenerateMany(SAASND object, BYTE * pBuffer, unsigned long nSamples);
void EXTAPI SAASNDSetClockRate(SAASND object, unsigned int nClockRate);
void EXTAPI SAASNDSetSampleRate(SAASND object, unsigned int nSampleRate);
void EXTAPI SAASNDSetOversample(SAASND object, unsigned int nOversample);
#ifdef __cplusplus
}; // extern "C"
#endif
#endif // SAASNDC_H_INCLUDED

13
src/sound/saasound/SAASound.cpp Executable file
View File

@@ -0,0 +1,13 @@
// Part of SAASound copyright 1998-2018 Dave Hooper <dave@beermex.com>
//
// SAASound.cpp - dummy function
//
//////////////////////////////////////////////////////////////////////
#include <stdio.h>
// Provide something so the compiler doesn't optimise us out of existance
int SomeFunction ()
{
return 42;
}

View File

@@ -0,0 +1,130 @@
// Part of SAASound copyright 1998-2018 Dave Hooper <dave@beermex.com>
//
// SAASound.h: interface for the CSAASound class.
//
// This corresponds to the public (exported) DLL interface, so all
// APIs and client factory methods belong here.
//
// Compatibility notes : the intention is for this to be fully backwards
// compatible across minor and patch versions. Any backwards breaking changes
// should be reflected as a major version increment. New functionality can be added
// in minor versions so long as backwards compatiblity is maintained
//
// Version 3.3.0 (4th Dec 2018)
//
//////////////////////////////////////////////////////////////////////
#ifndef SAASOUND_H_INCLUDED
#define SAASOUND_H_INCLUDED
// define this if you want to output diagnostic text and PCM files
//#define DEBUGSAA
// Parameters for use with SetSoundParameters, for example,
// SetSoundParameters(SAAP_NOFILTER | SAAP_44100 | SAA_16BIT | SAA_STEREO);
// SAAP_FILTER_HIGHPASS_SIMPLE can be ORd with SAAP_FILTER_OVERSAMPLE64x/2x
#define SAAP_FILTER_HIGHPASS_SIMPLE 0x00000400
#define SAAP_FILTER_OVERSAMPLE64x 0x00000300
#define SAAP_FILTER_OVERSAMPLE2x 0x00000200
#define SAAP_FILTER SAAP_FILTER_OVERSAMPLE2x
#define SAAP_NOFILTER 0x00000100
#define SAAP_44100 0x00000030
#define SAAP_22050 0x00000020
#define SAAP_11025 0x00000010
#define SAAP_16BIT 0x0000000c
#define SAAP_8BIT 0x00000004
#define SAAP_STEREO 0x00000003
#define SAAP_MONO 0x00000001
// Bitmasks for use with GetCurrentSoundParameters, for example,
// unsigned long CurrentSampleRateParameter = GetCurrentSoundParameters()
#define SAAP_MASK_FILTER 0x00000f00
#define SAAP_MASK_FILTER_HIGHPASS 0x00000c00
#define SAAP_MASK_FILTER_OVERSAMPLE 0x00000300
#define SAAP_MASK_SAMPLERATE 0x000000030
#define SAAP_MASK_BITDEPTH 0x0000000c
#define SAAP_MASK_CHANNELS 0x00000003
typedef unsigned long SAAPARAM;
#ifndef BYTE
#define BYTE unsigned char
#endif
#ifdef _WIN32
#define SAAAPI _stdcall
#else
#define SAAAPI
#endif
#ifdef __cplusplus
class CSAASound
{
public:
virtual ~CSAASound() { }
virtual void SetSoundParameters (SAAPARAM uParam) = 0;
virtual void WriteAddress (BYTE nReg) = 0;
virtual void WriteData (BYTE nData) = 0;
virtual void WriteAddressData (BYTE nReg, BYTE nData) = 0;
virtual void Clear () = 0;
virtual BYTE ReadAddress () = 0;
virtual SAAPARAM GetCurrentSoundParameters () = 0;
virtual unsigned long GetCurrentSampleRate () = 0;
static unsigned long GetSampleRate (SAAPARAM uParam);
virtual unsigned short GetCurrentBytesPerSample () = 0;
static unsigned short GetBytesPerSample (SAAPARAM uParam);
virtual void GenerateMany (BYTE * pBuffer, unsigned long nSamples) = 0;
virtual void SetClockRate(unsigned int nClockRate) = 0;
virtual void SetSampleRate(unsigned int nSampleRate) = 0;
virtual void SetOversample(unsigned int nOversample) = 0;
};
typedef class CSAASound * LPCSAASOUND;
LPCSAASOUND SAAAPI CreateCSAASound(void);
void SAAAPI DestroyCSAASound(LPCSAASOUND object);
#endif // __cplusplus
#ifdef __cplusplus
extern "C" {
#endif
typedef void * SAASND;
// "C-style" interface for the CSAASound class
SAASND SAAAPI newSAASND(void);
void SAAAPI deleteSAASND(SAASND object);
void SAAAPI SAASNDSetSoundParameters(SAASND object, SAAPARAM uParam);
void SAAAPI SAASNDWriteAddress(SAASND object, BYTE nReg);
void SAAAPI SAASNDWriteData(SAASND object, BYTE nData);
void SAAAPI SAASNDWriteAddressData(SAASND object, BYTE nReg, BYTE nData);
void SAAAPI SAASNDClear(SAASND object);
SAAPARAM SAAAPI SAASNDGetCurrentSoundParameters(SAASND object);
unsigned short SAAAPI SAASNDGetCurrentBytesPerSample(SAASND object);
unsigned short SAAAPI SAASNDGetBytesPerSample(SAAPARAM uParam);
unsigned long SAAAPI SAASNDGetCurrentSampleRate(SAASND object);
unsigned long SAAAPI SAASNDGetSampleRate(SAAPARAM uParam);
void SAAAPI SAASNDGenerateMany(SAASND object, BYTE * pBuffer, unsigned long nSamples);
void SAAAPI SAASNDSetClockRate(SAASND object, unsigned int nClockRate);
void SAAAPI SAASNDSetSampleRate(SAASND object, unsigned int nSampleRate);
void SAAAPI SAASNDSetOversample(SAASND object, unsigned int nOversample);
BYTE SAAAPI SAASNDReadAddress(SAASND object);
#ifdef __cplusplus
}; // extern "C"
#endif
#endif // SAASOUND_H_INCLUDED

View File

@@ -0,0 +1,59 @@
// Part of SAASound copyright 2020 Dave Hooper <dave@beermex.com>
//
// defns.h: compile-time configuration parameters
//
//////////////////////////////////////////////////////////////////////
#ifndef DEFNS_H_INCLUDED
#define DEFNS_H_INCLUDED
#define HAVE_CONFIG_H
#ifdef HAVE_CONFIG_H
// using CMAKE
#include "saasound_cmake_config.h"
#else
// initial default SAA1099 crystal clock rate in HZ (can be changed subsequently by calling SetClockRate)
#define EXTERNAL_CLK_HZ 8000000
// define SAAFREQ_FIXED_CLOCKRATE if the above external clock rate is the only supported clock rate
// i.e. only support a single compile-time clock rate (=> this also prevents using the SetClockRate method)
#undef SAAFREQ_FIXED_CLOCKRATE
// #define SAAFREQ_FIXED_CLOCKRATE
// initial default sample rate (audio samplerate)
#define SAMPLE_RATE_HZ 44100
// initial default oversample (audio quality) recommend 0<=oversample<=6
#define DEFAULT_OVERSAMPLE 6
// Whether to dump out a log of all register and value changes and raw output pcm
//#define DEBUGSAA
#undef DEBUGSAA
// the (default) names of the register output and pcm output log files.
// If you're using a config file, you can change these (or, if you enable
// debugging via the config file settings, but leave the filenames unspecified,
// it will use these defaults)
#define DEBUG_SAA_REGISTER_LOG "debugsaa.txt"
#define DEBUG_SAA_PCM_LOG "debugsaa.pcm"
// Whether to include support for these debug logs via config file (only making
// sense if USE_CONFIG_FILE is also defined)
// Whether to support a startup configuration file that is parsed at load time
// #undef USE_CONFIG_FILE
#define USE_CONFIG_FILE
// and if so, what is its location
#ifdef USE_CONFIG_FILE
#define CONFIG_FILE_PATH "SAASound.cfg"
#endif // USE_CONFIG_FILE
#define DEFAULT_UNBOOSTED_MULTIPLIER 11.35
#define DEFAULT_BOOST 1
#endif // HAVE_CONFIG_H
#endif // DEFNS_H_INCLUDED

15
src/sound/saasound/resource.h Executable file
View File

@@ -0,0 +1,15 @@
//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by SAASound.rc
//
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 101
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1000
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif

View File

@@ -0,0 +1,14 @@
#pragma once
#define EXTERNAL_CLK_HZ 7159090
/* #undef SAAFREQ_FIXED_CLOCKRATE */
#define SAMPLE_RATE_HZ 44100
#define DEFAULT_OVERSAMPLE 6
#define DEFAULT_UNBOOSTED_MULTIPLIER 11.3
#define DEFAULT_BOOST 1
/* #undef DEBUGSAA */
#define DEBUG_SAA_REGISTER_LOG "debugsaa.txt"
#define DEBUG_SAA_PCM_LOG "debugsaa.pcm"
/* #undef USE_CONFIG_FILE */
#define CONFIG_FILE_PATH "SAASound.cfg"

34
src/sound/saasound/types.h Executable file
View File

@@ -0,0 +1,34 @@
// Part of SAASound copyright 1998-2018 Dave Hooper <dave@beermex.com>
//
// handy typedefs
//
//////////////////////////////////////////////////////////////////////
#ifndef TYPES_H_INCLUDED
#define TYPES_H_INCLUDED
#if defined(__i386__) || defined(WIN32) || \
(defined(__alpha__) || defined(__alpha)) || \
defined(__arm__) || \
(defined(__mips__) && defined(__MIPSEL__))
#else
#define __BIG_ENDIAN
#endif
#ifndef NULL
#define NULL 0
#endif
typedef struct
{
int nNumberOfPhases;
bool bLooping;
int nLevels[2][2][16]; // [Resolution][Phase][Withinphase]
} ENVDATA;
#ifdef WIN32
extern "C" void _stdcall OutputDebugStringA (char*);
#endif
#endif

View File

@@ -8,6 +8,7 @@
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/io.h>
#include "saasound/SAASound.h"
#include <86box/snd_cms.h>
#include <86box/sound.h>
#include <86box/plat_unused.h>
@@ -15,62 +16,13 @@
void
cms_update(cms_t *cms)
{
for (; cms->pos < sound_pos_global; cms->pos++) {
int16_t out_l = 0;
int16_t out_r = 0;
for (uint8_t c = 0; c < 4; c++) {
switch (cms->noisetype[c >> 1][c & 1]) {
case 0:
cms->noisefreq[c >> 1][c & 1] = MASTER_CLOCK / 256;
break;
case 1:
cms->noisefreq[c >> 1][c & 1] = MASTER_CLOCK / 512;
break;
case 2:
cms->noisefreq[c >> 1][c & 1] = MASTER_CLOCK / 1024;
break;
case 3:
cms->noisefreq[c >> 1][c & 1] = cms->freq[c >> 1][(c & 1) * 3];
break;
default:
break;
if (cms->pos < wavetable_pos_global) {
SAASNDGenerateMany(cms->saasound, (unsigned char*)&cms->buffer[cms->pos], wavetable_pos_global - cms->pos);
cms->pos = wavetable_pos_global;
}
}
for (uint8_t c = 0; c < 2; c++) {
if (cms->regs[c][0x1C] & 1) {
for (uint8_t d = 0; d < 6; d++) {
if (cms->regs[c][0x14] & (1 << d)) {
if (cms->stat[c][d])
out_l += (cms->vol[c][d][0] * 90);
if (cms->stat[c][d])
out_r += (cms->vol[c][d][1] * 90);
cms->count[c][d] += cms->freq[c][d];
if (cms->count[c][d] >= 24000) {
cms->count[c][d] -= 24000;
cms->stat[c][d] ^= 1;
}
} else if (cms->regs[c][0x15] & (1 << d)) {
if (cms->noise[c][d / 3] & 1)
out_l += (cms->vol[c][d][0] * 90);
if (cms->noise[c][d / 3] & 1)
out_r += (cms->vol[c][d][0] * 90);
}
}
for (uint8_t d = 0; d < 2; d++) {
cms->noisecount[c][d] += cms->noisefreq[c][d];
while (cms->noisecount[c][d] >= 24000) {
cms->noisecount[c][d] -= 24000;
cms->noise[c][d] <<= 1;
if (!(((cms->noise[c][d] & 0x4000) >> 8) ^ (cms->noise[c][d] & 0x40)))
cms->noise[c][d] |= 1;
}
}
}
}
cms->buffer[cms->pos << 1] = out_l;
cms->buffer[(cms->pos << 1) + 1] = out_r;
if (cms->pos2 < wavetable_pos_global) {
SAASNDGenerateMany(cms->saasound2, (unsigned char*)&cms->buffer2[cms->pos2], wavetable_pos_global - cms->pos2);
cms->pos2 = wavetable_pos_global;
}
}
@@ -87,6 +39,19 @@ cms_get_buffer(int32_t *buffer, int len, void *priv)
cms->pos = 0;
}
void
cms_get_buffer_2(int32_t *buffer, int len, void *priv)
{
cms_t *cms = (cms_t *) priv;
cms_update(cms);
for (int c = 0; c < len * 2; c++)
buffer[c] += cms->buffer2[c];
cms->pos2 = 0;
}
void
cms_write(uint16_t addr, uint8_t val, void *priv)
{
@@ -96,54 +61,19 @@ cms_write(uint16_t addr, uint8_t val, void *priv)
switch (addr & 0xf) {
case 0x1: /* SAA #1 Register Select Port */
cms->addrs[0] = val & 31;
SAASNDWriteAddress(cms->saasound, val & 31);
break;
case 0x3: /* SAA #2 Register Select Port */
cms->addrs[1] = val & 31;
SAASNDWriteAddress(cms->saasound2, val & 31);
break;
case 0x0: /* SAA #1 Data Port */
cms_update(cms);
SAASNDWriteData(cms->saasound, val);
break;
case 0x2: /* SAA #2 Data Port */
cms_update(cms);
cms->regs[chip][cms->addrs[chip] & 31] = val;
switch (cms->addrs[chip] & 31) {
case 0x00:
case 0x01:
case 0x02: /*Volume*/
case 0x03:
case 0x04:
case 0x05:
voice = cms->addrs[chip] & 7;
cms->vol[chip][voice][0] = val & 0xf;
cms->vol[chip][voice][1] = val >> 4;
break;
case 0x08:
case 0x09:
case 0x0A: /*Frequency*/
case 0x0B:
case 0x0C:
case 0x0D:
voice = cms->addrs[chip] & 7;
cms->latch[chip][voice] = (cms->latch[chip][voice] & 0x700) | val;
cms->freq[chip][voice] = (MASTER_CLOCK / 512 << (cms->latch[chip][voice] >> 8)) / (511 - (cms->latch[chip][voice] & 255));
break;
case 0x10:
case 0x11:
case 0x12: /*Octave*/
voice = (cms->addrs[chip] & 3) << 1;
cms->latch[chip][voice] = (cms->latch[chip][voice] & 0xFF) | ((val & 7) << 8);
cms->latch[chip][voice + 1] = (cms->latch[chip][voice + 1] & 0xFF) | ((val & 0x70) << 4);
cms->freq[chip][voice] = (MASTER_CLOCK / 512 << (cms->latch[chip][voice] >> 8)) / (511 - (cms->latch[chip][voice] & 255));
cms->freq[chip][voice + 1] = (MASTER_CLOCK / 512 << (cms->latch[chip][voice + 1] >> 8)) / (511 - (cms->latch[chip][voice + 1] & 255));
break;
case 0x16: /*Noise*/
cms->noisetype[chip][0] = val & 3;
cms->noisetype[chip][1] = (val >> 4) & 3;
break;
default:
break;
}
SAASNDWriteData(cms->saasound2, val);
break;
case 0x6: /* GameBlaster Write Port */
@@ -163,9 +93,9 @@ cms_read(uint16_t addr, void *priv)
switch (addr & 0xf) {
case 0x1: /* SAA #1 Register Select Port */
return cms->addrs[0];
return SAASNDReadAddress(cms->saasound);
case 0x3: /* SAA #2 Register Select Port */
return cms->addrs[1];
return SAASNDReadAddress(cms->saasound2);
case 0x4: /* GameBlaster Read port (Always returns 0x7F) */
return 0x7f;
case 0xa: /* GameBlaster Read Port */
@@ -185,7 +115,12 @@ cms_init(UNUSED(const device_t *info))
uint16_t addr = device_get_config_hex16("base");
io_sethandler(addr, 0x0010, cms_read, NULL, NULL, cms_write, NULL, NULL, cms);
sound_add_handler(cms_get_buffer, cms);
cms->saasound = newSAASND();
SAASNDSetSoundParameters(cms->saasound, SAAP_44100 | SAAP_16BIT | SAAP_NOFILTER | SAAP_STEREO);
cms->saasound2 = newSAASND();
SAASNDSetSoundParameters(cms->saasound2, SAAP_44100 | SAAP_16BIT | SAAP_NOFILTER | SAAP_STEREO);
wavetable_add_handler(cms_get_buffer, cms);
wavetable_add_handler(cms_get_buffer_2, cms);
return cms;
}
@@ -194,6 +129,9 @@ cms_close(void *priv)
{
cms_t *cms = (cms_t *) priv;
deleteSAASND(cms->saasound);
deleteSAASND(cms->saasound2);
free(cms);
}