# HG changeset patch # User Michael Pavone # Date 1459408057 25200 # Node ID 4c9dbfa30a6698da4996b0d0915ba924e3ecf9df # Parent a085f17b79e9daa9b4eddec6d60658ebd5dd7340 Implemented audio diff -r a085f17b79e9 -r 4c9dbfa30a66 Makefile --- a/Makefile Wed Mar 30 20:31:04 2016 -0700 +++ b/Makefile Thu Mar 31 00:07:37 2016 -0700 @@ -18,7 +18,7 @@ $(TARGETDIR) : mkdir $(TARGETDIR) -$(TARGETDIR)/s16 : $(TARGETDIR)/main.o $(TARGETDIR)/cpu.o $(TARGETDIR)/vdp.o $(TARGETDIR)/system_sdl.o +$(TARGETDIR)/s16 : $(TARGETDIR)/main.o $(TARGETDIR)/cpu.o $(TARGETDIR)/vdp.o $(TARGETDIR)/audio.o $(TARGETDIR)/system_sdl.o $(CC) -o $@ $^ $(LDFLAGS) $(TARGETDIR)/asm : $(TARGETDIR)/asm.o $(TARGETDIR)/cpu.o diff -r a085f17b79e9 -r 4c9dbfa30a66 audio.s16 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/audio.s16 Thu Mar 31 00:07:37 2016 -0700 @@ -0,0 +1,33 @@ + ;48000 x 16 / VALUE = FREQ + ;48000 x 16 = VALUE * FREQ + ;48000 x 16 / FREQ = VALUE + ;A = 440Hz ~ 1745 = $6D1 + ;C# = 554.365Hz ~ 1385 = $569 + ;E = 659.255Hz ~ 1165 = $48D + + ;Channel A + ldim $D1, r0 + ldimh $6, r0 + outi 4, r0 + + ;Channel B + ldim $69, r0 + ldimh $5, r0 + outi 5, r0 + + ;Channel C + ldim $8D, r0 + ldimh $4, r0 + outi 6, r0 + + ;Channel D + ldim $A2, r0 + ldimh $D, r0 + outi 7, r0 + + ldim $FF, r0 + ldimh $FF, r0 + outi 8, r0 + outi 9, r0 +done + bra done \ No newline at end of file diff -r a085f17b79e9 -r 4c9dbfa30a66 src/audio.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/audio.c Thu Mar 31 00:07:37 2016 -0700 @@ -0,0 +1,69 @@ +#include +#include +#include +#include "audio.h" +#include "system.h" + +#define BUFFER_INC_RES 1000000000UL + +audio *alloc_audio(uint32_t master_clock, uint32_t clock_div, int sample_rate, int buffer_size) +{ + size_t alloc_size = sizeof(audio) + buffer_size * sizeof(int16_t) * 2; + audio *context = malloc(alloc_size); + memset(context, 0, alloc_size); + context->writebuffer = context->buffer; + context->playbuffer = context->buffer + buffer_size; + + context->buffer_size = buffer_size; + context->clock_inc = clock_div; + context->buffer_inc = ((BUFFER_INC_RES * (uint64_t)sample_rate) / (uint64_t)master_clock) * clock_div; + for (int i = 0; i < NUM_CHANNELS; i++) + { + context->value[i] = context->volume[i] << 5; + } +} + +void audio_run(audio *context, uint32_t target) +{ + while (context->cycles < target) + { + for (int i = 0; i < 4; i++) + { + if (context->timer_cur[i]) { + context->timer_cur[i]--; + if (!context->timer_cur[i]) { + context->value[i] = context->value[i] ? 0 : (context->volume[i] << 5); + } + } else { + context->timer_cur[i] = context->timer_load[i]; + } + } + context->buffer_fraction += context->buffer_inc; + if (context->buffer_fraction >= BUFFER_INC_RES) { + context->buffer_fraction -= BUFFER_INC_RES; + context->writebuffer[context->buffer_pos++] = + context->value[0] + context->value[1] + context->value[2] + context->value[3]; + if (context->buffer_pos == context->buffer_size) { + int16_t *tmp = context->playbuffer; + context->playbuffer = context->writebuffer; + context->writebuffer = tmp; + system_present_audio(context->playbuffer); + context->buffer_pos = 0; + } + } + context->cycles += context->clock_inc; + } +} + +void audio_write_freq(audio *context, int channel, uint16_t value) +{ + context->timer_cur[channel] = context->timer_load[channel] = value; +} + +void audio_write_vol(audio *context, int pair, uint16_t value) +{ + int channel = pair * 2; + context->value[channel] = context->volume[channel] = value >> 8; + channel++; + context->value[channel] = context->volume[channel] = value; +} diff -r a085f17b79e9 -r 4c9dbfa30a66 src/audio.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/audio.h Thu Mar 31 00:07:37 2016 -0700 @@ -0,0 +1,31 @@ +#ifndef AUDIO_H_ +#define AUDIO_H_ + +#define NUM_CHANNELS 4 + +typedef struct { + int16_t *writebuffer; + int16_t *playbuffer; + + uint64_t buffer_fraction; + uint64_t buffer_inc; + uint32_t buffer_pos; + uint32_t buffer_size; + + uint32_t cycles; + uint32_t clock_inc; + + uint16_t timer_load[NUM_CHANNELS]; + uint16_t timer_cur[NUM_CHANNELS]; + int16_t value[NUM_CHANNELS]; + uint8_t volume[NUM_CHANNELS]; + + int16_t buffer[]; +} audio; + +audio *alloc_audio(uint32_t master_clock, uint32_t clock_div, int sample_rate, int buffer_size); +void audio_run(audio *context, uint32_t target); +void audio_write_freq(audio *context, int channel, uint16_t value); +void audio_write_vol(audio *context, int pair, uint16_t value); + +#endif //AUDIO_H_ diff -r a085f17b79e9 -r 4c9dbfa30a66 src/main.c --- a/src/main.c Wed Mar 30 20:31:04 2016 -0700 +++ b/src/main.c Thu Mar 31 00:07:37 2016 -0700 @@ -4,9 +4,11 @@ #include #include "cpu.h" #include "vdp.h" +#include "audio.h" #include "system.h" #define CYCLES_PER_FRAME (832*262) +#define MASTER_CLOCK 13056000 uint8_t rom[48 * 1024]; uint8_t ram[16 * 1024]; @@ -31,8 +33,9 @@ }; typedef struct { - cpu *proc; - vdp video; + cpu *proc; + vdp video; + audio *audio; } console; void debug_port_write(cpu *context, uint8_t port, uint16_t value) @@ -94,6 +97,20 @@ vdp_write_data(&system->video, value); } +void frequency_port_write(cpu *context, uint8_t port, uint16_t value) +{ + console *system = context->system; + audio_run(system->audio, context->cycles); + audio_write_freq(system->audio, port - PORT_FREQUENCY_A, value); +} + +void volume_port_write(cpu *context, uint8_t port, uint16_t value) +{ + console *system = context->system; + audio_run(system->audio, context->cycles); + audio_write_vol(system->audio, port - PORT_VOLUME_AB, value); +} + memory_region regions[] = { {rom, 0, sizeof(rom)-1, MEM_READ}, {ram, sizeof(rom), sizeof(rom)-1+sizeof(ram), MEM_READ}, @@ -104,9 +121,11 @@ for(;;) { run_cpu(context->proc, CYCLES_PER_FRAME); + audio_run(context->audio, CYCLES_PER_FRAME); vdp_run(&context->video, CYCLES_PER_FRAME); context->proc->cycles -= CYCLES_PER_FRAME; context->video.cycles -= CYCLES_PER_FRAME; + context->audio->cycles -= CYCLES_PER_FRAME; system_poll_events(); } } @@ -131,6 +150,12 @@ context.proc = alloc_cpu(10, sizeof(regions)/sizeof(memory_region), regions); context.proc->system = &context; vdp_init(&context.video, 2); + context.proc->port_handlers[PORT_FREQUENCY_A].write = frequency_port_write; + context.proc->port_handlers[PORT_FREQUENCY_B].write = frequency_port_write; + context.proc->port_handlers[PORT_FREQUENCY_C].write = frequency_port_write; + context.proc->port_handlers[PORT_FREQUENCY_D].write = frequency_port_write; + context.proc->port_handlers[PORT_VOLUME_AB].write = volume_port_write; + context.proc->port_handlers[PORT_VOLUME_CD].write = volume_port_write; context.proc->port_handlers[PORT_SERIAL].write = debug_port_write; context.proc->port_handlers[PORT_SERIAL].read = debug_port_read; context.proc->port_handlers[PORT_VERTICAL].write = vertical_port_write; @@ -141,10 +166,12 @@ context.proc->port_handlers[PORT_VRAM_ADDRESS].read = address_port_read; context.proc->port_handlers[PORT_VRAM_DATA].write = data_port_write; - if (!system_init(640, 480)) { + if (!system_init(640, 480, 48000)) { return 1; } + context.audio = alloc_audio(MASTER_CLOCK, 17, system_sample_rate(), system_buffer_size()); + run_console(&context); return 0; } diff -r a085f17b79e9 -r 4c9dbfa30a66 src/system.h --- a/src/system.h Wed Mar 30 20:31:04 2016 -0700 +++ b/src/system.h Thu Mar 31 00:07:37 2016 -0700 @@ -1,9 +1,23 @@ #ifndef SYSTEM_H_ #define SYSTEM_H_ -int system_init(int width, int height); +//initializes audio and video output with the given parameters +int system_init(int width, int height, int desired_sample_rate); +//Should be called once per frame to get a pointer to the output buffer at the start of rendering uint16_t *system_get_framebuffer(int *pitch); +//Should be called once per frame at the competion of rendering +//The pointer returned by system_get_framebuffer should be discarded after calling this function void system_framebuffer_updated(); +//Pumps the event queue and processes events void system_poll_events(); +//Presents an audio buffer to the system for playback +//Will block until the system is ready for more samples +//The buffer passed to this function should not be used again +//by the caller until the next call to system_present_audio +void system_present_audio(int16_t *buffer); +//Returns the audio sample rate, which may be different than what was requested +int system_sample_rate(); +//Returns the number of samples that should be provided in a call to system_present_audio +int system_buffer_size(); #endif //SYSTEM_H_ diff -r a085f17b79e9 -r 4c9dbfa30a66 src/system_sdl.c --- a/src/system_sdl.c Wed Mar 30 20:31:04 2016 -0700 +++ b/src/system_sdl.c Thu Mar 31 00:07:37 2016 -0700 @@ -3,13 +3,75 @@ #include -SDL_Window *window; -SDL_Renderer *renderer; -SDL_Texture *texture; +static SDL_Window *window; +static SDL_Renderer *renderer; +static SDL_Texture *texture; +static int sample_rate; +static int buffer_size; +static uint8_t quitting; + +static SDL_mutex * audio_mutex; +static SDL_cond * source_ready; +static SDL_cond * output_ready; +static int16_t *source_buffer; -int system_init(int width, int height) +static void audio_callback(void * userdata, uint8_t *stream, int len) +{ + uint8_t local_quit; + int16_t *local_source; + SDL_LockMutex(audio_mutex); + local_source = NULL; + do { + if (!local_source) { + local_source = source_buffer; + source_buffer = NULL; + SDL_CondSignal(output_ready); + } + if (!quitting && !local_source) { + SDL_CondWait(source_ready, audio_mutex); + } + } while (!quitting && !local_source); + local_quit = quitting; + SDL_UnlockMutex(audio_mutex); + if (!local_quit) { + fflush(stdout); + memcpy(stream, local_source, len); + } +} + +static void close_audio() { - if (SDL_Init(SDL_INIT_VIDEO) < 0) { + SDL_LockMutex(audio_mutex); + quitting = 1; + SDL_CondSignal(source_ready); + SDL_UnlockMutex(audio_mutex); + SDL_CloseAudio(); +} + +void system_present_audio(int16_t *buffer) +{ + SDL_LockMutex(audio_mutex); + while (source_buffer) { + SDL_CondWait(output_ready, audio_mutex); + } + source_buffer = buffer; + SDL_CondSignal(source_ready); + SDL_UnlockMutex(audio_mutex); +} + +int system_sample_rate() +{ + return sample_rate; +} + +int system_buffer_size() +{ + return buffer_size; +} + +int system_init(int width, int height, int desired_sample_rate) +{ + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) { fprintf(stderr, "Failed to init SDL: %s\n", SDL_GetError()); return 0; } @@ -23,20 +85,46 @@ renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); if (!renderer) { fprintf(stderr, "Failed to create renderer: %s\n", SDL_GetError()); - return 0; + goto renderer_error; } texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGB444, SDL_TEXTUREACCESS_STREAMING, 320, 240); if (!texture) { fprintf(stderr, "Failed to create texture: %s\n", SDL_GetError()); - SDL_DestroyRenderer(renderer); - SDL_DestroyWindow(window); - return 0; + goto error; } + + audio_mutex = SDL_CreateMutex(); + source_ready = SDL_CreateCond(); + output_ready = SDL_CreateCond(); + + SDL_AudioSpec desired, actual; + desired.freq = desired_sample_rate; + desired.format = AUDIO_S16SYS; + desired.channels = 1; + desired.callback = audio_callback; + desired.userdata = NULL; + desired.samples = 512; + + if (SDL_OpenAudio(&desired, &actual) < 0) { + fprintf(stderr, "Failed to open audio: %s\n", SDL_GetError()); + goto error; + } + printf("Initialized audio at frequency %d with a %d sample buffer and %d channels\n", actual.freq, actual.samples, actual.channels); + sample_rate = actual.freq; + buffer_size = actual.samples; + atexit(close_audio); + SDL_PauseAudio(0); + return 1; + +error: + SDL_DestroyRenderer(renderer); +renderer_error: + SDL_DestroyWindow(window); + return 0; } -//Should be called once per frame to get a pointer to the output buffer at the start of rendering uint16_t *system_get_framebuffer(int *pitch) { void *pixels; @@ -47,8 +135,6 @@ return pixels; } -//Should be called once per frame at the competion of rendering -//The pointer returned by system_get_framebuffer should be discarded after calling this function void system_framebuffer_updated() { SDL_UnlockTexture(texture);