From 4ec553785f73d05b343c8d72c0daf19e2f0b7fdf Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Wed, 24 Jan 2018 22:20:44 +0100 Subject: [PATCH] Implement and connect audio emulation. --- src/emucore/tia/Audio.cxx | 88 ++++++++++++++++++- src/emucore/tia/Audio.hxx | 28 +++++- src/emucore/tia/AudioChannel.cxx | 143 +++++++++++++++++++++++++++++++ src/emucore/tia/AudioChannel.hxx | 61 +++++++++++++ src/emucore/tia/TIA.cxx | 13 +++ src/emucore/tia/module.mk | 3 +- 6 files changed, 333 insertions(+), 3 deletions(-) create mode 100644 src/emucore/tia/AudioChannel.cxx create mode 100644 src/emucore/tia/AudioChannel.hxx diff --git a/src/emucore/tia/Audio.cxx b/src/emucore/tia/Audio.cxx index 9f697b332..b776447bc 100644 --- a/src/emucore/tia/Audio.cxx +++ b/src/emucore/tia/Audio.cxx @@ -18,11 +18,97 @@ #include "Audio.hxx" #include "AudioQueue.hxx" +#include + +namespace { + constexpr double R_MAX = 30.; + constexpr double R = 1.; + + Int16 mixingTableEntry(uInt8 v, uInt8 vMax) + { + return floor(0x7fff * double(v) / double(vMax) * (R_MAX + R * double(vMax)) / (R_MAX + R * double(v))); + } +} + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Audio::Audio() {} +Audio::Audio() + : myAudioQueue(0), + myCurrentFragment(0) +{ + for (uInt8 i = 0; i <= 0x1e; i++) myMixingTableSum[i]= mixingTableEntry(i, 0x1e); + for (uInt8 i = 0; i <= 0x0f; i++) myMixingTableIndividual[i] = mixingTableEntry(i, 0x0f); + + reset(); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void Audio::reset() +{ + myCounter = 0; + mySampleIndex = 0; + + myChannel0.reset(); + myChannel1.reset(); +} // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void Audio::setAudioQueue(AudioQueue* queue) { myAudioQueue = queue; + + myCurrentFragment = myAudioQueue->enqueue(); + mySampleIndex = 0; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void Audio::tick() +{ switch (myCounter) { + case 9: + case 81: + myChannel0.phase0(); + myChannel1.phase0(); + + break; + + case 37: + case 149: + phase1(); + break; + } + + if (++myCounter == 228) myCounter = 0; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void Audio::phase1() +{ + uInt8 sample0 = myChannel0.phase1(); + uInt8 sample1 = myChannel1.phase1(); + + if (!myAudioQueue) return; + + if (myAudioQueue->isStereo()) { + myCurrentFragment[2*mySampleIndex] = myMixingTableIndividual[sample0]; + myCurrentFragment[2*mySampleIndex + 1] = myMixingTableIndividual[sample1]; + } else { + myCurrentFragment[mySampleIndex] = myMixingTableSum[sample0 + sample1]; + } + + if (++mySampleIndex == myAudioQueue->fragmentSize()) { + mySampleIndex = 0; + myCurrentFragment = myAudioQueue->enqueue(myCurrentFragment); + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +AudioChannel& Audio::channel0() +{ + return myChannel0; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +AudioChannel& Audio::channel1() +{ + return myChannel1; } diff --git a/src/emucore/tia/Audio.hxx b/src/emucore/tia/Audio.hxx index d309c0f76..595b53765 100644 --- a/src/emucore/tia/Audio.hxx +++ b/src/emucore/tia/Audio.hxx @@ -18,17 +18,43 @@ #ifndef TIA_AUDIO_HXX #define TIA_AUDIO_HXX +#include "bspf.hxx" +#include "AudioChannel.hxx" + class AudioQueue; -class Audio { +class Audio +{ public: Audio(); + void reset(); + void setAudioQueue(AudioQueue *queue); + void tick(); + + AudioChannel& channel0(); + + AudioChannel& channel1(); + + private: + void phase1(); + private: AudioQueue* myAudioQueue; + uInt8 myCounter; + + AudioChannel myChannel0; + AudioChannel myChannel1; + + Int16 myMixingTableSum[0x1e + 1]; + Int16 myMixingTableIndividual[0x0f + 1]; + + Int16* myCurrentFragment; + uInt32 mySampleIndex; + private: Audio(const Audio&) = delete; Audio(Audio&&) = delete; diff --git a/src/emucore/tia/AudioChannel.cxx b/src/emucore/tia/AudioChannel.cxx new file mode 100644 index 000000000..b7e13fa30 --- /dev/null +++ b/src/emucore/tia/AudioChannel.cxx @@ -0,0 +1,143 @@ +//============================================================================ +// +// SSSS tt lll lll +// SS SS tt ll ll +// SS tttttt eeee ll ll aaaa +// SSSS tt ee ee ll ll aa +// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" +// SS SS tt ee ll ll aa aa +// SSSS ttt eeeee llll llll aaaaa +// +// Copyright (c) 1995-2018 by Bradford W. Mott, Stephen Anthony +// and the Stella Team +// +// See the file "License.txt" for information on usage and redistribution of +// this file, and for a DISCLAIMER OF ALL WARRANTIES. +//============================================================================ + +#include "AudioChannel.hxx" + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +AudioChannel::AudioChannel() +{ + reset(); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void AudioChannel::reset() +{ + myAudc = myAudv = myAudf = 0; + myClockEnable = myNoiseFeedback = myNoiseCounterBit4 = myPulseCounterHold = false; + myDivCounter = myPulseCounter = myNoiseCounter = 0; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void AudioChannel::phase0() +{ + if (myClockEnable) { + myNoiseCounterBit4 = myNoiseCounter & 0x01; + + switch (myAudc & 0x03) { + case 0x00: + case 0x01: + myPulseCounterHold = false; + break; + + case 0x02: + myPulseCounterHold = (myNoiseCounter & 0x1e) != 0x02; + break; + + case 0x03: + myPulseCounterHold = myNoiseCounterBit4; + break; + } + + switch (myAudc & 0x03) { + case 0x00: + myNoiseFeedback = + ((myPulseCounter ^ myNoiseCounter) & 0x01) || + !(myNoiseCounter || (myPulseCounter != 0x0a)) || + !(myAudc & 0x0c); + + break; + + default: + myNoiseFeedback = + ((myNoiseCounter & 0x04 ? 1 : 0) ^ (myNoiseCounter & 0x01)) || + myNoiseCounter == 0; + + break; + } + } + + myClockEnable = myDivCounter == myAudf; + + if (myDivCounter == myAudf || myDivCounter == 0x1f) { + myDivCounter = 0; + } else { + myDivCounter++; + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +uInt8 AudioChannel::phase1() +{ + bool pulseFeedback = false; + + if (myClockEnable) { + switch (myAudc >> 2) { + case 0x00: + pulseFeedback = + ((myPulseCounter & 0x02 ? 1 : 0) ^ (myPulseCounter & 0x01)) && + (myPulseCounter != 0x0a) && + (myAudc & 0x03); + + break; + + case 0x01: + pulseFeedback = !(myPulseCounter & 0x08); + break; + + case 0x02: + pulseFeedback = !myNoiseCounterBit4; + break; + + case 0x03: + pulseFeedback = !((myPulseCounter & 0x02) || !(myPulseCounter & 0x0e)); + break; + } + + myNoiseCounter >>= 1; + if (myNoiseFeedback) { + myNoiseCounter |= 0x10; + } + + if (!myPulseCounterHold) { + myPulseCounter = ~(myPulseCounter >> 1) & 0x07; + + if (pulseFeedback) { + myPulseCounter |= 0x08; + } + } + } + + return (myPulseCounter & 0x01) * myAudv; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void AudioChannel::audc(uInt8 value) +{ + myAudc = value & 0x0f; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void AudioChannel::audv(uInt8 value) +{ + myAudv = value & 0x0f; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void AudioChannel::audf(uInt8 value) +{ + myAudc = value & 0x1f; +} diff --git a/src/emucore/tia/AudioChannel.hxx b/src/emucore/tia/AudioChannel.hxx new file mode 100644 index 000000000..de596edbd --- /dev/null +++ b/src/emucore/tia/AudioChannel.hxx @@ -0,0 +1,61 @@ +//============================================================================ +// +// SSSS tt lll lll +// SS SS tt ll ll +// SS tttttt eeee ll ll aaaa +// SSSS tt ee ee ll ll aa +// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" +// SS SS tt ee ll ll aa aa +// SSSS ttt eeeee llll llll aaaaa +// +// Copyright (c) 1995-2018 by Bradford W. Mott, Stephen Anthony +// and the Stella Team +// +// See the file "License.txt" for information on usage and redistribution of +// this file, and for a DISCLAIMER OF ALL WARRANTIES. +//============================================================================ + +#ifndef TIA_AUDIO_CHANNEL_HXX +#define TIA_AUDIO_CHANNEL_HXX + +#include "bspf.hxx" + +class AudioChannel +{ + public: + AudioChannel(); + + void reset(); + + void phase0(); + + uInt8 phase1(); + + void audc(uInt8 value); + + void audf(uInt8 value); + + void audv(uInt8 value); + + private: + uInt8 myAudc; + uInt8 myAudv; + uInt8 myAudf; + + bool myClockEnable; + bool myNoiseFeedback; + bool myNoiseCounterBit4; + bool myPulseCounterHold; + + uInt8 myDivCounter; + uInt8 myPulseCounter; + uInt8 myNoiseCounter; + + private: + AudioChannel(const AudioChannel&); + AudioChannel(AudioChannel&&); + AudioChannel& operator=(const AudioChannel&); + AudioChannel& operator=(AudioChannel&&); +}; + +#endif // TIA_AUDIO_CHANNEL_HXX diff --git a/src/emucore/tia/TIA.cxx b/src/emucore/tia/TIA.cxx index ae9c9666a..641d1d4c9 100644 --- a/src/emucore/tia/TIA.cxx +++ b/src/emucore/tia/TIA.cxx @@ -521,21 +521,32 @@ bool TIA::poke(uInt16 address, uInt8 value) break; case AUDV0: + myAudio.channel0().audv(value); myShadowRegisters[address] = value; break; + case AUDV1: + myAudio.channel1().audv(value); myShadowRegisters[address] = value; break; + case AUDF0: + myAudio.channel0().audf(value); myShadowRegisters[address] = value; break; + case AUDF1: + myAudio.channel1().audf(value); myShadowRegisters[address] = value; break; + case AUDC0: + myAudio.channel0().audc(value); myShadowRegisters[address] = value; break; + case AUDC1: + myAudio.channel1().audc(value); myShadowRegisters[address] = value; break; @@ -1179,6 +1190,8 @@ void TIA::cycle(uInt32 colorClocks) if (++myHctr >= 228) nextLine(); + myAudio.tick(); + myTimestamp++; } } diff --git a/src/emucore/tia/module.mk b/src/emucore/tia/module.mk index 569dfc35e..d6302e805 100644 --- a/src/emucore/tia/module.mk +++ b/src/emucore/tia/module.mk @@ -10,7 +10,8 @@ MODULE_OBJS := \ src/emucore/tia/Background.o \ src/emucore/tia/LatchedInput.o \ src/emucore/tia/PaddleReader.o \ - src/emucore/tia/Audio.o + src/emucore/tia/Audio.o \ + src/emucore/tia/AudioChannel.o MODULE_DIRS += \ src/emucore/tia