changeset 2116:cd057d6fe030

Initial stab at subcode emulation
author Michael Pavone <pavone@retrodev.com>
date Sun, 06 Mar 2022 22:03:52 -0800
parents e93ced356a21
children 03304d350339
files cdd_mcu.c cdd_mcu.h cdimage.c segacd.c segacd.h system.h
diffstat 6 files changed, 161 insertions(+), 52 deletions(-) [+]
line wrap: on
line diff
--- a/cdd_mcu.c	Sat Mar 05 14:17:59 2022 -0800
+++ b/cdd_mcu.c	Sun Mar 06 22:03:52 2022 -0800
@@ -7,6 +7,7 @@
 #define SECTOR_CLOCKS (CD_BLOCK_CLKS/75)
 #define NIBBLE_CLOCKS (CDD_MCU_DIVIDER * 77)
 #define BYTE_CLOCKS (SECTOR_CLOCKS/2352) // 96
+#define SUBCODE_CLOCKS (SECTOR_CLOCKS/98)
 #define PROCESSING_DELAY 54000 //approximate, based on Wondermega M1 measurements
 
 //lead in start max diameter 46 mm
@@ -38,20 +39,26 @@
 void cdd_mcu_init(cdd_mcu *context, system_media *media)
 {
 	context->next_int_cycle = CYCLE_NEVER;
-	context->last_subcode_cycle = CYCLE_NEVER;
+	context->next_subcode_int_cycle = CYCLE_NEVER;
+	context->last_sector_cycle = CYCLE_NEVER;
 	context->last_nibble_cycle = CYCLE_NEVER;
 	context->next_byte_cycle = 0;
+	context->next_subcode_cycle = CYCLE_NEVER;
 	context->requested_format = SF_NOTREADY;
 	context->media = media;
 	context->current_status_nibble = -1;
 	context->current_cmd_nibble = -1;
 	context->current_sector_byte = -1;
+	context->current_subcode_byte = -1;
+	context->current_subcode_dest = 0;
 }
 
 enum {
 	GAO_CDD_CTRL,
 	GAO_CDD_STATUS,
-	GAO_CDD_CMD = GAO_CDD_STATUS+5
+	GAO_CDD_CMD = GAO_CDD_STATUS+5,
+	GAO_SUBCODE_ADDR = (0x68-0x36)/2,
+	GAO_SUBCODE_START = (0x100-0x36)/2
 };
 //GAO_CDD_CTRL
 #define BIT_MUTE 0x100
@@ -376,7 +383,7 @@
 			context->error_status = DS_CMD_ERROR;
 			break;
 		}
-		if (context->requested_format == SF_TOCT || context->requested_format == SF_TOCN) {
+		if (context->requested_format == SF_TOCT || context->requested_format == SF_TOCN || context->requested_format == SF_TOCO) {
 			context->requested_format = SF_ABSOLUTE;
 		}
 		if (!context->toc_valid) {
@@ -541,12 +548,12 @@
 		gate_array[GAO_CDD_CTRL] |= BIT_MUTE;
 		return;
 	}
-	uint32_t next_subcode = context->last_subcode_cycle + SECTOR_CLOCKS;
+	uint32_t next_subcode = context->last_sector_cycle + SECTOR_CLOCKS;
 	uint32_t next_nibble;
 	if (context->current_status_nibble > 0) {
 		next_nibble = context->last_nibble_cycle + NIBBLE_CLOCKS;
 	} else if (!context->current_status_nibble) {
-		next_nibble = context->last_subcode_cycle + PROCESSING_DELAY;
+		next_nibble = context->last_sector_cycle + PROCESSING_DELAY;
 	} else {
 		next_nibble = CYCLE_NEVER;
 	}
@@ -555,14 +562,22 @@
 	for (; context->cycle < cd_cycle; context->cycle += CDD_MCU_DIVIDER)
 	{
 		if (context->cycle >= next_subcode) {
-			context->last_subcode_cycle = context->cycle;
+			context->last_sector_cycle = context->cycle;
 			next_subcode = context->cycle + SECTOR_CLOCKS;
 			update_status(context, gate_array);
 			next_nibble = context->cycle + PROCESSING_DELAY;
 			context->current_status_nibble = 0;
 			gate_array[GAO_CDD_STATUS] |= BIT_DRS;
+			if (context->next_subcode_int_cycle != CYCLE_NEVER) {
+				context->subcode_int_pending = 1;
+			}
 			if ((context->status == DS_PLAY || context->status == DS_PAUSE) && context->head_pba >= LEADIN_SECTORS) {
 				context->current_sector_byte = 0;
+				context->current_subcode_byte = 0;
+				context->next_subcode_cycle = context->cycle;
+				context->next_subcode_int_cycle = cd_block_to_mclks(next_subcode);
+			} else {
+				context->next_subcode_int_cycle = CYCLE_NEVER;
 			}
 		}
 		if (context->cycle >= next_nibble) {
@@ -618,7 +633,7 @@
 				cdd_fader_data(fader, 0);
 				if (context->current_sector_byte >= 0) {
 					next_subcode += BYTE_CLOCKS;
-					context->last_subcode_cycle += BYTE_CLOCKS;
+					context->last_sector_cycle += BYTE_CLOCKS;
 				}
 			}
 			if (context->current_sector_byte == 2352) {
@@ -626,6 +641,36 @@
 			}
 			context->next_byte_cycle += BYTE_CLOCKS;
 		}
+		if (context->cycle >= context->next_subcode_cycle) {
+			uint8_t byte;
+			if (!context->current_subcode_byte) {
+				byte = 0x9F;
+				//This probably happens after the second sync symbol, but doing it here simplifies things a little
+				context->current_subcode_dest &= 0x7E;
+				gate_array[GAO_SUBCODE_ADDR] = (context->current_subcode_dest - 96) & 0x7E;
+			} else if (context->current_subcode_byte == 1) {
+				byte = 0xFD;
+			} else {
+				byte = context->media->read_subcodes(context->media, context->current_subcode_byte - 2);
+			}
+			int offset = GAO_SUBCODE_START + (context->current_subcode_dest >> 1);
+			if (context->current_subcode_dest & 1) {
+				gate_array[offset] &= 0xFF00;
+				gate_array[offset] |= byte;
+			} else {
+				gate_array[offset] &= 0x00FF;
+				gate_array[offset] |= byte << 8;
+			}
+			context->current_subcode_byte++;
+			if (context->current_subcode_byte == 98) {
+				context->current_subcode_byte = 0;
+			} else if (context->current_subcode_byte == 32) {
+				gate_array[GAO_SUBCODE_ADDR] |= 0x80;
+			}
+			context->current_subcode_dest++;
+			context->current_subcode_dest &= 0x7F;
+			context->next_subcode_cycle += SUBCODE_CLOCKS;
+		}
 	}
 }
 
@@ -643,13 +688,13 @@
 
 void cdd_hock_enabled(cdd_mcu *context)
 {
-	context->last_subcode_cycle = context->cycle;
+	context->last_sector_cycle = context->cycle;
 	context->next_int_cycle = cd_block_to_mclks(context->cycle + SECTOR_CLOCKS + PROCESSING_DELAY + 7 * NIBBLE_CLOCKS);
 }
 
 void cdd_hock_disabled(cdd_mcu *context)
 {
-	context->last_subcode_cycle = CYCLE_NEVER;
+	context->last_sector_cycle = CYCLE_NEVER;
 	context->next_int_cycle = CYCLE_NEVER;
 	context->last_nibble_cycle = CYCLE_NEVER;
 	context->current_status_nibble = -1;
@@ -667,11 +712,11 @@
 	if (context->next_int_cycle != CYCLE_NEVER) {
 		context->next_int_cycle -= deduction;
 	}
-	if (context->last_subcode_cycle != CYCLE_NEVER) {
-		if (context->last_subcode_cycle > cd_deduction) {
-			context->last_subcode_cycle -= cd_deduction;
+	if (context->last_sector_cycle != CYCLE_NEVER) {
+		if (context->last_sector_cycle > cd_deduction) {
+			context->last_sector_cycle -= cd_deduction;
 		} else {
-			context->last_subcode_cycle = 0;
+			context->last_sector_cycle = 0;
 		}
 	}
 	if (context->last_nibble_cycle != CYCLE_NEVER) {
@@ -682,4 +727,7 @@
 		}
 	}
 	context->next_byte_cycle -= cd_deduction;
+	if (context->next_subcode_cycle != CYCLE_NEVER) {
+		context->next_subcode_cycle -= cd_deduction;
+	}
 }
--- a/cdd_mcu.h	Sat Mar 05 14:17:59 2022 -0800
+++ b/cdd_mcu.h	Sun Mar 06 22:03:52 2022 -0800
@@ -136,12 +136,16 @@
 	system_media  *media;
 	uint32_t      cycle;          //this is in CD block CLKS
 	uint32_t      next_int_cycle; //this is in SCD MCLKS
-	uint32_t      last_subcode_cycle;
+	uint32_t      next_subcode_int_cycle;
+	uint32_t      last_sector_cycle;
 	uint32_t      last_nibble_cycle;
 	uint32_t      next_byte_cycle;
+	uint32_t      next_subcode_cycle;
 	int           current_status_nibble;
 	int           current_cmd_nibble;
 	int           current_sector_byte;
+	int           current_subcode_byte;
+	int           current_subcode_dest;
 	uint32_t      head_pba;
 	uint32_t      seek_pba;
 	uint32_t      pause_pba;
@@ -154,6 +158,7 @@
 	uint8_t       cmd_recv_wait;
 	uint8_t       cmd_recv_pending;
 	uint8_t       int_pending;
+	uint8_t       subcode_int_pending;
 	uint8_t       toc_valid;
 	uint8_t       first_cmd_received;
 	uint8_t       seeking;
--- a/cdimage.c	Sat Mar 05 14:17:59 2022 -0800
+++ b/cdimage.c	Sun Mar 06 22:03:52 2022 -0800
@@ -107,6 +107,16 @@
 			if (track) {
 				lba -= media->tracks[track - 1].end_lba;
 			}
+			if (media->tracks[track].has_subcodes) {
+				if (!media->tmp_buffer) {
+					media->tmp_buffer = calloc(1, 96);
+				}
+				fseek(media->tracks[track].f, media->tracks[track].file_offset + (lba + 1) * media->tracks[track].sector_bytes - 96, SEEK_SET);
+				int bytes = fread(media->tmp_buffer, 1, 96, media->tracks[track].f);
+				if (bytes != 96) {
+					fprintf(stderr, "Only read %d subcode bytes\n", bytes);
+				}
+			}
 			fseek(media->tracks[track].f, media->tracks[track].file_offset + lba * media->tracks[track].sector_bytes, SEEK_SET);
 		}
 	}
@@ -153,6 +163,16 @@
 	}
 }
 
+static uint8_t bin_subcode_read(system_media *media, uint32_t offset)
+{
+	if (media->in_fake_pregap || !media->tracks[media->cur_track].has_subcodes) {
+		//TODO: Fake PQ subcodes
+		return 0;
+	}
+	//TODO: Translate "cooked" subcodes back to raw format
+	return media->tmp_buffer[offset];
+}
+
 uint8_t parse_cue(system_media *media)
 {
 	char *line = media->buffer;
@@ -318,10 +338,11 @@
 			tracks[0].fake_pregap = 2 * 75;
 		}
 
-		fseek(tracks[0].f, tracks[0].sector_bytes == 2352 ? 16 : 0, SEEK_SET);
+		fseek(tracks[0].f, tracks[0].sector_bytes >= 2352 ? 16 : 0, SEEK_SET);
 		media->size = fread(media->buffer, 1, 2048, tracks[0].f);
 		media->seek = bin_seek;
 		media->read = bin_read;
+		media->read_subcodes = bin_subcode_read;
 	}
 	uint8_t valid = media->num_tracks > 0 && media->tracks[0].f != NULL;
 	media->type = valid ? MEDIA_CDROM : MEDIA_CART;
@@ -379,8 +400,10 @@
 						//TODO: record whether subcode is in raw format or not
 						if (startswith(cmd, "RW_RAW")) {
 							tracks[track].sector_bytes += 96;
+							tracks[track].has_subcodes = SUBCODES_RAW;
 						} else if (startswith(cmd, "RW")) {
 							tracks[track].sector_bytes += 96;
+							tracks[track].has_subcodes = SUBCODES_COOKED;
 						}
 					}
 				}
@@ -485,6 +508,7 @@
 		media->size = fread(media->buffer, 1, 2048, tracks[0].f);
 		media->seek = bin_seek;
 		media->read = bin_read;
+		media->read_subcodes = bin_subcode_read;
 	}
 	uint8_t valid = media->num_tracks > 0 && media->tracks[0].f != NULL;
 	media->type = valid ? MEDIA_CDROM : MEDIA_CART;
@@ -508,11 +532,13 @@
 		.start_lba = 0,
 		.end_lba = file_size(f),
 		.sector_bytes = 2048,
+		.has_subcodes = SUBCODES_NONE,
 		.need_swap = 0,
 		.type = TRACK_DATA
 	};
 	media->type = MEDIA_CDROM;
 	media->seek = bin_seek;
 	media->read = bin_read;
+	media->read_subcodes = bin_subcode_read;
 	return media->size;
 }
--- a/segacd.c	Sat Mar 05 14:17:59 2022 -0800
+++ b/segacd.c	Sun Mar 06 22:03:52 2022 -0800
@@ -55,6 +55,8 @@
 	GA_FONT_DATA1,
 	GA_FONT_DATA2,
 	GA_FONT_DATA3,
+	GA_SUBCODE_START = 0x80,
+	GA_SUBCODE_MIRROR = 0xC0,
 
 	GA_HINT_VECTOR = GA_CDC_REG_DATA
 };
@@ -387,48 +389,57 @@
 	context->int_cycle = CYCLE_NEVER;
 	uint8_t mask = context->status & 0x7;
 	uint32_t cdc_cycle = CYCLE_NEVER;
-	if (mask < 5) {
-		if (cd->gate_array[GA_INT_MASK] & BIT_MASK_IEN5) {
-			cdc_cycle = lc8951_next_interrupt(&cd->cdc);
-			//CDC interrupts only generated on falling edge of !INT signal
-			if (cd->cdc_int_ack) {
-				if (cdc_cycle > cd->cdc.cycle) {
-					cd->cdc_int_ack = 0;
-				} else {
-					cdc_cycle = CYCLE_NEVER;
-				}
-			}
-			if (cdc_cycle < context->int_cycle) {
-				context->int_cycle = cdc_cycle;
-				context->int_num = 5;
+	if (mask < 6) {
+		if (cd->gate_array[GA_INT_MASK] & BIT_MASK_IEN6) {
+			uint32_t subcode_cycle = cd->cdd.subcode_int_pending ? context->current_cycle : cd->cdd.next_subcode_int_cycle;
+			if (subcode_cycle != CYCLE_NEVER) {
+				context->int_cycle = subcode_cycle;
+				context->int_num = 6;
 			}
 		}
-		if (mask < 4) {
-			if (cd->gate_array[GA_INT_MASK] & BIT_MASK_IEN4) {
-				uint32_t cdd_cycle = cd->cdd.int_pending ? context->current_cycle : cd->cdd.next_int_cycle;
-				if (cdd_cycle < context->int_cycle) {
-					context->int_cycle = cdd_cycle;
-					context->int_num = 4;
+		if (mask < 5) {
+			if (cd->gate_array[GA_INT_MASK] & BIT_MASK_IEN5) {
+				cdc_cycle = lc8951_next_interrupt(&cd->cdc);
+				//CDC interrupts only generated on falling edge of !INT signal
+				if (cd->cdc_int_ack) {
+					if (cdc_cycle > cd->cdc.cycle) {
+						cd->cdc_int_ack = 0;
+					} else {
+						cdc_cycle = CYCLE_NEVER;
+					}
+				}
+				if (cdc_cycle < context->int_cycle) {
+					context->int_cycle = cdc_cycle;
+					context->int_num = 5;
 				}
 			}
-			if (mask < 3) {
-				uint32_t next_timer;
-				if (cd->gate_array[GA_INT_MASK] & BIT_MASK_IEN3) {
-					uint32_t next_timer_cycle = next_timer_int(cd);
-					if (next_timer_cycle < context->int_cycle) {
-						context->int_cycle = next_timer_cycle;
-						context->int_num = 3;
+			if (mask < 4) {
+				if (cd->gate_array[GA_INT_MASK] & BIT_MASK_IEN4) {
+					uint32_t cdd_cycle = cd->cdd.int_pending ? context->current_cycle : cd->cdd.next_int_cycle;
+					if (cdd_cycle < context->int_cycle) {
+						context->int_cycle = cdd_cycle;
+						context->int_num = 4;
 					}
 				}
-				if (mask < 2) {
-					if (cd->int2_cycle < context->int_cycle && (cd->gate_array[GA_INT_MASK] & BIT_MASK_IEN2)) {
-						context->int_cycle = cd->int2_cycle;
-						context->int_num = 2;
+				if (mask < 3) {
+					uint32_t next_timer;
+					if (cd->gate_array[GA_INT_MASK] & BIT_MASK_IEN3) {
+						uint32_t next_timer_cycle = next_timer_int(cd);
+						if (next_timer_cycle < context->int_cycle) {
+							context->int_cycle = next_timer_cycle;
+							context->int_num = 3;
+						}
 					}
-					if (mask < 1) {
-						if (cd->graphics_int_cycle < context->int_cycle && (cd->gate_array[GA_INT_MASK] & BIT_MASK_IEN1)) {
-							context->int_cycle = cd->graphics_int_cycle;
-							context->int_num = 1;
+					if (mask < 2) {
+						if (cd->int2_cycle < context->int_cycle && (cd->gate_array[GA_INT_MASK] & BIT_MASK_IEN2)) {
+							context->int_cycle = cd->int2_cycle;
+							context->int_num = 2;
+						}
+						if (mask < 1) {
+							if (cd->graphics_int_cycle < context->int_cycle && (cd->gate_array[GA_INT_MASK] & BIT_MASK_IEN1)) {
+								context->int_cycle = cd->graphics_int_cycle;
+								context->int_num = 1;
+							}
 						}
 					}
 				}
@@ -521,6 +532,7 @@
 			value |= pixel << (i * 4);
 		}
 		return value;
+	}
 	case GA_STAMP_SIZE:
 	case GA_IMAGE_BUFFER_LINES:
 		//these two have bits that change based on graphics operations
@@ -529,8 +541,10 @@
 	case GA_TRACE_VECTOR_BASE:
 		//write only
 		return 0xFFFF;
-	}
 	default:
+		if (reg >= GA_SUBCODE_MIRROR) {
+			return cd->gate_array[GA_SUBCODE_START + (reg & 0x3F)];
+		}
 		return cd->gate_array[reg];
 	}
 }
@@ -655,6 +669,10 @@
 		calculate_target_cycle(m68k);
 		break;
 	case GA_INT_MASK:
+		if (!(cd->gate_array[reg] & BIT_MASK_IEN6)) {
+			//subcode interrupts can't be made pending when they are disabled in this reg
+			cd->cdd.subcode_int_pending = 0;
+		}
 		cd->gate_array[reg] = value & (BIT_MASK_IEN6|BIT_MASK_IEN5|BIT_MASK_IEN4|BIT_MASK_IEN3|BIT_MASK_IEN2|BIT_MASK_IEN1);
 		calculate_target_cycle(m68k);
 		break;
@@ -903,6 +921,9 @@
 	case 5:
 		cd->cdc_int_ack = 1;
 		break;
+	case 6:
+		cd->cdd.subcode_int_pending = 0;
+		break;
 	}
 	context->int_ack = 0;
 	calculate_target_cycle(context);
--- a/segacd.h	Sat Mar 05 14:17:59 2022 -0800
+++ b/segacd.h	Sun Mar 06 22:03:52 2022 -0800
@@ -9,7 +9,7 @@
 	m68k_context    *m68k;
 	system_media    *media;
 	genesis_context *genesis;
-	uint16_t        gate_array[0x100];
+	uint16_t        gate_array[0xC0];
 	uint16_t        *rom;     //unaltered ROM, needed for mirrored locations
 	uint16_t        *rom_mut; //ROM with low 16-bit of HINT vector modified by register write
 	uint16_t        *prog_ram;
--- a/system.h	Sat Mar 05 14:17:59 2022 -0800
+++ b/system.h	Sun Mar 06 22:03:52 2022 -0800
@@ -90,6 +90,12 @@
 	TRACK_DATA
 } track_type;
 
+enum {
+	SUBCODES_NONE,
+	SUBCODES_RAW,
+	SUBCODES_COOKED
+};
+
 typedef struct {
 	FILE       *f;
 	uint32_t   file_offset;
@@ -99,6 +105,7 @@
 	uint32_t   end_lba;
 	uint16_t   sector_bytes;
 	uint8_t    need_swap;
+	uint8_t    has_subcodes;
 	track_type type;
 } track_info;
 
@@ -112,8 +119,10 @@
 	char         *extension;
 	system_media *chain;
 	track_info   *tracks;
+	uint8_t      *tmp_buffer;
 	seek_fun     seek;
 	read_fun     read;
+	read_fun     read_subcodes;
 	uint32_t     num_tracks;
 	uint32_t     cur_track;
 	uint32_t     size;