From 3a94a7acde0357fd6a74d4992a65dcb43ed89fb8 Mon Sep 17 00:00:00 2001 From: Waldemar Pawlaszek Date: Thu, 22 Dec 2022 21:53:29 +0100 Subject: [PATCH 1/5] 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 0bd77ebc8..0b6bd1390 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 4d22a1968..4a6955878 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 6e17145f2..9560e1135 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 000000000..f9f110d28 --- /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 000000000..09a21c4a2 --- /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; +}; + +} From 4a7e76c4488c9ae4d426484508d2a42cc07533de Mon Sep 17 00:00:00 2001 From: Waldemar Pawlaszek Date: Thu, 22 Dec 2022 22:40:29 +0100 Subject: [PATCH 2/5] Renaming new POKEY core to AltASAP. Added core selection. --- CMakeLists.txt | 2 +- src/engine/dispatchContainer.cpp | 1 + src/engine/platform/pokey.cpp | 51 +++++++++++++++---- src/engine/platform/pokey.h | 8 ++- .../sound/pokey/{Pokey.cpp => AltASAP.cpp} | 11 ++-- .../sound/pokey/{Pokey.hpp => AltASAP.hpp} | 4 +- src/gui/gui.h | 2 + src/gui/settings.cpp | 15 +++++- 8 files changed, 71 insertions(+), 23 deletions(-) rename src/engine/platform/sound/pokey/{Pokey.cpp => AltASAP.cpp} (95%) rename src/engine/platform/sound/pokey/{Pokey.hpp => AltASAP.hpp} (75%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0b6bd1390..6e37c88b1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -425,7 +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/pokey/AltASAP.cpp src/engine/platform/sound/qsound.c diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 710a2de4f..161aa4e1c 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -341,6 +341,7 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do break; case DIV_SYSTEM_POKEY: dispatch=new DivPlatformPOKEY; + ((DivPlatformPOKEY*)dispatch)->setAltASAP(eng->getConfInt("pokeyCore",0)==1); break; case DIV_SYSTEM_QSOUND: dispatch=new DivPlatformQSound; diff --git a/src/engine/platform/pokey.cpp b/src/engine/platform/pokey.cpp index 4a6955878..8e1bef25a 100644 --- a/src/engine/platform/pokey.cpp +++ b/src/engine/platform/pokey.cpp @@ -65,18 +65,22 @@ const char** DivPlatformPOKEY::getRegisterSheet() { } void DivPlatformPOKEY::acquire(short* bufL, short* bufR, size_t start, size_t len) { + if (useAltASAP) + acquireASAP(bufL, start, len); + else + acquireMZ(bufL, start, len); +} + +void DivPlatformPOKEY::acquireMZ(short* buf, size_t start, size_t len) { 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; + mzpokeysnd_process_16(&pokey,&buf[h],1); if (++oscBufDelay>=14) { oscBufDelay=0; @@ -86,6 +90,23 @@ void DivPlatformPOKEY::acquire(short* bufL, short* bufR, size_t start, size_t le oscBuf[3]->data[oscBuf[3]->needle++]=pokey.outvol_3<<11; } } +} + +void DivPlatformPOKEY::acquireASAP(short* buf, size_t start, size_t len) { + while (!writes.empty()) { + QueuedWrite w=writes.front(); + altASAP->write(w.addr, w.val); + writes.pop(); + } + + for (size_t h=start; h=14) { + oscBufDelay=0; + buf[h]=altASAP->sampleAudio(oscBuf); + } else { + buf[h]=altASAP->sampleAudio(); + } + } } void DivPlatformPOKEY::tick(bool sysTick) { @@ -373,8 +394,10 @@ DivDispatchOscBuffer* DivPlatformPOKEY::getOscBuffer(int ch) { } unsigned char* DivPlatformPOKEY::getRegisterPool() { - return const_cast( mPokey2->getRegisterPool() ); - return regPool; + if (useAltASAP) + return const_cast(altASAP->getRegisterPool()); + else + return regPool; } int DivPlatformPOKEY::getRegisterPoolSize() { @@ -392,8 +415,10 @@ void DivPlatformPOKEY::reset() { addWrite(0xffffffff,0); } - ResetPokeyState(&pokey); - mPokey2->reset(); + if (useAltASAP) + altASAP->reset(); + else + ResetPokeyState(&pokey); audctl=0; audctlChanged=true; @@ -425,7 +450,8 @@ void DivPlatformPOKEY::setFlags(const DivConfig& flags) { oscBuf[i]->rate=rate/14; } - mPokey2 = std::make_unique( chipClock, chipClock ); + if (useAltASAP) + altASAP=std::make_unique(chipClock, chipClock); } @@ -447,7 +473,8 @@ int DivPlatformPOKEY::init(DivEngine* p, int channels, int sugRate, const DivCon oscBuf[i]=new DivDispatchOscBuffer; } - MZPOKEYSND_Init(&pokey); + if (!useAltASAP) + MZPOKEYSND_Init(&pokey); setFlags(flags); reset(); @@ -458,6 +485,10 @@ void DivPlatformPOKEY::quit() { for (int i=0; i<4; i++) { delete oscBuf[i]; } +} + +void DivPlatformPOKEY::setAltASAP(bool value){ + useAltASAP=value; } DivPlatformPOKEY::~DivPlatformPOKEY() { diff --git a/src/engine/platform/pokey.h b/src/engine/platform/pokey.h index 9560e1135..a326bb377 100644 --- a/src/engine/platform/pokey.h +++ b/src/engine/platform/pokey.h @@ -27,7 +27,7 @@ extern "C" { #include "sound/pokey/mzpokeysnd.h" } -#include "sound/pokey/Pokey.hpp" +#include "sound/pokey/AltASAP.hpp" class DivPlatformPOKEY: public DivDispatch { @@ -52,12 +52,15 @@ class DivPlatformPOKEY: public DivDispatch { bool audctlChanged; unsigned char oscBufDelay; PokeyState pokey; - std::unique_ptr mPokey2; + std::unique_ptr altASAP; + bool useAltASAP; unsigned char regPool[16]; friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); public: void acquire(short* bufL, short* bufR, size_t start, size_t len); + void acquireMZ(short* buf, size_t start, size_t len); + void acquireASAP(short* buf, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); DivMacroInt* getChanMacroInt(int ch); @@ -77,6 +80,7 @@ class DivPlatformPOKEY: public DivDispatch { const char** getRegisterSheet(); int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags); void quit(); + void setAltASAP(bool useAltASAP); ~DivPlatformPOKEY(); }; diff --git a/src/engine/platform/sound/pokey/Pokey.cpp b/src/engine/platform/sound/pokey/AltASAP.cpp similarity index 95% rename from src/engine/platform/sound/pokey/Pokey.cpp rename to src/engine/platform/sound/pokey/AltASAP.cpp index f9f110d28..37174513d 100644 --- a/src/engine/platform/sound/pokey/Pokey.cpp +++ b/src/engine/platform/sound/pokey/AltASAP.cpp @@ -20,14 +20,14 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "Pokey.hpp" +#include "AltASAP.hpp" #include #include #include #include #include -namespace Test +namespace AltASAP { namespace @@ -631,12 +631,9 @@ void Pokey::write( uint8_t address, uint8_t value ) mPokey->write( address, value ); } -void Pokey::sampleAudio( int16_t* buf, size_t size, DivDispatchOscBuffer** oscb ) +int16_t Pokey::sampleAudio( DivDispatchOscBuffer** oscb ) { - for ( size_t i = 0; i < size; ++i ) - { - buf[i] = mPokey->sampleAudio( oscb ); - } + return mPokey->sampleAudio( oscb ) * 160; //just some magick value to match the audio level of mzpokeysnd } uint8_t const* Pokey::getRegisterPool() diff --git a/src/engine/platform/sound/pokey/Pokey.hpp b/src/engine/platform/sound/pokey/AltASAP.hpp similarity index 75% rename from src/engine/platform/sound/pokey/Pokey.hpp rename to src/engine/platform/sound/pokey/AltASAP.hpp index 09a21c4a2..930bf6aa1 100644 --- a/src/engine/platform/sound/pokey/Pokey.hpp +++ b/src/engine/platform/sound/pokey/AltASAP.hpp @@ -6,7 +6,7 @@ // can you forgive me #include "../../../dispatch.h" -namespace Test +namespace AltASAP { class PokeyPimpl; @@ -19,7 +19,7 @@ public: ~Pokey(); void write( uint8_t address, uint8_t value ); - void sampleAudio( int16_t* buf, size_t size, DivDispatchOscBuffer** oscb = NULL ); + int16_t sampleAudio( DivDispatchOscBuffer** oscb = nullptr ); uint8_t const* getRegisterPool(); diff --git a/src/gui/gui.h b/src/gui/gui.h index 805ce67ca..bc12fe2c6 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1177,6 +1177,7 @@ class FurnaceGUI { int nesCore; int fdsCore; int c64Core; + int pokeyCore; int pcSpeakerOutMethod; String yrw801Path; String tg100Path; @@ -1309,6 +1310,7 @@ class FurnaceGUI { nesCore(0), fdsCore(0), c64Core(1), + pokeyCore(0), pcSpeakerOutMethod(0), yrw801Path(""), tg100Path(""), diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index d40e4dac7..95f83d94c 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -102,6 +102,11 @@ const char* c64Cores[]={ "reSIDfp" }; +const char* pokeyCores[]={ + "mzpokeysnd", + "altASAP" +}; + const char* pcspkrOutMethods[]={ "evdev SND_TONE", "KIOCSOUND on /dev/tty1", @@ -1069,6 +1074,10 @@ void FurnaceGUI::drawSettings() { ImGui::SameLine(); ImGui::Combo("##C64Core",&settings.c64Core,c64Cores,2); + ImGui::Text("POKEY core"); + ImGui::SameLine(); + ImGui::Combo("##POKEYCore",&settings.pokeyCore,pokeyCores,2); + ImGui::Separator(); ImGui::Text("PC Speaker strategy"); @@ -2336,6 +2345,7 @@ void FurnaceGUI::syncSettings() { settings.nesCore=e->getConfInt("nesCore",0); settings.fdsCore=e->getConfInt("fdsCore",0); settings.c64Core=e->getConfInt("c64Core",1); + settings.pokeyCore=e->getConfInt("pokeyCore",0); settings.pcSpeakerOutMethod=e->getConfInt("pcSpeakerOutMethod",0); settings.yrw801Path=e->getConfString("yrw801Path",""); settings.tg100Path=e->getConfString("tg100Path",""); @@ -2461,6 +2471,7 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.nesCore,0,1); clampSetting(settings.fdsCore,0,1); clampSetting(settings.c64Core,0,1); + clampSetting(settings.pokeyCore,0,1); clampSetting(settings.pcSpeakerOutMethod,0,4); clampSetting(settings.mainFont,0,6); clampSetting(settings.patFont,0,6); @@ -2604,7 +2615,8 @@ void FurnaceGUI::commitSettings() { settings.snCore!=e->getConfInt("snCore",0) || settings.nesCore!=e->getConfInt("nesCore",0) || settings.fdsCore!=e->getConfInt("fdsCore",0) || - settings.c64Core!=e->getConfInt("c64Core",1) + settings.c64Core!=e->getConfInt("c64Core",1) || + settings.pokeyCore!=e->getConfInt("pokeyCore",0) ); e->setConf("mainFontSize",settings.mainFontSize); @@ -2624,6 +2636,7 @@ void FurnaceGUI::commitSettings() { e->setConf("nesCore",settings.nesCore); e->setConf("fdsCore",settings.fdsCore); e->setConf("c64Core",settings.c64Core); + e->setConf("pokeyCore",settings.pokeyCore); e->setConf("pcSpeakerOutMethod",settings.pcSpeakerOutMethod); e->setConf("yrw801Path",settings.yrw801Path); e->setConf("tg100Path",settings.tg100Path); From 1b2eb9cacb0fd251a7d46147b71ff6cf9f334f74 Mon Sep 17 00:00:00 2001 From: Waldemar Pawlaszek Date: Thu, 22 Dec 2022 23:04:03 +0100 Subject: [PATCH 3/5] bugfixes --- src/engine/platform/sound/pokey/AltASAP.cpp | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/engine/platform/sound/pokey/AltASAP.cpp b/src/engine/platform/sound/pokey/AltASAP.cpp index 37174513d..befc5b7da 100644 --- a/src/engine/platform/sound/pokey/AltASAP.cpp +++ b/src/engine/platform/sound/pokey/AltASAP.cpp @@ -39,11 +39,6 @@ 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; @@ -131,7 +126,7 @@ private: 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 } + AudioChannel( ActionQueue& queue, uint32_t number ) : mQueue{ queue }, mNumber{ number }, mAudf{ 0 }, mAudc{ 0 }, mPeriodCycles{ 28 }, mMute{ MuteFrequency }, mDelta{ 0 }, mOut{ 0 } { } @@ -178,9 +173,9 @@ public: mOut ^= 1; else { - int newOut; + uint32_t newOut; if ( ( mAudc & 0x40 ) != 0 ) - newOut = 0x5370 >> (int)( poly % 15 ); // 000011101100101 + newOut = 0x5370u >> (int)( poly % 15 ); // 000011101100101 else if ( pokey.mAudctl < 0x80 ) { poly %= 131071; @@ -278,7 +273,6 @@ private: uint32_t mMute; int32_t mDelta; uint32_t mOut; - bool mInit; }; } From 44a26791c603d7e7eff6059f6bc8d5bd61fab417 Mon Sep 17 00:00:00 2001 From: Waldemar Pawlaszek Date: Thu, 22 Dec 2022 23:17:38 +0100 Subject: [PATCH 4/5] Applied code review remarks. --- src/engine/platform/pokey.cpp | 27 +++++++++++++-------- src/engine/platform/sound/pokey/AltASAP.cpp | 2 +- src/gui/settings.cpp | 4 +-- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/engine/platform/pokey.cpp b/src/engine/platform/pokey.cpp index 8e1bef25a..32c677edd 100644 --- a/src/engine/platform/pokey.cpp +++ b/src/engine/platform/pokey.cpp @@ -65,10 +65,12 @@ const char** DivPlatformPOKEY::getRegisterSheet() { } void DivPlatformPOKEY::acquire(short* bufL, short* bufR, size_t start, size_t len) { - if (useAltASAP) + if (useAltASAP) { acquireASAP(bufL, start, len); - else + } + else { acquireMZ(bufL, start, len); + } } void DivPlatformPOKEY::acquireMZ(short* buf, size_t start, size_t len) { @@ -394,10 +396,12 @@ DivDispatchOscBuffer* DivPlatformPOKEY::getOscBuffer(int ch) { } unsigned char* DivPlatformPOKEY::getRegisterPool() { - if (useAltASAP) + if (useAltASAP) { return const_cast(altASAP->getRegisterPool()); - else + } + else { return regPool; + } } int DivPlatformPOKEY::getRegisterPoolSize() { @@ -415,10 +419,12 @@ void DivPlatformPOKEY::reset() { addWrite(0xffffffff,0); } - if (useAltASAP) + if (useAltASAP) { altASAP->reset(); - else + } + else { ResetPokeyState(&pokey); + } audctl=0; audctlChanged=true; @@ -450,9 +456,9 @@ void DivPlatformPOKEY::setFlags(const DivConfig& flags) { oscBuf[i]->rate=rate/14; } - if (useAltASAP) + if (useAltASAP) { altASAP=std::make_unique(chipClock, chipClock); - + } } void DivPlatformPOKEY::poke(unsigned int addr, unsigned short val) { @@ -473,8 +479,9 @@ int DivPlatformPOKEY::init(DivEngine* p, int channels, int sugRate, const DivCon oscBuf[i]=new DivDispatchOscBuffer; } - if (!useAltASAP) + if (!useAltASAP) { MZPOKEYSND_Init(&pokey); + } setFlags(flags); reset(); @@ -487,7 +494,7 @@ void DivPlatformPOKEY::quit() { } } -void DivPlatformPOKEY::setAltASAP(bool value){ +void DivPlatformPOKEY::setAltASAP(bool value) { useAltASAP=value; } diff --git a/src/engine/platform/sound/pokey/AltASAP.cpp b/src/engine/platform/sound/pokey/AltASAP.cpp index befc5b7da..ec5bbf742 100644 --- a/src/engine/platform/sound/pokey/AltASAP.cpp +++ b/src/engine/platform/sound/pokey/AltASAP.cpp @@ -612,7 +612,7 @@ private: }; -Pokey::Pokey( uint32_t pokeyClock, uint32_t sampleRate ) : mPokey{}, mPokeyClock{ pokeyClock }, mSampleRate{ sampleRate } +Pokey::Pokey( uint32_t pokeyClock, uint32_t sampleRate ) : mPokey{ std::make_unique( mPokeyClock, mSampleRate ) }, mPokeyClock{ pokeyClock }, mSampleRate{ sampleRate } { } diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 95f83d94c..9f7de2aef 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -103,8 +103,8 @@ const char* c64Cores[]={ }; const char* pokeyCores[]={ - "mzpokeysnd", - "altASAP" + "Atari800 (mzpokeysnd)", + "ASAP (C++ port)" }; const char* pcspkrOutMethods[]={ From d66042b9c72421dd340edfd6c19b1560192b70af Mon Sep 17 00:00:00 2001 From: Waldemar Pawlaszek Date: Thu, 22 Dec 2022 23:25:14 +0100 Subject: [PATCH 5/5] macOS compilation fix --- src/engine/platform/sound/pokey/AltASAP.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/engine/platform/sound/pokey/AltASAP.cpp b/src/engine/platform/sound/pokey/AltASAP.cpp index ec5bbf742..65ad339a2 100644 --- a/src/engine/platform/sound/pokey/AltASAP.cpp +++ b/src/engine/platform/sound/pokey/AltASAP.cpp @@ -36,7 +36,6 @@ 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; struct PokeyBase