diff --git a/CMakeLists.txt b/CMakeLists.txt index 8adb6214a..421ee83bf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -630,6 +630,7 @@ src/engine/platform/sound/pokey/AltASAP.cpp src/engine/platform/sound/qsound.c src/engine/platform/sound/swan.c +src/engine/platform/sound/swan_mdfn.cpp src/engine/platform/sound/su.cpp diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index dda41ea65..e594ecc95 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -619,6 +619,11 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do break; case DIV_SYSTEM_SWAN: dispatch=new DivPlatformSwan; + if (isRender) { + ((DivPlatformSwan*)dispatch)->setUseMdfn(eng->getConfInt("swanCoreRender",0)); + } else { + ((DivPlatformSwan*)dispatch)->setUseMdfn(eng->getConfInt("swanCore",0)); + } break; case DIV_SYSTEM_T6W28: dispatch=new DivPlatformT6W28; diff --git a/src/engine/platform/sound/swan_mdfn.cpp b/src/engine/platform/sound/swan_mdfn.cpp new file mode 100644 index 000000000..0ab752cd0 --- /dev/null +++ b/src/engine/platform/sound/swan_mdfn.cpp @@ -0,0 +1,363 @@ +/******************************************************************************/ +/* Mednafen - Multi-system Emulator */ +/******************************************************************************/ +/* sound.cpp - WonderSwan Sound Emulation +** Copyright (C) 2007-2017 Mednafen Team +** Copyright (C) 2016 Alex 'trap15' Marshall - http://daifukkat.su/ +** +** 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 "swan_mdfn.h" +#include + +#define MK_SAMPLE_CACHE \ + { \ + int sample; \ + sample = (((wsRAM[(/*(SampleRAMPos << 6) + */(sample_pos[ch] >> 1) + (ch << 4)) ] >> ((sample_pos[ch] & 1) ? 4 : 0)) & 0x0F)); \ + sample_cache[ch][0] = sample * ((volume[ch] >> 4) & 0x0F); \ + sample_cache[ch][1] = sample * ((volume[ch] >> 0) & 0x0F); \ + } + +#define MK_SAMPLE_CACHE_NOISE \ + { \ + int sample; \ + sample = ((nreg & 1) ? 0xF : 0x0); \ + sample_cache[ch][0] = sample * ((volume[ch] >> 4) & 0x0F); \ + sample_cache[ch][1] = sample * ((volume[ch] >> 0) & 0x0F); \ + } + +#define MK_SAMPLE_CACHE_VOICE \ + { \ + int sample, half; \ + sample = volume[ch]; \ + half = sample >> 1; \ + sample_cache[ch][0] = (voice_volume & 4) ? sample : (voice_volume & 8) ? half : 0; \ + sample_cache[ch][1] = (voice_volume & 1) ? sample : (voice_volume & 2) ? half : 0; \ + } + + +#define SYNCSAMPLE(wt) \ + { \ + int32_t left = sample_cache[ch][0] << 5, right = sample_cache[ch][1] << 5; \ + if (left!=last_val[ch][0]) { \ + blip_add_delta(sbuf[0], wt, left - last_val[ch][0]); \ + last_val[ch][0] = left; \ + } \ + if (right!=last_val[ch][1]) { \ + blip_add_delta(sbuf[1], wt, right - last_val[ch][1]); \ + last_val[ch][1] = right; \ + } \ + oscBuf[ch]->putSample(wt,(left+right)<<1); \ + } + +#define SYNCSAMPLE_NOISE(wt) SYNCSAMPLE(wt) + +void WSwan::SoundUpdate(void) +{ + int32_t run_time; + + //printf("%d\n", v30mz_timestamp); + //printf("%02x %02x\n", control, noise_control); + run_time = v30mz_timestamp - last_ts; + + for(unsigned int ch = 0; ch < 4; ch++) + { + // Channel is disabled? + if(!(control & (1 << ch))) + continue; + + if(ch == 1 && (control & 0x20)) // Direct D/A mode? + { + MK_SAMPLE_CACHE_VOICE; + SYNCSAMPLE(v30mz_timestamp); + } + else if(ch == 2 && (control & 0x40) && sweep_value) // Sweep + { + uint32_t tmp_pt = 2048 - period[ch]; + uint32_t meow_timestamp = v30mz_timestamp - run_time; + uint32_t tmp_run_time = run_time; + + while(tmp_run_time) + { + int32_t sub_run_time = tmp_run_time; + + if(sub_run_time > sweep_8192_divider) + sub_run_time = sweep_8192_divider; + + sweep_8192_divider -= sub_run_time; + if(sweep_8192_divider <= 0) + { + sweep_8192_divider += 8192; + sweep_counter--; + if(sweep_counter <= 0) + { + sweep_counter = sweep_step + 1; + period[ch] = (period[ch] + (int8_t)sweep_value) & 0x7FF; + } + } + + meow_timestamp += sub_run_time; + if(tmp_pt > 4) + { + period_counter[ch] -= sub_run_time; + while(period_counter[ch] <= 0) + { + sample_pos[ch] = (sample_pos[ch] + 1) & 0x1F; + + MK_SAMPLE_CACHE; + SYNCSAMPLE(meow_timestamp + period_counter[ch]); + period_counter[ch] += tmp_pt; + } + } + tmp_run_time -= sub_run_time; + } + } + else if(ch == 3 && (control & 0x80) && (noise_control & 0x10)) // Noise + { + uint32_t tmp_pt = 2048 - period[ch]; + + period_counter[ch] -= run_time; + while(period_counter[ch] <= 0) + { + static const uint8_t stab[8] = { 14, 10, 13, 4, 8, 6, 9, 11 }; + + nreg = ((nreg << 1) | ((1 ^ (nreg >> 7) ^ (nreg >> stab[noise_control & 0x7])) & 1)) & 0x7FFF; + + if(control & 0x80) + { + MK_SAMPLE_CACHE_NOISE; + SYNCSAMPLE_NOISE(v30mz_timestamp + period_counter[ch]); + } + else if(tmp_pt > 4) + { + sample_pos[ch] = (sample_pos[ch] + 1) & 0x1F; + MK_SAMPLE_CACHE; + SYNCSAMPLE(v30mz_timestamp + period_counter[ch]); + } + period_counter[ch] += tmp_pt; + } + } + else + { + uint32_t tmp_pt = 2048 - period[ch]; + + if(tmp_pt > 4) + { + period_counter[ch] -= run_time; + while(period_counter[ch] <= 0) + { + sample_pos[ch] = (sample_pos[ch] + 1) & 0x1F; + + MK_SAMPLE_CACHE; + SYNCSAMPLE(v30mz_timestamp + period_counter[ch]); // - period_counter[ch]); + period_counter[ch] += tmp_pt; + } + } + } + } + + if(HVoiceCtrl & 0x80) + { + int16_t sample = (uint8_t)HyperVoice; + + switch(HVoiceCtrl & 0xC) + { + case 0x0: sample = (uint16_t)sample << (8 - (HVoiceCtrl & 3)); break; + case 0x4: sample = (uint16_t)(sample | -0x100) << (8 - (HVoiceCtrl & 3)); break; + case 0x8: sample = (uint16_t)((int8_t)sample) << (8 - (HVoiceCtrl & 3)); break; + case 0xC: sample = (uint16_t)sample << 8; break; + } + // bring back to 11bit, keeping signedness + sample >>= 5; + + int32_t left, right; + left = (HVoiceChanCtrl & 0x40) ? (sample << 5) : 0; + right = (HVoiceChanCtrl & 0x20) ? (sample << 5) : 0; + + if (left!=last_hv_val[0]) { + blip_add_delta(sbuf[0], v30mz_timestamp, left - last_hv_val[0]); + last_hv_val[0] = left; + } + if (right!=last_hv_val[1]) { + blip_add_delta(sbuf[1], v30mz_timestamp, right - last_hv_val[1]); + last_hv_val[1] = right; + } + } + last_ts = v30mz_timestamp; +} + +void WSwan::SoundWrite(uint32_t A, uint8_t V) +{ + SoundUpdate(); + + if(A >= 0x80 && A <= 0x87) + { + int ch = (A - 0x80) >> 1; + + if(A & 1) + period[ch] = (period[ch] & 0x00FF) | ((V & 0x07) << 8); + else + period[ch] = (period[ch] & 0x0700) | ((V & 0xFF) << 0); + + //printf("Period %d: 0x%04x --- %f\n", ch, period[ch], 3072000.0 / (2048 - period[ch])); + } + else if(A >= 0x88 && A <= 0x8B) + { + volume[A - 0x88] = V; + } + else if(A == 0x8C) + sweep_value = V; + else if(A == 0x8D) + { + sweep_step = V; + sweep_counter = sweep_step + 1; + sweep_8192_divider = 8192; + } + else if(A == 0x8E) + { + //printf("NOISECONTROL: %02x\n", V); + if(V & 0x8) + nreg = 0; + + noise_control = V & 0x17; + } + else if(A == 0x90) + { + for(int n = 0; n < 4; n++) + { + if(!(control & (1 << n)) && (V & (1 << n))) + { + period_counter[n] = 1; + sample_pos[n] = 0x1F; + } + } + control = V; + //printf("Sound Control: %02x\n", V); + } + else if(A == 0x91) + { + output_control = V & 0xF; + //printf("%02x, %02x\n", V, (V >> 1) & 3); + } + else if(A == 0x92) + nreg = (nreg & 0xFF00) | (V << 0); + else if(A == 0x93) + nreg = (nreg & 0x00FF) | ((V & 0x7F) << 8); + else if(A == 0x94) + { + voice_volume = V & 0xF; + //printf("%02x\n", V); + } + else switch(A) + { + case 0x6A: HVoiceCtrl = V; break; + case 0x6B: HVoiceChanCtrl = V & 0x6F; break; + case 0x8F: SampleRAMPos = V; break; + case 0x95: HyperVoice = V; break; // Pick a port, any port?! + //default: printf("%04x:%02x\n", A, V); break; + } + SoundUpdate(); +} + +uint8_t WSwan::SoundRead(uint32_t A) +{ + SoundUpdate(); + + if(A >= 0x80 && A <= 0x87) + { + int ch = (A - 0x80) >> 1; + + if(A & 1) + return(period[ch] >> 8); + else + return(period[ch]); + } + else if(A >= 0x88 && A <= 0x8B) + return(volume[A - 0x88]); + else switch(A) + { + default: /*printf("SoundRead: %04x\n", A);*/ return(0); + case 0x6A: return(HVoiceCtrl); + case 0x6B: return(HVoiceChanCtrl); + case 0x8C: return(sweep_value); + case 0x8D: return(sweep_step); + case 0x8E: return(noise_control); + case 0x8F: return(SampleRAMPos); + case 0x90: return(control); + case 0x91: return(output_control | 0x80); + case 0x92: return((nreg >> 0) & 0xFF); + case 0x93: return((nreg >> 8) & 0xFF); + case 0x94: return(voice_volume); + } +} + +void WSwan::RAMWrite(uint32_t A, uint8_t V) +{ + wsRAM[A & 0x3F] = V; +} + +int32_t WSwan::SoundFlush(int16_t *SoundBuf, const int32_t MaxSoundFrames) +{ + int32_t FrameCount = 0; + + SoundUpdate(); + + + last_ts = 0; + + return(FrameCount); +} + +// Call before wsRAM is updated +void WSwan::SoundCheckRAMWrite(uint32_t A) +{ + if((A >> 6) == SampleRAMPos) + SoundUpdate(); +} + +void WSwan::SoundReset(void) +{ + memset(period, 0, sizeof(period)); + memset(volume, 0, sizeof(volume)); + voice_volume = 0; + sweep_step = 0; + sweep_value = 0; + noise_control = 0; + control = 0; + output_control = 0; + + sweep_8192_divider = 8192; + sweep_counter = 1; + SampleRAMPos = 0; + + for(unsigned ch = 0; ch < 4; ch++) + period_counter[ch] = 1; + + memset(sample_pos, 0, sizeof(sample_pos)); + nreg = 0; + + memset(sample_cache, 0, sizeof(sample_cache)); + memset(last_val, 0, sizeof(last_val)); + last_v_val = 0; + + HyperVoice = 0; + last_hv_val[0] = last_hv_val[1] = 0; + HVoiceCtrl = 0; + HVoiceChanCtrl = 0; + + last_ts = 0; + v30mz_timestamp = 0; +} diff --git a/src/engine/platform/sound/swan_mdfn.h b/src/engine/platform/sound/swan_mdfn.h new file mode 100644 index 000000000..32fb37a82 --- /dev/null +++ b/src/engine/platform/sound/swan_mdfn.h @@ -0,0 +1,79 @@ +/******************************************************************************/ +/* Mednafen - Multi-system Emulator */ +/******************************************************************************/ +/* sound.h - WonderSwan Sound Emulation +** Copyright (C) 2007-2016 Mednafen Team +** +** 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. +*/ + +#ifndef __WSWAN_SOUND_H +#define __WSWAN_SOUND_H + +#include +#include "blip_buf.h" +#include "../../dispatch.h" + +class WSwan +{ +public: + int32_t SoundFlush(int16_t *SoundBuf, const int32_t MaxSoundFrames); + + void SoundWrite(uint32_t, uint8_t); + uint8_t SoundRead(uint32_t); + void SoundReset(void); + void SoundCheckRAMWrite(uint32_t A); + + void SoundUpdate(); + void RAMWrite(uint32_t, uint8_t); + + int32_t sample_cache[4][2]; + + // Blip_Synth WaveSynth; + + // Blip_Buffer *sbuf[2] = { NULL }; + blip_buffer_t* sbuf[2]; + DivDispatchOscBuffer* oscBuf[4]; + + uint16_t period[4]; + uint8_t volume[4]; // left volume in upper 4 bits, right in lower 4 bits + uint8_t voice_volume; + + uint8_t sweep_step, sweep_value; + uint8_t noise_control; + uint8_t control; + uint8_t output_control; + + int32_t sweep_8192_divider; + uint8_t sweep_counter; + uint8_t SampleRAMPos; + + int32_t last_v_val; + + uint8_t HyperVoice; + int32_t last_hv_val[2]; + uint8_t HVoiceCtrl, HVoiceChanCtrl; + + int32_t period_counter[4]; + int32_t last_val[4][2]; // Last outputted value, l&r + uint8_t sample_pos[4]; + uint16_t nreg; + uint32_t last_ts; + uint32_t v30mz_timestamp; + + uint8_t wsRAM[64]; +}; + +#endif diff --git a/src/engine/platform/swan.cpp b/src/engine/platform/swan.cpp index b7ec3bd9c..e7430096e 100644 --- a/src/engine/platform/swan.cpp +++ b/src/engine/platform/swan.cpp @@ -58,6 +58,79 @@ const char** DivPlatformSwan::getRegisterSheet() { return regCheatSheetWS; } +// Mednafen +void DivPlatformSwan::acquireDirect(blip_buffer_t** bb, size_t len) { + for (int i=0; i<4; i++) { + oscBuf[i]->begin(len); + ws_mdfn->oscBuf[i]=oscBuf[i]; + } + + ws_mdfn->sbuf[0]=bb[0]; + ws_mdfn->sbuf[1]=bb[1]; + + for (size_t h=0; hv30mz_timestamp=h; + // heuristic + int pcmAdvance=1; + if (writes.empty()) { + if (!pcm || dacSample==-1) { + break; + } else { + pcmAdvance=len-h; + if (dacRate>0) { + int remainTime=(rate-dacPeriod+dacRate-1)/dacRate; + if (remainTime=rate) { + DivSample* s=parent->getSample(dacSample); + if (s->samples<=0 || dacPos>=s->samples) { + dacSample=-1; + dacPeriod=0; + break; + } + rWrite(0x09,(unsigned char)s->data8[dacPos++]+0x80); + if (s->isLoopable() && dacPos>=(unsigned int)s->loopEnd) { + dacPos=s->loopStart; + } else if (dacPos>=s->samples) { + dacSample=-1; + } + dacPeriod-=rate; + } + } + + h+=pcmAdvance-1; + + // the rest + while (!writes.empty()) { + QueuedWrite w=writes.front(); + regPool[w.addr]=w.val; + if (w.addr<0x40) { + ws_mdfn->SoundWrite(w.addr|0x80,w.val); + } else { + ws_mdfn->SoundCheckRAMWrite(w.addr&0x3f); + ws_mdfn->RAMWrite(w.addr&0x3f,w.val); + } + writes.pop(); + } + } + + ws_mdfn->v30mz_timestamp=len; + ws_mdfn->SoundUpdate(); + ws_mdfn->SoundFlush(NULL,0); + + for (int i=0; i<4; i++) { + oscBuf[i]->end(len); + } +} + +// asiekierka void DivPlatformSwan::acquire(short** buf, size_t len) { for (int i=0; i<4; i++) { oscBuf[i]->begin(len); @@ -598,6 +671,7 @@ void DivPlatformSwan::reset() { if (dumpWrites) { addWrite(0xffffffff,0); } + ws_mdfn->SoundReset(); swan_sound_init(&ws, true); pcm=false; sweep=false; @@ -614,7 +688,15 @@ void DivPlatformSwan::reset() { } int DivPlatformSwan::getOutputCount() { - return stereo?2:1; + return (stereo || useMdfn)?2:1; +} + +bool DivPlatformSwan::hasAcquireDirect() { + return useMdfn; +} + +void DivPlatformSwan::setUseMdfn(bool use) { + useMdfn=use; } void DivPlatformSwan::notifyWaveChange(int wave) { @@ -643,7 +725,11 @@ void DivPlatformSwan::poke(std::vector& wlist) { void DivPlatformSwan::setFlags(const DivConfig& flags) { chipClock=3072000; CHECK_CUSTOM_CLOCK; - rate=chipClock/128; + if (useMdfn) { + rate=chipClock; + } else { + rate=chipClock/128; + } stereo=flags.getBool("stereo",true); for (int i=0; i<4; i++) { oscBuf[i]->setRate(rate); @@ -654,12 +740,15 @@ int DivPlatformSwan::init(DivEngine* p, int channels, int sugRate, const DivConf parent=p; dumpWrites=false; skipRegisterWrites=false; + stereo=false; for (int i=0; i<4; i++) { isMuted[i]=false; oscBuf[i]=new DivDispatchOscBuffer; } + ws_mdfn=new WSwan(); + setFlags(flags); reset(); return 4; @@ -669,6 +758,7 @@ void DivPlatformSwan::quit() { for (int i=0; i<4; i++) { delete oscBuf[i]; } + delete ws_mdfn; } DivPlatformSwan::~DivPlatformSwan() { diff --git a/src/engine/platform/swan.h b/src/engine/platform/swan.h index 31ef96faa..e3ea745f9 100644 --- a/src/engine/platform/swan.h +++ b/src/engine/platform/swan.h @@ -23,6 +23,7 @@ #include "../dispatch.h" #include "../waveSynth.h" #include "sound/swan.h" +#include "sound/swan_mdfn.h" #include "../../fixedQueue.h" class DivPlatformSwan: public DivDispatch { @@ -38,7 +39,8 @@ class DivPlatformSwan: public DivDispatch { Channel chan[4]; DivDispatchOscBuffer* oscBuf[4]; bool isMuted[4]; - bool stereo=true; + bool stereo; + bool useMdfn; bool pcm, sweep, furnaceDac, setPos; unsigned char sampleBank, noise; int dacPeriod, dacRate; @@ -56,11 +58,13 @@ class DivPlatformSwan: public DivDispatch { FixedQueue postDACWrites; swan_sound_t ws; + WSwan* ws_mdfn; void updateWave(int ch); friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); public: + void acquireDirect(blip_buffer_t** bb, size_t len); void acquire(short** buf, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); @@ -78,6 +82,8 @@ class DivPlatformSwan: public DivDispatch { void notifyWaveChange(int wave); void notifyInsDeletion(void* ins); int getOutputCount(); + bool hasAcquireDirect(); + void setUseMdfn(bool use); void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); const char** getRegisterSheet(); diff --git a/src/engine/platform/swan_before.cpp b/src/engine/platform/swan_before.cpp new file mode 100644 index 000000000..79cfbd695 --- /dev/null +++ b/src/engine/platform/swan_before.cpp @@ -0,0 +1,670 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2025 tildearrow and contributors + * + * 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 "swan_before.h" +#include "../engine.h" +#include "furIcons.h" +#include "IconsFontAwesome4.h" +#include + +#define rWrite(a,v) if (!skipRegisterWrites) {writes.push(QueuedWrite(a,v)); if (dumpWrites) {addWrite(a,v);}} +#define postWrite(a,v) postDACWrites.push(DivRegWrite(a,v)); + +#define CHIP_DIVIDER 32 + +const char* regCheatSheetWS[]={ + "CH1_Pitch", "00", + "CH2_Pitch", "02", + "CH3_Pitch", "04", + "CH4_Pitch", "06", + "CH1_Vol", "08", + "CH2_Vol", "09", + "CH3_Vol", "0A", + "CH4_Vol", "0B", + "Sweep_Value", "0C", + "Sweep_Time", "0D", + "Noise", "0E", + "Wave_Base", "0F", + "Ctrl", "10", + "Output", "11", + "Random", "12", + "Voice_Ctrl", "14", + "Wave_Mem", "40", + NULL +}; + +const char** DivPlatformSwan::getRegisterSheet() { + return regCheatSheetWS; +} + +void DivPlatformSwan::acquireDirect(blip_buffer_t** bb, size_t len) { + for (int i=0; i<4; i++) { + oscBuf[i]->begin(len); + ws_mdfn->oscBuf[i]=oscBuf[i]; + } + + ws_mdfn->sbuf[0]=bb[0]; + ws_mdfn->sbuf[1]=bb[1]; + + for (size_t h=0; hv30mz_timestamp=h; + // heuristic + int pcmAdvance=1; + if (writes.empty()) { + if (!pcm || dacSample==-1) { + break; + } else { + pcmAdvance=len-h; + if (dacRate>0) { + int remainTime=(rate-dacPeriod+dacRate-1)/dacRate; + if (remainTime=rate) { + DivSample* s=parent->getSample(dacSample); + if (s->samples<=0 || dacPos>=s->samples) { + dacSample=-1; + dacPeriod=0; + break; + } + rWrite(0x09,(unsigned char)s->data8[dacPos++]+0x80); + if (s->isLoopable() && dacPos>=(unsigned int)s->loopEnd) { + dacPos=s->loopStart; + } else if (dacPos>=s->samples) { + dacSample=-1; + } + dacPeriod-=rate; + } + } + + h+=pcmAdvance-1; + + // the rest + while (!writes.empty()) { + QueuedWrite w=writes.front(); + regPool[w.addr]=w.val; + if (w.addr<0x40) { + ws_mdfn->SoundWrite(w.addr|0x80,w.val); + } else { + ws_mdfn->SoundCheckRAMWrite(w.addr&0x3f); + ws_mdfn->RAMWrite(w.addr&0x3f,w.val); + } + writes.pop(); + } + } + + ws_mdfn->v30mz_timestamp=len; + ws_mdfn->SoundUpdate(); + ws_mdfn->SoundFlush(NULL,0); + + for (int i=0; i<4; i++) { + oscBuf[i]->end(len); + } +} + +void DivPlatformSwan::updateWave(int ch) { + unsigned char addr=0x40+ch*16; + for (int i=0; i<16; i++) { + int nibble1=chan[ch].ws.output[i<<1]; + int nibble2=chan[ch].ws.output[1+(i<<1)]; + rWrite(addr+i,nibble1|(nibble2<<4)); + } +} + +void DivPlatformSwan::calcAndWriteOutVol(int ch, int env) { + int vl=chan[ch].vol*((chan[ch].pan>>4)&0x0f)*env/225; + int vr=chan[ch].vol*(chan[ch].pan&0x0f)*env/225; + if (ch==1&&pcm) { + vl=(vl>0)?((vl>7)?3:2):0; + vr=(vr>0)?((vr>7)?3:2):0; + chan[1].outVol=vr|(vl<<2); + } else { + chan[ch].outVol=vr|(vl<<4); + } + writeOutVol(ch); +} + +void DivPlatformSwan::writeOutVol(int ch) { + unsigned char val=isMuted[ch]?0:chan[ch].outVol; + if (ch==1&&pcm) { + rWrite(0x14,val) + } else { + rWrite(0x08+ch,val); + } +} + +void DivPlatformSwan::tick(bool sysTick) { + unsigned char sndCtrl=(pcm?0x20:0)|(sweep?0x40:0)|((noise>0)?0x80:0); + for (int i=0; i<4; i++) { + chan[i].std.next(); + if (chan[i].std.vol.had) { + int env=chan[i].std.vol.val; + if(parent->getIns(chan[i].ins,DIV_INS_SWAN)->type==DIV_INS_AMIGA) { + env=MIN(env/4,15); + } + calcAndWriteOutVol(i,env); + } + if (NEW_ARP_STRAT) { + chan[i].handleArp(); + } else if (chan[i].std.arp.had) { + if (!chan[i].inPorta) { + chan[i].baseFreq=NOTE_PERIODIC(parent->calcArp(chan[i].note,chan[i].std.arp.val)); + } + chan[i].freqChanged=true; + } + if (chan[i].std.wave.had && !(i==1 && pcm)) { + if (chan[i].wave!=chan[i].std.wave.val || chan[i].ws.activeChanged()) { + chan[i].wave=chan[i].std.wave.val; + chan[i].ws.changeWave1(chan[i].wave); + } + } + if (chan[i].std.panL.had) { + chan[i].pan&=0x0f; + chan[i].pan|=(chan[i].std.panL.val&15)<<4; + } + if (chan[i].std.panR.had) { + chan[i].pan&=0xf0; + chan[i].pan|=chan[i].std.panR.val&15; + } + if (chan[i].std.panL.had || chan[i].std.panR.had) { + calcAndWriteOutVol(i,chan[i].std.vol.will?chan[i].std.vol.val:15); + } + if (chan[i].std.pitch.had) { + if (chan[i].std.pitch.mode) { + chan[i].pitch2+=chan[i].std.pitch.val; + CLAMP_VAR(chan[i].pitch2,-32768,32767); + } else { + chan[i].pitch2=chan[i].std.pitch.val; + } + chan[i].freqChanged=true; + } + if (chan[i].active) { + sndCtrl|=(1<calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER); + if (i==1 && pcm && furnaceDac) { + double off=1.0; + if (dacSample>=0 && dacSamplesong.sampleLen) { + DivSample* s=parent->getSample(dacSample); + if (s->centerRate<1) { + off=1.0; + } else { + off=parent->getCenterRate()/(double)s->centerRate; + } + } + dacRate=((double)chipClock/2)/MAX(1,off*chan[i].freq); + if (dumpWrites) postWrite(0xffff0001,dacRate); + } + if (chan[i].freq>2048) chan[i].freq=2048; + if (chan[i].freq<1) chan[i].freq=1; + int rVal=2048-chan[i].freq; + rWrite(i*2,rVal&0xff); + rWrite(i*2+1,rVal>>8); + if (chan[i].keyOn) { + if (!chan[i].std.vol.will) { + calcAndWriteOutVol(i,15); + } + chan[i].keyOn=false; + } + if (chan[i].keyOff) { + chan[i].keyOff=false; + } + chan[i].freqChanged=false; + } + } + if (chan[3].std.duty.had) { + if (noise!=chan[3].std.duty.val) { + noise=chan[3].std.duty.val; + if (noise>0) { + rWrite(0x0e,((noise-1)&0x07)|0x18); + sndCtrl|=0x80; + } else { + sndCtrl&=~0x80; + } + } + } + if (chan[3].std.phaseReset.had) { + if (noise>0) { + rWrite(0x0e,((noise-1)&0x07)|0x18); + sndCtrl|=0x80; + } else { + sndCtrl&=~0x80; + } + } + unsigned char origSndCtrl=sndCtrl; + bool phaseResetHappens=false; + for (int i=0; i<4; i++) { + if (chan[i].std.phaseReset.had) { + phaseResetHappens=true; + sndCtrl&=~(1<getIns(chan[c.chan].ins,DIV_INS_SWAN); + if (c.chan==1) { + if (ins->type==DIV_INS_AMIGA || ins->amiga.useSample) { + pcm=true; + } else if (furnaceDac) { + pcm=false; + chan[c.chan].sampleNote=DIV_NOTE_NULL; + chan[c.chan].sampleNoteDelta=0; + } + if (pcm) { + if (skipRegisterWrites) break; + if (setPos) { + setPos=false; + } else { + dacPos=0; + } + dacPeriod=0; + if (ins->type==DIV_INS_AMIGA || ins->amiga.useSample) { + if (c.value!=DIV_NOTE_NULL) { + dacSample=ins->amiga.getSample(c.value); + chan[c.chan].sampleNote=c.value; + c.value=ins->amiga.getFreq(c.value); + chan[c.chan].sampleNoteDelta=c.value-chan[c.chan].sampleNote; + } else if (chan[c.chan].sampleNote!=DIV_NOTE_NULL) { + dacSample=ins->amiga.getSample(chan[c.chan].sampleNote); + c.value=ins->amiga.getFreq(chan[c.chan].sampleNote); + } + if (dacSample<0 || dacSample>=parent->song.sampleLen) { + dacSample=-1; + if (dumpWrites) postWrite(0xffff0002,0); + break; + } else { + if (dumpWrites) { + postWrite(0xffff0000,dacSample); + } + } + if (c.value!=DIV_NOTE_NULL) { + chan[1].baseFreq=NOTE_PERIODIC(c.value); + chan[1].freqChanged=true; + chan[1].note=c.value; + } + chan[1].active=true; + chan[1].keyOn=true; + chan[1].macroInit(ins); + furnaceDac=true; + } else { + if (c.value!=DIV_NOTE_NULL) { + chan[1].note=c.value; + } + dacSample=12*sampleBank+chan[1].note%12; + if (dacSample>=parent->song.sampleLen) { + dacSample=-1; + if (dumpWrites) postWrite(0xffff0002,0); + break; + } else { + if (dumpWrites) postWrite(0xffff0000,dacSample); + } + dacRate=parent->getSample(dacSample)->rate; + if (dumpWrites) { + postWrite(0xffff0001,dacRate); + } + chan[1].active=true; + chan[1].keyOn=true; + furnaceDac=false; + } + break; + } + } + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + } + chan[c.chan].active=true; + chan[c.chan].keyOn=true; + chan[c.chan].macroInit(ins); + if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { + chan[c.chan].outVol=chan[c.chan].vol; + } + if (chan[c.chan].wave<0) { + chan[c.chan].wave=0; + chan[c.chan].ws.changeWave1(chan[c.chan].wave); + } + chan[c.chan].ws.init(ins,32,15,chan[c.chan].insChanged); + chan[c.chan].insChanged=false; + break; + } + case DIV_CMD_NOTE_OFF: + if (c.chan==1&&pcm) { + dacSample=-1; + if (dumpWrites) postWrite(0xffff0002,0); + pcm=false; + chan[c.chan].sampleNote=DIV_NOTE_NULL; + chan[c.chan].sampleNoteDelta=0; + } + chan[c.chan].active=false; + chan[c.chan].keyOff=true; + chan[c.chan].macroInit(NULL); + break; + case DIV_CMD_NOTE_OFF_ENV: + case DIV_CMD_ENV_RELEASE: + chan[c.chan].std.release(); + break; + case DIV_CMD_INSTRUMENT: + if (chan[c.chan].ins!=c.value || c.value2==1) { + chan[c.chan].ins=c.value; + chan[c.chan].insChanged=true; + } + break; + case DIV_CMD_VOLUME: + if (chan[c.chan].vol!=c.value) { + chan[c.chan].vol=c.value; + if (!chan[c.chan].std.vol.has) { + calcAndWriteOutVol(c.chan,15); + } + } + break; + case DIV_CMD_GET_VOLUME: + return chan[c.chan].vol; + break; + case DIV_CMD_PITCH: + chan[c.chan].pitch=c.value; + chan[c.chan].freqChanged=true; + break; + case DIV_CMD_WAVE: + chan[c.chan].wave=c.value; + chan[c.chan].ws.changeWave1(chan[c.chan].wave); + chan[c.chan].keyOn=true; + break; + case DIV_CMD_WS_SWEEP_TIME: + if (c.chan==2) { + if (c.value==0) { + sweep=false; + } else { + sweep=true; + rWrite(0x0d,(c.value-1)&0xff); + } + } + break; + case DIV_CMD_WS_SWEEP_AMOUNT: + if (c.chan==2) { + rWrite(0x0c,c.value&0xff); + } + break; + case DIV_CMD_NOTE_PORTA: { + int destFreq=NOTE_PERIODIC(c.value2+chan[c.chan].sampleNoteDelta); + bool return2=false; + if (destFreq>chan[c.chan].baseFreq) { + chan[c.chan].baseFreq+=c.value; + if (chan[c.chan].baseFreq>=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } else { + chan[c.chan].baseFreq-=c.value; + if (chan[c.chan].baseFreq<=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } + chan[c.chan].freqChanged=true; + if (return2) { + chan[c.chan].inPorta=false; + return 2; + } + break; + } + case DIV_CMD_STD_NOISE_MODE: + if (c.chan==3) { + noise=c.value&0xff; + if (noise>0) rWrite(0x0e,((noise-1)&0x07)|0x18); + } + break; + case DIV_CMD_SAMPLE_MODE: + if (c.chan==1) { + pcm=c.value; + if (!pcm) { + chan[c.chan].sampleNote=DIV_NOTE_NULL; + chan[c.chan].sampleNoteDelta=0; + } + } + break; + case DIV_CMD_SAMPLE_BANK: + sampleBank=c.value; + if (sampleBank>(parent->song.sample.size()/12)) { + sampleBank=parent->song.sample.size()/12; + } + break; + case DIV_CMD_SAMPLE_POS: + dacPos=c.value; + setPos=true; + break; + case DIV_CMD_PANNING: { + chan[c.chan].pan=(c.value&0xf0)|(c.value2>>4); + calcAndWriteOutVol(c.chan,chan[c.chan].std.vol.will?chan[c.chan].std.vol.val:15); + break; + } + case DIV_CMD_LEGATO: + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+chan[c.chan].sampleNoteDelta+((HACKY_LEGATO_MESS)?(chan[c.chan].std.arp.val):(0))); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + break; + case DIV_CMD_PRE_PORTA: + if (chan[c.chan].active && c.value2) { + if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_SWAN)); + } + if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note); + chan[c.chan].inPorta=c.value; + break; + case DIV_CMD_GET_VOLMAX: + return 15; + break; + case DIV_CMD_MACRO_OFF: + chan[c.chan].std.mask(c.value,true); + break; + case DIV_CMD_MACRO_ON: + chan[c.chan].std.mask(c.value,false); + break; + case DIV_CMD_MACRO_RESTART: + chan[c.chan].std.restart(c.value); + break; + default: + break; + } + return 1; +} + +void DivPlatformSwan::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; + writeOutVol(ch); +} + +void DivPlatformSwan::forceIns() { + noise=0; + for (int i=0; i<4; i++) { + chan[i].insChanged=true; + chan[i].freqChanged=true; + updateWave(i); + writeOutVol(i); + } +} + +void* DivPlatformSwan::getChanState(int ch) { + return &chan[ch]; +} + +DivMacroInt* DivPlatformSwan::getChanMacroInt(int ch) { + return &chan[ch].std; +} + +unsigned short DivPlatformSwan::getPan(int ch) { + return ((chan[ch].pan&0xf0)<<4)|(chan[ch].pan&15); +} + +DivChannelModeHints DivPlatformSwan::getModeHints(int ch) { + DivChannelModeHints ret; + + switch (ch) { + case 1: // PCM + ret.count=1; + ret.hint[0]=ICON_FA_VOLUME_UP; + ret.type[0]=pcm?4:0; + break; + case 2: // sweep + ret.count=1; + ret.hint[0]=ICON_FUR_SAW; + ret.type[0]=sweep?2:0; + break; + case 3: // noise + ret.count=1; + ret.hint[0]=ICON_FUR_NOISE; + ret.type[0]=noise?4:0; + break; + } + + return ret; +} + +DivDispatchOscBuffer* DivPlatformSwan::getOscBuffer(int ch) { + return oscBuf[ch]; +} + +unsigned char* DivPlatformSwan::getRegisterPool() { + // get Random from emulator + regPool[0x12]=ws_mdfn->SoundRead(0x92); + regPool[0x13]=ws_mdfn->SoundRead(0x93); + return regPool; +} + +int DivPlatformSwan::getRegisterPoolSize() { + return 128; +} + +void DivPlatformSwan::reset() { + while (!writes.empty()) writes.pop(); + while (!postDACWrites.empty()) postDACWrites.pop(); + memset(regPool,0,128); + for (int i=0; i<4; i++) { + chan[i]=Channel(); + chan[i].vol=15; + chan[i].pan=0xff; + chan[i].std.setEngine(parent); + chan[i].ws.setEngine(parent); + chan[i].ws.init(NULL,32,15,false); + rWrite(0x08+i,0xff); + } + if (dumpWrites) { + addWrite(0xffffffff,0); + } + ws_mdfn->SoundReset(); + pcm=false; + sweep=false; + furnaceDac=false; + setPos=false; + noise=0; + dacPeriod=0; + dacRate=0; + dacPos=0; + dacSample=-1; + sampleBank=0; + rWrite(0x0f,0x00); // wave table at 0x0000 + rWrite(0x11,0x09); // enable speakers +} + +int DivPlatformSwan::getOutputCount() { + return 2; +} + +bool DivPlatformSwan::hasAcquireDirect() { + return true; +} + +void DivPlatformSwan::notifyWaveChange(int wave) { + for (int i=0; i<4; i++) { + if (chan[i].wave==wave) { + chan[i].ws.changeWave1(wave); + updateWave(i); + } + } +} + +void DivPlatformSwan::notifyInsDeletion(void* ins) { + for (int i=0; i<4; i++) { + chan[i].std.notifyInsDeletion((DivInstrument*)ins); + } +} + +void DivPlatformSwan::poke(unsigned int addr, unsigned short val) { + rWrite(addr,val); +} + +void DivPlatformSwan::poke(std::vector& wlist) { + for (DivRegWrite& i: wlist) rWrite(i.addr,i.val); +} + +void DivPlatformSwan::setFlags(const DivConfig& flags) { + chipClock=3072000; + CHECK_CUSTOM_CLOCK; + rate=chipClock; + for (int i=0; i<4; i++) { + oscBuf[i]->setRate(rate); + } +} + +int DivPlatformSwan::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) { + parent=p; + dumpWrites=false; + skipRegisterWrites=false; + for (int i=0; i<4; i++) { + isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; + } + ws_mdfn=new WSwan(); + setFlags(flags); + reset(); + return 4; +} + +void DivPlatformSwan::quit() { + for (int i=0; i<4; i++) { + delete oscBuf[i]; + } + delete ws_mdfn; +} + +DivPlatformSwan::~DivPlatformSwan() { +} diff --git a/src/engine/platform/swan_before.h b/src/engine/platform/swan_before.h new file mode 100644 index 000000000..63508f163 --- /dev/null +++ b/src/engine/platform/swan_before.h @@ -0,0 +1,90 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2025 tildearrow and contributors + * + * 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. + */ + +#ifndef _SWAN_H +#define _SWAN_H + +#include "../dispatch.h" +#include "../waveSynth.h" +#include "sound/swan_mdfn.h" +#include "../../fixedQueue.h" + +class DivPlatformSwan: public DivDispatch { + struct Channel: public SharedChannel { + unsigned char pan; + int wave; + DivWaveSynth ws; + Channel(): + SharedChannel(15), + pan(255), + wave(-1) {} + }; + Channel chan[4]; + DivDispatchOscBuffer* oscBuf[4]; + bool isMuted[4]; + bool pcm, sweep, furnaceDac, setPos; + unsigned char sampleBank, noise; + int dacPeriod, dacRate; + unsigned int dacPos; + int dacSample; + + unsigned char regPool[0x80]; + struct QueuedWrite { + unsigned char addr; + unsigned char val; + QueuedWrite(): addr(0), val(0) {} + QueuedWrite(unsigned char a, unsigned char v): addr(a), val(v) {} + }; + FixedQueue writes; + FixedQueue postDACWrites; + WSwan* ws_mdfn; + void updateWave(int ch); + friend void putDispatchChip(void*,int); + friend void putDispatchChan(void*,int,int); + public: + void acquireDirect(blip_buffer_t** bb, size_t len); + int dispatch(DivCommand c); + void* getChanState(int chan); + DivMacroInt* getChanMacroInt(int ch); + unsigned short getPan(int chan); + DivChannelModeHints getModeHints(int chan); + DivDispatchOscBuffer* getOscBuffer(int chan); + unsigned char* getRegisterPool(); + int getRegisterPoolSize(); + void reset(); + void forceIns(); + void tick(bool sysTick=true); + void muteChannel(int ch, bool mute); + void setFlags(const DivConfig& flags); + void notifyWaveChange(int wave); + void notifyInsDeletion(void* ins); + int getOutputCount(); + bool hasAcquireDirect(); + void poke(unsigned int addr, unsigned short val); + void poke(std::vector& wlist); + const char** getRegisterSheet(); + int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags); + void quit(); + ~DivPlatformSwan(); + private: + void calcAndWriteOutVol(int ch, int env); + void writeOutVol(int ch); +}; + +#endif diff --git a/src/gui/gui.h b/src/gui/gui.h index f29485b8c..4195440ae 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1797,6 +1797,7 @@ class FurnaceGUI { int esfmCore; int opllCore; int ayCore; + int swanCore; int dsidQuality; int gbQuality; int pnQuality; @@ -1817,6 +1818,7 @@ class FurnaceGUI { int esfmCoreRender; int opllCoreRender; int ayCoreRender; + int swanCoreRender; int dsidQualityRender; int gbQualityRender; int pnQualityRender; @@ -2049,6 +2051,7 @@ class FurnaceGUI { esfmCore(0), opllCore(0), ayCore(0), + swanCore(0), dsidQuality(3), gbQuality(3), pnQuality(3), @@ -2069,6 +2072,7 @@ class FurnaceGUI { esfmCoreRender(0), opllCoreRender(0), ayCoreRender(0), + swanCoreRender(0), dsidQualityRender(3), gbQualityRender(3), pnQualityRender(3), diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 0f1c3a881..08b79267a 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -209,6 +209,11 @@ const char* ayCores[]={ "AtomicSSG" }; +const char* swanCores[]={ + "asiekierka new core", + "Mednafen" +}; + const char* coreQualities[]={ _N("Lower"), _N("Low"), @@ -2053,6 +2058,17 @@ void FurnaceGUI::drawSettings() { ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); if (ImGui::Combo("##AYCoreRender",&settings.ayCoreRender,ayCores,2)) settingsChanged=true; + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::AlignTextToFramePadding(); + ImGui::Text("WonderSwan"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::Combo("##SwanCore",&settings.swanCore,swanCores,2)) settingsChanged=true; + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::Combo("##SwanCoreRender",&settings.swanCoreRender,swanCores,2)) settingsChanged=true; + ImGui::EndTable(); } @@ -5123,6 +5139,7 @@ void FurnaceGUI::readConfig(DivConfig& conf, FurnaceGUISettingGroups groups) { settings.esfmCore=conf.getInt("esfmCore",0); settings.opllCore=conf.getInt("opllCore",0); settings.ayCore=conf.getInt("ayCore",0); + settings.swanCore=conf.getInt("swanCore",0); settings.dsidQuality=conf.getInt("dsidQuality",3); settings.gbQuality=conf.getInt("gbQuality",3); @@ -5145,6 +5162,7 @@ void FurnaceGUI::readConfig(DivConfig& conf, FurnaceGUISettingGroups groups) { settings.esfmCoreRender=conf.getInt("esfmCoreRender",0); settings.opllCoreRender=conf.getInt("opllCoreRender",0); settings.ayCoreRender=conf.getInt("ayCoreRender",0); + settings.swanCoreRender=conf.getInt("swanCoreRender",0); settings.dsidQualityRender=conf.getInt("dsidQualityRender",3); settings.gbQualityRender=conf.getInt("gbQualityRender",3); @@ -5184,6 +5202,7 @@ void FurnaceGUI::readConfig(DivConfig& conf, FurnaceGUISettingGroups groups) { clampSetting(settings.esfmCore,0,1); clampSetting(settings.opllCore,0,1); clampSetting(settings.ayCore,0,1); + clampSetting(settings.swanCore,0,1); clampSetting(settings.dsidQuality,0,5); clampSetting(settings.gbQuality,0,5); clampSetting(settings.pnQuality,0,5); @@ -5204,6 +5223,7 @@ void FurnaceGUI::readConfig(DivConfig& conf, FurnaceGUISettingGroups groups) { clampSetting(settings.esfmCoreRender,0,1); clampSetting(settings.opllCoreRender,0,1); clampSetting(settings.ayCoreRender,0,1); + clampSetting(settings.swanCoreRender,0,1); clampSetting(settings.dsidQualityRender,0,5); clampSetting(settings.gbQualityRender,0,5); clampSetting(settings.pnQualityRender,0,5); @@ -5697,6 +5717,7 @@ void FurnaceGUI::writeConfig(DivConfig& conf, FurnaceGUISettingGroups groups) { conf.set("esfmCore",settings.esfmCore); conf.set("opllCore",settings.opllCore); conf.set("ayCore",settings.ayCore); + conf.set("swanCore",settings.swanCore); conf.set("dsidQuality",settings.dsidQuality); conf.set("gbQuality",settings.gbQuality); @@ -5719,6 +5740,7 @@ void FurnaceGUI::writeConfig(DivConfig& conf, FurnaceGUISettingGroups groups) { conf.set("esfmCoreRender",settings.esfmCoreRender); conf.set("opllCoreRender",settings.opllCoreRender); conf.set("ayCoreRender",settings.ayCoreRender); + conf.set("swanCoreRender",settings.swanCoreRender); conf.set("dsidQualityRender",settings.dsidQualityRender); conf.set("gbQualityRender",settings.gbQualityRender); @@ -5776,6 +5798,7 @@ void FurnaceGUI::commitSettings() { settings.esfmCore!=e->getConfInt("esfmCore",0) || settings.opllCore!=e->getConfInt("opllCore",0) || settings.ayCore!=e->getConfInt("ayCore",0) || + settings.swanCore!=e->getConfInt("swanCore",0) || settings.dsidQuality!=e->getConfInt("dsidQuality",3) || settings.gbQuality!=e->getConfInt("gbQuality",3) || settings.pnQuality!=e->getConfInt("pnQuality",3) ||