diff psg.c @ 354:15dd6418fe67

Initial PSG support. Mostly works, noise channel is borked though.
author Mike Pavone <pavone@retrodev.com>
date Thu, 23 May 2013 23:42:42 -0700
parents
children fcd31d19dddd
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/psg.c	Thu May 23 23:42:42 2013 -0700
@@ -0,0 +1,112 @@
+#include "psg.h"
+#include "render.h"
+#include <string.h>
+#include <stdlib.h>
+
+void psg_init(psg_context * context, uint32_t sample_rate, uint32_t clock_rate, uint32_t samples_frame)
+{
+	memset(context, 0, sizeof(*context));
+	context->audio_buffer = malloc(sizeof(*context->audio_buffer) * samples_frame);
+	context->back_buffer = malloc(sizeof(*context->audio_buffer) * samples_frame);
+	context->buffer_inc = (double)sample_rate / (double)clock_rate;
+	context->samples_frame = samples_frame;
+	for (int i = 0; i < 4; i++) {
+		context->volume[i] = 0xF;
+	}
+}
+
+void psg_write(psg_context * context, uint8_t value)
+{
+	if (value & 0x80) {
+		context->latch = value & 0x70;
+		uint8_t channel = value >> 5 & 0x3;
+		if (value & 0x10) {
+			context->volume[channel] = value & 0xF;
+		} else {
+			if (channel == 3) {
+				switch(value & 0x3)
+				{
+				case 0:
+				case 1:
+				case 2:
+					context->counter_load[3] = 0x10 << (value & 0x3);
+					context->noise_use_tone = 0;
+					break;
+				default:
+					context->counter_load[3] = context->counter_load[2];
+					context->noise_use_tone = 1;
+				}
+				context->noise_type = value & 0x4;
+				context->lsfr = 0x8000;
+			} else {
+				context->counter_load[channel] = (context->counter_load[channel] & 0x3F0) | (value & 0xF);
+				if (channel == 2 && context->noise_use_tone) {
+					context->counter_load[3] = context->counter_load[2];
+				}
+			}
+		}
+	} else {
+		if (!(context->latch & 0x10)) {
+			uint8_t channel = context->latch >> 5 & 0x3;
+			if (channel != 3) {
+				context->counter_load[channel] = (value << 4 & 0x3F0) | (context->counter_load[channel] & 0xF);
+				if (channel == 2 && context->noise_use_tone) {
+					context->counter_load[3] = context->counter_load[2];
+				}
+			}
+		}
+	}
+}
+
+#define PSG_VOL_DIV 2
+
+//table shamelessly swiped from PSG doc from smspower.org
+int16_t volume_table[16] = {
+	32767/PSG_VOL_DIV, 26028/PSG_VOL_DIV, 20675/PSG_VOL_DIV, 16422/PSG_VOL_DIV, 13045/PSG_VOL_DIV, 10362/PSG_VOL_DIV,
+	8231/PSG_VOL_DIV, 6568/PSG_VOL_DIV, 5193/PSG_VOL_DIV, 4125/PSG_VOL_DIV, 3277/PSG_VOL_DIV, 2603/PSG_VOL_DIV, 
+	2067/PSG_VOL_DIV, 1642/PSG_VOL_DIV, 1304/PSG_VOL_DIV, 0
+};
+
+void psg_run(psg_context * context, uint32_t cycles)
+{
+	while (context->cycles < cycles) {
+		for (int i = 0; i < 4; i++) {
+			if (context->counters[i]) {
+				context->counters[i] -= 1;
+			}
+			if (!context->counters[i]) {
+				context->counters[i] = context->counter_load[i];
+				context->output_state[i] = !context->output_state[i];
+				if (i == 3 && context->output_state[i]) {
+					context->noise_out = context->lsfr & 1;
+					context->lsfr = (context->lsfr >> 1) | (context->lsfr << 15);
+					if (context->noise_type) {
+						//white noise
+						if (context->lsfr & 0x40) {
+							context->lsfr ^= 0x8000;
+						}
+					}
+				}
+			}
+		}
+		context->buffer_fraction += context->buffer_inc;
+		if (context->buffer_fraction >= 1.0) {
+			context->buffer_fraction -= 1.0;
+			int16_t acc = 0;
+			for (int i = 0; i < 3; i++) {
+				if (context->output_state[i]) {
+					acc += volume_table[context->volume[i]];
+				}
+			}
+			if (context->noise_out) {
+				acc += volume_table[context->volume[3]];
+			}
+			context->audio_buffer[context->buffer_pos++] = acc;
+			if (context->buffer_pos == context->samples_frame) {
+				render_wait_audio(context);
+			}
+		}
+		context->cycles++;
+	}
+}
+