Added stereo support. Correct some PCM register behavior.

This commit is contained in:
Lior Halphon 2016-06-10 15:28:50 +03:00
parent 4d8f2cfac8
commit 6bc64a9902
8 changed files with 51 additions and 44 deletions

View File

@ -1,5 +1,5 @@
#include <CoreAudio/CoreAudio.h> #include <CoreAudio/CoreAudio.h>
#include "AudioClient.h" #include "GBAudioClient.h"
#include "Document.h" #include "Document.h"
#include "AppDelegate.h" #include "AppDelegate.h"
#include "gb.h" #include "gb.h"
@ -15,7 +15,7 @@
NSString *lastConsoleInput; NSString *lastConsoleInput;
} }
@property AudioClient *audioClient; @property GBAudioClient *audioClient;
- (void) vblank; - (void) vblank;
- (void) log: (const char *) log withAttributes: (gb_log_attributes) attributes; - (void) log: (const char *) log withAttributes: (gb_log_attributes) attributes;
- (const char *) getDebuggerInput; - (const char *) getDebuggerInput;
@ -103,8 +103,7 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, unsigned char r, unsigned char g, un
gb_set_pixels_output(&gb, self.view.pixels); gb_set_pixels_output(&gb, self.view.pixels);
self.view.gb = &gb; self.view.gb = &gb;
gb_set_sample_rate(&gb, 96000); gb_set_sample_rate(&gb, 96000);
self.audioClient = [[AudioClient alloc] initWithRendererBlock:^(UInt32 sampleRate, UInt32 nFrames, SInt16 *buffer) { self.audioClient = [[GBAudioClient alloc] initWithRendererBlock:^(UInt32 sampleRate, UInt32 nFrames, GB_sample_t *buffer) {
//apu_render(&gb, sampleRate, nFrames, buffer);
apu_copy_buffer(&gb, buffer, nFrames); apu_copy_buffer(&gb, buffer, nFrames);
} andSampleRate:96000]; } andSampleRate:96000];
[self.audioClient start]; [self.audioClient start];

View File

@ -1,11 +1,12 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import "apu.h"
@interface AudioClient : NSObject @interface GBAudioClient : NSObject
@property (strong) void (^renderBlock)(UInt32 sampleRate, UInt32 nFrames, SInt16 *buffer); @property (strong) void (^renderBlock)(UInt32 sampleRate, UInt32 nFrames, GB_sample_t *buffer);
@property (readonly) UInt32 rate; @property (readonly) UInt32 rate;
@property (readonly, getter=isPlaying) bool playing; @property (readonly, getter=isPlaying) bool playing;
-(void) start; -(void) start;
-(void) stop; -(void) stop;
-(id) initWithRendererBlock:(void (^)(UInt32 sampleRate, UInt32 nFrames, SInt16 *buffer)) block -(id) initWithRendererBlock:(void (^)(UInt32 sampleRate, UInt32 nFrames, GB_sample_t *buffer)) block
andSampleRate:(UInt32) rate; andSampleRate:(UInt32) rate;
@end @end

View File

@ -1,9 +1,9 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import <AudioToolbox/AudioToolbox.h> #import <AudioToolbox/AudioToolbox.h>
#import "AudioClient.h" #import "GBAudioClient.h"
static OSStatus render( static OSStatus render(
AudioClient *self, GBAudioClient *self,
AudioUnitRenderActionFlags *ioActionFlags, AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp, const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber, UInt32 inBusNumber,
@ -11,23 +11,19 @@ static OSStatus render(
AudioBufferList *ioData) AudioBufferList *ioData)
{ {
// This is a mono tone generator so we only need the first buffer GB_sample_t *buffer = (GB_sample_t *)ioData->mBuffers[0].mData;
const int channel = 0;
SInt16 *buffer = (SInt16 *)ioData->mBuffers[channel].mData;
self.renderBlock(self.rate, inNumberFrames, buffer); self.renderBlock(self.rate, inNumberFrames, buffer);
return noErr; return noErr;
} }
@implementation AudioClient @implementation GBAudioClient
{ {
AudioComponentInstance audioUnit; AudioComponentInstance audioUnit;
} }
-(id) initWithRendererBlock:(void (^)(UInt32 sampleRate, UInt32 nFrames, SInt16 *buffer)) block -(id) initWithRendererBlock:(void (^)(UInt32 sampleRate, UInt32 nFrames, GB_sample_t *buffer)) block
andSampleRate:(UInt32) rate andSampleRate:(UInt32) rate
{ {
if(!(self = [super init])) if(!(self = [super init]))
@ -70,10 +66,10 @@ static OSStatus render(
streamFormat.mFormatID = kAudioFormatLinearPCM; streamFormat.mFormatID = kAudioFormatLinearPCM;
streamFormat.mFormatFlags = streamFormat.mFormatFlags =
kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked | kAudioFormatFlagsNativeEndian; kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked | kAudioFormatFlagsNativeEndian;
streamFormat.mBytesPerPacket = 2; streamFormat.mBytesPerPacket = 4;
streamFormat.mFramesPerPacket = 1; streamFormat.mFramesPerPacket = 1;
streamFormat.mBytesPerFrame = 2; streamFormat.mBytesPerFrame = 4;
streamFormat.mChannelsPerFrame = 1; streamFormat.mChannelsPerFrame = 2;
streamFormat.mBitsPerChannel = 2 * 8; streamFormat.mBitsPerChannel = 2 * 8;
err = AudioUnitSetProperty (audioUnit, err = AudioUnitSetProperty (audioUnit,
kAudioUnitProperty_StreamFormat, kAudioUnitProperty_StreamFormat,

View File

@ -60,10 +60,10 @@ static int16_t step_lfsr(uint16_t lfsr, bool uses_7_bit)
/* General Todo: The APU emulation seems to fail many accuracy tests. It might require a rewrite with /* General Todo: The APU emulation seems to fail many accuracy tests. It might require a rewrite with
these tests in mind. */ these tests in mind. */
void apu_render(GB_gameboy_t *gb, unsigned long sample_rate, unsigned long n_samples, int16_t *samples) void apu_render(GB_gameboy_t *gb, unsigned long sample_rate, unsigned long n_samples, GB_sample_t *samples)
{ {
for (; n_samples--; samples++) { for (; n_samples--; samples++) {
*samples = 0; samples->left = samples->right = 0;
if (!gb->apu.global_enable) { if (!gb->apu.global_enable) {
continue; continue;
} }
@ -71,39 +71,45 @@ void apu_render(GB_gameboy_t *gb, unsigned long sample_rate, unsigned long n_sam
gb->io_registers[GB_IO_PCM_12] = 0; gb->io_registers[GB_IO_PCM_12] = 0;
gb->io_registers[GB_IO_PCM_34] = 0; gb->io_registers[GB_IO_PCM_34] = 0;
// Todo: Stereo support {
if (gb->apu.left_on[0] || gb->apu.right_on[0]) {
int16_t sample = generate_square(gb->apu.wave_channels[0].phase, int16_t sample = generate_square(gb->apu.wave_channels[0].phase,
gb->apu.wave_channels[0].amplitude, gb->apu.wave_channels[0].amplitude,
gb->apu.wave_channels[0].duty); gb->apu.wave_channels[0].duty);
*samples += sample; if (gb->apu.left_on [0]) samples->left += sample;
if (gb->apu.right_on[0]) samples->right += sample;
gb->io_registers[GB_IO_PCM_12] = ((int)sample) * 0xF / MAX_CH_AMP; gb->io_registers[GB_IO_PCM_12] = ((int)sample) * 0xF / MAX_CH_AMP;
} }
if (gb->apu.left_on[1] || gb->apu.right_on[1]) {
{
int16_t sample = generate_square(gb->apu.wave_channels[1].phase, int16_t sample = generate_square(gb->apu.wave_channels[1].phase,
gb->apu.wave_channels[1].amplitude, gb->apu.wave_channels[1].amplitude,
gb->apu.wave_channels[1].duty); gb->apu.wave_channels[1].duty);
*samples += sample; if (gb->apu.left_on [1]) samples->left += sample;
if (gb->apu.right_on[1]) samples->right += sample;
gb->io_registers[GB_IO_PCM_12] |= (((int)sample) * 0xF / MAX_CH_AMP) << 4; gb->io_registers[GB_IO_PCM_12] |= (((int)sample) * 0xF / MAX_CH_AMP) << 4;
} }
if (gb->apu.wave_enable && (gb->apu.left_on[2] || gb->apu.right_on[2])) {
{
int16_t sample = generate_wave(gb->apu.wave_channels[2].phase, int16_t sample = generate_wave(gb->apu.wave_channels[2].phase,
MAX_CH_AMP, MAX_CH_AMP,
gb->apu.wave_form, gb->apu.wave_form,
gb->apu.wave_shift); gb->apu.wave_shift);
*samples += sample; if (gb->apu.left_on [2]) samples->left += sample;
if (gb->apu.right_on[2]) samples->right += sample;
gb->io_registers[GB_IO_PCM_34] = ((int)sample) * 0xF / MAX_CH_AMP; gb->io_registers[GB_IO_PCM_34] = ((int)sample) * 0xF / MAX_CH_AMP;
} }
if (gb->apu.left_on[3] || gb->apu.right_on[3]) {
{
int16_t sample = generate_noise(gb->apu.wave_channels[3].phase, int16_t sample = generate_noise(gb->apu.wave_channels[3].phase,
gb->apu.wave_channels[3].amplitude, gb->apu.wave_channels[3].amplitude,
gb->apu.lfsr); gb->apu.lfsr);
*samples += sample; if (gb->apu.left_on [3]) samples->left += sample;
if (gb->apu.right_on[3]) samples->right += sample;
gb->io_registers[GB_IO_PCM_34] |= (((int)sample) * 0xF / MAX_CH_AMP) << 4; gb->io_registers[GB_IO_PCM_34] |= (((int)sample) * 0xF / MAX_CH_AMP) << 4;
} }
*samples *= gb->apu.left_volume; samples->left *= gb->apu.left_volume;
samples->right *= gb->apu.right_volume;
for (unsigned char i = 0; i < 4; i++) { for (unsigned char i = 0; i < 4; i++) {
/* Phase */ /* Phase */
@ -168,7 +174,7 @@ void apu_run(GB_gameboy_t *gb)
while (gb->audio_copy_in_progress); while (gb->audio_copy_in_progress);
double ticks_per_sample = (double) CPU_FREQUENCY / gb->sample_rate; double ticks_per_sample = (double) CPU_FREQUENCY / gb->sample_rate;
while (gb->apu_cycles > ticks_per_sample) { while (gb->apu_cycles > ticks_per_sample) {
int16_t sample = 0; GB_sample_t sample = {0, };
apu_render(gb, gb->sample_rate, 1, &sample); apu_render(gb, gb->sample_rate, 1, &sample);
gb->apu_cycles -= ticks_per_sample; gb->apu_cycles -= ticks_per_sample;
if (gb->audio_position == gb->buffer_size) { if (gb->audio_position == gb->buffer_size) {
@ -186,7 +192,7 @@ void apu_run(GB_gameboy_t *gb)
} }
} }
void apu_copy_buffer(GB_gameboy_t *gb, int16_t *dest, unsigned int count) void apu_copy_buffer(GB_gameboy_t *gb, GB_sample_t *dest, unsigned int count)
{ {
gb->audio_copy_in_progress = true; gb->audio_copy_in_progress = true;
@ -198,11 +204,11 @@ void apu_copy_buffer(GB_gameboy_t *gb, int16_t *dest, unsigned int count)
if (count > gb->audio_position) { if (count > gb->audio_position) {
// gb_log(gb, "Audio underflow: %d\n", count - gb->audio_position); // gb_log(gb, "Audio underflow: %d\n", count - gb->audio_position);
memset(dest + gb->audio_position, 0, (count - gb->audio_position) * 2); memset(dest + gb->audio_position, 0, (count - gb->audio_position) * sizeof(*gb->audio_buffer));
count = gb->audio_position; count = gb->audio_position;
} }
memcpy(dest, gb->audio_buffer, count * 2); memcpy(dest, gb->audio_buffer, count * sizeof(*gb->audio_buffer));
memmove(gb->audio_buffer, gb->audio_buffer + count, (gb->audio_position - count) * 2); memmove(gb->audio_buffer, gb->audio_buffer + count, (gb->audio_position - count) * sizeof(*gb->audio_buffer));
gb->audio_position -= count; gb->audio_position -= count;
gb->audio_copy_in_progress = false; gb->audio_copy_in_progress = false;

View File

@ -11,6 +11,12 @@
struct GB_gameboy_s; struct GB_gameboy_s;
typedef struct GB_gameboy_s GB_gameboy_t; typedef struct GB_gameboy_s GB_gameboy_t;
typedef struct
{
int16_t left;
int16_t right;
} GB_sample_t;
/* Not all used on all channels */ /* Not all used on all channels */
typedef struct typedef struct
{ {
@ -48,12 +54,11 @@ typedef struct
bool global_enable; bool global_enable;
} GB_apu_t; } GB_apu_t;
void apu_render(GB_gameboy_t *gb, unsigned long sample_rate, unsigned long n_samples, int16_t *samples); void apu_render(GB_gameboy_t *gb, unsigned long sample_rate, unsigned long n_samples, GB_sample_t *samples);
void apu_copy_buffer(GB_gameboy_t *gb, int16_t *dest, unsigned int count); void apu_copy_buffer(GB_gameboy_t *gb, GB_sample_t *dest, unsigned int count);
void apu_write(GB_gameboy_t *gb, unsigned char reg, unsigned char value); void apu_write(GB_gameboy_t *gb, unsigned char reg, unsigned char value);
unsigned char apu_read(GB_gameboy_t *gb, unsigned char reg); unsigned char apu_read(GB_gameboy_t *gb, unsigned char reg);
void apu_init(GB_gameboy_t *gb); void apu_init(GB_gameboy_t *gb);
void apu_copy_buffer(GB_gameboy_t *gb, int16_t *dest, unsigned int count);
void apu_run(GB_gameboy_t *gb); void apu_run(GB_gameboy_t *gb);
#endif /* apu_h */ #endif /* apu_h */

View File

@ -445,7 +445,7 @@ void gb_set_sample_rate(GB_gameboy_t *gb, unsigned int sample_rate)
free(gb->audio_buffer); free(gb->audio_buffer);
} }
gb->buffer_size = sample_rate / 25; // 40ms delay gb->buffer_size = sample_rate / 25; // 40ms delay
gb->audio_buffer = malloc(gb->buffer_size * 2); gb->audio_buffer = malloc(gb->buffer_size * sizeof(*gb->audio_buffer));
gb->sample_rate = sample_rate; gb->sample_rate = sample_rate;
gb->audio_position = 0; gb->audio_position = 0;
} }

View File

@ -239,7 +239,7 @@ typedef struct GB_gameboy_s{
/* APU */ /* APU */
GB_apu_t apu; GB_apu_t apu;
int16_t *audio_buffer; GB_sample_t *audio_buffer;
unsigned int buffer_size; unsigned int buffer_size;
unsigned int sample_rate; unsigned int sample_rate;
unsigned int audio_position; unsigned int audio_position;

View File

@ -136,7 +136,7 @@ static void debugger_interrupt(int ignore)
static void audio_callback(void *gb, Uint8 *stream, int len) static void audio_callback(void *gb, Uint8 *stream, int len)
{ {
apu_copy_buffer(gb, (int16_t *) stream, len / sizeof(int16_t)); apu_copy_buffer(gb, (GB_sample_t *) stream, len / sizeof(GB_sample_t));
} }
#ifdef __APPLE__ #ifdef __APPLE__
@ -206,7 +206,7 @@ usage:
SDL_memset(&want, 0, sizeof(want)); SDL_memset(&want, 0, sizeof(want));
want.freq = 96000; want.freq = 96000;
want.format = AUDIO_S16SYS; want.format = AUDIO_S16SYS;
want.channels = 1; want.channels = 2;
want.samples = 512; want.samples = 512;
want.callback = audio_callback; want.callback = audio_callback;
want.userdata = &gb; want.userdata = &gb;