From 3a94a7acde0357fd6a74d4992a65dcb43ed89fb8 Mon Sep 17 00:00:00 2001 From: Waldemar Pawlaszek Date: Thu, 22 Dec 2022 21:53:29 +0100 Subject: [PATCH] Implementation of POKEY core based on ASAP (http://asap.sourceforge.net) --- CMakeLists.txt | 1 + src/engine/platform/pokey.cpp | 8 + src/engine/platform/pokey.h | 4 + src/engine/platform/sound/pokey/Pokey.cpp | 652 ++++++++++++++++++++++ src/engine/platform/sound/pokey/Pokey.hpp | 35 ++ 5 files changed, 700 insertions(+) create mode 100644 src/engine/platform/sound/pokey/Pokey.cpp create mode 100644 src/engine/platform/sound/pokey/Pokey.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 0bd77ebc..0b6bd139 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -425,6 +425,7 @@ src/engine/platform/sound/ymfm/ymfm_ssg.cpp src/engine/platform/sound/lynx/Mikey.cpp src/engine/platform/sound/pokey/mzpokeysnd.c +src/engine/platform/sound/pokey/Pokey.cpp src/engine/platform/sound/qsound.c diff --git a/src/engine/platform/pokey.cpp b/src/engine/platform/pokey.cpp index 4d22a196..4a695587 100644 --- a/src/engine/platform/pokey.cpp +++ b/src/engine/platform/pokey.cpp @@ -68,12 +68,15 @@ void DivPlatformPOKEY::acquire(short* bufL, short* bufR, size_t start, size_t le for (size_t h=start; hwrite( w.addr, w.val ); Update_pokey_sound_mz(&pokey,w.addr,w.val,0); regPool[w.addr&0x0f]=w.val; writes.pop(); } mzpokeysnd_process_16(&pokey,&bufL[h],1); + mPokey2->sampleAudio( &bufL[h], 1 ); + bufL[h] *= 160; if (++oscBufDelay>=14) { oscBufDelay=0; @@ -370,6 +373,7 @@ DivDispatchOscBuffer* DivPlatformPOKEY::getOscBuffer(int ch) { } unsigned char* DivPlatformPOKEY::getRegisterPool() { + return const_cast( mPokey2->getRegisterPool() ); return regPool; } @@ -389,6 +393,7 @@ void DivPlatformPOKEY::reset() { } ResetPokeyState(&pokey); + mPokey2->reset(); audctl=0; audctlChanged=true; @@ -419,6 +424,9 @@ void DivPlatformPOKEY::setFlags(const DivConfig& flags) { for (int i=0; i<4; i++) { oscBuf[i]->rate=rate/14; } + + mPokey2 = std::make_unique( chipClock, chipClock ); + } void DivPlatformPOKEY::poke(unsigned int addr, unsigned short val) { diff --git a/src/engine/platform/pokey.h b/src/engine/platform/pokey.h index 6e17145f..9560e113 100644 --- a/src/engine/platform/pokey.h +++ b/src/engine/platform/pokey.h @@ -27,6 +27,9 @@ extern "C" { #include "sound/pokey/mzpokeysnd.h" } +#include "sound/pokey/Pokey.hpp" + + class DivPlatformPOKEY: public DivDispatch { struct Channel: public SharedChannel { unsigned char wave; @@ -49,6 +52,7 @@ class DivPlatformPOKEY: public DivDispatch { bool audctlChanged; unsigned char oscBufDelay; PokeyState pokey; + std::unique_ptr mPokey2; unsigned char regPool[16]; friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); diff --git a/src/engine/platform/sound/pokey/Pokey.cpp b/src/engine/platform/sound/pokey/Pokey.cpp new file mode 100644 index 00000000..f9f110d2 --- /dev/null +++ b/src/engine/platform/sound/pokey/Pokey.cpp @@ -0,0 +1,652 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * Original author: Piotr Fusik (http://asap.sourceforge.net) + * Rewritten based on Mikey emulation by Waldemar Pawlaszek + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "Pokey.hpp" +#include +#include +#include +#include +#include + +namespace Test +{ + +namespace +{ + +static constexpr int64_t CNT_MAX = std::numeric_limits::max() & ~7; +static constexpr int MuteFrequency = 1; +static constexpr int MuteInit = 2; +static constexpr int MuteUser = 4; +static constexpr int MuteSerialInput = 8; + +int32_t clamp( int32_t v, int32_t lo, int32_t hi ) +{ + return v < lo ? lo : ( v > hi ? hi : v ); +} + +struct PokeyBase +{ + int64_t mPolyIndex; + int mAudctl; + int mSkctl; + bool mInit; + std::array mPoly9Lookup; + std::array mPoly17Lookup; + + + PokeyBase() : mPolyIndex{ 15 * 31 * 131071 }, mAudctl{ 0 }, mSkctl{ 3 }, mInit{ false }, mPoly9Lookup{}, mPoly17Lookup{} + { + int reg = 0x1ff; + for ( int i = 0; i < 511; i++ ) + { + reg = ( ( ( reg >> 5 ^ reg ) & 1 ) << 8 ) + ( reg >> 1 ); + mPoly9Lookup[i] = reg & 0xff; + } + reg = 0x1ffff; + for ( int i = 0; i < 16385; i++ ) + { + reg = ( ( ( reg >> 5 ^ reg ) & 0xff ) << 9 ) + ( reg >> 8 ); + mPoly17Lookup[i] = reg >> 1 & 0xff; + } + + } +}; + + +/* + "Queue" holding event timepoints. + - 4 channel timer fire points + - 1 sample point + Three LSBs are used to encode event kind: 0-3 are channels, 4 is sampling. + Channel sequence is 2,3,0,1 +*/ +class ActionQueue +{ +public: + + ActionQueue() : mTab{ CNT_MAX | 0, CNT_MAX | 1, CNT_MAX | 2, CNT_MAX | 3, CNT_MAX | 4 } + { + } + + void insert( int idx, int64_t value ) + { + assert( idx < (int)mTab.size() ); + idx ^= 2; + mTab[idx] = value | idx; + } + + void insertSampling( int64_t value ) + { + mTab[4] = value | 4; + } + + void disable( int idx ) + { + assert( idx < (int)mTab.size() ); + idx ^= 2; + mTab[idx] = CNT_MAX | idx; + } + + bool enqueued( int idx ) + { + assert( idx < (int)mTab.size() ); + idx ^= 2; + return mTab[idx] < CNT_MAX; + } + + int64_t pop() + { + int64_t min1 = std::min( mTab[0], mTab[1] ); + int64_t min2 = std::min( mTab[2], mTab[3] ); + int64_t min3 = std::min( min1, mTab[4] ); + int64_t min4 = std::min( min2, min3 ); + + return min4 ^ 2; + } + +private: + std::array mTab; +}; + +class AudioChannel +{ +public: + AudioChannel( ActionQueue& queue, uint32_t number ) : mQueue{ queue }, mNumber{ number }, mAudf{ 0 }, mAudc{ 0 }, mPeriodCycles{ 28 }, mMute{ MuteFrequency }, mOut{ 0 }, mDelta{ 0 } + { + } + + int16_t getOutput() const + { + return (int16_t)mDelta; + } + + void fillRegisterPool( uint8_t* regs ) + { + regs[0] = (uint8_t)mAudf; + regs[1] = (uint8_t)mAudc; + } + + void renewTrigger( int64_t cycle ) + { + overrideTrigger( cycle + mPeriodCycles ); + } + + void overrideTrigger( int64_t cycle ) + { + mQueue.insert( mNumber, cycle << 3 ); + } + + void doStimer( int64_t cycle ) + { + if ( mQueue.enqueued( mNumber ) ) + renewTrigger( cycle ); + } + + void trigger( int64_t cycle, PokeyBase const& pokey ) + { + renewTrigger( cycle ); + if ( ( mAudc & 0xb0 ) == 0xa0 ) + mOut ^= 1; + else if ( ( mAudc & 0x10 ) != 0 || pokey.mInit ) + return; + else + { + int64_t poly = cycle + pokey.mPolyIndex - mNumber; + if ( mAudc < 0x80 && ( 0x65bd44e0 & 1 << ( poly % 31 ) ) == 0 ) // 0000011100100010101111011010011 + return; + if ( ( mAudc & 0x20 ) != 0 ) + mOut ^= 1; + else + { + int newOut; + if ( ( mAudc & 0x40 ) != 0 ) + newOut = 0x5370 >> (int)( poly % 15 ); // 000011101100101 + else if ( pokey.mAudctl < 0x80 ) + { + poly %= 131071; + newOut = pokey.mPoly17Lookup[poly >> 3] >> ( poly & 7 ); + } + else + newOut = pokey.mPoly9Lookup[poly % 511]; + newOut &= 1; + if ( mOut == newOut ) + return; + mOut = newOut; + } + } + toggle(); + } + + uint32_t mute() const + { + return mMute; + } + + uint32_t audf() const + { + return mAudf; + } + + uint32_t audc() const + { + return mAudc; + } + + void setAudf( uint32_t value ) + { + mAudf = value; + } + + void setAudc( int64_t cycle, uint32_t value ) + { + if ( mAudc == value ) + return; + mAudc = value; + int32_t volume = value & 0x0f; + if ( ( value & 0x10 ) != 0 ) + { + mDelta = volume; + } + else + { + muteUltrasound( cycle ); + if ( mDelta > 0 ) + mDelta = volume; + else + mDelta = -volume; + } + } + + void toggle() + { + mDelta = -mDelta; + } + + void setPeriodCycles( int64_t value ) + { + mPeriodCycles = value; + } + + void setMute( bool enable, int mask, int64_t cycle ) + { + if ( enable ) + { + mMute |= mask; + mQueue.disable( mNumber ); + } + else + { + mMute &= ~mask; + if ( mMute == 0 && !mQueue.enqueued( mNumber ) ) + overrideTrigger( cycle ); + } + } + + void muteUltrasound( int64_t cycle ) + { + static constexpr int UltrasoundCycles = 112; + setMute( mPeriodCycles <= UltrasoundCycles && ( mAudc & 0xb0 ) == 0xa0, MuteFrequency, cycle ); + } + +private: + ActionQueue& mQueue; + uint32_t mNumber; + uint32_t mAudf; + uint32_t mAudc; + int64_t mPeriodCycles; + + uint32_t mMute; + int32_t mDelta; + uint32_t mOut; + bool mInit; +}; + +} + + +class PokeyPimpl : public PokeyBase +{ +public: + + PokeyPimpl( uint32_t pokeyClock, uint32_t sampleRate ) : PokeyBase{}, mQueue{ std::make_unique() }, + mAudioChannels{ AudioChannel{ *mQueue, 0u }, AudioChannel{ *mQueue, 1u }, AudioChannel{ *mQueue, 2u }, AudioChannel{ *mQueue, 3u } }, + mRegisterPool{}, mPokeyClock{ pokeyClock * 8 }, mTick{}, mNextTick{}, mSampleRate{ sampleRate }, mSamplesRemainder{}, + mReloadCycles1{ 28 }, mReloadCycles3{ 28 }, mDivCycles{ 28 }, + mTicksPerSample{ mPokeyClock / mSampleRate, mPokeyClock % mSampleRate } + { + std::fill_n( mRegisterPool.data(), mRegisterPool.size(), (uint8_t)0xff ); + enqueueSampling(); + } + + ~PokeyPimpl() {} + + void write( uint8_t address, uint8_t value ) + { + auto cycle = mTick >> 3; + + switch ( address & 0xf ) + { + case 0x00: + if ( value == mAudioChannels[0].audf() ) + break; + mAudioChannels[0].setAudf( value ); + switch ( mAudctl & 0x50 ) + { + case 0x00: + mAudioChannels[0].setPeriodCycles( mDivCycles * ( value + 1 ) ); + break; + case 0x10: + mAudioChannels[1].setPeriodCycles( mDivCycles * ( value + ( mAudioChannels[1].audf() << 8 ) + 1 ) ); + mReloadCycles1 = mDivCycles * ( value + 1 ); + mAudioChannels[1].muteUltrasound( cycle ); + break; + case 0x40: + mAudioChannels[0].setPeriodCycles( value + 4 ); + break; + case 0x50: + mAudioChannels[1].setPeriodCycles( value + ( mAudioChannels[1].audf() << 8 ) + 7 ); + mReloadCycles1 = value + 4; + mAudioChannels[1].muteUltrasound( cycle ); + break; + default: + assert( false ); + } + mAudioChannels[0].muteUltrasound( cycle ); + break; + case 0x01: + mAudioChannels[0].setAudc( cycle, value ); + break; + case 0x02: + if ( value == mAudioChannels[1].audf() ) + break; + mAudioChannels[1].setAudf( value ); + switch ( mAudctl & 0x50 ) + { + case 0x00: + case 0x40: + mAudioChannels[1].setPeriodCycles( mDivCycles * ( value + 1 ) ); + break; + case 0x10: + mAudioChannels[1].setPeriodCycles( mDivCycles * ( mAudioChannels[0].audf() + ( value << 8 ) + 1 ) ); + break; + case 0x50: + mAudioChannels[1].setPeriodCycles( mAudioChannels[0].audf() + ( value << 8 ) + 7 ); + break; + default: + assert( false ); + } + mAudioChannels[1].muteUltrasound( cycle ); + break; + case 0x03: + mAudioChannels[1].setAudc( cycle, value ); + break; + case 0x04: + if ( value == mAudioChannels[2].audf() ) + break; + mAudioChannels[2].setAudf( value ); + switch ( mAudctl & 0x28 ) + { + case 0x00: + mAudioChannels[2].setPeriodCycles( mDivCycles * ( value + 1 ) ); + break; + case 0x08: + mAudioChannels[3].setPeriodCycles( mDivCycles * ( value + ( mAudioChannels[3].audf() << 8 ) + 1 ) ); + mReloadCycles3 = mDivCycles * ( value + 1 ); + mAudioChannels[3].muteUltrasound( cycle ); + break; + case 0x20: + mAudioChannels[2].setPeriodCycles( value + 4 ); + break; + case 0x28: + mAudioChannels[3].setPeriodCycles( value + ( mAudioChannels[3].audf() << 8 ) + 7 ); + mReloadCycles3 = value + 4; + mAudioChannels[3].muteUltrasound( cycle ); + break; + default: + assert( false ); + } + mAudioChannels[2].muteUltrasound( cycle ); + break; + case 0x05: + mAudioChannels[2].setAudc( cycle, value ); + break; + case 0x06: + if ( value == mAudioChannels[3].audf() ) + break; + mAudioChannels[3].setAudf( value ); + switch ( mAudctl & 0x28 ) + { + case 0x00: + case 0x20: + mAudioChannels[3].setPeriodCycles( mDivCycles * ( value + 1 ) ); + break; + case 0x08: + mAudioChannels[3].setPeriodCycles( mDivCycles * ( mAudioChannels[2].audf() + ( value << 8 ) + 1 ) ); + break; + case 0x28: + mAudioChannels[3].setPeriodCycles( mAudioChannels[2].audf() + ( value << 8 ) + 7 ); + break; + default: + assert( false ); + } + mAudioChannels[3].muteUltrasound( cycle ); + break; + case 0x07: + mAudioChannels[3].setAudc( cycle, value ); + break; + case 0x08: + if ( value == mAudctl ) + break; + mAudctl = value; + mDivCycles = ( value & 1 ) != 0 ? 114 : 28; + switch ( value & 0x50 ) + { + case 0x00: + mAudioChannels[0].setPeriodCycles( mDivCycles * ( mAudioChannels[0].audf() + 1 ) ); + mAudioChannels[1].setPeriodCycles( mDivCycles * ( mAudioChannels[1].audf() + 1 ) ); + break; + case 0x10: + mAudioChannels[0].setPeriodCycles( (int64_t)mDivCycles << 8 ); + mAudioChannels[1].setPeriodCycles( mDivCycles * ( mAudioChannels[0].audf() + ( mAudioChannels[1].audf() << 8 ) + 1 ) ); + mReloadCycles1 = mDivCycles * ( mAudioChannels[0].audf() + 1 ); + break; + case 0x40: + mAudioChannels[0].setPeriodCycles( mAudioChannels[0].audf() + 4 ); + mAudioChannels[1].setPeriodCycles( mDivCycles * ( mAudioChannels[1].audf() + 1 ) ); + break; + case 0x50: + mAudioChannels[0].setPeriodCycles( 256 ); + mAudioChannels[1].setPeriodCycles( mAudioChannels[0].audf() + ( mAudioChannels[1].audf() << 8 ) + 7 ); + mReloadCycles1 = mAudioChannels[0].audf() + 4; + break; + default: + assert( false ); + } + mAudioChannels[0].muteUltrasound( cycle ); + mAudioChannels[1].muteUltrasound( cycle ); + switch ( value & 0x28 ) + { + case 0x00: + mAudioChannels[2].setPeriodCycles( mDivCycles * ( mAudioChannels[2].audf() + 1 ) ); + mAudioChannels[3].setPeriodCycles( mDivCycles * ( mAudioChannels[3].audf() + 1 ) ); + break; + case 0x08: + mAudioChannels[2].setPeriodCycles( (int64_t)mDivCycles << 8 ); + mAudioChannels[3].setPeriodCycles( mDivCycles * ( mAudioChannels[2].audf() + ( mAudioChannels[3].audf() << 8 ) + 1 ) ); + mReloadCycles3 = mDivCycles * ( mAudioChannels[2].audf() + 1 ); + break; + case 0x20: + mAudioChannels[2].setPeriodCycles( mAudioChannels[2].audf() + 4 ); + mAudioChannels[3].setPeriodCycles( mDivCycles * ( mAudioChannels[3].audf() + 1 ) ); + break; + case 0x28: + mAudioChannels[2].setPeriodCycles( 256 ); + mAudioChannels[3].setPeriodCycles( mAudioChannels[2].audf() + ( mAudioChannels[3].audf() << 8 ) + 7 ); + mReloadCycles3 = mAudioChannels[2].audf() + 4; + break; + default: + assert( false ); + } + mAudioChannels[2].muteUltrasound( cycle ); + mAudioChannels[3].muteUltrasound( cycle ); + initMute( cycle ); + break; + case 0x09: + for ( int i = 0; i < 4; i++ ) + mAudioChannels[i].doStimer( cycle ); + break; + case 0x0f: + { + if ( value == mSkctl ) + break; + mSkctl = value; + bool init = ( value & 3 ) == 0; + if ( mInit && !init ) + mPolyIndex = ( ( mAudctl & 0x80 ) != 0 ? 15 * 31 * 511 - 1 : 15 * 31 * 131071 - 1 ) - cycle; + mInit = init; + initMute( cycle ); + mAudioChannels[2].setMute( ( value & 0x10 ) != 0, MuteSerialInput, cycle ); + mAudioChannels[3].setMute( ( value & 0x10 ) != 0, MuteSerialInput, cycle ); + break; + } + default: + break; + } + } + + int16_t sampleAudio( DivDispatchOscBuffer** oscb ) + { + for ( ;; ) + { + int64_t value = mQueue->pop(); + if ( ( value & 7 ) == 6 ) // 6 == 4 ^ 2 + { + int16_t ch0 = mAudioChannels[0].getOutput(); + int16_t ch1 = mAudioChannels[1].getOutput(); + int16_t ch2 = mAudioChannels[2].getOutput(); + int16_t ch3 = mAudioChannels[3].getOutput(); + + if ( oscb != nullptr ) + { + oscb[0]->data[oscb[0]->needle++]=ch0; + oscb[1]->data[oscb[1]->needle++]=ch1; + oscb[2]->data[oscb[2]->needle++]=ch2; + oscb[3]->data[oscb[3]->needle++]=ch3; + } + + enqueueSampling(); + return ch0 + ch1 + ch2 + ch3; + } + else + { + fireTimer( value ); + } + } + } + + uint8_t const* getRegisterPool() + { + for ( size_t i = 0; i < mAudioChannels.size(); ++i ) + { + mAudioChannels[i].fillRegisterPool( mRegisterPool.data() + 2 * i ); + } + + mRegisterPool[8] = mAudctl; + + return mRegisterPool.data(); + } + +private: + + void initMute( int64_t cycle ) + { + mAudioChannels[0].setMute( mInit && ( mAudctl & 0x40 ) == 0, MuteInit, cycle ); + mAudioChannels[1].setMute( mInit && ( mAudctl & 0x50 ) != 0x50, MuteInit, cycle ); + mAudioChannels[2].setMute( mInit && ( mAudctl & 0x20 ) == 0, MuteInit, cycle ); + mAudioChannels[3].setMute( mInit && ( mAudctl & 0x28 ) != 0x28, MuteInit, cycle ); + } + + void fireTimer( int64_t tick ) + { + mTick = tick & ~7; + size_t ch = tick & 3; + auto cycle = tick >> 3; + + switch ( ch ) + { + case 0: + if ( ( mSkctl & 0x88 ) == 8 ) // two-tone, sending 1 (i.e. timer1) + mAudioChannels[1].renewTrigger( cycle ); + mAudioChannels[0].trigger( cycle, *this ); + break; + case 1: + if ( ( mAudctl & 0x10 ) != 0 ) + mAudioChannels[0].overrideTrigger( cycle + mReloadCycles1 ); + else if ( ( mSkctl & 8 ) != 0 ) // two-tone + mAudioChannels[0].renewTrigger( cycle ); + mAudioChannels[1].trigger( cycle, *this ); + break; + case 2: + if ( ( mAudctl & 4 ) != 0 && mAudioChannels[0].getOutput() > 0 && mAudioChannels[0].mute() == 0 ) + mAudioChannels[0].toggle(); + mAudioChannels[2].trigger( cycle, *this ); + break; + case 3: + if ( ( mAudctl & 8 ) != 0 ) + mAudioChannels[2].overrideTrigger( cycle + mReloadCycles3 ); + if ( ( mAudctl & 2 ) != 0 && mAudioChannels[1].getOutput() > 0 && mAudioChannels[1].mute() == 0 ) + mAudioChannels[1].toggle(); + mAudioChannels[3].trigger( cycle, *this ); + break; + default: + break; + } + } + + void enqueueSampling() + { + mTick = mNextTick & ~7; + mNextTick = mNextTick + mTicksPerSample.first; + mSamplesRemainder += mTicksPerSample.second; + if ( mSamplesRemainder > mSampleRate ) + { + mSamplesRemainder %= mSampleRate; + mNextTick += 1; + } + + mQueue->insertSampling( mNextTick & ~7 ); + } + + +private: + + std::unique_ptr mQueue; + std::array mAudioChannels; + + std::array mRegisterPool; + + uint32_t mPokeyClock; + uint64_t mTick; + uint64_t mNextTick; + uint32_t mSampleRate; + uint32_t mSamplesRemainder; + int mReloadCycles1; + int mReloadCycles3; + int mDivCycles; + std::pair mTicksPerSample; + +}; + + +Pokey::Pokey( uint32_t pokeyClock, uint32_t sampleRate ) : mPokey{}, mPokeyClock{ pokeyClock }, mSampleRate{ sampleRate } +{ +} + +Pokey::~Pokey() +{ +} + +void Pokey::write( uint8_t address, uint8_t value ) +{ + mPokey->write( address, value ); +} + +void Pokey::sampleAudio( int16_t* buf, size_t size, DivDispatchOscBuffer** oscb ) +{ + for ( size_t i = 0; i < size; ++i ) + { + buf[i] = mPokey->sampleAudio( oscb ); + } +} + +uint8_t const* Pokey::getRegisterPool() +{ + return mPokey->getRegisterPool(); +} + +void Pokey::reset() +{ + mPokey = std::make_unique( mPokeyClock, mSampleRate ); +} + +} diff --git a/src/engine/platform/sound/pokey/Pokey.hpp b/src/engine/platform/sound/pokey/Pokey.hpp new file mode 100644 index 00000000..09a21c4a --- /dev/null +++ b/src/engine/platform/sound/pokey/Pokey.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include +#include + +// can you forgive me +#include "../../../dispatch.h" + +namespace Test +{ + +class PokeyPimpl; + +class Pokey +{ +public: + + Pokey( uint32_t pokeyClock, uint32_t sampleRate ); + ~Pokey(); + + void write( uint8_t address, uint8_t value ); + void sampleAudio( int16_t* buf, size_t size, DivDispatchOscBuffer** oscb = NULL ); + + uint8_t const* getRegisterPool(); + + void reset(); + +private: + + std::unique_ptr mPokey; + uint32_t mPokeyClock; + uint32_t mSampleRate; +}; + +}