Merge pull request #799 from laoo/ASAP_POKEY

Asap pokey
This commit is contained in:
tildearrow 2022-12-22 19:13:39 -05:00 committed by GitHub
commit f587d0dde6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 753 additions and 5 deletions

View file

@ -425,6 +425,7 @@ src/engine/platform/sound/ymfm/ymfm_ssg.cpp
src/engine/platform/sound/lynx/Mikey.cpp src/engine/platform/sound/lynx/Mikey.cpp
src/engine/platform/sound/pokey/mzpokeysnd.c src/engine/platform/sound/pokey/mzpokeysnd.c
src/engine/platform/sound/pokey/AltASAP.cpp
src/engine/platform/sound/qsound.c src/engine/platform/sound/qsound.c

View file

@ -341,6 +341,7 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
break; break;
case DIV_SYSTEM_POKEY: case DIV_SYSTEM_POKEY:
dispatch=new DivPlatformPOKEY; dispatch=new DivPlatformPOKEY;
((DivPlatformPOKEY*)dispatch)->setAltASAP(eng->getConfInt("pokeyCore",0)==1);
break; break;
case DIV_SYSTEM_QSOUND: case DIV_SYSTEM_QSOUND:
dispatch=new DivPlatformQSound; dispatch=new DivPlatformQSound;

View file

@ -65,6 +65,15 @@ const char** DivPlatformPOKEY::getRegisterSheet() {
} }
void DivPlatformPOKEY::acquire(short* bufL, short* bufR, size_t start, size_t len) { 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<start+len; h++) { for (size_t h=start; h<start+len; h++) {
while (!writes.empty()) { while (!writes.empty()) {
QueuedWrite w=writes.front(); QueuedWrite w=writes.front();
@ -73,7 +82,7 @@ void DivPlatformPOKEY::acquire(short* bufL, short* bufR, size_t start, size_t le
writes.pop(); writes.pop();
} }
mzpokeysnd_process_16(&pokey,&bufL[h],1); mzpokeysnd_process_16(&pokey,&buf[h],1);
if (++oscBufDelay>=14) { if (++oscBufDelay>=14) {
oscBufDelay=0; 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; 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<start+len; h++) {
if (++oscBufDelay>=14) {
oscBufDelay=0;
buf[h]=altASAP->sampleAudio(oscBuf);
} else {
buf[h]=altASAP->sampleAudio();
}
}
} }
void DivPlatformPOKEY::tick(bool sysTick) { void DivPlatformPOKEY::tick(bool sysTick) {
@ -370,7 +396,12 @@ DivDispatchOscBuffer* DivPlatformPOKEY::getOscBuffer(int ch) {
} }
unsigned char* DivPlatformPOKEY::getRegisterPool() { unsigned char* DivPlatformPOKEY::getRegisterPool() {
return regPool; if (useAltASAP) {
return const_cast<unsigned char*>(altASAP->getRegisterPool());
}
else {
return regPool;
}
} }
int DivPlatformPOKEY::getRegisterPoolSize() { int DivPlatformPOKEY::getRegisterPoolSize() {
@ -388,7 +419,12 @@ void DivPlatformPOKEY::reset() {
addWrite(0xffffffff,0); addWrite(0xffffffff,0);
} }
ResetPokeyState(&pokey); if (useAltASAP) {
altASAP->reset();
}
else {
ResetPokeyState(&pokey);
}
audctl=0; audctl=0;
audctlChanged=true; audctlChanged=true;
@ -419,6 +455,10 @@ void DivPlatformPOKEY::setFlags(const DivConfig& flags) {
for (int i=0; i<4; i++) { for (int i=0; i<4; i++) {
oscBuf[i]->rate=rate/14; oscBuf[i]->rate=rate/14;
} }
if (useAltASAP) {
altASAP=std::make_unique<AltASAP::Pokey>(chipClock, chipClock);
}
} }
void DivPlatformPOKEY::poke(unsigned int addr, unsigned short val) { 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; oscBuf[i]=new DivDispatchOscBuffer;
} }
MZPOKEYSND_Init(&pokey); if (!useAltASAP) {
MZPOKEYSND_Init(&pokey);
}
setFlags(flags); setFlags(flags);
reset(); reset();
@ -450,6 +492,10 @@ void DivPlatformPOKEY::quit() {
for (int i=0; i<4; i++) { for (int i=0; i<4; i++) {
delete oscBuf[i]; delete oscBuf[i];
} }
}
void DivPlatformPOKEY::setAltASAP(bool value) {
useAltASAP=value;
} }
DivPlatformPOKEY::~DivPlatformPOKEY() { DivPlatformPOKEY::~DivPlatformPOKEY() {

View file

@ -27,6 +27,9 @@ extern "C" {
#include "sound/pokey/mzpokeysnd.h" #include "sound/pokey/mzpokeysnd.h"
} }
#include "sound/pokey/AltASAP.hpp"
class DivPlatformPOKEY: public DivDispatch { class DivPlatformPOKEY: public DivDispatch {
struct Channel: public SharedChannel<int> { struct Channel: public SharedChannel<int> {
unsigned char wave; unsigned char wave;
@ -49,11 +52,15 @@ class DivPlatformPOKEY: public DivDispatch {
bool audctlChanged; bool audctlChanged;
unsigned char oscBufDelay; unsigned char oscBufDelay;
PokeyState pokey; PokeyState pokey;
std::unique_ptr<AltASAP::Pokey> altASAP;
bool useAltASAP;
unsigned char regPool[16]; unsigned char regPool[16];
friend void putDispatchChip(void*,int); friend void putDispatchChip(void*,int);
friend void putDispatchChan(void*,int,int); friend void putDispatchChan(void*,int,int);
public: public:
void acquire(short* bufL, short* bufR, size_t start, size_t len); 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); int dispatch(DivCommand c);
void* getChanState(int chan); void* getChanState(int chan);
DivMacroInt* getChanMacroInt(int ch); DivMacroInt* getChanMacroInt(int ch);
@ -73,6 +80,7 @@ class DivPlatformPOKEY: public DivDispatch {
const char** getRegisterSheet(); const char** getRegisterSheet();
int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags); int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags);
void quit(); void quit();
void setAltASAP(bool useAltASAP);
~DivPlatformPOKEY(); ~DivPlatformPOKEY();
}; };

View file

@ -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 <array>
#include <vector>
#include <cassert>
#include <algorithm>
#include <limits>
namespace AltASAP
{
namespace
{
static constexpr int64_t CNT_MAX = std::numeric_limits<int64_t>::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<uint8_t, 511> mPoly9Lookup;
std::array<uint8_t, 16385> 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<int64_t, 5> 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<ActionQueue>() },
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<ActionQueue> mQueue;
std::array<AudioChannel, 4> mAudioChannels;
std::array<uint8_t, 4 * 2 + 1> mRegisterPool;
uint32_t mPokeyClock;
uint64_t mTick;
uint64_t mNextTick;
uint32_t mSampleRate;
uint32_t mSamplesRemainder;
int mReloadCycles1;
int mReloadCycles3;
int mDivCycles;
std::pair<uint32_t, uint32_t> mTicksPerSample;
};
Pokey::Pokey( uint32_t pokeyClock, uint32_t sampleRate ) : mPokey{ std::make_unique<PokeyPimpl>( 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<PokeyPimpl>( mPokeyClock, mSampleRate );
}
}

View file

@ -0,0 +1,35 @@
#pragma once
#include <cstdint>
#include <memory>
// 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<PokeyPimpl> mPokey;
uint32_t mPokeyClock;
uint32_t mSampleRate;
};
}

View file

@ -1178,6 +1178,7 @@ class FurnaceGUI {
int nesCore; int nesCore;
int fdsCore; int fdsCore;
int c64Core; int c64Core;
int pokeyCore;
int pcSpeakerOutMethod; int pcSpeakerOutMethod;
String yrw801Path; String yrw801Path;
String tg100Path; String tg100Path;
@ -1310,6 +1311,7 @@ class FurnaceGUI {
nesCore(0), nesCore(0),
fdsCore(0), fdsCore(0),
c64Core(1), c64Core(1),
pokeyCore(0),
pcSpeakerOutMethod(0), pcSpeakerOutMethod(0),
yrw801Path(""), yrw801Path(""),
tg100Path(""), tg100Path(""),

View file

@ -102,6 +102,11 @@ const char* c64Cores[]={
"reSIDfp" "reSIDfp"
}; };
const char* pokeyCores[]={
"Atari800 (mzpokeysnd)",
"ASAP (C++ port)"
};
const char* pcspkrOutMethods[]={ const char* pcspkrOutMethods[]={
"evdev SND_TONE", "evdev SND_TONE",
"KIOCSOUND on /dev/tty1", "KIOCSOUND on /dev/tty1",
@ -1069,6 +1074,10 @@ void FurnaceGUI::drawSettings() {
ImGui::SameLine(); ImGui::SameLine();
ImGui::Combo("##C64Core",&settings.c64Core,c64Cores,2); ImGui::Combo("##C64Core",&settings.c64Core,c64Cores,2);
ImGui::Text("POKEY core");
ImGui::SameLine();
ImGui::Combo("##POKEYCore",&settings.pokeyCore,pokeyCores,2);
ImGui::Separator(); ImGui::Separator();
ImGui::Text("PC Speaker strategy"); ImGui::Text("PC Speaker strategy");
@ -2336,6 +2345,7 @@ void FurnaceGUI::syncSettings() {
settings.nesCore=e->getConfInt("nesCore",0); settings.nesCore=e->getConfInt("nesCore",0);
settings.fdsCore=e->getConfInt("fdsCore",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);
settings.pcSpeakerOutMethod=e->getConfInt("pcSpeakerOutMethod",0); settings.pcSpeakerOutMethod=e->getConfInt("pcSpeakerOutMethod",0);
settings.yrw801Path=e->getConfString("yrw801Path",""); settings.yrw801Path=e->getConfString("yrw801Path","");
settings.tg100Path=e->getConfString("tg100Path",""); settings.tg100Path=e->getConfString("tg100Path","");
@ -2461,6 +2471,7 @@ void FurnaceGUI::syncSettings() {
clampSetting(settings.nesCore,0,1); clampSetting(settings.nesCore,0,1);
clampSetting(settings.fdsCore,0,1); clampSetting(settings.fdsCore,0,1);
clampSetting(settings.c64Core,0,1); clampSetting(settings.c64Core,0,1);
clampSetting(settings.pokeyCore,0,1);
clampSetting(settings.pcSpeakerOutMethod,0,4); clampSetting(settings.pcSpeakerOutMethod,0,4);
clampSetting(settings.mainFont,0,6); clampSetting(settings.mainFont,0,6);
clampSetting(settings.patFont,0,6); clampSetting(settings.patFont,0,6);
@ -2604,7 +2615,8 @@ void FurnaceGUI::commitSettings() {
settings.snCore!=e->getConfInt("snCore",0) || settings.snCore!=e->getConfInt("snCore",0) ||
settings.nesCore!=e->getConfInt("nesCore",0) || settings.nesCore!=e->getConfInt("nesCore",0) ||
settings.fdsCore!=e->getConfInt("fdsCore",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); e->setConf("mainFontSize",settings.mainFontSize);
@ -2624,6 +2636,7 @@ void FurnaceGUI::commitSettings() {
e->setConf("nesCore",settings.nesCore); e->setConf("nesCore",settings.nesCore);
e->setConf("fdsCore",settings.fdsCore); e->setConf("fdsCore",settings.fdsCore);
e->setConf("c64Core",settings.c64Core); e->setConf("c64Core",settings.c64Core);
e->setConf("pokeyCore",settings.pokeyCore);
e->setConf("pcSpeakerOutMethod",settings.pcSpeakerOutMethod); e->setConf("pcSpeakerOutMethod",settings.pcSpeakerOutMethod);
e->setConf("yrw801Path",settings.yrw801Path); e->setConf("yrw801Path",settings.yrw801Path);
e->setConf("tg100Path",settings.tg100Path); e->setConf("tg100Path",settings.tg100Path);