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]
This commit is contained in:
superctr 2018-08-17 15:58:29 +02:00 committed by R. Belmont
parent 59c2a0536c
commit 3b57b7e90c
11 changed files with 334 additions and 153 deletions

View File

@ -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));
}

View File

@ -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 {

View File

@ -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;
}

View File

@ -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;

View File

@ -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",

View File

@ -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;

View File

@ -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)

View File

@ -22,7 +22,12 @@ and a Zoom Corp. ZFX-2 DSP instead of the TMS57002.
TODO:
- add DSP, sound is tinny without it
- Raycrisis song 9 gets cut off due to clipping. Possible DSP emulation bug,
or just have to change the volumes. in the sound test, it will start to cut
at about 55%. and the timbre doesn't sound right anyway.
- check DSP behavior
- Implement the ramping control registers in zsg2.cpp
***************************************************************************/
@ -38,9 +43,11 @@ DEFINE_DEVICE_TYPE(TAITO_ZOOM, taito_zoom_device, "taito_zoom", "Taito Zoom Soun
// taito_zoom_device - constructor
//-------------------------------------------------
taito_zoom_device::taito_zoom_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: device_t(mconfig, TAITO_ZOOM, tag, owner, clock),
taito_zoom_device::taito_zoom_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
device_t(mconfig, TAITO_ZOOM, tag, owner, clock),
device_mixer_interface(mconfig, *this, 2),
m_soundcpu(*this, "mn10200"),
m_tms57002(*this, "tms57002"),
m_zsg2(*this, "zsg2"),
m_reg_address(0),
m_tms_ctrl(0),
@ -71,6 +78,7 @@ void taito_zoom_device::device_reset()
m_reg_address = 0;
m_zsg2->reset();
m_tms57002->reset();
}
@ -98,15 +106,20 @@ READ8_MEMBER(taito_zoom_device::tms_ctrl_r)
WRITE8_MEMBER(taito_zoom_device::tms_ctrl_w)
{
#if 0
tms57002_reset_w(data & 4);
tms57002_cload_w(data & 2);
tms57002_pload_w(data & 1);
#endif
// According to the TMS57002 manual, reset should NOT be set low during the data transfer.
//m_tms57002->set_input_line(INPUT_LINE_RESET, data & 0x10 ? CLEAR_LINE : ASSERT_LINE);
m_tms57002->cload_w(data & 2);
m_tms57002->pload_w(data & 1);
// Other bits unknown (0x9F at most games)
m_tms_ctrl = data;
}
void taito_zoom_device::update_status_pin(int state)
{
printf("inside callback set status to %d\n",state);
m_soundcpu->set_input_line(1, state);
machine().scheduler().synchronize(); // the fix to all problems
}
void taito_zoom_device::taitozoom_mn_map(address_map &map)
{
@ -116,11 +129,17 @@ void taito_zoom_device::taitozoom_mn_map(address_map &map)
map(0x080000, 0x0fffff).rom().region("mn10200", 0);
}
map(0x400000, 0x41ffff).ram();
map(0x800000, 0x8007ff).rw("zsg2", FUNC(zsg2_device::read), FUNC(zsg2_device::write));
map(0xc00000, 0xc00001).ram(); // TMS57002 comms
map(0x800000, 0x8007ff).rw(m_zsg2, FUNC(zsg2_device::read), FUNC(zsg2_device::write));
map(0xc00000, 0xc00000).rw(m_tms57002, FUNC(tms57002_device::data_r), FUNC(tms57002_device::data_w)); // TMS57002 comms
map(0xe00000, 0xe000ff).rw(FUNC(taito_zoom_device::shared_ram_r), FUNC(taito_zoom_device::shared_ram_w)); // M66220FP for comms with maincpu
}
#ifdef USE_DSP
void taito_zoom_device::tms57002_map(address_map &map)
{
map(0x00000, 0x3ffff).ram();
}
#endif
/***************************************************************************
@ -148,14 +167,14 @@ WRITE16_MEMBER(taito_zoom_device::reg_data_w)
// zsg2+dsp global volume left
if (data & 0xc0c0)
popmessage("ZOOM gain L %04X, contact MAMEdev", data);
m_zsg2->set_output_gain(0, (data & 0x3f) / 63.0);
m_tms57002->set_output_gain(2, (data & 0x3f) / 63.0);
break;
case 0x05:
// zsg2+dsp global volume right
if (data & 0xc0c0)
popmessage("ZOOM gain R %04X, contact MAMEdev", data);
m_zsg2->set_output_gain(1, (data & 0x3f) / 63.0);
m_tms57002->set_output_gain(3, (data & 0x3f) / 63.0);
break;
default:
@ -184,13 +203,27 @@ MACHINE_CONFIG_START(taito_zoom_device::device_add_mconfig)
MCFG_QUANTUM_TIME(attotime::from_hz(60000))
MCFG_ZSG2_ADD("zsg2", XTAL(25'000'000))
TMS57002(config, m_tms57002, XTAL(25'000'000)/2);
#ifdef USE_DSP
//m_tms57002->empty_callback().set_inputline(m_soundcpu, MN10200_IRQ1, m_tms57002->empty_r()); /*.invert();*/
m_tms57002->empty_callback().set_inputline(m_soundcpu, MN10200_IRQ1).invert();
// we assume the parent machine has created lspeaker/rspeaker
MCFG_SOUND_ROUTE(0, "^lspeaker", 1.0) // bypass DSP
MCFG_SOUND_ROUTE(1, "^rspeaker", 1.0)
m_tms57002->set_addrmap(AS_DATA, &taito_zoom_device::tms57002_map);
m_tms57002->add_route(2, *this, 1.0, AUTO_ALLOC_INPUT, 0);
m_tms57002->add_route(3, *this, 1.0, AUTO_ALLOC_INPUT, 1);
#else // Unsupported opcode issue
m_tms57002->set_disable();
#endif
//MCFG_SOUND_ROUTE(2, "^lspeaker", 1.0) // DSP reverb
//MCFG_SOUND_ROUTE(3, "^rspeaker", 1.0) // DSP chorus
ZSG2(config, m_zsg2, XTAL(25'000'000));
#ifdef USE_DSP
m_zsg2->add_route(0, *m_tms57002, 0.5, 0); // reverb effect
m_zsg2->add_route(1, *m_tms57002, 0.5, 1); // chorus effect
m_zsg2->add_route(2, *m_tms57002, 0.5, 2); // left direct
m_zsg2->add_route(3, *m_tms57002, 0.5, 3); // right direct
#else
m_zsg2->add_route(2, *this, 1.0, AUTO_ALLOC_INPUT, 0);
m_zsg2->add_route(3, *this, 1.0, AUTO_ALLOC_INPUT, 1);
#endif
MACHINE_CONFIG_END

View File

@ -14,12 +14,15 @@
#include "cpu/tms57002/tms57002.h"
#include "sound/zsg2.h"
#define USE_DSP // Uncomment when DSP emulation is working
class taito_zoom_device : public device_t
class taito_zoom_device : public device_t, public device_mixer_interface
{
public:
taito_zoom_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
static constexpr feature_type imperfect_features() { return feature::SOUND; }
taito_zoom_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock = 0);
DECLARE_WRITE16_MEMBER(sound_irq_w);
DECLARE_READ16_MEMBER(sound_irq_r);
@ -34,6 +37,9 @@ public:
void set_use_flash() { m_use_flash = true; }
void taitozoom_mn_map(address_map &map);
#ifdef USE_DSP
void tms57002_map(address_map &map);
#endif
protected:
// device-level overrides
virtual void device_start() override;
@ -43,6 +49,7 @@ protected:
private:
// inherited devices/pointers
required_device<mn10200_device> m_soundcpu;
required_device<tms57002_device> m_tms57002;
required_device<zsg2_device> m_zsg2;
// internal state
@ -50,6 +57,8 @@ private:
uint8_t m_tms_ctrl;
bool m_use_flash;
std::unique_ptr<uint8_t[]> m_snd_shared_ram;
void update_status_pin(int state);
};
DECLARE_DEVICE_TYPE(TAITO_ZOOM, taito_zoom_device)

View File

@ -744,8 +744,10 @@ MACHINE_CONFIG_START(taitogn_state::coh3002t)
MCFG_SOUND_ROUTE(0, "lspeaker", 0.45)
MCFG_SOUND_ROUTE(1, "rspeaker", 0.45)
MCFG_TAITO_ZOOM_ADD("taito_zoom")
MCFG_TAITO_ZOOM_USE_FLASH
TAITO_ZOOM(config, m_zoom);
m_zoom->set_use_flash();
m_zoom->add_route(0, "lspeaker", 1.0);
m_zoom->add_route(1, "rspeaker", 1.0);
MCFG_DEVICE_MODIFY("taito_zoom:zsg2")
MCFG_ZSG2_EXT_READ_HANDLER(READ32(*this, taitogn_state, zsg2_ext_r))

View File

@ -1206,7 +1206,9 @@ MACHINE_CONFIG_START(zn_state::coh1000tb)
MCFG_SOUND_ROUTE(0, "lspeaker", 0.45)
MCFG_SOUND_ROUTE(1, "rspeaker", 0.45)
MCFG_TAITO_ZOOM_ADD("taito_zoom")
TAITO_ZOOM(config, m_zoom);
m_zoom->add_route(0, "lspeaker", 1.0);
m_zoom->add_route(1, "rspeaker", 1.0);
MACHINE_CONFIG_END
MACHINE_CONFIG_START(zn_state::coh1002tb)
@ -1228,7 +1230,9 @@ MACHINE_CONFIG_START(zn_state::coh1002tb)
MCFG_SOUND_ROUTE(0, "lspeaker", 0.45)
MCFG_SOUND_ROUTE(1, "rspeaker", 0.45)
MCFG_TAITO_ZOOM_ADD("taito_zoom")
TAITO_ZOOM(config, m_zoom);
m_zoom->add_route(0, "lspeaker", 1.0);
m_zoom->add_route(1, "rspeaker", 1.0);
MACHINE_CONFIG_END
/*