changeset 24:4c9dbfa30a66

Implemented audio
author Michael Pavone <pavone@retrodev.com>
date Thu, 31 Mar 2016 00:07:37 -0700
parents a085f17b79e9
children fb14515266f4
files Makefile audio.s16 src/audio.c src/audio.h src/main.c src/system.h src/system_sdl.c
diffstat 7 files changed, 277 insertions(+), 17 deletions(-) [+]
line wrap: on
line diff
--- 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
--- /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
--- /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 <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#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;
+}
--- /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_
--- 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 <string.h>
 #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;
 }
--- 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_
--- 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 <stdlib.h>
 
 
-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);