i don't know whether I will be able to do this
This commit is contained in:
parent
16b752dc8a
commit
093a45bc3f
|
@ -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
|
|
@ -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
|
|
@ -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
|
@ -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();
|
Loading…
Reference in New Issue