summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices
diff options
context:
space:
mode:
author cam900 <dbtlrchl@naver.com>2021-02-23 21:12:22 +0900
committer GitHub <noreply@github.com>2021-02-23 23:12:22 +1100
commit0a2caa105dafac435294f9f33fa56c35c299d004 (patch)
treeefae02f1dfabf0dc03183443b934387bc35dd917 /src/devices
parente6caf6fb9c697bc2b4c95d67111de54e7029eb8d (diff)
Refactored Seta sound, adding preliminary ST0032 sound support. (#7800)
* Renamed sound/nile.cpp to sound/setapcm.cpp. * Added preliminary support for 16-voice ST0032 variant. * jclub2.cpp: Hooked up ST0032 sound. * jclub2.cpp, srmp6.cpp: Derive sound clocks from crystal frequencies.
Diffstat (limited to 'src/devices')
-rw-r--r--src/devices/sound/nile.cpp233
-rw-r--r--src/devices/sound/nile.h48
-rw-r--r--src/devices/sound/setapcm.cpp429
-rw-r--r--src/devices/sound/setapcm.h103
4 files changed, 532 insertions, 281 deletions
diff --git a/src/devices/sound/nile.cpp b/src/devices/sound/nile.cpp
deleted file mode 100644
index ef05b670e57..00000000000
--- a/src/devices/sound/nile.cpp
+++ /dev/null
@@ -1,233 +0,0 @@
-// license:BSD-3-Clause
-// copyright-holders:Tomasz Slanina
-/************************************
- Seta custom Nile ST-0026 chip
- sound emulation by Tomasz Slanina
- based on ST-0016 emulation
-
-8 voices, 16 words of config data for each:
-
- 00
- 01 - sptr ?? (always 0)
- 02 - sptr LO
- 03 - sptr HI
- 04
- 05 - flags? 00000000 0000?L0? - bit 0 loops, other bits appear to be not used by the chip
- 06 - freq
- 07 - lsptr LO
- 08
- 09 - lsptr HI
- 0a - leptr LO
- 0b - leptr HI
- 0c - eptr LO
- 0d - eptr HI
- 0e - vol R
- 0f - vol L
-
-************************************/
-
-#include "emu.h"
-#include "nile.h"
-
-enum
-{
- NILE_REG_UNK0=0,
- NILE_REG_SPTR_TOP,
- NILE_REG_SPTR_LO,
- NILE_REG_SPTR_HI,
- NILE_REG_UNK_4,
- NILE_REG_FLAGS,
- NILE_REG_FREQ,
- NILE_REG_LSPTR_LO,
- MILE_REG_UNK_8,
- NILE_REG_LSPTR_HI,
- NILE_REG_LEPTR_LO,
- NILE_REG_LEPTR_HI,
- NILE_REG_EPTR_LO,
- NILE_REG_EPTR_HI,
- NILE_REG_VOL_R,
- NILE_REG_VOL_L
-};
-
-
-DEFINE_DEVICE_TYPE(NILE, nile_device, "nile", "Seta ST-0026 NiLe")
-
-nile_device::nile_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
- : device_t(mconfig, NILE, tag, owner, clock),
- device_sound_interface(mconfig, *this),
- m_stream(nullptr),
- m_sound_ram(*this, DEVICE_SELF),
- m_ctrl(0)
-{
-}
-
-
-//-------------------------------------------------
-// device_start - device-specific startup
-//-------------------------------------------------
-
-void nile_device::device_start()
-{
- m_stream = stream_alloc(0, 2, 44100);
- save_item(NAME(m_sound_regs));
- save_item(NAME(m_vpos));
- save_item(NAME(m_frac));
- save_item(NAME(m_lponce));
- save_item(NAME(m_ctrl));
-}
-
-
-//-------------------------------------------------
-// sound_stream_update - handle update requests
-// for our sound stream
-//-------------------------------------------------
-
-void nile_device::sound_stream_update(sound_stream &stream, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs)
-{
- uint8_t *sound_ram = &m_sound_ram[0];
- int v, i, snum;
- uint16_t *slot;
- int32_t mix[4800*2];
- int32_t *mixp;
- int16_t sample;
- int sptr, eptr, freq, lsptr, leptr;
-
- lsptr=leptr=0;
-
- sound_assert(outputs[0].samples() * 2 < std::size(mix));
- std::fill_n(&mix[0], outputs[0].samples()*2, 0);
-
- for (v = 0; v < NILE_VOICES; v++)
- {
- slot = &m_sound_regs[v * 16];
-
- if (m_ctrl&(1<<v))
- {
- mixp = &mix[0];
-
- sptr = slot[NILE_REG_SPTR_HI]<<16 | slot[NILE_REG_SPTR_LO];
- eptr = slot[NILE_REG_EPTR_HI]<<16 | slot[NILE_REG_EPTR_LO];
-
- freq=slot[NILE_REG_FREQ]*14;
- lsptr = slot[NILE_REG_LSPTR_HI]<<16 | slot[NILE_REG_LSPTR_LO];
- leptr = slot[NILE_REG_LEPTR_HI]<<16 | slot[NILE_REG_LEPTR_LO];
-
- for (snum = 0; snum < outputs[0].samples(); snum++)
- {
- sample = sound_ram[sptr + m_vpos[v]]<<8;
-
- *mixp++ += (sample * (int32_t)slot[NILE_REG_VOL_R]) >> 16;
- *mixp++ += (sample * (int32_t)slot[NILE_REG_VOL_L]) >> 16;
-
- m_frac[v] += freq;
- m_vpos[v] += m_frac[v]>>16;
- m_frac[v] &= 0xffff;
-
- // stop if we're at the end
- if (m_lponce[v])
- {
- // we've looped once, check loop end rather than sample end
- if ((m_vpos[v] + sptr) >= leptr)
- {
- m_vpos[v] = (lsptr - sptr);
- }
- }
- else
- {
- // not looped yet, check sample end
- if ((m_vpos[v] + sptr) >= eptr)
- {
- // code at 11d8c:
- // if bit 2 (0x4) is set, check if loop start = loop end.
- // if they are equal, clear bit 0 and don't set the loop start/end
- // registers in the NiLe. if they aren't, set bit 0 and set
- // the loop start/end registers in the NiLe.
- if ((slot[NILE_REG_FLAGS] & 0x5) == 0x5)
- {
- m_vpos[v] = (lsptr - sptr);
- m_lponce[v] = 1;
- }
- else
- {
- m_ctrl &= ~(1<<v);
- m_vpos[v] = (eptr - sptr);
- m_frac[v] = 0;
- }
-
- }
- }
- }
- }
- }
- mixp = &mix[0];
- for (i = 0; i < outputs[0].samples(); i++)
- {
- outputs[0].put_int(i, *mixp++, 32768 * 16);
- outputs[1].put_int(i, *mixp++, 32768 * 16);
- }
-}
-
-
-void nile_device::nile_sndctrl_w(offs_t offset, uint16_t data, uint16_t mem_mask)
-{
- uint16_t ctrl=m_ctrl;
-
- m_stream->update();
-
- COMBINE_DATA(&m_ctrl);
-
-// logerror("CTRL: %04x -> %04x %s\n", ctrl, m_ctrl, machine().describe_context());
-
- ctrl^=m_ctrl;
-}
-
-
-uint16_t nile_device::nile_sndctrl_r()
-{
- m_stream->update();
- return m_ctrl;
-}
-
-
-uint16_t nile_device::nile_snd_r(offs_t offset)
-{
- int reg=offset&0xf;
-
- m_stream->update();
-
- if(reg==2 || reg==3)
- {
- int slot=offset/16;
- int sptr = ((m_sound_regs[slot*16+3]<<16)|m_sound_regs[slot*16+2])+m_vpos[slot];
-
- if(reg==2)
- {
- return sptr&0xffff;
- }
- else
- {
- return sptr>>16;
- }
- }
- return m_sound_regs[offset];
-}
-
-
-void nile_device::nile_snd_w(offs_t offset, uint16_t data, uint16_t mem_mask)
-{
- int v, r;
-
- m_stream->update();
-
- COMBINE_DATA(&m_sound_regs[offset]);
-
- v = offset / 16;
- r = offset % 16;
-
- if ((r == 2) || (r == 3))
- {
- m_vpos[v] = m_frac[v] = m_lponce[v] = 0;
- }
-
- //logerror("v%02d: %04x to reg %02d (PC=%x)\n", v, m_sound_regs[offset], r, machine().describe_context());
-}
diff --git a/src/devices/sound/nile.h b/src/devices/sound/nile.h
deleted file mode 100644
index 61add38d44e..00000000000
--- a/src/devices/sound/nile.h
+++ /dev/null
@@ -1,48 +0,0 @@
-// license:BSD-3-Clause
-// copyright-holders:Tomasz Slanina
-#ifndef MAME_SOUND_NILE_H
-#define MAME_SOUND_NILE_H
-
-#pragma once
-
-
-//**************************************************************************
-// TYPE DEFINITIONS
-//**************************************************************************
-
-// ======================> nile_device
-
-class nile_device : public device_t,
- public device_sound_interface
-{
-public:
- nile_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
-
-protected:
- // device-level overrides
- virtual void device_start() override;
-
- // sound stream update overrides
- virtual void sound_stream_update(sound_stream &stream, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs) override;
-
-public:
- void nile_snd_w(offs_t offset, uint16_t data, uint16_t mem_mask = ~0);
- uint16_t nile_snd_r(offs_t offset);
- void nile_sndctrl_w(offs_t offset, uint16_t data, uint16_t mem_mask = ~0);
- uint16_t nile_sndctrl_r();
-
-private:
- static constexpr unsigned NILE_VOICES = 8;
-
- sound_stream *m_stream;
- required_region_ptr<uint8_t> m_sound_ram;
- uint16_t m_sound_regs[0x80];
- int m_vpos[NILE_VOICES];
- int m_frac[NILE_VOICES];
- int m_lponce[NILE_VOICES];
- uint16_t m_ctrl;
-};
-
-DECLARE_DEVICE_TYPE(NILE, nile_device)
-
-#endif // MAME_SOUND_NILE_H
diff --git a/src/devices/sound/setapcm.cpp b/src/devices/sound/setapcm.cpp
new file mode 100644
index 00000000000..a97e0ded106
--- /dev/null
+++ b/src/devices/sound/setapcm.cpp
@@ -0,0 +1,429 @@
+// license:BSD-3-Clause
+// copyright-holders:Tomasz Slanina,cam900
+/************************************
+ Seta PCM emulation
+ sound emulation by Tomasz Slanina
+ based on ST-0016 emulation
+
+ used by
+ - ST-0026 NiLe (srmp6, 8 voices)
+ - ST-0032 (jclub2, 16 voices)
+ - Capcom CPS3 sound hardware has similarity?
+
+ Register Format (32 byte per voices)
+
+ 00-1f: Voice 0
+ Offset Bit Description
+ fedcba98 76543210
+ 04 xxxxxxxx xxxxxxxx Start position LSB
+ 06 xxxxxxxx xxxxxxxx Start position MSB
+ 0a -------- ----xxx- Used but unknown
+ -------- -----x-- See below for NiLe specific? notes
+ -------- -------x Loop enable
+ 0c xxxxxxxx xxxxxxxx Frequency
+ 0e xxxxxxxx xxxxxxxx Loop Start position LSB
+ 12 xxxxxxxx xxxxxxxx Loop Start position MSB
+ 14 xxxxxxxx xxxxxxxx Loop End position LSB
+ 16 xxxxxxxx xxxxxxxx Loop End position MSB
+ 18 xxxxxxxx xxxxxxxx End position LSB
+ 1a xxxxxxxx xxxxxxxx End position MSB
+ 1c xxxxxxxx xxxxxxxx Right Volume
+ 1e xxxxxxxx xxxxxxxx Left Volume
+
+ 20-3f: Voice 1
+ ...
+ e0-ff: Voice 7
+
+ 100: Keyon/off, Bit 0-7 means Voice 0-7
+ 110: Used but unknown
+
+ below for 16 voice configurations:
+ 100-11f: Voice 8
+ 120-13f: Voice 9
+ ...
+ 1e0-1ff: Voice 15
+
+ 200: Keyon/off, Bit 0-15 means Voice 0-15
+ 210: Used but unknown
+
+ Other registers are unknown/unused
+
+ TODO:
+ - Verify loop and flag bit behavior from real hardware
+
+************************************/
+
+#include "emu.h"
+#include "setapcm.h"
+
+// constants
+template<unsigned MaxVoices, unsigned Divider>
+constexpr unsigned setapcm_device<MaxVoices, Divider>::MAX_VOICES;
+template<unsigned MaxVoices, unsigned Divider>
+constexpr unsigned setapcm_device<MaxVoices, Divider>::CLOCK_DIVIDER;
+
+// device type definition
+DEFINE_DEVICE_TYPE(NILE_SOUND, nile_sound_device, "nile_sound", "Seta ST-0026 NiLe (Sound)")
+DEFINE_DEVICE_TYPE(ST0032_SOUND, st0032_sound_device, "st0032_sound", "Seta ST-0032 (Sound)")
+
+//**************************************************************************
+// LIVE DEVICE
+//**************************************************************************
+
+//-------------------------------------------------
+// setapcm_device - constructor
+//-------------------------------------------------
+
+template<unsigned MaxVoices, unsigned Divider>
+setapcm_device<MaxVoices, Divider>::setapcm_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, u32 clock)
+ : device_t(mconfig, type, tag, owner, clock)
+ , device_sound_interface(mconfig, *this)
+ , device_rom_interface(mconfig, *this)
+ , m_stream(nullptr)
+ , m_keyctrl(0)
+{
+}
+
+nile_sound_device::nile_sound_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock)
+ : setapcm_device<8, 160>(mconfig, NILE_SOUND, tag, owner, clock)
+{
+}
+
+st0032_sound_device::st0032_sound_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock)
+ : setapcm_device<16, 384>(mconfig, ST0032_SOUND, tag, owner, clock)
+{
+}
+
+//-------------------------------------------------
+// device_start - device-specific startup
+//-------------------------------------------------
+
+template<unsigned MaxVoices, unsigned Divider>
+void setapcm_device<MaxVoices, Divider>::device_start()
+{
+ // allocate stream
+ m_stream = stream_alloc(0, 2, clock() / CLOCK_DIVIDER);
+
+ // set host device to each voices
+ for (auto & elem : m_voice)
+ elem.m_host = this;
+
+ save_item(STRUCT_MEMBER(m_voice, m_start));
+ save_item(STRUCT_MEMBER(m_voice, m_flags));
+ save_item(STRUCT_MEMBER(m_voice, m_freq));
+ save_item(STRUCT_MEMBER(m_voice, m_lpstart));
+ save_item(STRUCT_MEMBER(m_voice, m_lpend));
+ save_item(STRUCT_MEMBER(m_voice, m_end));
+ save_item(STRUCT_MEMBER(m_voice, m_vol_r));
+ save_item(STRUCT_MEMBER(m_voice, m_vol_l));
+ save_item(STRUCT_MEMBER(m_voice, m_pos));
+ save_item(STRUCT_MEMBER(m_voice, m_frac));
+ save_item(STRUCT_MEMBER(m_voice, m_lponce));
+ save_item(STRUCT_MEMBER(m_voice, m_keyon));
+ save_item(STRUCT_MEMBER(m_voice, m_out));
+
+ save_item(NAME(m_keyctrl));
+}
+
+//-------------------------------------------------
+// device_clock_changed - called if the clock
+// changes
+//-------------------------------------------------
+
+template<unsigned MaxVoices, unsigned Divider>
+void setapcm_device<MaxVoices, Divider>::device_clock_changed()
+{
+ m_stream->set_sample_rate(clock() / CLOCK_DIVIDER);
+}
+
+//-------------------------------------------------
+// sound_stream_update - handle update requests
+// for our sound stream
+//-------------------------------------------------
+
+template<unsigned MaxVoices, unsigned Divider>
+void setapcm_device<MaxVoices, Divider>::sound_stream_update(sound_stream &stream, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs)
+{
+ outputs[0].fill(0);
+ outputs[1].fill(0);
+
+ for (int sampleind = 0; sampleind < outputs[0].samples(); sampleind++)
+ {
+ for (int v = 0; v < MAX_VOICES; v++)
+ {
+ // check if voice is activated
+ if (m_voice[v].update())
+ {
+ outputs[0].add_int(sampleind, (m_voice[v].m_out * m_voice[v].m_vol_l) >> 16, 32768 * MAX_VOICES);
+ outputs[1].add_int(sampleind, (m_voice[v].m_out * m_voice[v].m_vol_r) >> 16, 32768 * MAX_VOICES);
+ }
+ }
+ }
+}
+
+//-------------------------------------------------
+// rom_bank_updated - the rom bank has changed
+//-------------------------------------------------
+
+template<unsigned MaxVoices, unsigned Divider>
+void setapcm_device<MaxVoices, Divider>::rom_bank_updated()
+{
+ m_stream->update();
+}
+
+//-------------------------------------------------
+// update - update single voice
+//-------------------------------------------------
+
+template<unsigned MaxVoices, unsigned Divider>
+bool setapcm_device<MaxVoices, Divider>::voice_t::update()
+{
+ if (m_keyon)
+ {
+ // fetch sample
+ m_out = s16(s8(m_host->read_byte(m_pos))) << 8;
+
+ // advance
+ m_frac += m_freq;
+ m_pos += m_frac >> 12;
+ m_frac &= 0xfff;
+
+ // stop if we're at the end
+ if (m_lponce)
+ {
+ // we've looped once, check loop end rather than sample end
+ if (m_pos >= m_lpend)
+ {
+ m_pos = m_lpstart;
+ }
+ }
+ else
+ {
+ // not looped yet, check sample end
+ if (m_pos >= m_end)
+ {
+ // code at 11d8c:
+ // if bit 2 (0x4) is set, check if loop start = loop end.
+ // if they are equal, clear bit 0 and don't set the loop start/end
+ // registers in the NiLe. if they aren't, set bit 0 and set
+ // the loop start/end registers in the NiLe.
+ // TODO: ST-0032 has same behavior?
+ if (BIT(m_flags, 0))
+ {
+ m_pos = m_lpstart;
+ m_lponce = true;
+ }
+ else
+ {
+ m_keyon = false;
+ }
+ }
+ }
+ return true;
+ }
+ // clear output
+ m_out = 0;
+ return false;
+}
+
+//-------------------------------------------------
+// keyon - set keyon flag for single voice
+//-------------------------------------------------
+
+template<unsigned MaxVoices, unsigned Divider>
+void setapcm_device<MaxVoices, Divider>::voice_t::keyon()
+{
+ m_keyon = true;
+ m_pos = m_start;
+ m_frac = 0;
+ m_lponce = false;
+}
+
+//-------------------------------------------------
+// keyoff - set keyoff flag for single voice
+//-------------------------------------------------
+
+template<unsigned MaxVoices, unsigned Divider>
+void setapcm_device<MaxVoices, Divider>::voice_t::keyoff()
+{
+ m_keyon = false;
+}
+
+//-------------------------------------------------
+// reg_r - read single voice registers
+//-------------------------------------------------
+
+template<unsigned MaxVoices, unsigned Divider>
+u16 setapcm_device<MaxVoices, Divider>::voice_t::reg_r(offs_t reg)
+{
+ u16 ret = 0;
+ switch (reg & 0xf)
+ {
+ case 0x02: // Current position LSB
+ ret = m_pos & 0xffff;
+ break;
+ case 0x03: // Current position MSB
+ ret = m_pos >> 16;
+ break;
+ case 0x05: // Flags
+ ret = m_flags;
+ break;
+ case 0x06: // Frequency
+ ret = m_freq;
+ break;
+ case 0x07: // Loop start position LSB
+ ret = m_lpstart & 0xffff;
+ break;
+ case 0x09: // Loop start position MSB
+ ret = m_lpstart >> 16;
+ break;
+ case 0x0a: // Loop end position LSB
+ ret = m_lpend & 0xffff;
+ break;
+ case 0x0b: // Loop end position MSB
+ ret = m_lpend >> 16;
+ break;
+ case 0x0c: // End position LSB
+ ret = m_end & 0xffff;
+ break;
+ case 0x0d: // End position MSB
+ ret = m_end >> 16;
+ break;
+ case 0x0e: // Right volume
+ ret = m_vol_r;
+ break;
+ case 0x0f: // Left volume
+ ret = m_vol_l;
+ break;
+ }
+ return ret;
+}
+
+//-------------------------------------------------
+// reg_w - write single voice registers
+//-------------------------------------------------
+
+template<unsigned MaxVoices, unsigned Divider>
+void setapcm_device<MaxVoices, Divider>::voice_t::reg_w(offs_t reg, u16 data, u16 mem_mask)
+{
+ switch (reg & 0xf)
+ {
+ case 0x02: // Start position LSB, also affected current position?
+ m_start = m_pos = (m_start & ~mem_mask) | (data & mem_mask);
+ break;
+ case 0x03: // Start position MSB
+ m_start = m_pos = (m_start & ~(u32(mem_mask) << 16)) | (u32(data & mem_mask) << 16);
+ break;
+ case 0x05: // Flags
+ COMBINE_DATA(&m_flags);
+ break;
+ case 0x06: // Frequency
+ COMBINE_DATA(&m_freq);
+ break;
+ case 0x07: // Loop start position LSB
+ m_lpstart = (m_lpstart & ~mem_mask) | (data & mem_mask);
+ break;
+ case 0x09: // Loop start position MSB
+ m_lpstart = (m_lpstart & ~(u32(mem_mask) << 16)) | (u32(data & mem_mask) << 16);
+ break;
+ case 0x0a: // Loop end position LSB
+ m_lpend = (m_lpend & ~mem_mask) | (data & mem_mask);
+ break;
+ case 0x0b: // Loop end position MSB
+ m_lpend = (m_lpend & ~(u32(mem_mask) << 16)) | (u32(data & mem_mask) << 16);
+ break;
+ case 0x0c: // End position LSB
+ m_end = (m_end & ~mem_mask) | (data & mem_mask);
+ break;
+ case 0x0d: // End position MSB
+ m_end = (m_end & ~(u32(mem_mask) << 16)) | (u32(data & mem_mask) << 16);
+ break;
+ case 0x0e: // Right volume
+ COMBINE_DATA(&m_vol_r);
+ break;
+ case 0x0f: // Left volume
+ COMBINE_DATA(&m_vol_l);
+ break;
+ }
+}
+
+//-------------------------------------------------
+// snd_w - write each voice registers
+//-------------------------------------------------
+
+template<unsigned MaxVoices, unsigned Divider>
+void setapcm_device<MaxVoices, Divider>::snd_w(offs_t offset, u16 data, u16 mem_mask)
+{
+ m_stream->update();
+
+ const int v = (offset >> 4);
+
+ if (v >= MAX_VOICES)
+ return;
+
+ m_voice[v].reg_w(offset & 0xf, data, mem_mask);
+ //logerror("v%02d: %04x & %04x to reg %02d (PC=%x)\n", v, data, mem_mask, r, machine().describe_context());
+}
+
+
+//-------------------------------------------------
+// snd_r - read each voice registers
+//-------------------------------------------------
+
+template<unsigned MaxVoices, unsigned Divider>
+u16 setapcm_device<MaxVoices, Divider>::snd_r(offs_t offset)
+{
+ const int v = (offset >> 4);
+
+ if (v >= MAX_VOICES)
+ return 0;
+
+ m_stream->update();
+
+ return m_voice[v].reg_r(offset & 0xf);
+}
+
+
+//-------------------------------------------------
+// key_w - set keyon/off flags for each voices
+//-------------------------------------------------
+
+template<unsigned MaxVoices, unsigned Divider>
+void setapcm_device<MaxVoices, Divider>::key_w(offs_t offset, u16 data, u16 mem_mask)
+{
+ const u16 prev = m_keyctrl;
+
+ m_stream->update();
+
+ COMBINE_DATA(&m_keyctrl);
+
+ //logerror("KEYCTRL: %04x -> %04x %s\n", prev, m_keyctrl, machine().describe_context());
+
+ for (int v = 0; v < MAX_VOICES; v++)
+ {
+ if (BIT(m_keyctrl, v) && (!(BIT(prev, v)))) // keyon
+ {
+ m_voice[v].keyon();
+ }
+ else if ((!(BIT(m_keyctrl, v))) && BIT(prev, v)) // keyoff
+ {
+ m_voice[v].keyoff();
+ }
+ }
+}
+
+
+//-------------------------------------------------
+// key_r - get keyon/off status from each voices
+//-------------------------------------------------
+
+template<unsigned MaxVoices, unsigned Divider>
+u16 setapcm_device<MaxVoices, Divider>::key_r()
+{
+ m_stream->update();
+ return m_keyctrl;
+}
+
+// template class definition
+template class setapcm_device<8, 160>;
+template class setapcm_device<16, 384>;
diff --git a/src/devices/sound/setapcm.h b/src/devices/sound/setapcm.h
new file mode 100644
index 00000000000..8c406c7076f
--- /dev/null
+++ b/src/devices/sound/setapcm.h
@@ -0,0 +1,103 @@
+// license:BSD-3-Clause
+// copyright-holders:Tomasz Slanina,cam900
+#ifndef MAME_SOUND_SETAPCM_H
+#define MAME_SOUND_SETAPCM_H
+
+#pragma once
+
+#include "dirom.h"
+
+//**************************************************************************
+// TYPE DEFINITIONS
+//**************************************************************************
+
+// device type definition
+DECLARE_DEVICE_TYPE(NILE_SOUND, nile_sound_device)
+DECLARE_DEVICE_TYPE(ST0032_SOUND, st0032_sound_device)
+
+// ======================> setapcm_device
+
+// TODO: unknown address bus width
+template<unsigned MaxVoices, unsigned Divider>
+class setapcm_device : public device_t,
+ public device_sound_interface,
+ public device_rom_interface<32>
+{
+public:
+ void snd_w(offs_t offset, u16 data, u16 mem_mask = ~0);
+ u16 snd_r(offs_t offset);
+
+ void key_w(offs_t offset, u16 data, u16 mem_mask = ~0);
+ u16 key_r();
+
+protected:
+ setapcm_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, u32 clock);
+
+ // device-level overrides
+ virtual void device_start() override;
+ virtual void device_clock_changed() override;
+
+ // sound stream update overrides
+ virtual void sound_stream_update(sound_stream &stream, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs) override;
+
+ // device_rom_interface implementation
+ virtual void rom_bank_updated() override;
+
+ static constexpr unsigned MAX_VOICES = MaxVoices; // max voices
+ static constexpr unsigned CLOCK_DIVIDER = Divider; // clock divider for generate output rate
+private:
+ struct voice_t
+ {
+ bool update();
+ void keyon();
+ void keyoff();
+
+ u16 reg_r(offs_t reg);
+ void reg_w(offs_t reg, u16 data, u16 mem_mask = ~0);
+
+ device_rom_interface<32> *m_host; // host device
+ u32 m_start = 0; // Start position
+ u16 m_flags = 0; // Flags (Bit 0 = loop)
+ u16 m_freq = 0; // Frequency (4.12 fixed point)
+ u32 m_lpstart = 0; // Loop start position
+ u32 m_lpend = 0; // Loop end position
+ u32 m_end = 0; // End position
+ s32 m_vol_r = 0; // Right volume
+ s32 m_vol_l = 0; // Left volume
+ u32 m_pos = 0; // Current position
+ u32 m_frac = 0; // Position fraction
+ bool m_lponce = false; // Is looped once?
+ bool m_keyon = false; // Keyon status
+ s32 m_out = 0; // output value
+ };
+
+ sound_stream *m_stream;
+
+ voice_t m_voice[MaxVoices]; // 8 or 16 Voice engines
+ u16 m_keyctrl; // Key on/off control bit
+};
+
+// ======================> nile_sound_device
+
+class nile_sound_device : public setapcm_device<8, 160>
+{
+public:
+ nile_sound_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock);
+};
+
+// ======================> st0032_sound_device
+
+class st0032_sound_device : public setapcm_device<16, 384>
+{
+public:
+ st0032_sound_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock);
+};
+
+//**************************************************************************
+// EXTERNAL TEMPLATE INSTANTIATIONS
+//**************************************************************************
+
+extern template class setapcm_device<8, 160>;
+extern template class setapcm_device<16, 384>;
+
+#endif // MAME_SOUND_SETAPCM_H