diff ymf262.c @ 2565:eb588f22ec76

More work on OPL3 emulation
author Michael Pavone <pavone@retrodev.com>
date Sun, 26 Jan 2025 23:32:40 -0800
parents 3f58fec775df
children
line wrap: on
line diff
--- 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;