Redesign and reimplement the audio API, let the frontends handle more stuff. Probably affects #161

This commit is contained in:
Lior Halphon 2019-06-15 23:22:27 +03:00
parent 083b4a2970
commit e268efefef
13 changed files with 146 additions and 149 deletions

View File

@ -55,6 +55,12 @@ enum model {
bool rewind; bool rewind;
bool modelsChanging; bool modelsChanging;
NSCondition *audioLock;
GB_sample_t *audioBuffer;
size_t audioBufferSize;
size_t audioBufferPosition;
size_t audioBufferNeeded;
} }
@property GBAudioClient *audioClient; @property GBAudioClient *audioClient;
@ -67,6 +73,7 @@ enum model {
- (void) printImage:(uint32_t *)image height:(unsigned) height - (void) printImage:(uint32_t *)image height:(unsigned) height
topMargin:(unsigned) topMargin bottomMargin: (unsigned) bottomMargin topMargin:(unsigned) topMargin bottomMargin: (unsigned) bottomMargin
exposure:(unsigned) exposure; exposure:(unsigned) exposure;
- (void) gotNewSample:(GB_sample_t *)sample;
@end @end
static void vblank(GB_gameboy_t *gb) static void vblank(GB_gameboy_t *gb)
@ -118,6 +125,12 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height,
[self printImage:image height:height topMargin:top_margin bottomMargin:bottom_margin exposure:exposure]; [self printImage:image height:height topMargin:top_margin bottomMargin:bottom_margin exposure:exposure];
} }
static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample)
{
Document *self = (__bridge Document *)GB_get_user_data(gb);
[self gotNewSample:sample];
}
@implementation Document @implementation Document
{ {
GB_gameboy_t gb; GB_gameboy_t gb;
@ -133,6 +146,7 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height,
has_debugger_input = [[NSConditionLock alloc] initWithCondition:0]; has_debugger_input = [[NSConditionLock alloc] initWithCondition:0];
debugger_input_queue = [[NSMutableArray alloc] init]; debugger_input_queue = [[NSMutableArray alloc] init];
console_output_lock = [[NSRecursiveLock alloc] init]; console_output_lock = [[NSRecursiveLock alloc] init];
audioLock = [[NSCondition alloc] init];
} }
return self; return self;
} }
@ -184,6 +198,7 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height,
GB_set_camera_update_request_callback(&gb, cameraRequestUpdate); GB_set_camera_update_request_callback(&gb, cameraRequestUpdate);
GB_set_highpass_filter_mode(&gb, (GB_highpass_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBHighpassFilter"]); GB_set_highpass_filter_mode(&gb, (GB_highpass_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBHighpassFilter"]);
GB_set_rewind_length(&gb, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRewindLength"]); GB_set_rewind_length(&gb, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRewindLength"]);
GB_apu_set_sample_callback(&gb, audioCallback);
} }
- (void) vblank - (void) vblank
@ -201,13 +216,57 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height,
} }
} }
- (void)gotNewSample:(GB_sample_t *)sample
{
[audioLock lock];
if (self.audioClient.isPlaying) {
if (audioBufferPosition == audioBufferSize) {
if (audioBufferSize >= 0x4000) {
audioBufferPosition = 0;
[audioLock unlock];
return;
}
if (audioBufferSize == 0) {
audioBufferSize = 512;
}
else {
audioBufferSize += audioBufferSize >> 2;
}
audioBuffer = realloc(audioBuffer, sizeof(*sample) * audioBufferSize);
}
audioBuffer[audioBufferPosition++] = *sample;
}
if (audioBufferPosition == audioBufferNeeded) {
[audioLock signal];
audioBufferNeeded = 0;
}
[audioLock unlock];
}
- (void) run - (void) run
{ {
running = true; running = true;
GB_set_pixels_output(&gb, self.view.pixels); GB_set_pixels_output(&gb, self.view.pixels);
GB_set_sample_rate(&gb, 96000); GB_set_sample_rate(&gb, 96000);
self.audioClient = [[GBAudioClient alloc] initWithRendererBlock:^(UInt32 sampleRate, UInt32 nFrames, GB_sample_t *buffer) { self.audioClient = [[GBAudioClient alloc] initWithRendererBlock:^(UInt32 sampleRate, UInt32 nFrames, GB_sample_t *buffer) {
GB_apu_copy_buffer(&gb, buffer, nFrames); [audioLock lock];
if (audioBufferPosition < nFrames) {
audioBufferNeeded = nFrames;
[audioLock wait];
}
if (audioBufferPosition >= nFrames && audioBufferPosition < nFrames + 4800) {
memcpy(buffer, audioBuffer, nFrames * sizeof(*buffer));
memmove(audioBuffer, audioBuffer + nFrames, (audioBufferPosition - nFrames) * sizeof(*buffer));
audioBufferPosition = audioBufferPosition - nFrames;
}
else {
memcpy(buffer, audioBuffer + (audioBufferPosition - nFrames), nFrames * sizeof(*buffer));
audioBufferPosition = 0;
}
[audioLock unlock];
} andSampleRate:96000]; } andSampleRate:96000];
if (![[NSUserDefaults standardUserDefaults] boolForKey:@"Mute"]) { if (![[NSUserDefaults standardUserDefaults] boolForKey:@"Mute"]) {
[self.audioClient start]; [self.audioClient start];
@ -227,6 +286,11 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height,
} }
} }
[hex_timer invalidate]; [hex_timer invalidate];
[audioLock lock];
memset(audioBuffer, 0, (audioBufferSize - audioBufferPosition) * sizeof(*audioBuffer));
audioBufferPosition = audioBufferNeeded;
[audioLock signal];
[audioLock unlock];
[self.audioClient stop]; [self.audioClient stop];
self.audioClient = nil; self.audioClient = nil;
self.view.mouseHidingEnabled = NO; self.view.mouseHidingEnabled = NO;
@ -329,6 +393,9 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height,
if (cameraImage) { if (cameraImage) {
CVBufferRelease(cameraImage); CVBufferRelease(cameraImage);
} }
if (audioBuffer) {
free(audioBuffer);
}
} }
- (void)windowControllerDidLoadNib:(NSWindowController *)aController { - (void)windowControllerDidLoadNib:(NSWindowController *)aController {

View File

@ -1,6 +1,7 @@
#include <stdint.h> #include <stdint.h>
#include <math.h> #include <math.h>
#include <string.h> #include <string.h>
#include <assert.h>
#include "gb.h" #include "gb.h"
#define likely(x) __builtin_expect((x), 1) #define likely(x) __builtin_expect((x), 1)
@ -117,7 +118,7 @@ static double smooth(double x)
return 3*x*x - 2*x*x*x; return 3*x*x - 2*x*x*x;
} }
static void render(GB_gameboy_t *gb, bool no_downsampling, GB_sample_t *dest) static void render(GB_gameboy_t *gb)
{ {
GB_sample_t output = {0,0}; GB_sample_t output = {0,0};
@ -147,7 +148,7 @@ static void render(GB_gameboy_t *gb, bool no_downsampling, GB_sample_t *dest)
} }
} }
if (likely(gb->apu_output.last_update[i] == 0 || no_downsampling)) { if (likely(gb->apu_output.last_update[i] == 0)) {
output.left += gb->apu_output.current_sample[i].left * multiplier; output.left += gb->apu_output.current_sample[i].left * multiplier;
output.right += gb->apu_output.current_sample[i].right * multiplier; output.right += gb->apu_output.current_sample[i].right * multiplier;
} }
@ -205,17 +206,9 @@ static void render(GB_gameboy_t *gb, bool no_downsampling, GB_sample_t *dest)
} }
} }
if (dest) {
*dest = filtered_output;
return;
}
while (gb->apu_output.copy_in_progress); assert(gb->apu_output.sample_callback);
while (!__sync_bool_compare_and_swap(&gb->apu_output.lock, false, true)); gb->apu_output.sample_callback(gb, &filtered_output);
if (gb->apu_output.buffer_position < gb->apu_output.buffer_size) {
gb->apu_output.buffer[gb->apu_output.buffer_position++] = filtered_output;
}
gb->apu_output.lock = false;
} }
static uint16_t new_sweep_sample_legnth(GB_gameboy_t *gb) static uint16_t new_sweep_sample_legnth(GB_gameboy_t *gb)
@ -504,56 +497,12 @@ void GB_apu_run(GB_gameboy_t *gb)
if (gb->apu_output.sample_rate) { if (gb->apu_output.sample_rate) {
gb->apu_output.cycles_since_render += cycles; gb->apu_output.cycles_since_render += cycles;
if (gb->apu_output.sample_cycles > gb->apu_output.cycles_per_sample) { if (gb->apu_output.sample_cycles >= gb->apu_output.cycles_per_sample) {
gb->apu_output.sample_cycles -= gb->apu_output.cycles_per_sample; gb->apu_output.sample_cycles -= gb->apu_output.cycles_per_sample;
render(gb, false, NULL); render(gb);
} }
} }
} }
void GB_apu_copy_buffer(GB_gameboy_t *gb, GB_sample_t *dest, size_t count)
{
if (gb->sgb) {
if (GB_sgb_render_jingle(gb, dest, count)) return;
}
gb->apu_output.copy_in_progress = true;
/* TODO: Rewrite this as a proper cyclic buffer. This is a workaround to avoid a very rare crashing race condition */
size_t buffer_position = gb->apu_output.buffer_position;
if (!gb->apu_output.stream_started) {
// Intentionally fail the first copy to sync the stream with the Gameboy.
gb->apu_output.stream_started = true;
gb->apu_output.buffer_position = 0;
buffer_position = 0;
}
if (count > buffer_position) {
// GB_log(gb, "Audio underflow: %d\n", count - gb->apu_output.buffer_position);
GB_sample_t output;
render(gb, true, &output);
for (unsigned i = 0; i < count - buffer_position; i++) {
dest[buffer_position + i] = output;
}
if (buffer_position) {
if (gb->apu_output.buffer_size + (count - buffer_position) < count * 3) {
gb->apu_output.buffer_size += count - buffer_position;
gb->apu_output.buffer = realloc(gb->apu_output.buffer,
gb->apu_output.buffer_size * sizeof(*gb->apu_output.buffer));
gb->apu_output.stream_started = false;
}
}
count = buffer_position;
}
memcpy(dest, gb->apu_output.buffer, count * sizeof(*gb->apu_output.buffer));
memmove(gb->apu_output.buffer, gb->apu_output.buffer + count, (buffer_position - count) * sizeof(*gb->apu_output.buffer));
gb->apu_output.buffer_position -= count;
gb->apu_output.copy_in_progress = false;
}
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));
@ -1009,26 +958,21 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
gb->io_registers[reg] = value; gb->io_registers[reg] = value;
} }
size_t GB_apu_get_current_buffer_length(GB_gameboy_t *gb) void GB_set_sample_rate(GB_gameboy_t *gb, unsigned sample_rate)
{ {
return gb->apu_output.buffer_position;
}
void GB_set_sample_rate(GB_gameboy_t *gb, unsigned int sample_rate)
{
if (gb->apu_output.buffer) {
free(gb->apu_output.buffer);
}
gb->apu_output.buffer_size = sample_rate / 25; // 40ms delay
gb->apu_output.buffer = malloc(gb->apu_output.buffer_size * sizeof(*gb->apu_output.buffer));
gb->apu_output.sample_rate = sample_rate; gb->apu_output.sample_rate = sample_rate;
gb->apu_output.buffer_position = 0;
if (sample_rate) { if (sample_rate) {
gb->apu_output.highpass_rate = pow(0.999958, GB_get_clock_rate(gb) / (double)sample_rate); gb->apu_output.highpass_rate = pow(0.999958, GB_get_clock_rate(gb) / (double)sample_rate);
} }
GB_apu_update_cycles_per_sample(gb); GB_apu_update_cycles_per_sample(gb);
} }
void GB_apu_set_sample_callback(GB_gameboy_t *gb, GB_sample_callback_t callback)
{
gb->apu_output.sample_callback = callback;
}
void GB_set_highpass_filter_mode(GB_gameboy_t *gb, GB_highpass_mode_t mode) void GB_set_highpass_filter_mode(GB_gameboy_t *gb, GB_highpass_mode_t mode)
{ {
gb->apu_output.highpass_mode = mode; gb->apu_output.highpass_mode = mode;

View File

@ -46,6 +46,8 @@ enum GB_CHANNELS {
GB_N_CHANNELS GB_N_CHANNELS
}; };
typedef void (*GB_sample_callback_t)(GB_gameboy_t *gb, GB_sample_t *sample);
typedef struct typedef struct
{ {
bool global_enable; bool global_enable;
@ -126,14 +128,6 @@ typedef enum {
typedef struct { typedef struct {
unsigned sample_rate; unsigned sample_rate;
GB_sample_t *buffer;
size_t buffer_size;
size_t buffer_position;
bool stream_started; /* detects first copy request to minimize lag */
volatile bool copy_in_progress;
volatile bool lock;
double sample_cycles; // In 8 MHz units double sample_cycles; // In 8 MHz units
double cycles_per_sample; double cycles_per_sample;
@ -147,13 +141,13 @@ typedef struct {
GB_highpass_mode_t highpass_mode; GB_highpass_mode_t highpass_mode;
double highpass_rate; double highpass_rate;
GB_double_sample_t highpass_diff; GB_double_sample_t highpass_diff;
GB_sample_callback_t sample_callback;
} GB_apu_output_t; } GB_apu_output_t;
void GB_set_sample_rate(GB_gameboy_t *gb, unsigned int sample_rate); void GB_set_sample_rate(GB_gameboy_t *gb, unsigned sample_rate);
void GB_apu_copy_buffer(GB_gameboy_t *gb, GB_sample_t *dest, size_t count);
size_t GB_apu_get_current_buffer_length(GB_gameboy_t *gb);
void GB_set_highpass_filter_mode(GB_gameboy_t *gb, GB_highpass_mode_t mode); void GB_set_highpass_filter_mode(GB_gameboy_t *gb, GB_highpass_mode_t mode);
void GB_apu_set_sample_callback(GB_gameboy_t *gb, GB_sample_callback_t callback);
#ifdef GB_INTERNAL #ifdef GB_INTERNAL
bool GB_apu_is_DAC_enabled(GB_gameboy_t *gb, unsigned index); bool GB_apu_is_DAC_enabled(GB_gameboy_t *gb, unsigned index);
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);

View File

@ -455,12 +455,12 @@ static lvalue_t debugger_evaluate_lvalue(GB_gameboy_t *gb, const char *string,
case 'p': if (string[1] == 'c') return (lvalue_t){LVALUE_REG16, .register_address = &gb->pc}; case 'p': if (string[1] == 'c') return (lvalue_t){LVALUE_REG16, .register_address = &gb->pc};
} }
} }
GB_log(gb, "Unknown register: %.*s\n", (unsigned int) length, string); GB_log(gb, "Unknown register: %.*s\n", (unsigned) length, string);
*error = true; *error = true;
return (lvalue_t){0,}; return (lvalue_t){0,};
} }
GB_log(gb, "Expression is not an lvalue: %.*s\n", (unsigned int) length, string); GB_log(gb, "Expression is not an lvalue: %.*s\n", (unsigned) length, string);
*error = true; *error = true;
return (lvalue_t){0,}; return (lvalue_t){0,};
} }
@ -564,8 +564,8 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string,
} }
// Search for lowest priority operator // Search for lowest priority operator
signed int depth = 0; signed int depth = 0;
unsigned int operator_index = -1; unsigned operator_index = -1;
unsigned int operator_pos = 0; unsigned operator_pos = 0;
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
if (string[i] == '(') depth++; if (string[i] == '(') depth++;
else if (string[i] == ')') depth--; else if (string[i] == ')') depth--;
@ -593,7 +593,7 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string,
} }
} }
if (operator_index != -1) { if (operator_index != -1) {
unsigned int right_start = (unsigned int)(operator_pos + strlen(operators[operator_index].string)); unsigned right_start = (unsigned)(operator_pos + strlen(operators[operator_index].string));
value_t right = debugger_evaluate(gb, string + right_start, length - right_start, error, watchpoint_address, watchpoint_new_value); value_t right = debugger_evaluate(gb, string + right_start, length - right_start, error, watchpoint_address, watchpoint_new_value);
if (*error) goto exit; if (*error) goto exit;
if (operators[operator_index].lvalue_operator) { if (operators[operator_index].lvalue_operator) {
@ -661,7 +661,7 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string,
goto exit; goto exit;
} }
GB_log(gb, "Unknown register or symbol: %.*s\n", (unsigned int) length, string); GB_log(gb, "Unknown register or symbol: %.*s\n", (unsigned) length, string);
*error = true; *error = true;
goto exit; goto exit;
} }
@ -675,7 +675,7 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string,
} }
uint16_t literal = (uint16_t) (strtol(string, &end, base)); uint16_t literal = (uint16_t) (strtol(string, &end, base));
if (end != string + length) { if (end != string + length) {
GB_log(gb, "Failed to parse: %.*s\n", (unsigned int) length, string); GB_log(gb, "Failed to parse: %.*s\n", (unsigned) length, string);
*error = true; *error = true;
goto exit; goto exit;
} }
@ -880,13 +880,13 @@ static bool breakpoint(GB_gameboy_t *gb, char *arguments, char *modifiers, const
condition += strlen(" if "); condition += strlen(" if ");
/* Verify condition is sane (Todo: This might have side effects!) */ /* Verify condition is sane (Todo: This might have side effects!) */
bool error; bool error;
debugger_evaluate(gb, condition, (unsigned int)strlen(condition), &error, NULL, NULL); debugger_evaluate(gb, condition, (unsigned)strlen(condition), &error, NULL, NULL);
if (error) return true; if (error) return true;
} }
bool error; bool error;
value_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL); value_t result = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL, NULL);
uint32_t key = BP_KEY(result); uint32_t key = BP_KEY(result);
if (error) return true; if (error) return true;
@ -949,7 +949,7 @@ static bool delete(GB_gameboy_t *gb, char *arguments, char *modifiers, const deb
} }
bool error; bool error;
value_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL); value_t result = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL, NULL);
uint32_t key = BP_KEY(result); uint32_t key = BP_KEY(result);
if (error) return true; if (error) return true;
@ -1065,13 +1065,13 @@ print_usage:
/* To make $new and $old legal */ /* To make $new and $old legal */
uint16_t dummy = 0; uint16_t dummy = 0;
uint8_t dummy2 = 0; uint8_t dummy2 = 0;
debugger_evaluate(gb, condition, (unsigned int)strlen(condition), &error, &dummy, &dummy2); debugger_evaluate(gb, condition, (unsigned)strlen(condition), &error, &dummy, &dummy2);
if (error) return true; if (error) return true;
} }
bool error; bool error;
value_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL); value_t result = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL, NULL);
uint32_t key = WP_KEY(result); uint32_t key = WP_KEY(result);
if (error) return true; if (error) return true;
@ -1132,7 +1132,7 @@ static bool unwatch(GB_gameboy_t *gb, char *arguments, char *modifiers, const de
} }
bool error; bool error;
value_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL); value_t result = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL, NULL);
uint32_t key = WP_KEY(result); uint32_t key = WP_KEY(result);
if (error) return true; if (error) return true;
@ -1234,7 +1234,7 @@ static bool _should_break(GB_gameboy_t *gb, value_t addr, bool jump_to)
} }
bool error; bool error;
bool condition = debugger_evaluate(gb, gb->breakpoints[index].condition, bool condition = debugger_evaluate(gb, gb->breakpoints[index].condition,
(unsigned int)strlen(gb->breakpoints[index].condition), &error, NULL, NULL).value; (unsigned)strlen(gb->breakpoints[index].condition), &error, NULL, NULL).value;
if (error) { if (error) {
/* Should never happen */ /* Should never happen */
GB_log(gb, "An internal error has occured\n"); GB_log(gb, "An internal error has occured\n");
@ -1273,7 +1273,7 @@ static bool print(GB_gameboy_t *gb, char *arguments, char *modifiers, const debu
} }
bool error; bool error;
value_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL); value_t result = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL, NULL);
if (!error) { if (!error) {
switch (modifiers[0]) { switch (modifiers[0]) {
case 'a': case 'a':
@ -1319,7 +1319,7 @@ static bool examine(GB_gameboy_t *gb, char *arguments, char *modifiers, const de
} }
bool error; bool error;
value_t addr = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL); value_t addr = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL, NULL);
uint16_t count = 32; uint16_t count = 32;
if (modifiers) { if (modifiers) {
@ -1371,7 +1371,7 @@ static bool disassemble(GB_gameboy_t *gb, char *arguments, char *modifiers, cons
} }
bool error; bool error;
value_t addr = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL); value_t addr = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL, NULL);
uint16_t count = 5; uint16_t count = 5;
if (modifiers) { if (modifiers) {
@ -1468,7 +1468,7 @@ static bool backtrace(GB_gameboy_t *gb, char *arguments, char *modifiers, const
} }
GB_log(gb, " 1. %s\n", debugger_value_to_string(gb, (value_t){true, bank_for_addr(gb, gb->pc), gb->pc}, true)); GB_log(gb, " 1. %s\n", debugger_value_to_string(gb, (value_t){true, bank_for_addr(gb, gb->pc), gb->pc}, true));
for (unsigned int i = gb->backtrace_size; i--;) { for (unsigned i = gb->backtrace_size; i--;) {
GB_log(gb, "%3d. %s\n", gb->backtrace_size - i + 1, debugger_value_to_string(gb, (value_t){true, gb->backtrace_returns[i].bank, gb->backtrace_returns[i].addr}, true)); GB_log(gb, "%3d. %s\n", gb->backtrace_size - i + 1, debugger_value_to_string(gb, (value_t){true, gb->backtrace_returns[i].bank, gb->backtrace_returns[i].addr}, true));
} }
@ -1937,7 +1937,7 @@ static bool _GB_debugger_test_write_watchpoint(GB_gameboy_t *gb, value_t addr, u
} }
bool error; bool error;
bool condition = debugger_evaluate(gb, gb->watchpoints[index].condition, bool condition = debugger_evaluate(gb, gb->watchpoints[index].condition,
(unsigned int)strlen(gb->watchpoints[index].condition), &error, &addr.value, &value).value; (unsigned)strlen(gb->watchpoints[index].condition), &error, &addr.value, &value).value;
if (error) { if (error) {
/* Should never happen */ /* Should never happen */
GB_log(gb, "An internal error has occured\n"); GB_log(gb, "An internal error has occured\n");
@ -1982,7 +1982,7 @@ static bool _GB_debugger_test_read_watchpoint(GB_gameboy_t *gb, value_t addr)
} }
bool error; bool error;
bool condition = debugger_evaluate(gb, gb->watchpoints[index].condition, bool condition = debugger_evaluate(gb, gb->watchpoints[index].condition,
(unsigned int)strlen(gb->watchpoints[index].condition), &error, &addr.value, NULL).value; (unsigned)strlen(gb->watchpoints[index].condition), &error, &addr.value, NULL).value;
if (error) { if (error) {
/* Should never happen */ /* Should never happen */
GB_log(gb, "An internal error has occured\n"); GB_log(gb, "An internal error has occured\n");
@ -2169,7 +2169,7 @@ void GB_debugger_load_symbol_file(GB_gameboy_t *gb, const char *path)
} }
if (length == 0) continue; if (length == 0) continue;
unsigned int bank, address; unsigned bank, address;
char symbol[length]; char symbol[length];
if (sscanf(line, "%02x:%04x %s", &bank, &address, symbol) == 3) { if (sscanf(line, "%02x:%04x %s", &bank, &address, symbol) == 3) {

View File

@ -135,9 +135,6 @@ void GB_free(GB_gameboy_t *gb)
if (gb->rom) { if (gb->rom) {
free(gb->rom); free(gb->rom);
} }
if (gb->apu_output.buffer) {
free(gb->apu_output.buffer);
}
if (gb->breakpoints) { if (gb->breakpoints) {
free(gb->breakpoints); free(gb->breakpoints);
} }

View File

@ -555,7 +555,7 @@ struct GB_gameboy_internal_s {
uint16_t addr_for_call_depth[0x200]; uint16_t addr_for_call_depth[0x200];
/* Backtrace */ /* Backtrace */
unsigned int backtrace_size; unsigned backtrace_size;
uint16_t backtrace_sps[0x200]; uint16_t backtrace_sps[0x200];
struct { struct {
uint16_t bank; uint16_t bank;

View File

@ -126,13 +126,13 @@ static uint8_t read_rom(GB_gameboy_t *gb, uint16_t addr)
if (!gb->rom_size) { if (!gb->rom_size) {
return 0xFF; return 0xFF;
} }
unsigned int effective_address = (addr & 0x3FFF) + gb->mbc_rom0_bank * 0x4000; unsigned effective_address = (addr & 0x3FFF) + gb->mbc_rom0_bank * 0x4000;
return gb->rom[effective_address & (gb->rom_size - 1)]; return gb->rom[effective_address & (gb->rom_size - 1)];
} }
static uint8_t read_mbc_rom(GB_gameboy_t *gb, uint16_t addr) static uint8_t read_mbc_rom(GB_gameboy_t *gb, uint16_t addr)
{ {
unsigned int effective_address = (addr & 0x3FFF) + gb->mbc_rom_bank * 0x4000; unsigned effective_address = (addr & 0x3FFF) + gb->mbc_rom_bank * 0x4000;
return gb->rom[effective_address & (gb->rom_size - 1)]; return gb->rom[effective_address & (gb->rom_size - 1)];
} }

View File

@ -1,6 +1,7 @@
#include "gb.h" #include "gb.h"
#include "random.h" #include "random.h"
#include <math.h> #include <math.h>
#include <assert.h>
#define INTRO_ANIMATION_LENGTH 200 #define INTRO_ANIMATION_LENGTH 200
@ -461,8 +462,13 @@ static void render_boot_animation (GB_gameboy_t *gb)
} }
} }
static void render_jingle(GB_gameboy_t *gb, size_t count);
void GB_sgb_render(GB_gameboy_t *gb) void GB_sgb_render(GB_gameboy_t *gb)
{ {
if (gb->apu_output.sample_rate) {
render_jingle(gb, gb->apu_output.sample_rate / (GB_get_clock_rate(gb) / LCDC_PERIOD));
}
if (gb->sgb->intro_animation < INTRO_ANIMATION_LENGTH) gb->sgb->intro_animation++; if (gb->sgb->intro_animation < INTRO_ANIMATION_LENGTH) gb->sgb->intro_animation++;
if (!gb->screen || !gb->rgb_encode_callback) return; if (!gb->screen || !gb->rgb_encode_callback) return;
@ -689,7 +695,7 @@ static double random_double(void)
return ((signed)(GB_random32() % 0x10001) - 0x8000) / (double) 0x8000; return ((signed)(GB_random32() % 0x10001) - 0x8000) / (double) 0x8000;
} }
bool GB_sgb_render_jingle(GB_gameboy_t *gb, GB_sample_t *dest, size_t count) static void render_jingle(GB_gameboy_t *gb, size_t count)
{ {
const double frequencies[7] = { const double frequencies[7] = {
466.16, // Bb4 466.16, // Bb4
@ -701,15 +707,17 @@ bool GB_sgb_render_jingle(GB_gameboy_t *gb, GB_sample_t *dest, size_t count)
1567.98, // G6 1567.98, // G6
}; };
assert(gb->apu_output.sample_callback);
if (gb->sgb->intro_animation < 0) { if (gb->sgb->intro_animation < 0) {
GB_sample_t sample = {0, 0};
for (unsigned i = 0; i < count; i++) { for (unsigned i = 0; i < count; i++) {
dest->left = dest->right = 0; gb->apu_output.sample_callback(gb, &sample);
dest++;
} }
return true; return;
} }
if (gb->sgb->intro_animation >= INTRO_ANIMATION_LENGTH) return false; if (gb->sgb->intro_animation >= INTRO_ANIMATION_LENGTH) return;
signed jingle_stage = (gb->sgb->intro_animation - 64) / 3; signed jingle_stage = (gb->sgb->intro_animation - 64) / 3;
double sweep_cutoff_ratio = 2000.0 * pow(2, gb->sgb->intro_animation / 20.0) / gb->apu_output.sample_rate; double sweep_cutoff_ratio = 2000.0 * pow(2, gb->sgb->intro_animation / 20.0) / gb->apu_output.sample_rate;
@ -718,6 +726,7 @@ bool GB_sgb_render_jingle(GB_gameboy_t *gb, GB_sample_t *dest, size_t count)
sweep_cutoff_ratio = 1; sweep_cutoff_ratio = 1;
} }
GB_sample_t stereo;
for (unsigned i = 0; i < count; i++) { for (unsigned i = 0; i < count; i++) {
double sample = 0; double sample = 0;
for (signed f = 0; f < 7 && f < jingle_stage; f++) { for (signed f = 0; f < 7 && f < jingle_stage; f++) {
@ -738,10 +747,10 @@ bool GB_sgb_render_jingle(GB_gameboy_t *gb, GB_sample_t *dest, size_t count)
sample += gb->sgb_intro_sweep_previous_sample * pow((120 - gb->sgb->intro_animation) / 120.0, 2) * 0.8; sample += gb->sgb_intro_sweep_previous_sample * pow((120 - gb->sgb->intro_animation) / 120.0, 2) * 0.8;
} }
dest->left = dest->right = sample * 0x7000; stereo.left = stereo.right = sample * 0x7000;
dest++; gb->apu_output.sample_callback(gb, &stereo);
} }
return true; return;
} }

View File

@ -54,7 +54,6 @@ struct GB_sgb_s {
void GB_sgb_write(GB_gameboy_t *gb, uint8_t value); void GB_sgb_write(GB_gameboy_t *gb, uint8_t value);
void GB_sgb_render(GB_gameboy_t *gb); void GB_sgb_render(GB_gameboy_t *gb);
void GB_sgb_load_default_data(GB_gameboy_t *gb); void GB_sgb_load_default_data(GB_gameboy_t *gb);
bool GB_sgb_render_jingle(GB_gameboy_t *gb, GB_sample_t *dest, size_t count);
#endif #endif

View File

@ -8,7 +8,7 @@
#include <sys/time.h> #include <sys/time.h>
#endif #endif
static const unsigned int GB_TAC_TRIGGER_BITS[] = {512, 8, 32, 128}; static const unsigned GB_TAC_TRIGGER_BITS[] = {512, 8, 32, 128};
#ifndef DISABLE_TIMEKEEPING #ifndef DISABLE_TIMEKEEPING
static int64_t get_nanoseconds(void) static int64_t get_nanoseconds(void)
@ -251,8 +251,8 @@ void GB_emulate_timer_glitch(GB_gameboy_t *gb, uint8_t old_tac, uint8_t new_tac)
/* Glitch only happens when old_tac is enabled. */ /* Glitch only happens when old_tac is enabled. */
if (!(old_tac & 4)) return; if (!(old_tac & 4)) return;
unsigned int old_clocks = GB_TAC_TRIGGER_BITS[old_tac & 3]; unsigned old_clocks = GB_TAC_TRIGGER_BITS[old_tac & 3];
unsigned int new_clocks = GB_TAC_TRIGGER_BITS[new_tac & 3]; unsigned new_clocks = GB_TAC_TRIGGER_BITS[new_tac & 3];
/* The bit used for overflow testing must have been 1 */ /* The bit used for overflow testing must have been 1 */
if (gb->div_counter & old_clocks) { if (gb->div_counter & old_clocks) {

View File

@ -33,6 +33,7 @@ static double clock_mutliplier = 1.0;
static char *filename = NULL; static char *filename = NULL;
static typeof(free) *free_function = NULL; static typeof(free) *free_function = NULL;
static char *battery_save_path_ptr; static char *battery_save_path_ptr;
static bool skip_audio;
SDL_AudioDeviceID device_id; SDL_AudioDeviceID device_id;
@ -336,6 +337,8 @@ static void vblank(GB_gameboy_t *gb)
} }
do_rewind = rewind_down; do_rewind = rewind_down;
handle_events(gb); handle_events(gb);
skip_audio = (SDL_GetQueuedAudioSize(device_id) / sizeof(GB_sample_t)) > have_aspec.freq / 20;
} }
@ -355,17 +358,13 @@ static void debugger_interrupt(int ignore)
GB_debugger_break(&gb); GB_debugger_break(&gb);
} }
static void gb_audio_callback(GB_gameboy_t *gb, GB_sample_t *sample)
static void audio_callback(void *gb, Uint8 *stream, int len)
{ {
if (GB_is_inited(gb)) { if (skip_audio) return;
GB_apu_copy_buffer(gb, (GB_sample_t *) stream, len / sizeof(GB_sample_t)); SDL_QueueAudio(device_id, sample, sizeof(*sample));
}
else {
memset(stream, 0, len);
}
} }
static bool handle_pending_command(void) static bool handle_pending_command(void)
{ {
switch (pending_command) { switch (pending_command) {
@ -436,6 +435,7 @@ restart:
GB_set_highpass_filter_mode(&gb, configuration.highpass_mode); GB_set_highpass_filter_mode(&gb, configuration.highpass_mode);
GB_set_rewind_length(&gb, configuration.rewind_length); GB_set_rewind_length(&gb, configuration.rewind_length);
GB_set_update_input_hint_callback(&gb, handle_events); GB_set_update_input_hint_callback(&gb, handle_events);
GB_apu_set_sample_callback(&gb, gb_audio_callback);
} }
SDL_DestroyTexture(texture); SDL_DestroyTexture(texture);
@ -612,9 +612,6 @@ int main(int argc, char **argv)
} }
#endif #endif
want_aspec.callback = audio_callback;
want_aspec.userdata = &gb;
device_id = SDL_OpenAudioDevice(0, 0, &want_aspec, &have_aspec, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE); device_id = SDL_OpenAudioDevice(0, 0, &want_aspec, &have_aspec, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE);
/* Start Audio */ /* Start Audio */

View File

@ -12,6 +12,7 @@ SOURCES_C := $(CORE_DIR)/Core/gb.c \
$(CORE_DIR)/Core/sm83_cpu.c \ $(CORE_DIR)/Core/sm83_cpu.c \
$(CORE_DIR)/Core/joypad.c \ $(CORE_DIR)/Core/joypad.c \
$(CORE_DIR)/Core/save_state.c \ $(CORE_DIR)/Core/save_state.c \
$(CORE_DIR)/Core/random.c \
$(CORE_DIR)/libretro/agb_boot.c \ $(CORE_DIR)/libretro/agb_boot.c \
$(CORE_DIR)/libretro/cgb_boot.c \ $(CORE_DIR)/libretro/cgb_boot.c \
$(CORE_DIR)/libretro/dmg_boot.c \ $(CORE_DIR)/libretro/dmg_boot.c \

View File

@ -150,34 +150,22 @@ static void GB_update_keys_status(GB_gameboy_t *gb, unsigned port)
} }
static void audio_callback(void *gb) static void audio_callback(GB_gameboy_t *gb, GB_sample_t *sample)
{ {
size_t length = GB_apu_get_current_buffer_length(gb); if ((audio_out == GB_1 && gb == &gameboy[0]) ||
(audio_out == GB_2 && gb == &gameboy[1])) {
while (length > sizeof(soundbuf) / 4) audio_batch_cb((void*)sample, 1);
{
GB_apu_copy_buffer(gb, (GB_sample_t *) soundbuf, 1024);
audio_batch_cb(soundbuf, 1024);
length -= 1024;
}
if (length) {
GB_apu_copy_buffer(gb, (GB_sample_t *) soundbuf, length);
audio_batch_cb(soundbuf, length);
} }
} }
static void vblank1(GB_gameboy_t *gb) static void vblank1(GB_gameboy_t *gb)
{ {
vblank1_occurred = true; vblank1_occurred = true;
if (audio_out == GB_1)
audio_callback(gb);
} }
static void vblank2(GB_gameboy_t *gb) static void vblank2(GB_gameboy_t *gb)
{ {
vblank2_occurred = true; vblank2_occurred = true;
if (audio_out == GB_2)
audio_callback(gb);
} }
static bool bit_to_send1 = true, bit_to_send2 = true; static bool bit_to_send1 = true, bit_to_send2 = true;
@ -383,6 +371,7 @@ static void init_for_current_model(unsigned id)
GB_set_pixels_output(&gameboy[i],(unsigned int*)(frame_buf + i * ((model[i] == MODEL_SGB || model[i] == MODEL_SGB2) ? SGB_VIDEO_PIXELS : VIDEO_PIXELS))); GB_set_pixels_output(&gameboy[i],(unsigned int*)(frame_buf + i * ((model[i] == MODEL_SGB || model[i] == MODEL_SGB2) ? SGB_VIDEO_PIXELS : VIDEO_PIXELS)));
GB_set_rgb_encode_callback(&gameboy[i], rgb_encode); GB_set_rgb_encode_callback(&gameboy[i], rgb_encode);
GB_set_sample_rate(&gameboy[i], AUDIO_FREQUENCY); GB_set_sample_rate(&gameboy[i], AUDIO_FREQUENCY);
GB_apu_set_sample_callback(&gameboy[i], audio_callback);
/* todo: attempt to make these more generic */ /* todo: attempt to make these more generic */
GB_set_vblank_callback(&gameboy[0], (GB_vblank_callback_t) vblank1); GB_set_vblank_callback(&gameboy[0], (GB_vblank_callback_t) vblank1);