457 lines
11 KiB
C++
457 lines
11 KiB
C++
#include <string.h>
|
|
|
|
#include "../Util.h"
|
|
#include "../gba/Sound.h"
|
|
#include "gb.h"
|
|
#include "gbGlobals.h"
|
|
#include "gbSound.h"
|
|
|
|
#include "../apu/Effects_Buffer.h"
|
|
#include "../apu/Gb_Apu.h"
|
|
|
|
extern long soundSampleRate; // current sound quality
|
|
|
|
gb_effects_config_t gb_effects_config = { false, 0.20f, 0.15f, false };
|
|
|
|
static gb_effects_config_t gb_effects_config_current;
|
|
static Simple_Effects_Buffer* stereo_buffer;
|
|
static Gb_Apu* gb_apu;
|
|
|
|
static float soundVolume_ = -1;
|
|
static int prevSoundEnable = -1;
|
|
static bool declicking = false;
|
|
|
|
int const chan_count = 4;
|
|
int const ticks_to_time = 2 * GB_APU_OVERCLOCK;
|
|
|
|
static inline blip_time_t blip_time()
|
|
{
|
|
return (SOUND_CLOCK_TICKS - soundTicks) * ticks_to_time;
|
|
}
|
|
|
|
uint8_t gbSoundRead(uint16_t address)
|
|
{
|
|
if (gb_apu && address >= NR10 && address <= 0xFF3F)
|
|
return gb_apu->read_register(blip_time(), address);
|
|
|
|
return gbMemory[address];
|
|
}
|
|
|
|
void gbSoundEvent(register uint16_t address, register int data)
|
|
{
|
|
gbMemory[address] = data;
|
|
|
|
if (gb_apu && address >= NR10 && address <= 0xFF3F)
|
|
gb_apu->write_register(blip_time(), address, data);
|
|
}
|
|
|
|
static void end_frame(blip_time_t time)
|
|
{
|
|
gb_apu->end_frame(time);
|
|
stereo_buffer->end_frame(time);
|
|
}
|
|
|
|
static void apply_effects()
|
|
{
|
|
prevSoundEnable = soundGetEnable();
|
|
gb_effects_config_current = gb_effects_config;
|
|
|
|
stereo_buffer->config().enabled = gb_effects_config_current.enabled;
|
|
stereo_buffer->config().echo = gb_effects_config_current.echo;
|
|
stereo_buffer->config().stereo = gb_effects_config_current.stereo;
|
|
stereo_buffer->config().surround = gb_effects_config_current.surround;
|
|
stereo_buffer->apply_config();
|
|
|
|
for (int i = 0; i < chan_count; i++) {
|
|
Multi_Buffer::channel_t ch = { 0, 0, 0 };
|
|
if (prevSoundEnable >> i & 1)
|
|
ch = stereo_buffer->channel(i);
|
|
gb_apu->set_output(ch.center, ch.left, ch.right, i);
|
|
}
|
|
}
|
|
|
|
void gbSoundConfigEffects(gb_effects_config_t const& c)
|
|
{
|
|
gb_effects_config = c;
|
|
}
|
|
|
|
static void apply_volume()
|
|
{
|
|
soundVolume_ = soundGetVolume();
|
|
|
|
if (gb_apu)
|
|
gb_apu->volume(soundVolume_);
|
|
}
|
|
|
|
void gbSoundTick()
|
|
{
|
|
if (gb_apu && stereo_buffer) {
|
|
// Run sound hardware to present
|
|
end_frame(SOUND_CLOCK_TICKS * ticks_to_time);
|
|
|
|
flush_samples(stereo_buffer);
|
|
|
|
// Update effects config if it was changed
|
|
if (memcmp(&gb_effects_config_current, &gb_effects_config,
|
|
sizeof gb_effects_config)
|
|
|| soundGetEnable() != prevSoundEnable)
|
|
apply_effects();
|
|
|
|
if (soundVolume_ != soundGetVolume())
|
|
apply_volume();
|
|
}
|
|
}
|
|
|
|
static void reset_apu()
|
|
{
|
|
Gb_Apu::mode_t mode = Gb_Apu::mode_dmg;
|
|
if (gbHardware & 2)
|
|
mode = Gb_Apu::mode_cgb;
|
|
if (gbHardware & 8 || declicking)
|
|
mode = Gb_Apu::mode_agb;
|
|
gb_apu->reset(mode);
|
|
gb_apu->reduce_clicks(declicking);
|
|
|
|
if (stereo_buffer)
|
|
stereo_buffer->clear();
|
|
|
|
soundTicks = SOUND_CLOCK_TICKS;
|
|
}
|
|
|
|
static void remake_stereo_buffer()
|
|
{
|
|
// APU
|
|
if (!gb_apu) {
|
|
gb_apu = new Gb_Apu; // TODO: handle errors
|
|
reset_apu();
|
|
}
|
|
|
|
// Stereo_Buffer
|
|
delete stereo_buffer;
|
|
stereo_buffer = 0;
|
|
|
|
stereo_buffer = new Simple_Effects_Buffer; // TODO: handle out of memory
|
|
if (stereo_buffer->set_sample_rate(soundSampleRate)) {
|
|
} // TODO: handle out of memory
|
|
stereo_buffer->clock_rate(gb_apu->clock_rate);
|
|
|
|
// Multi_Buffer
|
|
static int const chan_types[chan_count] = {
|
|
Multi_Buffer::wave_type + 1, Multi_Buffer::wave_type + 2,
|
|
Multi_Buffer::wave_type + 3, Multi_Buffer::mixed_type + 1
|
|
};
|
|
if (stereo_buffer->set_channel_count(chan_count, chan_types)) {
|
|
} // TODO: handle errors
|
|
|
|
// Volume Level
|
|
apply_effects();
|
|
apply_volume();
|
|
}
|
|
|
|
void gbSoundSetDeclicking(bool enable)
|
|
{
|
|
if (declicking != enable) {
|
|
declicking = enable;
|
|
if (gb_apu) {
|
|
// Can't change sound hardware mode without resetting APU, so save/load
|
|
// state around mode change
|
|
gb_apu_state_t state;
|
|
gb_apu->save_state(&state);
|
|
reset_apu();
|
|
if (gb_apu->load_state(state)) {
|
|
} // ignore error
|
|
}
|
|
}
|
|
}
|
|
|
|
bool gbSoundGetDeclicking()
|
|
{
|
|
return declicking;
|
|
}
|
|
|
|
void gbSoundReset()
|
|
{
|
|
SOUND_CLOCK_TICKS = 20000; // 1/100 second
|
|
|
|
remake_stereo_buffer();
|
|
reset_apu();
|
|
|
|
soundPaused = 1;
|
|
|
|
gbSoundEvent(0xff10, 0x80);
|
|
gbSoundEvent(0xff11, 0xbf);
|
|
gbSoundEvent(0xff12, 0xf3);
|
|
gbSoundEvent(0xff14, 0xbf);
|
|
gbSoundEvent(0xff16, 0x3f);
|
|
gbSoundEvent(0xff17, 0x00);
|
|
gbSoundEvent(0xff19, 0xbf);
|
|
|
|
gbSoundEvent(0xff1a, 0x7f);
|
|
gbSoundEvent(0xff1b, 0xff);
|
|
gbSoundEvent(0xff1c, 0xbf);
|
|
gbSoundEvent(0xff1e, 0xbf);
|
|
|
|
gbSoundEvent(0xff20, 0xff);
|
|
gbSoundEvent(0xff21, 0x00);
|
|
gbSoundEvent(0xff22, 0x00);
|
|
gbSoundEvent(0xff23, 0xbf);
|
|
gbSoundEvent(0xff24, 0x77);
|
|
gbSoundEvent(0xff25, 0xf3);
|
|
|
|
if (gbHardware & 0x4)
|
|
gbSoundEvent(0xff26, 0xf0);
|
|
else
|
|
gbSoundEvent(0xff26, 0xf1);
|
|
|
|
/* workaround for game Beetlejuice */
|
|
if (gbHardware & 0x1) {
|
|
gbSoundEvent(0xff24, 0x77);
|
|
gbSoundEvent(0xff25, 0xf3);
|
|
}
|
|
|
|
int addr = 0xff30;
|
|
|
|
while (addr < 0xff40) {
|
|
gbMemory[addr++] = 0x00;
|
|
gbMemory[addr++] = 0xff;
|
|
}
|
|
}
|
|
|
|
void gbSoundSetSampleRate(long sampleRate)
|
|
{
|
|
if (soundSampleRate != sampleRate) {
|
|
if (systemCanChangeSoundQuality()) {
|
|
soundShutdown();
|
|
soundSampleRate = sampleRate;
|
|
soundInit();
|
|
} else {
|
|
soundSampleRate = sampleRate;
|
|
}
|
|
|
|
remake_stereo_buffer();
|
|
}
|
|
}
|
|
|
|
static struct {
|
|
int version;
|
|
gb_apu_state_t apu;
|
|
} state;
|
|
|
|
static char dummy_state[735 * 2];
|
|
|
|
#define SKIP(type, name) \
|
|
{ \
|
|
dummy_state, sizeof(type) \
|
|
}
|
|
|
|
#define LOAD(type, name) \
|
|
{ \
|
|
&name, sizeof(type) \
|
|
}
|
|
|
|
// Old save state support
|
|
|
|
static variable_desc gbsound_format[] = {
|
|
SKIP(int, soundPaused),
|
|
SKIP(int, soundPlay),
|
|
SKIP(int, soundTicks),
|
|
SKIP(int, SOUND_CLOCK_TICKS),
|
|
SKIP(int, soundLevel1),
|
|
SKIP(int, soundLevel2),
|
|
SKIP(int, soundBalance),
|
|
SKIP(int, soundMasterOn),
|
|
SKIP(int, soundIndex),
|
|
SKIP(int, soundVIN),
|
|
SKIP(int, soundOn[0]),
|
|
SKIP(int, soundATL[0]),
|
|
SKIP(int, sound1Skip),
|
|
SKIP(int, soundIndex[0]),
|
|
SKIP(int, sound1Continue),
|
|
SKIP(int, soundEnvelopeVolume[0]),
|
|
SKIP(int, soundEnvelopeATL[0]),
|
|
SKIP(int, sound1EnvelopeATLReload),
|
|
SKIP(int, sound1EnvelopeUpDown),
|
|
SKIP(int, sound1SweepATL),
|
|
SKIP(int, sound1SweepATLReload),
|
|
SKIP(int, sound1SweepSteps),
|
|
SKIP(int, sound1SweepUpDown),
|
|
SKIP(int, sound1SweepStep),
|
|
SKIP(int, soundOn[1]),
|
|
SKIP(int, soundATL[1]),
|
|
SKIP(int, sound2Skip),
|
|
SKIP(int, soundIndex[1]),
|
|
SKIP(int, sound2Continue),
|
|
SKIP(int, soundEnvelopeVolume[1]),
|
|
SKIP(int, soundEnvelopeATL[1]),
|
|
SKIP(int, sound2EnvelopeATLReload),
|
|
SKIP(int, sound2EnvelopeUpDown),
|
|
SKIP(int, soundOn[2]),
|
|
SKIP(int, soundATL[2]),
|
|
SKIP(int, sound3Skip),
|
|
SKIP(int, soundIndex[2]),
|
|
SKIP(int, sound3Continue),
|
|
SKIP(int, sound3OutputLevel),
|
|
SKIP(int, soundOn[3]),
|
|
SKIP(int, soundATL[3]),
|
|
SKIP(int, sound4Skip),
|
|
SKIP(int, soundIndex[3]),
|
|
SKIP(int, sound4Clock),
|
|
SKIP(int, sound4ShiftRight),
|
|
SKIP(int, sound4ShiftSkip),
|
|
SKIP(int, sound4ShiftIndex),
|
|
SKIP(int, sound4NSteps),
|
|
SKIP(int, sound4CountDown),
|
|
SKIP(int, sound4Continue),
|
|
SKIP(int, soundEnvelopeVolume[2]),
|
|
SKIP(int, soundEnvelopeATL[2]),
|
|
SKIP(int, sound4EnvelopeATLReload),
|
|
SKIP(int, sound4EnvelopeUpDown),
|
|
SKIP(int, soundEnableFlag),
|
|
{ NULL, 0 }
|
|
};
|
|
|
|
static variable_desc gbsound_format2[] = {
|
|
SKIP(int, sound1ATLreload),
|
|
SKIP(int, freq1low),
|
|
SKIP(int, freq1high),
|
|
SKIP(int, sound2ATLreload),
|
|
SKIP(int, freq2low),
|
|
SKIP(int, freq2high),
|
|
SKIP(int, sound3ATLreload),
|
|
SKIP(int, freq3low),
|
|
SKIP(int, freq3high),
|
|
SKIP(int, sound4ATLreload),
|
|
SKIP(int, freq4),
|
|
{ NULL, 0 }
|
|
};
|
|
|
|
static variable_desc gbsound_format3[] = {
|
|
SKIP(uint8_t[2 * 735], soundBuffer),
|
|
SKIP(uint8_t[2 * 735], soundBuffer),
|
|
SKIP(uint16_t[735], soundFinalWave),
|
|
{ NULL, 0 }
|
|
};
|
|
|
|
enum {
|
|
nr10 = 0,
|
|
nr11,
|
|
nr12,
|
|
nr13,
|
|
nr14,
|
|
nr20,
|
|
nr21,
|
|
nr22,
|
|
nr23,
|
|
nr24,
|
|
nr30,
|
|
nr31,
|
|
nr32,
|
|
nr33,
|
|
nr34,
|
|
nr40,
|
|
nr41,
|
|
nr42,
|
|
nr43,
|
|
nr44,
|
|
nr50,
|
|
nr51,
|
|
nr52
|
|
};
|
|
|
|
static void gbSoundReadGameOld(int version, gzFile gzFile)
|
|
{
|
|
if (version == 11) {
|
|
// Version 11 didn't save any state
|
|
// TODO: same for version 10?
|
|
state.apu.regs[nr50] = 0x77; // volume at max
|
|
state.apu.regs[nr51] = 0xFF; // channels enabled
|
|
state.apu.regs[nr52] = 0x80; // power on
|
|
return;
|
|
}
|
|
|
|
// Load state
|
|
utilReadData(gzFile, gbsound_format);
|
|
|
|
if (version >= 11) // TODO: never executed; remove?
|
|
utilReadData(gzFile, gbsound_format2);
|
|
|
|
utilReadData(gzFile, gbsound_format3);
|
|
|
|
int quality = 1;
|
|
if (version >= 7)
|
|
quality = utilReadInt(gzFile);
|
|
|
|
gbSoundSetSampleRate(44100 / quality);
|
|
|
|
// Convert to format Gb_Apu uses
|
|
gb_apu_state_t& s = state.apu;
|
|
|
|
// Only some registers are properly preserved
|
|
static int const regs_to_copy[] = {
|
|
nr10, nr11, nr12, nr21, nr22, nr30, nr32, nr42, nr43, nr50, nr51, nr52, -1
|
|
};
|
|
for (int i = 0; regs_to_copy[i] >= 0; i++)
|
|
s.regs[regs_to_copy[i]] = gbMemory[0xFF10 + regs_to_copy[i]];
|
|
|
|
memcpy(&s.regs[0x20], &gbMemory[0xFF30], 0x10); // wave
|
|
}
|
|
|
|
// New state format
|
|
|
|
static variable_desc gb_state[] = {
|
|
LOAD(int, state.version), // room_for_expansion will be used by later versions
|
|
|
|
// APU
|
|
LOAD(uint8_t[0x40], state.apu.regs), // last values written to registers and wave RAM (both banks)
|
|
LOAD(int, state.apu.frame_time), // clocks until next frame sequencer action
|
|
LOAD(int, state.apu.frame_phase), // next step frame sequencer will run
|
|
|
|
LOAD(int, state.apu.sweep_freq), // sweep's internal frequency register
|
|
LOAD(int, state.apu.sweep_delay), // clocks until next sweep action
|
|
LOAD(int, state.apu.sweep_enabled),
|
|
LOAD(int, state.apu.sweep_neg), // obscure internal flag
|
|
LOAD(int, state.apu.noise_divider),
|
|
LOAD(int, state.apu.wave_buf), // last read byte of wave RAM
|
|
|
|
LOAD(int[4], state.apu.delay), // clocks until next channel action
|
|
LOAD(int[4], state.apu.length_ctr),
|
|
LOAD(int[4], state.apu.phase), // square/wave phase, noise LFSR
|
|
LOAD(int[4], state.apu.enabled), // internal enabled flag
|
|
|
|
LOAD(int[3], state.apu.env_delay), // clocks until next envelope action
|
|
LOAD(int[3], state.apu.env_volume),
|
|
LOAD(int[3], state.apu.env_enabled),
|
|
|
|
SKIP(int[13], room_for_expansion),
|
|
|
|
// Emulator
|
|
SKIP(int[16], room_for_expansion),
|
|
|
|
{ NULL, 0 }
|
|
};
|
|
|
|
void gbSoundSaveGame(gzFile out)
|
|
{
|
|
gb_apu->save_state(&state.apu);
|
|
|
|
// Be sure areas for expansion get written as zero
|
|
memset(dummy_state, 0, sizeof dummy_state);
|
|
|
|
state.version = 1;
|
|
utilWriteData(out, gb_state);
|
|
}
|
|
|
|
void gbSoundReadGame(int version, gzFile in)
|
|
{
|
|
// Prepare APU and default state
|
|
reset_apu();
|
|
gb_apu->save_state(&state.apu);
|
|
|
|
if (version > 11)
|
|
utilReadData(in, gb_state);
|
|
else
|
|
gbSoundReadGameOld(version, in);
|
|
|
|
gb_apu->load_state(state.apu);
|
|
}
|