diff --git a/CMakeLists.txt b/CMakeLists.txt index a74d3817d..8adb6214a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -629,7 +629,7 @@ src/engine/platform/sound/pokey/AltASAP.cpp src/engine/platform/sound/qsound.c -src/engine/platform/sound/swan.cpp +src/engine/platform/sound/swan.c src/engine/platform/sound/su.cpp diff --git a/src/engine/platform/sound/swan.c b/src/engine/platform/sound/swan.c new file mode 100644 index 000000000..5fce1cc6c --- /dev/null +++ b/src/engine/platform/sound/swan.c @@ -0,0 +1,259 @@ +/* +WonderSwan sound core + +Copyright (c) 2025 Adrian "asie" Siekierka + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +#include +#include +#include "swan.h" + +#define HYPERV_ENABLE 0x0080 + +#define SND_NOISE_ENABLE 0x10 +#define SND_NOISE_RESET 0x08 + +#define SND_CH1_ENABLE 0x01 +#define SND_CH2_ENABLE 0x02 +#define SND_CH3_ENABLE 0x04 +#define SND_CH4_ENABLE 0x08 +#define SND_CH2_VOICE 0x20 +#define SND_CH3_SWEEP 0x40 +#define SND_CH4_NOISE 0x80 + +#define SND_OUT_HEADPHONES 0x80 +#define SND_OUT_HEADPHONES_ENABLE 0x08 +#define SND_OUT_SPEAKER_ENABLE 0x01 + +#define SND_VOL_CH2_LEFT_HALF 0x08 +#define SND_VOL_CH2_LEFT_FULL 0x01 +#define SND_VOL_CH2_RIGHT_HALF 0x02 +#define SND_VOL_CH2_RIGHT_FULL 0x01 + +#define SND_TEST_CH_VOICE 0x20 +#define SND_TEST_FAST_SWEEP 0x02 +#define SND_TEST_HOLD_CH 0x01 + +static const uint8_t lfsr_tap_bits[8] = { 14, 10, 13, 4, 8, 6, 9, 11 }; + +void swan_sound_init(swan_sound_t *snd, bool headphones) { + memset(snd, 0, sizeof(swan_sound_t)); + + snd->out_ctrl = headphones ? 0x80 : 0x00; +} + +uint8_t swan_sound_in(swan_sound_t *snd, uint16_t port) { + switch (port & 0x1FF) { + case 0x6A: return snd->hyper_ctrl; + case 0x6B: return snd->hyper_ctrl >> 8; + case 0x80: return snd->frequency[0]; + case 0x81: return snd->frequency[0] >> 8; + case 0x82: return snd->frequency[1]; + case 0x83: return snd->frequency[1] >> 8; + case 0x84: return snd->frequency[2]; + case 0x85: return snd->frequency[2] >> 8; + case 0x86: return snd->frequency[3]; + case 0x87: return snd->frequency[3] >> 8; + case 0x88: return snd->volume[0]; + case 0x89: return snd->volume[1]; + case 0x8A: return snd->volume[2]; + case 0x8B: return snd->volume[3]; + case 0x8C: return snd->sweep_amount; + case 0x8D: return snd->sweep_ticks; + case 0x8E: return snd->noise_ctrl; + case 0x8F: return snd->wave_address; + case 0x90: return snd->ch_ctrl; + case 0x91: return snd->out_ctrl; + case 0x92: return snd->noise_lfsr; + case 0x93: return snd->noise_lfsr >> 8; + case 0x94: return snd->voice_volume; + case 0x95: return snd->test_flags; + case 0x96: return snd->synth_output_right; + case 0x97: return snd->synth_output_right >> 8; + case 0x98: return snd->synth_output_left; + case 0x99: return snd->synth_output_left >> 8; + case 0x9A: return snd->synth_output_mono; + case 0x9B: return snd->synth_output_mono >> 8; + default: return 0; + } +} + +void swan_sound_out(swan_sound_t *snd, uint16_t port, uint8_t value) { + switch (port & 0x1FF) { + case 0x6A: snd->hyper_ctrl = (snd->hyper_ctrl & 0xFF00) | value; break; + case 0x6B: snd->hyper_ctrl = (snd->hyper_ctrl & 0x0FFF) | ((value & 0x70) << 8); break; + case 0x80: snd->frequency[0] = (snd->frequency[0] & 0xFF00) | value; break; + case 0x81: snd->frequency[0] = (snd->frequency[0] & 0xFF) | ((value & 0x7) << 8); break; + case 0x82: snd->frequency[1] = (snd->frequency[1] & 0xFF00) | value; break; + case 0x83: snd->frequency[1] = (snd->frequency[1] & 0xFF) | ((value & 0x7) << 8); break; + case 0x84: snd->frequency[2] = (snd->frequency[2] & 0xFF00) | value; break; + case 0x85: snd->frequency[2] = (snd->frequency[2] & 0xFF) | ((value & 0x7) << 8); break; + case 0x86: snd->frequency[3] = (snd->frequency[3] & 0xFF00) | value; break; + case 0x87: snd->frequency[3] = (snd->frequency[3] & 0xFF) | ((value & 0x7) << 8); break; + case 0x88: snd->volume[0] = value; break; + case 0x89: snd->volume[1] = value; break; + case 0x8A: snd->volume[2] = value; break; + case 0x8B: snd->volume[3] = value; break; + case 0x8C: snd->sweep_amount = value; break; + case 0x8D: snd->sweep_ticks = value & 0x1F; break; + case 0x8E: + snd->noise_ctrl = value & 0x17; + if (value & SND_NOISE_RESET) { + snd->noise_lfsr = 0; + } + break; + case 0x8F: snd->wave_address = value; break; + case 0x90: snd->ch_ctrl = value; break; + case 0x91: snd->out_ctrl = (snd->out_ctrl & 0x80) | (value & 0x0F); break; + case 0x94: snd->voice_volume = value; break; + case 0x95: snd->test_flags = value; break; + } +} + +static void swan_sound_subtick(swan_sound_t *snd, uint32_t cycles) { + for (int ch = 0; ch < 4; ch++) { + snd->period_counter[ch] += cycles; + uint32_t step = 2048 - snd->frequency[ch]; + while (snd->period_counter[ch] >= step) { + snd->sample_index[ch] = (snd->sample_index[ch] + 1) & 0x1F; + snd->period_counter[ch] -= step; + } + } + + if (snd->ch_ctrl & SND_CH3_SWEEP) { + snd->sweep_counter += cycles; + uint32_t step = ((snd->test_flags & SND_TEST_FAST_SWEEP) ? 1 : 8192) * (snd->sweep_ticks + 1); + while (snd->sweep_counter >= step) { + snd->frequency[2] = (snd->frequency[2] + snd->sweep_amount) & 0x7FF; + snd->sweep_counter -= step; + } + } +} + +static inline void sample_render_channel(swan_sound_t *snd, uint8_t ch, uint8_t sample) { + snd->ch_output_right[ch] = sample * (snd->volume[ch] & 0xF); + snd->ch_output_left[ch] = sample * (snd->volume[ch] >> 4); +} + +static inline void wavetable_render_channel(swan_sound_t *snd, uint8_t ch) { + uint8_t index = snd->sample_index[ch]; + uint8_t addr = (ch << 4) | (index >> 1); + sample_render_channel(snd, ch, (index & 1) ? (snd->wave_ram[addr] >> 4) : (snd->wave_ram[addr] & 0xF)); +} + +static void voice_render_channel2(swan_sound_t *snd) { + if (snd->voice_volume & SND_VOL_CH2_RIGHT_FULL) { + snd->ch_output_right[1] = snd->volume[1]; + } else if (snd->voice_volume & SND_VOL_CH2_RIGHT_HALF) { + snd->ch_output_right[1] = snd->volume[1] >> 1; + } + if (snd->voice_volume & SND_VOL_CH2_LEFT_FULL) { + snd->ch_output_left[1] = snd->volume[1]; + } else if (snd->voice_volume & SND_VOL_CH2_LEFT_HALF) { + snd->ch_output_left[1] = snd->volume[1] >> 1; + } +} + +// See https://ws.nesdev.org/wiki/Sound +void swan_sound_tick(swan_sound_t *snd) { + // Update counters + swan_sound_subtick(snd, 128); + + // Update noise counter + if (snd->noise_ctrl & SND_NOISE_ENABLE) { + uint8_t lfsr_new = (1 ^ (snd->noise_lfsr >> 7) ^ (snd->noise_lfsr >> lfsr_tap_bits[snd->noise_ctrl & 7])) & 0x1; + snd->noise_lfsr = (snd->noise_lfsr << 1) | lfsr_new; + } + + // Calculate synthesizer channels + if (!(snd->test_flags & SND_TEST_HOLD_CH)) { + for (int i = 0; i < 4; i++) { + snd->ch_output_left[i] = 0; + snd->ch_output_right[i] = 0; + } + + if (snd->ch_ctrl & SND_CH1_ENABLE) { + wavetable_render_channel(snd, 0); + } + + if (snd->ch_ctrl & SND_CH2_ENABLE) { + if (snd->ch_ctrl & SND_CH2_VOICE) { + voice_render_channel2(snd); + } else { + wavetable_render_channel(snd, 1); + } + } + + if (snd->ch_ctrl & SND_CH3_ENABLE) { + wavetable_render_channel(snd, 2); + } + + if (snd->ch_ctrl & SND_CH4_ENABLE) { + if (snd->ch_ctrl & SND_CH4_NOISE) { + sample_render_channel(snd, 3, (snd->noise_lfsr & 1) * 15); + } else { + wavetable_render_channel(snd, 3); + } + } + } + + // Sum synthesizer channels + if (snd->test_flags & SND_TEST_CH_VOICE) { + if (snd->ch_ctrl & SND_CH2_VOICE) { + snd->synth_output_left = snd->ch_output_left[1] * 5; + snd->synth_output_right = snd->ch_output_right[1] * 5; + } else { + snd->synth_output_left = 0; + snd->synth_output_right = 0; + } + } else { + snd->synth_output_left = + snd->ch_output_left[0] + + snd->ch_output_left[1] + + snd->ch_output_left[2] + + snd->ch_output_left[3]; + + snd->synth_output_right = + snd->ch_output_right[0] + + snd->ch_output_right[1] + + snd->ch_output_right[2] + + snd->ch_output_right[3]; + } + + // Mix speaker output + snd->synth_output_mono = (snd->synth_output_left & 0x3FF) + (snd->synth_output_right & 0x3FF); + if (snd->out_ctrl & SND_OUT_SPEAKER_ENABLE) { + snd->output_speaker = (snd->synth_output_mono >> ((snd->out_ctrl >> 1) & 3)) & 0xFF; + } else { + snd->output_speaker = 0; + } + + // Mix headphone output + if (snd->out_ctrl & SND_OUT_HEADPHONES_ENABLE) { + snd->output_left = snd->synth_output_left << 5; + snd->output_right = snd->synth_output_right << 5; + if (snd->hyper_ctrl & HYPERV_ENABLE) { + snd->output_left += snd->hyper_output_left; + snd->output_right += snd->hyper_output_right; + } + } else { + snd->output_left = 0; + snd->output_right = 0; + } +} diff --git a/src/engine/platform/sound/swan.cpp b/src/engine/platform/sound/swan.cpp deleted file mode 100644 index 405198a2b..000000000 --- a/src/engine/platform/sound/swan.cpp +++ /dev/null @@ -1,363 +0,0 @@ -/******************************************************************************/ -/* 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.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.h b/src/engine/platform/sound/swan.h index 32fb37a82..23466c352 100644 --- a/src/engine/platform/sound/swan.h +++ b/src/engine/platform/sound/swan.h @@ -1,79 +1,92 @@ -/******************************************************************************/ -/* 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. +/* +WonderSwan sound core + +Note: Neither Sound DMA nor Hyper Voice DMA is implemented. + +Copyright (c) 2025 Adrian "asie" Siekierka + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. */ -#ifndef __WSWAN_SOUND_H -#define __WSWAN_SOUND_H +#ifndef _DRIVER_SWAN_H +#define _DRIVER_SWAN_H +#include #include -#include "blip_buf.h" -#include "../../dispatch.h" -class WSwan -{ -public: - int32_t SoundFlush(int16_t *SoundBuf, const int32_t MaxSoundFrames); +typedef struct swan_sound { + // Ports + uint16_t frequency[4]; + uint8_t volume[4]; + int8_t sweep_amount; + uint8_t sweep_ticks; + uint8_t noise_ctrl; + uint8_t wave_address; + uint8_t ch_ctrl; + uint8_t out_ctrl; + uint16_t noise_lfsr; + uint8_t voice_volume; + // TODO: Implement test flag bits 7, 6, 4, 3, 2 + uint8_t test_flags; - void SoundWrite(uint32_t, uint8_t); - uint8_t SoundRead(uint32_t); - void SoundReset(void); - void SoundCheckRAMWrite(uint32_t A); + uint8_t wave_ram[64]; - void SoundUpdate(); - void RAMWrite(uint32_t, uint8_t); + uint16_t hyper_ctrl; + + // State + uint8_t sample_index[4]; + uint32_t period_counter[4]; + uint32_t sweep_counter; + + // Outputs + /// Individual channel outputs (range: 0 .. 255) + int16_t ch_output_right[4]; + int16_t ch_output_left[4]; + + /// Hyper Voice outputs (range: -32768 .. 32767) + int16_t hyper_output_left; + int16_t hyper_output_right; + + /// Stereo synth outputs (range: 0 .. 1023) + uint16_t synth_output_right; + uint16_t synth_output_left; + + /// Mono synth output (range: 0 .. 2047) + uint16_t synth_output_mono; - int32_t sample_cache[4][2]; + /// Headphones output (range: -32768 .. 32767) + int16_t output_right; + int16_t output_left; - // Blip_Synth WaveSynth; + /// Internal speaker output (range: 0 .. 255) + uint8_t output_speaker; +} swan_sound_t; - // Blip_Buffer *sbuf[2] = { NULL }; - blip_buffer_t* sbuf[2]; - DivDispatchOscBuffer* oscBuf[4]; +#ifdef __cplusplus +extern "C" { +#endif - uint16_t period[4]; - uint8_t volume[4]; // left volume in upper 4 bits, right in lower 4 bits - uint8_t voice_volume; +void swan_sound_init(swan_sound_t *snd, bool headphones); +uint8_t swan_sound_in(swan_sound_t *snd, uint16_t port); +void swan_sound_out(swan_sound_t *snd, uint16_t port, uint8_t value); +void swan_sound_tick(swan_sound_t *snd); - 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]; -}; +#ifdef __cplusplus +} +#endif #endif diff --git a/src/engine/platform/swan.cpp b/src/engine/platform/swan.cpp index a7ef3ae06..9a0c2acfc 100644 --- a/src/engine/platform/swan.cpp +++ b/src/engine/platform/swan.cpp @@ -21,6 +21,7 @@ #include "../engine.h" #include "furIcons.h" #include "IconsFontAwesome4.h" +#include "sound/swan.h" #include #define rWrite(a,v) if (!skipRegisterWrites) {writes.push(QueuedWrite(a,v)); if (dumpWrites) {addWrite(a,v);}} @@ -39,12 +40,16 @@ const char* regCheatSheetWS[]={ "CH4_Vol", "0B", "Sweep_Value", "0C", "Sweep_Time", "0D", - "Noise", "0E", + "Noise_Ctrl", "0E", "Wave_Base", "0F", - "Ctrl", "10", - "Output", "11", - "Random", "12", - "Voice_Ctrl", "14", + "Channel_Ctrl", "10", + "Output_Ctrl", "11", + "Noise_Random", "12", + "CH2_Voice_Vol", "14", + "Test", "15", + "Output_Right", "16", + "Output_Left", "18", + "Output_Mono", "1A", "Wave_Mem", "40", NULL }; @@ -53,35 +58,15 @@ const char** DivPlatformSwan::getRegisterSheet() { return regCheatSheetWS; } -void DivPlatformSwan::acquireDirect(blip_buffer_t** bb, size_t len) { +void DivPlatformSwan::acquire(short** buf, size_t len) { for (int i=0; i<4; i++) { oscBuf[i]->begin(len); - ws->oscBuf[i]=oscBuf[i]; } - ws->sbuf[0]=bb[0]; - ws->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) { @@ -99,25 +84,32 @@ void DivPlatformSwan::acquireDirect(blip_buffer_t** bb, size_t len) { } } - h+=pcmAdvance-1; - - // the rest + // Register writes while (!writes.empty()) { QueuedWrite w=writes.front(); - regPool[w.addr]=w.val; - if (w.addr<0x40) { - ws->SoundWrite(w.addr|0x80,w.val); + if (w.addr < 0x40) { + swan_sound_out(&ws, w.addr|0x80, w.val); } else { - ws->SoundCheckRAMWrite(w.addr&0x3f); - ws->RAMWrite(w.addr&0x3f,w.val); + ws.wave_ram[w.addr & 0x3f] = w.val; + regPool[w.addr]=w.val; } writes.pop(); } - } - ws->v30mz_timestamp=len; - ws->SoundUpdate(); - ws->SoundFlush(NULL,0); + swan_sound_tick(&ws); + + // Update individual channel buffers + for (int i = 0; i < 4; i++) { + if (isMuted[i]) { + oscBuf[i]->putSample(h, 0); + } else { + oscBuf[i]->putSample(h, (ws.ch_output_left[i] + ws.ch_output_right[i]) << 5); + } + } + + buf[0][h] = ws.output_left; + buf[1][h] = ws.output_right; + } for (int i=0; i<4; i++) { oscBuf[i]->end(len); @@ -564,9 +556,9 @@ DivDispatchOscBuffer* DivPlatformSwan::getOscBuffer(int ch) { } unsigned char* DivPlatformSwan::getRegisterPool() { - // get Random from emulator - regPool[0x12]=ws->SoundRead(0x92); - regPool[0x13]=ws->SoundRead(0x93); + for (int i = 0; i < 0x20; i++) { + regPool[i] = swan_sound_in(&ws, i | 0x80); + } return regPool; } @@ -577,7 +569,7 @@ int DivPlatformSwan::getRegisterPoolSize() { void DivPlatformSwan::reset() { while (!writes.empty()) writes.pop(); while (!postDACWrites.empty()) postDACWrites.pop(); - memset(regPool,0,128); + memset(regPool,0,64); for (int i=0; i<4; i++) { chan[i]=Channel(); chan[i].vol=15; @@ -590,7 +582,7 @@ void DivPlatformSwan::reset() { if (dumpWrites) { addWrite(0xffffffff,0); } - ws->SoundReset(); + swan_sound_init(&ws, true); pcm=false; sweep=false; furnaceDac=false; @@ -601,18 +593,13 @@ void DivPlatformSwan::reset() { dacPos=0; dacSample=-1; sampleBank=0; - rWrite(0x0f,0x00); // wave table at 0x0000 - rWrite(0x11,0x09); // enable speakers + rWrite(0x11,0x0f); // enable speakers, minimum headphone volume } 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) { @@ -639,7 +626,7 @@ void DivPlatformSwan::poke(std::vector& wlist) { void DivPlatformSwan::setFlags(const DivConfig& flags) { chipClock=3072000; CHECK_CUSTOM_CLOCK; - rate=chipClock; + rate=chipClock/128; for (int i=0; i<4; i++) { oscBuf[i]->setRate(rate); } @@ -653,7 +640,7 @@ int DivPlatformSwan::init(DivEngine* p, int channels, int sugRate, const DivConf isMuted[i]=false; oscBuf[i]=new DivDispatchOscBuffer; } - ws=new WSwan(); + swan_sound_init(&ws, true); setFlags(flags); reset(); return 4; @@ -663,7 +650,6 @@ void DivPlatformSwan::quit() { for (int i=0; i<4; i++) { delete oscBuf[i]; } - delete ws; } DivPlatformSwan::~DivPlatformSwan() { diff --git a/src/engine/platform/swan.h b/src/engine/platform/swan.h index 095b338aa..d4de0f061 100644 --- a/src/engine/platform/swan.h +++ b/src/engine/platform/swan.h @@ -53,12 +53,14 @@ class DivPlatformSwan: public DivDispatch { }; FixedQueue writes; FixedQueue postDACWrites; - WSwan* ws; + + swan_sound_t ws; + 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); DivMacroInt* getChanMacroInt(int ch); @@ -75,7 +77,6 @@ class DivPlatformSwan: public DivDispatch { 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();