gbemu/lib/audio.c

401 lines
12 KiB
C

#include <audio.h>
#include <math.h>
#include <windows.h>
#include <portaudio.h>
#define SAMPLE_RATE 44100
#define FRAMES_PER_BUFFER 64
#define TABLE_SIZE 44100
#define M_PI 3.14159265
static float sine[TABLE_SIZE];
static unsigned long ind = 0;
static unsigned long ind2 = 0;
static int sq1_on_time;
static int sq1_off_time;
static float sq1 = 1;
static int sq1_timer = 0;
static int sq2_on_time;
static int sq2_off_time;
static float sq2 = 1;
static int sq2_timer = 0;
static audio_context ctx;
HANDLE data_to_read;
HANDLE data_to_write;
const u8 square_sample[8] = {
0x0,
0x0,
0x0,
0x0,
0x1,
0x1,
0x1,
0x1
};
static int audio_callback(const void* input_uffer, void *output_buffer,
unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo *time_info,
PaStreamCallbackFlags status_flags,
void *userData ) {
//printf("Audio Callback!\n");
//audio_context *ctx = (audio_context *) userData;
float *out = (float *)output_buffer;
//static float last = 0;
//float val;
//if(!ctx.ready) {
// val = last;
//} else {
// val = ctx.sq1_value;
// last = val;
// ctx.ready = false;
//}
//PaTime time = time_info->currentTime;
//sq1_on_time = 1/(ctx->sq1_freq/(float)SAMPLE_RATE) * ctx->sq1_duty;
//sq1_off_time = 1/(ctx->sq1_freq/(float)SAMPLE_RATE) * (1 - ctx->sq1_duty);
//sq2_on_time = 1/(ctx->sq2_freq/(float)SAMPLE_RATE) * ctx->sq2_duty;
//sq2_off_time = 1/(ctx->sq2_freq/(float)SAMPLE_RATE) * (1 - ctx->sq2_duty);
//ctx.sq1_period_timer++;
//if(ctx.sq1_period_timer >= 0x800) {
//ctx.sq1_period_timer = ctx.sq1_period_reset;
//ctx.sq1_sample = (ctx.sq1_sample + 1) % 8;
//ctx.sq1_value = (float)square_sample[ctx.sq1_sample];///15.0f;
//ctx.sq1_value = - ctx.sq1_value;
//}
//printf("val: %f\n", val);
float left = 0;
float right = 0;
for(int i = 0; i < framesPerBuffer; i++) {
if(!ctx.audio_enabled) {
*out++ = 0;
*out++ = 0;
continue;
}
if(WaitForSingleObject(data_to_read, INFINITE) == WAIT_TIMEOUT) {
continue;
}
if(ctx.sq1_write_head != ctx.sq1_read_head) {
if(ctx.ch1_left){
//left = ((float)(ctx.sq1_volume * ctx.sq1_audio_buffer[ctx.sq1_read_head])/15.0f) - 0.5f;
}
if(ctx.ch1_right){
//right = ((float)(ctx.sq1_volume * ctx.sq1_audio_buffer[ctx.sq1_read_head])/15.0f) - 0.5f;
}
ctx.sq1_read_head++;
if (ctx.sq1_read_head >= 4096) {
ctx.sq1_read_head = 0;
}
}
//if(ctx.sq2_write_head != ctx.sq2_read_head) {
if(ctx.ch2_left){
left = ctx.sq2_audio_buffer[ctx.sq2_read_head];
//printf("audio: %d\n", ctx.sq2_audio_buffer[ctx.sq2_read_head]);
//left = ((float)(ctx.sq2_volume * square_sample[ctx.sq2_sample])/15.0f) - 0.5f;
}
if(ctx.ch2_right){
//right += ((float)(ctx.sq2_volume * ctx.sq2_audio_buffer[ctx.sq2_read_head])/15.0f) - 0.5f;
}
ctx.sq2_read_head++;
if (ctx.sq2_read_head >= 4096) {
ctx.sq2_read_head = 0;
}
//}
left *= (float)ctx.volume_left/8.0f;
right *= (float)ctx.volume_right/8.0f;
*out++ = left;
*out++ = right;
ReleaseSemaphore(data_to_write, 1, NULL);
}
return paContinue;
}
const int semaphore_count = 128;
static PaStream *stream;
void audio_init(){
data_to_read = CreateSemaphore(NULL, 0, semaphore_count, NULL);
data_to_write = CreateSemaphore(NULL, semaphore_count, semaphore_count, NULL);
PaStreamParameters output_parameters;
PaError err;
ctx.sq1_volume = 1;
ctx.sq1_duty = 0.5;
ctx.sq1_freq = 200;
ctx.sq2_volume = 1;
ctx.sq2_duty = 0.5;
ctx.sq2_freq = 400;
ctx.audio_enabled = true;
ctx.sq2_period_reset = 0x700;
ctx.sq2_period_timer = 0x700;
ctx.sq2_enable = true;
ctx.sq2_len_enable = false;
ctx.ch2_left = true;
ctx.volume_left = 8;
ctx.sq2_volume = 1;
ctx.sq1_value = -1;
ctx.sq1_write_head = 0;
ctx.sq1_read_head = 0;
ctx.sq2_read_head = 0;
ctx.sq2_write_head = 0;
ctx.sq2_env_pace = 0;
for(int i = 0; i < TABLE_SIZE; i++) {
sine[i] = (float) sin(((double)i/(double)TABLE_SIZE) * M_PI * 2.);
}
err = Pa_Initialize();
if (err != paNoError) goto error;
output_parameters.device = Pa_GetDefaultOutputDevice();
if(output_parameters.device == paNoDevice) {
fprintf(stderr, "No default audio device!\n");
goto error;
}
output_parameters.channelCount = 2;
output_parameters.sampleFormat = paFloat32;
output_parameters.suggestedLatency = Pa_GetDeviceInfo(output_parameters.device)->defaultLowOutputLatency;
output_parameters.hostApiSpecificStreamInfo = NULL;
err = Pa_OpenStream(&stream,
NULL,
&output_parameters,
SAMPLE_RATE,
FRAMES_PER_BUFFER,
paClipOff,
audio_callback,
&ctx);
if(err != paNoError) goto error;
err = Pa_StartStream(stream);
if(err != paNoError) goto error;
return;
error:
Pa_Terminate();
fprintf(stderr, "portaudio stream error\n\tError Number: %d\n\tError Message: %s\n", err, Pa_GetErrorText(err));
}
static int change = 1;
static u32 ticks = 0;
void audio_tick(){
u32 prev_ticks = ticks;
ticks++;
if(!(ticks & 0b1)) {
if(ctx.sq1_len_enable) {
ctx.sq1_len++;
if(ctx.sq1_len >= 64) {
ctx.sq1_enable = false;
}
}
if(ctx.sq2_len_enable) {
ctx.sq2_len++;
if(ctx.sq2_len >= 64) {
ctx.sq2_enable = false;
}
}
}
if((prev_ticks & (1 << 3)) && !(ticks & (1 << 3))) {
if(ctx.sq1_env_pace != 0){
ctx.sq1_env_timer++;
if(ctx.sq1_env_timer >= ctx.sq1_env_pace) {
ctx.sq1_volume += ctx.sq1_env_direction ? 1 : -1;
if(ctx.sq1_volume < 0)
ctx.sq1_volume = 0;
if(ctx.sq1_volume > 7)
ctx.sq1_volume = 7;
ctx.sq1_env_timer = 0;
}
}
if(ctx.sq2_env_pace != 0){
ctx.sq2_env_timer++;
if(ctx.sq2_env_timer >= ctx.sq2_env_pace) {
ctx.sq2_volume += ctx.sq2_env_direction ? 1 : -1;
if(ctx.sq2_volume < 0)
ctx.sq2_volume = 0;
if(ctx.sq2_volume > 7)
ctx.sq2_volume = 7;
ctx.sq2_env_timer = 0;
}
}
}
}
static u32 updates;
static u32 last;
void audio_period_tick(){
//DWORD dwaitResult = WaitForSingleObject(mutex, INFINITE);
//if(dwaitResult == WAIT_ABANDONED)
// return;
u32 now = get_ticks();
if(now - last >= 1000) {
printf("Updates: %d\n", updates);
updates = 0;
last = now;
}
ctx.sq1_period_timer++;
ctx.sq2_period_timer++;
if(ctx.sq1_period_timer >= 0x800) {
ctx.sq1_period_timer = ctx.sq1_period_reset;
ctx.sq1_sample = (ctx.sq1_sample + 1) % 8;
//ctx.sq1_value = (float)square_sample[ctx.sq1_sample];///15.0f;
//ctx.ready = true;
}
if(ctx.sq2_period_timer >= 0x800) {
ctx.sq2_period_timer = ctx.sq2_period_reset;
ctx.sq2_sample = (ctx.sq2_sample + 1) % 8;
//ctx.sq1_value = (float)square_sample[ctx.sq1_sample];///15.0f;
//ctx.ready = true;
if(ctx.sq2_sample == 0) {
updates++;
}
}
DWORD result = WaitForSingleObject(data_to_write, 0);
if(result != WAIT_TIMEOUT) {
//updates++;
//updates = 0;
//u32 read = ctx.read_head;
//u32 write = ctx.write_head;
//if(read > write) {
// write += 4096;
//}
//if(write - read <= 64){
// return;
//}
if(ctx.sq1_enable){
ctx.sq1_audio_buffer[ctx.sq1_write_head++] = ((float)(ctx.sq2_volume * square_sample[ctx.sq1_sample])/7.5f) - 1.0f;
} else {
ctx.sq1_audio_buffer[ctx.sq1_write_head++] = 0x0;
}
if(ctx.sq1_write_head >= 4096) {
ctx.sq1_write_head = 0;
}
if(ctx.sq2_enable){
ctx.sq2_audio_buffer[ctx.sq2_write_head++] = ((float)(ctx.sq2_volume * (square_sample[ctx.sq2_sample] - .5f))/7.5f);
//printf("Audio: %f\n", ctx.sq2_audio_buffer[ctx.sq2_write_head - 1]);
} else {
ctx.sq2_audio_buffer[ctx.sq2_write_head++] = 0.0f;
}
if(ctx.sq2_write_head >= 4096) {
ctx.sq2_write_head = 0;
}
ReleaseSemaphore(data_to_read, 1, NULL);
}
//ReleaseMutex(mutex);
}
void enable_square1() {
ctx.sq1_enable = true;
if(ctx.sq1_len >= 64) {
ctx.sq1_len = ctx.sq1_initial_len;
}
ctx.sq1_volume = ctx.sq1_initial_volume;
ctx.sq1_env_timer = 0;
}
void enable_square2() {
ctx.sq2_enable = true;
if(ctx.sq2_len >= 64) {
ctx.sq2_len = ctx.sq2_initial_len;
}
ctx.sq2_volume = ctx.sq2_initial_volume;
ctx.sq2_env_timer = 0;
ctx.sq2_sample = 0;
//printf("Channel 2 enabled, Timer: %d\n", ctx.sq2_len_enable);
}
u8 audio_read(u16 address) {
return 0x00;
}
void audio_write(u16 address, u8 value){
return;
if(address == 0xFF26) {
ctx.audio_enabled = value & 0x80;
}
if(address == 0xFF25) {
ctx.ch4_left = value & 0b10000000;
ctx.ch3_left = value & 0b01000000;
ctx.ch2_left = value & 0b00100000;
ctx.ch1_left = value & 0b00010000;
ctx.ch4_right = value & 0b00001000;
ctx.ch3_right = value & 0b00000100;
ctx.ch2_right = value & 0b00000010;
ctx.ch1_right = value & 0b00000001;
}
if(address == 0xFF24) {
ctx.volume_left = (value >> 4) & 0b111;
if(ctx.volume_left == 0) ctx.volume_left = 1;
ctx.volume_right = value & 0b111;
if(ctx.volume_right == 0) ctx.volume_right = 1;
}
if(address == 0xFF10) {
ctx.sq1_sweep_pace = (value >> 4) & 0b111;
ctx.sq1_sweep_direction = value & 0x08;
ctx.sq1_sweep_step = value & 0b111;
}
if(address == 0xFF11) {
ctx.sq1_duty = value >> 6;
ctx.sq1_initial_len = value & 0x3F;
}
if(address == 0xFF12) {
ctx.sq1_initial_volume = value >> 4;
ctx.sq1_env_direction = value & 0x8;
ctx.sq1_env_pace = value & 0b111;
}
if(address == 0xFF13) {
ctx.sq1_period_reset = (ctx.sq1_period_reset & 0xF0) | value;
}
if(address == 0xFF14) {
ctx.sq1_period_reset = (ctx.sq1_period_reset & 0x0F) | ((value & 0b111) << 8);
ctx.sq1_len_enable = value & 0x40;
if(value & 0x80) {
enable_square1();
}
}
if(address == 0xFF16) {
ctx.sq2_duty = value >> 6;
ctx.sq2_initial_len = value & 0x3F;
}
if(address == 0xFF17) {
ctx.sq2_initial_volume = value >> 4;
ctx.sq2_env_direction = value & 0x8;
ctx.sq2_env_pace = value * 0b111;
if(!ctx.sq2_env_direction && !ctx.sq2_initial_volume) {
ctx.sq2_enable = false;
}
}
if(address == 0xFF18) {
ctx.sq2_period_reset = (ctx.sq2_period_reset & 0xF0) | value;
}
if(address == 0xFF19) {
ctx.sq2_period_reset = (ctx.sq2_period_reset & 0x0F) | ((value & 0b111) << 8);
ctx.sq2_len_enable = value & 0x40;
if(value & 0x80) {
enable_square2();
}
}
}