mirror of
https://github.com/tildearrow/furnace.git
synced 2024-11-27 06:53:01 +00:00
commit
f587d0dde6
8 changed files with 753 additions and 5 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<start+len; h++) {
|
||||
while (!writes.empty()) {
|
||||
QueuedWrite w=writes.front();
|
||||
|
@ -73,7 +82,7 @@ void DivPlatformPOKEY::acquire(short* bufL, short* bufR, size_t start, size_t le
|
|||
writes.pop();
|
||||
}
|
||||
|
||||
mzpokeysnd_process_16(&pokey,&bufL[h],1);
|
||||
mzpokeysnd_process_16(&pokey,&buf[h],1);
|
||||
|
||||
if (++oscBufDelay>=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<start+len; h++) {
|
||||
if (++oscBufDelay>=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<unsigned char*>(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<AltASAP::Pokey>(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() {
|
||||
|
|
|
@ -27,6 +27,9 @@ extern "C" {
|
|||
#include "sound/pokey/mzpokeysnd.h"
|
||||
}
|
||||
|
||||
#include "sound/pokey/AltASAP.hpp"
|
||||
|
||||
|
||||
class DivPlatformPOKEY: public DivDispatch {
|
||||
struct Channel: public SharedChannel<int> {
|
||||
unsigned char wave;
|
||||
|
@ -49,11 +52,15 @@ class DivPlatformPOKEY: public DivDispatch {
|
|||
bool audctlChanged;
|
||||
unsigned char oscBufDelay;
|
||||
PokeyState pokey;
|
||||
std::unique_ptr<AltASAP::Pokey> 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();
|
||||
};
|
||||
|
||||
|
|
642
src/engine/platform/sound/pokey/AltASAP.cpp
Normal file
642
src/engine/platform/sound/pokey/AltASAP.cpp
Normal 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 );
|
||||
}
|
||||
|
||||
}
|
35
src/engine/platform/sound/pokey/AltASAP.hpp
Normal file
35
src/engine/platform/sound/pokey/AltASAP.hpp
Normal 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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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(""),
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue