changeset 2565:eb588f22ec76

More work on OPL3 emulation
author Michael Pavone <pavone@retrodev.com>
date Sun, 26 Jan 2025 23:32:40 -0800
parents 553a0b4888db
children e5de445e2cf0
files ym2612.c ym_common.c ym_common.h ymf262.c
diffstat 4 files changed, 259 insertions(+), 47 deletions(-) [+]
line wrap: on
line diff
--- a/ym2612.c	Sun Jan 26 01:02:18 2025 -0800
+++ b/ym2612.c	Sun Jan 26 23:32:40 2025 -0800
@@ -38,13 +38,6 @@
 
 static uint32_t ym_calc_phase_inc(ym2612_context * context, ym_operator * operator, uint32_t op);
 
-enum {
-	PHASE_ATTACK,
-	PHASE_DECAY,
-	PHASE_SUSTAIN,
-	PHASE_RELEASE
-};
-
 static int16_t ams_shift[] = {8, 1, -1, -2};
 static uint8_t lfo_timer_values[] = {108, 77, 71, 67, 62, 44, 8, 5};
 
@@ -208,48 +201,8 @@
 
 #define CSM_MODE 0x80
 
-#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];
-	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;
-	}
-}
-
-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;
--- a/ym_common.c	Sun Jan 26 01:02:18 2025 -0800
+++ b/ym_common.c	Sun Jan 26 23:32:40 2025 -0800
@@ -191,3 +191,36 @@
 	}
 	return output;
 }
+
+void start_envelope(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;
+	}
+}
+
+void keyon(ym_operator *op, ym_channel *channel)
+{
+	start_envelope(op, channel);
+	op->phase_counter = 0;
+	op->inverted = op->ssg & SSG_INVERT;
+}
+
+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;
+	}
+}
--- a/ym_common.h	Sun Jan 26 01:02:18 2025 -0800
+++ b/ym_common.h	Sun Jan 26 23:32:40 2025 -0800
@@ -6,6 +6,20 @@
 #define MAX_ENVELOPE 0xFFC
 #define MAX_OPL_ENVELOPE 0xFF8
 
+#define SSG_ENABLE    8
+#define SSG_INVERT    4
+#define SSG_ALTERNATE 2
+#define SSG_HOLD      1
+
+#define SSG_CENTER 0x800
+
+enum {
+	PHASE_ATTACK,
+	PHASE_DECAY,
+	PHASE_SUSTAIN,
+	PHASE_RELEASE
+};
+
 typedef struct {
 	int16_t  *mod_src[2];
 	uint32_t phase_counter;
@@ -23,6 +37,7 @@
 	uint8_t  ssg;
 	uint8_t  inverted;
 	uint8_t  phase_overflow;
+	uint8_t  wave;
 } ym_operator;
 
 typedef struct {
@@ -50,5 +65,8 @@
 void ym_init_tables(void);
 int16_t ym_sine(uint16_t phase, int16_t mod, uint16_t env);
 int16_t ym_opl_wave(uint16_t phase, int16_t mod, uint16_t env, uint8_t waveform);
+void start_envelope(ym_operator *op, ym_channel *channel);
+void keyon(ym_operator *op, ym_channel *channel);
+void keyoff(ym_operator *op);
 
 #endif //YM_COMMON_H_
--- a/ymf262.c	Sun Jan 26 01:02:18 2025 -0800
+++ b/ymf262.c	Sun Jan 26 23:32:40 2025 -0800
@@ -56,6 +56,73 @@
 	context->selected_part = 0;
 }
 
+static void ymf262_update_connections(ymf262_context *context, uint8_t channel, uint8_t csel_bit)
+{
+	uint8_t channel_off = channel >= 9 ? channel - 9 : channel;
+	uint8_t op = channel_off;
+	if (op > 5) {
+		op += 6;
+	} else if (op > 2) {
+		op += 3;
+	}
+	if (channel >= 9) {
+		op += 18;
+	}
+	if (context->opl3_mode && channel_off < 6 && (context->connection_sel & csel_bit)) {
+		if (channel_off > 2) {
+			channel -= 3;
+			op -= 6;
+		}
+		uint8_t alg = (context->channels[channel].algorithm & 1) << 1;
+		alg |= context->channels[channel + 3].algorithm & 1;
+		switch (alg)
+		{
+		case 0:
+			context->operators[op + 3].mod_src[0] = &context->operators[op].output;
+			context->operators[op + 6].mod_src[0] = &context->operators[op + 3].output;
+			context->operators[op + 9].mod_src[0] = &context->operators[op + 6].output;
+			break;
+		case 1:
+			context->operators[op + 3].mod_src[0] = &context->operators[op].output;
+			context->operators[op + 6].mod_src[0] = NULL;
+			context->operators[op + 9].mod_src[0] = &context->operators[op + 6].output;
+			break;
+		case 2:
+			context->operators[op + 3].mod_src[0] = NULL;
+			context->operators[op + 6].mod_src[0] = &context->operators[op + 3].output;
+			context->operators[op + 9].mod_src[0] = &context->operators[op + 6].output;
+			break;
+		case 3:
+			context->operators[op + 3].mod_src[0] = NULL;
+			context->operators[op + 6].mod_src[0] = &context->operators[op + 3].output;
+			context->operators[op + 9].mod_src[0] = NULL;
+			break;
+		}
+	} else {
+		context->operators[op].mod_src[0] = NULL;
+		context->operators[op + 3].mod_src[0] = context->channels[channel].algorithm ? NULL : &context->operators[op].output;
+	}
+}
+
+void ymf262_calc_phase_inc(ymf262_context *context, ym_channel *channel, ym_operator *operator)
+{
+	int32_t inc = channel->fnum;
+	//TODO: vibrato?
+	if (!channel->block) {
+		inc >>= 1;
+	} else {
+		inc <<= (channel->block-1);
+	}
+	if (operator->multiple) {
+		inc *= operator->multiple;
+		inc &= 0xFFFFF;
+	} else {
+		//0.5
+		inc >>= 1;
+	}
+	operator->phase_inc = inc;
+}
+
 #define OPL3_NTS 0x08
 
 void ymf262_data_write(ymf262_context *context, uint8_t value)
@@ -65,12 +132,134 @@
 	}
 	uint8_t old = 0;
 	if (context->selected_reg >= OPL3_PARAM_START && context->selected_reg < OPL3_PARAM_END) {
+		uint8_t channel, op;
 		if (context->selected_part) {
 			old = context->part2_regs[context->selected_reg - OPL3_PARAM_START];
 			context->part2_regs[context->selected_reg - OPL3_PARAM_START] = value;
+			channel = 9;
+			op = 18;
 		} else {
 			old = context->part1_regs[context->selected_reg - OPL3_PARAM_START];
 			context->part1_regs[context->selected_reg - OPL3_PARAM_START] = value;
+			channel = 0;
+			op = 0;
+		}
+		if (context->selected_reg < 0xA0 || context->selected_reg >= 0xE0) {
+			uint8_t op_off = context->selected_reg & 0x1F;
+			if ((op_off >= 0x26 && op_off < 0x28) || (op_off >= 0x2E && op_off < 0x30) || op_off > 0x35) {
+				return;
+			}
+			if (op_off >= 0x30) {
+				op_off -= 4;
+			} else if (op_off >= 0x28) {
+				op_off -= 2;
+			}
+			op += op_off;
+			ym_operator *operator = context->operators + op;
+			switch (context->selected_reg & 0xE0)
+			{
+			case 0x20:
+				operator->multiple = value & 0xF;
+				operator->rates[PHASE_SUSTAIN] = (value & 0x20) ? 0 : operator->rates[PHASE_RELEASE];
+				operator->am = value & 0x80;
+				//TODO: KSR,VIB
+				break;
+			case 0x40:
+				operator->total_level = (value & 0x3F) << 6;
+				//TODO: KSL
+				break;
+			case 0x60:
+				//TODO: what should the LSB be?
+				operator->rates[PHASE_ATTACK] = (value & 0xF0) >> 3 | 1;
+				operator->rates[PHASE_DECAY] = (value & 0xF) << 1 | 1;
+				break;
+			case 0x80:
+				operator->rates[PHASE_RELEASE] = (value & 0xF) << 1 | 1;
+				operator->sustain_level = (value & 0xF0) << 3;
+				if (operator->sustain_level == 0x780) {
+					operator->sustain_level = MAX_ENVELOPE;
+				}
+				if (!((context->selected_part ? context->part2_regs : context->part1_regs)[context->selected_reg - 0x60] & 0x20)) {
+					operator->rates[PHASE_SUSTAIN] = operator->rates[PHASE_RELEASE];
+				}
+				break;
+			case 0xE0:
+				operator->wave = value & (context->opl3_mode ? 0x7 : 0x3);
+				break;
+			}
+		} else {
+			uint8_t channel_off = context->selected_reg & 0xF;
+			if (channel_off > 8 && context->selected_reg != 0xBD) {
+				return;
+			}
+			uint8_t csel_bit = channel_off > 2 ? channel_off - 3 : channel_off;
+			if (channel) {
+				csel_bit += 3;
+			}
+			csel_bit = 1 << csel_bit;
+			if (context->selected_reg < 0xC0 && context->opl3_mode && (channel_off > 2 && channel_off < 6) && (context->connection_sel & csel_bit)) {
+				//ignore writes to "upper" channel in 4-op mode
+				return;
+			}
+			channel += channel_off;
+			op = channel_off;
+			if (op > 5) {
+				op += 6;
+			} else if (op > 2) {
+				op += 3;
+			}
+			ym_channel *chan = context->channels + channel;
+			switch(context->selected_reg & 0xF0)
+			{
+			case 0xA0:
+				chan->fnum &= ~0xFF;
+				chan->fnum |= value;
+				ymf262_calc_phase_inc(context, chan, context->operators + op);
+				ymf262_calc_phase_inc(context, chan, context->operators + op + 3);
+				if (context->opl3_mode && channel_off < 6 && (context->connection_sel & csel_bit)) {
+					//4-op mode
+					ymf262_calc_phase_inc(context, chan, context->operators + op + 6);
+					ymf262_calc_phase_inc(context, chan, context->operators + op + 9);
+				}
+				break;
+			case 0xB0:
+				chan->fnum &= 0xFF;
+				chan->fnum |= (value & 0x3) << 8;
+				chan->block = (value >> 2) & 7;
+				ymf262_calc_phase_inc(context, chan, context->operators + op);
+				ymf262_calc_phase_inc(context, chan, context->operators + op + 3);
+				if (context->opl3_mode && channel_off < 6 && (context->connection_sel & csel_bit)) {
+					//4-op mode
+					ymf262_calc_phase_inc(context, chan, context->operators + op + 6);
+					ymf262_calc_phase_inc(context, chan, context->operators + op + 9);
+				}
+				if ((value ^ old) & 0x20) {
+					if (value & 0x20) {
+						keyon(context->operators + op, chan);
+						keyon(context->operators + op + 3, chan);
+						if (context->opl3_mode && channel_off < 6 && (context->connection_sel & csel_bit)) {
+							//4-op mode
+							keyon(context->operators + op + 6, chan);
+							keyon(context->operators + op + 9, chan);
+						}
+					} else {
+						keyoff(context->operators + op);
+						keyoff(context->operators + op + 3);
+						if (context->opl3_mode && channel_off < 6 && (context->connection_sel & csel_bit)) {
+							//4-op mode
+							keyoff(context->operators + op + 6);
+							keyoff(context->operators + op + 9);
+						}
+					}
+				}
+				break;
+			case 0xC0:
+				chan->algorithm = value & 1;
+				chan->feedback = value >> 1 & 0x7;
+				chan->lr = value & 0xF0;
+				ymf262_update_connections(context, channel, csel_bit);
+				break;
+			}
 		}
 	} else if (context->selected_part) {
 		if (context->selected_reg <= sizeof(context->timer_test)) {
@@ -83,6 +272,7 @@
 			return;
 		}
 	} else {
+	
 		switch (context->selected_reg)
 		{
 		case 0x01:
@@ -92,6 +282,24 @@
 		case 0x04:
 			old = context->connection_sel;
 			context->connection_sel = value;
+			if (context->opl3_mode) {
+				uint8_t changes = old ^ value;
+				for (uint8_t i = 0; i < 6; i++)
+				{
+					uint8_t csel_bit = 1 << i;
+					if (changes & csel_bit) {
+						uint8_t channel = i > 2 ? i + 9 : i;
+						if (value & csel_bit) {
+							//switched to 4-op mode
+							ymf262_update_connections(context, channel, csel_bit);
+						} else {
+							//switched to 2-op mode
+							ymf262_update_connections(context, channel, csel_bit);
+							ymf262_update_connections(context, channel + 3, csel_bit);
+						}
+					}
+				}
+			}
 			break;
 		case 0x05:
 			old = context->opl3_mode;