Added stereo support. Correct some PCM register behavior.
This commit is contained in:
parent
4d8f2cfac8
commit
6bc64a9902
@ -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];
|
||||
|
@ -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
|
@ -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,
|
42
Core/apu.c
42
Core/apu.c
@ -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;
|
||||
|
11
Core/apu.h
11
Core/apu.h
@ -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 */
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user