summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/sound/gt155.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/sound/gt155.cpp')
-rw-r--r--src/devices/sound/gt155.cpp408
1 files changed, 408 insertions, 0 deletions
diff --git a/src/devices/sound/gt155.cpp b/src/devices/sound/gt155.cpp
new file mode 100644
index 00000000000..940bf051d78
--- /dev/null
+++ b/src/devices/sound/gt155.cpp
@@ -0,0 +1,408 @@
+// license:BSD-3-Clause
+// copyright-holders:Devin Acker
+
+/***************************************************************************
+
+ Casio GT155 (HG51B155FD)
+
+ This is the sound generator and DSP used in various higher-end
+ "A-Squared Sound Source" keyboards and pianos between roughly 1994-2001.
+
+ TODO:
+ - verify per-voice lowpass filter behavior
+ - DSP (architecture/instruction set seems to be the same as the standalone
+ "GD277" DSP used in other contemporary keyboards)
+
+***************************************************************************/
+
+#include "emu.h"
+#include "gt155.h"
+
+#include <algorithm>
+
+//**************************************************************************
+// DEVICE DEFINITIONS
+//**************************************************************************
+
+DEFINE_DEVICE_TYPE(GT155, gt155_device, "gt155", "Casio GT155")
+
+gt155_device::gt155_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock)
+ : device_t(mconfig, GT155, tag, owner, clock)
+ , device_sound_interface(mconfig, *this)
+ , device_rom_interface(mconfig, *this)
+{
+}
+
+/**************************************************************************/
+void gt155_device::device_start()
+{
+ m_stream = stream_alloc(0, 2, clock() / CLOCKS_PER_SAMPLE); // 16.384 MHz -> 32 kHz, or 24.576 MHz -> 48 kHz
+
+ for (int i = 0; i < 0x800; i++)
+ {
+ const double frac = 1.0 - ((double)i / 0x7ff);
+ m_volume[i] = 0x800 * pow(10, -2.15 * frac) * cos(0.5 * M_PI * frac * frac * frac);
+ }
+
+ save_item(NAME(m_data));
+ save_item(NAME(m_dsp_data));
+ save_item(NAME(m_rom_addr));
+
+ save_item(STRUCT_MEMBER(m_voices, m_enable));
+ save_item(STRUCT_MEMBER(m_voices, m_format));
+
+ save_item(STRUCT_MEMBER(m_voices, m_addr));
+ save_item(STRUCT_MEMBER(m_voices, m_addr_frac));
+ save_item(STRUCT_MEMBER(m_voices, m_addr_end));
+ save_item(STRUCT_MEMBER(m_voices, m_addr_loop));
+ save_item(STRUCT_MEMBER(m_voices, m_addr_loop_frac));
+
+ save_item(STRUCT_MEMBER(m_voices, m_pitch));
+
+ save_item(STRUCT_MEMBER(m_voices, m_filter_gain));
+ save_item(STRUCT_MEMBER(m_voices, m_filter));
+ save_item(STRUCT_MEMBER(m_voices, m_filter_out));
+ save_item(STRUCT_MEMBER(m_voices, m_filter_unk));
+
+ save_item(STRUCT_MEMBER(m_voices, m_sample_last));
+ save_item(STRUCT_MEMBER(m_voices, m_sample));
+
+ save_item(STRUCT_MEMBER(m_voices, m_env_current));
+ save_item(STRUCT_MEMBER(m_voices, m_env_target));
+ save_item(STRUCT_MEMBER(m_voices, m_env_level));
+ save_item(STRUCT_MEMBER(m_voices, m_env_scale));
+ save_item(STRUCT_MEMBER(m_voices, m_env_rate));
+
+ save_item(STRUCT_MEMBER(m_voices, m_balance));
+ save_item(STRUCT_MEMBER(m_voices, m_dsp_send));
+
+}
+
+/**************************************************************************/
+void gt155_device::device_reset()
+{
+ std::fill(std::begin(m_data), std::end(m_data), 0);
+ std::fill(std::begin(m_dsp_data), std::end(m_dsp_data), 0);
+ std::fill(std::begin(m_voices), std::end(m_voices), voice_t());
+}
+
+/**************************************************************************/
+void gt155_device::device_clock_changed()
+{
+ m_stream->set_sample_rate(clock() / CLOCKS_PER_SAMPLE);
+}
+
+/**************************************************************************/
+void gt155_device::sound_stream_update(sound_stream& stream, std::vector<read_stream_view> const& inputs, std::vector<write_stream_view>& outputs)
+{
+ for (int i = 0; i < outputs[0].samples(); i++)
+ {
+ s64 left = 0, right = 0;
+
+ for (auto &voice : m_voices)
+ {
+ if (voice.m_enable)
+ {
+ mix_sample(voice, left, right);
+ voice.update_envelope();
+ }
+ }
+
+ outputs[0].put_int_clamp(i, left >> 11, 32678);
+ outputs[1].put_int_clamp(i, right >> 11, 32768);
+ }
+}
+
+/**************************************************************************/
+void gt155_device::rom_bank_pre_change()
+{
+ m_stream->update();
+}
+
+/**************************************************************************/
+void gt155_device::mix_sample(voice_t &voice, s64 &left, s64 &right)
+{
+ // update sample position
+ voice.m_addr_frac += voice.m_pitch;
+ if (voice.m_addr_frac >= (1 << 15))
+ update_sample(voice);
+
+ // interpolate, apply envelope + channel gain and lowpass, and mix into output
+ s64 sample = voice.m_sample_last + (s64(voice.m_sample - voice.m_sample_last) * voice.m_addr_frac >> 15);
+
+ // TODO: does this produce accurate filter output?
+ sample = sample * voice.m_filter_gain;
+ sample += s32(voice.m_filter_out) * (voice.m_filter ^ 0xffff);
+ sample >>= 16;
+ voice.m_filter_out = sample;
+
+ const u16 env_level = voice.m_env_current >> (ENV_SHIFT + 4);
+ sample *= m_volume[env_level];
+
+ left += (sample * voice.m_balance[0]) / 0x1f;
+ right += (sample * voice.m_balance[1]) / 0x1f;
+}
+
+/**************************************************************************/
+void gt155_device::voice_t::update_envelope()
+{
+ if (m_env_target > m_env_current
+ && (m_env_target - m_env_current) > m_env_rate)
+ {
+ m_env_current += m_env_rate;
+ }
+ else if (m_env_target < m_env_current
+ && (m_env_current - m_env_target) > m_env_rate)
+ {
+ m_env_current -= m_env_rate;
+ }
+ else
+ {
+ m_env_current = m_env_target;
+ }
+}
+
+/**************************************************************************/
+void gt155_device::update_sample(voice_t &voice)
+{
+ voice.m_sample_last = voice.m_sample;
+
+ while (voice.m_addr_frac >= (1 << 15))
+ {
+ voice.m_addr += (voice.m_addr_frac >> 15) * (voice.m_format ? 2 : 1);
+ voice.m_addr_frac &= 0x7fff;
+
+ if (voice.m_addr >= voice.m_addr_end)
+ {
+ if (voice.m_addr_loop == voice.m_addr_end)
+ {
+ // if this is a one-shot sample, just disable it now
+ voice.m_enable = 0;
+ voice.m_env_current = voice.m_env_target = 0;
+ return;
+ }
+
+ // apply the fractional component of the loop
+ if (voice.m_format)
+ {
+ voice.m_addr -= (voice.m_addr_end - (voice.m_addr_loop & ~1));
+ voice.m_addr_frac += (voice.m_addr_loop_frac >> 1);
+ if (BIT(voice.m_addr_loop, 0))
+ voice.m_addr_frac += 1 << 14;
+ }
+ else
+ {
+ voice.m_addr -= (voice.m_addr_end - voice.m_addr_loop);
+ voice.m_addr_frac += voice.m_addr_loop_frac;
+ }
+ }
+ }
+
+ if (voice.m_format)
+ {
+ voice.m_sample = read_word(voice.m_addr & ~1);
+ }
+ else
+ {
+ // wk1800 apparently expects 8bit samples to only be expanded to 9 bits
+ // (with m_filter_gain then boosted to compensate)
+ voice.m_sample = s16(s8(read_byte(voice.m_addr))) << 1;
+ }
+}
+
+/**************************************************************************/
+void gt155_device::write(offs_t offset, u8 data)
+{
+ offset &= 0xf;
+
+ if (offset < 6)
+ {
+ m_data[offset] = data;
+ }
+ else if (offset == 6)
+ {
+ switch (data)
+ {
+ case 0x00:
+ m_rom_addr = (m_data[0] << 1) | (m_data[1] << 9) | (m_data[2] << 17);
+ break;
+
+ case 0x06:
+ m_dsp_data[m_data[5] & 0x7f] &= 0xffff;
+ m_dsp_data[m_data[5] & 0x7f] |= ((m_data[0] << 16) | (m_data[1] << 24));
+ break;
+
+ case 0x07:
+ m_dsp_data[m_data[5] & 0x7f] &= 0xffff0000;
+ m_dsp_data[m_data[5] & 0x7f] |= (m_data[0] | (m_data[1] << 8));
+ return;
+
+ default:
+ voice_command(data);
+ break;
+ }
+
+ }
+ else
+ {
+ logerror("%s: write offset %u = %02x\n", machine().describe_context(), offset, data);
+ }
+}
+
+/**************************************************************************/
+u8 gt155_device::read(offs_t offset)
+{
+ u8 data = 0;
+ offset &= 0xf;
+
+ if (offset < 0x6)
+ {
+ data = m_data[offset];
+ }
+ else if (offset == 0xe)
+ {
+ data = read_byte(m_rom_addr);
+ }
+ else if (offset == 0xf)
+ {
+ data = read_byte(m_rom_addr + 1);
+ if (!machine().side_effects_disabled())
+ m_rom_addr += 2;
+ }
+ else if (!machine().side_effects_disabled())
+ {
+ logerror("%s: read offset %u\n", machine().describe_context(), offset);
+ }
+
+ return data;
+}
+
+/**************************************************************************/
+u16 gt155_device::reg16(u8 num) const
+{
+ return m_data[num] | (m_data[num+1] << 8);
+}
+
+/**************************************************************************/
+u32 gt155_device::reg24(u8 num) const
+{
+ return m_data[num] | (m_data[num+1] << 8) | (m_data[num+2] << 16);
+}
+
+/**************************************************************************/
+u32 gt155_device::reg32(u8 num) const
+{
+ return m_data[num] | (m_data[num+1] << 8) | (m_data[num+2] << 16) | (m_data[num+3] << 24);
+}
+
+/**************************************************************************/
+void gt155_device::voice_command(u8 data)
+{
+ m_stream->update();
+
+ voice_t &voice = m_voices[m_data[5] & 0x1f];
+ const u16 cmd = data | ((m_data[5] & 0xe0) << 8);
+
+ switch (cmd)
+ {
+ case 0x0001: // sample start address
+ voice.m_addr = reg32(1) >> 7;
+ voice.m_addr_frac = reg16(0) & 0x7fff;
+ break;
+
+ case 0x2001: // sample loop address
+ voice.m_addr_loop = reg32(1) >> 7;
+ voice.m_addr_loop_frac = reg16(0) & 0x7fff;
+ break;
+
+ case 0x0002: // sample envelope step
+ case 0x2002: // sample envelope step (double rate?) used when forcing a voice off
+ voice.m_env_rate = reg16(0) & 0x7fff;
+ if (cmd == 0x2002)
+ voice.m_env_rate <<= 1;
+ voice.m_env_level = reg16(2);
+ voice.m_env_target = u32(voice.m_env_level) * voice.m_env_scale;
+ break;
+
+ case 0x0003: // sample end address and envelope scale
+ voice.m_addr_end = reg24(0) << 1;
+ voice.m_env_scale = m_data[3];
+ voice.m_env_target = u32(voice.m_env_level) * voice.m_env_scale;
+ break;
+
+ case 0x0004:
+ /*
+ * params used by wk1800:
+ * ff ff 00 00 (on boot)
+ * fe 07 0c 00 (before note on)
+ * f5 0d 0a 02 (starting 8-bit sample)
+ * f5 05 0a 0a (starting 16-bit sample)
+ * 00 20 00 00 (before note off)
+ * 14 00 08 02 (after envelope update)
+ *
+ * TODO: is any of this related to how 8bit samples are expanded?
+ * (see comment at bottom of update_sample)
+ */
+ if (!voice.m_enable && !BIT(m_data[0], 1))
+ {
+ voice.m_format = BIT(m_data[3], 3);
+ voice.m_sample_last = voice.m_sample = 0;
+ }
+ voice.m_enable = BIT(~m_data[0], 1);
+ break;
+
+ case 0x0005: // sample pitch
+ voice.m_pitch = reg16(0);
+ break;
+
+ case 0x2005:
+ {
+ // this is actually gain premultiplied by filter level, so the sound HW does one fewer mult when applying the filter.
+ const u16 gain = reg16(0);
+ voice.m_filter_gain = (gain & 0x1fff) << (3 + (gain >> 13));
+ }
+ break;
+
+ case 0x4005:
+ /*
+ filter coefficient (bit 15 inverted) - this is also premultiplied into the value written to m_filter_gain,
+ with this value probably only being used as a coefficient for the previous filter output?
+ */
+ voice.m_filter = reg16(0) ^ 0x8000;
+ break;
+
+ case 0x6005:
+ voice.m_filter_unk = reg16(0);
+ break;
+
+ case 0x000e:
+ voice.m_balance[0] = m_data[0] & 0x1f;
+ voice.m_balance[1] = m_data[1] & 0x1f;
+ break;
+
+ case 0x200e:
+ voice.m_dsp_send[0] = m_data[0] & 0x1f;
+ voice.m_dsp_send[1] = m_data[1] & 0x1f;
+ break;
+
+ case 0x0014: // read envelope ready status
+ if (voice.m_env_current == voice.m_env_target)
+ m_data[2] = 0x00;
+ else
+ m_data[2] = 0x08;
+ break;
+
+ case 0x2018: // read envelope output & current sample output
+ m_data[1] = voice.m_env_current >> ENV_SHIFT;
+ m_data[2] = voice.m_env_current >> (ENV_SHIFT + 8);
+ m_data[3] = voice.m_sample;
+ m_data[4] = voice.m_sample >> 8;
+ break;
+
+ default:
+ logerror("%s: sound cmd %02x%02x, param = %02x %02x %02x %02x %02x\n", machine().describe_context(),
+ m_data[5], data,
+ m_data[0], m_data[1], m_data[2], m_data[3], m_data[4]);
+ break;
+ }
+}