Merge pull request #5417 from Cacodemon345/cms-saasound
Switch to SAASound for CMS
This commit is contained in:
@@ -7,23 +7,20 @@
|
|||||||
#define MASTER_CLOCK 7159090
|
#define MASTER_CLOCK 7159090
|
||||||
|
|
||||||
typedef struct cms_t {
|
typedef struct cms_t {
|
||||||
int addrs[2];
|
#ifdef SAASOUND_H_INCLUDED
|
||||||
uint8_t regs[2][32];
|
SAASND saasound;
|
||||||
uint16_t latch[2][6];
|
SAASND saasound2;
|
||||||
int freq[2][6];
|
#else
|
||||||
float count[2][6];
|
void* saasound;
|
||||||
int vol[2][6][2];
|
void* saasound2;
|
||||||
int stat[2][6];
|
#endif
|
||||||
uint16_t noise[2][2];
|
|
||||||
uint16_t noisefreq[2][2];
|
|
||||||
int noisecount[2][2];
|
|
||||||
int noisetype[2][2];
|
|
||||||
|
|
||||||
uint8_t latched_data;
|
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;
|
} cms_t;
|
||||||
|
|
||||||
extern void cms_update(cms_t *cms);
|
extern void cms_update(cms_t *cms);
|
||||||
|
|||||||
@@ -180,6 +180,9 @@ endif()
|
|||||||
add_subdirectory(ymfm)
|
add_subdirectory(ymfm)
|
||||||
target_link_libraries(86Box ymfm)
|
target_link_libraries(86Box ymfm)
|
||||||
|
|
||||||
|
add_subdirectory(saasound)
|
||||||
|
target_link_libraries(86Box saasound)
|
||||||
|
|
||||||
if(GUSMAX)
|
if(GUSMAX)
|
||||||
target_compile_definitions(snd PRIVATE USE_GUSMAX)
|
target_compile_definitions(snd PRIVATE USE_GUSMAX)
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
16
src/sound/saasound/CMakeLists.txt
Normal file
16
src/sound/saasound/CMakeLists.txt
Normal 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
203
src/sound/saasound/SAAAmp.cpp
Executable 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
44
src/sound/saasound/SAAAmp.h
Executable 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
|
||||||
41
src/sound/saasound/SAAConfig.h
Normal file
41
src/sound/saasound/SAAConfig.h
Normal 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
|
||||||
392
src/sound/saasound/SAADevice.cpp
Normal file
392
src/sound/saasound/SAADevice.cpp
Normal 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;
|
||||||
|
}
|
||||||
69
src/sound/saasound/SAADevice.h
Normal file
69
src/sound/saasound/SAADevice.h
Normal 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
380
src/sound/saasound/SAAEnv.cpp
Executable 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
54
src/sound/saasound/SAAEnv.h
Executable 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
280
src/sound/saasound/SAAFreq.cpp
Executable 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
141
src/sound/saasound/SAAFreq.dat
Executable 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
72
src/sound/saasound/SAAFreq.h
Executable 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
|
||||||
487
src/sound/saasound/SAAImpl.cpp
Normal file
487
src/sound/saasound/SAAImpl.cpp
Normal 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
75
src/sound/saasound/SAAImpl.h
Executable 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
180
src/sound/saasound/SAANoise.cpp
Executable 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
54
src/sound/saasound/SAANoise.h
Executable 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
100
src/sound/saasound/SAASndC.cpp
Executable 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();
|
||||||
|
}
|
||||||
102
src/sound/saasound/SAASndC.h
Normal file
102
src/sound/saasound/SAASndC.h
Normal 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
13
src/sound/saasound/SAASound.cpp
Executable 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;
|
||||||
|
}
|
||||||
130
src/sound/saasound/SAASound.h
Normal file
130
src/sound/saasound/SAASound.h
Normal 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
|
||||||
59
src/sound/saasound/defns.h
Normal file
59
src/sound/saasound/defns.h
Normal 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
15
src/sound/saasound/resource.h
Executable 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
|
||||||
14
src/sound/saasound/saasound_cmake_config.h
Normal file
14
src/sound/saasound/saasound_cmake_config.h
Normal 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
34
src/sound/saasound/types.h
Executable 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
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
#include <86box/86box.h>
|
#include <86box/86box.h>
|
||||||
#include <86box/device.h>
|
#include <86box/device.h>
|
||||||
#include <86box/io.h>
|
#include <86box/io.h>
|
||||||
|
#include "saasound/SAASound.h"
|
||||||
#include <86box/snd_cms.h>
|
#include <86box/snd_cms.h>
|
||||||
#include <86box/sound.h>
|
#include <86box/sound.h>
|
||||||
#include <86box/plat_unused.h>
|
#include <86box/plat_unused.h>
|
||||||
@@ -15,62 +16,13 @@
|
|||||||
void
|
void
|
||||||
cms_update(cms_t *cms)
|
cms_update(cms_t *cms)
|
||||||
{
|
{
|
||||||
for (; cms->pos < sound_pos_global; cms->pos++) {
|
if (cms->pos < wavetable_pos_global) {
|
||||||
int16_t out_l = 0;
|
SAASNDGenerateMany(cms->saasound, (unsigned char*)&cms->buffer[cms->pos], wavetable_pos_global - cms->pos);
|
||||||
int16_t out_r = 0;
|
cms->pos = wavetable_pos_global;
|
||||||
|
|
||||||
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->pos2 < wavetable_pos_global) {
|
||||||
for (uint8_t c = 0; c < 2; c++) {
|
SAASNDGenerateMany(cms->saasound2, (unsigned char*)&cms->buffer2[cms->pos2], wavetable_pos_global - cms->pos2);
|
||||||
if (cms->regs[c][0x1C] & 1) {
|
cms->pos2 = wavetable_pos_global;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,6 +39,19 @@ cms_get_buffer(int32_t *buffer, int len, void *priv)
|
|||||||
cms->pos = 0;
|
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
|
void
|
||||||
cms_write(uint16_t addr, uint8_t val, void *priv)
|
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) {
|
switch (addr & 0xf) {
|
||||||
case 0x1: /* SAA #1 Register Select Port */
|
case 0x1: /* SAA #1 Register Select Port */
|
||||||
cms->addrs[0] = val & 31;
|
SAASNDWriteAddress(cms->saasound, val & 31);
|
||||||
break;
|
break;
|
||||||
case 0x3: /* SAA #2 Register Select Port */
|
case 0x3: /* SAA #2 Register Select Port */
|
||||||
cms->addrs[1] = val & 31;
|
SAASNDWriteAddress(cms->saasound2, val & 31);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x0: /* SAA #1 Data Port */
|
case 0x0: /* SAA #1 Data Port */
|
||||||
|
cms_update(cms);
|
||||||
|
SAASNDWriteData(cms->saasound, val);
|
||||||
|
break;
|
||||||
case 0x2: /* SAA #2 Data Port */
|
case 0x2: /* SAA #2 Data Port */
|
||||||
cms_update(cms);
|
cms_update(cms);
|
||||||
cms->regs[chip][cms->addrs[chip] & 31] = val;
|
SAASNDWriteData(cms->saasound2, 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;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x6: /* GameBlaster Write Port */
|
case 0x6: /* GameBlaster Write Port */
|
||||||
@@ -163,9 +93,9 @@ cms_read(uint16_t addr, void *priv)
|
|||||||
|
|
||||||
switch (addr & 0xf) {
|
switch (addr & 0xf) {
|
||||||
case 0x1: /* SAA #1 Register Select Port */
|
case 0x1: /* SAA #1 Register Select Port */
|
||||||
return cms->addrs[0];
|
return SAASNDReadAddress(cms->saasound);
|
||||||
case 0x3: /* SAA #2 Register Select Port */
|
case 0x3: /* SAA #2 Register Select Port */
|
||||||
return cms->addrs[1];
|
return SAASNDReadAddress(cms->saasound2);
|
||||||
case 0x4: /* GameBlaster Read port (Always returns 0x7F) */
|
case 0x4: /* GameBlaster Read port (Always returns 0x7F) */
|
||||||
return 0x7f;
|
return 0x7f;
|
||||||
case 0xa: /* GameBlaster Read Port */
|
case 0xa: /* GameBlaster Read Port */
|
||||||
@@ -185,7 +115,12 @@ cms_init(UNUSED(const device_t *info))
|
|||||||
|
|
||||||
uint16_t addr = device_get_config_hex16("base");
|
uint16_t addr = device_get_config_hex16("base");
|
||||||
io_sethandler(addr, 0x0010, cms_read, NULL, NULL, cms_write, NULL, NULL, cms);
|
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;
|
return cms;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,6 +129,9 @@ cms_close(void *priv)
|
|||||||
{
|
{
|
||||||
cms_t *cms = (cms_t *) priv;
|
cms_t *cms = (cms_t *) priv;
|
||||||
|
|
||||||
|
deleteSAASND(cms->saasound);
|
||||||
|
deleteSAASND(cms->saasound2);
|
||||||
|
|
||||||
free(cms);
|
free(cms);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user