From 8493ff5fea8991f7ba25c25ebfd6a9afe19407df Mon Sep 17 00:00:00 2001 From: Samuel Walker Date: Sun, 2 Feb 2025 20:36:32 -0700 Subject: [PATCH] audio is getting closer? --- .vscode/settings.json | 3 +- include/audio.h | 68 +++++++++- lib/audio.c | 303 ++++++++++++++++++++++++++++++++++++------ lib/io.c | 7 +- lib/timer.c | 20 +++ 5 files changed, 355 insertions(+), 46 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index d0523bb..e351eb9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,6 +4,7 @@ "ppu_sm.h": "c", "string.h": "c", "common.h": "c", - "ppu.h": "c" + "ppu.h": "c", + "profileapi.h": "c" } } \ No newline at end of file diff --git a/include/audio.h b/include/audio.h index 28a1cb7..1e12e0a 100644 --- a/include/audio.h +++ b/include/audio.h @@ -3,13 +3,71 @@ #include typedef struct { + + bool audio_enabled; + bool ch1_left; + bool ch1_right; + bool ch2_left; + bool ch2_right; + bool ch3_left; + bool ch3_right; + bool ch4_left; + bool ch4_right; + + u8 volume_left; + u8 volume_right; + float sq1_freq; - float sq1_duty; - float sq1_amp; + u8 sq1_duty; + u8 sq1_volume; float sq2_freq; - float sq2_duty; - float sq2_amp; + u8 sq2_duty; + u8 sq2_volume; + + bool ready; + u8 sq1_sample; + float sq1_value; + u16 sq1_period_reset; + u16 sq1_period_timer; + bool sq1_enable; + bool sq1_len_enable; + u8 sq1_sweep_pace; + bool sq1_sweep_direction; + u8 sq1_initial_len; + u8 sq1_len; + u8 sq1_initial_volume; + bool sq1_env_direction; + u8 sq1_env_pace; + u8 sq1_env_timer; + u8 sq1_sweep_step; + u8 sq1_audio_buffer[4096]; + u32 sq1_write_head; + u32 sq1_read_head; + + u8 sq2_sample; + float sq2_value; + u16 sq2_period_reset; + u16 sq2_period_timer; + bool sq2_enable; + bool sq2_len_enable; + u8 sq2_sweep_pace; + bool sq2_sweep_direction; + u8 sq2_initial_len; + u8 sq2_len; + u8 sq2_initial_volume; + bool sq2_env_direction; + u8 sq2_env_pace; + u8 sq2_env_timer; + u8 sq2_sweep_step; + u8 sq2_audio_buffer[4096]; + u32 sq2_write_head; + u32 sq2_read_head; + } audio_context; void audio_init(); -void audio_tick(); \ No newline at end of file +void audio_tick(); +void audio_period_tick(); + +u8 audio_read(u16 address); +void audio_write(u16 address, u8 value); \ No newline at end of file diff --git a/lib/audio.c b/lib/audio.c index f98a1da..f46a0d4 100644 --- a/lib/audio.c +++ b/lib/audio.c @@ -1,5 +1,6 @@ #include #include +#include #include @@ -25,66 +26,109 @@ static int sq2_timer = 0; static audio_context ctx; +HANDLE mutex; + +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; + //audio_context *ctx = (audio_context *) userData; float *out = (float *)output_buffer; - 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); + //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); + //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++) { - float val = 0; - //handle audio channels - //val += 0.2 * sine[ind]; - //ind += 500; - if((sq1_timer >= sq1_on_time && sq1 == 1)) { - sq1 = 0; - sq1_timer = 0; - } else if((sq1_timer >= sq1_off_time && sq1 == 0)){ - sq1 = 1; - sq1_timer = 0; + 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; + } + if(ctx.ch1_right){ + right = (float)(ctx.sq1_volume * ctx.sq1_audio_buffer[ctx.sq1_read_head])/15.0f; + } + ctx.sq1_read_head++; + if (ctx.sq1_read_head >= 4096) { + ctx.sq1_read_head = 0; + } } - sq1_timer++; - val += ctx->sq1_amp * sq1; - if((sq2_timer >= sq2_on_time && sq2 == 1)) { - sq2 = 0; - sq2_timer = 0; - } else if((sq2_timer >= sq2_off_time && sq2 == 0)){ - sq2 = 1; - sq2_timer = 0; + if(ctx.sq2_write_head != ctx.sq2_read_head) { + if(ctx.ch2_left){ + left += (float)(ctx.sq2_volume * ctx.sq2_audio_buffer[ctx.sq2_read_head])/15.0f; + } + if(ctx.ch2_right){ + right += (float)(ctx.sq2_volume * ctx.sq2_audio_buffer[ctx.sq2_read_head])/15.0f; + } + ctx.sq2_read_head++; + if (ctx.sq2_read_head >= 4096) { + ctx.sq2_read_head = 0; + } } - sq2_timer++; - val += ctx->sq2_amp * sq2; - //val += 0.2 * sine[ind2]; - //ind2 += 200; - if(ind >= TABLE_SIZE) ind -= TABLE_SIZE; - if(ind2 >= TABLE_SIZE) ind2 -= TABLE_SIZE; - *out++ = val; + left *= (float)ctx.volume_left/8.0f; + right *= (float)ctx.volume_right/8.0f; + *out++ = left; + *out++ = right; } return paContinue; } void audio_init(){ + mutex = CreateMutex(NULL, FALSE, NULL); PaStreamParameters output_parameters; PaStream *stream; PaError err; - ctx.sq1_amp = 0.2; + ctx.sq1_volume = 1; ctx.sq1_duty = 0.5; ctx.sq1_freq = 200; - ctx.sq2_amp = 0; + ctx.sq2_volume = 1; ctx.sq2_duty = 0.5; ctx.sq2_freq = 400; + ctx.sq1_period_reset = 0x100; + ctx.sq1_period_timer = 0x100; + ctx.sq1_value = -1; + ctx.sq1_write_head = 0; + ctx.sq1_read_head = 0; + ctx.sq2_read_head = 0; + ctx.sq2_write_head = 0; + for(int i = 0; i < TABLE_SIZE; i++) { sine[i] = (float) sin(((double)i/(double)TABLE_SIZE) * M_PI * 2.); } @@ -96,7 +140,7 @@ void audio_init(){ fprintf(stderr, "No default audio device!\n"); goto error; } - output_parameters.channelCount = 1; + output_parameters.channelCount = 2; output_parameters.sampleFormat = paFloat32; output_parameters.suggestedLatency = Pa_GetDeviceInfo(output_parameters.device)->defaultLowOutputLatency; output_parameters.hostApiSpecificStreamInfo = NULL; @@ -119,10 +163,195 @@ error: } static int change = 1; +static u32 ticks = 0; void audio_tick(){ - ctx.sq1_freq += change; - if(ctx.sq1_freq >= 600 || ctx.sq1_freq <= 200) { - change = -change; + 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; + printf("sq2 timer"); + } + } + } + + 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; + +void audio_period_tick(){ + updates++; + //DWORD dwaitResult = WaitForSingleObject(mutex, INFINITE); + //if(dwaitResult == WAIT_ABANDONED) + // return; + + 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(updates >= 24) { + 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++] = square_sample[ctx.sq1_sample]; + if(ctx.sq1_write_head >= 4096) { + ctx.sq1_write_head = 0; + } + } + if(ctx.sq2_enable){ + ctx.sq2_audio_buffer[ctx.sq2_write_head++] = square_sample[ctx.sq2_sample]; + if(ctx.sq2_write_head >= 4096) { + ctx.sq2_write_head = 0; + } + } + } + //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; + +} + +u8 audio_read(u16 address) { + return 0x00; +} + +void audio_write(u16 address, u8 value){ + 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(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(); + } } } \ No newline at end of file diff --git a/lib/io.c b/lib/io.c index 1c5a3e2..95888e0 100644 --- a/lib/io.c +++ b/lib/io.c @@ -4,6 +4,7 @@ #include #include #include +#include static char serial_data[2]; @@ -32,8 +33,7 @@ u8 io_read(u16 address){ } if(BETWEEN(address, 0xFF10, 0xFF3F)) { - //ignore sound - return 0; + return audio_read(address); } printf("UNSUPPORTED io_read(%04X)\n", address); @@ -73,10 +73,11 @@ void io_write(u16 address, u8 value){ } if(BETWEEN(address, 0xFF10, 0xFF3F)) { - //ignore sound + audio_write(address, value); return; } + printf("UNSUPPORTED io_write(%04X)\n", address); } diff --git a/lib/timer.c b/lib/timer.c index 376a021..4e0ae09 100644 --- a/lib/timer.c +++ b/lib/timer.c @@ -1,15 +1,31 @@ +#include + #include #include #include +#include static timer_context ctx = {0}; +long long counts_per_cycle; +long long last; +long long now; void timer_init() { ctx.div = 0XAC00; + LARGE_INTEGER freq; + QueryPerformanceFrequency(&freq); + counts_per_cycle = freq.QuadPart / (1 << 19); } void timer_tick() { + //while(now - last < counts_per_cycle){ + // LARGE_INTEGER current; + // QueryPerformanceCounter(¤t); + // now = current.QuadPart; + // //printf("Last-now: %lld, counts: %lld\n", now - last, counts_per_cycle); + //} + last = now; u16 prev_div = ctx.div; ctx.div++; bool timer_update = false; @@ -42,6 +58,10 @@ void timer_tick() { if((prev_div & (1 << 12)) && (!(ctx.div & (1 << 12)))){ audio_tick(); } + + if((prev_div & (1 << 1)) && (!(ctx.div & (1 << 1)))){ + audio_period_tick(); + } } void timer_write(u16 address, u8 value){