From 74a5481e2b2d8b0ad11c9744935e57bec79c9c91 Mon Sep 17 00:00:00 2001 From: dinkc64 Date: Sat, 16 Apr 2022 20:39:26 -0400 Subject: [PATCH] random new devices & soundcores for future use --- makefile.burn_rules | 6 +- src/burn/devices/rtc9701.cpp | 458 ++++++++++++++++++ src/burn/devices/rtc9701.h | 22 + src/burn/devices/serflash.cpp | 489 +++++++++++++++++++ src/burn/devices/serflash.h | 26 + src/burn/snd/mpeg_audio.cpp | 796 ++++++++++++++++++++++++++++++ src/burn/snd/mpeg_audio.h | 134 +++++ src/burn/snd/ymz770.cpp | 886 ++++++++++++++++++++++++++++++++++ src/burn/snd/ymz770.h | 11 + 9 files changed, 2825 insertions(+), 3 deletions(-) create mode 100644 src/burn/devices/rtc9701.cpp create mode 100644 src/burn/devices/rtc9701.h create mode 100644 src/burn/devices/serflash.cpp create mode 100644 src/burn/devices/serflash.h create mode 100644 src/burn/snd/mpeg_audio.cpp create mode 100644 src/burn/snd/mpeg_audio.h create mode 100644 src/burn/snd/ymz770.cpp create mode 100644 src/burn/snd/ymz770.h diff --git a/makefile.burn_rules b/makefile.burn_rules index fb412ba3f..9a0b27566 100644 --- a/makefile.burn_rules +++ b/makefile.burn_rules @@ -103,14 +103,14 @@ depobj = burn.o burn_bitmap.o burn_gun.o burn_led.o burn_shift.o burn_memory.o \ 6821pia.o 8255ppi.o 8257dma.o c169.o atariic.o atarijsa.o atarimo.o atarirle.o atarivad.o avgdvg.o bsmt2000.o decobsmt.o ds2404.o earom.o eeprom.o gaelco_crypt.o i4x00.o intelfsh.o \ joyprocess.o nb1414m4.o nb1414m4_8bit.o nmk004.o nmk112.o k1ge.o kaneko_tmap.o mathbox.o mb87078.o mermaid.o midcsd.o midsat.o midsg.o midssio.o midtcs.o \ - namco_c45.o namcoio.o pandora.o poly.o qs1000.o resnet.o seibucop.o seibusnd.o sknsspr.o slapstic.o st0020.o t5182.o timekpr.o tlc34076.o tms34061.o v3021.o vdc.o \ + namco_c45.o namcoio.o pandora.o poly.o qs1000.o resnet.o rtc9701.o seibucop.o seibusnd.o serflash.o sknsspr.o slapstic.o st0020.o t5182.o timekpr.o tlc34076.o tms34061.o v3021.o vdc.o \ tms9928a.o watchdog.o x2212.o \ \ asteroids.o ay8910.o burn_y8950.o burn_ym2151.o burn_ym2203.o burn_ym2413.o burn_ym2608.o burn_ym2610.o burn_ym2612.o burn_md2612.o \ burn_ym3526.o burn_ym3812.o burn_ymf262.o burn_ymf271.o burn_ymf278b.o bzone.o c6280.o dac.o digitalk.o es5506.o es8712.o flower.o flt_rc.o fm.o fmopl.o ym2612.o gaelco.o hc55516.o \ - i5000.o ics2115.o iremga20.o k005289.o k007232.o k051649.o k053260.o k054539.o llander.o msm5205.o msm5232.o msm6295.o multipcm.o namco_snd.o c140.o c352.o nes_apu.o \ + i5000.o ics2115.o iremga20.o k005289.o k007232.o k051649.o k053260.o k054539.o llander.o mpeg_audio.o msm5205.o msm5232.o msm6295.o multipcm.o namco_snd.o c140.o c352.o nes_apu.o \ t6w28.o tms5110.o tms5220.o tms36xx.o phoenixsound.o pleiadssound.o pokey.o redbaron.o rf5c68.o s14001a.o saa1099.o samples.o segapcm.o sn76477.o sn76496.o \ - upd7759.o vlm5030.o wiping.o x1010.o ym2151.o ym2413.o ymdeltat.o ymf262.o ymf271.o ymf278b.o ymz280b.o snk6502_sound.o sp0250.o sp0256.o \ + upd7759.o vlm5030.o wiping.o x1010.o ym2151.o ym2413.o ymdeltat.o ymf262.o ymf271.o ymf278b.o ymz280b.o ymz770.o snk6502_sound.o sp0250.o sp0256.o \ \ adsp2100.o adsp2100_intf.o arm7_intf.o arm_intf.o f8.o h6280_intf.o hd6309_intf.o konami_intf.o m6502_intf.o m6800_intf.o m6805_intf.o m6809_intf.o \ m68000_intf.o mips3_intf.o nec_intf.o pic16c5x_intf.o s2650_intf.o tlcs90_intf.o tms34010.o tms34_intf.o z80_intf.o \ diff --git a/src/burn/devices/rtc9701.cpp b/src/burn/devices/rtc9701.cpp new file mode 100644 index 000000000..de1b23596 --- /dev/null +++ b/src/burn/devices/rtc9701.cpp @@ -0,0 +1,458 @@ +// license:BSD-3-Clause +// copyright-holders:Angelo Salese, David Haywood +/*************************************************************************** + + rtc9701.c + + Epson RTC-9701-JE + + Serial Real Time Clock + EEPROM + + +***************************************************************************/ + +#include "burnint.h" +#include "rtc9701.h" + +enum { + CMD_WAIT = 0, + RTC_READ, + RTC_WRITE, + EEPROM_READ, + EEPROM_WRITE, + AFTER_WRITE_ENABLE +}; + +struct regs_t +{ + UINT8 sec, min, hour, day, wday, month, year; +}; + +static int m_latch; +static int m_reset_line; +static int m_clock_line; + +static UINT8 rtc_state; +static int cmd_stream_pos; +static int current_cmd; + +static int rtc9701_address_pos; +static int rtc9701_current_address; + +static UINT16 rtc9701_current_data; +static int rtc9701_data_pos; + +static UINT16 rtc9701_data[0x100]; + +static regs_t m_rtc; + +static UINT32 framenum; + +void rtc9701_scan(INT32 nAction, INT32 *pnMin) +{ + if (nAction & ACB_DRIVER_DATA) { + SCAN_VAR(m_latch); + SCAN_VAR(m_reset_line); + SCAN_VAR(m_clock_line); + SCAN_VAR(rtc_state); + SCAN_VAR(cmd_stream_pos); + SCAN_VAR(current_cmd); + SCAN_VAR(rtc9701_address_pos); + SCAN_VAR(rtc9701_current_address); + SCAN_VAR(rtc9701_current_data); + SCAN_VAR(rtc9701_data_pos); + SCAN_VAR(m_rtc); + SCAN_VAR(framenum); + } + + if (nAction & ACB_NVRAM) { + SCAN_VAR(rtc9701_data); + } +} + + +void rtc9701_once_per_frame() +{ + framenum++; + if ((framenum % 60) == 59) + { + static const UINT8 dpm[12] = { 0x31, 0x28, 0x31, 0x30, 0x31, 0x30, 0x31, 0x31, 0x30, 0x31, 0x30, 0x31 }; + int dpm_count; + + m_rtc.sec++; + + if((m_rtc.sec & 0x0f) >= 0x0a) { m_rtc.sec+=0x10; m_rtc.sec&=0xf0; } + if((m_rtc.sec & 0xf0) >= 0x60) { m_rtc.min++; m_rtc.sec = 0; } + if((m_rtc.min & 0x0f) >= 0x0a) { m_rtc.min+=0x10; m_rtc.min&=0xf0; } + if((m_rtc.min & 0xf0) >= 0x60) { m_rtc.hour++; m_rtc.min = 0; } + if((m_rtc.hour & 0x0f) >= 0x0a) { m_rtc.hour+=0x10; m_rtc.hour&=0xf0; } + if((m_rtc.hour & 0xff) >= 0x24) { m_rtc.day++; m_rtc.wday<<=1; m_rtc.hour = 0; } + if(m_rtc.wday & 0x80) { m_rtc.wday = 1; } + if((m_rtc.day & 0x0f) >= 0x0a) { m_rtc.day+=0x10; m_rtc.day&=0xf0; } + + /* TODO: crude leap year support */ + dpm_count = (m_rtc.month & 0xf) + (((m_rtc.month & 0x10) >> 4)*10)-1; + + if(((m_rtc.year % 4) == 0) && m_rtc.month == 2) + { + if((m_rtc.day & 0xff) >= dpm[dpm_count]+1+1) + { m_rtc.month++; m_rtc.day = 0x01; } + } + else if((m_rtc.day & 0xff) >= dpm[dpm_count]+1){ m_rtc.month++; m_rtc.day = 0x01; } + if((m_rtc.month & 0x0f) >= 0x0a) { m_rtc.month = 0x10; } + if(m_rtc.month >= 0x13) { m_rtc.year++; m_rtc.month = 1; } + if((m_rtc.year & 0x0f) >= 0x0a) { m_rtc.year+=0x10; m_rtc.year&=0xf0; } + if((m_rtc.year & 0xf0) >= 0xa0) { m_rtc.year = 0; } //2000-2099 possible timeframe + } +} + +//------------------------------------------------- +// device_start - device-specific startup +//------------------------------------------------- + +void rtc9701_init() +{ + tm time; + BurnGetLocalTime(&time); + + m_rtc.day = ((time.tm_mday / 10)<<4) | ((time.tm_mday % 10) & 0xf); + m_rtc.month = (((time.tm_mon+1) / 10) << 4) | (((time.tm_mon+1) % 10) & 0xf); + m_rtc.wday = 1 << time.tm_wday; + m_rtc.year = (((time.tm_year % 100)/10)<<4) | ((time.tm_year % 10) & 0xf); + m_rtc.hour = ((time.tm_hour / 10)<<4) | ((time.tm_hour % 10) & 0xf); + m_rtc.min = ((time.tm_min / 10)<<4) | ((time.tm_min % 10) & 0xf); + m_rtc.sec = ((time.tm_sec / 10)<<4) | ((time.tm_sec % 10) & 0xf); + + rtc_state = CMD_WAIT; + cmd_stream_pos = 0; + current_cmd = 0; + + framenum = 0; + + for (INT32 offs = 0; offs < 0x100; offs++) + rtc9701_data[offs] = 0xffff; +} + +void rtc9701_exit() +{ +} + +//------------------------------------------------- +// device_reset - device-specific reset +//------------------------------------------------- + +void rtc9701_reset() +{ +} + + + +//------------------------------------------------- +// rtc_read - used to route RTC reading registers +//------------------------------------------------- + +static UINT8 rtc_read(UINT8 offset) +{ + UINT8 res; + + res = 0; + + switch(offset) + { + case 0: res = m_rtc.sec; break; + case 1: res = m_rtc.min; break; + case 2: res = m_rtc.hour; break; + case 3: res = m_rtc.wday; break; /* untested */ + case 4: res = m_rtc.day; break; + case 5: res = m_rtc.month; break; + case 6: res = m_rtc.year & 0xff; break; + case 7: res = 0x20; break; + } + + return res; +} + +static void rtc_write(UINT8 offset, UINT8 data) +{ + switch(offset) + { + case 0: m_rtc.sec = data; break; + case 1: m_rtc.min = data; break; + case 2: m_rtc.hour = data; break; + case 3: m_rtc.wday = data; break; /* untested */ + case 4: m_rtc.day = data; break; + case 5: m_rtc.month = data; break; + case 6: m_rtc.year = data; break; + case 7: break; // NOP + } +} + +//************************************************************************** +// READ/WRITE HANDLERS +//************************************************************************** + +void rtc9701_write_bit(UINT8 data) +{ + m_latch = data & 1; +} + + +UINT8 rtc9701_read_bit() +{ + if (rtc_state == RTC_READ) + { + //printf("RTC data bits left c9701_data_pos %02x\n", rtc9701_data_pos); + return ((rtc9701_current_data) >> (rtc9701_data_pos-1))&1; + + } + else if (rtc_state == EEPROM_READ) + { + //printf("EEPROM data bits left c9701_data_pos %02x\n", rtc9701_data_pos); + return ((rtc9701_current_data) >> (rtc9701_data_pos-1))&1; + + } + else + { + //printf("read something else (status?) %02x\n", rtc9701_data_pos); + } + + + + return 0; +} + + +void rtc9701_set_cs_line(UINT8 data) +{ + //logerror("set reset line %d\n",data); + m_reset_line = data; + + if (m_reset_line != 0) + { + rtc_state = CMD_WAIT; + cmd_stream_pos = 0; + current_cmd = 0; + rtc9701_address_pos = 0; + rtc9701_current_address = 0; + rtc9701_current_data = 0; + rtc9701_data_pos = 0; + + } +} + + +void rtc9701_set_clock_line(UINT8 data) +{ + //logerror("set clock line %d\n",data); + + if (m_reset_line == 0) + { + if (data==1) + { + //logerror("write latched bit %d\n",m_latch); + + switch (rtc_state) + { + case CMD_WAIT: + + //logerror("xx\n"); + current_cmd = (current_cmd << 1) | (m_latch&1); + cmd_stream_pos++; + + if (cmd_stream_pos==4) + { + cmd_stream_pos = 0; + //logerror("Comamnd is %02x\n", current_cmd); + + if (current_cmd==0x00) /* 0000 */ + { + //logerror("WRITE RTC MODE\n"); + rtc_state = RTC_WRITE; + cmd_stream_pos = 0; + rtc9701_address_pos = 0; + rtc9701_current_address = 0; + rtc9701_data_pos = 0; + rtc9701_current_data = 0; + } + else if (current_cmd==0x02) /* 0010 */ + { + //logerror("WRITE EEPROM MODE\n"); + rtc_state = EEPROM_WRITE; + cmd_stream_pos = 0; + rtc9701_address_pos = 0; + rtc9701_current_address = 0; + rtc9701_data_pos = 0; + rtc9701_current_data = 0; + + } + else if (current_cmd==0x06) /* 0110 */ + { + //logerror("WRITE ENABLE\n"); + rtc_state = AFTER_WRITE_ENABLE; + cmd_stream_pos = 0; + } + else if (current_cmd==0x08) /* 1000 */ + { + //logerror("READ RTC MODE\n"); + rtc_state = RTC_READ; + cmd_stream_pos = 0; + rtc9701_address_pos = 0; + rtc9701_current_address = 0; + rtc9701_data_pos = 0; + rtc9701_current_data = 0; + } + else if (current_cmd==0x0a) /* 1010 */ + { + //logerror("READ EEPROM MODE\n"); + rtc_state = EEPROM_READ; + cmd_stream_pos = 0; + rtc9701_address_pos = 0; + rtc9701_current_address = 0; + rtc9701_data_pos = 0; + rtc9701_current_data = 0; + + + } + else + { + //logerror("RTC9701 UNKNOWN MODE\n"); + } + + current_cmd = 0; + } + break; + + case AFTER_WRITE_ENABLE: + cmd_stream_pos++; + if (cmd_stream_pos==12) + { + cmd_stream_pos = 0; + //logerror("Written 12 bits, going back to WAIT mode\n"); + rtc_state = CMD_WAIT; + } + break; + + case RTC_WRITE: + cmd_stream_pos++; + if (cmd_stream_pos<=4) + { + rtc9701_address_pos++; + rtc9701_current_address = (rtc9701_current_address << 1) | (m_latch&1); + if (cmd_stream_pos==4) + { + //printf("Set RTC Write Address To %04x\n", rtc9701_current_address ); + } + } + + if (cmd_stream_pos>4) + { + rtc9701_data_pos++; + rtc9701_current_data = (rtc9701_current_data << 1) | (m_latch&1); + } + + if (cmd_stream_pos==12) + { + cmd_stream_pos = 0; + rtc_write(rtc9701_current_address,rtc9701_current_data); + //logerror("Written 12 bits, going back to WAIT mode\n"); + rtc_state = CMD_WAIT; + } + break; + + + + case EEPROM_READ: + cmd_stream_pos++; + if (cmd_stream_pos<=12) + { + rtc9701_address_pos++; + rtc9701_current_address = (rtc9701_current_address << 1) | (m_latch&1); + if (cmd_stream_pos==12) + { + //printf("Set EEPROM Read Address To %04x - ", (rtc9701_current_address>>1)&0xff ); + rtc9701_current_data = rtc9701_data[(rtc9701_current_address>>1)&0xff]; + //printf("Setting data latch for reading to %04x\n", rtc9701_current_data); + rtc9701_data_pos = 16; + } + } + + if (cmd_stream_pos>12) + { + rtc9701_data_pos--; + + } + + if (cmd_stream_pos==28) + { + cmd_stream_pos = 0; + // //logerror("accesed 28 bits, going back to WAIT mode\n"); + // rtc_state = CMD_WAIT; + } + break; + + + + case EEPROM_WRITE: + cmd_stream_pos++; + + if (cmd_stream_pos<=12) + { + rtc9701_address_pos++; + rtc9701_current_address = (rtc9701_current_address << 1) | (m_latch&1); + if (cmd_stream_pos==12) + { + //printf("Set EEPROM Write Address To %04x\n", rtc9701_current_address ); + } + } + + if (cmd_stream_pos>12) + { + rtc9701_data_pos++; + rtc9701_current_data = (rtc9701_current_data << 1) | (m_latch&1); + } + + if (cmd_stream_pos==28) + { + cmd_stream_pos = 0; + //printf("written 28 bits - writing data %04x to %04x and going back to WAIT mode\n", rtc9701_current_data, (rtc9701_current_address>>1)&0xff); + rtc9701_data[(rtc9701_current_address>>1)&0xff] = rtc9701_current_data; + rtc_state = CMD_WAIT; + } + break; + + case RTC_READ: + cmd_stream_pos++; + if (cmd_stream_pos<=4) + { + rtc9701_address_pos++; + rtc9701_current_address = (rtc9701_current_address << 1) | (m_latch&1); + if (cmd_stream_pos==4) + { + //printf("Set RTC Read Address To %04x\n", rtc9701_current_address ); + rtc9701_current_data = rtc_read(rtc9701_current_address); + //printf("Setting data latch for reading to %04x\n", rtc9701_current_data); + rtc9701_data_pos = 8; + } + } + + if (cmd_stream_pos>4) + { + rtc9701_data_pos--; + } + + if (cmd_stream_pos==12) + { + cmd_stream_pos = 0; + // //logerror("accessed 12 bits, going back to WAIT mode\n"); + // rtc_state = CMD_WAIT; + } + break; + + + default: + break; + + } + } + } +} diff --git a/src/burn/devices/rtc9701.h b/src/burn/devices/rtc9701.h new file mode 100644 index 000000000..f9e1f403a --- /dev/null +++ b/src/burn/devices/rtc9701.h @@ -0,0 +1,22 @@ +// license:BSD-3-Clause +// copyright-holders:Angelo Salese, David Haywood +/*************************************************************************** + + rtc9701.h + + Serial rtc9701s. + +***************************************************************************/ + +void rtc9701_init(); +void rtc9701_exit(); +void rtc9701_reset(); +void rtc9701_scan(INT32 nAction, INT32 *pnMin); + +void rtc9701_once_per_frame(); + +void rtc9701_write_bit(UINT8 data); +UINT8 rtc9701_read_bit(); +void rtc9701_set_cs_line(UINT8 data); +void rtc9701_set_clock_line(UINT8 data); + diff --git a/src/burn/devices/serflash.cpp b/src/burn/devices/serflash.cpp new file mode 100644 index 000000000..4d0d8d80d --- /dev/null +++ b/src/burn/devices/serflash.cpp @@ -0,0 +1,489 @@ +// license:BSD-3-Clause +// copyright-holders:David Haywood, Luca Elia +/* Serial Flash Device */ + +/* todo: cleanup, refactor etc. */ +/* ghosteo.c is similar? */ + +// **FBNeo port note** +// Flash data saved to the device doesn't get saved or loaded, since what +// we're using this for simply uses the flash chips as a ROM source. +// - dink april 16, 2022 + +#include "burnint.h" +#include "serflash.h" + + +// runtime state +enum { IDLE = 0, READ, READ_ID, READ_STATUS, BLOCK_ERASE, PAGE_PROGRAM }; + +static INT32 m_length; +static UINT8* m_region; + +static UINT32 m_row_num; +static UINT16 m_flash_page_size; + +static UINT8 m_flash_state; + +static UINT8 m_flash_enab; + +static UINT8 m_flash_cmd_seq; +static UINT32 m_flash_cmd_prev; + +static UINT8 m_flash_addr_seq; +static UINT8 m_flash_read_seq; + +static UINT32 m_flash_row; +static UINT16 m_flash_col; +static int m_flash_page_addr; +static UINT32 m_flash_page_index; + +static UINT8 *m_flashwritemap; + +static UINT8 m_last_flash_cmd; + +static UINT32 m_flash_addr; + +static UINT8 *m_flash_page_data; + +static void flash_change_state(UINT8 state); // forward + +//------------------------------------------------- +// device_start - device-specific startup +//------------------------------------------------- + +void serflash_scan(INT32 nAction, INT32 *pnMin) +{ + if (nAction & ACB_DRIVER_DATA) { + SCAN_VAR(m_flash_state); + SCAN_VAR(m_flash_enab); + SCAN_VAR(m_flash_cmd_seq); + SCAN_VAR(m_flash_cmd_prev); + SCAN_VAR(m_flash_addr_seq); + SCAN_VAR(m_flash_read_seq); + SCAN_VAR(m_flash_row); + SCAN_VAR(m_flash_col); + SCAN_VAR(m_flash_page_addr); + SCAN_VAR(m_flash_page_index); + + ScanVar(m_flashwritemap, m_row_num, "FlashWriteMap"); + + SCAN_VAR(m_last_flash_cmd); + + SCAN_VAR(m_flash_addr); + + ScanVar(m_flash_page_data, m_flash_page_size, "FlashPageData"); + } +} + +void serflash_init(UINT8 *rom, INT32 length) +{ + m_length = length; + m_region = rom; + + m_flash_page_size = 2048+64; + m_row_num = m_length / m_flash_page_size; + + m_flashwritemap = (UINT8*)BurnMalloc(m_row_num); + memset(m_flashwritemap, 0, m_row_num); + + m_flash_page_data = (UINT8*)BurnMalloc(m_flash_page_size); + memset(m_flash_page_data, 0, m_flash_page_size); +} + +void serflash_exit() +{ + BurnFree(m_flashwritemap); + BurnFree(m_flash_page_data); +} + +static void serflash_hard_reset() +{ +// logerror("%08x FLASH: RESET\n", cpuexec_describe_context(machine)); + + m_flash_state = READ; + + m_flash_cmd_prev = -1; + m_flash_cmd_seq = 0; + + m_flash_addr_seq = 0; + m_flash_read_seq = 0; + + m_flash_row = 0; + m_flash_col = 0; + + memset(m_flash_page_data, 0, m_flash_page_size); + m_flash_page_addr = 0; + m_flash_page_index = 0; +} + +void serflash_reset() +{ + m_flash_enab = 0; + serflash_hard_reset(); + + m_last_flash_cmd = 0x00; + m_flash_addr_seq = 0; + m_flash_addr = 0; + + m_flash_page_addr = 0; +} + + +//------------------------------------------------- +// nvram_read - called to read SERFLASH from the +// .nv file +//------------------------------------------------- + +void serflash_nvram_read() +{ +#if 0 + if (m_length % m_flash_page_size) return; // region size must be multiple of flash page size + int size = m_length / m_flash_page_size; + + + if (file.is_open()) + { + UINT32 page; + file.read(&page, 4); + while (page < size) + { + m_flashwritemap[page] = 1; + file.read(m_region + page * m_flash_page_size, m_flash_page_size); + file.read(&page, 4); + } + } +#endif +} + + +//------------------------------------------------- +// nvram_write - called to write SERFLASH to the +// .nv file +//------------------------------------------------- + +void serflash_nvram_write() +{ +#if 0 + if (m_length % m_flash_page_size) return; // region size must be multiple of flash page size + int size = m_length / m_flash_page_size; + + UINT32 page = 0; + while (page < size) + { + if (m_flashwritemap[page]) + { + file.write(&page, 4); + file.write(m_region + page * m_flash_page_size, m_flash_page_size); + } + page++; + } + file.write(&page, 4); +#endif +} + +void serflash_enab_write(UINT8 data) +{ + //logerror("%08x FLASH: enab = %02X\n", m_maincpu->pc(), data); + m_flash_enab = data; +} + +static void flash_change_state(UINT8 state) +{ + m_flash_state = state; + + m_flash_cmd_prev = -1; + m_flash_cmd_seq = 0; + + m_flash_read_seq = 0; + m_flash_addr_seq = 0; + + //logerror("flash_change_state - FLASH: state = %s\n", m_flash_state_name[state]); +} + +void serflash_cmd_write(UINT8 data) +{ + if (!m_flash_enab) + return; + + //logerror("%08x FLASH: cmd = %02X (prev = %02X)\n", m_maincpu->pc(), data, m_flash_cmd_prev); + + if (m_flash_cmd_prev == -1) + { + m_flash_cmd_prev = data; + + switch (data) + { + case 0x00: // READ + m_flash_addr_seq = 0; + break; + + case 0x60: // BLOCK ERASE + m_flash_addr_seq = 2; // row address only + break; + + case 0x70: // READ STATUS + flash_change_state( READ_STATUS ); + break; + + case 0x80: // PAGE / CACHE PROGRAM + m_flash_addr_seq = 0; + // this actually seems to be set with the next 2 writes? + m_flash_page_addr = 0; + break; + + case 0x90: // READ ID + flash_change_state( READ_ID ); + break; + + case 0xff: // RESET + flash_change_state( IDLE ); + break; + + default: + { + //logerror("%s FLASH: unknown cmd1 = %02X\n", machine().describe_context(), data); + } + } + } + else + { + switch (m_flash_cmd_prev) + { + case 0x00: // READ + if (data == 0x30) + { + if (m_flash_row < m_row_num) + { + memcpy(m_flash_page_data, m_region + m_flash_row * m_flash_page_size, m_flash_page_size); + m_flash_page_addr = m_flash_col; + m_flash_page_index = m_flash_row; + } + flash_change_state( READ ); + + //logerror("%08x FLASH: caching page = %04X\n", m_maincpu->pc(), m_flash_row); + } + break; + + case 0x60: // BLOCK ERASE + if (data==0xd0) + { + flash_change_state( BLOCK_ERASE ); + if (m_flash_row < m_row_num) + { + m_flashwritemap[m_flash_row] |= 1; + memset(m_region + m_flash_col * m_flash_page_size, 0xff, m_flash_page_size); + } + //logerror("erased block %04x (%08x - %08x)\n", m_flash_col, m_flash_col * m_flash_page_size, ((m_flash_col+1) * m_flash_page_size)-1); + } + else + { + //logerror("unexpected 2nd command after BLOCK ERASE\n"); + } + break; + case 0x80: + if (data==0x10) + { + flash_change_state( PAGE_PROGRAM ); + if (m_flash_row < m_row_num) + { + m_flashwritemap[m_flash_row] |= (memcmp(m_region + m_flash_row * m_flash_page_size, &m_flash_page_data[0], m_flash_page_size) != 0); + memcpy(m_region + m_flash_row * m_flash_page_size, m_flash_page_data, m_flash_page_size); + } + //logerror("re-written block %04x (%08x - %08x)\n", m_flash_row, m_flash_row * m_flash_page_size, ((m_flash_row+1) * m_flash_page_size)-1); + } + else + { + //logerror("unexpected 2nd command after SPAGE PROGRAM\n"); + } + break; + + + default: + { + //logerror("%08x FLASH: unknown cmd2 = %02X (cmd1 = %02X)\n", m_maincpu->pc(), data, m_flash_cmd_prev); + } + } + } +} + +void serflash_data_write(UINT8 data) +{ + if (!m_flash_enab) + return; + + //logerror("flash data write %04x\n", m_flash_page_addr); + if (m_flash_page_addr < m_flash_page_size) + { + m_flash_page_data[m_flash_page_addr] = data; + } + m_flash_page_addr++; +} + +void serflash_addr_write(UINT8 data) +{ + if (!m_flash_enab) + return; + + //logerror("%08x FLASH: addr = %02X (seq = %02X)\n", m_maincpu->pc(), data, m_flash_addr_seq); + + switch( m_flash_addr_seq++ ) + { + case 0: + m_flash_col = (m_flash_col & 0xff00) | data; + break; + case 1: + m_flash_col = (m_flash_col & 0x00ff) | (data << 8); + break; + case 2: + m_flash_row = (m_flash_row & 0xffff00) | data; + if (m_row_num <= 256) + { + m_flash_addr_seq = 0; + } + break; + case 3: + m_flash_row = (m_flash_row & 0xff00ff) | (data << 8); + if (m_row_num <= 65536) + { + m_flash_addr_seq = 0; + } + break; + case 4: + m_flash_row = (m_flash_row & 0x00ffff) | (data << 16); + m_flash_addr_seq = 0; + break; + } +} + +UINT8 serflash_io_read() +{ + UINT8 data = 0x00; +// UINT32 old; + + if (!m_flash_enab) + return 0xff; + + switch (m_flash_state) + { + case READ_ID: + //old = m_flash_read_seq; + + switch( m_flash_read_seq++ ) + { + case 0: + data = 0xEC; // Manufacturer + break; + case 1: + data = 0xF1; // Device + break; + case 2: + data = 0x00; // XX + break; + case 3: + data = 0x15; // Flags + m_flash_read_seq = 0; + break; + } + + //logerror("%08x FLASH: read %02X from id(%02X)\n", m_maincpu->pc(), data, old); + break; + + case READ: + if (m_flash_page_addr > m_flash_page_size-1) + m_flash_page_addr = m_flash_page_size-1; + + //old = m_flash_page_addr; + + data = m_flash_page_data[m_flash_page_addr++]; + + //logerror("%08x FLASH: read data %02X from addr %03X (page %04X)\n", m_maincpu->pc(), data, old, m_flash_page_index); + break; + + case READ_STATUS: + // bit 7 = writeable, bit 6 = ready, bit 5 = ready/true ready, bit 1 = fail(N-1), bit 0 = fail + data = 0xe0; + //logerror("%08x FLASH: read status %02X\n", m_maincpu->pc(), data); + break; + + default: + { + // logerror("%08x FLASH: unknown read in state %s\n",0x00/*m_maincpu->pc()*/, m_flash_state_name[m_flash_state]); + } + } + + return data; +} + +UINT8 serflash_ready_read() +{ + return 1; +} + + + +UINT8 serflash_n3d_flash_read(INT32 offset) +{ + if (m_last_flash_cmd==0x70) return 0xe0; + + if (m_last_flash_cmd==0x00) + { + UINT8 retdat = m_flash_page_data[m_flash_page_addr]; + + //logerror("n3d_flash_r %02x %04x\n", offset, m_flash_page_addr); + + m_flash_page_addr++; + return retdat; + } + + return 0x00; +} + + +void serflash_n3d_flash_cmd_write(INT32 offset, UINT8 data) +{ + m_last_flash_cmd = data; + + if (data==0x00) + { + if (m_flash_addr < m_row_num) + { + memcpy(m_flash_page_data, m_region + m_flash_addr * m_flash_page_size, m_flash_page_size); + } + } +} + +void serflash_n3d_flash_addr_write(INT32 offset, UINT8 data) +{ +// logerror("n3d_flash_addr_w %02x %02x\n", offset, data); + + m_flash_addr_seq++; + + if (m_flash_addr_seq==3) + { + m_flash_addr = (m_flash_addr & 0xffff00) | data; + if (m_row_num <= 256) + { + m_flash_addr_seq = 0; + m_flash_page_addr = 0; + //logerror("set flash block to %08x\n", m_flash_addr); + } + } + if (m_flash_addr_seq==4) + { + m_flash_addr = (m_flash_addr & 0xff00ff) | data << 8; + if (m_row_num <= 65536) + { + m_flash_addr_seq = 0; + m_flash_page_addr = 0; + //logerror("set flash block to %08x\n", m_flash_addr); + } + } + if (m_flash_addr_seq==5) + { + m_flash_addr = (m_flash_addr & 0x00ffff) | data << 16; + m_flash_addr_seq = 0; + m_flash_page_addr = 0; + //logerror("set flash block to %08x\n", m_flash_addr); + } +} diff --git a/src/burn/devices/serflash.h b/src/burn/devices/serflash.h new file mode 100644 index 000000000..cf92c663a --- /dev/null +++ b/src/burn/devices/serflash.h @@ -0,0 +1,26 @@ +// license:BSD-3-Clause +// copyright-holders:David Haywood, Luca Elia +/* Serial Flash */ + +// **FBNeo port note** +// Flash data saved to the device doesn't get saved or loaded, since what +// we're using this for simply uses the flash chips as a ROM source. +// - dink april 16, 2022 + +void serflash_init(UINT8 *rom, INT32 length); +void serflash_exit(); +void serflash_reset(); + +void serflash_scan(INT32 nAction, INT32 *pnMin); + +void serflash_enab_write(UINT8 data); +void serflash_cmd_write(UINT8 data); +void serflash_data_write(UINT8 data); +void serflash_addr_write(UINT8 data); +UINT8 serflash_io_read(); +UINT8 serflash_ready_read(); + +UINT8 serflash_n3d_flash_read(INT32 offset); +void serflash_n3d_flash_cmd_write(INT32 offset, UINT8 data); +void serflash_n3d_flash_addr_write(INT32 offset, UINT8 data); + diff --git a/src/burn/snd/mpeg_audio.cpp b/src/burn/snd/mpeg_audio.cpp new file mode 100644 index 000000000..0c0512164 --- /dev/null +++ b/src/burn/snd/mpeg_audio.cpp @@ -0,0 +1,796 @@ +// license:BSD-3-Clause +// copyright-holders:Olivier Galibert +/*************************************************************************** + + MPEG audio support. Only layer2 and variants for now. + +***************************************************************************/ + +#if defined (_MSC_VER) +#define _USE_MATH_DEFINES +#endif + +#include +#include "burnint.h" +#include "mpeg_audio.h" + +mpeg_audio::mpeg_audio(const void *_base, unsigned int _accepted, bool lsb_first, int _position_align) +{ + base = (const UINT8 *)_base; + accepted = _accepted; + do_gb = lsb_first ? do_gb_lsb : do_gb_msb; + position_align = _position_align ? _position_align - 1 : 0; + + for (int i = 0; i < 32; i++) { + for (int j = 0; j < 32; j++) + m_cos_cache[i][j] = cos(i*(2 * j + 1)*M_PI / 64); + } + + clear(); +} + +void mpeg_audio::clear() +{ + memset(audio_buffer, 0, sizeof(audio_buffer)); + audio_buffer_pos[0] = 16*32; + audio_buffer_pos[1] = 16*32; +} + +void mpeg_audio::scan() +{ + SCAN_VAR(accepted); + SCAN_VAR(position_align); + + SCAN_VAR(sampling_rate); + SCAN_VAR(last_frame_number); + SCAN_VAR(param_index); + + SCAN_VAR(channel_count); + SCAN_VAR(total_bands); + SCAN_VAR(joint_bands); + + SCAN_VAR(band_param); + SCAN_VAR(scfsi); + SCAN_VAR(scf); + SCAN_VAR(amp_values); + SCAN_VAR(bdata); + SCAN_VAR(subbuffer); + SCAN_VAR(audio_buffer); + SCAN_VAR(audio_buffer_pos); + SCAN_VAR(m_cos_cache); + + SCAN_VAR(current_pos); + SCAN_VAR(current_limit); +} + +bool mpeg_audio::decode_buffer(int &pos, int limit, short *output, + int &output_samples, int &sample_rate, int &channels) +{ + if(limit - pos < 16) + return false; + + // Scan for the sync mark + // + // Avoid the exception dance at the point where going out of bound + // is the most probable and easily avoidable + + current_pos = pos; + current_limit = limit; + unsigned short sync = do_gb(base, current_pos, 12); + + retry_sync: + while(sync != 0xfff && current_pos < limit) + sync = ((sync << 1) | do_gb(base, current_pos, 1)) & 0xfff; + + if(limit - current_pos < 4) + return false; + + int layer = 0; + int variant = do_gb(base, current_pos, 3); + switch(variant) { + case 2: + if(accepted & L2_5) + layer = 2; + else if(accepted & AMM) + layer = 4; + break; + + case 5: + if(accepted & L3) + layer = 3; + break; + + case 6: + if(accepted & (L2|L2_5)) + layer = 2; + else if(accepted & AMM) + layer = 4; + break; + + case 7: + if(accepted & L1) + layer = 1; + break; + } + + if(!layer) { + current_pos -= 3; + sync = ((sync << 1) | do_gb(base, current_pos, 1)) & 0xfff; + goto retry_sync; + } + + switch(layer) { + case 1: + abort(); + case 2: + try { + read_header_mpeg2(variant == 2); + read_data_mpeg2(); + decode_mpeg2(output, output_samples); + } catch(limit_hit) { + return false; + } + break; + case 3: + abort(); + case 4: + try { + read_header_amm(variant == 2); + read_data_mpeg2(); + if(last_frame_number) + decode_mpeg2(output, output_samples); + } catch(limit_hit) { + return false; + } + break; + } + + if(position_align) + current_pos = (current_pos + position_align) & ~position_align; + + pos = current_pos; + sample_rate = sample_rates[sampling_rate]; + channels = channel_count; + return true; +} + +void mpeg_audio::read_header_amm(bool layer25) +{ + gb(1); // unused + int full_packets_count = gb(4); // max 12 + int srate_index = gb(2); // max 2 + sampling_rate = srate_index + 4 * layer25; + int last_packet_frame_id = gb(2); // max 2 + last_frame_number = 3*full_packets_count + last_packet_frame_id; + int stereo_mode = gb(2); + int stereo_mode_ext = gb(2); + param_index = gb(3); + gb(1); // must be zero + + channel_count = stereo_mode != 3 ? 2 : 1; + + total_bands = total_band_counts[param_index]; + joint_bands = total_bands; + if(stereo_mode == 1) // joint stereo + joint_bands = joint_band_counts[stereo_mode_ext]; + if(joint_bands > total_bands ) + joint_bands = total_bands; +} + +void mpeg_audio::read_header_mpeg2(bool layer25) +{ + int prot = gb(1); + int bitrate_index = gb(4); + sampling_rate = gb(2); + gb(1); // padding + gb(1); + last_frame_number = 36; + int stereo_mode = gb(2); + int stereo_mode_ext = gb(2); + gb(2); // copyright, original + gb(2); // emphasis + if(!prot) + gb(16); // crc + + channel_count = stereo_mode != 3 ? 2 : 1; + + param_index = layer2_param_index[channel_count-1][sampling_rate][bitrate_index]; + assert(param_index != -1); + + total_bands = total_band_counts[param_index]; + joint_bands = total_bands; + if(stereo_mode == 1) // joint stereo + joint_bands = joint_band_counts[stereo_mode_ext]; + if(joint_bands > total_bands ) + joint_bands = total_bands; +} + +void mpeg_audio::read_data_mpeg2() +{ + read_band_params(); + read_scfci(); + read_band_amplitude_params(); +} + +void mpeg_audio::decode_mpeg2(short *output, int &output_samples) +{ + output_samples = 0; + build_amplitudes(); + + // Supposed to stop at last_frame_number when it's not 12*3+2 = 38 + int frame_number = 0; + for(int upper_step = 0; upper_step<3; upper_step++) + for(int middle_step = 0; middle_step < 4; middle_step++) { + build_next_segments(upper_step); + for(int lower_step = 0; lower_step < 3; lower_step++) { + retrieve_subbuffer(lower_step); + + for(int chan=0; chan> 3] & (0x80 >> (pos & 7))) + v |= 1; + pos++; + } + return v; +} + +int mpeg_audio::do_gb_lsb(const unsigned char *data, int &pos, int count) +{ + int v = 0; + for(int i=0; i != count; i++) { + v <<= 1; + if(data[pos >> 3] & (0x01 << (pos & 7))) + v |= 1; + pos++; + } + return v; +} + +int mpeg_audio::get_band_param(int band) +{ + int bit_count = band_parameter_index_bits_count[param_index][band]; + int index = gb(bit_count); + return band_parameter_indexed_values[param_index][band][index]; +} + +void mpeg_audio::read_band_params() +{ + int band = 0; + + while(band < joint_bands) { + for(int chan=0; chan < channel_count; chan++) + band_param[chan][band] = get_band_param(band); + band++; + } + + while(band < total_bands) { + int val = get_band_param(band); + band_param[0][band] = val; + band_param[1][band] = val; + band++; + } + + while(band < 32) { + band_param[0][band] = 0; + band_param[1][band] = 0; + band++; + } +} + +void mpeg_audio::read_scfci() +{ + memset(scfsi, 0, sizeof(scfsi)); + for(int band=0; band < total_bands; band++) + for(int chan=0; chan < channel_count; chan++) + if(band_param[chan][band]) + scfsi[chan][band] = gb(2); +} + +void mpeg_audio::read_band_amplitude_params() +{ + memset(scf, 0, sizeof(scf)); + for(int band=0; band < total_bands; band++) + for(int chan=0; chan= 32767) + cval = 32767; + else + cval = int(val); + *output = cval; + output += step; + } +} diff --git a/src/burn/snd/mpeg_audio.h b/src/burn/snd/mpeg_audio.h new file mode 100644 index 000000000..7f821565e --- /dev/null +++ b/src/burn/snd/mpeg_audio.h @@ -0,0 +1,134 @@ +// license:BSD-3-Clause +// copyright-holders:Olivier Galibert +/*************************************************************************** + + MPEG audio support. Only layer2 and variants for now. + +***************************************************************************/ + +#ifndef FBNEO_SOUND_MPEG_AUDIO_H +#define FBNEO_SOUND_MPEG_AUDIO_H + +#pragma once + +class mpeg_audio { +public: + // Accepted layers. Beware that AMM is incompatible with L2 (and + // not automatically recognizable) and that 2.5 implies 2. + + enum { + L1 = 1, + L2 = 2, + L2_5 = 4, + L3 = 8, + AMM = 16 + }; + + // base = Start of the mpeg data block + // accepted = Binary or of accepted layers + // lsb_first = Read bits out of bytes lsb-first rather than msb first + // position_align = Position alignment after reading a block (0 = pure bitstream, must be a power of 2 otherwise) + + mpeg_audio(const void *base, unsigned int accepted, bool lsb_first, int position_align); + + // Decode one mpeg buffer. + // pos = position in *bits* relative to base + // limit = maximum accepted position in bits + // output = output samples, interleaved + // output_samples = number of samples written to output per channel + // sample_rate = output sample rate + // channels = number of channels written to output (total sample count is output_samples*channels) + // + // returns true if the buffer was complete and the new position in pos, false otherwise + // + // Sample rate and channels can change every buffer. That's mpeg + // for you. Channels rarely changes, sample rate sometimes do, + // especially in amm samples (drops to half at the end). + // + // One call to output buffer will generate 0 or 1 frame, which is + // 384 samples per channel in layer I and 1152 otherwise (up to + // 1152 in the amm case, <1152 indicating end of stream). + + bool decode_buffer(int &pos, int limit, short *output, + int &output_samples, int &sample_rate, int &channels); + + + // Clear audio buffer + void clear(); + + // state-scan + void scan(); + +private: + struct limit_hit {}; + + struct band_info { + int modulo; + double s1; + int bits, cube_bits; + int s4, s5; + double range, s7, scale, offset; + }; + + static const double scalefactors[64]; + static const int sample_rates[8]; + static const int layer2_param_index[2][4][16]; + static const int band_parameter_indexed_values[5][32][17]; + static const int band_parameter_index_bits_count[5][32]; + static const int joint_band_counts[4], total_band_counts[5]; + static const band_info band_infos[18]; + static const double synthesis_filter[512]; + + const UINT8 *base; + int accepted, position_align; + + int sampling_rate, last_frame_number; + int param_index; + + int channel_count, total_bands, joint_bands; + + int band_param[2][32]; + int scfsi[2][32]; + int scf[2][3][32]; + double amp_values[2][3][32]; + double bdata[2][3][32]; + double subbuffer[2][32]; + double audio_buffer[2][32*32]; + int audio_buffer_pos[2]; + double m_cos_cache[32][32]; + + int current_pos, current_limit; + + void read_header_amm(bool layer25); + void read_header_mpeg2(bool layer25); + void read_data_mpeg2(); + void decode_mpeg2(short *output, int &output_samples); + + int get_band_param(int band); + void read_band_params(); + void read_scfci(); + void read_band_amplitude_params(); + void read_band_value_triplet(int chan, int band); + void build_amplitudes(); + void build_next_segments(int step); + void retrieve_subbuffer(int step); + void idct32(const double *input, double *output); + void resynthesis(const double *input, double *output); + void scale_and_clamp(const double *input, short *output, int step); + + + static int do_gb_msb(const unsigned char *data, int &pos, int count); + static int do_gb_lsb(const unsigned char *data, int &pos, int count); + + int (*do_gb)(const unsigned char *data, int &pos, int count); + + inline int gb(int count) + { + if(current_pos + count > current_limit) + throw limit_hit(); + + return do_gb(base, current_pos, count); + } +}; + +#endif // FBNEO_SOUND_MPEG_AUDIO_H diff --git a/src/burn/snd/ymz770.cpp b/src/burn/snd/ymz770.cpp new file mode 100644 index 000000000..e163053f6 --- /dev/null +++ b/src/burn/snd/ymz770.cpp @@ -0,0 +1,886 @@ +// license:BSD-3-Clause +// copyright-holders:Olivier Galibert, R. Belmont, MetalliC +/*************************************************************************** + + Yamaha YMZ770C "AMMS-A" and YMZ774 "AMMS2C" + + Emulation by R. Belmont and MetalliC + AMM decode by Olivier Galibert + +----- +TODO: +- What does channel ATBL mean? +- Simple Access mode. SACs is register / data lists same as SEQ. in 770C, when both /SEL and /CS pins goes low - will be run SAC with number set at data bus. + can not be used in CV1K (/SEL pin is NC, internally pulled to VCC), probably not used in PGM2 too. + 770: +- sequencer timers implemented but seems unused, presumably because of design flaws or bugs, likely due to lack of automatic adding of sequencer # to register offset. + in result sequences uses very long chains of 32-sample wait commands instead, wasting a lot of ROM space. +- sequencer triggers not implemented, not sure how they works (Deathsmiles ending tune starts sequence with TGST = 01h, likely a bug and don't affect tune playback) + 774: +- 4 channel output +- Equalizer +- pan delayed transition (not used in games) +- sequencer off trigger (not used in games) + + known SPUs in this series: + YMZ770B AMMSL Capcom medal hardware (alien.cpp), sample format is not AMM, in other parts looks like 770C + YMZ770C AMMS-A Cave CV1000 + YMZ771 SSGS3 + YMZ773 AMMS2 Cron corp. video slots + YMZ775 AMMS2B + YMZ774 AMMS2C IGS PGM2 + YMZ776 AMMS3 uses AM3 sample format (modified Ogg?) + YMZ778 AMMS3S + YMZ779 AMMS3D + YMZ870 AMMS3EX +// ported from mame 0.228 -dink april, 2022 +***************************************************************************/ + +#include "burnint.h" +#include "ymz770.h" +#include "stream.h" +#include "mpeg_audio.h" + +#define logerror + +static Stream stream; + +static UINT8 *m_rom; +static INT32 m_rom_size; +static INT32 m_rom_mask; + +static void (*sequencer)(); +static void (*internal_reg_write)(UINT8, UINT8); +static UINT32 (*get_phrase_offs)(int); +static UINT32 (*get_seq_offs)(int); + +static void ymz770_internal_reg_write(UINT8 reg, UINT8 data); // forward +static void ymz774_internal_reg_write(UINT8 reg, UINT8 data); +static void ymz770_sequencer(); +static void ymz774_sequencer(); +static void ymz770_stream_update(INT16 **streams, INT32 samples); + +static UINT32 ymz770_get_phrase_offs(int phrase) { return m_rom[(4 * phrase) + 1] << 16 | m_rom[(4 * phrase) + 2] << 8 | m_rom[(4 * phrase) + 3]; } +static UINT32 ymz770_get_seq_offs(int sqn) { return m_rom[(4 * sqn) + 1 + 0x400] << 16 | m_rom[(4 * sqn) + 2 + 0x400] << 8 | m_rom[(4 * sqn) + 3 + 0x400]; } +static UINT8 get_rom_byte(UINT32 offset) { return m_rom[offset & m_rom_mask]; } + +static UINT32 ymz774_get_phrase_offs(int phrase) { int ph = phrase * 4; return ((m_rom[ph] & 0x0f) << 24 | m_rom[ph + 1] << 16 | m_rom[ph + 2] << 8 | m_rom[ph + 3]) * 2; } +static UINT32 ymz774_get_seq_offs(int sqn) { int sq = sqn * 4 + 0x2000; return ((m_rom[sq] & 0x0f) << 24 | m_rom[sq + 1] << 16 | m_rom[sq + 2] << 8 | m_rom[sq + 3]) * 2; } +static UINT32 ymz774_get_sqc_offs(int sqc) { int sq = sqc * 4 + 0x6000; return ((m_rom[sq] & 0x0f) << 24 | m_rom[sq + 1] << 16 | m_rom[sq + 2] << 8 | m_rom[sq + 3]) * 2; } + +// data +static UINT8 m_cur_reg; +static UINT8 m_mute; // mute chip +static UINT8 m_doen; // digital output enable +static UINT8 m_vlma; // overall volume L0/R0 +static UINT8 m_vlma1; // overall volume L1/R1 +static UINT8 m_bsl; // boost level +static UINT8 m_cpl; // clip limiter +static int m_bank; // ymz774 +static UINT32 volinc[256]; // ymz774 (no scan) + +struct ymz_channel +{ + UINT16 phrase; + UINT8 pan; + UINT8 pan_delay; + UINT8 pan1; + UINT8 pan1_delay; + INT32 volume; + UINT8 volume_target; + UINT8 volume_delay; + UINT8 volume2; + UINT8 loop; + + bool is_playing, last_block, is_paused; + + INT16 output_data[0x1000]; + int output_remaining; + int output_ptr; + int atbl; + int pptr; + + int end_channel_data; +}; + +static mpeg_audio *mpeg_decoder[16]; + +struct ymz_sequence +{ + UINT32 delay; + UINT16 sequence; + UINT16 timer; + UINT16 stopchan; + UINT8 loop; + UINT32 offset; + UINT8 bank; + bool is_playing; + bool is_paused; +}; +struct ymz_sqc +{ + UINT8 sqc; + UINT8 loop; + UINT32 offset; + bool is_playing; + bool is_waiting; +}; + +static ymz_channel m_channels[16]; +static ymz_sequence m_sequences[8]; +static ymz_sqc m_sqcs[8]; + +static INT32 ymz_initted = 0; + +//------------------------------------------------- +// device_start - device-specific startup +//------------------------------------------------- + +static void init_common() +{ + for (int i = 0; i < 16; i++) + { + m_channels[i].is_playing = false; + mpeg_decoder[i] = new mpeg_audio(m_rom, mpeg_audio::AMM, false, 0); + } + for (int i = 0; i < 8; i++) + m_sequences[i].is_playing = false; + for (int i = 0; i < 8; i++) + m_sqcs[i].is_playing = false; + + ymz_initted = 1; +} + +void ymz770_exit() +{ + if (ymz_initted) { + ymz_initted = 0; + stream.exit(); + + for (int i = 0; i < 16; i++) + { + delete mpeg_decoder[i]; + } + } +} + +void ymz770_set_buffered(INT32 (*pCPUCyclesCB)(), INT32 nCPUMhz) +{ + stream.set_buffered(pCPUCyclesCB, nCPUMhz); +} + +void ymz770_init(UINT8 *rom, INT32 rom_length) +{ + // create the stream + stream.init(16000, nBurnSoundRate, 2, 0, ymz770_stream_update); + + m_rom = rom; + m_rom_size = rom_length; + m_rom_mask = rom_length - 1; + + // setup function pointers + sequencer = ymz770_sequencer; + internal_reg_write = ymz770_internal_reg_write; + get_phrase_offs = ymz770_get_phrase_offs; + get_seq_offs = ymz770_get_seq_offs; + + init_common(); +} + +void ymz774_init(UINT8 *rom, INT32 rom_length) +{ + // create the stream + stream.init(44100, nBurnSoundRate, 2, 0, ymz770_stream_update); + + m_rom = rom; + m_rom_size = rom_length; + m_rom_mask = rom_length - 1; + + // setup function pointers + sequencer = ymz774_sequencer; + internal_reg_write = ymz774_internal_reg_write; + get_phrase_offs = ymz774_get_phrase_offs; + get_seq_offs = ymz774_get_seq_offs; + + init_common(); + + // calculate volume increments, fixed point values, fractions of 0x20000 + for (INT32 i = 0; i < 256; i++) + { + if (i < 0x20) + volinc[i] = i; + else + volinc[i] = (0x20 | (i & 0x1f)) << ((i >> 5) - 1); + } +} + + + +//------------------------------------------------- +// device_reset - device-specific reset +//------------------------------------------------- + +void ymz770_reset() +{ + memset(&m_channels, 0, sizeof(m_channels)); + for (int i = 0; i < 16; i++) + { + m_channels[i].phrase = 0; + m_channels[i].pan = 64; + m_channels[i].pan_delay = 0; + m_channels[i].pan1 = 64; + m_channels[i].pan1_delay = 0; + m_channels[i].volume = 0; + m_channels[i].volume_target = 0; + m_channels[i].volume_delay = 0; + m_channels[i].volume2 = 0; + m_channels[i].loop = 0; + m_channels[i].is_playing = false; + m_channels[i].is_paused = false; + m_channels[i].output_remaining = 0; + + mpeg_decoder[i]->clear(); + } + memset(&m_sequences, 0, sizeof(m_sequences)); + for (int i = 0; i < 8; i++) + { + m_sequences[i].delay = 0; + m_sequences[i].sequence = 0; + m_sequences[i].timer = 0; + m_sequences[i].stopchan = 0; + m_sequences[i].loop = 0; + m_sequences[i].bank = 0; + m_sequences[i].is_playing = false; + m_sequences[i].is_paused = false; + } + memset(&m_sqcs, 0, sizeof(m_sqcs)); + for (int i = 0; i < 8; i++) + { + m_sqcs[i].is_playing = false; + m_sqcs[i].loop = 0; + } +} + +void ymz770_update(INT16 *output, INT32 samples_len) +{ + if (samples_len != nBurnSoundLen) { + bprintf(0, _T("ymz77x_update(): once per frame, please!\n")); + return; + } + + stream.render(output, samples_len); +} + +void ymz770_scan(INT32 nAction, INT32 *pnMin) +{ + if (nAction & ACB_DRIVER_DATA) { + SCAN_VAR(m_cur_reg); + SCAN_VAR(m_mute); + SCAN_VAR(m_doen); + SCAN_VAR(m_vlma); + SCAN_VAR(m_vlma1); + SCAN_VAR(m_bsl); + SCAN_VAR(m_cpl); + SCAN_VAR(m_bank); + SCAN_VAR(m_channels); + SCAN_VAR(m_sequences); + SCAN_VAR(m_sqcs); + + for (int i = 0; i < 16; i++) { + mpeg_decoder[i]->scan(); // mpeg/amm2 decoder state + } + } +} + + +//------------------------------------------------- +// sound_stream_update - handle update requests for +// our sound stream +//------------------------------------------------- + +static void ymz770_stream_update(INT16 **streams, INT32 samples) +{ + INT16 *outL = streams[0]; + INT16 *outR = streams[1]; + + for (int i = 0; i < samples; i++) + { + sequencer(); + + // process channels + INT32 mixl = 0; + INT32 mixr = 0; + + for (int ch = 0; ch < 16; ch++) + { + ymz_channel &channel = m_channels[ch]; + + if (channel.output_remaining > 0) + { + // force finish current block + INT32 smpl = ((INT32)channel.output_data[channel.output_ptr++] * (channel.volume >> 17)) >> 7; // volume is linear, 0 - 128 (100%) + smpl = (smpl * channel.volume2) >> 7; + mixr += (smpl * channel.pan) >> 7; // pan seems linear, 0 - 128, where 0 = 100% left, 128 = 100% right, 64 = 50% left 50% right + mixl += (smpl * (128 - channel.pan)) >> 7; + channel.output_remaining--; + + if (channel.output_remaining == 0 && !channel.is_playing) + mpeg_decoder[ch]->clear(); + } + + else if (channel.is_playing && !channel.is_paused) + { +retry: + if (channel.last_block) + { + if (channel.loop) + { + if (channel.loop != 255) + --channel.loop; + // loop sample + int phrase = channel.phrase; + channel.atbl = m_rom[(4*phrase)+0] >> 4 & 7; + channel.pptr = 8 * get_phrase_offs(phrase); + } + else + { + channel.is_playing = false; + channel.output_remaining = 0; + mpeg_decoder[ch]->clear(); + } + } + + if (channel.is_playing) + { + // next block + int sample_rate, channel_count; + if (!mpeg_decoder[ch]->decode_buffer(channel.pptr, m_rom_size*8, channel.output_data, channel.output_remaining, sample_rate, channel_count) || channel.output_remaining == 0) + { + channel.is_playing = !channel.last_block; // detect infinite retry loop + channel.last_block = true; + channel.output_remaining = 0; + goto retry; + } + + channel.last_block = channel.output_remaining < 1152; + channel.output_remaining--; + channel.output_ptr = 1; + + INT32 smpl = ((INT32)channel.output_data[0] * (channel.volume >> 17)) >> 7; + smpl = (smpl * channel.volume2) >> 7; + mixr += (smpl * channel.pan) >> 7; + mixl += (smpl * (128 - channel.pan)) >> 7; + } + } + } + + mixr *= m_vlma; // main volume is linear, 0 - 255, where 128 = 100% + mixl *= m_vlma; + mixr >>= 7 - m_bsl; + mixl >>= 7 - m_bsl; + // Clip limiter: 0 - off, 1 - 6.02 dB (100%), 2 - 4.86 dB (87.5%), 3 - 3.52 dB (75%) + const INT32 ClipMax3 = 32768 * 75 / 100; + const INT32 ClipMax2 = 32768 * 875 / 1000; + switch (m_cpl) + { + case 3: + mixl = (mixl > ClipMax3) ? ClipMax3 : (mixl < -ClipMax3) ? -ClipMax3 : mixl; + mixr = (mixr > ClipMax3) ? ClipMax3 : (mixr < -ClipMax3) ? -ClipMax3 : mixr; + break; + case 2: + mixl = (mixl > ClipMax2) ? ClipMax2 : (mixl < -ClipMax2) ? -ClipMax2 : mixl; + mixr = (mixr > ClipMax2) ? ClipMax2 : (mixr < -ClipMax2) ? -ClipMax2 : mixr; + break; + case 1: + mixl = (mixl > 32767) ? 32767 : (mixl < -32768) ? -32768 : mixl; + mixr = (mixr > 32767) ? 32767 : (mixr < -32768) ? -32768 : mixr; + break; + } + if (m_mute) + mixr = mixl = 0; + + outL[i] = mixl; + outR[i] = mixr; + } +} + +static void ymz770_sequencer() +{ + for (int i = 0; i < 8; i++) + { + ymz_sequence &sequence = m_sequences[i]; + + if (sequence.is_playing) + { + if (sequence.delay > 0) + sequence.delay--; + else + { + int reg = get_rom_byte(sequence.offset++); + UINT8 data = get_rom_byte(sequence.offset++); + switch (reg) + { + case 0x0f: + for (int ch = 0; ch < 8; ch++) // might be wrong, ie not needed in case of loop + if (sequence.stopchan & (1 << ch)) + m_channels[ch].is_playing = false; + if (sequence.loop) + sequence.offset = get_seq_offs(sequence.sequence); // loop sequence + else + sequence.is_playing = false; + break; + case 0x0e: + sequence.delay = sequence.timer * 32 + 32 - 1; + break; + default: + internal_reg_write(reg, data); + break; + } + } + } + } +} + +//------------------------------------------------- +// write - write to the chip's registers +//------------------------------------------------- + +void ymz770_write(INT32 offset, UINT8 data) +{ + if (offset & 1) + { + stream.update(); + internal_reg_write(m_cur_reg, data); + } + else + m_cur_reg = data; +} + + +static void ymz770_internal_reg_write(UINT8 reg, UINT8 data) +{ + // global registers + if (reg < 0x40) + { + switch (reg) + { + case 0x00: + m_mute = data & 1; + m_doen = data >> 1 & 1; + break; + + case 0x01: + m_vlma = data; + break; + + case 0x02: + m_bsl = data & 7; + m_cpl = data >> 4 & 7; + break; + + // unused + default: + logerror("unimplemented write %02X %02X\n", reg, data); + break; + } + } + + // playback registers + else if (reg < 0x60) + { + int ch = reg >> 2 & 0x07; + + switch (reg & 0x03) + { + case 0: + m_channels[ch].phrase = data; + break; + + case 1: + m_channels[ch].volume2 = data; + m_channels[ch].volume = 128 << 17; + break; + + case 2: + m_channels[ch].pan = data << 3; + break; + + case 3: + if ((data & 6) == 2 || ((data & 6) == 6 && !m_channels[ch].is_playing)) // both KON bits is 1 = "Keep Playing", do not restart channel in this case + { + UINT8 phrase = m_channels[ch].phrase; + m_channels[ch].atbl = m_rom[(4*phrase)+0] >> 4 & 7; + m_channels[ch].pptr = 8 * get_phrase_offs(phrase); + m_channels[ch].last_block = false; + + m_channels[ch].is_playing = true; + } + else if ((data & 6) == 0) + m_channels[ch].is_playing = false; + + m_channels[ch].loop = (data & 1) ? 255 : 0; + break; + } + } + + // sequencer registers + else if (reg >= 0x80) + { + int ch = reg >> 4 & 0x07; + + switch (reg & 0x0f) + { + case 0: // SQSN + m_sequences[ch].sequence = data; + break; + case 1: // SQON SQLP + if ((data & 6) == 2 || ((data & 6) == 6 && !m_sequences[ch].is_playing)) // both KON bits 1 is "Keep Playing" + { + m_sequences[ch].offset = get_seq_offs(m_sequences[ch].sequence); + m_sequences[ch].delay = 0; + m_sequences[ch].is_playing = true; + } + else if ((data & 6) == 0 && m_sequences[ch].is_playing) + { + m_sequences[ch].is_playing = false; + for (int i = 0; i < 8; i++) + if (m_sequences[ch].stopchan & (1 << i)) + m_channels[i].is_playing = false; + } + + m_sequences[ch].loop = data & 1; + break; + case 2: // TMRH + m_sequences[ch].timer = (m_sequences[ch].timer & 0x00ff) | (data << 8); + break; + case 3: // TMRL + m_sequences[ch].timer = (m_sequences[ch].timer & 0xff00) | data; + break; + case 6: // SQOF + m_sequences[ch].stopchan = data; + break; + //case 4: // TGST "ON trigger playback channel selection bitmask" + //case 5: // TGEN "OFF trigger playback channel selection bitmask" + //case 7: // YMZ770: unused, but CV1K games write 1 here, must be game bugs or YMZ770C datasheet is wrong and have messed 7 and 8 registers ? ; YMZ771: SQOF_SSG + //case 8: // YMZ770: docs said "set to 01h" but CV1K games never write it, see above. ; YMZ771: TEMPO (wait timer speed control) + default: + if (data) + logerror("unimplemented write %02X %02X\n", reg, data); + break; + } + } + else + logerror("unimplemented write %02X %02X\n", reg, data); +} + + +//------------------------------------------------- +// ymz774_device +//------------------------------------------------- + +UINT8 ymz774_read(INT32 offset) +{ + if (offset & 1) + { + if (m_cur_reg == 0xe3 || m_cur_reg == 0xe4) + { + stream.update(); + UINT8 res = 0; + int bank = (m_cur_reg == 0xe3) ? 8 : 0; + for (int i = 0; i < 8; i++) + if (m_channels[i + bank].is_playing) + res |= 1 << i; + return res; + } + } + logerror("unimplemented read %02X\n", m_cur_reg); + return 0; +} + +static void ymz774_internal_reg_write(UINT8 reg, UINT8 data) +{ + // playback registers + if (reg < 0x10) // phrase num H and L + { + int ch = ((reg >> 1) & 7) + m_bank * 8; + if (reg & 1) + m_channels[ch].phrase = (m_channels[ch].phrase & 0xff00) | data; + else + m_channels[ch].phrase = (m_channels[ch].phrase & 0x00ff) | ((data & 7) << 8); + } + else if (reg < 0x60) + { + int ch = (reg & 7) + m_bank * 8; + switch (reg & 0xf8) + { + case 0x10: // Volume 1 + m_channels[ch].volume_target = data; + break; + case 0x18: // Volume 1 delayed transition + m_channels[ch].volume_delay = data; + break; + case 0x20: // Volume 2 + m_channels[ch].volume2 = data; + break; + case 0x28: // Pan L/R + m_channels[ch].pan = data; + break; + case 0x30: // Pan L/R delayed transition + if (data) logerror("unimplemented write %02X %02X\n", reg, data); + m_channels[ch].pan_delay = data; + break; + case 0x38: // Pan T/B + m_channels[ch].pan1 = data; + break; + case 0x40: // Pan T/B delayed transition + if (data) logerror("unimplemented write %02X %02X\n", reg, data); + m_channels[ch].pan1_delay = data; + break; + case 0x48: // Loop + m_channels[ch].loop = data; + break; + case 0x50: // Start / Stop + if (data) + { + int phrase = m_channels[ch].phrase; + m_channels[ch].atbl = m_rom[(4 * phrase) + 0] >> 4 & 7; + m_channels[ch].pptr = 8 * get_phrase_offs(phrase); + m_channels[ch].last_block = false; + + m_channels[ch].is_playing = true; + m_channels[ch].is_paused = false; // checkme, might be not needed + } + else + m_channels[ch].is_playing = false; + break; + case 0x58: // Pause / Resume + m_channels[ch].is_paused = data ? true : false; + if (data) logerror("CHECKME: CHAN pause/resume %02X %02X\n", reg, data); + break; + } + } + else if (reg < 0xd0) + { + if (m_bank == 0) + { + int sq = reg & 7; + switch (reg & 0xf8) + { + case 0x60: // sequence num H and L + case 0x68: + sq = (reg >> 1) & 7; + if (reg & 1) + m_sequences[sq].sequence = (m_sequences[sq].sequence & 0xff00) | data; + else + m_sequences[sq].sequence = (m_sequences[sq].sequence & 0x00ff) | ((data & 0x07) << 8); + break; + case 0x70: // Start / Stop + if (data) + { + //logerror("SEQ %d start (%s)\n", sq, m_sequences[sq].is_playing ? "playing":"stopped"); + m_sequences[sq].offset = get_seq_offs(m_sequences[sq].sequence); + m_sequences[sq].delay = 0; + m_sequences[sq].is_playing = true; + m_sequences[sq].is_paused = false; // checkme, might be not needed + } + else + { + //logerror("SEQ %d stop (%s)\n", sq, m_sequences[sq].is_playing ? "playing" : "stopped"); + if (m_sequences[sq].is_playing) + for (int ch = 0; ch < 16; ch++) + if (m_sequences[sq].stopchan & (1 << ch)) + m_channels[ch].is_playing = false; + m_sequences[sq].is_playing = false; + } + break; + case 0x78: // Pause / Resume + m_sequences[sq].is_paused = data ? true : false; + if (data) logerror("CHECKME: SEQ pause/resume %02X %02X\n", reg, data); + break; + case 0x80: // Loop count, 0 = off, 255 - infinite + m_sequences[sq].loop = data; + break; + case 0x88: // timer H and L + case 0x90: + sq = (reg - 0x88) >> 1; + if (reg & 1) + m_sequences[sq].timer = (m_sequences[sq].timer & 0xff00) | data; + else + m_sequences[sq].timer = (m_sequences[sq].timer & 0x00ff) | (data << 8); + break; + case 0x98: // Off trigger, bit4 = on/off, bits0-3 channel (end sequence when channel playback ends) + if (data) logerror("SEQ Off trigger unimplemented %02X %02X\n", reg, data); + break; + case 0xa0: // stop channel mask H and L (when sequence stopped) + case 0xa8: + sq = (reg >> 1) & 7; + if (reg & 1) + m_sequences[sq].stopchan = (m_sequences[sq].stopchan & 0xff00) | data; + else + m_sequences[sq].stopchan = (m_sequences[sq].stopchan & 0x00ff) | (data << 8); + break; + case 0xb0: // SQC number + m_sqcs[sq].sqc = data; + break; + case 0xb8: // SQC start / stop + if (data) + { + //logerror("SQC %d start (%s)\n", sq, m_sqcs[sq].is_playing ? "playing" : "stopped"); + m_sqcs[sq].offset = ymz774_get_sqc_offs(m_sqcs[sq].sqc); + m_sqcs[sq].is_playing = true; + m_sqcs[sq].is_waiting = false; + } + else + { + //logerror("SQC %d stop (%s)\n", sq, m_sqcs[sq].is_playing ? "playing" : "stopped"); + m_sqcs[sq].is_playing = false; + // stop SEQ too, and stop channels + if (m_sequences[sq].is_playing) + for (int ch = 0; ch < 16; ch++) + if (m_sequences[sq].stopchan & (1 << ch)) + m_channels[ch].is_playing = false; + m_sequences[sq].is_playing = false; + } + break; + case 0xc0: // SQC loop (255 = infinite) + m_sqcs[sq].loop = data; + break; + default: + logerror("unimplemented write %02X %02X\n", reg, data); + break; + } + } + // else bank1 - Equalizer control + } + // global registers + else + { + switch (reg) { + case 0xd0: // Total Volume L0/R0 + m_vlma = data; + break; + case 0xd1: // Total Volume L1/R1 + m_vlma1 = data; + break; + case 0xd2: // Clip limit + m_cpl = data; + break; + //case 0xd3: // Digital/PWM output + //case 0xd4: // Digital audio IF/IS L0/R0 + //case 0xd5: // Digital audio IF/IS L1/R1 + //case 0xd8: // GPIO A + //case 0xdd: // GPIO B + //case 0xde: // GPIO C + case 0xf0: + m_bank = data & 1; + if (data > 1) logerror("Set bank %02X!\n", data); + break; + default: + logerror("unimplemented write %02X %02X\n", reg, data); + break; + } + } +} + +static void ymz774_sequencer() +{ + for (int i = 0; i < 16; i++) + { + ymz_channel &chan = m_channels[i]; + + if (chan.is_playing && !chan.is_paused && (chan.volume >> 17) != chan.volume_target) + { + if (chan.volume_delay) + { + if ((chan.volume >> 17) < chan.volume_target) + chan.volume += volinc[chan.volume_delay]; + else + chan.volume -= volinc[chan.volume_delay]; + } + else + chan.volume = chan.volume_target << 17; + } + } + + for (int i = 0; i < 8; i++) + { + ymz_sqc &sqc = m_sqcs[i]; + ymz_sequence &sequence = m_sequences[i]; + + if (sqc.is_playing && !sqc.is_waiting) + { + // SQC consists of 4 byte records: SEQ num H, SEQ num L, SEQ Loop count, End flag (0xff) + sequence.sequence = ((get_rom_byte(sqc.offset) << 8) | get_rom_byte(sqc.offset + 1)) & 0x7ff; + sqc.offset += 2; + sequence.loop = get_rom_byte(sqc.offset++); + sequence.offset = get_seq_offs(sequence.sequence); + sequence.delay = 0; + sequence.is_playing = true; + sequence.is_paused = false; // checkme, might be not needed + sqc.is_waiting = true; + if (get_rom_byte(sqc.offset++) == 0xff) + { + if (sqc.loop) + { + if (sqc.loop != 255) + --sqc.loop; + sqc.offset = ymz774_get_sqc_offs(sqc.sqc); + } + else + sqc.is_playing = false; + } + } + + if (sequence.is_playing && !sequence.is_paused) + { + if (sequence.delay > 0) + --sequence.delay; + else + { + int reg = get_rom_byte(sequence.offset++); + UINT8 data = get_rom_byte(sequence.offset++); + switch (reg) + { + case 0xff: // end + for (int ch = 0; ch < 16; ch++) // might be wrong, ie not needed in case of loop + if (sequence.stopchan & (1 << ch)) + m_channels[ch].is_playing = false; + if (sequence.loop) + { + if (sequence.loop != 255) + --sequence.loop; + sequence.offset = get_seq_offs(sequence.sequence); + } + else + { + sequence.is_playing = false; + sqc.is_waiting = false; + } + break; + case 0xfe: // timer delay + sequence.delay = sequence.timer * 32 + 32 - 1; + break; + case 0xf0: + sequence.bank = data & 1; + break; + default: + { + UINT8 temp = m_bank; + m_bank = sequence.bank; + if (m_bank == 0 && reg >= 0x60 && reg < 0xb0) // if we hit SEQ registers need to add this sequence offset + { + int sqn = i; + if (reg < 0x70 || (reg >= 0x88 && reg < 0x98) || reg >= 0xa0) + sqn = i * 2; + internal_reg_write(reg + sqn, data); + } + else + internal_reg_write(reg, data); + m_bank = temp; + } + break; + } + } + } + } +} diff --git a/src/burn/snd/ymz770.h b/src/burn/snd/ymz770.h new file mode 100644 index 000000000..97623be69 --- /dev/null +++ b/src/burn/snd/ymz770.h @@ -0,0 +1,11 @@ +void ymz770_init(UINT8 *rom, INT32 rom_length); +void ymz774_init(UINT8 *rom, INT32 rom_length); // ymz774-specific! +void ymz770_exit(); +void ymz770_set_buffered(INT32 (*pCPUCyclesCB)(), INT32 nCPUMhz); + +void ymz770_write(INT32 offset, UINT8 data); +UINT8 ymz774_read(INT32 offset); // ymz774-specific! + +void ymz770_scan(INT32 nAction, INT32 *pnMin); +void ymz770_update(INT16 *output, INT32 samples_len); +void ymz770_reset();