diff --git a/pico/pico.c b/pico/pico.c index 357ba8e04..1d93d60db 100644 --- a/pico/pico.c +++ b/pico/pico.c @@ -54,6 +54,7 @@ void PicoExit(void) PicoExitMCD(); z80_exit(); PsndExit(); + PicoCloseTape(); free(Pico.sv.data); Pico.sv.data = NULL; diff --git a/pico/pico_int.h b/pico/pico_int.h index 21ea30405..8d9eef9f8 100644 --- a/pico/pico_int.h +++ b/pico/pico_int.h @@ -1018,6 +1018,9 @@ void PicoMemSetupMS(void); void PicoStateLoadedMS(void); void PicoFrameMS(void); void PicoFrameDrawOnlyMS(void); +int PicoPlayTape(const char *fname); +int PicoRecordTape(const char *fname); +void PicoCloseTape(void); #else #define PicoPowerMS() #define PicoResetMS() @@ -1025,6 +1028,9 @@ void PicoFrameDrawOnlyMS(void); #define PicoStateLoadedMS() #define PicoFrameMS() #define PicoFrameDrawOnlyMS() +#define PicoPlayTape(f) 1 +#define PicoRecordTape(f) 1 +#define PicoCloseTape() #endif // 32x/32x.c diff --git a/pico/sms.c b/pico/sms.c index 3ecb35a27..0bbf1f696 100644 --- a/pico/sms.c +++ b/pico/sms.c @@ -17,6 +17,9 @@ #include "sound/sn76496.h" #include "sound/emu2413/emu2413.h" +#include // for keyboard handling + + extern void YM2413_regWrite(unsigned reg); extern void YM2413_dataWrite(unsigned data); @@ -132,8 +135,6 @@ static u8 vdp_hcounter(int cycles) return hc; } -#include - // keyboard matrix: // port A @0xdc: // row 0: 1 2 3 4 5 6 7 @@ -229,16 +230,151 @@ static void kbd_update(void) u32 sft = (PicoIn.kbd & 0xff00) >> 8; memset(kbd_matrix, 0, sizeof(kbd_matrix)); - if (/*PicoPicohw.kb.active &&*/ sft) { + if (sft) { int rc = kbd_map[sft]; kbd_matrix[rc>>4] = (1 << (rc&0x7)); } - if (/*PicoPicohw.kb.active &&*/ key) { + if (key) { int rc = kbd_map[key]; kbd_matrix[rc>>4] = (1 << (rc&0x7)); } } +// tape handling + +static struct tape { + FILE *ftape; + int fsize; // size of sample in bytes + int mode; // "w", "r" + + int cycle; // latest polling cycle + int pause; // pause tape playing + int poll_cycles; // for auto play/pause detection + int poll_count; + + int phase; // start cycle of current sample + int cycles_sample; // cycles per sample + u32 cycles_mult; // Q32, inverse of cycles per sample + + int isbit; // is bitstream format? + u8 bitsample; // bitstream sample + s16 wavsample; // wave file sample +} tape; + +static u8 tape_update(int cycle) +{ + struct tape *pt = &tape; + u8 ret = 0; + int phase = cycle - pt->phase; + int count = ((u64)phase * pt->cycles_mult) >> 32; + int cycles = cycle - pt->cycle; + + if (cycles < 0 || pt->ftape == NULL || pt->mode != 'r') return 0; + pt->cycle = cycle; + + // auto play/pause detection: + pt->poll_cycles += cycles; + if (pt->pause) { + // if in pause and poll cycles are short for 1/4s, play + if (cycles < OSC_NTSC/15/1000) { + if (pt->poll_cycles > OSC_NTSC/15/4) { + pt->pause = pt->poll_cycles = pt->poll_count = 0; + } + } else { + // long poll cycles reset the logic + pt->poll_cycles = 0; + } + } else { + // if in play and poll cycles are long for 1/4s, pause + if (cycles >= OSC_NTSC/15/1000) { + if (pt->poll_cycles > OSC_NTSC/15/4) { + pt->pause = 1; pt->poll_cycles = 0; + } + pt->poll_count = 0; + } else if (++pt->poll_count > 10) { + // >10 short poll cycles reset the logic. This is to cover for software + // polling the keyboard matrix, which is partly on PB too. + pt->poll_cycles = pt->poll_count = 0; + } + } + + if (pt->pause) { + pt->phase = cycle; + return 0; + } + + // skip samples if necessary + if (count > 1) { + fseek(pt->ftape, (count-1) * pt->fsize, SEEK_CUR); + pt->phase += (count-1) * pt->cycles_sample; + } + + // read a new sample from file if needed + if (count) { + if (pt->isbit) + fread(&pt->bitsample, 1, sizeof(u8), pt->ftape); + else { + // read sample only from 1st channel + fread(&pt->wavsample, 1, sizeof(u16), pt->ftape); + if (pt->fsize > sizeof(u16)) // skip other channels + fseek(pt->ftape, pt->fsize - sizeof(u16), SEEK_CUR); +#if ! CPU_IS_LE + pt->wavsample = (u8)(pt->wavsample >> 8) | (pt->wavsample << 8); +#endif + } + // catch EOF and reading errors + if (feof(pt->ftape) || ferror(pt->ftape)) { + fclose(pt->ftape); + pt->ftape = NULL; + } + pt->phase += pt->cycles_sample; + } + + // compute result from sample + if (pt->isbit) { + phase = cycle - pt->phase; // recompute as phase might have changed above. + if (pt->bitsample == '0') ret = phase >= pt->cycles_sample*1/2; + if (pt->bitsample == '1') ret = phase >= pt->cycles_sample*3/4 || + (phase >= pt->cycles_sample*1/4 && phase < pt->cycles_sample*2/4); + } else + ret = pt->wavsample >= 0x0800; // 1/16th of the max volume + + return ret; +} + +static void tape_write(int cycle, int data) +{ + struct tape *pt = &tape; + int cycles = cycle - pt->cycle; // cycles since last write + int samples; + + if (cycles < 0 || pt->ftape == NULL || pt->mode != 'w') return; + pt->cycle = cycle; + pt->poll_cycles += cycles; + + // write samples to file. Stop if the sample doesn't change for more than 2s + if (pt->wavsample && pt->poll_cycles <= OSC_NTSC/15*2) { + samples = ((u64)cycles * pt->cycles_mult) >> 32; + while (samples-- > 0 && !ferror(pt->ftape)) + fwrite(&pt->wavsample, 1, sizeof(s16), pt->ftape); + if (ferror(pt->ftape)) { + fclose(pt->ftape); + pt->ftape = NULL; + } + } + + // current sample value in little endian, for writing next time + if (data != -1) { + pt->wavsample = (data ? 0x7ff8 : 0x8008); +#if ! CPU_IS_LE + pt->wavsample = (u8)(pt->wavsample >> 8) | (pt->wavsample << 8); +#endif + pt->poll_cycles = 0; + } +} + +// NB: SC-3000 has a 8255 chip, mapped to 0xdc-0xdf. Not fully emulated. + static unsigned char z80_sms_in(unsigned short a) { unsigned char d = 0xff; @@ -296,6 +432,7 @@ static unsigned char z80_sms_in(unsigned short a) break; case 0xc0: /* I/O port A and B */ + // For SC-3000: 8255 port A, assume always configured for input if (! (PicoIn.AHW & PAHW_SC) || (Pico.ms.io_sg & 7) == 7) { d = ~((PicoIn.pad[0] & 0x3f) | (PicoIn.pad[1] << 6)); if (!(Pico.ms.io_ctl & 0x01)) // TR as output @@ -309,6 +446,7 @@ static unsigned char z80_sms_in(unsigned short a) break; case 0xc1: /* I/O port B and miscellaneous */ + // For SC-3000: 8255 port B, assume always configured for input if (! (PicoIn.AHW & PAHW_SC) || (Pico.ms.io_sg & 7) == 7) { d = (Pico.ms.io_ctl & 0x80) | ((Pico.ms.io_ctl << 1) & 0x40) | 0x30; d |= ~(PicoIn.pad[1] >> 2) & 0x0f; @@ -321,6 +459,14 @@ static unsigned char z80_sms_in(unsigned short a) kbd_update(); for (i = 11; i >= 8; i--) d = (d<<1) | !(kbd_matrix[i] & (1<<(Pico.ms.io_sg&7))); + // bit 5 = printer fault + // bit 6 = printer busy + d &= ~0x60; // set so that BASIC thinks printer is connected + } + if (PicoIn.AHW & PAHW_SC) { + // bit 7 = tape input + d &= ~0x80; + d |= tape_update(z80_cyclesDone()) ? 0x80 : 0; } break; } @@ -389,9 +535,40 @@ static void z80_sms_out(unsigned short a, unsigned char d) break; case 0xc0: - // SG-1000 has a 8255 chip, mapped to 0xdc-0xdf - if ((PicoIn.AHW & PAHW_SC) && (a & 0x2)) + if ((PicoIn.AHW & PAHW_SC) && (a & 0x2)) { + // For SC-3000: 8255 port C, assume always configured for output Pico.ms.io_sg = d; // 0xc2 = kbd/pad matrix column select + // bit 4 = tape output + // bit 5 = printer data + // bit 6 = printer reset + // bit 7 = printer feed + } + break; + case 0xc1: + if ((PicoIn.AHW & PAHW_SC) && (a & 0x2) && !(d & 0x80)) { + // For SC-3000: 8255 control port. BSR mode used for printer and tape. + // debug hack to copy printer data to stdout. + // Printer data is sent at about 4 KBaud, 10 bits per character: + // start=0, 8 data bits (LSB first), stop=1. data line is inverted. + // no Baud tracking needed as all bits are sent through here. + static int chr, bit; + if (d>>1 == 4) { // tape out + tape_write(z80_cyclesDone(), d&1); + } else if (d>>1 == 5) { // !data + if (!bit) { + if (d&1) // start bit + bit = 8; + } else { + chr = (chr>>1) | (d&1 ? 0 : 0x80); + if (!--bit) { + printf("%c",chr); + if (chr == 0xd) printf("\n"); + } + } + } else if (d>>1 == 6 && !(d&1)) // !reset + bit = 0; + } + break; } } } @@ -646,6 +823,7 @@ static void write_bank_x32k(unsigned short a, unsigned char d) a &= 0xc000; Pico.ms.carthw[0] = a >> 12; // NB this deactivates internal RAM and all mapper detection + memcpy(PicoMem.vram+0x4000+(0x8000-0x2000)/2, PicoMem.zram, 0x2000); z80_map_set(z80_read_map, a, a+0x7fff, PicoMem.vram+0x4000, 0); z80_map_set(z80_write_map, a, a+0x7fff, PicoMem.vram+0x4000, 0); } @@ -824,6 +1002,7 @@ void PicoResetMS(void) z80_reset(); PsndReset(); // pal must be known here + PicoCloseTape(); Pico.ms.io_ctl = (PicoIn.AHW & (PAHW_SG|PAHW_SC)) ? 0xf5 : 0xff; Pico.ms.fm_ctl = 0xff; @@ -891,9 +1070,13 @@ void PicoMemSetupMS(void) z80_map_set(z80_read_map, 0x0000, 0xbfff, Pico.rom, 0); z80_map_set(z80_write_map, 0x0000, 0xbfff, xwrite, 1); // mapper detection + // SC-3000 has 2KB, but no harm in mapping the 32KB for BASIC here + if ((PicoIn.AHW & PAHW_SC) && mapper == PMS_MAP_AUTO) + mapper = PMS_MAP_32KBRAM; // Nemesis mapper maps last 8KB rom bank #15 to adress 0 if (mapper == PMS_MAP_NEMESIS && Pico.romsize > 0x1e000) z80_map_set(z80_read_map, 0x0000, 0x1fff, Pico.rom + 0x1e000, 0); + #ifdef _USE_DRZ80 drZ80.z80_in = z80_sms_in; drZ80.z80_out = z80_sms_out; @@ -932,6 +1115,8 @@ void PicoMemSetupMS(void) xwrite(0xfffd, 0); xwrite(0xfffe, 1); xwrite(0xffff, 2); + } else if (mapper == PMS_MAP_32KBRAM) { + xwrite(0x8000, 0); } else if (mapper == PMS_MAP_AUTO) { // pre-initialize Sega mapper to linear mapping (else state load may fail) Pico.ms.carthw[0xe] = 0x1; @@ -997,7 +1182,7 @@ void PicoFrameMS(void) // for SMS the pause button generates an NMI, for GG ths is not the case nmi = (PicoIn.pad[0] >> 7) & 1; - if (!(PicoIn.AHW & PAHW_GG) && !Pico.ms.nmi_state && nmi) + if ((PicoIn.AHW & PAHW_8BIT) == PAHW_SMS && !Pico.ms.nmi_state && nmi) z80_nmi(); Pico.ms.nmi_state = nmi; @@ -1069,6 +1254,12 @@ void PicoFrameMS(void) z80_exec(Pico.t.z80c_line_start + cycles_line); } + // end of frame updates + tape_update(Pico.t.z80c_aim); + tape_write(Pico.t.z80c_aim, -1); + tape.cycle -= Pico.t.z80c_aim; + tape.phase -= Pico.t.z80c_aim; + z80_resetCycles(); PsndGetSamplesMS(lines); } @@ -1089,4 +1280,100 @@ void PicoFrameDrawOnlyMS(void) } } +// open tape file for reading (WAV and bitstream files) +int PicoPlayTape(const char *fname) +{ + struct tape *pt = &tape; + const char *ext = strrchr(fname, '.'); + int rate; + + if (pt->ftape) PicoCloseTape(); + pt->ftape = fopen(fname, "rb"); + if (pt->ftape == NULL) return 1; + pt->mode = 'r'; + + pt->isbit = ext && ! memcmp(ext, ".bit", 4); + if (! pt->isbit) { + u8 hdr[44]; + int chans; + fread(hdr, 1, sizeof(hdr), pt->ftape); + // TODO add checks for WAV header... + chans = hdr[22] | (hdr[23]<<8); + rate = hdr[24] | (hdr[25]<<8) | (hdr[26]<<16) | (hdr[27]<<24); + pt->wavsample = 0; + pt->fsize = chans*sizeof(s16); + } else { + rate = 1200; + pt->bitsample = ' '; + pt->fsize = 1; + } + + pt->cycles_sample = (Pico.m.pal ? OSC_PAL/15 : OSC_NTSC/15) / rate; + pt->cycles_mult = (1LL<<32) / pt->cycles_sample; + pt->cycle = Pico.t.z80c_aim; + pt->phase = Pico.t.z80c_aim; + pt->pause = 0; + return 0; +} + +// open tape file for writing, and write WAV hdr (44KHz, mono, 16 bit samples) +int PicoRecordTape(const char *fname) +{ + struct tape *pt = &tape; + int i; + + // WAV header "riffraff" for PCM 44KHz mono, 16 bit samples. + u8 hdr[44] = { // file and data size updated on file close + 'R','I','F','F', 0,0,0,0, 'W','A','V','E', // "RIFF", file size, "WAVE" + // "fmt ", hdr size, type, chans, rate, bytes/sec, bytes/sample, bits/sample + 'f','m','t',' ', 16,0,0,0, 1,0, 1,0, 68,172,0,0, 136,88,1,0, 2,0, 16,0, + 'd','a','t','a', 0,0,0,0 }; // "data", data size + + if (pt->ftape) PicoCloseTape(); + pt->ftape = fopen(fname, "wb"); + if (pt->ftape == NULL) return 1; + pt->mode = 'w'; + + pt->isbit = 0; + pt->wavsample = 0; // Marker for "don't write yet" + pt->fsize = sizeof(s16); + + fwrite(hdr, 1, sizeof(hdr), pt->ftape); + for (i = 0; i < 44100; i++) + fwrite(&pt->wavsample, 1, sizeof(s16), pt->ftape); + + pt->cycles_sample = (Pico.m.pal ? OSC_PAL/15 : OSC_NTSC/15) / 44100; + pt->cycles_mult = (1LL<<32) / pt->cycles_sample; + pt->cycle = Pico.t.z80c_aim; + pt->phase = Pico.t.z80c_aim; + pt->pause = 0; + return 0; +} + +void PicoCloseTape(void) +{ + struct tape *pt = &tape; + + // if recording, write last data, and update length in header + if (pt->mode == 'w') { + int i, le; + for (i = 0; i < 44100; i++) + fwrite(&pt->wavsample, 1, sizeof(s16), pt->ftape); + le = i = ftell(pt->ftape); +#if ! CPU_IS_LE + le = (u8)(le>>24) | ((u8)le<<24) | ((u8)(le>>16)<<8) | ((u8)(le>>8)<<16); +#endif + fseek(pt->ftape, 4, SEEK_SET); + fwrite(&le, 1, 4, pt->ftape); + le = i-44; +#if ! CPU_IS_LE + le = (u8)(le>>24) | ((u8)le<<24) | ((u8)(le>>16)<<8) | ((u8)(le>>8)<<16); +#endif + fseek(pt->ftape, 40, SEEK_SET); + fwrite(&le, 1, 4, pt->ftape); + } + + if (pt->ftape) fclose(pt->ftape); + pt->ftape = NULL; +} // vim:ts=2:sw=2:expandtab diff --git a/platform/common/emu.c b/platform/common/emu.c index 627fc98c3..8bd90de15 100644 --- a/platform/common/emu.c +++ b/platform/common/emu.c @@ -604,6 +604,18 @@ int emu_swap_cd(const char *fname) return 1; } +int emu_play_tape(const char *fname) +{ + int ret; + + ret = PicoPlayTape(fname); + if (ret != 0) { + menu_update_msg("loading tape failed"); + return 0; + } + return 1; +} + // void emu_make_path(char *buff, const char *end, int size) { @@ -1288,7 +1300,7 @@ static void run_events_ui(unsigned int which) { if (! (PicoIn.opt & POPT_EN_KBD)) { kbd_mode = 0; - emu_status_msg("No keyboard"); + emu_status_msg("No keyboard configured"); } else { kbd_mode = !kbd_mode; emu_status_msg("Keyboard %s", kbd_mode ? "on" : "off"); diff --git a/platform/common/emu.h b/platform/common/emu.h index beb4955bc..ff9f84417 100644 --- a/platform/common/emu.h +++ b/platform/common/emu.h @@ -139,6 +139,7 @@ void emu_loop(void); int emu_reload_rom(const char *rom_fname_in); int emu_swap_cd(const char *fname); +int emu_play_tape(const char *fname); int emu_save_load_game(int load, int sram); void emu_reset_game(void); diff --git a/platform/common/menu_pico.c b/platform/common/menu_pico.c index bf14cca5d..b8092e864 100644 --- a/platform/common/menu_pico.c +++ b/platform/common/menu_pico.c @@ -1410,6 +1410,19 @@ static void menu_main_draw_status(void) bp[(w - i) + g_menuscreen_pp * u] = menu_text_color; } +static int menu_load_sc3000_tape(void) +{ + static const char *rom_exts[] = { "bit", "wav", NULL }; + const char *ret_name; + + ret_name = menu_loop_romsel_d(rom_fname_loaded, + sizeof(rom_fname_loaded), rom_exts, NULL, menu_draw_prep); + if (ret_name == NULL) + return 0; + + return emu_play_tape(ret_name); +} + static menu_entry e_menu_main[]; static int main_menu_handler(int id, int keys) @@ -1456,6 +1469,10 @@ static int main_menu_handler(int id, int keys) return 1; } break; + case MA_MAIN_LOAD_TAPE: + if (PicoIn.AHW & PAHW_SC) + return menu_load_sc3000_tape(); + break; case MA_MAIN_CREDITS: draw_menu_message(credits, draw_frame_credits); in_menu_wait(PBTN_MOK|PBTN_MBACK, NULL, 70); @@ -1552,6 +1569,7 @@ static menu_entry e_menu_main[] = mee_handler_id("Reset game", MA_MAIN_RESET_GAME, main_menu_handler), mee_handler_id("Change CD", MA_MAIN_CHANGE_CD, main_menu_handler), mee_cust_s_h ("Storyware page", MA_MAIN_PICO_PAGE, 0,mh_picopage, mgn_picopage, NULL), + mee_handler_id("Load tape", MA_MAIN_LOAD_TAPE, main_menu_handler), mee_handler_id("Patches / GameGenie",MA_MAIN_PATCHES, main_menu_handler), mee_handler_id("Load new game", MA_MAIN_LOAD_ROM, main_menu_handler), mee_handler ("Change options", menu_loop_options), @@ -1572,6 +1590,7 @@ void menu_loop(void) me_enable(e_menu_main, MA_MAIN_LOAD_STATE, PicoGameLoaded); me_enable(e_menu_main, MA_MAIN_RESET_GAME, PicoGameLoaded); me_enable(e_menu_main, MA_MAIN_CHANGE_CD, PicoIn.AHW & PAHW_MCD); + me_enable(e_menu_main, MA_MAIN_LOAD_TAPE, PicoIn.AHW & PAHW_SC); me_enable(e_menu_main, MA_MAIN_PICO_PAGE, PicoIn.AHW & PAHW_PICO); me_enable(e_menu_main, MA_MAIN_PATCHES, PicoPatches != NULL); me_enable(e_menu_main, MA_OPT_SAVECFG_GAME, PicoGameLoaded); diff --git a/platform/common/menu_pico.h b/platform/common/menu_pico.h index 8539ca9f8..cd6edc682 100644 --- a/platform/common/menu_pico.h +++ b/platform/common/menu_pico.h @@ -12,6 +12,7 @@ typedef enum MA_MAIN_RESET_GAME, MA_MAIN_LOAD_ROM, MA_MAIN_CHANGE_CD, + MA_MAIN_LOAD_TAPE, MA_MAIN_PICO_PAGE, MA_MAIN_CONTROLS, MA_MAIN_CREDITS,