diff --git a/CMakeLists.txt b/CMakeLists.txt index 0bd77ebc..6e37c88b 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/AltASAP.cpp src/engine/platform/sound/qsound.c diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 710a2de4..161aa4e1 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 4d22a196..32c677ed 100644 --- a/src/engine/platform/pokey.cpp +++ b/src/engine/platform/pokey.cpp @@ -65,6 +65,15 @@ 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; h=14) { oscBufDelay=0; @@ -83,6 +92,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) { @@ -370,7 +396,12 @@ DivDispatchOscBuffer* DivPlatformPOKEY::getOscBuffer(int ch) { } unsigned char* DivPlatformPOKEY::getRegisterPool() { - return regPool; + if (useAltASAP) { + return const_cast(altASAP->getRegisterPool()); + } + else { + return regPool; + } } int DivPlatformPOKEY::getRegisterPoolSize() { @@ -388,7 +419,12 @@ void DivPlatformPOKEY::reset() { addWrite(0xffffffff,0); } - ResetPokeyState(&pokey); + if (useAltASAP) { + altASAP->reset(); + } + else { + ResetPokeyState(&pokey); + } audctl=0; audctlChanged=true; @@ -419,6 +455,10 @@ void DivPlatformPOKEY::setFlags(const DivConfig& flags) { for (int i=0; i<4; i++) { oscBuf[i]->rate=rate/14; } + + if (useAltASAP) { + altASAP=std::make_unique(chipClock, chipClock); + } } void DivPlatformPOKEY::poke(unsigned int addr, unsigned short val) { @@ -439,7 +479,9 @@ 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(); @@ -450,6 +492,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 6e17145f..a326bb37 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/AltASAP.hpp" + + class DivPlatformPOKEY: public DivDispatch { struct Channel: public SharedChannel { unsigned char wave; @@ -49,11 +52,15 @@ class DivPlatformPOKEY: public DivDispatch { bool audctlChanged; unsigned char oscBufDelay; PokeyState pokey; + 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); @@ -73,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/AltASAP.cpp b/src/engine/platform/sound/pokey/AltASAP.cpp new file mode 100644 index 00000000..65ad339a --- /dev/null +++ b/src/engine/platform/sound/pokey/AltASAP.cpp @@ -0,0 +1,642 @@ +/** + * 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 "AltASAP.hpp" +#include +#include +#include +#include +#include + +namespace AltASAP +{ + +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 MuteSerialInput = 8; + +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 }, mDelta{ 0 }, mOut{ 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 + { + uint32_t newOut; + if ( ( mAudc & 0x40 ) != 0 ) + newOut = 0x5370u >> (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; +}; + +} + + +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{ std::make_unique( mPokeyClock, mSampleRate ) }, mPokeyClock{ pokeyClock }, mSampleRate{ sampleRate } +{ +} + +Pokey::~Pokey() +{ +} + +void Pokey::write( uint8_t address, uint8_t value ) +{ + mPokey->write( address, value ); +} + +int16_t Pokey::sampleAudio( DivDispatchOscBuffer** oscb ) +{ + return mPokey->sampleAudio( oscb ) * 160; //just some magick value to match the audio level of mzpokeysnd +} + +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/AltASAP.hpp b/src/engine/platform/sound/pokey/AltASAP.hpp new file mode 100644 index 00000000..930bf6aa --- /dev/null +++ b/src/engine/platform/sound/pokey/AltASAP.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include +#include + +// can you forgive me +#include "../../../dispatch.h" + +namespace AltASAP +{ + +class PokeyPimpl; + +class Pokey +{ +public: + + Pokey( uint32_t pokeyClock, uint32_t sampleRate ); + ~Pokey(); + + void write( uint8_t address, uint8_t value ); + int16_t sampleAudio( DivDispatchOscBuffer** oscb = nullptr ); + + uint8_t const* getRegisterPool(); + + void reset(); + +private: + + std::unique_ptr mPokey; + uint32_t mPokeyClock; + uint32_t mSampleRate; +}; + +} diff --git a/src/gui/gui.h b/src/gui/gui.h index 51ce9c08..2b7a2be4 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1178,6 +1178,7 @@ class FurnaceGUI { int nesCore; int fdsCore; int c64Core; + int pokeyCore; int pcSpeakerOutMethod; String yrw801Path; String tg100Path; @@ -1310,6 +1311,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 d40e4dac..9f7de2ae 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -102,6 +102,11 @@ const char* c64Cores[]={ "reSIDfp" }; +const char* pokeyCores[]={ + "Atari800 (mzpokeysnd)", + "ASAP (C++ port)" +}; + 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);