changeset 1301:babff81e4cfd

Initial implementation of YM2612 SSG-EG mode
author Michael Pavone <pavone@retrodev.com>
date Mon, 27 Mar 2017 00:40:10 -0700
parents 4b893b02444e
children d2cb97ab3cff
files ym2612.c ym2612.h
diffstat 2 files changed, 74 insertions(+), 5 deletions(-) [+]
line wrap: on
line diff
--- a/ym2612.c	Sat Mar 25 15:41:52 2017 -0700
+++ b/ym2612.c	Mon Mar 27 00:40:10 2017 -0700
@@ -254,7 +254,14 @@
 
 #define CSM_MODE 0x80
 
-static void keyon(ym_operator *op, ym_channel *channel)
+#define SSG_ENABLE    8
+#define SSG_INVERT    4
+#define SSG_ALTERNATE 2
+#define SSG_HOLD      1
+
+#define SSG_CENTER 0x800
+
+static void start_envelope(ym_operator *op, ym_channel *channel)
 {
 	//Deal with "infinite" attack rates
 	uint8_t rate = op->rates[PHASE_ATTACK];
@@ -268,11 +275,27 @@
 	} else {
 		op->env_phase = PHASE_ATTACK;
 	}
+}
+
+static void keyon(ym_operator *op, ym_channel *channel)
+{
+	start_envelope(op, channel);
 	op->phase_counter = 0;
+	op->inverted = op->ssg & SSG_INVERT;
 }
 
 static const uint8_t keyon_bits[] = {0x10, 0x40, 0x20, 0x80};
 
+static void keyoff(ym_operator *op)
+{
+	op->env_phase = PHASE_RELEASE;
+	if (op->inverted) {
+		//Nemesis says the inversion state doesn't change here, but I don't see how that is observable either way
+		op->inverted = 0;
+		op->envelope = (SSG_CENTER - op->envelope) & MAX_ENVELOPE;
+	}
+}
+
 static void csm_keyoff(ym2612_context *context)
 {
 	context->csm_keyon = 0;
@@ -280,7 +303,7 @@
 	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;
+			keyoff(context->operators + op);
 		}
 	}
 }
@@ -389,10 +412,20 @@
 						dfprintf(debug_file, "Changing op %d envelope %d by %d in %s phase\n", op, operator->envelope, envelope_inc,
 							operator->env_phase == PHASE_SUSTAIN ? "sustain" : (operator->env_phase == PHASE_DECAY ? "decay": "release"));
 					}
+					if (operator->ssg) {
+						if (operator->envelope < SSG_CENTER) {
+							envelope_inc *= 4;
+						} else {
+							envelope_inc = 0;
+						}
+					}
 					//envelope value is 10-bits, but it will be used as a 4.8 value
 					operator->envelope += envelope_inc << 2;
 					//clamp to max attenuation value
-					if (operator->envelope > MAX_ENVELOPE) {
+					if (
+						operator->envelope > MAX_ENVELOPE 
+						|| (operator->env_phase == PHASE_RELEASE && operator->envelope >= SSG_CENTER)
+					) {
 						operator->envelope = MAX_ENVELOPE;
 					}
 				}
@@ -468,7 +501,31 @@
 				}
 				break;
 			}
-			uint16_t env = operator->envelope + operator->total_level;
+			uint16_t env = operator->envelope;
+			if (operator->ssg) {
+				if (env >= SSG_CENTER) {
+					if (operator->ssg & SSG_ALTERNATE) {
+						if (operator->env_phase != PHASE_RELEASE && (
+							!(operator->ssg & SSG_HOLD) || ((operator->ssg ^ operator->inverted) & SSG_INVERT) == 0
+						)) {
+							operator->inverted ^= SSG_INVERT;
+						}
+					} else if (!(operator->ssg & SSG_HOLD)) {
+						phase = operator->phase_counter = 0;
+					}
+					if (
+						(operator->env_phase == PHASE_DECAY || operator->env_phase == PHASE_SUSTAIN) 
+						&& !(operator->ssg & SSG_HOLD)
+					) {
+						start_envelope(operator, chan);
+						env = operator->envelope;
+					}
+				}
+				if (operator->inverted) {
+					env = (SSG_CENTER - env) & MAX_ENVELOPE;
+				}
+			}
+			env += operator->total_level;
 			if (operator->am) {
 				uint16_t base_am = (context->lfo_am_step & 0x80 ? context->lfo_am_step : ~context->lfo_am_step) & 0x7E;
 				if (ams_shift[chan->ams] >= 0) {
@@ -787,7 +844,7 @@
 							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;
+							keyoff(context->operators + op);
 						}
 					}
 				}
@@ -841,6 +898,15 @@
 					operator->sustain_level = MAX_ENVELOPE;
 				}
 				break;
+			case REG_SSG_EG:
+				if (!(value & SSG_ENABLE)) {
+					value = 0;
+				}
+				if ((value ^ operator->ssg) & SSG_INVERT) {
+					operator->inverted ^= SSG_INVERT;
+				}
+				operator->ssg = value;
+				break;
 			}
 		}
 	} else {
--- a/ym2612.h	Sat Mar 25 15:41:52 2017 -0700
+++ b/ym2612.h	Mon Mar 27 00:40:10 2017 -0700
@@ -27,6 +27,8 @@
 	uint8_t  detune;
 	uint8_t  am;
 	uint8_t  env_phase;
+	uint8_t  ssg;
+	uint8_t  inverted;
 } ym_operator;
 
 typedef struct {
@@ -118,6 +120,7 @@
 	REG_DECAY_AM     = 0x60,
 	REG_SUSTAIN_RATE = 0x70,
 	REG_S_LVL_R_RATE = 0x80,
+	REG_SSG_EG       = 0x90,
 
 	REG_FNUM_LOW     = 0xA0,
 	REG_BLOCK_FNUM_H = 0xA4,