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/swan.cpp
src/engine/platform/sound/swan.c
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 */
/******************************************************************************/
/* 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 <stdbool.h>
#include <stdint.h>
#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;
int32_t sample_cache[4][2];
// State
uint8_t sample_index[4];
uint32_t period_counter[4];
uint32_t sweep_counter;
// Blip_Synth<blip_good_quality, 4096> WaveSynth;
// Outputs
/// Individual channel outputs (range: 0 .. 255)
int16_t ch_output_right[4];
int16_t ch_output_left[4];
// Blip_Buffer *sbuf[2] = { NULL };
blip_buffer_t* sbuf[2];
DivDispatchOscBuffer* oscBuf[4];
/// Hyper Voice outputs (range: -32768 .. 32767)
int16_t hyper_output_left;
int16_t hyper_output_right;
uint16_t period[4];
uint8_t volume[4]; // left volume in upper 4 bits, right in lower 4 bits
uint8_t voice_volume;
/// Stereo synth outputs (range: 0 .. 1023)
uint16_t synth_output_right;
uint16_t synth_output_left;
uint8_t sweep_step, sweep_value;
uint8_t noise_control;
uint8_t control;
uint8_t output_control;
/// Mono synth output (range: 0 .. 2047)
uint16_t synth_output_mono;
int32_t sweep_8192_divider;
uint8_t sweep_counter;
uint8_t SampleRAMPos;
/// Headphones output (range: -32768 .. 32767)
int16_t output_right;
int16_t output_left;
int32_t last_v_val;
/// Internal speaker output (range: 0 .. 255)
uint8_t output_speaker;
} swan_sound_t;
uint8_t HyperVoice;
int32_t last_hv_val[2];
uint8_t HVoiceCtrl, HVoiceChanCtrl;
#ifdef __cplusplus
extern "C" {
#endif
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;
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 wsRAM[64];
};
#ifdef __cplusplus
}
#endif
#endif

View file

@ -21,6 +21,7 @@
#include "../engine.h"
#include "furIcons.h"
#include "IconsFontAwesome4.h"
#include "sound/swan.h"
#include <math.h>
#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; h<len; h++) {
ws->v30mz_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<pcmAdvance) pcmAdvance=remainTime;
if (remainTime<1) pcmAdvance=1;
}
}
}
// PCM part
// PCM
if (pcm && dacSample!=-1) {
dacPeriod+=dacRate*pcmAdvance;
dacPeriod+=dacRate;
while (dacPeriod>=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<DivRegWrite>& 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() {

View file

@ -53,12 +53,14 @@ class DivPlatformSwan: public DivDispatch {
};
FixedQueue<QueuedWrite,256> writes;
FixedQueue<DivRegWrite,2048> 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<DivRegWrite>& wlist);
const char** getRegisterSheet();