diff options
author | 2018-08-17 15:58:29 +0200 | |
---|---|---|
committer | 2018-08-17 09:58:29 -0400 | |
commit | 3b57b7e90ca67d58ac46136aacc30b55a15e1352 (patch) | |
tree | 1087dcc17f86bf689bb70f40fd8f0b27c5b8c809 /src/devices | |
parent | 59c2a0536c857ef7ddb24559b2385dc2d8a8b154 (diff) |
taito_zm: DSP emulation (work in progress!) (#3854)
* taito_zm.cpp : Updates
Add DSP, Reduce MCFGs, Add device_mixer_interface for sound gain, Add imperfect_features related to DSP, Add notes
* taito_zm.cpp : Fix TMS57002 clock
* Improve Taito Zoom ZSG-2 sound emulation
zsg2.cpp: implement emphasis filter, this is a noise reduction scheme
that amplifies higher frequncies to reduce quantization noise.
zsg2.cpp: Add sample interpolation and another adjustable lowpass
filter. This seems to be roughly what real hardware does...
zsg2.cpp: Improve panning registers and identify DSP output gain
registers.
* zsg2: minor changes [nw]
zsg2: Register 0b appears to be status flags [nw]
zsg2: Linear ramping probably makes more sense [nw]
* zsg2: slight adjustment of emphasis filter [nw]
* zsg2: slight adjustment of emphasis filter #2 [nw]
* zsg2: more sober ramping algorithm [nw]
* tms57002: add instructions 3c/3d, make them behave as NOP as they're undocumented and not understood
* tms57002: Add dready callback for superctr (nw)
* tms57002: Fixes to make Taito Zoom DSP working
tms57002: Add undocumented instruction saom / raom, they set saturation
mode for the ALU.
tms57002: Implement MACC pipeline.
tms57002: Add callbacks for EMPTY and PC0 pins.
tms57002: Add a few unimplemented instructions.
tms57002: Proper behavior of CMEM UPLOAD mode.
tms57002: Fix an issue where program is not properly loaded if PLOAD is
set after a program has already been written.
* Documentation fix, properly identified registers as ramping control, will implement that soon [nw]
* taito_zm: Working DSP emulation
Pretty much OST quality now. A pretty decent upgrade from how it was
previously, I'd say.
* typo [nw]
* just adding some quick notes about the WIP [nw]
* Fix build [nw]
* zsg2: Proper ramping implemenation, add register map, minor cleanups
* oops [nw]
Diffstat (limited to 'src/devices')
-rw-r--r-- | src/devices/cpu/tms57002/tms57002.cpp | 108 | ||||
-rw-r--r-- | src/devices/cpu/tms57002/tms57002.h | 19 | ||||
-rw-r--r-- | src/devices/cpu/tms57002/tms57kdec.cpp | 9 | ||||
-rw-r--r-- | src/devices/cpu/tms57002/tmsinstr.lst | 44 | ||||
-rwxr-xr-x | src/devices/cpu/tms57002/tmsmake.py | 18 | ||||
-rw-r--r-- | src/devices/sound/zsg2.cpp | 166 | ||||
-rw-r--r-- | src/devices/sound/zsg2.h | 25 |
7 files changed, 261 insertions, 128 deletions
diff --git a/src/devices/cpu/tms57002/tms57002.cpp b/src/devices/cpu/tms57002/tms57002.cpp index 6e93f82040f..8a6b4ce0786 100644 --- a/src/devices/cpu/tms57002/tms57002.cpp +++ b/src/devices/cpu/tms57002/tms57002.cpp @@ -25,8 +25,10 @@ void tms57002_device::internal_pgm(address_map &map) tms57002_device::tms57002_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : cpu_device(mconfig, TMS57002, tag, owner, clock) , device_sound_interface(mconfig, *this) - , macc(0), st0(0), st1(0), sti(0), txrd(0) + , macc(0), macc_read(0), macc_write(0), st0(0), st1(0), sti(0), txrd(0) , m_dready_callback(*this) + , m_pc0_callback(*this) + , m_empty_callback(*this) , program_config("program", ENDIANNESS_LITTLE, 32, 8, -2, address_map_constructor(FUNC(tms57002_device::internal_pgm), this)) , data_config("data", ENDIANNESS_LITTLE, 8, 20) { @@ -47,7 +49,9 @@ WRITE_LINE_MEMBER(tms57002_device::pload_w) if(olds ^ sti) { if (sti & IN_PLOAD) { hidx = 0; - hpc = 0; + pc = 0; + ca = 0; + sti &= ~(SU_MASK); } } } @@ -62,26 +66,30 @@ WRITE_LINE_MEMBER(tms57002_device::cload_w) if(olds ^ sti) { if (sti & IN_CLOAD) { hidx = 0; - ca = 0; + //ca = 0; // Seems extremely dubious } } } void tms57002_device::device_reset() { - sti = (sti & ~(SU_MASK|S_READ|S_WRITE|S_BRANCH|S_HOST)) | (SU_ST0|S_IDLE); + sti = (sti & ~(SU_MASK|S_READ|S_WRITE|S_BRANCH|S_HOST|S_UPDATE)) | (SU_ST0|S_IDLE); pc = 0; ca = 0; hidx = 0; id = 0; ba0 = 0; ba1 = 0; + update_counter_tail = 0; + update_counter_head = 0; st0 &= ~(ST0_INCS | ST0_DIRI | ST0_FI | ST0_SIM | ST0_PLRI | ST0_PBCI | ST0_DIRO | ST0_FO | ST0_SOM | ST0_PLRO | ST0_PBCO | ST0_CNS); - st1 &= ~(ST1_AOV | ST1_SFAI | ST1_SFAO | ST1_MOVM | ST1_MOV | + st1 &= ~(ST1_AOV | ST1_SFAI | ST1_SFAO | ST1_AOVM | ST1_MOVM | ST1_MOV | ST1_SFMA | ST1_SFMO | ST1_RND | ST1_CRM | ST1_DBP); update_dready(); + update_pc0(); + update_empty(); xba = 0; xoa = 0; @@ -112,6 +120,7 @@ WRITE8_MEMBER(tms57002_device::data_w) break; case SU_PRG: program->write_dword(pc++, val); + update_pc0(); break; } } @@ -121,9 +130,11 @@ WRITE8_MEMBER(tms57002_device::data_w) host[hidx++] = data; if(hidx >= 4) { uint32_t val = (host[0]<<24) | (host[1]<<16) | (host[2]<<8) | host[3]; - cmem[sa] = val; sti &= ~SU_CVAL; - allow_update = 0; + update[update_counter_head] = val; + update_counter_head = (update_counter_head + 1) & 0x0f; + hidx = 1; // the write shouldn't really happen until CLOAD is high though + update_empty(); } } else { sa = data; @@ -160,11 +171,6 @@ READ8_MEMBER(tms57002_device::data_r) return res; } -READ_LINE_MEMBER(tms57002_device::empty_r) -{ - return 1; -} - READ_LINE_MEMBER(tms57002_device::dready_r) { return sti & S_HOST ? 0 : 1; @@ -180,9 +186,24 @@ READ_LINE_MEMBER(tms57002_device::pc0_r) return pc == 0 ? 0 : 1; } +void tms57002_device::update_pc0() +{ + m_pc0_callback(pc == 0 ? 0 : 1); +} + +READ_LINE_MEMBER(tms57002_device::empty_r) +{ + return (update_counter_head == update_counter_tail); +} + +void tms57002_device::update_empty() +{ + m_empty_callback(update_counter_head == update_counter_tail); +} + WRITE_LINE_MEMBER(tms57002_device::sync_w) { - if(sti & (IN_PLOAD | IN_CLOAD)) + if(sti & (IN_PLOAD /*| IN_CLOAD*/)) return; allow_update = 1; @@ -293,7 +314,7 @@ inline void tms57002_device::xm_step_write() int64_t tms57002_device::macc_to_output_0(int64_t rounding, uint64_t rmask) { - int64_t m = macc; + int64_t m = macc_read; uint64_t m1; int over = 0; @@ -318,7 +339,7 @@ int64_t tms57002_device::macc_to_output_0(int64_t rounding, uint64_t rmask) int64_t tms57002_device::macc_to_output_1(int64_t rounding, uint64_t rmask) { - int64_t m = macc; + int64_t m = macc_read; uint64_t m1; int over = 0; @@ -344,7 +365,7 @@ int64_t tms57002_device::macc_to_output_1(int64_t rounding, uint64_t rmask) int64_t tms57002_device::macc_to_output_2(int64_t rounding, uint64_t rmask) { - int64_t m = macc; + int64_t m = macc_read; uint64_t m1; int over = 0; @@ -370,7 +391,7 @@ int64_t tms57002_device::macc_to_output_2(int64_t rounding, uint64_t rmask) int64_t tms57002_device::macc_to_output_3(int64_t rounding, uint64_t rmask) { - int64_t m = macc; + int64_t m = macc_read; uint64_t m1; int over = 0; @@ -393,7 +414,7 @@ int64_t tms57002_device::macc_to_output_3(int64_t rounding, uint64_t rmask) int64_t tms57002_device::macc_to_output_0s(int64_t rounding, uint64_t rmask) { - int64_t m = macc; + int64_t m = macc_read; uint64_t m1; int over = 0; @@ -422,7 +443,7 @@ int64_t tms57002_device::macc_to_output_0s(int64_t rounding, uint64_t rmask) int64_t tms57002_device::macc_to_output_1s(int64_t rounding, uint64_t rmask) { - int64_t m = macc; + int64_t m = macc_read; uint64_t m1; int over = 0; @@ -452,7 +473,7 @@ int64_t tms57002_device::macc_to_output_1s(int64_t rounding, uint64_t rmask) int64_t tms57002_device::macc_to_output_2s(int64_t rounding, uint64_t rmask) { - int64_t m = macc; + int64_t m = macc_read; uint64_t m1; int over = 0; @@ -482,7 +503,7 @@ int64_t tms57002_device::macc_to_output_2s(int64_t rounding, uint64_t rmask) int64_t tms57002_device::macc_to_output_3s(int64_t rounding, uint64_t rmask) { - int64_t m = macc; + int64_t m = macc_read; uint64_t m1; int over = 0; @@ -607,6 +628,34 @@ int64_t tms57002_device::check_macc_overflow_3s() return macc; } +uint32_t tms57002_device::get_cmem(uint8_t addr) +{ + if(sa == addr && update_counter_head != update_counter_tail) + sti |= S_UPDATE; + + if(sti & S_UPDATE) + { + cmem[addr] = update[update_counter_tail]; + update_counter_tail = (update_counter_tail + 1) & 0x0f; + update_empty(); + + if(update_counter_head == update_counter_tail) + sti &= ~S_UPDATE; + + return cmem[addr]; // The value of crm is ignored during an update. + } + else + { + int crm = (st1 & ST1_CRM) >> ST1_CRM_SHIFT; + uint32_t cvar = cmem[addr]; + if(crm == 1) + return (cvar & 0xffff0000); + else if(crm == 2) + return (cvar << 16); + return cvar; + } +} + void tms57002_device::cache_flush() { int i; @@ -730,7 +779,7 @@ void tms57002_device::execute_run() { int ipc = -1; - while(icount > 0 && !(sti & (S_IDLE | IN_PLOAD | IN_CLOAD))) { + while(icount > 0 && !(sti & (S_IDLE | IN_PLOAD /*| IN_CLOAD*/))) { int iipc; debugger_instruction_hook(pc); @@ -746,6 +795,9 @@ void tms57002_device::execute_run() else xm_step_write(); } + + macc_read = macc_write; + macc_write = macc; for(;;) { uint32_t c, d; @@ -786,8 +838,10 @@ void tms57002_device::execute_run() } else if(sti & S_BRANCH) { sti &= ~S_BRANCH; ipc = -1; - } else + } else { pc++; // Wraps if it reaches 256, next wraps too + update_pc0(); + } if(rptc_next) { rptc = rptc_next; @@ -825,6 +879,8 @@ void tms57002_device::sound_stream_update(sound_stream &stream, stream_sample_t void tms57002_device::device_resolve_objects() { m_dready_callback.resolve_safe(); + m_pc0_callback.resolve_safe(); + m_empty_callback.resolve_safe(); } void tms57002_device::device_start() @@ -861,10 +917,13 @@ void tms57002_device::device_start() stream_alloc(4, 4, STREAM_SYNC); save_item(NAME(macc)); + save_item(NAME(macc_read)); + save_item(NAME(macc_write)); save_item(NAME(cmem)); save_item(NAME(dmem0)); save_item(NAME(dmem1)); + save_item(NAME(update)); save_item(NAME(si)); save_item(NAME(so)); @@ -893,6 +952,9 @@ void tms57002_device::device_start() save_item(NAME(host)); save_item(NAME(hidx)); + + save_item(NAME(update_counter_head)); + save_item(NAME(update_counter_tail)); save_item(NAME(allow_update)); } diff --git a/src/devices/cpu/tms57002/tms57002.h b/src/devices/cpu/tms57002/tms57002.h index 640aed2a55b..0db37c49fdf 100644 --- a/src/devices/cpu/tms57002/tms57002.h +++ b/src/devices/cpu/tms57002/tms57002.h @@ -17,6 +17,8 @@ public: tms57002_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); auto dready_callback() { return m_dready_callback.bind(); } + auto pc0_callback() { return m_pc0_callback.bind(); } + auto empty_callback() { return m_empty_callback.bind(); } DECLARE_READ8_MEMBER(data_r); DECLARE_WRITE8_MEMBER(data_w); @@ -51,7 +53,8 @@ private: S_READ = 0x00000040, S_WRITE = 0x00000080, S_BRANCH = 0x00000100, - S_HOST = 0x00000200 + S_HOST = 0x00000200, + S_UPDATE = 0x00000400, }; enum { @@ -75,6 +78,7 @@ private: ST1_AOV = 0x000001, ST1_SFAI = 0x000002, ST1_SFAO = 0x000004, + ST1_AOVM = 0x000008, // undocumented! ST1_MOVM = 0x000020, ST1_MOV = 0x000040, ST1_SFMA = 0x000180, ST1_SFMA_SHIFT = 7, @@ -120,7 +124,8 @@ private: short ipc; }; - int64_t macc; + // macc_read and macc_write are used by non-pipelined instructions + int64_t macc, macc_read, macc_write; uint32_t cmem[256]; uint32_t dmem0[256]; @@ -136,10 +141,15 @@ private: uint32_t xm_adr; uint8_t host[4], hidx, allow_update; - + + uint32_t update[16]; + uint8_t update_counter_head, update_counter_tail; + cd cache; devcb_write_line m_dready_callback; + devcb_write_line m_pc0_callback; + devcb_write_line m_empty_callback; const address_space_config program_config, data_config; @@ -164,6 +174,8 @@ private: inline int sfma(uint32_t st1); void update_dready(); + void update_pc0(); + void update_empty(); void xm_init(); void xm_step_read(); @@ -190,6 +202,7 @@ private: short get_hash(unsigned char adr, uint32_t st1, short *pnode); short get_hashnode(unsigned char adr, uint32_t st1, short pnode); int decode_get_pc(); + uint32_t get_cmem(uint8_t addr); }; enum { diff --git a/src/devices/cpu/tms57002/tms57kdec.cpp b/src/devices/cpu/tms57002/tms57kdec.cpp index 8cca7ba825f..4c6dd2dc026 100644 --- a/src/devices/cpu/tms57002/tms57kdec.cpp +++ b/src/devices/cpu/tms57002/tms57kdec.cpp @@ -11,6 +11,7 @@ #include "emu.h" #include "debugger.h" #include "tms57002.h" +#include <algorithm> inline int tms57002_device::xmode(uint32_t opcode, char type, cstate *cs) { @@ -40,6 +41,10 @@ inline int tms57002_device::dbp(uint32_t st1) inline int tms57002_device::crm(uint32_t st1) { + // value overridden during cvar update + if(update_counter_head != update_counter_tail) + return 0; + int crm = (st1 & ST1_CRM) >> ST1_CRM_SHIFT; return crm <= 2 ? crm : 0; } @@ -90,6 +95,7 @@ void tms57002_device::decode_cat1(uint32_t opcode, unsigned short *op, cstate *c #undef CDEC1 default: + logerror("Unhandled cat1 opcode %02x\n",opcode >> 18); decode_error(opcode); break; } @@ -106,6 +112,7 @@ void tms57002_device::decode_cat2_pre(uint32_t opcode, unsigned short *op, cstat #undef CDEC2A default: + logerror("Unhandled cat2_pre opcode %02x \n",(opcode >> 11) & 0x7f); decode_error(opcode); break; } @@ -122,6 +129,7 @@ void tms57002_device::decode_cat2_post(uint32_t opcode, unsigned short *op, csta #undef CDEC2B default: + logerror("Unhandled cat2_post opcode %02x\n",(opcode >> 11) & 0x7f); decode_error(opcode); break; } @@ -138,6 +146,7 @@ void tms57002_device::decode_cat3(uint32_t opcode, unsigned short *op, cstate *c #undef CDEC3 default: + logerror("Unhandled cat3 opcode %02x\n",(opcode >> 11) & 0x7f); decode_error(opcode); break; } diff --git a/src/devices/cpu/tms57002/tmsinstr.lst b/src/devices/cpu/tms57002/tmsinstr.lst index c1c0a2cf658..9160d9ecd5b 100644 --- a/src/devices/cpu/tms57002/tmsinstr.lst +++ b/src/devices/cpu/tms57002/tmsinstr.lst @@ -219,17 +219,17 @@ lirk 3 20 1 n lmhc 1 33 1 n lmhc %c - macc = ((int64_t)(int32_t)%c) << 16; + macc_write = macc = ((int64_t)(int32_t)%c) << 16; lmhd 1 31 1 n lmhd %d - macc = ((int64_t)(int32_t)%d) << 16; + macc_write = macc = ((int64_t)(int32_t)%d) << 16; lmld 1 32 1 n lmld %d - macc = (macc & ~0xffffffULL) | %d24; + macc_write = macc = (macc & ~0xffffffULL) | %d24; -lpc 2b 31 1 n +lpc 2a 31 1 n lpc %c if(sti & S_HOST) break; @@ -242,7 +242,7 @@ lpc 2b 31 1 n sti |= S_HOST; update_dready(); -lpd 2b 30 1 n +lpd 2a 30 1 n lpd %d mac 1 24 1 y @@ -256,6 +256,12 @@ mac 1 24 1 y mac 1 25 1 y mac a,%d + d = %d24; + if(d & 0x00800000) + d |= 0xff000000; + creg = c = %a; + r = (int64_t)(int32_t)c * (int64_t)(int32_t)d; + macc = %ml + (r >> 7); mac 1 26 1 y mac %c,a @@ -310,6 +316,7 @@ mpyu 1 28 1 y neg 1 02 1 n neg + %wa(-(int64_t)%a); or 1 17 1 n or %d,a @@ -408,11 +415,11 @@ scrm 2a 4b 1 n f scrm <3> st1 = (st1 & ~ST1_CRM) | (3 << ST1_CRM_SHIFT); -sfai 2b 54 1 n f +sfai 2a 54 1 n f sfai 0 st1 &= ~ST1_SFAI; -sfai 2b 55 1 n f +sfai 2a 55 1 n f sfai -1 st1 |= ST1_SFAI; @@ -422,21 +429,21 @@ sfao 2a 50 1 n f sfao 2a 51 1 n f sfao 7 - st1 |= ST1_SFAI; + st1 |= ST1_SFAO; -sfma 2b 58 1 n f +sfma 2a 58 1 n f sfma 0 st1 = (st1 & ~ST1_SFMA) | (0 << ST1_SFMA_SHIFT); -sfma 2b 59 1 n f +sfma 2a 59 1 n f sfma 2 st1 = (st1 & ~ST1_SFMA) | (1 << ST1_SFMA_SHIFT); -sfma 2b 5a 1 n f +sfma 2a 5a 1 n f sfma 4 st1 = (st1 & ~ST1_SFMA) | (2 << ST1_SFMA_SHIFT); -sfma 2b 5b 1 n f +sfma 2a 5b 1 n f sfma -16 st1 = (st1 & ~ST1_SFMA) | (3 << ST1_SFMA_SHIFT); @@ -504,9 +511,12 @@ sub 1 0a 1 y sub 1 0b 1 y sub %d,m + %sfai(d, %d); + %wa((int64_t)(int32_t)d - (%mo >> 16)); sub 1 0c 1 y sub %c,m + %wa((int64_t)(int32_t)%c - (%mo >> 16)); sub 1 0d 1 y sub %d,%c @@ -535,3 +545,13 @@ zacc 1 10 1 n zmac 1 30 1 n zmac + +raom 2a 3c 1 n + raom + /* Undocumented instruction, reset ALU saturation flag */ + st1 &= ~ST1_AOVM; + +saom 2a 3d 1 n + saom + /* Undocumented instruction, sets ALU saturation flag */ + st1 |= ST1_AOVM; diff --git a/src/devices/cpu/tms57002/tmsmake.py b/src/devices/cpu/tms57002/tmsmake.py index 19336061c11..3b5c4a0f6e2 100755 --- a/src/devices/cpu/tms57002/tmsmake.py +++ b/src/devices/cpu/tms57002/tmsmake.py @@ -23,11 +23,6 @@ TYPES = { "f": None, } -def expand_c(v): - fmt = ["%s", "(%s & 0xffff0000)", "(%s << 16)"][v["crm"]] - param = ["cmem[i->param]", "cmem[ca]"][v["cmode"]] - return fmt % param - def expand_d(v): index = ["(i->param + ", "(id + "][v["dmode"]] mask = ["ba0) & 0xff] << 8)", "ba1) & 0x1f] << 8)"][v["dbp"]] @@ -44,6 +39,8 @@ def expand_mv(v): c = ["", "s"][v["movm"]] return "check_macc_overflow_%d%s()" % (v["sfmo"], c) + +EXPAND_C = ["get_cmem(i->param)", "get_cmem(ca)"] EXPAND_WC = ["cmem[i->param] =", "cmem[ca] ="] @@ -70,14 +67,15 @@ def expand_wd1(v): return "dmem%d[" % v["dbp"] + index + mask WA2 = ( -" if(r < -0x80000000 || r > 0x7fffffff)\n" +" if(r < -2147483648 || r > 2147483647) {\n" " st1 |= ST1_AOV;\n" +" if(st1 & ST1_AOVM) r = std::max(int64_t(-2147483648), std::min(int64_t(2147483647), r));\n" +" }" " aacc = r;") - PDESC_EXPAND = { "a": lambda v: ["aacc", "(aacc << 7)"][v["sfao"]], - "c": expand_c, + "c": lambda v: EXPAND_C[v["cmode"]], "d": expand_d, "d24": expand_d24, "i": lambda v: "i->param", @@ -97,7 +95,7 @@ PDESC_EXPAND = { PDESC = { "a": (0, ["sfao"]), - "c": (0, ["cmode", "crm"]), + "c": (0, ["cmode"]), "d": (0, ["dmode", "dbp"]), "d24": (0, ["dmode", "dbp"]), "i": (0, []), @@ -115,7 +113,6 @@ VARIANTS = { "cmode": (2, "xmode(opcode, 'c', cs)" ), "dmode": (2, "xmode(opcode, 'd', cs)" ), "sfai": (2, "sfai(st1)"), - "crm": (3, "crm(st1)"), "dbp": (2, "dbp(st1)"), "sfao": (2, "sfao(st1)"), "sfmo": (4, "sfmo(st1)"), @@ -131,7 +128,6 @@ VARIANT_CANONICAL_ORDER = [ "cmode", "dmode", "sfai", - "crm", "dbp", "sfao", "sfmo", diff --git a/src/devices/sound/zsg2.cpp b/src/devices/sound/zsg2.cpp index 9e0eb89bdb8..de2b9dad263 100644 --- a/src/devices/sound/zsg2.cpp +++ b/src/devices/sound/zsg2.cpp @@ -6,9 +6,48 @@ Written by Olivier Galibert MAME conversion by R. Belmont Working emulation by The Talentuous Hands Of The Popularious hap - Improvements by superctr + Properly working emulation by superctr --------------------------------------------------------- + Register map: + 000-5fe : Channel specific registers + (high) (low) + +000 : xxxxxxxx -------- : Start address (low) + +000 : -------- xxxxxxxx : Unknown register (usually cleared) + +002 : xxxxxxxx -------- : Address page + : -------- xxxxxxxx : Start address (high) + +004 : -------- -------- : Unknown register (usually cleared) + +006 : -----x-- -------- : Unknown bit, always set + +008 : xxxxxxxx xxxxxxxx : Frequency + +00a : xxxxxxxx -------- : DSP ch 3 (right) output gain + : -------- xxxxxxxx : Loop address (low) + +00c : xxxxxxxx xxxxxxxx : End address + +00e : xxxxxxxx -------- : DSP ch 2 (Left) output gain + : -------- xxxxxxxx : Loop address (high) + +010 : xxxxxxxx xxxxxxxx : Filter time constant (latch) + +012 : -------- -------- : Unknown register (usually cleared) + +014 : xxxxxxxx xxxxxxxx : Volume (latch) + +016 : --x----- -------- : Key on status flag (only read) + +018 : xxxxxxxx xxxxxxxx : Filter time constant (Ramping target) + +01a : xxxxxxxx -------- : DSP ch 1 (chorus) output gain + : -------- xxxxxxxx : Filter ramping speed + +01c : xxxxxxxx xxxxxxxx : Volume (target) + +01e : xxxxxxxx -------- : DSP ch 0 (reverb) output gain + : -------- xxxxxxxx : Filter ramping speed + 600-604 : Key on flags (each bit corresponds to a channel) + 608-60c : Key off flags (each bit corresponds to a channel) + 618 : Unknown register (usually 0x5cbc is written) + 61a : Unknown register (usually 0x5cbc is written) + 620 : Unknown register (usually 0x0128 is written) + 628 : Unknown register (usually 0x0066 is written) + 630 : Unknown register (usually 0x0001 is written) + 638 : ROM readback address low + 63a : ROM readback address high + 63c : ROM readback word low + 63e : ROM readback word high + + --------------------------------------------------------- + Additional notes on the sample format, reverse-engineered by Olivier Galibert and David Haywood: @@ -45,12 +84,8 @@ --------------------------------------------------------- TODO: -- Filter behavior might not be perfect. -- Volume ramping probably behaves differently on hardware. -- hook up DSP, it's used for reverb and chorus effects. -- identify sample flags - * bassdrum in shikigam level 1 music is a good hint: it should be one octave - lower, indicating possible stereo sample, or base octave(like in ymf278) +- Filter and ramping behavior might not be perfect. +- sometimes clicking - memory reads out of range sometimes */ @@ -63,7 +98,6 @@ TODO: #include <cmath> #define EMPHASIS_CUTOFF_BASE 0x800 -#define EMPHASIS_CUTOFF_SHIFT 1 #define EMPHASIS_OUTPUT_SHIFT 15 // device type definition @@ -104,16 +138,18 @@ void zsg2_device::device_start() save_item(NAME(m_read_address)); // Generate the output gain table. Assuming -1dB per step for now. - for (int i = 0; i < 32; i++) + for (int i = 0, history=0; i < 32; i++) { double val = pow(10, -(31 - i) / 20.) * 65535.; gain_tab[i] = val; + gain_tab_frac[i] = val-history; + history = val; } for (int ch = 0; ch < 48; ch++) { save_item(NAME(m_chan[ch].v), ch); - save_item(NAME(m_chan[ch].is_playing), ch); + save_item(NAME(m_chan[ch].status), ch); save_item(NAME(m_chan[ch].cur_pos), ch); save_item(NAME(m_chan[ch].step_ptr), ch); save_item(NAME(m_chan[ch].step), ch); @@ -125,14 +161,12 @@ void zsg2_device::device_start() save_item(NAME(m_chan[ch].vol), ch); save_item(NAME(m_chan[ch].vol_initial), ch); save_item(NAME(m_chan[ch].vol_target), ch); - - save_item(NAME(m_chan[ch].emphasis_cutoff), ch); - save_item(NAME(m_chan[ch].emphasis_cutoff_initial), ch); - save_item(NAME(m_chan[ch].emphasis_cutoff_target), ch); + save_item(NAME(m_chan[ch].vol_delta), ch); save_item(NAME(m_chan[ch].output_cutoff), ch); save_item(NAME(m_chan[ch].output_cutoff_initial), ch); save_item(NAME(m_chan[ch].output_cutoff_target), ch); + save_item(NAME(m_chan[ch].output_cutoff_delta), ch); save_item(NAME(m_chan[ch].emphasis_filter_state), ch); save_item(NAME(m_chan[ch].output_filter_state), ch); @@ -141,6 +175,8 @@ void zsg2_device::device_start() save_item(NAME(m_chan[ch].samples), ch); } + + save_item(NAME(m_sample_count)); } //------------------------------------------------- @@ -159,6 +195,7 @@ void zsg2_device::device_reset() for (int ch = 0; ch < 48; ch++) for (int reg = 0; reg < 0x10; reg++) chan_w(ch, reg, 0); + m_sample_count = 0; #if 0 for (int i = 0; i < m_mem_blocks; i++) @@ -232,7 +269,7 @@ void zsg2_device::filter_samples(zchan *ch) // not sure if the filter works exactly this way, however I am pleased // with the output for now. - ch->emphasis_filter_state += (raw_samples[i]-(ch->emphasis_filter_state>>16)) * (EMPHASIS_CUTOFF_BASE - ch->emphasis_cutoff); + ch->emphasis_filter_state += (raw_samples[i]-(ch->emphasis_filter_state>>16)) * EMPHASIS_CUTOFF_BASE; ch->samples[i+1] = (ch->emphasis_filter_state) >> EMPHASIS_OUTPUT_SHIFT; } } @@ -243,6 +280,7 @@ void zsg2_device::filter_samples(zchan *ch) void zsg2_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples) { + // DSP is programmed to expect 24-bit samples! So we're not limiting to 16-bit here for (int i = 0; i < samples; i++) { int32_t mix[4] = {}; @@ -254,7 +292,7 @@ void zsg2_device::sound_stream_update(sound_stream &stream, stream_sample_t **in //auto & elem = m_chan[0]; { ch++; - if (!elem.is_playing) + if (!(elem.status & STATUS_ACTIVE)) continue; elem.step_ptr += elem.step; @@ -268,12 +306,11 @@ void zsg2_device::sound_stream_update(sound_stream &stream, stream_sample_t **in if ((elem.cur_pos + 1) >= elem.end_pos) { // end of sample - elem.is_playing = false; + elem.status &= ~STATUS_ACTIVE; continue; } } filter_samples(&elem); - //elem.samples = prepare_samples(elem.page | elem.cur_pos); } uint8_t sample_pos = elem.step_ptr >> 14 & 3; @@ -282,6 +319,7 @@ void zsg2_device::sound_stream_update(sound_stream &stream, stream_sample_t **in // linear interpolation (hardware certainly does something similar) sample = elem.samples[sample_pos]; sample += ((uint16_t)(elem.step_ptr<<2&0xffff) * (int16_t)(elem.samples[sample_pos+1] - sample))>>16; + sample = (sample * elem.vol) >> 16; // another filter... @@ -299,10 +337,13 @@ void zsg2_device::sound_stream_update(sound_stream &stream, stream_sample_t **in mix[output] += (output_sample * gain_tab[output_gain&0x1f]) >> 13; } - // Apply transitions (This is not accurate yet) - elem.vol = ramp(elem.vol, elem.vol_target); - elem.output_cutoff = ramp(elem.output_cutoff, elem.output_cutoff_target); - elem.emphasis_cutoff = ramp(elem.emphasis_cutoff, elem.emphasis_cutoff_target); + // Apply ramping every other update + // It's possible key on is handled on the other sample + if(m_sample_count & 1) + { + elem.vol = ramp(elem.vol, elem.vol_target, elem.vol_delta); + elem.output_cutoff = ramp(elem.output_cutoff, elem.output_cutoff_target, elem.output_cutoff_delta); + } } ch = 0; @@ -311,6 +352,7 @@ void zsg2_device::sound_stream_update(sound_stream &stream, stream_sample_t **in outputs[output][i] = mix[output]; } + m_sample_count++; } /******************************************************************************/ @@ -347,9 +389,9 @@ void zsg2_device::chan_w(int ch, int reg, uint16_t data) case 0x5: // lo byte: loop address low - // hi byte: right output gain (bypass DSP) + // hi byte: right output gain (direct) m_chan[ch].loop_pos = (m_chan[ch].loop_pos & 0xff00) | (data & 0xff); - m_chan[ch].output_gain[1] = data >> 8; + m_chan[ch].output_gain[3] = data >> 8; break; case 0x6: @@ -359,13 +401,13 @@ void zsg2_device::chan_w(int ch, int reg, uint16_t data) case 0x7: // lo byte: loop address high - // hi byte: left output gain (bypass DSP) + // hi byte: left output gain (direct) m_chan[ch].loop_pos = (m_chan[ch].loop_pos & 0x00ff) | (data << 8 & 0xff00); - m_chan[ch].output_gain[0] = data >> 8; + m_chan[ch].output_gain[2] = data >> 8; break; case 0x8: - // Filter cutoff (Direct) + // IIR lowpass time constant (initial, latched on key on) m_chan[ch].output_cutoff_initial = data; break; @@ -374,7 +416,7 @@ void zsg2_device::chan_w(int ch, int reg, uint16_t data) break; case 0xa: - // volume (Direct) + // volume (initial, latched on key on) m_chan[ch].vol_initial = data; break; @@ -384,27 +426,27 @@ void zsg2_device::chan_w(int ch, int reg, uint16_t data) break; case 0xc: - // filter gain ? + // IIR lowpass time constant (target) m_chan[ch].output_cutoff_target = data; break; case 0xd: - // hi byte: DSP Chorus volume - // lo byte: Emphasis filter time constant (direct value) - m_chan[ch].output_gain[3] = data >> 8; - m_chan[ch].emphasis_cutoff_initial = expand_reg(data & 0xff); + // hi byte: DSP channel 1 (chorus) gain + // lo byte: Filter ramping speed + m_chan[ch].output_gain[1] = data >> 8; + m_chan[ch].output_cutoff_delta = get_ramp(data & 0xff); break; case 0xe: - // volume (Target) + // volume target m_chan[ch].vol_target = data; break; case 0xf: - // hi byte: DSP Reverb volume - // lo byte: Emphasis filter time constant - m_chan[ch].output_gain[2] = data >> 8; - m_chan[ch].emphasis_cutoff_target = expand_reg(data & 0xff); + // hi byte: DSP channel 0 (reverb) gain + // lo byte: Volume ramping speed + m_chan[ch].output_gain[0] = data >> 8; + m_chan[ch].vol_delta = get_ramp(data & 0xff); break; default: @@ -419,7 +461,7 @@ uint16_t zsg2_device::chan_r(int ch, int reg) switch (reg) { case 0xb: // Only later games (taitogn) read this register... - return m_chan[ch].is_playing << 13; + return m_chan[ch].status; default: break; } @@ -427,37 +469,30 @@ uint16_t zsg2_device::chan_r(int ch, int reg) return m_chan[ch].v[reg]; } -// expand 8-bit reg to 16-bit value. This is used for the emphasis filter -// register. Not sure about how this works, the sound +// Convert ramping register value to something more usable. +// Upper 4 bits is a shift amount, lower 4 bits is a 2's complement value. +// Get ramp amount by sign extending the low 4 bits, XOR by 8, then +// shifting it by the upper 4 bits. // CPU uses a lookup table (stored in gdarius sound cpu ROM at 0x6332) to // calculate this value, for now I'm generating an opproximate inverse. -int16_t zsg2_device::expand_reg(uint8_t val) +int16_t zsg2_device::get_ramp(uint8_t val) { - static const signed char frac_tab[16] = {8,9,10,11,12,13,14,15,-15,-14,-13,-12,-11,-10,-9,-8}; - static const unsigned char shift_tab[8] = {1, 2, 3, 4, 5, 6, 7, 8}; - - return (frac_tab[val&0x0f] << shift_tab[val>>4])>>EMPHASIS_CUTOFF_SHIFT; + int16_t frac = val<<12; // sign extend + frac = ((frac>>12) ^ 8) << (val >> 4); + + return (frac >> 4); } -// ramp registers -// The CPU does not write often enough to make the transitions always sound -// smooth, so the sound chip probably helps by smoothing the changes. -// There are two sets of the volume and filter cutoff registers. -// At key on, the CPU writes to the "direct" registers, after that it will -// write to the "target" register instead. -inline int32_t zsg2_device::ramp(int32_t current, int32_t target) +inline uint16_t zsg2_device::ramp(uint16_t current, uint16_t target, int16_t delta) { - int32_t difference = abs(target-current); - difference -= 0x40; + int32_t rampval = current + delta; - if(difference < 0) - return target; - else if(target < current) - return target + difference; - else if(target > current) - return target - difference; - - return target; + if(delta < 0 && rampval < target) + rampval = target; + else if(delta >= 0 && rampval > target) + rampval = target; + + return rampval; } /******************************************************************************/ @@ -475,13 +510,12 @@ void zsg2_device::control_w(int reg, uint16_t data) if (data & (1 << i)) { int ch = base | i; - m_chan[ch].is_playing = true; + m_chan[ch].status |= STATUS_ACTIVE; m_chan[ch].cur_pos = m_chan[ch].start_pos; m_chan[ch].step_ptr = 0; m_chan[ch].emphasis_filter_state = 0; m_chan[ch].vol = m_chan[ch].vol_initial; m_chan[ch].output_cutoff = m_chan[ch].output_cutoff_initial; - m_chan[ch].emphasis_cutoff = m_chan[ch].emphasis_cutoff_initial; filter_samples(&m_chan[ch]); } } @@ -497,7 +531,7 @@ void zsg2_device::control_w(int reg, uint16_t data) if (data & (1 << i)) { int ch = base | i; - m_chan[ch].is_playing = false; + m_chan[ch].status &= ~STATUS_ACTIVE; } } break; diff --git a/src/devices/sound/zsg2.h b/src/devices/sound/zsg2.h index 10ebb799b6f..18ec9304775 100644 --- a/src/devices/sound/zsg2.h +++ b/src/devices/sound/zsg2.h @@ -1,5 +1,5 @@ // license:BSD-3-Clause -// copyright-holders:Olivier Galibert, R. Belmont, hap +// copyright-holders:Olivier Galibert, R. Belmont, hap, superctr /* ZOOM ZSG-2 custom wavetable synthesizer */ @@ -14,11 +14,6 @@ // INTERFACE CONFIGURATION MACROS //************************************************************************** -#define MCFG_ZSG2_ADD(_tag, _clock) \ - MCFG_DEVICE_ADD(_tag, ZSG2, _clock) -#define MCFG_ZSG2_REPLACE(_tag, _clock) \ - MCFG_DEVICE_REPLACE(_tag, ZSG2, _clock) - #define MCFG_ZSG2_EXT_READ_HANDLER(_devcb) \ downcast<zsg2_device &>(*device).set_ext_read_handler(DEVCB_##_devcb); @@ -46,11 +41,14 @@ protected: virtual void sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples) override; private: + const uint16_t STATUS_ACTIVE = 0x2000; + // 16 registers per channel, 48 channels struct zchan { uint16_t v[16]; - bool is_playing; + + uint16_t status; uint32_t cur_pos; uint32_t step_ptr; uint32_t step; @@ -62,14 +60,12 @@ private: uint16_t vol; uint16_t vol_initial; uint16_t vol_target; - - int16_t emphasis_cutoff; - int16_t emphasis_cutoff_initial; - int16_t emphasis_cutoff_target; + int16_t vol_delta; uint16_t output_cutoff; uint16_t output_cutoff_initial; uint16_t output_cutoff_target; + int16_t output_cutoff_delta; int32_t emphasis_filter_state; int32_t output_filter_state; @@ -81,7 +77,10 @@ private: }; uint16_t gain_tab[256]; + uint16_t gain_tab_frac[256]; + zchan m_chan[48]; + uint32_t m_sample_count; required_region_ptr<uint32_t> m_mem_base; uint32_t m_read_address; @@ -100,8 +99,8 @@ private: uint16_t control_r(int reg); int16_t *prepare_samples(uint32_t offset); void filter_samples(zchan *ch); - int16_t expand_reg(uint8_t val); - inline int32_t ramp(int32_t current, int32_t target); + int16_t get_ramp(uint8_t val); + inline uint16_t ramp(uint16_t current, uint16_t target, int16_t delta); }; DECLARE_DEVICE_TYPE(ZSG2, zsg2_device) |