swan: Rewritten audio driver

Now outputs 24000 Hz digital samples, matching real hardware (as
opposed to 3072000 Hz samples). It has also been rewritten from
scratch to match recent research and make the code significantly
more readable.
This commit is contained in:
Adrian Siekierka 2025-03-08 19:43:59 +01:00 committed by tildearrow
parent a02d289d49
commit 03b87258c8
6 changed files with 382 additions and 486 deletions

View file

@ -629,7 +629,7 @@ src/engine/platform/sound/pokey/AltASAP.cpp
src/engine/platform/sound/qsound.c src/engine/platform/sound/qsound.c
src/engine/platform/sound/swan.cpp src/engine/platform/sound/swan.c
src/engine/platform/sound/su.cpp src/engine/platform/sound/su.cpp

View file

@ -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 <stdio.h>
#include <string.h>
#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;
}
}

View file

@ -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 <string.h>
#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;
}

View file

@ -1,79 +1,92 @@
/******************************************************************************/ /*
/* Mednafen - Multi-system Emulator */ WonderSwan sound core
/******************************************************************************/
/* sound.h - WonderSwan Sound Emulation Note: Neither Sound DMA nor Hyper Voice DMA is implemented.
** Copyright (C) 2007-2016 Mednafen Team
** Copyright (c) 2025 Adrian "asie" Siekierka
** This program is free software; you can redistribute it and/or
** modify it under the terms of the GNU General Public License This software is provided 'as-is', without any express or implied
** as published by the Free Software Foundation; either version 2 warranty. In no event will the authors be held liable for any damages
** of the License, or (at your option) any later version. arising from the use of this software.
**
** This program is distributed in the hope that it will be useful, Permission is granted to anyone to use this software for any purpose,
** but WITHOUT ANY WARRANTY; without even the implied warranty of including commercial applications, and to alter it and redistribute it
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the freely, subject to the following restrictions:
** GNU General Public License for more details.
** 1. The origin of this software must not be misrepresented; you must not
** You should have received a copy of the GNU General Public License claim that you wrote the original software. If you use this software
** along with this program; if not, write to the Free Software Foundation, Inc., in a product, an acknowledgment in the product documentation would be
** 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 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 #ifndef _DRIVER_SWAN_H
#define __WSWAN_SOUND_H #define _DRIVER_SWAN_H
#include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include "blip_buf.h"
#include "../../dispatch.h"
class WSwan typedef struct swan_sound {
{ // Ports
public: uint16_t frequency[4];
int32_t SoundFlush(int16_t *SoundBuf, const int32_t MaxSoundFrames); uint8_t volume[4];
int8_t sweep_amount;
void SoundWrite(uint32_t, uint8_t); uint8_t sweep_ticks;
uint8_t SoundRead(uint32_t); uint8_t noise_ctrl;
void SoundReset(void); uint8_t wave_address;
void SoundCheckRAMWrite(uint32_t A); uint8_t ch_ctrl;
uint8_t out_ctrl;
void SoundUpdate(); uint16_t noise_lfsr;
void RAMWrite(uint32_t, uint8_t);
int32_t sample_cache[4][2];
// Blip_Synth<blip_good_quality, 4096> 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 voice_volume;
// TODO: Implement test flag bits 7, 6, 4, 3, 2
uint8_t test_flags;
uint8_t sweep_step, sweep_value; uint8_t wave_ram[64];
uint8_t noise_control;
uint8_t control;
uint8_t output_control;
int32_t sweep_8192_divider; uint16_t hyper_ctrl;
uint8_t sweep_counter;
uint8_t SampleRAMPos;
int32_t last_v_val; // State
uint8_t sample_index[4];
uint32_t period_counter[4];
uint32_t sweep_counter;
uint8_t HyperVoice; // Outputs
int32_t last_hv_val[2]; /// Individual channel outputs (range: 0 .. 255)
uint8_t HVoiceCtrl, HVoiceChanCtrl; int16_t ch_output_right[4];
int16_t ch_output_left[4];
int32_t period_counter[4]; /// Hyper Voice outputs (range: -32768 .. 32767)
int32_t last_val[4][2]; // Last outputted value, l&r int16_t hyper_output_left;
uint8_t sample_pos[4]; int16_t hyper_output_right;
uint16_t nreg;
uint32_t last_ts;
uint32_t v30mz_timestamp;
uint8_t wsRAM[64]; /// 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;
/// Headphones output (range: -32768 .. 32767)
int16_t output_right;
int16_t output_left;
/// Internal speaker output (range: 0 .. 255)
uint8_t output_speaker;
} swan_sound_t;
#ifdef __cplusplus
extern "C" {
#endif
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);
#ifdef __cplusplus
}
#endif
#endif #endif

View file

@ -21,6 +21,7 @@
#include "../engine.h" #include "../engine.h"
#include "furIcons.h" #include "furIcons.h"
#include "IconsFontAwesome4.h" #include "IconsFontAwesome4.h"
#include "sound/swan.h"
#include <math.h> #include <math.h>
#define rWrite(a,v) if (!skipRegisterWrites) {writes.push(QueuedWrite(a,v)); if (dumpWrites) {addWrite(a,v);}} #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", "CH4_Vol", "0B",
"Sweep_Value", "0C", "Sweep_Value", "0C",
"Sweep_Time", "0D", "Sweep_Time", "0D",
"Noise", "0E", "Noise_Ctrl", "0E",
"Wave_Base", "0F", "Wave_Base", "0F",
"Ctrl", "10", "Channel_Ctrl", "10",
"Output", "11", "Output_Ctrl", "11",
"Random", "12", "Noise_Random", "12",
"Voice_Ctrl", "14", "CH2_Voice_Vol", "14",
"Test", "15",
"Output_Right", "16",
"Output_Left", "18",
"Output_Mono", "1A",
"Wave_Mem", "40", "Wave_Mem", "40",
NULL NULL
}; };
@ -53,35 +58,15 @@ const char** DivPlatformSwan::getRegisterSheet() {
return regCheatSheetWS; 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++) { for (int i=0; i<4; i++) {
oscBuf[i]->begin(len); oscBuf[i]->begin(len);
ws->oscBuf[i]=oscBuf[i];
} }
ws->sbuf[0]=bb[0];
ws->sbuf[1]=bb[1];
for (size_t h=0; h<len; h++) { for (size_t h=0; h<len; h++) {
ws->v30mz_timestamp=h; // PCM
// 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<pcmAdvance) pcmAdvance=remainTime;
if (remainTime<1) pcmAdvance=1;
}
}
}
// PCM part
if (pcm && dacSample!=-1) { if (pcm && dacSample!=-1) {
dacPeriod+=dacRate*pcmAdvance; dacPeriod+=dacRate;
while (dacPeriod>=rate) { while (dacPeriod>=rate) {
DivSample* s=parent->getSample(dacSample); DivSample* s=parent->getSample(dacSample);
if (s->samples<=0 || dacPos>=s->samples) { if (s->samples<=0 || dacPos>=s->samples) {
@ -99,25 +84,32 @@ void DivPlatformSwan::acquireDirect(blip_buffer_t** bb, size_t len) {
} }
} }
h+=pcmAdvance-1; // Register writes
// the rest
while (!writes.empty()) { while (!writes.empty()) {
QueuedWrite w=writes.front(); QueuedWrite w=writes.front();
regPool[w.addr]=w.val;
if (w.addr < 0x40) { if (w.addr < 0x40) {
ws->SoundWrite(w.addr|0x80,w.val); swan_sound_out(&ws, w.addr|0x80, w.val);
} else { } else {
ws->SoundCheckRAMWrite(w.addr&0x3f); ws.wave_ram[w.addr & 0x3f] = w.val;
ws->RAMWrite(w.addr&0x3f,w.val); regPool[w.addr]=w.val;
} }
writes.pop(); writes.pop();
} }
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);
}
} }
ws->v30mz_timestamp=len; buf[0][h] = ws.output_left;
ws->SoundUpdate(); buf[1][h] = ws.output_right;
ws->SoundFlush(NULL,0); }
for (int i=0; i<4; i++) { for (int i=0; i<4; i++) {
oscBuf[i]->end(len); oscBuf[i]->end(len);
@ -564,9 +556,9 @@ DivDispatchOscBuffer* DivPlatformSwan::getOscBuffer(int ch) {
} }
unsigned char* DivPlatformSwan::getRegisterPool() { unsigned char* DivPlatformSwan::getRegisterPool() {
// get Random from emulator for (int i = 0; i < 0x20; i++) {
regPool[0x12]=ws->SoundRead(0x92); regPool[i] = swan_sound_in(&ws, i | 0x80);
regPool[0x13]=ws->SoundRead(0x93); }
return regPool; return regPool;
} }
@ -577,7 +569,7 @@ int DivPlatformSwan::getRegisterPoolSize() {
void DivPlatformSwan::reset() { void DivPlatformSwan::reset() {
while (!writes.empty()) writes.pop(); while (!writes.empty()) writes.pop();
while (!postDACWrites.empty()) postDACWrites.pop(); while (!postDACWrites.empty()) postDACWrites.pop();
memset(regPool,0,128); memset(regPool,0,64);
for (int i=0; i<4; i++) { for (int i=0; i<4; i++) {
chan[i]=Channel(); chan[i]=Channel();
chan[i].vol=15; chan[i].vol=15;
@ -590,7 +582,7 @@ void DivPlatformSwan::reset() {
if (dumpWrites) { if (dumpWrites) {
addWrite(0xffffffff,0); addWrite(0xffffffff,0);
} }
ws->SoundReset(); swan_sound_init(&ws, true);
pcm=false; pcm=false;
sweep=false; sweep=false;
furnaceDac=false; furnaceDac=false;
@ -601,18 +593,13 @@ void DivPlatformSwan::reset() {
dacPos=0; dacPos=0;
dacSample=-1; dacSample=-1;
sampleBank=0; sampleBank=0;
rWrite(0x0f,0x00); // wave table at 0x0000 rWrite(0x11,0x0f); // enable speakers, minimum headphone volume
rWrite(0x11,0x09); // enable speakers
} }
int DivPlatformSwan::getOutputCount() { int DivPlatformSwan::getOutputCount() {
return 2; return 2;
} }
bool DivPlatformSwan::hasAcquireDirect() {
return true;
}
void DivPlatformSwan::notifyWaveChange(int wave) { void DivPlatformSwan::notifyWaveChange(int wave) {
for (int i=0; i<4; i++) { for (int i=0; i<4; i++) {
if (chan[i].wave==wave) { if (chan[i].wave==wave) {
@ -639,7 +626,7 @@ void DivPlatformSwan::poke(std::vector<DivRegWrite>& wlist) {
void DivPlatformSwan::setFlags(const DivConfig& flags) { void DivPlatformSwan::setFlags(const DivConfig& flags) {
chipClock=3072000; chipClock=3072000;
CHECK_CUSTOM_CLOCK; CHECK_CUSTOM_CLOCK;
rate=chipClock; rate=chipClock/128;
for (int i=0; i<4; i++) { for (int i=0; i<4; i++) {
oscBuf[i]->setRate(rate); oscBuf[i]->setRate(rate);
} }
@ -653,7 +640,7 @@ int DivPlatformSwan::init(DivEngine* p, int channels, int sugRate, const DivConf
isMuted[i]=false; isMuted[i]=false;
oscBuf[i]=new DivDispatchOscBuffer; oscBuf[i]=new DivDispatchOscBuffer;
} }
ws=new WSwan(); swan_sound_init(&ws, true);
setFlags(flags); setFlags(flags);
reset(); reset();
return 4; return 4;
@ -663,7 +650,6 @@ void DivPlatformSwan::quit() {
for (int i=0; i<4; i++) { for (int i=0; i<4; i++) {
delete oscBuf[i]; delete oscBuf[i];
} }
delete ws;
} }
DivPlatformSwan::~DivPlatformSwan() { DivPlatformSwan::~DivPlatformSwan() {

View file

@ -53,12 +53,14 @@ class DivPlatformSwan: public DivDispatch {
}; };
FixedQueue<QueuedWrite,256> writes; FixedQueue<QueuedWrite,256> writes;
FixedQueue<DivRegWrite,2048> postDACWrites; FixedQueue<DivRegWrite,2048> postDACWrites;
WSwan* ws;
swan_sound_t ws;
void updateWave(int ch); void updateWave(int ch);
friend void putDispatchChip(void*,int); friend void putDispatchChip(void*,int);
friend void putDispatchChan(void*,int,int); friend void putDispatchChan(void*,int,int);
public: public:
void acquireDirect(blip_buffer_t** bb, size_t len); void acquire(short** buf, size_t len);
int dispatch(DivCommand c); int dispatch(DivCommand c);
void* getChanState(int chan); void* getChanState(int chan);
DivMacroInt* getChanMacroInt(int ch); DivMacroInt* getChanMacroInt(int ch);
@ -75,7 +77,6 @@ class DivPlatformSwan: public DivDispatch {
void notifyWaveChange(int wave); void notifyWaveChange(int wave);
void notifyInsDeletion(void* ins); void notifyInsDeletion(void* ins);
int getOutputCount(); int getOutputCount();
bool hasAcquireDirect();
void poke(unsigned int addr, unsigned short val); void poke(unsigned int addr, unsigned short val);
void poke(std::vector<DivRegWrite>& wlist); void poke(std::vector<DivRegWrite>& wlist);
const char** getRegisterSheet(); const char** getRegisterSheet();