Mercurial > repos > blastem
comparison sms.c @ 2528:90a40be940f7
Implement read-only SC-3000 cassette support
author | Michael Pavone <pavone@retrodev.com> |
---|---|
date | Mon, 25 Nov 2024 22:26:45 -0800 |
parents | 8cf7cadc17ee |
children | a6687a6fb69d |
comparison
equal
deleted
inserted
replaced
2524:25e40370e0e4 | 2528:90a40be940f7 |
---|---|
16 #define z80_handle_code_write(...) | 16 #define z80_handle_code_write(...) |
17 #else | 17 #else |
18 #define Z80_CYCLE current_cycle | 18 #define Z80_CYCLE current_cycle |
19 #define Z80_OPTS options | 19 #define Z80_OPTS options |
20 #endif | 20 #endif |
21 | |
22 enum { | |
23 TAPE_NONE, | |
24 TAPE_STOPPED, | |
25 TAPE_PLAYING, | |
26 TAPE_RECORDING | |
27 }; | |
21 | 28 |
22 static void *memory_io_write(uint32_t location, void *vcontext, uint8_t value) | 29 static void *memory_io_write(uint32_t location, void *vcontext, uint8_t value) |
23 { | 30 { |
24 z80_context *z80 = vcontext; | 31 z80_context *z80 = vcontext; |
25 sms_context *sms = z80->system; | 32 sms_context *sms = z80->system; |
132 sms_context *sms = ppi->system; | 139 sms_context *sms = ppi->system; |
133 sms->kb_mux = data & 0x7; | 140 sms->kb_mux = data & 0x7; |
134 } | 141 } |
135 } | 142 } |
136 | 143 |
144 static void cassette_run(sms_context *sms, uint32_t cycle) | |
145 { | |
146 if (!sms->cassette) { | |
147 return; | |
148 } | |
149 if (cycle > sms->cassette_cycle) { | |
150 uint64_t diff = cycle - sms->cassette_cycle; | |
151 diff *= sms->cassette_wave.sample_rate; | |
152 diff /= sms->normal_clock; | |
153 if (sms->cassette_state == TAPE_PLAYING) { | |
154 uint64_t bytes_per_sample = sms->cassette_wave.num_channels * sms->cassette_wave.bits_per_sample / 8; | |
155 uint64_t offset = diff * bytes_per_sample + sms->cassette_offset; | |
156 if (offset > UINT32_MAX || offset > sms->cassette->size - bytes_per_sample) { | |
157 sms->cassette_offset = sms->cassette->size - bytes_per_sample; | |
158 } else { | |
159 sms->cassette_offset = offset; | |
160 } | |
161 static uint32_t last_displayed_seconds; | |
162 uint32_t seconds = (sms->cassette_offset - (sms->cassette_wave.format_header.size + offsetof(wave_header, audio_format))) / (bytes_per_sample * sms->cassette_wave.sample_rate); | |
163 if (seconds != last_displayed_seconds) { | |
164 last_displayed_seconds = seconds; | |
165 printf("Cassette: %02d:%02d\n", seconds / 60, seconds % 60); | |
166 } | |
167 } | |
168 diff *= sms->normal_clock; | |
169 diff /= sms->cassette_wave.sample_rate; | |
170 sms->cassette_cycle += diff; | |
171 } | |
172 } | |
173 | |
174 static uint8_t cassette_read(sms_context *sms, uint32_t cycle) | |
175 { | |
176 cassette_run(sms, cycle); | |
177 if (sms->cassette_state != TAPE_PLAYING) { | |
178 return 0; | |
179 } | |
180 int64_t sample = 0; | |
181 for (uint16_t i = 0; i < sms->cassette_wave.num_channels; i++) | |
182 { | |
183 if (sms->cassette_wave.audio_format == 3) { | |
184 if (sms->cassette_wave.bits_per_sample == 64) { | |
185 sample += 32767.0 * ((double *)(((char *)sms->cassette->buffer) + sms->cassette_offset))[i]; | |
186 } else if (sms->cassette_wave.bits_per_sample == 32) { | |
187 sample += 32767.0f * ((float *)(((char *)sms->cassette->buffer) + sms->cassette_offset))[i]; | |
188 } | |
189 } else if (sms->cassette_wave.audio_format == 1) { | |
190 if (sms->cassette_wave.bits_per_sample == 32) { | |
191 sample += ((int32_t *)(((char *)sms->cassette->buffer) + sms->cassette_offset))[i]; | |
192 } else if (sms->cassette_wave.bits_per_sample == 16) { | |
193 sample += ((int16_t *)(((char *)sms->cassette->buffer) + sms->cassette_offset))[i]; | |
194 } else if (sms->cassette_wave.bits_per_sample == 8) { | |
195 sample += ((uint8_t *)sms->cassette->buffer)[sms->cassette_offset + i] - 0x80; | |
196 } | |
197 } | |
198 } | |
199 uint32_t bytes_per_sample = sms->cassette_wave.num_channels * sms->cassette_wave.bits_per_sample / 8; | |
200 if (sms->cassette_offset == sms->cassette->size - bytes_per_sample) { | |
201 sms->cassette_state = TAPE_STOPPED; | |
202 puts("Cassette reached end of file, playback stoped"); | |
203 } | |
204 return sample > 0 ? 0x80 : 0; | |
205 } | |
206 | |
207 static void cassette_action(system_header *header, uint8_t action) | |
208 { | |
209 sms_context *sms = (sms_context*)header; | |
210 if (!sms->cassette) { | |
211 return; | |
212 } | |
213 cassette_run(sms, sms->z80->Z80_CYCLE); | |
214 switch(action) | |
215 { | |
216 case CASSETTE_PLAY: | |
217 sms->cassette_state = TAPE_PLAYING; | |
218 puts("Cassette playback started"); | |
219 break; | |
220 case CASSETTE_RECORD: | |
221 break; | |
222 case CASSETTE_STOP: | |
223 sms->cassette_state = TAPE_STOPPED; | |
224 puts("Cassette playback stoped"); | |
225 break; | |
226 case CASSETTE_REWIND: | |
227 sms->cassette_offset = sms->cassette_wave.format_header.size + offsetof(wave_header, audio_format); | |
228 break; | |
229 } | |
230 } | |
231 | |
137 static uint8_t i8255_input_poll(i8255 *ppi, uint32_t cycle, uint32_t port) | 232 static uint8_t i8255_input_poll(i8255 *ppi, uint32_t cycle, uint32_t port) |
138 { | 233 { |
139 if (port > 1) { | 234 if (port > 1) { |
140 return 0xFF; | 235 return 0xFF; |
141 } | 236 } |
142 sms_context *sms = ppi->system; | 237 sms_context *sms = ppi->system; |
143 if (sms->kb_mux == 7) { | 238 if (sms->kb_mux == 7) { |
144 if (port) { | 239 if (port) { |
145 //TODO: cassette-in | |
146 //TODO: printer port BUSY/FAULT | 240 //TODO: printer port BUSY/FAULT |
147 uint8_t port_b = io_data_read(sms->io.ports+1, cycle); | 241 uint8_t port_b = io_data_read(sms->io.ports+1, cycle); |
148 return (port_b >> 2 & 0xF) | 0x10; | 242 return (port_b >> 2 & 0xF) | 0x10 | cassette_read(sms, cycle); |
149 } else { | 243 } else { |
150 uint8_t port_a = io_data_read(sms->io.ports, cycle); | 244 uint8_t port_a = io_data_read(sms->io.ports, cycle); |
151 uint8_t port_b = io_data_read(sms->io.ports+1, cycle); | 245 uint8_t port_b = io_data_read(sms->io.ports+1, cycle); |
152 return (port_a & 0x3F) | (port_b << 6); | 246 return (port_a & 0x3F) | (port_b << 6); |
153 } | 247 } |
154 } | 248 } |
155 //TODO: keyboard matrix ghosting | 249 //TODO: keyboard matrix ghosting |
156 if (port) { | 250 if (port) { |
157 //TODO: cassette-in | |
158 //TODO: printer port BUSY/FAULT | 251 //TODO: printer port BUSY/FAULT |
159 return (sms->keystate[sms->kb_mux] >> 8) | 0x10; | 252 return (sms->keystate[sms->kb_mux] >> 8) | 0x10 | cassette_read(sms, cycle); |
160 } | 253 } |
161 return sms->keystate[sms->kb_mux]; | 254 return sms->keystate[sms->kb_mux]; |
162 } | 255 } |
163 | 256 |
164 static void update_mem_map(uint32_t location, sms_context *sms, uint8_t value) | 257 static void update_mem_map(uint32_t location, sms_context *sms, uint8_t value) |
602 z80_clear_reset(sms->z80, sms->z80->Z80_CYCLE + 128*15); | 695 z80_clear_reset(sms->z80, sms->z80->Z80_CYCLE + 128*15); |
603 } | 696 } |
604 target_cycle = sms->z80->Z80_CYCLE; | 697 target_cycle = sms->z80->Z80_CYCLE; |
605 vdp_run_context(sms->vdp, target_cycle); | 698 vdp_run_context(sms->vdp, target_cycle); |
606 psg_run(sms->psg, target_cycle); | 699 psg_run(sms->psg, target_cycle); |
700 cassette_run(sms, target_cycle); | |
607 | 701 |
608 if (system->save_state) { | 702 if (system->save_state) { |
609 while (!sms->z80->pc) { | 703 while (!sms->z80->pc) { |
610 //advance Z80 to an instruction boundary | 704 //advance Z80 to an instruction boundary |
611 z80_run(sms->z80, sms->z80->Z80_CYCLE + 1); | 705 z80_run(sms->z80, sms->z80->Z80_CYCLE + 1); |
620 io_adjust_cycles(sms->io.ports, sms->z80->Z80_CYCLE, adjust); | 714 io_adjust_cycles(sms->io.ports, sms->z80->Z80_CYCLE, adjust); |
621 io_adjust_cycles(sms->io.ports+1, sms->z80->Z80_CYCLE, adjust); | 715 io_adjust_cycles(sms->io.ports+1, sms->z80->Z80_CYCLE, adjust); |
622 z80_adjust_cycles(sms->z80, adjust); | 716 z80_adjust_cycles(sms->z80, adjust); |
623 vdp_adjust_cycles(sms->vdp, adjust); | 717 vdp_adjust_cycles(sms->vdp, adjust); |
624 sms->psg->cycles -= adjust; | 718 sms->psg->cycles -= adjust; |
719 sms->cassette_cycle -= adjust; | |
625 target_cycle -= adjust; | 720 target_cycle -= adjust; |
626 } | 721 } |
627 } | 722 } |
628 if (sms->header.force_release || render_should_release_on_exit()) { | 723 if (sms->header.force_release || render_should_release_on_exit()) { |
629 bindings_release_capture(); | 724 bindings_release_capture(); |
884 } | 979 } |
885 } | 980 } |
886 #endif | 981 #endif |
887 } | 982 } |
888 | 983 |
984 void load_cassette(sms_context *sms, system_media *media) | |
985 { | |
986 sms->cassette = NULL; | |
987 sms->cassette_state = TAPE_NONE; | |
988 memcpy(&sms->cassette_wave, media->buffer, offsetof(wave_header, data_header)); | |
989 if (memcmp(sms->cassette_wave.chunk.format, "WAVE", 4)) { | |
990 return; | |
991 } | |
992 if (sms->cassette_wave.chunk.size < offsetof(wave_header, data_header)) { | |
993 return; | |
994 } | |
995 if (memcmp(sms->cassette_wave.format_header.id, "fmt ", 4)) { | |
996 return; | |
997 } | |
998 if (sms->cassette_wave.format_header.size < offsetof(wave_header, data_header) - offsetof(wave_header, audio_format)) { | |
999 return; | |
1000 } | |
1001 if (sms->cassette_wave.bits_per_sample != 8 && sms->cassette_wave.bits_per_sample != 16) { | |
1002 return; | |
1003 } | |
1004 uint32_t data_sub_chunk = sms->cassette_wave.format_header.size + offsetof(wave_header, audio_format); | |
1005 if (data_sub_chunk > media->size || media->size - data_sub_chunk < sizeof(riff_sub_chunk)) { | |
1006 return; | |
1007 } | |
1008 memcpy(&sms->cassette_wave.data_header, ((uint8_t *)media->buffer) + data_sub_chunk, sizeof(riff_sub_chunk)); | |
1009 sms->cassette_state = TAPE_STOPPED; | |
1010 sms->cassette_offset = data_sub_chunk; | |
1011 sms->cassette = media; | |
1012 } | |
1013 | |
889 sms_context *alloc_configure_sms(system_media *media, uint32_t opts, uint8_t force_region) | 1014 sms_context *alloc_configure_sms(system_media *media, uint32_t opts, uint8_t force_region) |
890 { | 1015 { |
891 sms_context *sms = calloc(1, sizeof(sms_context)); | 1016 sms_context *sms = calloc(1, sizeof(sms_context)); |
892 tern_node *rom_db = get_rom_db(); | 1017 tern_node *rom_db = get_rom_db(); |
893 const memmap_chunk base_map[] = { | 1018 const memmap_chunk base_map[] = { |
945 if (is_gamegear) { | 1070 if (is_gamegear) { |
946 init_z80_opts(zopts, sms->header.info.map, sms->header.info.map_chunks, io_gg, 6, 15, 0xFF); | 1071 init_z80_opts(zopts, sms->header.info.map, sms->header.info.map_chunks, io_gg, 6, 15, 0xFF); |
947 sms->start_button_region = 0xC0; | 1072 sms->start_button_region = 0xC0; |
948 } else if (is_sc3000) { | 1073 } else if (is_sc3000) { |
949 sms->keystate = calloc(sizeof(uint16_t), 7); | 1074 sms->keystate = calloc(sizeof(uint16_t), 7); |
950 memset(sms->keystate, 0xFF, sizeof(uint16_t) * 7); | 1075 for (int i = 0; i < 7; i++) |
1076 { | |
1077 sms->keystate[i] = 0xFFF; | |
1078 } | |
951 sms->i8255 = calloc(1, sizeof(i8255)); | 1079 sms->i8255 = calloc(1, sizeof(i8255)); |
952 i8255_init(sms->i8255, i8255_output_updated, i8255_input_poll); | 1080 i8255_init(sms->i8255, i8255_output_updated, i8255_input_poll); |
953 sms->i8255->system = sms; | 1081 sms->i8255->system = sms; |
954 sms->kb_mux = 7; | 1082 sms->kb_mux = 7; |
955 init_z80_opts(zopts, sms->header.info.map, sms->header.info.map_chunks, io_sc, 7, 15, 0xFF); | 1083 init_z80_opts(zopts, sms->header.info.map, sms->header.info.map_chunks, io_sc, 7, 15, 0xFF); |
956 } else { | 1084 } else { |
957 init_z80_opts(zopts, sms->header.info.map, sms->header.info.map_chunks, io_map, 4, 15, 0xFF); | 1085 init_z80_opts(zopts, sms->header.info.map, sms->header.info.map_chunks, io_map, 4, 15, 0xFF); |
1086 } | |
1087 if (is_sc3000 && media->chain) { | |
1088 load_cassette(sms, media->chain); | |
958 } | 1089 } |
959 sms->z80 = init_z80_context(zopts); | 1090 sms->z80 = init_z80_context(zopts); |
960 sms->z80->system = sms; | 1091 sms->z80->system = sms; |
961 sms->z80->Z80_OPTS->gen.debug_cmd_handler = debug_commands; | 1092 sms->z80->Z80_OPTS->gen.debug_cmd_handler = debug_commands; |
962 | 1093 |
1017 sms->header.keyboard_up = keyboard_up; | 1148 sms->header.keyboard_up = keyboard_up; |
1018 sms->header.config_updated = config_updated; | 1149 sms->header.config_updated = config_updated; |
1019 sms->header.serialize = serialize; | 1150 sms->header.serialize = serialize; |
1020 sms->header.deserialize = deserialize; | 1151 sms->header.deserialize = deserialize; |
1021 sms->header.toggle_debug_view = toggle_debug_view; | 1152 sms->header.toggle_debug_view = toggle_debug_view; |
1153 sms->header.cassette_action = cassette_action; | |
1022 sms->header.type = SYSTEM_SMS; | 1154 sms->header.type = SYSTEM_SMS; |
1023 | 1155 |
1024 return sms; | 1156 return sms; |
1025 } | 1157 } |