Complete rewrite of the APU. Channel 3 is complete and passes all the relevant tests from blargg’s suite, as well as PCM34-based tests. Actual sound output is basic and limited, though.
This commit is contained in:
parent
c0a8a570e8
commit
baccf336d7
485
Core/apu.c
485
Core/apu.c
@ -3,243 +3,72 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include "gb.h"
|
#include "gb.h"
|
||||||
|
|
||||||
#undef max
|
#define likely(x) __builtin_expect((x),1)
|
||||||
#define max(a,b) \
|
#define unlikely(x) __builtin_expect((x),0)
|
||||||
({ __typeof__ (a) _a = (a); \
|
|
||||||
__typeof__ (b) _b = (b); \
|
|
||||||
_a > _b ? _a : _b; })
|
|
||||||
|
|
||||||
#undef min
|
static void update_sample(GB_gameboy_t *gb, unsigned index, uint8_t value)
|
||||||
#define min(a,b) \
|
|
||||||
({ __typeof__ (a) _a = (a); \
|
|
||||||
__typeof__ (b) _b = (b); \
|
|
||||||
_a < _b ? _a : _b; })
|
|
||||||
|
|
||||||
#define APU_FREQUENCY 0x80000
|
|
||||||
|
|
||||||
static int16_t generate_square(uint64_t phase, uint32_t wave_length, int16_t amplitude, uint8_t duty)
|
|
||||||
{
|
{
|
||||||
if (!wave_length) return 0;
|
gb->apu.samples[index] = value;
|
||||||
if (phase % wave_length > wave_length * duty / 8) {
|
|
||||||
return amplitude;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int16_t generate_wave(uint64_t phase, uint32_t wave_length, int16_t amplitude, int8_t *wave, uint8_t shift)
|
void GB_apu_div_event(GB_gameboy_t *gb)
|
||||||
{
|
{
|
||||||
if (!wave_length) wave_length = 1;
|
if (gb->apu.is_active[GB_WAVE] && gb->apu.wave_channel.length_enabled) {
|
||||||
phase = phase % wave_length;
|
if (gb->apu.wave_channel.pulse_length) {
|
||||||
return ((wave[(int)(phase * 32 / wave_length)]) >> shift) * (int)amplitude / 0xF;
|
gb->apu.wave_channel.pulse_length--;
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
static int16_t generate_noise(int16_t amplitude, uint16_t lfsr)
|
gb->apu.is_active[GB_WAVE] = false;
|
||||||
{
|
gb->apu.wave_channel.current_sample = 0;
|
||||||
if (lfsr & 1) {
|
update_sample(gb, GB_WAVE, 0);
|
||||||
return amplitude;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int16_t step_lfsr(uint16_t lfsr, bool uses_7_bit)
|
|
||||||
{
|
|
||||||
bool xor = (lfsr & 1) ^ ((lfsr & 2) >> 1);
|
|
||||||
lfsr >>= 1;
|
|
||||||
if (xor) {
|
|
||||||
lfsr |= 0x4000;
|
|
||||||
}
|
|
||||||
if (uses_7_bit) {
|
|
||||||
lfsr &= ~0x40;
|
|
||||||
if (xor) {
|
|
||||||
lfsr |= 0x40;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return lfsr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* General Todo: The APU emulation seems to fail many accuracy tests. It might require a rewrite with
|
static void render(GB_gameboy_t *gb)
|
||||||
these tests in mind. */
|
|
||||||
|
|
||||||
static void GB_apu_run_internal(GB_gameboy_t *gb)
|
|
||||||
{
|
{
|
||||||
|
while (gb->audio_copy_in_progress);
|
||||||
while (!__sync_bool_compare_and_swap(&gb->apu_lock, false, true));
|
while (!__sync_bool_compare_and_swap(&gb->apu_lock, false, true));
|
||||||
uint32_t steps = gb->apu.apu_cycles / (CPU_FREQUENCY/APU_FREQUENCY);
|
if (gb->audio_position >= gb->buffer_size) {
|
||||||
if (!steps) goto exit;
|
gb->apu_lock = false;
|
||||||
|
return;
|
||||||
gb->apu.apu_cycles %= (CPU_FREQUENCY/APU_FREQUENCY);
|
|
||||||
for (uint8_t i = 0; i < 4; i++) {
|
|
||||||
/* Phase */
|
|
||||||
gb->apu.wave_channels[i].phase += steps;
|
|
||||||
while (gb->apu.wave_channels[i].wave_length && gb->apu.wave_channels[i].phase >= gb->apu.wave_channels[i].wave_length) {
|
|
||||||
if (i == 3) {
|
|
||||||
gb->apu.lfsr = step_lfsr(gb->apu.lfsr, gb->apu.lfsr_7_bit);
|
|
||||||
}
|
|
||||||
|
|
||||||
gb->apu.wave_channels[i].phase -= gb->apu.wave_channels[i].wave_length;
|
|
||||||
}
|
|
||||||
/* Stop on Length */
|
|
||||||
if (gb->apu.wave_channels[i].stop_on_length) {
|
|
||||||
if (gb->apu.wave_channels[i].sound_length > 0) {
|
|
||||||
gb->apu.wave_channels[i].sound_length -= steps;
|
|
||||||
}
|
|
||||||
if (gb->apu.wave_channels[i].sound_length <= 0) {
|
|
||||||
gb->apu.wave_channels[i].amplitude = 0;
|
|
||||||
gb->apu.wave_channels[i].is_playing = false;
|
|
||||||
gb->apu.wave_channels[i].sound_length = i == 2? APU_FREQUENCY : APU_FREQUENCY / 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
gb->audio_buffer[gb->audio_position++] = (GB_sample_t) {gb->apu.samples[GB_WAVE] * CH_STEP,
|
||||||
gb->apu.envelope_step_timer += steps;
|
gb->apu.samples[GB_WAVE] * CH_STEP};
|
||||||
while (gb->apu.envelope_step_timer >= APU_FREQUENCY / 64) {
|
|
||||||
gb->apu.envelope_step_timer -= APU_FREQUENCY / 64;
|
|
||||||
for (uint8_t i = 0; i < 4; i++) {
|
|
||||||
if (gb->apu.wave_channels[i].envelope_steps && !--gb->apu.wave_channels[i].cur_envelope_steps) {
|
|
||||||
gb->apu.wave_channels[i].amplitude = min(max(gb->apu.wave_channels[i].amplitude + gb->apu.wave_channels[i].envelope_direction * CH_STEP, 0), MAX_CH_AMP);
|
|
||||||
gb->apu.wave_channels[i].cur_envelope_steps = gb->apu.wave_channels[i].envelope_steps;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
gb->apu.sweep_step_timer += steps;
|
|
||||||
while (gb->apu.sweep_step_timer >= APU_FREQUENCY / 128) {
|
|
||||||
gb->apu.sweep_step_timer -= APU_FREQUENCY / 128;
|
|
||||||
if (gb->apu.wave_channels[0].sweep_steps && !--gb->apu.wave_channels[0].cur_sweep_steps) {
|
|
||||||
|
|
||||||
// Convert back to GB format
|
|
||||||
uint16_t temp = 2048 - gb->apu.wave_channels[0].wave_length / (APU_FREQUENCY / 131072);
|
|
||||||
|
|
||||||
// Apply sweep
|
|
||||||
temp = temp + gb->apu.wave_channels[0].sweep_direction *
|
|
||||||
(temp / (1 << gb->apu.wave_channels[0].sweep_shift));
|
|
||||||
if (temp > 2047) {
|
|
||||||
temp = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Back to frequency
|
|
||||||
gb->apu.wave_channels[0].wave_length = (2048 - temp) * (APU_FREQUENCY / 131072);
|
|
||||||
|
|
||||||
|
|
||||||
gb->apu.wave_channels[0].cur_sweep_steps = gb->apu.wave_channels[0].sweep_steps;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
exit:
|
|
||||||
gb->apu_lock = false;
|
gb->apu_lock = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GB_apu_get_samples_and_update_pcm_regs(GB_gameboy_t *gb, GB_sample_t *samples)
|
void GB_apu_run(GB_gameboy_t *gb, uint8_t cycles)
|
||||||
{
|
{
|
||||||
GB_apu_run_internal(gb);
|
/* Convert 4MHZ to 2MHz. cycles is always even. */
|
||||||
|
cycles >>= 1;
|
||||||
|
|
||||||
samples->left = samples->right = 0;
|
double cycles_per_sample = gb->sample_rate ? CPU_FREQUENCY / (double)gb->sample_rate : 0; // TODO: this should be cached!
|
||||||
if (!gb->apu.global_enable) {
|
|
||||||
return;
|
if (gb->sample_rate && gb->apu_sample_cycles > cycles_per_sample) {
|
||||||
|
gb->apu_sample_cycles -= cycles_per_sample;
|
||||||
|
render(gb);
|
||||||
}
|
}
|
||||||
|
|
||||||
gb->io_registers[GB_IO_PCM_12] = 0;
|
gb->apu.wave_channel.wave_form_just_read = false;
|
||||||
gb->io_registers[GB_IO_PCM_34] = 0;
|
if (gb->apu.is_active[GB_WAVE]) {
|
||||||
|
uint8_t cycles_left = cycles;
|
||||||
{
|
while (unlikely(cycles_left > gb->apu.wave_channel.sample_countdown)) {
|
||||||
int16_t sample = generate_square(gb->apu.wave_channels[0].phase,
|
cycles_left -= gb->apu.wave_channel.sample_countdown + 1;
|
||||||
gb->apu.wave_channels[0].wave_length,
|
gb->apu.wave_channel.sample_countdown = gb->apu.wave_channel.sample_length;
|
||||||
gb->apu.wave_channels[0].amplitude,
|
gb->apu.wave_channel.current_sample_index++;
|
||||||
gb->apu.wave_channels[0].duty);
|
gb->apu.wave_channel.current_sample_index &= 0x1F;
|
||||||
if (gb->apu.wave_channels[0].left_on ) samples->left += sample;
|
gb->apu.wave_channel.current_sample =
|
||||||
if (gb->apu.wave_channels[0].right_on) samples->right += sample;
|
gb->apu.wave_channel.wave_form[gb->apu.wave_channel.current_sample_index];
|
||||||
gb->io_registers[GB_IO_PCM_12] = ((int)sample) * 0xF / MAX_CH_AMP;
|
update_sample(gb, GB_WAVE, gb->apu.wave_channel.current_sample >> gb->apu.wave_channel.shift);
|
||||||
}
|
gb->apu.wave_channel.wave_form_just_read = true;
|
||||||
|
|
||||||
{
|
|
||||||
int16_t sample = generate_square(gb->apu.wave_channels[1].phase,
|
|
||||||
gb->apu.wave_channels[1].wave_length,
|
|
||||||
gb->apu.wave_channels[1].amplitude,
|
|
||||||
gb->apu.wave_channels[1].duty);
|
|
||||||
if (gb->apu.wave_channels[1].left_on ) samples->left += sample;
|
|
||||||
if (gb->apu.wave_channels[1].right_on) samples->right += sample;
|
|
||||||
gb->io_registers[GB_IO_PCM_12] |= (((int)sample) * 0xF / MAX_CH_AMP) << 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gb->apu.wave_channels[2].is_playing)
|
|
||||||
{
|
|
||||||
int16_t sample = generate_wave(gb->apu.wave_channels[2].phase,
|
|
||||||
gb->apu.wave_channels[2].wave_length,
|
|
||||||
MAX_CH_AMP,
|
|
||||||
gb->apu.wave_form,
|
|
||||||
gb->apu.wave_shift);
|
|
||||||
if (gb->apu.wave_channels[2].left_on ) samples->left += sample;
|
|
||||||
if (gb->apu.wave_channels[2].right_on) samples->right += sample;
|
|
||||||
gb->io_registers[GB_IO_PCM_34] = ((int)sample) * 0xF / MAX_CH_AMP;
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
int16_t sample = generate_noise(gb->apu.wave_channels[3].amplitude,
|
|
||||||
gb->apu.lfsr);
|
|
||||||
if (gb->apu.wave_channels[3].left_on ) samples->left += sample;
|
|
||||||
if (gb->apu.wave_channels[3].right_on) samples->right += sample;
|
|
||||||
gb->io_registers[GB_IO_PCM_34] |= (((int)sample) * 0xF / MAX_CH_AMP) << 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
samples->left = (int) samples->left * gb->apu.left_volume / 7;
|
|
||||||
samples->right = (int) samples->right * gb->apu.right_volume / 7;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GB_apu_run(GB_gameboy_t *gb)
|
|
||||||
{
|
|
||||||
if (gb->sample_rate == 0) {
|
|
||||||
if (gb->apu.apu_cycles > 0xFF00) {
|
|
||||||
GB_sample_t dummy;
|
|
||||||
GB_apu_get_samples_and_update_pcm_regs(gb, &dummy);
|
|
||||||
}
|
}
|
||||||
return;
|
if (cycles_left) {
|
||||||
}
|
gb->apu.wave_channel.sample_countdown -= cycles_left;
|
||||||
while (gb->audio_copy_in_progress);
|
gb->apu.wave_channel.wave_form_just_read = false;
|
||||||
double ticks_per_sample = (double) CPU_FREQUENCY / gb->sample_rate;
|
|
||||||
|
|
||||||
if (gb->audio_quality == 0) {
|
|
||||||
GB_sample_t sample;
|
|
||||||
GB_apu_get_samples_and_update_pcm_regs(gb, &sample);
|
|
||||||
gb->current_supersample.left += sample.left;
|
|
||||||
gb->current_supersample.right += sample.right;
|
|
||||||
gb->n_subsamples++;
|
|
||||||
}
|
|
||||||
else if (gb->audio_quality != 1) {
|
|
||||||
double ticks_per_subsample = ticks_per_sample / gb->audio_quality;
|
|
||||||
if (ticks_per_subsample < 1) {
|
|
||||||
ticks_per_subsample = 1;
|
|
||||||
}
|
|
||||||
if (gb->apu_subsample_cycles > ticks_per_subsample) {
|
|
||||||
gb->apu_subsample_cycles -= ticks_per_subsample;
|
|
||||||
}
|
|
||||||
|
|
||||||
GB_sample_t sample;
|
|
||||||
GB_apu_get_samples_and_update_pcm_regs(gb, &sample);
|
|
||||||
gb->current_supersample.left += sample.left;
|
|
||||||
gb->current_supersample.right += sample.right;
|
|
||||||
gb->n_subsamples++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gb->apu_sample_cycles > ticks_per_sample) {
|
|
||||||
gb->apu_sample_cycles -= ticks_per_sample;
|
|
||||||
if (gb->audio_position == gb->buffer_size) {
|
|
||||||
/*
|
|
||||||
if (!gb->turbo) {
|
|
||||||
GB_log(gb, "Audio overflow\n");
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (gb->audio_quality == 1) {
|
|
||||||
GB_apu_get_samples_and_update_pcm_regs(gb, &gb->audio_buffer[gb->audio_position++]);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
gb->audio_buffer[gb->audio_position].left = round(gb->current_supersample.left / gb->n_subsamples);
|
|
||||||
gb->audio_buffer[gb->audio_position].right = round(gb->current_supersample.right / gb->n_subsamples);
|
|
||||||
gb->n_subsamples = 0;
|
|
||||||
gb->current_supersample = (GB_double_sample_t){0, };
|
|
||||||
gb->audio_position++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GB_apu_copy_buffer(GB_gameboy_t *gb, GB_sample_t *dest, unsigned int count)
|
void GB_apu_copy_buffer(GB_gameboy_t *gb, GB_sample_t *dest, unsigned int count)
|
||||||
@ -274,24 +103,23 @@ void GB_apu_copy_buffer(GB_gameboy_t *gb, GB_sample_t *dest, unsigned int count)
|
|||||||
void GB_apu_init(GB_gameboy_t *gb)
|
void GB_apu_init(GB_gameboy_t *gb)
|
||||||
{
|
{
|
||||||
memset(&gb->apu, 0, sizeof(gb->apu));
|
memset(&gb->apu, 0, sizeof(gb->apu));
|
||||||
gb->apu.wave_channels[0].duty = gb->apu.wave_channels[1].duty = 4;
|
// gb->apu.wave_channels[0].duty = gb->apu.wave_channels[1].duty = 4;
|
||||||
gb->apu.lfsr = 0x7FFF;
|
// gb->apu.lfsr = 0x7FFF;
|
||||||
gb->apu.left_volume = 7;
|
gb->apu.left_volume = 7;
|
||||||
gb->apu.right_volume = 7;
|
gb->apu.right_volume = 7;
|
||||||
for (int i = 0; i < 4; i++) {
|
for (int i = 0; i < 4; i++) {
|
||||||
gb->apu.wave_channels[i].left_on = gb->apu.wave_channels[i].right_on = 1;
|
gb->apu.left_enabled[i] = gb->apu.right_enabled[i] = true;
|
||||||
}
|
}
|
||||||
|
gb->apu.wave_channel.sample_length = 0x7FF;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg)
|
uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg)
|
||||||
{
|
{
|
||||||
GB_apu_run_internal(gb);
|
|
||||||
|
|
||||||
if (reg == GB_IO_NR52) {
|
if (reg == GB_IO_NR52) {
|
||||||
uint8_t value = 0;
|
uint8_t value = 0;
|
||||||
for (int i = 0; i < 4; i++) {
|
for (int i = 0; i < GB_N_CHANNELS; i++) {
|
||||||
value >>= 1;
|
value >>= 1;
|
||||||
if (gb->apu.wave_channels[i].is_playing) {
|
if (gb->apu.is_active[i]) {
|
||||||
value |= 0x8;
|
value |= 0x8;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -315,12 +143,11 @@ uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg)
|
|||||||
0, /* ... */
|
0, /* ... */
|
||||||
};
|
};
|
||||||
|
|
||||||
if (reg >= GB_IO_WAV_START && reg <= GB_IO_WAV_END && gb->apu.wave_channels[2].is_playing) {
|
if (reg >= GB_IO_WAV_START && reg <= GB_IO_WAV_END && gb->apu.is_active[GB_WAVE]) {
|
||||||
if (gb->apu.wave_channels[2].wave_length == 0) {
|
if (!gb->is_cgb && !gb->apu.wave_channel.wave_form_just_read) {
|
||||||
return gb->apu.wave_form[0];
|
return 0xFF;
|
||||||
}
|
}
|
||||||
gb->apu.wave_channels[2].phase %= gb->apu.wave_channels[2].wave_length;
|
reg = GB_IO_WAV_START + gb->apu.wave_channel.current_sample_index / 2;
|
||||||
return gb->apu.wave_form[(int)(gb->apu.wave_channels[2].phase * 32 / gb->apu.wave_channels[2].wave_length)];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return gb->io_registers[reg] | read_mask[reg - GB_IO_NR10];
|
return gb->io_registers[reg] | read_mask[reg - GB_IO_NR10];
|
||||||
@ -328,145 +155,23 @@ uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg)
|
|||||||
|
|
||||||
void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
|
void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
|
||||||
{
|
{
|
||||||
GB_apu_run_internal(gb);
|
|
||||||
|
|
||||||
static const uint8_t duties[] = {1, 2, 4, 6}; /* Values are in 1/8 */
|
|
||||||
uint8_t channel = 0;
|
|
||||||
|
|
||||||
if (!gb->apu.global_enable && reg != GB_IO_NR52) {
|
if (!gb->apu.global_enable && reg != GB_IO_NR52) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
gb->io_registers[reg] = value;
|
if (reg >= GB_IO_WAV_START && reg <= GB_IO_WAV_END && gb->apu.is_active[GB_WAVE]) {
|
||||||
|
if (!gb->is_cgb && !gb->apu.wave_channel.wave_form_just_read) {
|
||||||
switch (reg) {
|
return;
|
||||||
case GB_IO_NR10:
|
}
|
||||||
case GB_IO_NR11:
|
reg = GB_IO_WAV_START + gb->apu.wave_channel.current_sample_index / 2;
|
||||||
case GB_IO_NR12:
|
|
||||||
case GB_IO_NR13:
|
|
||||||
case GB_IO_NR14:
|
|
||||||
channel = 0;
|
|
||||||
break;
|
|
||||||
case GB_IO_NR21:
|
|
||||||
case GB_IO_NR22:
|
|
||||||
case GB_IO_NR23:
|
|
||||||
case GB_IO_NR24:
|
|
||||||
channel = 1;
|
|
||||||
break;
|
|
||||||
case GB_IO_NR33:
|
|
||||||
case GB_IO_NR34:
|
|
||||||
channel = 2;
|
|
||||||
break;
|
|
||||||
case GB_IO_NR41:
|
|
||||||
case GB_IO_NR42:
|
|
||||||
channel = 3;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gb->io_registers[reg] = value;
|
||||||
|
|
||||||
|
|
||||||
switch (reg) {
|
switch (reg) {
|
||||||
case GB_IO_NR10:
|
/* Globals */
|
||||||
gb->apu.wave_channels[channel].sweep_direction = value & 8? -1 : 1;
|
|
||||||
gb->apu.wave_channels[channel].cur_sweep_steps =
|
|
||||||
gb->apu.wave_channels[channel].sweep_steps = (value & 0x70) >> 4;
|
|
||||||
gb->apu.wave_channels[channel].sweep_shift = value & 7;
|
|
||||||
break;
|
|
||||||
case GB_IO_NR11:
|
|
||||||
case GB_IO_NR21:
|
|
||||||
case GB_IO_NR41:
|
|
||||||
gb->apu.wave_channels[channel].duty = duties[value >> 6];
|
|
||||||
gb->apu.wave_channels[channel].sound_length = (64 - (value & 0x3F)) * (APU_FREQUENCY / 256);
|
|
||||||
if (gb->apu.wave_channels[channel].sound_length == 0) {
|
|
||||||
gb->apu.wave_channels[channel].is_playing = false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case GB_IO_NR12:
|
|
||||||
case GB_IO_NR22:
|
|
||||||
case GB_IO_NR42:
|
|
||||||
gb->apu.wave_channels[channel].start_amplitude =
|
|
||||||
gb->apu.wave_channels[channel].amplitude = CH_STEP * (value >> 4);
|
|
||||||
if (value >> 4 == 0) {
|
|
||||||
gb->apu.wave_channels[channel].is_playing = false;
|
|
||||||
}
|
|
||||||
gb->apu.wave_channels[channel].envelope_direction = value & 8? 1 : -1;
|
|
||||||
gb->apu.wave_channels[channel].cur_envelope_steps =
|
|
||||||
gb->apu.wave_channels[channel].envelope_steps = value & 7;
|
|
||||||
break;
|
|
||||||
case GB_IO_NR13:
|
|
||||||
case GB_IO_NR23:
|
|
||||||
case GB_IO_NR33:
|
|
||||||
gb->apu.wave_channels[channel].NRX3_X4_temp = (gb->apu.wave_channels[channel].NRX3_X4_temp & 0xFF00) | value;
|
|
||||||
gb->apu.wave_channels[channel].wave_length = (2048 - gb->apu.wave_channels[channel].NRX3_X4_temp) * (APU_FREQUENCY / 131072);
|
|
||||||
if (channel == 2) {
|
|
||||||
gb->apu.wave_channels[channel].wave_length *= 2;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case GB_IO_NR14:
|
|
||||||
case GB_IO_NR24:
|
|
||||||
case GB_IO_NR34:
|
|
||||||
gb->apu.wave_channels[channel].stop_on_length = value & 0x40;
|
|
||||||
if ((value & 0x80) && (channel != 2 || gb->apu.wave_enable)) {
|
|
||||||
gb->apu.wave_channels[channel].is_playing = true;
|
|
||||||
gb->apu.wave_channels[channel].phase = 0;
|
|
||||||
gb->apu.wave_channels[channel].amplitude = gb->apu.wave_channels[channel].start_amplitude;
|
|
||||||
gb->apu.wave_channels[channel].cur_envelope_steps = gb->apu.wave_channels[channel].envelope_steps;
|
|
||||||
}
|
|
||||||
|
|
||||||
gb->apu.wave_channels[channel].NRX3_X4_temp = (gb->apu.wave_channels[channel].NRX3_X4_temp & 0xFF) | ((value & 0x7) << 8);
|
|
||||||
gb->apu.wave_channels[channel].wave_length = (2048 - gb->apu.wave_channels[channel].NRX3_X4_temp) * (APU_FREQUENCY / 131072);
|
|
||||||
if (channel == 2) {
|
|
||||||
gb->apu.wave_channels[channel].wave_length *= 2;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case GB_IO_NR30:
|
|
||||||
gb->apu.wave_enable = value & 0x80;
|
|
||||||
gb->apu.wave_channels[2].is_playing &= gb->apu.wave_enable;
|
|
||||||
break;
|
|
||||||
case GB_IO_NR31:
|
|
||||||
gb->apu.wave_channels[2].sound_length = (256 - value) * (APU_FREQUENCY / 256);
|
|
||||||
if (gb->apu.wave_channels[2].sound_length == 0) {
|
|
||||||
gb->apu.wave_channels[2].is_playing = false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case GB_IO_NR32:
|
|
||||||
gb->apu.wave_shift = ((value >> 5) + 3) & 3;
|
|
||||||
if (gb->apu.wave_shift == 3) {
|
|
||||||
gb->apu.wave_shift = 4;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case GB_IO_NR43:
|
|
||||||
{
|
|
||||||
double r = value & 0x7;
|
|
||||||
if (r == 0) r = 0.5;
|
|
||||||
uint8_t s = value >> 4;
|
|
||||||
gb->apu.wave_channels[3].wave_length = r * (1 << s) * (APU_FREQUENCY / 262144) ;
|
|
||||||
gb->apu.lfsr_7_bit = value & 0x8;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case GB_IO_NR44:
|
|
||||||
gb->apu.wave_channels[3].stop_on_length = value & 0x40;
|
|
||||||
if (value & 0x80) {
|
|
||||||
gb->apu.wave_channels[3].is_playing = true;
|
|
||||||
gb->apu.lfsr = 0x7FFF;
|
|
||||||
gb->apu.wave_channels[3].amplitude = gb->apu.wave_channels[3].start_amplitude;
|
|
||||||
gb->apu.wave_channels[3].cur_envelope_steps = gb->apu.wave_channels[3].envelope_steps;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case GB_IO_NR50:
|
|
||||||
gb->apu.left_volume = (value & 7);
|
|
||||||
gb->apu.right_volume = ((value >> 4) & 7);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case GB_IO_NR51:
|
|
||||||
for (int i = 0; i < 4; i++) {
|
|
||||||
gb->apu.wave_channels[i].left_on = value & 1;
|
|
||||||
gb->apu.wave_channels[i].right_on = value & 0x10;
|
|
||||||
value >>= 1;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case GB_IO_NR52:
|
case GB_IO_NR52:
|
||||||
|
|
||||||
if ((value & 0x80) && !gb->apu.global_enable) {
|
if ((value & 0x80) && !gb->apu.global_enable) {
|
||||||
GB_apu_init(gb);
|
GB_apu_init(gb);
|
||||||
gb->apu.global_enable = true;
|
gb->apu.global_enable = true;
|
||||||
@ -474,21 +179,71 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
|
|||||||
else if (!(value & 0x80) && gb->apu.global_enable) {
|
else if (!(value & 0x80) && gb->apu.global_enable) {
|
||||||
memset(&gb->apu, 0, sizeof(gb->apu));
|
memset(&gb->apu, 0, sizeof(gb->apu));
|
||||||
memset(gb->io_registers + GB_IO_NR10, 0, GB_IO_WAV_START - GB_IO_NR10);
|
memset(gb->io_registers + GB_IO_NR10, 0, GB_IO_WAV_START - GB_IO_NR10);
|
||||||
|
gb->apu.global_enable = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* Wave channel */
|
||||||
|
case GB_IO_NR30:
|
||||||
|
gb->apu.wave_channel.enable = value & 0x80;
|
||||||
|
if (!gb->apu.wave_channel.enable) {
|
||||||
|
gb->apu.is_active[GB_WAVE] = false;
|
||||||
|
gb->apu.wave_channel.current_sample = 0;
|
||||||
|
update_sample(gb, GB_WAVE, 0);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case GB_IO_NR31:
|
||||||
|
break;
|
||||||
|
case GB_IO_NR32:
|
||||||
|
gb->apu.wave_channel.shift = (uint8_t[]){4, 0, 1, 2}[(value >> 5) & 3];
|
||||||
|
update_sample(gb, GB_WAVE, gb->apu.wave_channel.current_sample >> gb->apu.wave_channel.shift);
|
||||||
|
break;
|
||||||
|
case GB_IO_NR33:
|
||||||
|
gb->apu.wave_channel.sample_length &= ~0xFF;
|
||||||
|
gb->apu.wave_channel.sample_length |= (~value) & 0xFF;
|
||||||
|
break;
|
||||||
|
case GB_IO_NR34:
|
||||||
|
gb->apu.wave_channel.length_enabled = value & 0x40;
|
||||||
|
gb->apu.wave_channel.sample_length &= 0xFF;
|
||||||
|
gb->apu.wave_channel.sample_length |= ((~value) & 7) << 8;
|
||||||
|
if ((value & 0x80) && gb->apu.wave_channel.enable) {
|
||||||
|
/* DMG bug: wave RAM gets corrupted if the channel is retriggerred 1 cycle before the APU
|
||||||
|
reads from it. */
|
||||||
|
if (!gb->is_cgb && gb->apu.is_active[GB_WAVE] && gb->apu.wave_channel.sample_countdown == 0) {
|
||||||
|
unsigned offset = ((gb->apu.wave_channel.current_sample_index + 1) >> 1) & 0xF;
|
||||||
|
|
||||||
|
/* On SGB2 (and probably SGB1 and MGB as well) this behavior is not accurate,
|
||||||
|
however these systems are not currently emulated. */
|
||||||
|
if (offset < 4) {
|
||||||
|
gb->io_registers[GB_IO_WAV_START] = gb->io_registers[GB_IO_WAV_START + offset];
|
||||||
|
gb->apu.wave_channel.wave_form[0] = gb->apu.wave_channel.wave_form[offset / 2];
|
||||||
|
gb->apu.wave_channel.wave_form[1] = gb->apu.wave_channel.wave_form[offset / 2 + 1];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
memcpy(gb->io_registers + GB_IO_WAV_START,
|
||||||
|
gb->io_registers + GB_IO_WAV_START + (offset & ~3),
|
||||||
|
4);
|
||||||
|
memcpy(gb->apu.wave_channel.wave_form,
|
||||||
|
gb->apu.wave_channel.wave_form + (offset & ~3) * 2,
|
||||||
|
8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gb->apu.is_active[GB_WAVE] = true;
|
||||||
|
gb->apu.wave_channel.pulse_length = ~gb->io_registers[GB_IO_NR31];
|
||||||
|
gb->apu.wave_channel.sample_countdown = gb->apu.wave_channel.sample_length + 3;
|
||||||
|
gb->apu.wave_channel.current_sample_index = 0;
|
||||||
|
/* Note that we don't change the sample just yet! This was verified on hardware. */
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
if (reg >= GB_IO_WAV_START && reg <= GB_IO_WAV_END) {
|
if (reg >= GB_IO_WAV_START && reg <= GB_IO_WAV_END) {
|
||||||
gb->apu.wave_form[(reg - GB_IO_WAV_START) * 2] = value >> 4;
|
gb->apu.wave_channel.wave_form[(reg - GB_IO_WAV_START) * 2] = value >> 4;
|
||||||
gb->apu.wave_form[(reg - GB_IO_WAV_START) * 2 + 1] = value & 0xF;
|
gb->apu.wave_channel.wave_form[(reg - GB_IO_WAV_START) * 2 + 1] = value & 0xF;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void GB_set_audio_quality(GB_gameboy_t *gb, unsigned quality)
|
|
||||||
{
|
|
||||||
gb->audio_quality = quality;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned GB_apu_get_current_buffer_length(GB_gameboy_t *gb)
|
unsigned GB_apu_get_current_buffer_length(GB_gameboy_t *gb)
|
||||||
|
74
Core/apu.h
74
Core/apu.h
@ -3,10 +3,16 @@
|
|||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include "gb_struct_def.h"
|
#include "gb_struct_def.h"
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef GB_INTERNAL
|
||||||
/* Divides nicely and never overflows with 4 channels */
|
/* Divides nicely and never overflows with 4 channels */
|
||||||
#define MAX_CH_AMP 0x1E00
|
#define MAX_CH_AMP 0x1E00
|
||||||
#define CH_STEP (0x1E00/0xF)
|
#define CH_STEP (MAX_CH_AMP/0xF)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Lengths are in either DIV ticks (256Hz, triggered by the DIV register) or
|
||||||
|
APU ticks (2MHz, triggered by an internal APU clock)*/
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
@ -20,59 +26,51 @@ typedef struct
|
|||||||
double right;
|
double right;
|
||||||
} GB_double_sample_t;
|
} GB_double_sample_t;
|
||||||
|
|
||||||
/* Not all used on all channels */
|
enum GB_CHANNELS {
|
||||||
/* All lengths are in APU ticks */
|
GB_SQUARE_1,
|
||||||
typedef struct
|
GB_SQUARE_2,
|
||||||
{
|
GB_WAVE,
|
||||||
uint32_t phase;
|
GB_NOISE,
|
||||||
uint32_t wave_length;
|
GB_N_CHANNELS
|
||||||
int32_t sound_length;
|
};
|
||||||
bool stop_on_length;
|
|
||||||
uint8_t duty;
|
|
||||||
int16_t amplitude;
|
|
||||||
int16_t start_amplitude;
|
|
||||||
uint8_t envelope_steps;
|
|
||||||
uint8_t cur_envelope_steps;
|
|
||||||
int8_t envelope_direction;
|
|
||||||
uint8_t sweep_steps;
|
|
||||||
uint8_t cur_sweep_steps;
|
|
||||||
int8_t sweep_direction;
|
|
||||||
uint8_t sweep_shift;
|
|
||||||
bool is_playing;
|
|
||||||
uint16_t NRX3_X4_temp;
|
|
||||||
bool left_on;
|
|
||||||
bool right_on;
|
|
||||||
} GB_apu_channel_t;
|
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
uint16_t apu_cycles;
|
|
||||||
bool global_enable;
|
bool global_enable;
|
||||||
uint32_t envelope_step_timer;
|
|
||||||
uint32_t sweep_step_timer;
|
|
||||||
int8_t wave_form[32];
|
|
||||||
uint8_t wave_shift;
|
|
||||||
bool wave_enable;
|
|
||||||
uint16_t lfsr;
|
|
||||||
bool lfsr_7_bit;
|
|
||||||
uint8_t left_volume;
|
uint8_t left_volume;
|
||||||
uint8_t right_volume;
|
uint8_t right_volume;
|
||||||
GB_apu_channel_t wave_channels[4];
|
|
||||||
|
uint8_t samples[GB_N_CHANNELS];
|
||||||
|
bool left_enabled[GB_N_CHANNELS];
|
||||||
|
bool right_enabled[GB_N_CHANNELS];
|
||||||
|
bool is_active[GB_N_CHANNELS];
|
||||||
|
|
||||||
|
struct {
|
||||||
|
bool enable; // NR30
|
||||||
|
uint8_t pulse_length; // Reloaded from NR31 (xorred), in DIV ticks
|
||||||
|
uint8_t shift; // NR32
|
||||||
|
uint16_t sample_length; // NR33, NR34, in APU ticks
|
||||||
|
bool length_enabled; // NR34
|
||||||
|
|
||||||
|
uint16_t sample_countdown; // in APU ticks
|
||||||
|
uint8_t current_sample_index;
|
||||||
|
uint8_t current_sample; // Current sample before shifting.
|
||||||
|
|
||||||
|
int8_t wave_form[32];
|
||||||
|
bool wave_form_just_read;
|
||||||
|
} wave_channel;
|
||||||
} GB_apu_t;
|
} GB_apu_t;
|
||||||
|
|
||||||
void GB_set_sample_rate(GB_gameboy_t *gb, unsigned int sample_rate);
|
void GB_set_sample_rate(GB_gameboy_t *gb, unsigned int sample_rate);
|
||||||
/* Quality is the number of subsamples per sampling, for the sake of resampling.
|
|
||||||
1 means on resampling at all, 0 is maximum quality. Default is 4. */
|
|
||||||
void GB_set_audio_quality(GB_gameboy_t *gb, unsigned quality);
|
|
||||||
void GB_apu_copy_buffer(GB_gameboy_t *gb, GB_sample_t *dest, unsigned int count);
|
void GB_apu_copy_buffer(GB_gameboy_t *gb, GB_sample_t *dest, unsigned int count);
|
||||||
unsigned GB_apu_get_current_buffer_length(GB_gameboy_t *gb);
|
unsigned GB_apu_get_current_buffer_length(GB_gameboy_t *gb);
|
||||||
|
|
||||||
#ifdef GB_INTERNAL
|
#ifdef GB_INTERNAL
|
||||||
void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value);
|
void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value);
|
||||||
uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg);
|
uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg);
|
||||||
void GB_apu_get_samples_and_update_pcm_regs(GB_gameboy_t *gb, GB_sample_t *samples);
|
void GB_apu_div_event(GB_gameboy_t *gb);
|
||||||
void GB_apu_init(GB_gameboy_t *gb);
|
void GB_apu_init(GB_gameboy_t *gb);
|
||||||
void GB_apu_run(GB_gameboy_t *gb);
|
void GB_apu_run(GB_gameboy_t *gb, uint8_t cycles);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif /* apu_h */
|
#endif /* apu_h */
|
||||||
|
@ -92,7 +92,6 @@ void GB_init(GB_gameboy_t *gb)
|
|||||||
gb->input_callback = default_input_callback;
|
gb->input_callback = default_input_callback;
|
||||||
gb->async_input_callback = default_async_input_callback;
|
gb->async_input_callback = default_async_input_callback;
|
||||||
gb->cartridge_type = &GB_cart_defs[0]; // Default cartridge type
|
gb->cartridge_type = &GB_cart_defs[0]; // Default cartridge type
|
||||||
gb->audio_quality = 4;
|
|
||||||
|
|
||||||
GB_reset(gb);
|
GB_reset(gb);
|
||||||
}
|
}
|
||||||
@ -107,7 +106,6 @@ void GB_init_cgb(GB_gameboy_t *gb)
|
|||||||
gb->input_callback = default_input_callback;
|
gb->input_callback = default_input_callback;
|
||||||
gb->async_input_callback = default_async_input_callback;
|
gb->async_input_callback = default_async_input_callback;
|
||||||
gb->cartridge_type = &GB_cart_defs[0]; // Default cartridge type
|
gb->cartridge_type = &GB_cart_defs[0]; // Default cartridge type
|
||||||
gb->audio_quality = 4;
|
|
||||||
|
|
||||||
GB_reset(gb);
|
GB_reset(gb);
|
||||||
}
|
}
|
||||||
|
@ -419,11 +419,6 @@ struct GB_gameboy_internal_s {
|
|||||||
volatile bool audio_copy_in_progress;
|
volatile bool audio_copy_in_progress;
|
||||||
volatile bool apu_lock;
|
volatile bool apu_lock;
|
||||||
double apu_sample_cycles;
|
double apu_sample_cycles;
|
||||||
double apu_subsample_cycles;
|
|
||||||
GB_double_sample_t current_supersample;
|
|
||||||
unsigned n_subsamples;
|
|
||||||
unsigned audio_quality;
|
|
||||||
|
|
||||||
|
|
||||||
/* Callbacks */
|
/* Callbacks */
|
||||||
void *user_data;
|
void *user_data;
|
||||||
|
@ -148,13 +148,11 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr)
|
|||||||
return gb->io_registers[GB_IO_DMG_EMULATION_INDICATION] | 0xFE;
|
return gb->io_registers[GB_IO_DMG_EMULATION_INDICATION] | 0xFE;
|
||||||
|
|
||||||
case GB_IO_PCM_12:
|
case GB_IO_PCM_12:
|
||||||
case GB_IO_PCM_34:
|
|
||||||
{
|
|
||||||
if (!gb->is_cgb) return 0xFF;
|
if (!gb->is_cgb) return 0xFF;
|
||||||
GB_sample_t dummy;
|
return (gb->apu.samples[GB_SQUARE_2] << 4) | gb->apu.samples[GB_SQUARE_1];
|
||||||
GB_apu_get_samples_and_update_pcm_regs(gb, &dummy);
|
case GB_IO_PCM_34:
|
||||||
}
|
if (!gb->is_cgb) return 0xFF;
|
||||||
/* Fall through */
|
return (gb->apu.samples[GB_NOISE] << 4) | gb->apu.samples[GB_WAVE];
|
||||||
case GB_IO_JOYP:
|
case GB_IO_JOYP:
|
||||||
case GB_IO_TMA:
|
case GB_IO_TMA:
|
||||||
case GB_IO_LCDC:
|
case GB_IO_LCDC:
|
||||||
|
@ -134,14 +134,12 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles)
|
|||||||
// Not affected by speed boost
|
// Not affected by speed boost
|
||||||
gb->hdma_cycles += cycles;
|
gb->hdma_cycles += cycles;
|
||||||
gb->apu_sample_cycles += cycles;
|
gb->apu_sample_cycles += cycles;
|
||||||
gb->apu_subsample_cycles += cycles;
|
|
||||||
gb->apu.apu_cycles += cycles;
|
|
||||||
gb->cycles_since_ir_change += cycles;
|
gb->cycles_since_ir_change += cycles;
|
||||||
gb->cycles_since_input_ir_change += cycles;
|
gb->cycles_since_input_ir_change += cycles;
|
||||||
gb->cycles_since_last_sync += cycles;
|
gb->cycles_since_last_sync += cycles;
|
||||||
GB_dma_run(gb);
|
GB_dma_run(gb);
|
||||||
GB_hdma_run(gb);
|
GB_hdma_run(gb);
|
||||||
GB_apu_run(gb);
|
GB_apu_run(gb, cycles);
|
||||||
GB_display_run(gb, cycles);
|
GB_display_run(gb, cycles);
|
||||||
GB_ir_run(gb);
|
GB_ir_run(gb);
|
||||||
}
|
}
|
||||||
@ -172,6 +170,9 @@ void GB_set_internal_div_counter(GB_gameboy_t *gb, uint32_t value)
|
|||||||
counter_overflow_check(gb->div_cycles, value, GB_TAC_RATIOS[gb->io_registers[GB_IO_TAC] & 3])) {
|
counter_overflow_check(gb->div_cycles, value, GB_TAC_RATIOS[gb->io_registers[GB_IO_TAC] & 3])) {
|
||||||
increase_tima(gb);
|
increase_tima(gb);
|
||||||
}
|
}
|
||||||
|
if (counter_overflow_check(gb->div_cycles, value, gb->cgb_double_speed? 0x4000 : 0x2000)) {
|
||||||
|
GB_apu_div_event(gb);
|
||||||
|
}
|
||||||
gb->div_cycles = value;
|
gb->div_cycles = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user