changeset 1300:4b893b02444e

Basic implementation of CSM mode that should handle documented edge cases. Dodesn't handle the weird undocumented edge cases I don't have a good understanding of yet though
author Michael Pavone <pavone@retrodev.com>
date Sat, 25 Mar 2017 15:41:52 -0700
parents da1ffc4026c4
children babff81e4cfd
files ym2612.c ym2612.h
diffstat 2 files changed, 67 insertions(+), 25 deletions(-) [+]
line wrap: on
line diff
--- a/ym2612.c	Sat Mar 25 11:31:43 2017 -0700
+++ b/ym2612.c	Sat Mar 25 15:41:52 2017 -0700
@@ -252,6 +252,39 @@
 #define TIMER_A_MAX 1023
 #define TIMER_B_MAX 255
 
+#define CSM_MODE 0x80
+
+static void keyon(ym_operator *op, ym_channel *channel)
+{
+	//Deal with "infinite" attack rates
+	uint8_t rate = op->rates[PHASE_ATTACK];
+	if (rate) {
+		uint8_t ks = channel->keycode >> op->key_scaling;;
+		rate = rate*2 + ks;
+	}
+	if (rate >= 62) {
+		op->env_phase = PHASE_DECAY;
+		op->envelope = 0;
+	} else {
+		op->env_phase = PHASE_ATTACK;
+	}
+	op->phase_counter = 0;
+}
+
+static const uint8_t keyon_bits[] = {0x10, 0x40, 0x20, 0x80};
+
+static void csm_keyoff(ym2612_context *context)
+{
+	context->csm_keyon = 0;
+	uint8_t changes = 0xF0 ^ context->channels[2].keyon;
+	for (uint8_t op = 2*4, bit = 0; op < 3*4; op++, bit++)
+	{
+		if (changes & keyon_bits[bit]) {
+			context->operators[op].env_phase = PHASE_RELEASE;
+		}
+	}
+}
+
 void ym_run(ym2612_context * context, uint32_t to_cycle)
 {
 	//printf("Running YM2612 from cycle %d to cycle %d\n", context->current_cycle, to_cycle);
@@ -262,6 +295,9 @@
 			if (context->timer_control & BIT_TIMERA_ENABLE) {
 				if (context->timer_a != TIMER_A_MAX) {
 					context->timer_a++;
+					if (context->csm_keyon) {
+						csm_keyoff(context);
+					}
 				} else {
 					if (context->timer_control & BIT_TIMERA_LOAD) {
 						context->timer_control &= ~BIT_TIMERA_LOAD;
@@ -269,6 +305,16 @@
 						context->status |= BIT_STATUS_TIMERA;
 					}
 					context->timer_a = context->timer_a_load;
+					if (!context->csm_keyon && context->ch3_mode == CSM_MODE) {
+						context->csm_keyon = 0xF0;
+						uint8_t changes = 0xF0 ^ context->channels[2].keyon;;
+						for (uint8_t op = 2*4, bit = 0; op < 3*4; op++, bit++)
+						{
+							if (changes & keyon_bits[bit]) {
+								keyon(context->operators + op, context->channels + 2);
+							}
+						}
+					}
 				}
 			}
 			if (!context->sub_timer_b) {
@@ -309,22 +355,12 @@
 				//operator->envelope = operator->sustain_level;
 				operator->env_phase = PHASE_SUSTAIN;
 			}
-			for(;;) {
-				rate = operator->rates[operator->env_phase];
-				if (rate) {
-					uint8_t ks = channel->keycode >> operator->key_scaling;;
-					rate = rate*2 + ks;
-					if (rate > 63) {
-						rate = 63;
-					}
-				}
-				//Deal with "infinite" rates
-				//According to Nemesis this should be handled in key-on instead
-				if (rate >= 62 && operator->env_phase == PHASE_ATTACK) {
-					operator->env_phase = PHASE_DECAY;
-					operator->envelope = 0;
-				} else {
-					break;
+			rate = operator->rates[operator->env_phase];
+			if (rate) {
+				uint8_t ks = channel->keycode >> operator->key_scaling;;
+				rate = rate*2 + ks;
+				if (rate > 63) {
+					rate = 63;
 				}
 			}
 			uint32_t cycle_shift = rate < 0x30 ? ((0x2F - rate) >> 2) : 0;
@@ -727,6 +763,9 @@
 			if (value & BIT_TIMERB_RESET) {
 				context->status &= ~BIT_STATUS_TIMERB;
 			}
+			if (context->ch3_mode == CSM_MODE && (value & 0xC0) != CSM_MODE && context->csm_keyon) {
+				csm_keyoff(context);
+			}
 			context->ch3_mode = value & 0xC0;
 			break;
 		}
@@ -736,19 +775,20 @@
 				if (channel > 2) {
 					channel--;
 				}
-				uint8_t bits[] = {0x10, 0x40, 0x20, 0x80};
+				uint8_t changes = channel == 2 
+					? (value | context->csm_keyon) ^  (context->channels[channel].keyon | context->csm_keyon)
+					: value ^ context->channels[channel].keyon;
+				context->channels[channel].keyon = value & 0xF0;
 				for (uint8_t op = channel * 4, bit = 0; op < (channel + 1) * 4; op++, bit++) {
-					if (value & bits[bit]) {
-						if (context->operators[op].env_phase == PHASE_RELEASE)
-						{
+					if (changes & keyon_bits[bit]) {
+						if (value & keyon_bits[bit]) {
 							first_key_on = 1;
 							//printf("Key On for operator %d in channel %d\n", op, channel);
-							context->operators[op].phase_counter = 0;
-							context->operators[op].env_phase = PHASE_ATTACK;
+							keyon(context->operators + op, context->channels + channel);
+						} else {
+							//printf("Key Off for operator %d in channel %d\n", op, channel);
+							context->operators[op].env_phase = PHASE_RELEASE;
 						}
-					} else {
-						//printf("Key Off for operator %d in channel %d\n", op, channel);
-						context->operators[op].env_phase = PHASE_RELEASE;
 					}
 				}
 			}
--- a/ym2612.h	Sat Mar 25 11:31:43 2017 -0700
+++ b/ym2612.h	Sat Mar 25 15:41:52 2017 -0700
@@ -42,6 +42,7 @@
 	uint8_t  ams;
 	uint8_t  pms;
 	uint8_t  lr;
+	uint8_t  keyon;
 } ym_channel;
 
 typedef struct {
@@ -93,6 +94,7 @@
 	uint8_t     lfo_counter;
 	uint8_t     lfo_am_step;
 	uint8_t     lfo_pm_step;
+	uint8_t     csm_keyon;
 	uint8_t     status;
 	uint8_t     selected_reg;
 	uint8_t     selected_part;