i don't know whether I will be able to do this

This commit is contained in:
tildearrow 2022-10-05 03:58:55 -05:00
parent 16b752dc8a
commit 093a45bc3f
6 changed files with 5688 additions and 0 deletions

View File

@ -0,0 +1,406 @@
// Altirra - Atari 800/800XL emulator
// Copyright (C) 2008 Avery Lee
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#ifndef AT_POKEY_H
#define AT_POKEY_H
#include <stdint.h>
#include <deque>
#include <vector>
#ifdef _MSC_VER
#pragma once
#endif
class IATAudioOutput;
class ATPokeyEmulator;
class ATSaveStateReader;
class ATAudioFilter;
struct ATPokeyTables;
class ATPokeyRenderer;
class IATObjectState;
class IATPokeyEmulatorConnections {
public:
virtual void PokeyAssertIRQ(bool cpuBased) = 0;
virtual void PokeyNegateIRQ(bool cpuBased) = 0;
virtual void PokeyBreak() = 0;
virtual bool PokeyIsInInterrupt() const = 0;
virtual bool PokeyIsKeyPushOK(uint8_t scanCode, bool cooldownExpired) const = 0;
};
class IATPokeySIODevice {
public:
virtual void PokeyAttachDevice(ATPokeyEmulator *pokey) = 0;
// Returns true if burst I/O is allowed.
virtual bool PokeyWriteSIO(uint8_t c, bool command, uint32_t cyclesPerBit, uint64_t startTime, bool framingError) = 0;
virtual void PokeyBeginCommand() = 0;
virtual void PokeyEndCommand() = 0;
virtual void PokeySerInReady() = 0;
};
class IATPokeyCassetteDevice {
public:
virtual void PokeyChangeSerialRate(uint32_t divisor) = 0;
virtual void PokeyResetSerialInput() = 0;
virtual void PokeyBeginCassetteData(uint8_t skctl) = 0;
virtual bool PokeyWriteCassetteData(uint8_t c, uint32_t cyclesPerBit) = 0;
};
class IATPokeyTraceOutput {
public:
virtual void AddIRQ(uint64_t start, uint64_t end) = 0;
};
struct ATPokeyRegisterState {
uint8_t mReg[0x20];
};
struct ATPokeyAudioState {
int mChannelOutputs[4];
};
struct ATPokeyAudioLog {
// Sampling buffer -- receives per-channel output state every N ticks, up to the given max
// number of samples per frame. Automatically cleared per frame.
ATPokeyAudioState *mpStates;
uint32_t mMaxSamples;
uint32_t mCyclesPerSample;
// Mixed sampling buffer -- receives combined output state every sample, up to the given max
// samples per frame. This buffer is NOT automatically cleared and must be manually retriggered.
float *mpMixedSamples;
uint32_t mMaxMixedSamples;
// === filled in by audio engine ===
uint32_t mFullScaleValue;
uint32_t mTicksPerSample;
uint32_t mLastFrameSampleCount;
uint32_t mNumMixedSamples;
// === for continuous use by audio engine ===
uint32_t mStartingAudioTick;
uint32_t mLastAudioTick;
uint32_t mAccumulatedAudioTicks;
uint32_t mSampleIndex;
uint32_t mLastOutputMask;
};
class ATPokeyEmulator {
public:
ATPokeyEmulator(bool isSlave);
~ATPokeyEmulator();
void Init(IATPokeyEmulatorConnections *mem, IATAudioOutput *output, ATPokeyTables *tables);
void ColdReset();
void SetSlave(ATPokeyEmulator *slave);
void SetCassette(IATPokeyCassetteDevice *dev);
void SetAudioLog(ATPokeyAudioLog *log);
void SetConsoleOutput(ATConsoleOutput *output);
void Set5200Mode(bool enable);
bool IsTraceSIOEnabled() const { return mbTraceSIO; }
void SetTraceSIOEnabled(bool enable) { mbTraceSIO = enable; }
void AddSIODevice(IATPokeySIODevice *device);
void RemoveSIODevice(IATPokeySIODevice *device);
void ReceiveSIOByte(uint8_t byte, uint32_t cyclesPerBit, bool simulateInputPort, bool allowBurst, bool synchronous, bool forceFramingError);
void SetSERIN(uint8_t v) { mSERIN = v; }
void SetAudioLine2(int v); // used for audio from motor control line
void SetDataLine(bool newState, uint64_t flipTime = ~uint64_t(0));
void SetCommandLine(bool newState);
void SetSpeaker(bool newState);
void SetStereoSoftEnable(bool enable);
void SetExternalSerialClock(uint32_t basetime, uint32_t period);
uint32_t GetSerialCyclesPerBitRecv() const;
uint32_t GetSerialInputResetCounter() const { return mSerialInputResetCounter; }
bool IsChannelEnabled(uint32_t channel) const;
void SetChannelEnabled(uint32_t channel, bool enabled);
bool IsSecondaryChannelEnabled(uint32_t channel) const;
void SetSecondaryChannelEnabled(uint32_t channel, bool enabled);
bool IsNonlinearMixingEnabled() const { return mbNonlinearMixingEnabled; }
void SetNonlinearMixingEnabled(bool enable);
bool IsSerialNoiseEnabled() const { return mbSerialNoiseEnabled; }
void SetSerialNoiseEnabled(bool enable) { mbSerialNoiseEnabled = enable; }
bool GetShiftKeyState() const { return mbShiftKeyState; }
void SetShiftKeyState(bool down, bool immediate);
bool GetControlKeyState() const { return mbControlKeyState; }
void SetControlKeyState(bool down);
void ClearKeyQueue();
void PushKey(uint8_t c, bool repeat, bool allowQueue = false, bool flushQueue = true, bool useCooldown = true);
uint64_t GetRawKeyMask() const;
void PushRawKey(uint8_t c, bool immediate);
void ReleaseRawKey(uint8_t c, bool immediate);
void ReleaseAllRawKeys(bool immediate);
void SetBreakKeyState(bool down, bool immediate);
void PushBreak();
void SetKeyMatrix(const bool matrix[64]);
void SetPotPos(unsigned idx, int pos);
void SetPotPosHires(unsigned idx, int pos, bool grounded);
// Get/set immediate pot mode. Immediate pot mode allows the POT0-7 registers
// to update within a frame of the last pot scan triggered via POTGO. This
// fibs accuracy slightly for reduction in latency.
bool IsImmediatePotUpdateEnabled() const { return mbAllowImmediatePotUpdate; }
void SetImmediatePotUpdateEnabled(bool enabled) { mbAllowImmediatePotUpdate = enabled; }
void AdvanceScanLine();
void AdvanceFrame(bool pushAudio, uint64_t timestamp);
uint8_t DebugReadByte(uint8_t reg) const;
uint8_t ReadByte(uint8_t reg);
void WriteByte(uint8_t reg, uint8_t value);
void DumpStatus(ATConsoleOutput& out);
void SaveState(IATObjectState **pp);
void LoadState(const IATObjectState& state);
void PostLoadState();
void GetRegisterState(ATPokeyRegisterState& state) const;
void FlushAudio(bool pushAudio, uint64_t timestamp);
void SetTraceOutput(IATPokeyTraceOutput *output);
uint32_t GetCyclesToTimerFire(uint32_t ch) const;
protected:
// override
void OnScheduledEvent(uint32_t id);
void PostFrameUpdate(uint32_t t);
template<uint8_t channel>
void FireTimer();
uint32_t UpdateLast15KHzTime();
uint32_t UpdateLast15KHzTime(uint32_t t);
uint32_t UpdateLast64KHzTime();
uint32_t UpdateLast64KHzTime(uint32_t t);
void UpdatePolyTime();
void OnSerialInputTick();
void OnSerialOutputTick();
bool IsSerialOutputClockRunning() const;
void FlushSerialOutput();
void RecomputeAllowedDeferredTimers();
template<int channel>
void RecomputeTimerPeriod();
template<int channel>
void UpdateTimerCounter();
void SetupTimers(uint8_t channels);
void FlushDeferredTimerEvents(int channel);
void SetupDeferredTimerEvents(int channel, uint32_t t0, uint32_t period);
void SetupDeferredTimerEventsLinked(int channel, uint32_t t0, uint32_t period, uint32_t hit0, uint32_t hiperiod, uint32_t hilooffset);
void DumpStatus(ATConsoleOutput& out, bool isSlave);
void UpdateMixTable();
void UpdateKeyMatrix(int index, uint16_t mask, uint16_t state);
void UpdateEffectiveKeyMatrix();
bool CanPushKey(uint8_t scanCode) const;
void TryPushNextKey();
void SetKeyboardModes(bool cooked, bool scanEnabled);
void UpdateKeyboardScanEvent();
void QueueKeyboardIRQ();
void AssertKeyboardIRQ();
void AssertBreakIRQ();
void AssertIrq(bool cpuBased);
void NegateIrq(bool cpuBased);
void ProcessReceivedSerialByte();
void SyncRenderers(ATPokeyRenderer *r);
void StartPotScan();
void UpdatePots(uint32_t timeSkew);
void UpdateAddressDecoding();
private:
ATPokeyRenderer *mpRenderer;
int mTimerCounters[4];
bool mbCommandLineState;
bool mbPal;
bool mb5200Mode;
bool mbTraceSIO;
bool mbNonlinearMixingEnabled;
bool mbSerialNoiseEnabled = true;
uint8_t mKBCODE;
uint32_t mKeyCodeTimer;
uint32_t mKeyCooldownTimer;
bool mbKeyboardIRQPending;
bool mbUseKeyCooldownTimer;
bool mbCookedKeyMode;
bool mbKeyboardScanEnabled;
bool mbShiftKeyState;
bool mbShiftKeyLatchedState;
bool mbControlKeyState;
bool mbControlKeyLatchedState;
bool mbBreakKeyState;
bool mbBreakKeyLatchedState;
uint8_t mAddressMask;
uint8_t mIRQEN;
uint8_t mIRQST;
uint8_t mAUDF[4]; // $D200/2/4/6: audio frequency, channel 1/2/3/4
uint8_t mAUDC[4]; // $D201/3/5/7: audio control, channel 1/2/3/4
uint8_t mAUDCTL; // $D208
// bit 7: use 9-bit poly instead of 17-bit poly
// bit 6: clock channel 1 with 1.79MHz instead of 64KHz
// bit 5: clock channel 3 with 1.79MHz instead of 64KHz
// bit 4: clock channel 2 with channel 1 instead of 64KHz
// bit 3: clock channel 4 with channel 3 instead of 64KHz
// bit 2: apply high pass filter to channel 1 using channel 3
// bit 1: apply high pass filter to channel 2 using channel 4
// bit 0: change 64KHz frequency to 15KHz
uint8_t mSERIN; // $D20D: SERIN
uint8_t mSEROUT; // $D20D: SEROUT
uint8_t mSKSTAT; // $D20F: SKSTAT
// bit 3: shift key depressed
// bit 2: key depressed
uint8_t mSKCTL; // $D20F: SKCTL
// bit 3: shift key depressed
// bit 2: key depressed
ATPokeyRegisterState mState;
// countdown timer values
int mAUDFP1[4]; // AUDF values, plus 1 (we use these everywhere)
int mCounter[4];
int mCounterBorrow[4];
uint32_t mTimerPeriod[4];
uint32_t mTimerFullPeriod[2]; // time for timer to count off 256 in linked mode (#1 and #3 only)
mutable uint32_t mLastPolyTime;
mutable uint32_t mPoly17Counter;
mutable uint32_t mPoly9Counter;
uint64_t mPolyShutOffTime;
uint64_t mSerialOutputStartTime;
uint8_t mSerialInputShiftRegister;
uint8_t mSerialOutputShiftRegister;
uint8_t mSerialInputCounter;
uint8_t mSerialOutputCounter;
uint8_t mSerialInputPendingStatus;
bool mbSerOutValid;
bool mbSerShiftValid;
bool mbSerialOutputState;
bool mbSpeakerActive;
bool mbSerialRateChanged;
bool mbSerialWaitingForStartBit;
bool mbSerInBurstPendingIRQ1;
bool mbSerInBurstPendingIRQ2;
bool mbSerInBurstPendingData;
bool mbSerInDeferredLoad;
uint32_t mSerOutBurstDeadline;
uint32_t mSerialInputResetCounter = 0;
uint32_t mSerialSimulateInputBaseTime;
uint32_t mSerialSimulateInputCyclesPerBit;
uint32_t mSerialSimulateInputData;
bool mbSerialSimulateInputPort;
uint32_t mSerialExtBaseTime;
uint32_t mSerialExtPeriod;
uint64_t mSerialDataInFlipTime = ~(uint64_t)0;
ATPokeyTables *mpTables = nullptr;
// AUDCTL breakout
bool mbFastTimer1;
bool mbFastTimer3;
bool mbLinkedTimers12;
bool mbLinkedTimers34;
bool mbUse15KHzClock;
bool mbAllowDeferredTimer[4];
uint32_t mLast15KHzTime;
uint32_t mLast64KHzTime;
bool mbDeferredTimerEvents[4];
uint32_t mDeferredTimerStarts[4];
uint32_t mDeferredTimerPeriods[4];
uint16_t mKeyMatrix[8] = {};
uint16_t mEffectiveKeyMatrix[8] = {};
IATPokeyEmulatorConnections *mpConn;
IATPokeyTraceOutput *mpTraceOutput = nullptr;
ATPokeyEmulator *mpSlave;
const bool mbIsSlave;
bool mbIrqAsserted;
IATAudioOutput *mpAudioOut = nullptr;
typedef std::vector<IATPokeySIODevice *> Devices;
Devices mDevices;
IATPokeyCassetteDevice *mpCassette = nullptr;
std::deque<uint8_t> mKeyQueue;
uint8_t mKeyScanState = 0;
uint8_t mKeyScanCode = 0;
uint8_t mKeyScanLatch = 0;
uint8_t mPotPositions[8] = {};
uint8_t mPotHiPositions[8] = {};
uint8_t mPotLatches[8] = {};
uint8_t mALLPOT = 0;
uint8_t mPotMasterCounter = 0;
uint64_t mPotLastScanTime = 0; // cycle time of last write to POTGO
uint32_t mPotLastTimeFast = 0;
uint32_t mPotLastTimeSlow = 0;
bool mbAllowImmediatePotUpdate = false;
bool mbStereoSoftEnable = true;
bool mTraceDirectionSend = false;
uint32_t mTraceByteIndex = 0;
bool mbTraceIrqPending = false;
uint64_t mTraceIrqStart = 0;
};
#endif

View File

@ -0,0 +1,266 @@
// Altirra - Atari 800/800XL/5200 emulator
// Copyright (C) 2008-2019 Avery Lee
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#ifndef f_AT_POKEYRENDERER_H
#define f_AT_POKEYRENDERER_H
#include <stdint.h>
#include <deque>
#include <vector>
class ATScheduler;
struct ATPokeyTables;
struct ATPokeyAudioState;
class IATSyncAudioEdgePlayer;
struct ATPokeyAudioLog;
struct ATSaveStatePokeyRenderer;
class ATPokeyRenderer {
ATPokeyRenderer(const ATPokeyRenderer&) = delete;
ATPokeyRenderer& operator=(const ATPokeyRenderer&) = delete;
public:
ATPokeyRenderer();
~ATPokeyRenderer();
void Init(ATPokeyTables *tables);
void ColdReset();
void SyncTo(const ATPokeyRenderer& src);
bool GetChannelOutput(int index) const { return (mChannelOutputMask & (1 << index)) != 0; }
const float *GetOutputBuffer() const { return mRawOutputBuffer; }
bool IsChannelEnabled(int channel) const { return mbChannelEnabled[channel]; }
void SetChannelEnabled(int channel, bool enable);
void SetAudioLog(ATPokeyAudioLog *log);
void RestartAudioLog(bool initial = false);
void SetFiltersEnabled(bool enable);
void SetInitMode(bool init);
bool SetSpeaker(bool state);
void SetAudioLine2(int v);
void ResetTimers();
void SetAUDCx(int index, uint8_t value);
void SetAUDCTL(uint8_t value);
void AddChannelEvent(int channel);
void SetChannelDeferredEvents(int channel, uint32_t start, uint32_t period);
void SetChannelDeferredEventsLinked(int channel, uint32_t loStart, uint32_t loPeriod, uint32_t hiStart, uint32_t hiPeriod, uint32_t loOffset);
void ClearChannelDeferredEvents(int channel, uint32_t t);
void AddSerialNoisePulse(uint32_t t);
void StartBlock();
struct EndBlockInfo {
uint32_t mTimestamp;
uint32_t mSamples;
};
EndBlockInfo EndBlock(IATSyncAudioEdgePlayer *edgePlayer);
void LoadState(const ATSaveStatePokeyRenderer& state);
ATSaveStatePokeyRenderer SaveState() const;
protected:
enum class ChangeType : uint8_t {
Audc0,
Audc1,
Audc2,
Audc3,
Audctl,
Init,
ResetOutputs,
Flush
};
void QueueChangeEvent(ChangeType type, uint8_t value);
void ProcessChangeEvents(uint32_t t);
void FlushDeferredEvents(int channel, uint32_t t);
void Flush(const uint32_t t);
void Flush2(const uint32_t t);
static void MergeOutputEvents(const uint32_t* src1, const uint32_t* src2, uint32_t* dst);
typedef std::pair<uint32_t *, const uint32_t *> (ATPokeyRenderer::*FireTimerRoutine)(uint32_t* dst, const uint32_t* src, uint32_t timeBase, uint32_t timeLimit);
FireTimerRoutine GetFireTimerRoutine(int ch) const;
template<int activeChannel>
FireTimerRoutine GetFireTimerRoutine() const;
template<int activeChannel, uint8_t audcn, bool outputAffectsSignal, bool T_UsePoly9>
std::pair<uint32_t *, const uint32_t *> FireTimer(uint32_t* dst, const uint32_t* src, uint32_t timeBase, uint32_t timeLimit);
void ProcessOutputEdges(uint32_t timeBase, const uint32_t *edges, uint32_t n);
void UpdateVolume(int channel);
void UpdateOutput(uint32_t t);
void UpdateOutput2(uint32_t t2);
void UpdateOutput2(uint32_t t2, uint32_t vpok);
void GenerateSamples(uint32_t t2);
void PostFilter();
void LogOutputChange(uint32_t t2) const;
void LogOutputEdges(uint32_t timeBase2, const uint32_t *edges, uint32_t n) const;
ATScheduler *mpScheduler;
ATPokeyTables *mpTables;
bool mbInitMode;
float mHighPassAccum;
float mOutputLevel;
uint32_t mLastFlushTime;
int mExternalInput;
bool mbSpeakerState;
// Noise/tone flip-flop state for all four channels. This is the version updated by the
// FireTimer() routines; change events are then produced to update the analogous bits 0-3
// in the channel output mask.
uint8_t mNoiseFlipFlops = 0;
// Noise/tone and high-pass flip-flop states. Bits 0-3 contain the noise flip-flop states,
// updated by the output code from change events generated from mNoiseFlipFlops; bits 4-5
// contain the high-pass flip-flops for ch1-2.
uint8_t mChannelOutputMask = 0;
// Bits 0-3 set if ch1-4 is in volume-only mode (AUDCx bit 4 = 1).
uint8_t mVolumeOnlyMask = 0;
// Bits 0-3 set if AUDCx bit 0-3 > 0. Note that this includes muting, so we cannot use
// this for architectural state.
uint8_t mNonZeroVolumeMask = 0;
uint8_t mChannelVolume[4] {};
uint32_t mChannelVolMixIndex[4] {};
struct BufferedState {
uint8_t mAUDC[4];
uint8_t mAUDCTL;
};
BufferedState mArchState {};
BufferedState mRenderState {};
// True if the channel is enabled for update or muted. This does NOT affect architectural
// state; it must not affect whether flip-flops are updated.
bool mbChannelEnabled[4];
struct DeferredEvent {
bool mbEnabled;
/// Set if 16-bit linked mode is enabled; this requires tracking the
/// high timer to know when to reset the low timer.
bool mbLinked;
/// Timestamp of next lo event.
uint32_t mNextTime;
/// Period of lo event in clocks.
uint32_t mPeriod;
/// Timestamp of next hi event.
uint32_t mNextHiTime;
/// Hi (16-bit) period in clocks.
uint32_t mHiPeriod;
/// Offset from hi event to next lo event.
uint32_t mHiLoOffset;
};
DeferredEvent mDeferredEvents[4] {};
struct ChangeEvent {
uint32_t mTime;
ChangeType mType;
uint8_t mValue;
};
std::deque<ChangeEvent> mChangeQueue;
struct PolyState {
uint32_t mInitMask = 0;
uintptr_t mPoly4Offset = 0;
uintptr_t mPoly5Offset = 0;
uintptr_t mPoly9Offset = 0;
uintptr_t mPoly17Offset = 0;
uint32_t mLastPoly17Time = 0;
uint32_t mPoly17Counter = 0;
uint32_t mLastPoly9Time = 0;
uint32_t mPoly9Counter = 0;
uint32_t mLastPoly5Time = 0;
uint32_t mPoly5Counter = 0;
uint32_t mLastPoly4Time = 0;
uint32_t mPoly4Counter = 0;
void UpdatePoly17Counter(uint32_t t);
void UpdatePoly9Counter(uint32_t t);
void UpdatePoly5Counter(uint32_t t);
void UpdatePoly4Counter(uint32_t t);
} mPolyState;
uint32_t mBlockStartTime = 0;
uint32_t mBlockStartTime2 = 0;
uint32_t mOutputSampleCount = 0;
ATPokeyAudioLog *mpAudioLog = nullptr;
// The sorted edge lists hold ordered output change events. The events are stored as
// packed bitfields for fast merging:
//
// bits 14-31 (18): half-cycle offset from beginning of flush operation
// bits 8-13 (6): AND mask to apply to flip/flops
// bits 0-5 (6): OR mask to apply to flip/flops
//
// Bits 4-5 of the masks are special as they apply to the high-pass flip/flops. The OR
// mask for these bits is ANDed with the ch1/2 outputs, so they update the HP F/Fs instead
// of setting them.
typedef std::vector<uint32_t> SortedEdges;
SortedEdges mSortedEdgesTemp[4];
SortedEdges mSortedEdgesHpTemp1;
SortedEdges mSortedEdgesHpTemp2;
SortedEdges mSortedEdgesTemp2[2];
SortedEdges mSortedEdges;
// The channel edge lists hold an ordered list of timer underflow events. The ticks are
// in system time (from ATScheduler).
typedef std::vector<uint32_t> ChannelEdges;
ChannelEdges mChannelEdges[4];
uint32_t mChannelEdgeBases[4] {};
std::vector<uint32_t> mSerialPulseTimes;
float mSerialPulse = 0;
enum : uint32_t {
// 1271 samples is the max (35568 cycles/frame / 28 cycles/sample + 1). We add a little bit here
// to round it out. We need a 16 sample holdover in order to run the FIR filter.
kBufferSize = 1536,
kMaxWriteIndex = kBufferSize - 16
};
alignas(16) float mRawOutputBuffer[kBufferSize];
template<int activeChannel, bool T_UsePoly9>
static const FireTimerRoutine kFireRoutines[2][16];
};
#endif // f_AT_POKEYRENDERER_H

View File

@ -0,0 +1,84 @@
// Altirra - Atari 800/800XL/5200 emulator
// Copyright (C) 2008-2018 Avery Lee
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#ifndef f_AT_POKEYTABLES_H
#define f_AT_POKEYTABLES_H
#include <stdint.h>
struct ATPokeyTables {
// Rate of decay per sample (28 cycles) for the first stage amplifier output. This
// affects POKEY output but not GTIA (CONSOL) output.
float mReferenceDecayPerSample;
// Min/max clamps for first stage amplifier output. These are in terms of final output
// polarity, so negated from volume values.
float mReferenceClampLo;
float mReferenceClampHi;
float mSpeakerLevel;
// Volume table for sums of all four audio channels.
//
// The index for this table is constructed as follows:
//
// index = sum(v[i] & 1) + 5*sum((v[i] & 2)/2) + 25*sum((v[i] & 12) / 4)
//
// The packed fields are thus: the number of channels with volume bit 0 set, the
// number of channels with volume bit 1 set, and the number of volume bit 2 equivalent
// steps for all channels. The last part takes advantage of the measurement for bit 3
// being close enough to twice bit 2 that we can combine the two to reduce table size.
// With this formulation, the POKEY channel volume levels can be translated to index
// deltas that are just added together.
//
// Also in this table is the saturation curve, which kicks in roughly at volume level
// 12 of a single channel, and negation, so the output is the same polarity as on
// the actual hardware (POKEY pulls down from ~4.80V).
//
// Finally, the result is divided by 56 to account for output being integrated across
// 56 half-cycles for each sample.
//
float mMixTable[325];
// Bit 0 = 17-bit polynomial
// Bit 1 = 9-bit polynomial
// Bit 2 = 5-bit polynomial
// Bit 3 = 4-bit polynomial
uint8_t mPolyBuffer[131071 * 2];
uint8_t mInitModeBuffer[131071 * 2];
ATPokeyTables();
};
// High-resolution filter table.
//
// This is used to resample from the 2x rate that audio events are generated
// at (3.54/3.58MHz) to the 1/28 mixing rate of 64KHz. It is a filter bank of
// 8-tap filter kernels at 56 sub-sample offsets, giving the response of the
// filter for a single half-tick impulse. Each transition in POKEY's output
// is converted to a half-tick edge pulse and the resampled through one of
// the 8-tap filters into the edge buffer.
struct ATPokeyHiFilterTable {
// This table is anti-symmetric, so we only need half of the kernel -- the
// other half is generated on the fly via reflection. Size: 928 bytes.
alignas(64) float mFilter[29][8];
};
extern const ATPokeyHiFilterTable g_ATPokeyHiFilterTable;
#endif // f_AT_POKEYTABLES_H

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,165 @@
// Altirra - Atari 800/800XL/5200 emulator
// Copyright (C) 2008-2011 Avery Lee
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#include "at/ataudio/pokeytables.h"
ATPokeyTables::ATPokeyTables() {
// The 4-bit and 5-bit polynomial counters are of the XNOR variety, which means
// that the all-1s case causes a lockup. The INIT mode shifts zeroes into the
// register.
uint32 poly4 = 0;
for(int i=0; i<131071; ++i) {
poly4 = (poly4+poly4) + (~((poly4 >> 2) ^ (poly4 >> 3)) & 1);
mPolyBuffer[i] = (poly4 & 1) << 3;
}
uint32 poly5 = 0;
for(int i=0; i<131071; ++i) {
poly5 = (poly5+poly5) + (~((poly5 >> 2) ^ (poly5 >> 4)) & 1);
mPolyBuffer[i] |= (poly5 & 1) << 2;
}
// The 17-bit polynomial counter is also of the XNOR variety, but one big
// difference is that you're allowed to read out 8 bits of it. The RANDOM
// register actually reports the INVERTED state of these bits (the Q' output
// of the flip flops is connected to the data bus). This means that even
// though we clear the register to 0, it reads as FF.
//
// From the perspective of the CPU through RANDOM, the LFSR shifts to the
// right, and new bits appear on the left. The equivalent operation for the
// 9-bit LFSR would be to set carry equal to (D0 ^ D5) and then execute
// a ROR.
uint32 poly9 = 0;
for(int i=0; i<131071; ++i) {
// Note: This one is actually verified against a real Atari.
// At WSYNC time, the pattern goes: 00 DF EE 16 B9....
poly9 = (poly9 >> 1) + (~((poly9 << 8) ^ (poly9 << 3)) & 0x100);
mPolyBuffer[i] |= (poly9 & 1) << 1;
}
// The 17-bit mode inserts an additional 8 register bits immediately after
// the XNOR. The generator polynomial is unchanged.
uint32 poly17 = 0;
for(int i=0; i<131071; ++i) {
poly17 = (poly17 >> 1) + (~((poly17 << 16) ^ (poly17 << 11)) & 0x10000);
mPolyBuffer[i] |= (poly17 >> 8) & 1;
}
memcpy(mPolyBuffer + 131071, mPolyBuffer, 131071);
memset(mInitModeBuffer, 0xFF, sizeof mInitModeBuffer);
}
constexpr ATPokeyHiFilterTable ATMakePokeyHiFilterTable() {
constexpr double kRawFilter[][8] = {
// Coefficients for a windowed-sinc LPF at 24KHz cutoff with Kaiser window. Scilab derivation:
//
// pi=atan(1)*4; fc = 24000/(7159090/2); M=sinc(2*pi*fc*[-223:1:223]).*(2*fc).*window('kr',447,8.6); plot(M); M2=matrix(cat(2,[0],M),[56 8]);
// mprintf("{ %10.7ff, %10.7ff, %10.7ff, %10.7ff, %10.7ff, %10.7ff, %10.7ff, %10.7ff, },\n", M2)
// [hm, fr] = frmag(M, 1024); plot(fr, hm)
{ 0.0000000f, 0.0000885f, -0.0009574f, 0.0030938f, 0.0134095f, 0.0030938f, -0.0009574f, 0.0000885f, },
{ 0.0000001f, 0.0000887f, -0.0009856f, 0.0033114f, 0.0134045f, 0.0028807f, -0.0009282f, 0.0000879f, },
{ 0.0000002f, 0.0000886f, -0.0010127f, 0.0035331f, 0.0133893f, 0.0026721f, -0.0008981f, 0.0000870f, },
{ 0.0000003f, 0.0000880f, -0.0010385f, 0.0037588f, 0.0133641f, 0.0024683f, -0.0008673f, 0.0000858f, },
{ 0.0000005f, 0.0000870f, -0.0010629f, 0.0039882f, 0.0133288f, 0.0022694f, -0.0008359f, 0.0000844f, },
{ 0.0000007f, 0.0000856f, -0.0010857f, 0.0042211f, 0.0132836f, 0.0020756f, -0.0008041f, 0.0000827f, },
{ 0.0000009f, 0.0000836f, -0.0011068f, 0.0044572f, 0.0132284f, 0.0018870f, -0.0007718f, 0.0000808f, },
{ 0.0000012f, 0.0000811f, -0.0011260f, 0.0046964f, 0.0131635f, 0.0017037f, -0.0007393f, 0.0000787f, },
{ 0.0000016f, 0.0000780f, -0.0011431f, 0.0049382f, 0.0130888f, 0.0015259f, -0.0007067f, 0.0000764f, },
{ 0.0000020f, 0.0000743f, -0.0011581f, 0.0051825f, 0.0130046f, 0.0013537f, -0.0006740f, 0.0000740f, },
{ 0.0000024f, 0.0000700f, -0.0011707f, 0.0054289f, 0.0129110f, 0.0011872f, -0.0006414f, 0.0000715f, },
{ 0.0000030f, 0.0000651f, -0.0011808f, 0.0056771f, 0.0128081f, 0.0010264f, -0.0006090f, 0.0000689f, },
{ 0.0000036f, 0.0000594f, -0.0011882f, 0.0059269f, 0.0126961f, 0.0008714f, -0.0005767f, 0.0000662f, },
{ 0.0000042f, 0.0000531f, -0.0011927f, 0.0061779f, 0.0125751f, 0.0007223f, -0.0005448f, 0.0000635f, },
{ 0.0000049f, 0.0000460f, -0.0011942f, 0.0064298f, 0.0124455f, 0.0005791f, -0.0005133f, 0.0000607f, },
{ 0.0000058f, 0.0000381f, -0.0011926f, 0.0066822f, 0.0123074f, 0.0004419f, -0.0004823f, 0.0000579f, },
{ 0.0000066f, 0.0000294f, -0.0011876f, 0.0069349f, 0.0121610f, 0.0003106f, -0.0004518f, 0.0000551f, },
{ 0.0000076f, 0.0000199f, -0.0011791f, 0.0071875f, 0.0120066f, 0.0001852f, -0.0004218f, 0.0000523f, },
{ 0.0000087f, 0.0000095f, -0.0011669f, 0.0074396f, 0.0118444f, 0.0000658f, -0.0003926f, 0.0000495f, },
{ 0.0000098f, -0.0000017f, -0.0011508f, 0.0076910f, 0.0116746f, -0.0000476f, -0.0003640f, 0.0000467f, },
{ 0.0000111f, -0.0000138f, -0.0011308f, 0.0079411f, 0.0114977f, -0.0001552f, -0.0003362f, 0.0000440f, },
{ 0.0000124f, -0.0000269f, -0.0011067f, 0.0081898f, 0.0113138f, -0.0002568f, -0.0003092f, 0.0000413f, },
{ 0.0000139f, -0.0000408f, -0.0010782f, 0.0084366f, 0.0111233f, -0.0003527f, -0.0002830f, 0.0000387f, },
{ 0.0000154f, -0.0000558f, -0.0010453f, 0.0086812f, 0.0109264f, -0.0004427f, -0.0002577f, 0.0000361f, },
{ 0.0000170f, -0.0000716f, -0.0010078f, 0.0089232f, 0.0107235f, -0.0005271f, -0.0002332f, 0.0000336f, },
{ 0.0000188f, -0.0000884f, -0.0009656f, 0.0091622f, 0.0105149f, -0.0006058f, -0.0002097f, 0.0000312f, },
{ 0.0000206f, -0.0001062f, -0.0009185f, 0.0093980f, 0.0103009f, -0.0006791f, -0.0001871f, 0.0000289f, },
{ 0.0000225f, -0.0001250f, -0.0008664f, 0.0096301f, 0.0100819f, -0.0007468f, -0.0001654f, 0.0000267f, },
{ 0.0000246f, -0.0001447f, -0.0008092f, 0.0098581f, 0.0098581f, -0.0008092f, -0.0001447f, 0.0000246f, },
{ 0.0000267f, -0.0001654f, -0.0007468f, 0.0100819f, 0.0096301f, -0.0008664f, -0.0001250f, 0.0000225f, },
{ 0.0000289f, -0.0001871f, -0.0006791f, 0.0103009f, 0.0093980f, -0.0009185f, -0.0001062f, 0.0000206f, },
{ 0.0000312f, -0.0002097f, -0.0006058f, 0.0105149f, 0.0091622f, -0.0009656f, -0.0000884f, 0.0000188f, },
{ 0.0000336f, -0.0002332f, -0.0005271f, 0.0107235f, 0.0089232f, -0.0010078f, -0.0000716f, 0.0000170f, },
{ 0.0000361f, -0.0002577f, -0.0004427f, 0.0109264f, 0.0086812f, -0.0010453f, -0.0000558f, 0.0000154f, },
{ 0.0000387f, -0.0002830f, -0.0003527f, 0.0111233f, 0.0084366f, -0.0010782f, -0.0000408f, 0.0000139f, },
{ 0.0000413f, -0.0003092f, -0.0002568f, 0.0113138f, 0.0081898f, -0.0011067f, -0.0000269f, 0.0000124f, },
{ 0.0000440f, -0.0003362f, -0.0001552f, 0.0114977f, 0.0079411f, -0.0011308f, -0.0000138f, 0.0000111f, },
{ 0.0000467f, -0.0003640f, -0.0000476f, 0.0116746f, 0.0076910f, -0.0011508f, -0.0000017f, 0.0000098f, },
{ 0.0000495f, -0.0003926f, 0.0000658f, 0.0118444f, 0.0074396f, -0.0011669f, 0.0000095f, 0.0000087f, },
{ 0.0000523f, -0.0004218f, 0.0001852f, 0.0120066f, 0.0071875f, -0.0011791f, 0.0000199f, 0.0000076f, },
{ 0.0000551f, -0.0004518f, 0.0003106f, 0.0121610f, 0.0069349f, -0.0011876f, 0.0000294f, 0.0000066f, },
{ 0.0000579f, -0.0004823f, 0.0004419f, 0.0123074f, 0.0066822f, -0.0011926f, 0.0000381f, 0.0000058f, },
{ 0.0000607f, -0.0005133f, 0.0005791f, 0.0124455f, 0.0064298f, -0.0011942f, 0.0000460f, 0.0000049f, },
{ 0.0000635f, -0.0005448f, 0.0007223f, 0.0125751f, 0.0061779f, -0.0011927f, 0.0000531f, 0.0000042f, },
{ 0.0000662f, -0.0005767f, 0.0008714f, 0.0126961f, 0.0059269f, -0.0011882f, 0.0000594f, 0.0000036f, },
{ 0.0000689f, -0.0006090f, 0.0010264f, 0.0128081f, 0.0056771f, -0.0011808f, 0.0000651f, 0.0000030f, },
{ 0.0000715f, -0.0006414f, 0.0011872f, 0.0129110f, 0.0054289f, -0.0011707f, 0.0000700f, 0.0000024f, },
{ 0.0000740f, -0.0006740f, 0.0013537f, 0.0130046f, 0.0051825f, -0.0011581f, 0.0000743f, 0.0000020f, },
{ 0.0000764f, -0.0007067f, 0.0015259f, 0.0130888f, 0.0049382f, -0.0011431f, 0.0000780f, 0.0000016f, },
{ 0.0000787f, -0.0007393f, 0.0017037f, 0.0131635f, 0.0046964f, -0.0011260f, 0.0000811f, 0.0000012f, },
{ 0.0000808f, -0.0007718f, 0.0018870f, 0.0132284f, 0.0044572f, -0.0011068f, 0.0000836f, 0.0000009f, },
{ 0.0000827f, -0.0008041f, 0.0020756f, 0.0132836f, 0.0042211f, -0.0010857f, 0.0000856f, 0.0000007f, },
{ 0.0000844f, -0.0008359f, 0.0022694f, 0.0133288f, 0.0039882f, -0.0010629f, 0.0000870f, 0.0000005f, },
{ 0.0000858f, -0.0008673f, 0.0024683f, 0.0133641f, 0.0037588f, -0.0010385f, 0.0000880f, 0.0000003f, },
{ 0.0000870f, -0.0008981f, 0.0026721f, 0.0133893f, 0.0035331f, -0.0010127f, 0.0000886f, 0.0000002f, },
{ 0.0000879f, -0.0009282f, 0.0028807f, 0.0134045f, 0.0033114f, -0.0009856f, 0.0000887f, 0.0000001f, },
};
static_assert(vdcountof(kRawFilter) == 56);
ATPokeyHiFilterTable table {};
for(int i=0; i<=28; ++i) {
float sum = 0;
for(int j=0; j<8; ++j) {
table.mFilter[i][j] = (float)kRawFilter[i][7-j];
sum += table.mFilter[i][j];
}
float scale = 1.0f / sum;
for(float& v : table.mFilter[i])
v *= scale;
}
return table;
}
constexpr ATPokeyHiFilterTable ATMakePokeyHiFilterTable2() {
constexpr ATPokeyHiFilterTable table = ATMakePokeyHiFilterTable();
return table;
}
extern const ATPokeyHiFilterTable g_ATPokeyHiFilterTable = ATMakePokeyHiFilterTable2();