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 "AudioClient.h"
#include "GBAudioClient.h"
#include "Document.h"
#include "AppDelegate.h"
#include "gb.h"
@ -15,7 +15,7 @@
NSString *lastConsoleInput;
}
@property AudioClient *audioClient;
@property GBAudioClient *audioClient;
- (void) vblank;
- (void) log: (const char *) log withAttributes: (gb_log_attributes) attributes;
- (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);
self.view.gb = &gb;
gb_set_sample_rate(&gb, 96000);
self.audioClient = [[AudioClient alloc] initWithRendererBlock:^(UInt32 sampleRate, UInt32 nFrames, SInt16 *buffer) {
//apu_render(&gb, sampleRate, nFrames, buffer);
self.audioClient = [[GBAudioClient alloc] initWithRendererBlock:^(UInt32 sampleRate, UInt32 nFrames, GB_sample_t *buffer) {
apu_copy_buffer(&gb, buffer, nFrames);
} andSampleRate:96000];
[self.audioClient start];

View File

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

View File

@ -1,9 +1,9 @@
#import <Foundation/Foundation.h>
#import <AudioToolbox/AudioToolbox.h>
#import "AudioClient.h"
#import "GBAudioClient.h"
static OSStatus render(
AudioClient *self,
GBAudioClient *self,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber,
@ -11,23 +11,19 @@ static OSStatus render(
AudioBufferList *ioData)
{
// This is a mono tone generator so we only need the first buffer
const int channel = 0;
SInt16 *buffer = (SInt16 *)ioData->mBuffers[channel].mData;
GB_sample_t *buffer = (GB_sample_t *)ioData->mBuffers[0].mData;
self.renderBlock(self.rate, inNumberFrames, buffer);
return noErr;
}
@implementation AudioClient
@implementation GBAudioClient
{
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
{
if(!(self = [super init]))
@ -70,10 +66,10 @@ static OSStatus render(
streamFormat.mFormatID = kAudioFormatLinearPCM;
streamFormat.mFormatFlags =
kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked | kAudioFormatFlagsNativeEndian;
streamFormat.mBytesPerPacket = 2;
streamFormat.mBytesPerPacket = 4;
streamFormat.mFramesPerPacket = 1;
streamFormat.mBytesPerFrame = 2;
streamFormat.mChannelsPerFrame = 1;
streamFormat.mBytesPerFrame = 4;
streamFormat.mChannelsPerFrame = 2;
streamFormat.mBitsPerChannel = 2 * 8;
err = AudioUnitSetProperty (audioUnit,
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
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++) {
*samples = 0;
samples->left = samples->right = 0;
if (!gb->apu.global_enable) {
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_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,
gb->apu.wave_channels[0].amplitude,
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;
}
if (gb->apu.left_on[1] || gb->apu.right_on[1]) {
{
int16_t sample = generate_square(gb->apu.wave_channels[1].phase,
gb->apu.wave_channels[1].amplitude,
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;
}
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,
MAX_CH_AMP,
gb->apu.wave_form,
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;
}
if (gb->apu.left_on[3] || gb->apu.right_on[3]) {
{
int16_t sample = generate_noise(gb->apu.wave_channels[3].phase,
gb->apu.wave_channels[3].amplitude,
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;
}
*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++) {
/* Phase */
@ -168,7 +174,7 @@ void apu_run(GB_gameboy_t *gb)
while (gb->audio_copy_in_progress);
double ticks_per_sample = (double) CPU_FREQUENCY / gb->sample_rate;
while (gb->apu_cycles > ticks_per_sample) {
int16_t sample = 0;
GB_sample_t sample = {0, };
apu_render(gb, gb->sample_rate, 1, &sample);
gb->apu_cycles -= ticks_per_sample;
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;
@ -198,11 +204,11 @@ void apu_copy_buffer(GB_gameboy_t *gb, int16_t *dest, unsigned int count)
if (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;
}
memcpy(dest, gb->audio_buffer, count * 2);
memmove(gb->audio_buffer, gb->audio_buffer + count, (gb->audio_position - count) * 2);
memcpy(dest, gb->audio_buffer, count * sizeof(*gb->audio_buffer));
memmove(gb->audio_buffer, gb->audio_buffer + count, (gb->audio_position - count) * sizeof(*gb->audio_buffer));
gb->audio_position -= count;
gb->audio_copy_in_progress = false;

View File

@ -11,6 +11,12 @@
struct GB_gameboy_s;
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 */
typedef struct
{
@ -48,12 +54,11 @@ typedef struct
bool global_enable;
} GB_apu_t;
void apu_render(GB_gameboy_t *gb, unsigned long sample_rate, unsigned long n_samples, int16_t *samples);
void apu_copy_buffer(GB_gameboy_t *gb, int16_t *dest, unsigned int count);
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, GB_sample_t *dest, unsigned int count);
void apu_write(GB_gameboy_t *gb, unsigned char reg, unsigned char value);
unsigned char apu_read(GB_gameboy_t *gb, unsigned char reg);
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);
#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);
}
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->audio_position = 0;
}

View File

@ -239,7 +239,7 @@ typedef struct GB_gameboy_s{
/* APU */
GB_apu_t apu;
int16_t *audio_buffer;
GB_sample_t *audio_buffer;
unsigned int buffer_size;
unsigned int sample_rate;
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)
{
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__
@ -206,7 +206,7 @@ usage:
SDL_memset(&want, 0, sizeof(want));
want.freq = 96000;
want.format = AUDIO_S16SYS;
want.channels = 1;
want.channels = 2;
want.samples = 512;
want.callback = audio_callback;
want.userdata = &gb;