summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
-rw-r--r--scripts/src/sound.lua12
-rw-r--r--src/devices/sound/bbd.cpp16
-rw-r--r--src/devices/sound/bbd.h11
-rw-r--r--src/devices/sound/flt_biquad.cpp253
-rw-r--r--src/devices/sound/flt_biquad.h12
-rw-r--r--src/devices/sound/upd931.cpp599
-rw-r--r--src/devices/sound/upd931.h153
-rw-r--r--src/emu/xtal.cpp1
-rw-r--r--src/mame/casio/ct8000.cpp857
-rw-r--r--src/mame/casio/ct8000_midi.cpp409
-rw-r--r--src/mame/casio/ct8000_midi.h89
-rw-r--r--src/mame/layout/ct8000.lay423
-rw-r--r--src/mame/layout/ctfk1.lay241
-rw-r--r--src/mame/mame.lst5
14 files changed, 2978 insertions, 103 deletions
diff --git a/scripts/src/sound.lua b/scripts/src/sound.lua
index e73db74d7e4..390b9e7a750 100644
--- a/scripts/src/sound.lua
+++ b/scripts/src/sound.lua
@@ -1504,6 +1504,18 @@ if (SOUNDS["LC82310"]~=null) then
end
---------------------------------------------------
+-- NEC uPD931
+--@src/devices/sound/upd931.h,SOUNDS["UPD931"] = true
+---------------------------------------------------
+
+if (SOUNDS["UPD931"]~=null) then
+ files {
+ MAME_DIR .. "src/devices/sound/upd931.cpp",
+ MAME_DIR .. "src/devices/sound/upd931.h",
+ }
+end
+
+---------------------------------------------------
-- NEC uPD933
--@src/devices/sound/upd933.h,SOUNDS["UPD933"] = true
---------------------------------------------------
diff --git a/src/devices/sound/bbd.cpp b/src/devices/sound/bbd.cpp
index e270108b04e..5b694b6970c 100644
--- a/src/devices/sound/bbd.cpp
+++ b/src/devices/sound/bbd.cpp
@@ -52,7 +52,8 @@ void bbd_device_base<Entries, Outputs>::device_start()
template<int Entries, int Outputs>
void bbd_device_base<Entries, Outputs>::device_clock_changed()
{
- m_stream->set_sample_rate(sample_rate());
+ if (m_cv_handler.isnull())
+ m_stream->set_sample_rate(sample_rate());
}
@@ -163,3 +164,16 @@ mn3204p_device::mn3204p_device(const machine_config &mconfig, const char *tag, d
bbd_device_base(mconfig, tag, owner, clock, MN3204P)
{
}
+
+
+//**************************************************************************
+// MN3207P
+//**************************************************************************
+
+// device type definition
+DEFINE_DEVICE_TYPE(MN3207, mn3207_device, "mn3207", "MN3207 BBD")
+
+mn3207_device::mn3207_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
+ bbd_device_base(mconfig, tag, owner, clock, MN3207)
+{
+}
diff --git a/src/devices/sound/bbd.h b/src/devices/sound/bbd.h
index a76c3ec2958..64394a90620 100644
--- a/src/devices/sound/bbd.h
+++ b/src/devices/sound/bbd.h
@@ -90,4 +90,15 @@ public:
DECLARE_DEVICE_TYPE(MN3204P, mn3204p_device)
+
+// ======================> mn3207_device
+
+class mn3207_device : public bbd_device_base<1024, 2>
+{
+public:
+ mn3207_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock = 0);
+};
+
+DECLARE_DEVICE_TYPE(MN3207, mn3207_device)
+
#endif // MAME_SOUND_BBD_H
diff --git a/src/devices/sound/flt_biquad.cpp b/src/devices/sound/flt_biquad.cpp
index f74bf6bae73..de6bfe63462 100644
--- a/src/devices/sound/flt_biquad.cpp
+++ b/src/devices/sound/flt_biquad.cpp
@@ -7,18 +7,15 @@
It has a number of constructor-helpers for automatically generating
a biquad filter equivalent to the filter response of a few standard
- analog second order filter topographies.
+ analog first and second order filter topographies.
This biquad filter implementation is based on one written by Frank
Palazzolo, K. Wilkins, Couriersud, and Derrick Renaud, with some changes:
* It uses the Q factor directly in the filter definitions, rather than the damping factor (1/2Q)
* It implements every common type of digital biquad filter which I could find documentation for.
* The filter is Direct-form II instead of Direct-form I, which results in shorter compiled code.
+ * (There are advantages to Direct-form I if the code used fixed-point math, but it does not.)
* Optional direct control of the 5 normalized biquad parameters for a custom/raw parameter filter.
-
- Possibly useful features which aren't implemented because nothing uses them yet:
- * More Sallen-Key filter variations (band-pass, high-pass)
-
*/
#include "emu.h"
#include "flt_biquad.h"
@@ -149,60 +146,106 @@ void filter_biquad_device::modify_raw(double a1, double a2, double b0, double b1
// Sallen-Key filters
+// (sometimes referred to as KRC or VCVS filter structures)
+
+/*
+ * The calculation of the cutoff parameter for the Sallen-Key low-pass and
+ * high-pass is identical, the only differences being the biquad filter
+ * type used, and the calculation of the q factor.
+ */
+filter_biquad_device::biquad_params filter_biquad_device::opamp_sk_lphp_calc(biquad_type type, double r1, double r2, double r3, double r4, double c1, double c2)
+{
+ filter_biquad_device::biquad_params r;
+ if ((r1 == 0) || (r2 == 0) || (r3 == 0) || (r4 == 0) || (c1 == 0) || (c2 == 0))
+ {
+ fatalerror("filter_biquad_device::opamp_sk_lphp_calc() - no parameters can be 0; parameters were: r1: %f, r2: %f, r3: %f, r4: %f, c1: %f, c2: %f", r1, r2, r3, r4, c1, c2); /* Filter can not be setup. Undefined results. */
+ }
+ r.type = type;
+ r.gain = 1.0 + (r4 / r3); // == (r3 + r4) / r3
+ r.fc = 1.0 / (2 * M_PI * sqrt(r1 * r2 * c1 * c2));
+ if (type == biquad_type::LOWPASS)
+ r.q = sqrt(r1 * r2 * c1 * c2) / ((r1 * c2) + (r2 * c2) + ((r1 * c1) * (1.0 - r.gain)));
+ else if (type == biquad_type::HIGHPASS)
+ r.q = sqrt(r1 * r2 * c1 * c2) / ((r1 * c2) + (r1 * c1) + ((r2 * c2) * (1.0 - r.gain)));
+ else
+ r.q = M_SQRT2/2.0; // we shouldn't get here but fail gracefully if we do.
+ LOGMASKED(LOG_SETUP,"filter_biquad_device::opamp_sk_lphp_calc(%f, %f, %f, %f, %f, %f) yields: fc = %f, Q = %f, gain = %f\n", r1, r2, r3, r4, c1*1000000, c2*1000000, r.fc, r.q, r.gain);
+ return r;
+}
/* Setup a biquad filter structure based on a single op-amp Sallen-Key low-pass filter circuit.
* This is sometimes, incorrectly, called a "Butterworth" filter structure.
*
- * .----------------------------.
- * | |
- * --- c1 |
- * --- |
- * | |
- * r1 | r2 |\ |
- * In >----ZZZZ----+--ZZZZ---+--------+ | \ |
- * | '--|+ \ |
- * --- c2 | >--+------> out
- * --- .--|- / |
- * | | | / |
- * gnd | |/ |
- * | |
- * | r4 |
- * +--ZZZZ---'
- * |
- * Z
- * Z r3
- * Z
- * |
- * gnd
+ * ,--------------------------.
+ * | |
+ * --- c1 |
+ * --- |
+ * | |\ |
+ * r1 | r2 | \ |
+ * In >---ZZZZ--+--ZZZZ--+----------|+ \ |
+ * | | >--+---> out
+ * --- c2 ,--|- / |
+ * --- | | / |
+ * | | |/ |
+ * | | |
+ * gnd | r4 |
+ * +--ZZZZ---'
+ * |
+ * Z r3
+ * Z
+ * Z
+ * |
+ * gnd
*/
filter_biquad_device& filter_biquad_device::opamp_sk_lowpass_setup(double r1, double r2, double r3, double r4, double c1, double c2)
{
- filter_biquad_device::biquad_params p = opamp_sk_lowpass_calc(r1, r2, r3, r4, c1, c2);
+ filter_biquad_device::biquad_params p = opamp_sk_lphp_calc(biquad_type::LOWPASS, r1, r2, r3, r4, c1, c2);
return setup(p);
}
void filter_biquad_device::opamp_sk_lowpass_modify(double r1, double r2, double r3, double r4, double c1, double c2)
{
- filter_biquad_device::biquad_params p = opamp_sk_lowpass_calc(r1, r2, r3, r4, c1, c2);
+ filter_biquad_device::biquad_params p = opamp_sk_lphp_calc(biquad_type::LOWPASS, r1, r2, r3, r4, c1, c2);
modify(p);
}
-filter_biquad_device::biquad_params filter_biquad_device::opamp_sk_lowpass_calc(double r1, double r2, double r3, double r4, double c1, double c2)
+
+/* Setup a biquad filter structure based on a single op-amp Sallen-Key high-pass filter circuit.
+ *
+ * ,--------------------------.
+ * | |
+ * Z r1 |
+ * Z |
+ * Z |\ |
+ * c1 | c2 | \ |
+ * In >----||---+---||---+----------|+ \ |
+ * | | >--+---> out
+ * Z r2 ,--|- / |
+ * Z | | / |
+ * Z | |/ |
+ * | | |
+ * gnd | r4 |
+ * +--ZZZZ---'
+ * |
+ * Z r3
+ * Z
+ * Z
+ * |
+ * gnd
+ */
+filter_biquad_device& filter_biquad_device::opamp_sk_highpass_setup(double r1, double r2, double r3, double r4, double c1, double c2)
{
- filter_biquad_device::biquad_params r;
- if ((r1 == 0) || (r2 == 0) || (r3 == 0) || (r4 == 0) || (c1 == 0) || (c2 == 0))
- {
- fatalerror("filter_biquad_device::opamp_sk_lowpass_calc() - no parameters can be 0; parameters were: r1: %f, r2: %f, r3: %f, r4: %f, c1: %f, c2: %f", r1, r2, r3, r4, c1, c2); /* Filter can not be setup. Undefined results. */
- }
- r.type = biquad_type::LOWPASS;
- r.gain = 1.0 + (r4 / r3); // == (r3 + r4) / r3
- r.fc = 1.0 / (2 * M_PI * sqrt(r1 * r2 * c1 * c2));
- r.q = sqrt(r1 * r2 * c1 * c2) / ((r1 * c2) + (r2 * c2) + ((r2 * c1) * (1.0 - r.gain)));
- LOGMASKED(LOG_SETUP,"filter_biquad_device::opamp_sk_lowpass_calc(%f, %f, %f, %f, %f, %f) yields: fc = %f, Q = %f, gain = %f\n", r1, r2, r3, r4, c1*1000000, c2*1000000, r.fc, r.q, r.gain);
- return r;
+ filter_biquad_device::biquad_params p = opamp_sk_lphp_calc(biquad_type::HIGHPASS, r1, r2, r3, r4, c1, c2);
+ return setup(p);
+}
+
+void filter_biquad_device::opamp_sk_highpass_modify(double r1, double r2, double r3, double r4, double c1, double c2)
+{
+ filter_biquad_device::biquad_params p = opamp_sk_lphp_calc(biquad_type::HIGHPASS, r1, r2, r3, r4, c1, c2);
+ modify(p);
}
-// TODO when needed: Sallen-Key high-pass filter
+// TODO when needed: Sallen-Key band-pass (there are several versions of this in the 1955 Sallen-Key paper)
// Multiple-Feedback filters
@@ -210,24 +253,39 @@ filter_biquad_device::biquad_params filter_biquad_device::opamp_sk_lowpass_calc(
/* Setup a biquad filter structure based on a single op-amp Multiple-Feedback low-pass filter circuit.
* This is sometimes called a "Rauch" filter circuit.
* NOTE: There is a well known 'proper' 1st order version of this circuit where
- * r2 is a dead short, and c1 omitted. As an exception to the usual rule about
- * missing components, set both c1 and r2 to 0 in this case.
- * NOTE: There is a variant of this filter with the c1 capacitor left off, and
- * r2 present. if so, set c1 to 0 and r2 to its expected value.
- * TODO: make this compatible with the RES_M(999.99) and RES_R(0.001) rules!
+ * r2 is a dead short, and c1 omitted: set r2 to 0 or RES_R(0.001) and c1 to 0
+ * in this case.
+ * NOTE: There is a variant of this filter where r2 is present but c1 is
+ * omitted: set r2 to its expected value, and c1 to 0.
+ *
+ * Typical variant: (set c1 to 0 if missing)
+ * ,--------+---------.
+ * | | |
+ * Z r3 --- c2 |
+ * Z --- |
+ * Z | |
+ * r1 | r2 | |\ |
+ * In >---ZZZZ--+-------+--ZZZZ--+ | \ |
+ * | `--|- \ |
+ * --- c1 | >--+---> out
+ * --- ,--|+ /
+ * | | | /
+ * | vRef >---' |/
+ * gnd
*
- * .--------+---------.
- * | | |
- * Z --- c2 |
- * Z r3 --- |
- * Z | |
- * r1 | r2 | |\ |
- * In >----ZZZZ----+---------+--ZZZZ--+ | \ |
- * | '--|- \ |
- * --- c1 | >--+------> out
- * --- .--|+ /
- * | | | /
- * gnd vRef >---' |/
+ * First order variant: (set c1 and r2 to 0)
+ * ,-------+---------.
+ * | | |
+ * Z r3 --- c2 |
+ * Z --- |
+ * Z | |
+ * r1 | | |\ |
+ * In >---ZZZZ--+-------+ | \ |
+ * `--|- \ |
+ * | >--+---> out
+ * ,--|+ /
+ * | | /
+ * vRef >--' |/
*
*/
filter_biquad_device& filter_biquad_device::opamp_mfb_lowpass_setup(double r1, double r2, double r3, double c1, double c2)
@@ -269,23 +327,21 @@ filter_biquad_device::biquad_params filter_biquad_device::opamp_mfb_lowpass_calc
/* Setup a biquad filter structure based on a single op-amp Multiple-Feedback band-pass filter circuit.
* This is sometimes called a "modified Deliyannis" or "Deliyannis-friend" filter circuit,
* or an "Infinite Gain Multiple-Feedback [band-pass] Filter" aka "IGMF".
- * TODO: There is a documented modification to this filter which adds a resistor ladder between
- * ground and the op-amp output, with the 'rung' of the ladder connecting to the + input of
- * the op-amp, and this allows more control of the filter.
* NOTE2: If r2 is not present, then set it to RES_M(999.99), the code will effectively be an Infinite Gain MFB Bandpass.
*
- * .--------+---------.
- * | | |
- * --- c1 Z |
- * --- Z r3 |
- * | Z |
- * r1 | c2 | |\ |
- * In >----ZZZZ----+---------+--||----+ | \ |
- * Z '--|- \ |
- * Z r2 | >--+------> out
- * Z .--|+ /
- * | | | /
- * gnd vRef >---' |/
+ * ,--------+---------.
+ * | | |
+ * --- c1 Z r3 |
+ * --- Z |
+ * | Z |
+ * r1 | c2 | |\ |
+ * In >---ZZZZ--+-------+---||---+ | \ |
+ * | `--|- \ |
+ * Z r2 | >--+---> out
+ * Z ,--|+ /
+ * Z | | /
+ * | vRef >---' |/
+ * gnd
*
*/
filter_biquad_device& filter_biquad_device::opamp_mfb_bandpass_setup(double r1, double r2, double r3, double c1, double c2)
@@ -305,18 +361,18 @@ filter_biquad_device& filter_biquad_device::opamp_mfb_bandpass_setup(double r1,
/* Setup a biquad filter structure based on a single op-amp Multiple-Feedback high-pass filter circuit.
*
- * .--------+---------.
- * | | |
- * --- c3 Z |
- * --- Z r2 |
- * | Z |
- * c1 | c2 | |\ |
- * In >-----||-----+---------+---||---+ | \ |
- * Z '--|- \ |
- * Z r1 | >--+------> out
- * Z .--|+ /
- * | | | /
- * gnd vRef >---' |/
+ * ,--------+---------.
+ * | | |
+ * --- c3 Z |
+ * --- Z r2 |
+ * | Z |
+ * c1 | c2 | |\ |
+ * In >---||----+-------+---||---+ | \ |
+ * Z `--|- \ |
+ * Z r1 | >--+---> out
+ * Z ,--|+ /
+ * | | | /
+ * gnd vRef >---' |/
*
*/
filter_biquad_device& filter_biquad_device::opamp_mfb_highpass_setup(double r1, double r2, double c1, double c2, double c3)
@@ -333,23 +389,26 @@ filter_biquad_device& filter_biquad_device::opamp_mfb_highpass_setup(double r1,
return setup(biquad_type::HIGHPASS, fc, q, gain);
}
+
+// Other filters:
+
// Differentiator Filter
/* Setup a biquad filter structure based on a single op-amp Differentiator band-pass filter circuit.
* This circuit is sometimes called an "Inverting Band Pass Filter Circuit"
*
- * .--------+---------.
- * | | |
- * --- c2 Z |
- * --- Z r2 |
- * | Z |
- * r1 c1 | | |\ |
- * In >----ZZZZ-----||-----+--------+ | \ |
- * '--|- \ |
- * | >--+------> out
- * .--|+ /
- * | | /
- * vRef >---' |/
+ * ,--------+---------.
+ * | | |
+ * --- c2 Z r2 |
+ * --- Z |
+ * | Z |
+ * r1 c1 | | |\ |
+ * In >---ZZZZ-----||---+--------+ | \ |
+ * `--|- \ |
+ * | >--+---> out
+ * ,--|+ /
+ * | | /
+ * vRef >---' |/
*
*/
filter_biquad_device& filter_biquad_device::opamp_diff_bandpass_setup(double r1, double r2, double c1, double c2)
diff --git a/src/devices/sound/flt_biquad.h b/src/devices/sound/flt_biquad.h
index 19d5f352354..ad58a91de79 100644
--- a/src/devices/sound/flt_biquad.h
+++ b/src/devices/sound/flt_biquad.h
@@ -52,16 +52,18 @@ public:
// Helper setup functions to create common filters representable by biquad filters:
// (and, as needed, modify/update/recalc helpers)
+ // universal calculator for both Sallen-Key low-pass and high-pass
+ biquad_params opamp_sk_lphp_calc(biquad_type type, double r1, double r2, double r3, double r4, double c1, double c2);
+
// Sallen-Key low-pass
filter_biquad_device& opamp_sk_lowpass_setup(double r1, double r2, double r3, double r4, double c1, double c2);
void opamp_sk_lowpass_modify(double r1, double r2, double r3, double r4, double c1, double c2);
- biquad_params opamp_sk_lowpass_calc(double r1, double r2, double r3, double r4, double c1, double c2);
-
- // TODO when needed: Sallen-Key band-pass
- // TODO when needed: Sallen-Key band-reject
+ // Sallen-Key high-pass
+ filter_biquad_device& opamp_sk_highpass_setup(double r1, double r2, double r3, double r4, double c1, double c2);
+ void opamp_sk_highpass_modify(double r1, double r2, double r3, double r4, double c1, double c2);
- // TODO when needed: Sallen-Key high-pass
+ // TODO when needed: Sallen-Key band-pass (there are several versions of this in the 1955 Sallen-Key paper)
// Multiple-Feedback low-pass
filter_biquad_device& opamp_mfb_lowpass_setup(double r1, double r2, double r3, double c1, double c2);
diff --git a/src/devices/sound/upd931.cpp b/src/devices/sound/upd931.cpp
new file mode 100644
index 00000000000..c5c1e1c22b4
--- /dev/null
+++ b/src/devices/sound/upd931.cpp
@@ -0,0 +1,599 @@
+// license:BSD-3-Clause
+// copyright-holders:Devin Acker
+
+/***************************************************************************
+ NEC/Casio uPD931 synthesis chip
+
+ Many details of this implementation are based on research and notes by Robin Whittle:
+ https://www.firstpr.com.au/rwi/casio/Casio-931-2006-06-17.txt
+ Any references to MT-65 behavior are based on this document.
+
+ TODO:
+ - implement vibrato register (CT-8000 doesn't use it)
+ - a few other unknown/unclear bits in the flags shift register
+***************************************************************************/
+
+#include "emu.h"
+#include "upd931.h"
+
+#include <algorithm>
+#include <cmath>
+
+DEFINE_DEVICE_TYPE(UPD931, upd931_device, "upd931", "NEC uPD931")
+
+upd931_device::upd931_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock)
+ : device_t(mconfig, UPD931, tag, owner, clock)
+ , device_sound_interface(mconfig, *this)
+ , device_memory_interface(mconfig, *this)
+ , m_io_config("io", ENDIANNESS_LITTLE, 8, 8, 0, address_map_constructor(FUNC(upd931_device::io_map), this))
+ , m_retrig_timer(nullptr)
+ , m_filter_cb(*this)
+ , m_sync_cb(*this)
+ , m_master(true)
+{
+}
+
+/**************************************************************************/
+device_memory_interface::space_config_vector upd931_device::memory_space_config() const
+{
+ return space_config_vector{std::make_pair(AS_IO, &m_io_config)};
+}
+
+/**************************************************************************/
+void upd931_device::io_map(address_map &map)
+{
+ map(0x20, 0x27).w(FUNC(upd931_device::note_w));
+ map(0x30, 0x37).w(FUNC(upd931_device::octave_w));
+ // waveform write position: ct8000 uses 40, mt65 uses 60
+ map(0x40, 0x40).mirror(0x20).w(FUNC(upd931_device::wave_pos_w));
+ map(0xa7, 0xa7).w(FUNC(upd931_device::wave_data_w));
+ map(0xb0, 0xb7).w(FUNC(upd931_device::flags_w)); // lower address bits are ignored
+ map(0xb8, 0xbf).w(FUNC(upd931_device::status_latch_w));
+ // vibrato/sustain: ct8000 uses c0-c7, mt65 uses d0-d7 (lower two bits are also ignored)
+ map(0xc0, 0xc3).mirror(0x10).w(FUNC(upd931_device::vibrato_w));
+ map(0xc4, 0xc7).mirror(0x10).w(FUNC(upd931_device::sustain_w));
+ map(0xe0, 0xe7).w(FUNC(upd931_device::note_on_w));
+ map(0xf4, 0xf4).lw8(NAME([this] (offs_t offset, u8 data) { m_filter_cb(data); }));
+}
+
+/**************************************************************************/
+void upd931_device::device_start()
+{
+ space(AS_IO).specific(m_io);
+
+ m_stream = stream_alloc(0, 1, clock() / CLOCKS_PER_SAMPLE);
+
+ if (m_master)
+ {
+ m_retrig_timer = timer_alloc(FUNC(upd931_device::timer_tick), this);
+ reset_timer();
+ }
+ m_last_clock = clock();
+
+ m_db = 0xf;
+ m_i1 = m_i2 = m_i3 = 1;
+ m_status = 0xf;
+
+ // generate note frequencies based on CT-8000 crystal frequency (probably not entirely accurate)
+ const unsigned sample_rate = 4'946'864 / CLOCKS_PER_SAMPLE;
+ for (int i = 0; i < 73; i++)
+ {
+ // A4 is note 33, 442 Hz
+ const double freq = 442.0 * pow(2, (i - 33) / 12.0);
+ m_pitch[i] = (1 << PITCH_SHIFT) * (freq * 16 / sample_rate);
+ }
+
+ save_item(NAME(m_db));
+ save_item(NAME(m_i1));
+ save_item(NAME(m_i2));
+ save_item(NAME(m_i3));
+
+ save_item(NAME(m_addr));
+ save_item(NAME(m_data));
+ save_item(NAME(m_status));
+
+ save_item(NAME(m_wave));
+ save_item(NAME(m_wave_pos));
+ save_item(NAME(m_vibrato));
+ save_item(NAME(m_sustain));
+ save_item(NAME(m_reverb));
+
+ save_item(NAME(m_flags));
+ save_item(NAME(m_last_clock));
+
+ save_item(STRUCT_MEMBER(m_voice, m_note));
+ save_item(STRUCT_MEMBER(m_voice, m_octave));
+ save_item(STRUCT_MEMBER(m_voice, m_pitch));
+ save_item(STRUCT_MEMBER(m_voice, m_pitch_counter));
+ save_item(STRUCT_MEMBER(m_voice, m_timbre_shift));
+
+ save_item(STRUCT_MEMBER(m_voice, m_wave_pos));
+ save_item(STRUCT_MEMBER(m_voice, m_wave_out));
+
+ save_item(STRUCT_MEMBER(m_voice, m_env_state));
+ save_item(STRUCT_MEMBER(m_voice, m_env_counter));
+ save_item(STRUCT_MEMBER(m_voice, m_env_level));
+ save_item(STRUCT_MEMBER(m_voice, m_force_release));
+}
+
+/**************************************************************************/
+TIMER_CALLBACK_MEMBER(upd931_device::timer_tick)
+{
+ m_sync_cb(1);
+ sync_w(1);
+}
+
+/**************************************************************************/
+void upd931_device::device_reset()
+{
+ std::fill(std::begin(m_wave[0]), std::end(m_wave[0]), 0);
+ std::fill(std::begin(m_wave[1]), std::end(m_wave[1]), 0);
+ m_wave_pos = 0;
+ m_vibrato = 0;
+ m_sustain = 0;
+ m_reverb = 0;
+
+ m_flags = 0;
+
+ std::fill(m_voice.begin(), m_voice.end(), voice_t());
+
+ m_filter_cb(0);
+ m_sync_cb(0);
+}
+
+/**************************************************************************/
+void upd931_device::device_clock_changed()
+{
+ m_stream->set_sample_rate(clock() / CLOCKS_PER_SAMPLE);
+
+ if (m_retrig_timer)
+ {
+ const u64 ticks_left = m_retrig_timer->remaining().as_ticks(m_last_clock);
+ const attotime remaining = attotime::from_ticks(ticks_left, clock());
+ const attotime period = attotime::from_ticks(RETRIG_RATE, clock());
+
+ m_retrig_timer->adjust(remaining, 0, period);
+ }
+
+ m_last_clock = clock();
+}
+
+/**************************************************************************/
+void upd931_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++)
+ {
+ s32 sample = 0;
+ for (voice_t &voice : m_voice)
+ {
+ update_env(voice);
+ update_wave(voice);
+
+ sample += voice.m_wave_out[0] * (voice.m_env_level[0] >> VOLUME_SHIFT);
+ sample += voice.m_wave_out[1] * (voice.m_env_level[1] >> VOLUME_SHIFT);
+ }
+ outputs[0].put_int_clamp(i, sample, 1 << 16);
+ }
+}
+
+/**************************************************************************/
+void upd931_device::i1_w(int state)
+{
+ if (!m_i3 && m_i1 && !state)
+ {
+ // address high nibble on I1 falling edge
+ m_addr &= 0x0f;
+ m_addr |= (m_db << 4);
+ }
+
+ m_i1 = state;
+}
+
+/**************************************************************************/
+void upd931_device::i2_w(int state)
+{
+ if (!m_i3)
+ {
+ if (!m_i2 && state)
+ {
+ // address low nibble on I2 rising edge
+ m_addr &= 0xf0;
+ m_addr |= (m_db & 0xf);
+
+ // apply register write
+ m_stream->update();
+ m_io.write_byte(m_addr, m_data & 0xf);
+ }
+ else if (m_i2 && !state)
+ {
+ // data on I2 falling edge
+ m_data = m_db;
+ }
+ }
+
+ m_i2 = state;
+}
+
+/**************************************************************************/
+void upd931_device::i3_w(int state)
+{
+ m_i3 = state;
+}
+
+/**************************************************************************/
+void upd931_device::db_w(u8 data)
+{
+ m_db = data;
+}
+
+/**************************************************************************/
+u8 upd931_device::db_r()
+{
+ if (m_i1 && m_i2 && !m_i3)
+ return m_status;
+ return 0xf;
+}
+
+/**************************************************************************/
+void upd931_device::sync_w(int state)
+{
+ if (BIT(m_flags, FLAG_RETRIGGER))
+ {
+ m_stream->update();
+
+ for (voice_t &voice : m_voice)
+ {
+ if (voice.m_env_state == ENV_DECAY1 || voice.m_env_state == ENV_DECAY2)
+ {
+ voice.m_env_state = ENV_ATTACK1;
+ voice.m_env_counter = 0;
+ }
+ }
+ }
+}
+
+/**************************************************************************/
+void upd931_device::note_w(offs_t offset, u8 data)
+{
+ m_voice[offset].m_note = data;
+}
+
+/**************************************************************************/
+void upd931_device::octave_w(offs_t offset, u8 data)
+{
+ m_voice[offset].m_octave = m_data;
+}
+
+/**************************************************************************/
+void upd931_device::wave_pos_w(u8 data)
+{
+ m_wave_pos = data;
+}
+
+/**************************************************************************/
+void upd931_device::wave_data_w(u8 data)
+{
+ const u8 sel = BIT(m_flags, FLAG_WAVE_SEL);
+ m_wave[sel][m_wave_pos & 0xf] = data;
+}
+
+/**************************************************************************/
+void upd931_device::flags_w(u8 data)
+{
+ m_flags >>= 1;
+ m_flags |= ((data & 1) << FLAG_WAVE_SEL);
+}
+
+/**************************************************************************/
+void upd931_device::status_latch_w(offs_t offset, u8 data)
+{
+ // TODO: more details. ct8000 only checks if bits 1-3 are all zero or not
+ if (m_voice[offset].m_env_state == ENV_IDLE)
+ m_status = 0xf;
+ else
+ m_status = 0;
+}
+
+/**************************************************************************/
+void upd931_device::vibrato_w(u8 data)
+{
+ // TODO: implement this. ct8000 always writes 0 since it uses the external VCO vibrato instead
+ m_vibrato = data & 3;
+}
+
+/**************************************************************************/
+void upd931_device::sustain_w(u8 data)
+{
+ m_sustain = data & 3;
+ m_reverb = BIT(data, 1, 2) ? 1 : 0;
+}
+
+/**************************************************************************/
+void upd931_device::note_on_w(offs_t offset, u8 data)
+{
+ voice_t &voice = m_voice[offset];
+ if (BIT(data, 0))
+ note_on(voice);
+ else
+ voice.m_env_state = ENV_RELEASE;
+
+ // mt65 turns off sustain and reverb when changing tones, ct8000 does this instead
+ // (and also when playing a new note that is already playing)
+ voice.m_force_release = BIT(data, 3);
+}
+
+/**************************************************************************/
+void upd931_device::note_on(voice_t &voice)
+{
+ if (voice.m_note >= 0x2 && voice.m_note <= 0xe)
+ {
+ const u8 note = voice.m_note - 2;
+ u8 octave = voice.m_octave & 7;
+
+ if (octave >= 2)
+ octave -= 2; // octave values 0-1 are the same as 2-3
+
+ /*
+ setting bit 3 of the octave reduces the duty cycle of individual notes, which is
+ implemented here by changing which part of the phase counter to use as the sample address.
+ ct8000 uses this for a few of its presets to produce a simple key-scaling effect.
+ */
+ if (BIT(voice.m_octave, 3))
+ voice.m_timbre_shift = 3 - octave;
+ else
+ voice.m_timbre_shift = 0;
+
+ voice.m_pitch = m_pitch[octave * 12 + note];
+ }
+ else
+ {
+ voice.m_pitch = 0;
+ }
+
+ voice.m_pitch_counter = 0;
+ voice.m_wave_pos = 0xff;
+ voice.m_wave_out[0] = voice.m_wave_out[1] = 0;
+ voice.m_env_state = ENV_ATTACK1;
+ voice.m_env_counter = 0;
+
+ if (m_master)
+ reset_timer();
+}
+
+/**************************************************************************/
+void upd931_device::reset_timer()
+{
+ const attotime period = attotime::from_ticks(RETRIG_RATE, clock());
+ m_retrig_timer->adjust(period, 0, period);
+}
+
+/**************************************************************************/
+void upd931_device::update_env(voice_t &voice)
+{
+ const unsigned shift = BIT(m_flags, FLAG_ENV_SHIFT, 2);
+
+ switch (voice.m_env_state)
+ {
+ case ENV_IDLE:
+ return;
+
+ case ENV_ATTACK1:
+ {
+ static const u16 rates[] =
+ {
+ 0, 2048, 512, 256, 160, 80, 32, 8
+ };
+
+ const u8 val = BIT(m_flags, FLAG_ATTACK1, 3);
+ u32 rate;
+
+ if (val == 0)
+ rate = VOLUME_MAX; // value 0 = instant
+ else if (val < 4 && voice.m_env_counter >= (0xe0 << VOLUME_SHIFT))
+ rate = 160 << shift; // values 1-3 slow down at 7/8 of max volume
+ else
+ rate = rates[val] << shift;
+ voice.m_env_counter = std::min(voice.m_env_counter + rate, VOLUME_MAX);
+
+ // only increase wave A level if it isn't set to rise during attack2 instead
+ if (BIT(m_flags, FLAG_ATTACK2_A))
+ voice.m_env_level[0] = 0;
+ else
+ voice.m_env_level[0] = voice.m_env_counter;
+ voice.m_env_level[1] = voice.m_env_counter;
+
+ if (voice.m_env_counter >= VOLUME_MAX)
+ {
+ voice.m_env_counter = 0;
+ voice.m_env_state = ENV_ATTACK2;
+ }
+ break;
+ }
+
+ case ENV_ATTACK2:
+ {
+ static const u32 rates[] =
+ {
+ 0, 2048, 256, 128, 64, 32, 16, 8
+ };
+
+ const u8 val = BIT(m_flags, FLAG_ATTACK2, 3);
+ u32 rate;
+
+ if (val == 0)
+ rate = VOLUME_MAX;
+ else
+ rate = rates[val] << shift;
+ voice.m_env_counter = std::min(voice.m_env_counter + rate, VOLUME_MAX);
+
+ // fade wave A in, if specified
+ if (BIT(m_flags, FLAG_ATTACK2_A))
+ voice.m_env_level[0] = voice.m_env_counter;
+
+ // fade wave B out, if specified
+ if (BIT(m_flags, FLAG_ATTACK2_B))
+ voice.m_env_level[1] = VOLUME_MAX - voice.m_env_counter;
+
+ if (voice.m_env_counter >= VOLUME_MAX)
+ voice.m_env_state = ENV_DECAY1;
+
+ break;
+ }
+
+ case ENV_DECAY1:
+ {
+ static const u32 rates[] =
+ {
+ 2048, 640, 160, 32, 16, 8, 2, 0
+ };
+
+ const u8 val = BIT(m_flags, FLAG_DECAY1, 3);
+ const u32 rate = rates[val] << shift;
+
+ if (voice.m_env_counter < rate)
+ {
+ voice.m_env_counter = 0;
+ voice.m_env_state = ENV_IDLE;
+ }
+ else
+ {
+ voice.m_env_counter -= rate;
+ }
+
+ voice.m_env_level[0] = voice.m_env_counter;
+ // only fade wave B if it didn't already fade out during attack2
+ if (voice.m_env_level[1])
+ voice.m_env_level[1] = voice.m_env_counter;
+
+ if (!BIT(m_flags, FLAG_DECAY2_DISABLE))
+ {
+ // transition to decay2 at 1/2 or 1/4 of max volume, if enabled
+ const u8 decay2_level = BIT(m_flags, FLAG_DECAY2_LEVEL) ? 0x40 : 0x80;
+ if (voice.m_env_counter < (decay2_level << VOLUME_SHIFT))
+ voice.m_env_state = ENV_DECAY2;
+ }
+
+ break;
+ }
+
+ case ENV_DECAY2:
+ {
+ // apply reverb rate (below 1/8 max volume) or decay2 rate
+ u16 rate;
+
+ if (m_reverb && voice.m_env_counter < (0x20 << VOLUME_SHIFT))
+ rate = 1 << shift;
+ else if (BIT(m_flags, FLAG_DECAY2))
+ rate = 3 << shift;
+ else
+ rate = 6 << shift;
+
+ if (voice.m_env_counter < rate)
+ {
+ voice.m_env_counter = 0;
+ voice.m_env_state = ENV_IDLE;
+ }
+ else
+ {
+ voice.m_env_counter -= rate;
+ }
+
+ voice.m_env_level[0] = voice.m_env_counter;
+ if (voice.m_env_level[1])
+ voice.m_env_level[1] = voice.m_env_counter;
+
+ break;
+ }
+
+ case ENV_RELEASE:
+ {
+ // apply reverb rate (below 1/8 max volume), sustain rate (if enabled) or default release rate
+ u16 rate = 512 << shift;
+
+ if (!voice.m_force_release)
+ {
+ if (m_reverb && voice.m_env_counter < (0x20 << VOLUME_SHIFT))
+ rate = 1 << shift;
+ else if (m_sustain == 1)
+ rate = 16 << shift;
+ else if (m_sustain == 2)
+ rate = 12 << shift;
+ }
+
+ if (voice.m_env_counter < rate)
+ {
+ voice.m_env_counter = 0;
+ voice.m_env_state = ENV_IDLE;
+ }
+ else
+ {
+ voice.m_env_counter -= rate;
+ }
+
+ // fade each wave individually because if keyed off during attack, they may be different from each other
+ voice.m_env_level[0] = std::min(voice.m_env_level[0], voice.m_env_counter);
+ voice.m_env_level[1] = std::min(voice.m_env_level[1], voice.m_env_counter);
+
+ break;
+ }
+
+ }
+}
+
+/**************************************************************************/
+void upd931_device::update_wave(voice_t &voice)
+{
+ voice.m_pitch_counter += voice.m_pitch;
+
+ const u8 cycle = BIT(voice.m_pitch_counter, PITCH_SHIFT + 4, 2);
+ /*
+ the part of the counter which is treated as the sample address depends on if key scaling is active.
+ if it is active, then the voice alternates between a narrowed waveform and silence, which alters
+ the timbre without affecting pitch
+ */
+ u8 pos = BIT(voice.m_pitch_counter, PITCH_SHIFT - voice.m_timbre_shift, 4 + voice.m_timbre_shift);
+ if (pos == voice.m_wave_pos || pos >= 0x10)
+ return;
+
+ voice.m_wave_pos = pos;
+
+ // play every other cycle backwards, if enabled
+ if (BIT(m_flags, FLAG_MIRROR) && BIT(cycle, 0))
+ pos ^= 0xf;
+
+ // flag bits determine which of 4 consecutive cycles to apply waveforms A and B
+ const unsigned cycle_mode[] =
+ {
+ BIT(m_flags, FLAG_MODE_A, 2),
+ BIT(m_flags, FLAG_MODE_B, 2)
+ };
+
+ static const u8 cycle_mask[] =
+ {
+ 0xf, // always on
+ 0x5, // on, off, on, off
+ 0x1, // on 1x, off 3x
+ 0x3 // on 2x, off 2x
+ };
+
+ static const s8 steps[] =
+ {
+ 0, 1, 2, 2, 4, 4, 8, 8,
+ 0, -1, -2, -2, -4, -4, -8, -8
+ };
+
+ for (int i = 0; i < 2; i++)
+ {
+ // check if this waveform is enabled for this cycle
+ if (!BIT(cycle_mask[cycle_mode[i]], cycle))
+ continue;
+
+ s8 step = steps[m_wave[i][pos] & 0xf];
+ // invert waveform on every other cycle, if enabled
+ if (BIT(m_flags, FLAG_INVERT) && BIT(cycle, 0))
+ step = -step;
+
+ voice.m_wave_out[i] += step;
+ voice.m_wave_out[i] = util::sext(voice.m_wave_out[i] & 0x3f, 6);
+ }
+}
diff --git a/src/devices/sound/upd931.h b/src/devices/sound/upd931.h
new file mode 100644
index 00000000000..a97397fc11b
--- /dev/null
+++ b/src/devices/sound/upd931.h
@@ -0,0 +1,153 @@
+// license:BSD-3-Clause
+// copyright-holders: Devin Acker
+
+/***************************************************************************
+ NEC/Casio uPD931 synthesis chip
+***************************************************************************/
+
+#ifndef MAME_SOUND_UPD931_H
+#define MAME_SOUND_UPD931_H
+
+#pragma once
+
+#include <array>
+
+class upd931_device : public device_t, public device_sound_interface, public device_memory_interface
+{
+public:
+ static constexpr feature_type imperfect_features() { return feature::SOUND; }
+
+ upd931_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock = 0);
+
+ auto &set_master(bool master) { m_master = master; return *this; }
+
+ auto filter_cb() { return m_filter_cb.bind(); }
+ auto sync_cb() { return m_sync_cb.bind(); }
+
+ void i1_w(int state);
+ void i2_w(int state);
+ void i3_w(int state);
+
+ void db_w(u8 data);
+ u8 db_r();
+
+ void sync_w(int state);
+
+protected:
+ device_memory_interface::space_config_vector memory_space_config() const override ATTR_COLD;
+
+ virtual void device_start() override ATTR_COLD;
+ virtual void device_reset() override ATTR_COLD;
+ virtual void device_clock_changed() override;
+
+ virtual void sound_stream_update(sound_stream &stream, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs) override;
+
+private:
+ static constexpr unsigned PITCH_SHIFT = 15;
+ static constexpr unsigned VOLUME_SHIFT = 15;
+ static constexpr unsigned VOLUME_MAX = (0xff << VOLUME_SHIFT);
+
+ static constexpr unsigned CLOCKS_PER_SAMPLE = 8;
+ static constexpr unsigned RETRIG_RATE = 0x60000;
+
+ enum
+ {
+ FLAG_DECAY1 = 0, // bits 0-2 = decay1 rate
+ FLAG_ATTACK2 = 3, // bits 3-5 = attack2 rate
+ FLAG_ATTACK1 = 6, // bits 6-8 = attack1 rate
+ FLAG_DECAY2_LEVEL = 9, // bit 9 = decay1->decay2 transition point
+ FLAG_DECAY2 = 10, // bit 10 = decay2 rate
+ FLAG_RETRIGGER = 11, // bit 11 = retrigger note during decay (mandolin effect)
+ FLAG_ENV_SPLIT = 12, // bit 12 = wave A envelope split(?)
+ FLAG_ATTACK2_B = 13, // bit 13 = wave B fades out during attack2
+ FLAG_ATTACK2_A = 14, // bit 14 = wave A fades in during attack2
+ FLAG_DECAY2_DISABLE = 15, // bit 15 = disable decay2
+ FLAG_ENV_SHIFT = 16, // bit 16-17 = envelope/vibrato rate shift
+ FLAG_MIRROR = 19, // bit 19 = mirror waveform on alternate cycles
+ FLAG_INVERT = 20, // bit 20 = invert waveform on alternate cycles
+ FLAG_MODE_B = 21, // bit 21-22 = wave B output mode
+ FLAG_MODE_A = 23, // bit 23-24 = wave A output mode
+ FLAG_WAVE_SEL = 25, // bit 25 = wave data input select
+ };
+
+ enum
+ {
+ ENV_IDLE,
+ ENV_ATTACK1,
+ ENV_ATTACK2,
+ ENV_DECAY1,
+ ENV_DECAY2,
+ ENV_RELEASE
+ };
+
+ struct voice_t
+ {
+ u8 m_note = 0;
+ u8 m_octave = 0;
+ u16 m_pitch = 0;
+ u32 m_pitch_counter = 0;
+ s8 m_timbre_shift = 0;
+
+ u8 m_wave_pos = 0;
+ s8 m_wave_out[2] = {0};
+
+ u8 m_env_state = ENV_IDLE;
+ u32 m_env_counter = 0;
+ u32 m_env_level[2] = {0};
+ u8 m_force_release = 0;
+ };
+
+ void io_map(address_map &map) ATTR_COLD;
+
+ TIMER_CALLBACK_MEMBER(timer_tick);
+
+ void note_w(offs_t offset, u8 data);
+ void octave_w(offs_t offset, u8 data);
+ void wave_pos_w(u8 data);
+ void wave_data_w(u8 data);
+ void flags_w(u8 data);
+ void status_latch_w(offs_t offset, u8 data);
+ void vibrato_w(u8 data);
+ void sustain_w(u8 data);
+ void note_on_w(offs_t offset, u8 data);
+
+ void note_on(voice_t &voice);
+ void reset_timer();
+ void update_env(voice_t &voice);
+ void update_wave(voice_t &voice);
+
+ address_space_config m_io_config;
+ memory_access<8, 0, 0, ENDIANNESS_LITTLE>::specific m_io;
+
+ sound_stream *m_stream;
+ emu_timer *m_retrig_timer;
+
+ devcb_write8 m_filter_cb;
+ devcb_write_line m_sync_cb;
+
+ bool m_master;
+
+ u16 m_pitch[12*6 + 1]; // valid note range = C1 to C7
+
+ u8 m_db;
+ u8 m_i1, m_i2, m_i3;
+
+ u8 m_addr, m_data;
+ u8 m_status;
+
+ // data global to all voices
+ u8 m_wave[2][16];
+ u8 m_wave_pos;
+ u8 m_vibrato;
+ u8 m_sustain;
+ u8 m_reverb;
+
+ u32 m_flags;
+ u32 m_last_clock;
+
+ std::array<voice_t, 8> m_voice;
+};
+
+DECLARE_DEVICE_TYPE(UPD931, upd931_device)
+
+#endif // MAME_SOUND_UPD931_H
diff --git a/src/emu/xtal.cpp b/src/emu/xtal.cpp
index e5536621d08..0af864ce6fb 100644
--- a/src/emu/xtal.cpp
+++ b/src/emu/xtal.cpp
@@ -119,6 +119,7 @@ const double XTAL::known_xtals[] = {
4'433'619, // 4.433619_MHz_XTAL PAL color subcarrier (technically 4.43361875mhz)
4'608'000, // 4.608_MHz_XTAL Luxor ABC-77 keyboard (Keytronic custom part #48-300-107 is equivalent)
4'915'200, // 4.9152_MHz_XTAL -
+ 4'946'864, // 4.946864_MHz_XTAL Casiotone 8000
4'952'000, // 4.952_MHz_XTAL IGS M036 based mahjong games, for TT5665 sound chip
5'000'000, // 5_MHz_XTAL Mutant Night
5'068'800, // 5.0688_MHz_XTAL Usually used as MC2661 or COM8116 baud rate clock
diff --git a/src/mame/casio/ct8000.cpp b/src/mame/casio/ct8000.cpp
new file mode 100644
index 00000000000..8406f107d0c
--- /dev/null
+++ b/src/mame/casio/ct8000.cpp
@@ -0,0 +1,857 @@
+// license:BSD-3-Clause
+// copyright-holders:Devin Acker
+// thanks-to:BCM
+
+/***************************************************************************
+ Casiotone 8000 / "Symphonytron" system
+
+ The Symphonytron was a modular electronic organ produced by Casio in 1983.
+ The full system consists of:
+
+ - up to two Casiotone 8000 keyboards (8049 CPU, 2x uPD931 "vowel-consonant synthesis")
+ - RC-1 accompaniment unit (uPD7801 CPU, uPD930 rhythm generator, analog percussion)
+ - MB-1 memory unit (8049 CPU, 2x uPD931, RAM cartridge slot)
+ - FK-1 pedal keyboard (8049 CPU, single uPD931)
+ - CS-100 or CS-200 keyboard stand with built-in mixer
+
+ The keyboards and memory unit all connect to the RC-1 via 14-pin DIN connectors.
+ Although the RAM cart slot is located on the MB-1, all actual access to the cart is controlled
+ remotely by the RC-1, which uses the cart to record and play back both rhythm/chord and melody
+ data. The MB-1's sound hardware is then used to play back recorded melody data independently of
+ the keyboards. The RC-1 also has a "tone mix" feature, where note data received from one keyboard
+ is automatically forwarded to the other.
+
+ The individual units can also be used on their own; the MB-1 will also respond to notes and tone
+ selection commands via the DIN connector, but it needs the RC-1 present to do much else.
+ It's marked as "not working" for this reason.
+
+ This driver also features MIDI in/thru support via an "adapter" device which translates a subset
+ of MIDI messages into the protocol used with the original connectors.
+
+ TODO:
+ - volume/expression pedal (for all systems)
+ - fix aliasing in BBD output for some presets
+
+***************************************************************************/
+
+#include "emu.h"
+
+#include "ct8000_midi.h"
+#include "bus/midi/midiinport.h"
+#include "bus/midi/midioutport.h"
+#include "cpu/mcs48/mcs48.h"
+#include "machine/i8243.h"
+#include "machine/rescap.h"
+#include "sound/bbd.h"
+#include "sound/flt_biquad.h"
+#include "sound/flt_rc.h"
+#include "sound/flt_vol.h"
+#include "sound/mixer.h"
+#include "sound/upd931.h"
+
+#include "speaker.h"
+
+#define LOG_VCO (1<<1)
+
+// #define VERBOSE (LOG_GENERAL | LOG_VCO)
+
+#include "logmacro.h"
+
+#include "ct8000.lh"
+#include "ctfk1.lh"
+
+namespace {
+
+class ct8000_state : public driver_device
+{
+public:
+ ct8000_state(machine_config const &mconfig, device_type type, char const *tag)
+ : driver_device(mconfig, type, tag)
+ , m_maincpu(*this, "maincpu")
+ , m_io(*this, "ioport%c", 'a')
+ , m_midi(*this, "midi")
+ , m_931a(*this, "upd931a")
+ , m_931b(*this, "upd931b")
+ , m_mixer(*this, "mixer")
+ , m_filter_rc(*this, "filter_rc%u", 0)
+ , m_filter_bq(*this, "filter_bq%u", 0)
+ , m_bbd(*this, "bbd")
+ , m_bank(*this, "bank")
+ , m_inputs(*this, "KC%X", 0) // schematic uses KC0-9, A-C
+ {
+ }
+
+ void config_base(machine_config &config) ATTR_COLD;
+ void ctmb1(machine_config &config) ATTR_COLD;
+ void ct8000(machine_config &config) ATTR_COLD;
+ void ctfk1(machine_config &config) ATTR_COLD;
+
+ ioport_value switch_r() { return m_switch; }
+ DECLARE_INPUT_CHANGED_MEMBER(switch_w);
+ DECLARE_INPUT_CHANGED_MEMBER(switch_clear_w);
+
+protected:
+ void ct8000_map(address_map &map) ATTR_COLD;
+ void ct8000_io_map(address_map &map) ATTR_COLD;
+
+ virtual void driver_start() override ATTR_COLD;
+
+ void p1_w(u8 data);
+ u8 p1_r();
+ void p2_w(u8 data);
+ u8 p2_r();
+
+ void p4a_w(u8 data);
+ void p5a_w(u8 data);
+ void p6a_w(u8 data);
+ void p7a_w(u8 data);
+
+ void p4b_w(u8 data);
+ void p7b_w(u8 data);
+
+ u8 keys_r();
+
+ void filter_main_w(u8 data);
+ void filter_sub_w(u8 data);
+ void filter_bass_w(u8 data);
+
+ void pll_w(offs_t offset, u8 data);
+ virtual void update_clocks();
+
+ attoseconds_t chorus_cv(attotime const &curtime);
+
+ required_device<i8049_device> m_maincpu;
+ required_device_array<i8243_device, 2> m_io;
+
+ required_device<ct8000_midi_device> m_midi;
+
+ required_device<upd931_device> m_931a;
+ optional_device<upd931_device> m_931b;
+ required_device<mixer_device> m_mixer;
+ optional_device_array<filter_rc_device, 3> m_filter_rc;
+ optional_device_array<filter_biquad_device, 3> m_filter_bq;
+ optional_device<mn3207_device> m_bbd;
+
+ required_memory_bank m_bank;
+
+ optional_ioport_array<13> m_inputs;
+
+ u16 m_key_select;
+ u8 m_key_enable;
+
+ ioport_value m_switch;
+
+ u16 m_pll_counter[2];
+ u16 m_pll_ref[2];
+
+ u8 m_clock_select;
+ u8 m_clock_div;
+};
+
+void ct8000_state::ct8000_map(address_map &map)
+{
+ map(0x800, 0xfff).bankr("bank");
+}
+
+void ct8000_state::ct8000_io_map(address_map &map)
+{
+ map(0x00, 0xff).rw(FUNC(ct8000_state::keys_r), FUNC(ct8000_state::pll_w));
+}
+
+//**************************************************************************
+void ct8000_state::config_base(machine_config &config)
+{
+ I8049(config, m_maincpu, 4.946864_MHz_XTAL);
+ m_maincpu->set_addrmap(AS_PROGRAM, &ct8000_state::ct8000_map);
+ m_maincpu->set_addrmap(AS_IO, &ct8000_state::ct8000_io_map);
+ m_maincpu->p1_out_cb().set(FUNC(ct8000_state::p1_w));
+ m_maincpu->p1_in_cb().set(FUNC(ct8000_state::p1_r));
+ m_maincpu->p2_out_cb().set(FUNC(ct8000_state::p2_w));
+ m_maincpu->p2_in_cb().set(FUNC(ct8000_state::p2_r));
+
+ CT8000_MIDI(config, m_midi);
+ m_midi->int_cb().set_inputline(m_maincpu, MCS48_INPUT_IRQ);
+ m_maincpu->t0_in_cb().set(m_midi, FUNC(ct8000_midi_device::ack_r));
+
+ midi_port_device &mdin(MIDI_PORT(config, "mdin", midiin_slot, "midiin"));
+ mdin.rxd_handler().set(m_midi, FUNC(ct8000_midi_device::rx_w));
+
+ MIDI_PORT(config, "mdthru", midiout_slot, "midiout");
+ mdin.rxd_handler().append("mdthru", FUNC(midi_port_device::write_txd));
+
+ I8243(config, m_io[0]);
+ m_maincpu->prog_out_cb().set(m_io[0], FUNC(i8243_device::prog_w));
+ m_io[0]->p4_out_cb().set(FUNC(ct8000_state::p4a_w));
+ m_io[0]->p5_out_cb().set(FUNC(ct8000_state::p5a_w));
+ m_io[0]->p6_out_cb().set(FUNC(ct8000_state::p6a_w));
+ m_io[0]->p7_out_cb().set(FUNC(ct8000_state::p7a_w));
+
+ I8243(config, m_io[1]);
+ m_maincpu->prog_out_cb().append(m_io[1], FUNC(i8243_device::prog_w));
+ m_io[1]->p4_out_cb().set(FUNC(ct8000_state::p4b_w));
+ m_io[1]->p5_in_cb().set(m_midi, FUNC(ct8000_midi_device::data_r));
+ m_io[1]->p5_out_cb().set_nop();
+ m_io[1]->p6_out_cb().set(m_midi, FUNC(ct8000_midi_device::data_w));
+ m_io[1]->p7_out_cb().set(FUNC(ct8000_state::p7b_w));
+}
+
+//**************************************************************************
+void ct8000_state::ctmb1(machine_config &config)
+{
+ config_base(config);
+
+ SPEAKER(config, "lspeaker").front_left();
+ SPEAKER(config, "rspeaker").front_right();
+
+ MIXER(config, m_mixer);
+ m_mixer->add_route(0, "lspeaker", 1.0);
+ m_mixer->add_route(0, "rspeaker", 1.0);
+
+ // 931 A - sub (consonant) waveform
+ UPD931(config, m_931a, m_maincpu->clock());
+ m_931a->set_master(false);
+ m_931a->filter_cb().set(FUNC(ct8000_state::filter_sub_w));
+ /*
+ Both of the uPD931s have a fairly large amount of headroom in their outputs,
+ which is partly compensated for via adjustable gain (see filter_main_w and filter_sub_w),
+ but the base output gain should be boosted as well to provide a decent volume.
+ */
+ m_931a->add_route(0, m_filter_rc[0], 2.0);
+
+ // sub HPF
+ FILTER_RC(config, m_filter_rc[0]).add_route(0, m_filter_bq[0], 1.0);
+ FILTER_BIQUAD(config, m_filter_bq[0]).add_route(0, m_filter_rc[1], 1.0);
+
+ // sub LPF
+ FILTER_RC(config, m_filter_rc[1]).add_route(0, m_filter_bq[1], 1.0);
+ FILTER_BIQUAD(config, m_filter_bq[1]).add_route(0, "filter_ac0", 1.0);
+
+ FILTER_RC(config, "filter_ac0").set_ac().add_route(0, m_mixer, 1.0);
+
+ // 931 B - main (vowel) waveform
+ UPD931(config, m_931b, m_maincpu->clock());
+ m_931b->sync_cb().set(m_931a, FUNC(upd931_device::sync_w));
+ m_931b->filter_cb().set(FUNC(ct8000_state::filter_main_w));
+ m_931b->add_route(0, m_filter_rc[2], 2.0);
+
+ // main LPF
+ FILTER_RC(config, m_filter_rc[2]).add_route(0, m_filter_bq[2], 1.0);
+ FILTER_BIQUAD(config, m_filter_bq[2]).add_route(0, "filter_ac1", 1.0);
+
+ FILTER_RC(config, "filter_ac1").set_ac().add_route(0, m_mixer, 1.0);
+
+ MN3207(config, m_bbd).set_cv_handler(FUNC(ct8000_state::chorus_cv));
+ m_mixer->add_route(0, m_bbd, 1.0);
+ m_bbd->add_route(ALL_OUTPUTS, "chorus", 0.5);
+
+ auto &bbd_mixer = MIXER(config, "chorus");
+ bbd_mixer.add_route(0, "lspeaker", 0.4);
+ bbd_mixer.add_route(0, "rspeaker", -0.4);
+}
+
+//**************************************************************************
+void ct8000_state::ct8000(machine_config &config)
+{
+ ctmb1(config);
+ config.set_default_layout(layout_ct8000);
+}
+
+//**************************************************************************
+void ct8000_state::ctfk1(machine_config &config)
+{
+ config_base(config);
+ m_io[0]->p4_out_cb().set_nop(); // no switchable clock
+
+ // valid program/patch numbers on FK-1 start at 1 instead of 0 for some reason
+ m_midi->set_base_program(1);
+
+ SPEAKER(config, "speaker").front_center();
+
+ MIXER(config, m_mixer).add_route(0, "speaker", 1.0);
+
+ UPD931(config, m_931a, m_maincpu->clock());
+ m_931a->filter_cb().set(FUNC(ct8000_state::filter_bass_w));
+ m_931a->add_route(0, m_filter_rc[0], 4.0); // boost volume even more since the FK-1 is monophonic
+
+ FILTER_RC(config, m_filter_rc[0]).add_route(0, m_filter_bq[0], 1.0);
+ FILTER_BIQUAD(config, m_filter_bq[0]).add_route(0, "filter_ac", 1.0);
+
+ FILTER_RC(config, "filter_ac").set_ac().add_route(0, m_mixer, 1.0);
+
+ config.set_default_layout(layout_ctfk1);
+}
+
+//**************************************************************************
+void ct8000_state::driver_start()
+{
+ m_bank->configure_entries(0, 2, memregion("bankrom")->base(), 0x800);
+
+ m_key_select = 0xffff;
+ m_key_enable = 0;
+ m_switch = 0;
+
+ m_pll_counter[0] = m_pll_counter[1] = 0;
+ m_pll_ref[0] = m_pll_ref[1] = 0;
+
+ m_clock_select = m_clock_div = 0;
+
+ save_item(NAME(m_key_select));
+ save_item(NAME(m_key_enable));
+ save_item(NAME(m_switch));
+
+ save_item(NAME(m_pll_counter));
+ save_item(NAME(m_pll_ref));
+
+ save_item(NAME(m_clock_select));
+ save_item(NAME(m_clock_div));
+}
+
+//**************************************************************************
+void ct8000_state::p1_w(u8 data)
+{
+ // bit 0-3: 931 data
+ // bit 4: 931 strobe 1 (I1)
+ // bit 5: 931 strobe 2 (I2)
+ // bit 6: 931 B select (ct8000) / 931 select (fk1)
+ // bit 7: 931 A select (ct8000) / unused (fk1)
+ m_931a->db_w(data & 0xf);
+ m_931a->i1_w(BIT(data, 4));
+ m_931a->i2_w(BIT(data, 5));
+
+ if (m_931b)
+ {
+ m_931b->db_w(data & 0xf);
+ m_931b->i1_w(BIT(data, 4));
+ m_931b->i2_w(BIT(data, 5));
+ m_931b->i3_w(BIT(data, 6));
+ m_931a->i3_w(BIT(data, 7));
+ }
+ else
+ {
+ m_931a->i3_w(BIT(data, 6));
+ }
+}
+
+//**************************************************************************
+u8 ct8000_state::p1_r()
+{
+ u8 status = m_931a->db_r();
+ if (m_931b)
+ status &= m_931b->db_r();
+
+ return 0xf0 | status;
+}
+
+//**************************************************************************
+void ct8000_state::p2_w(u8 data)
+{
+ m_io[0]->p2_w(data & 0xf);
+ m_io[1]->p2_w(data & 0xf);
+ m_io[0]->cs_w(BIT(data, 4));
+ m_io[1]->cs_w(BIT(data, 6));
+ m_bank->set_entry(BIT(data, 7));
+}
+
+//**************************************************************************
+u8 ct8000_state::p2_r()
+{
+ return 0xf0 | (m_io[0]->p2_r() & m_io[1]->p2_r());
+}
+
+//**************************************************************************
+void ct8000_state::p4a_w(u8 data)
+{
+ // bit 0: master 931 clock select (1 = master VCO, 0 = slave VCO)
+ // bit 1-2: master 931 clock divider (3, 6, 12, 4)
+ // bit 3: 931 reset
+ m_clock_select = BIT(~data, 0);
+ m_clock_div = BIT(data, 1, 2);
+
+ update_clocks();
+}
+
+//**************************************************************************
+void ct8000_state::p5a_w(u8 data)
+{
+ // key matrix out bits 4-7
+ m_key_select &= 0xff0f;
+ m_key_select |= (data << 4);
+}
+
+//**************************************************************************
+void ct8000_state::p6a_w(u8 data)
+{
+ // bit 0: unused
+ // bit 1: key matrix in enable (active low)
+ // bit 2-3: key matrix out bits 8-9
+ m_key_enable = BIT(~data, 1);
+ m_key_select &= 0xfcff;
+ m_key_select |= (BIT(data, 2, 2) << 8);
+}
+
+//**************************************************************************
+void ct8000_state::p7a_w(u8 data)
+{
+ // key matrix out bits 0-3
+ m_key_select &= 0xfff0;
+ m_key_select |= data;
+}
+
+//**************************************************************************
+void ct8000_state::p4b_w(u8 data)
+{
+ // bit 0: data out strobe to RC-1
+ // bit 1: data in ack to RC-1 (active low)
+ // bit 2: stereo chorus enable (active low)
+ // bit 3: stereo chorus enable for line in (active low)
+ m_midi->strobe_w(BIT(data, 0));
+ m_midi->ack_w(BIT(data, 1));
+
+ if (m_bbd)
+ m_bbd->set_input_gain(0, BIT(data, 2) ? 0 : 1.0);
+}
+
+//**************************************************************************
+void ct8000_state::p7b_w(u8 data)
+{
+ // bit 0-2: key matrix out bits 10-12
+ // bit 3: mute
+ m_key_select &= 0xe3ff;
+ m_key_select |= ((data & 0x7) << 10);
+
+ // mute is applied to mixed/filtered 931 output before the stereo chorus
+ const double gain = BIT(data, 3) ? 1.0 : 0.0;
+ m_mixer->set_input_gain(0, gain);
+ m_mixer->set_input_gain(1, gain);
+}
+
+//**************************************************************************
+u8 ct8000_state::keys_r()
+{
+ u8 data = 0xff;
+
+ if (m_key_enable)
+ {
+ for (int i = 0; i < m_inputs.size(); i++)
+ if (!BIT(m_key_select, i))
+ data &= m_inputs[i].read_safe(0xff);
+ }
+
+ return data;
+}
+
+//**************************************************************************
+INPUT_CHANGED_MEMBER(ct8000_state::switch_w)
+{
+ if (!oldval && newval)
+ m_switch |= param;
+}
+
+//**************************************************************************
+INPUT_CHANGED_MEMBER(ct8000_state::switch_clear_w)
+{
+ if (!oldval && newval)
+ m_switch = 0;
+}
+
+//**************************************************************************
+void ct8000_state::filter_main_w(u8 data)
+{
+ // bit 0-1 = main LPF cutoff
+ // bit 2-3 = absolute gain
+ double r, c;
+ if (BIT(data, 1))
+ {
+ r = RES_K(68);
+ c = CAP_P(470) + BIT(data, 0) ? CAP_P(330) : 0;
+ }
+ else
+ {
+ r = RES_K(43);
+ c = CAP_P(390);
+ }
+
+ m_filter_rc[2]->filter_rc_set_RC(filter_rc_device::LOWPASS, r, 0, 0, CAP_N(2.2));
+ m_filter_bq[2]->opamp_sk_lowpass_modify(r, r, RES_M(999.99), RES_R(0.001), CAP_N(5.6), c);
+
+ static const double gain[] = { 3.79, 2.82, 1.94, 1 };
+ m_mixer->set_output_gain(0, gain[BIT(data, 2, 2)]);
+}
+
+//**************************************************************************
+void ct8000_state::filter_sub_w(u8 data)
+{
+ // bit 0 = sub HPF enable
+ // bit 1 = sub LPF cutoff
+ // bit 2 = sub LPF disable
+ // bit 3 = consonant gain
+ if (BIT(data, 0))
+ {
+ const double c = CAP_N(2.2);
+ m_filter_rc[0]->filter_rc_set_RC(filter_rc_device::HIGHPASS, RES_K(18), 0, 0, c);
+ m_filter_bq[0]->opamp_sk_highpass_modify(RES_K(6.8), RES_K(220), RES_M(999.99), RES_R(0.001), c, c);
+ }
+ else
+ {
+ m_filter_rc[0]->filter_rc_set_RC(filter_rc_device::HIGHPASS, 0, 0, 0, 0);
+ m_filter_bq[0]->modify_raw(0, 0, 1, 0, 0);
+ }
+
+ if (BIT(data, 2))
+ {
+ m_filter_rc[1]->filter_rc_set_RC(filter_rc_device::LOWPASS, 0, 0, 0, 0);
+ m_filter_bq[1]->modify_raw(0, 0, 1, 0, 0);
+ }
+ else
+ {
+ const double r = BIT(data, 1) ? RES_K(22) : RES_K(56);
+ const double c = BIT(data, 1) ? CAP_P(430) : CAP_P(330);
+
+ m_filter_rc[1]->filter_rc_set_RC(filter_rc_device::LOWPASS, r, 0, 0, CAP_N(2.2));
+ m_filter_bq[1]->opamp_sk_lowpass_modify(r, r, RES_M(999.99), RES_R(0.001), CAP_N(5.6), c);
+ }
+
+ m_filter_bq[1]->set_output_gain(0, BIT(data, 3) ? 0.468 : 1);
+}
+
+//**************************************************************************
+void ct8000_state::filter_bass_w(u8 data)
+{
+ // bit 0 = LPF cutoff
+ // bit 1 = LPF enable (seems to be incorrectly inverted in the schematic)
+ // bit 2-3 = gain
+
+ if (BIT(data, 1))
+ {
+ const double r = RES_K(12);
+ const double c = CAP_N(3.3) + BIT(data, 0) ? CAP_N(10) : 0;
+
+ m_filter_rc[0]->filter_rc_set_RC(filter_rc_device::LOWPASS, r, 0, 0, CAP_N(22));
+ m_filter_bq[0]->opamp_sk_lowpass_modify(r, r, RES_M(999.99), RES_R(0.001), CAP_N(56), c);
+ }
+ else
+ {
+ m_filter_rc[0]->filter_rc_set_RC(filter_rc_device::LOWPASS, 0, 0, 0, 0);
+ m_filter_bq[0]->modify_raw(0, 0, 1, 0, 0);
+ }
+
+ static const double gain[] = { 3.79, 2.82, 1.94, 1 };
+ m_mixer->set_output_gain(ALL_OUTPUTS, gain[BIT(data, 2, 2)]);
+}
+
+//**************************************************************************
+void ct8000_state::pll_w(offs_t offset, u8 data)
+{
+ data &= 0xf;
+
+ for (int i = 0; i < 2; i++)
+ {
+ if (!BIT(offset, 3 + i))
+ {
+ switch (offset & 7)
+ {
+ case 0:
+ m_pll_counter[i] &= 0x3ff0;
+ m_pll_counter[i] |= data;
+ break;
+
+ case 1:
+ m_pll_counter[i] &= 0x3f0f;
+ m_pll_counter[i] |= (data << 4);
+ break;
+
+ case 2:
+ m_pll_counter[i] &= 0x30ff;
+ m_pll_counter[i] |= (data << 8);
+ break;
+
+ case 3:
+ m_pll_counter[i] &= 0x0fff;
+ m_pll_counter[i] |= ((data & 3) << 12);
+ break;
+
+ case 4:
+ m_pll_ref[i] &= 0xff0;
+ m_pll_ref[i] |= data;
+ break;
+
+ case 5:
+ m_pll_ref[i] &= 0xf0f;
+ m_pll_ref[i] |= (data << 4);
+ break;
+
+ case 6:
+ m_pll_ref[i] &= 0x0ff;
+ m_pll_ref[i] |= (data << 8);
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+
+ update_clocks();
+}
+
+//**************************************************************************
+void ct8000_state::update_clocks()
+{
+ double clock_scale[2] = { 1.0, 1.0 };
+
+ // master VCO freq (controls 931 B)
+ const int sel = m_clock_select & 1;
+ if (m_931b && m_pll_counter[sel] && m_pll_ref[sel])
+ {
+ // PLLs are tuned so that increasing/decreasing the counter by 1 raises or lowers pitch by ~1.5 cents
+ clock_scale[0] = (2.0 * m_pll_counter[sel]) / m_pll_ref[sel];
+
+ switch (m_clock_div & 3)
+ {
+ case 0: clock_scale[0] /= 3; break;
+ case 1: clock_scale[0] /= 6; break;
+ case 2: clock_scale[0] /= 12; break;
+ case 3: clock_scale[0] /= 4; break;
+ }
+
+ m_931b->set_clock_scale(clock_scale[0]);
+ }
+
+ // slave VCO freq (controls 931 A and CPU)
+ if (m_pll_counter[1] && m_pll_ref[1])
+ {
+ clock_scale[1] = (2.0 * m_pll_counter[1]) / (3 * m_pll_ref[1]);
+
+ m_maincpu->set_clock_scale(clock_scale[1]);
+ m_931a->set_clock_scale(clock_scale[1]);
+ }
+
+ LOGMASKED(LOG_VCO, "VCO #1 %.3f MHz, #2 %.3f MHz\n",
+ clock_scale[0] * m_maincpu->unscaled_clock() / 1'000'000,
+ clock_scale[1] * m_maincpu->unscaled_clock() / 1'000'000);
+}
+
+//**************************************************************************
+attoseconds_t ct8000_state::chorus_cv(attotime const &cvtime)
+{
+ // 62.5 to 80 kHz, varies at 0.6666... Hz
+ double pos = cvtime.as_double() / 1.5;
+ pos -= std::floor(pos);
+ pos = (pos < 0.5) ? (2 * pos) : 2 * (1.0 - pos);
+ const double bbd_freq = 62500 + (80000 - 62500) * pos;
+
+ return HZ_TO_ATTOSECONDS(bbd_freq);
+}
+
+
+INPUT_PORTS_START(ct8000)
+ PORT_START("KC0")
+ PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("C2")
+ PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("C#2")
+ PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("D2")
+ PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("D#2")
+ PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("E2")
+ PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("F2")
+ PORT_BIT(0xc0, IP_ACTIVE_LOW, IPT_UNUSED)
+
+ PORT_START("KC1")
+ PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("F#2")
+ PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("G2")
+ PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("G#2")
+ PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("A2")
+ PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("A#2")
+ PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("B2")
+ PORT_BIT(0xc0, IP_ACTIVE_LOW, IPT_UNUSED)
+
+ PORT_START("KC2")
+ PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("C3")
+ PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("C#3")
+ PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("D3")
+ PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("D#3")
+ PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("E3")
+ PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("F3")
+ PORT_BIT(0xc0, IP_ACTIVE_LOW, IPT_UNUSED)
+
+ PORT_START("KC3")
+ PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("F#3")
+ PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("G3")
+ PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("G#3")
+ PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("A3")
+ PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("A#3")
+ PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("B3")
+ PORT_BIT(0xc0, IP_ACTIVE_LOW, IPT_UNUSED)
+
+ PORT_START("KC4")
+ PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("C4")
+ PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("C#4")
+ PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("D4")
+ PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("D#4")
+ PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("E4")
+ PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("F4")
+ PORT_BIT(0xc0, IP_ACTIVE_LOW, IPT_UNUSED)
+
+ PORT_START("KC5")
+ PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("F#4")
+ PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("G4")
+ PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("G#4")
+ PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("A4")
+ PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("A#4")
+ PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("B4")
+ PORT_BIT(0xc0, IP_ACTIVE_LOW, IPT_UNUSED)
+
+ PORT_START("KC6")
+ PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("C5")
+ PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("C#5")
+ PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("D5")
+ PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("D#5")
+ PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("E5")
+ PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("F5")
+ PORT_BIT(0xc0, IP_ACTIVE_LOW, IPT_UNUSED)
+
+ PORT_START("KC7")
+ PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("F#5")
+ PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("G5")
+ PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("G#5")
+ PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("A5")
+ PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("A#5")
+ PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("B5")
+ PORT_BIT(0xc0, IP_ACTIVE_LOW, IPT_UNUSED)
+
+ PORT_START("KC8")
+ PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("C6")
+ PORT_BIT(0xfe, IP_ACTIVE_LOW, IPT_UNUSED)
+
+ PORT_START("KC9")
+ PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("Sustain Pedal")
+ PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_KEYPAD) PORT_NAME("Tune Up")
+ PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_KEYPAD) PORT_NAME("Tune Down")
+ PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_KEYPAD) PORT_NAME("Stereo Chorus") PORT_TOGGLE
+ PORT_BIT(0xf0, IP_ACTIVE_LOW, IPT_UNUSED)
+
+ PORT_START("KCA")
+ PORT_BIT(0x07, IP_ACTIVE_LOW, IPT_CUSTOM) PORT_CUSTOM_MEMBER(FUNC(ct8000_state::switch_r))
+ PORT_BIT(0xf8, IP_ACTIVE_LOW, IPT_UNUSED)
+
+ PORT_START("KCB")
+ PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_KEYPAD) PORT_NAME("Tone Memory 1")
+ PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_KEYPAD) PORT_NAME("Tone Memory 2")
+ PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_KEYPAD) PORT_NAME("Tone Memory 3")
+ PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_KEYPAD) PORT_NAME("Tone Memory 4")
+ PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_KEYPAD) PORT_NAME("Tone Set") PORT_TOGGLE
+ PORT_BIT(0xe0, IP_ACTIVE_LOW, IPT_UNUSED)
+
+ PORT_START("KCC")
+ PORT_BIT(0x0f, IP_ACTIVE_LOW, IPT_UNUSED)
+ PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_KEYPAD) PORT_NAME("Vibrato") PORT_TOGGLE
+ PORT_BIT(0xe0, IP_ACTIVE_LOW, IPT_UNUSED)
+
+ PORT_START("SWITCH")
+ PORT_BIT(0x01, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_NAME("Effect Cancel") PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(ct8000_state::switch_clear_w), 0)
+ PORT_BIT(0x02, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_NAME("Sustain") PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(ct8000_state::switch_w), 0x1)
+ PORT_BIT(0x04, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_NAME("Reverb") PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(ct8000_state::switch_w), 0x4)
+INPUT_PORTS_END
+
+INPUT_PORTS_START(ctfk1)
+ PORT_START("KC0")
+ PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("C1")
+ PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("C#1")
+ PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("D1")
+ PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("D#1")
+ PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("E1")
+ PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("F1")
+ PORT_BIT(0xc0, IP_ACTIVE_LOW, IPT_UNUSED)
+
+ PORT_START("KC1")
+ PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("F#1")
+ PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("G1")
+ PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("G#1")
+ PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("A1")
+ PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("A#1")
+ PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("B1")
+ PORT_BIT(0xc0, IP_ACTIVE_LOW, IPT_UNUSED)
+
+ PORT_START("KC2")
+ PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("C2")
+ PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("C#2")
+ PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("D2")
+ PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("D#2")
+ PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("E2")
+ PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("F2")
+ PORT_BIT(0xc0, IP_ACTIVE_LOW, IPT_UNUSED)
+
+ PORT_START("KC9")
+ PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_UNUSED)
+ PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_KEYPAD) PORT_NAME("Tune Up")
+ PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_KEYPAD) PORT_NAME("Tune Down")
+ PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_UNUSED)
+ PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_KEYPAD) PORT_NAME("Bass Select") PORT_TOGGLE
+ PORT_BIT(0xe0, IP_ACTIVE_LOW, IPT_UNUSED)
+
+ PORT_START("KCA")
+ PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_KEYPAD) PORT_NAME("Sustain") PORT_TOGGLE
+ PORT_BIT(0xfe, IP_ACTIVE_LOW, IPT_UNUSED)
+
+ PORT_START("KCB")
+ PORT_BIT(0x01, IP_ACTIVE_HIGH, IPT_UNUSED) // KI0->KCB diode
+ PORT_BIT(0x0e, IP_ACTIVE_LOW, IPT_UNUSED)
+ PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_KEYPAD) PORT_NAME("Tone Set") PORT_TOGGLE
+ PORT_BIT(0xe0, IP_ACTIVE_LOW, IPT_UNUSED)
+INPUT_PORTS_END
+
+INPUT_PORTS_START(ctmb1)
+ PORT_START("KC0")
+ PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_KEYPAD) PORT_NAME("Reverse")
+ PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_KEYPAD) PORT_NAME("Play")
+ PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_KEYPAD) PORT_NAME("Forward")
+ PORT_BIT(0xf8, IP_ACTIVE_LOW, IPT_UNUSED)
+
+ PORT_START("KC1")
+ PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_KEYPAD) PORT_NAME("Keyboard Synchro")
+ PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_KEYPAD) PORT_NAME("Record")
+ PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_KEYPAD) PORT_NAME("Reset")
+ PORT_BIT(0xf8, IP_ACTIVE_LOW, IPT_UNUSED)
+
+ PORT_START("KC2")
+ PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_KEYPAD) PORT_NAME("Pause")
+ PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_KEYPAD) PORT_NAME("Demonstration")
+ PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_KEYPAD) PORT_NAME("Number Select")
+ PORT_BIT(0xf8, IP_ACTIVE_LOW, IPT_UNUSED)
+
+ PORT_START("KC9")
+ PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_UNUSED)
+ PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_KEYPAD) PORT_NAME("Tune Up")
+ PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_KEYPAD) PORT_NAME("Tune Down")
+ PORT_BIT(0xf8, IP_ACTIVE_LOW, IPT_UNUSED)
+INPUT_PORTS_END
+
+
+ROM_START(ct8000)
+ ROM_REGION(0x800, "maincpu", 0)
+ ROM_LOAD("upd8049c-364.bin", 0x000, 0x800, CRC(8c9b3f40) SHA1(5507117671300fe350df39b4ca70239079a1b0e7))
+
+ ROM_REGION(0x1000, "bankrom", 0)
+ ROM_LOAD("ct8000_2732.bin", 0x0000, 0x1000, CRC(e6f1f3f9) SHA1(637a9d2b6be2f12240724c8aa5744c5f28ac4af0))
+ROM_END
+
+ROM_START(ctfk1)
+ ROM_REGION(0x800, "maincpu", 0)
+ ROM_LOAD("upd8049c-364.bin", 0x000, 0x800, CRC(8c9b3f40) SHA1(5507117671300fe350df39b4ca70239079a1b0e7))
+
+ ROM_REGION(0x1000, "bankrom", 0)
+ ROM_LOAD("fk1_2732.bin", 0x0000, 0x1000, CRC(f7416356) SHA1(6d92aa5f377769327451334324f1f32b52951968))
+ROM_END
+
+ROM_START(ctmb1)
+ ROM_REGION(0x800, "maincpu", 0)
+ ROM_LOAD("upd8049c-364.bin", 0x000, 0x800, CRC(8c9b3f40) SHA1(5507117671300fe350df39b4ca70239079a1b0e7))
+
+ ROM_REGION(0x1000, "bankrom", 0)
+ ROM_LOAD("mb1_2732.bin", 0x0000, 0x1000, CRC(fc128110) SHA1(8ff31fdccb8200836cf414bc709d5b9a68279205))
+ROM_END
+
+} // anonymous namespace
+
+// YEAR NAME PARENT COMPAT MACHINE INPUT CLASS INIT COMPANY FULLNAME FLAGS
+SYST( 1983, ct8000, 0, 0, ct8000, ct8000, ct8000_state, empty_init, "Casio", "Casiotone 8000", MACHINE_SUPPORTS_SAVE )
+SYST( 1983, ctfk1, 0, 0, ctfk1, ctfk1, ct8000_state, empty_init, "Casio", "Casiotone FK-1", MACHINE_SUPPORTS_SAVE )
+SYST( 1983, ctmb1, 0, 0, ctmb1, ctmb1, ct8000_state, empty_init, "Casio", "Casiotone MB-1", MACHINE_NOT_WORKING | MACHINE_SUPPORTS_SAVE )
diff --git a/src/mame/casio/ct8000_midi.cpp b/src/mame/casio/ct8000_midi.cpp
new file mode 100644
index 00000000000..5d89f817cbe
--- /dev/null
+++ b/src/mame/casio/ct8000_midi.cpp
@@ -0,0 +1,409 @@
+// license:BSD-3-Clause
+// copyright-holders:Devin Acker
+
+/***************************************************************************
+ Casiotone 8000 / Symphonytron MIDI interface
+
+ Translates MIDI messages into the 4-bit parallel protocol used by the Symphonytron.
+ Aside from Note Off/On and Program Change messages, this also handles:
+ - CC #1 (vibrato)
+ - CC #64 (sustain pedal)
+ - CC #69 (sustain switch)
+ - CC #91 (reverb)
+ - CC #93 (chorus)
+ - CC #120 (all sound off)
+ - CC #121 (reset controllers)
+ - CC #123 (all notes off)
+ - CC #124-127 (equivalent to 123, otherwise no effect)
+ - MIDI reset (status 0xFF)
+
+ Vibrato, reverb, and chorus CCs all behave as switches, where 64-127 is "on".
+
+-------------------------------------------------------------------------------
+
+ Summary of 4-bit Symphonytron messages
+
+ Messages are sent in 4-bit increments over the DIN connector, in the following order:
+ - high nibble of command
+ - number of following nibbles (normally 1 or 3)
+ - low nibble of command
+ - high nibble of data (if any)
+ - low nibble of data (if any)
+
+ The exception is the reset command, which is a single 0xF nibble.
+
+ The CT-8000 recognizes these messages:
+ 10 xx - set effects
+ 11 xx - lock effects (corresponding buttons/pedal do nothing while set)
+ 12 xx - ??? effects
+ ^^ bit 0 = reverb
+ bit 1 = vibrato
+ bit 2 = sustain pedal (only affects the local keyboard)
+ bit 3 = stereo chorus
+ bit 4 = sustain switch
+
+ 20 xx - set lowest possible note to send (default is none)
+ 21 xx - set highest possible note to send (default is none)
+ 22 xx - set lowest possible note to receive/play (default is 0x21 = C2)
+ 23 xx - set highest possible note to receive/play (default is 0x61 = C6)
+ 24 - stop all notes
+ 28 xx - note on or off
+ ^^ bit 7 = note on, bits 6-4 = octave, bits 3-0 = note from 0x1 (C) to 0xC (B)
+
+ C0 xx - set tuning (signed, 1.5 cent increments, 0x32 = quarter tone up)
+
+ E1 xx - set patch number
+
+ F - reset instrument
+
+ The MB-1 recognizes all of the above, plus:
+ A0 xx - ???
+ A1 xx - ???
+ A2 xx - ???
+ A3 xx - ???
+ A4 - clear values from A0-A3
+
+***************************************************************************/
+
+#include "emu.h"
+#include "ct8000_midi.h"
+
+#include <algorithm>
+
+DEFINE_DEVICE_TYPE(CT8000_MIDI, ct8000_midi_device, "ct8000_midi", "Casiotone 8000 MIDI adapter")
+
+namespace {
+
+INPUT_PORTS_START(ct8000_midi)
+ PORT_START("CHANNEL")
+ PORT_CONFNAME(0xff, 0x00, "MIDI channel")
+ PORT_CONFSETTING( 0x00, "1")
+ PORT_CONFSETTING( 0x01, "2")
+ PORT_CONFSETTING( 0x02, "3")
+ PORT_CONFSETTING( 0x03, "4")
+ PORT_CONFSETTING( 0x04, "5")
+ PORT_CONFSETTING( 0x05, "6")
+ PORT_CONFSETTING( 0x06, "7")
+ PORT_CONFSETTING( 0x07, "8")
+ PORT_CONFSETTING( 0x08, "9")
+ PORT_CONFSETTING( 0x09, "10")
+ PORT_CONFSETTING( 0x0a, "11")
+ PORT_CONFSETTING( 0x0b, "12")
+ PORT_CONFSETTING( 0x0c, "13")
+ PORT_CONFSETTING( 0x0d, "14")
+ PORT_CONFSETTING( 0x0e, "15")
+ PORT_CONFSETTING( 0x0f, "16")
+INPUT_PORTS_END
+
+} // anonymous namespace
+
+ct8000_midi_device::ct8000_midi_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : device_t(mconfig, CT8000_MIDI, tag, owner, clock)
+ , device_serial_interface(mconfig, *this)
+ , m_channel(*this, "CHANNEL")
+ , m_int_cb(*this)
+ , m_base_program(0)
+{
+}
+
+/**************************************************************************/
+ioport_constructor ct8000_midi_device::device_input_ports() const
+{
+ return INPUT_PORTS_NAME(ct8000_midi);
+}
+
+/**************************************************************************/
+void ct8000_midi_device::device_start()
+{
+ m_midi_data[0] = m_midi_data[1] = 0;
+
+ m_ack = 1;
+ std::fill(std::begin(m_out_data), std::end(m_out_data), 0);
+
+ m_in_data = 0xf;
+ m_in_strobe = 0;
+
+ set_data_frame(1, 8, PARITY_NONE, STOP_BITS_1);
+ set_rcv_rate(31250);
+ set_tra_rate(0);
+
+ save_item(NAME(m_status));
+ save_item(NAME(m_midi_data));
+ save_item(NAME(m_midi_data_pos));
+ save_item(NAME(m_midi_data_size));
+ save_item(NAME(m_midi_notes_on));
+ save_item(NAME(m_sustain));
+
+ save_item(NAME(m_ack));
+ save_item(NAME(m_int));
+ save_item(NAME(m_out_data));
+ save_item(NAME(m_out_data_read));
+ save_item(NAME(m_out_data_write));
+ save_item(NAME(m_out_data_space));
+ save_item(NAME(m_out_data_controls));
+
+ save_item(NAME(m_in_data));
+ save_item(NAME(m_in_strobe));
+}
+
+/**************************************************************************/
+void ct8000_midi_device::device_reset()
+{
+ m_status = 0;
+ m_midi_data_pos = m_midi_data_size = 0;
+ m_out_data_space = sizeof(m_out_data);
+ m_sustain = 0;
+
+ std::fill(std::begin(m_midi_notes_on), std::begin(m_midi_notes_on), 0);
+
+ m_out_data_read = m_out_data_write = 0;
+ m_out_data_controls = 0;
+
+ m_int_cb(m_int = 0);
+}
+
+/**************************************************************************/
+void ct8000_midi_device::rcv_complete()
+{
+ receive_register_extract();
+
+ if (is_receive_framing_error())
+ {
+ m_status = 0;
+ return;
+ }
+
+ const u8 data = get_received_char();
+ if (data < 0x80)
+ {
+ if (m_status >= 0x80)
+ {
+ m_midi_data[m_midi_data_pos++] = data;
+ if (m_midi_data_pos == m_midi_data_size)
+ {
+ midi_message();
+ m_midi_data_pos = 0;
+ }
+ }
+ }
+ else if (data < 0xf0)
+ {
+ m_status = data;
+
+ if (data >= 0xc0 && data <= 0xdf)
+ m_midi_data_size = 1;
+ else
+ m_midi_data_size = 2;
+ m_midi_data_pos = 0;
+ }
+ else if (data < 0xf8)
+ {
+ m_status = 0;
+ }
+ else if (data == 0xff)
+ {
+ reset();
+ send_nibble(0xf);
+ }
+}
+
+/**************************************************************************/
+u8 ct8000_midi_device::data_r()
+{
+ return m_out_data[m_out_data_read];
+}
+
+/**************************************************************************/
+void ct8000_midi_device::ack_w(int state)
+{
+ if (m_ack && !state)
+ {
+ m_int_cb(m_int = 0);
+ m_out_data_read++;
+ m_out_data_space++;
+ m_out_data_read %= sizeof(m_out_data);
+ }
+ else if (!m_ack && state)
+ {
+ if (m_out_data_read != m_out_data_write)
+ m_int_cb(m_int = 1);
+ }
+
+ m_ack = state;
+}
+
+/**************************************************************************/
+void ct8000_midi_device::strobe_w(int state)
+{
+ if (!m_in_strobe && state)
+ logerror("data write: %x\n", m_in_data);
+
+ m_in_strobe = state;
+}
+
+/**************************************************************************/
+void ct8000_midi_device::midi_message()
+{
+ if ((m_status & 0xf) != m_channel->read())
+ return;
+
+ switch (m_status >> 4)
+ {
+ case 0x8: // note off
+ m_midi_data[1] = 0;
+ [[fallthrough]];
+ case 0x9: // note on
+ if (m_midi_data[0] >= NOTE_MIN && m_midi_data[0] <= NOTE_MAX)
+ send_note(m_midi_data[0] - NOTE_MIN, m_midi_data[1]);
+ break;
+
+ case 0xb: // controller
+ switch (m_midi_data[0])
+ {
+ case 1: // mod wheel / vibrato
+ update_control(FX_VIBRATO);
+ break;
+
+ case 64: // sustain pedal
+ update_sustain(m_midi_data[1]);
+ break;
+
+ case 69: // hold 2 / sustain switch
+ update_control(FX_SUSTAIN);
+ break;
+
+ case 91: // reverb
+ update_control(FX_REVERB);
+ break;
+
+ case 93: // chorus
+ update_control(FX_CHORUS);
+ break;
+
+ case 120: // all sound off
+ send_cmd(CMD_STOP);
+ break;
+
+ case 121: // reset controllers
+ m_out_data_controls = 0;
+ send_cmd(CMD_EFFECTS, 0);
+ update_sustain(0);
+ break;
+
+ case 123: // all notes off
+ case 124: // omni off
+ case 125: // omni on
+ case 126: // mono mode
+ case 127: // poly mode
+ for (unsigned i = 0; i < sizeof(m_midi_notes_on); i++)
+ {
+ if (m_midi_notes_on[i])
+ send_note(i, 0);
+ }
+ break;
+ }
+ break;
+
+ case 0xc: // program change
+ send_cmd(CMD_TONE, m_midi_data[0] + m_base_program);
+ break;
+ }
+}
+
+/**************************************************************************/
+void ct8000_midi_device::send_nibble(u8 data)
+{
+ m_out_data[m_out_data_write++] = data;
+ m_out_data_write %= sizeof(m_out_data);
+ m_out_data_space--;
+
+ if (m_ack && !m_int)
+ m_int_cb(m_int = 1);
+}
+
+/**************************************************************************/
+void ct8000_midi_device::send_cmd(u8 cmd)
+{
+ if (m_out_data_space >= 3)
+ {
+ send_nibble(cmd >> 4);
+ send_nibble(1); // number of following nibbles
+ send_nibble(cmd & 0xf);
+ }
+}
+
+/**************************************************************************/
+void ct8000_midi_device::send_cmd(u8 cmd, u8 data)
+{
+ if (m_out_data_space >= 5)
+ {
+ send_nibble(cmd >> 4);
+ send_nibble(3); // number of following nibbles
+ send_nibble(cmd & 0xf);
+ send_nibble(data >> 4);
+ send_nibble(data & 0xf);
+ }
+}
+
+/**************************************************************************/
+void ct8000_midi_device::send_note(u8 note, u8 on)
+{
+ if (!on && m_sustain)
+ {
+ m_midi_notes_on[note] = 0x80;
+ return;
+ }
+
+ u8 out_note = ((note / 12) << 4) + (note % 12) + 1;
+
+ if (on && m_midi_notes_on[note])
+ {
+ // force existing note off even if sustained
+ send_cmd(CMD_NOTE, out_note);
+ }
+
+ if (on)
+ out_note |= 0x80;
+
+ send_cmd(CMD_NOTE, out_note);
+ m_midi_notes_on[note] = on;
+}
+
+/**************************************************************************/
+void ct8000_midi_device::update_control(u8 control)
+{
+ if (m_midi_data[1] >= 0x40)
+ {
+ if (m_out_data_controls & control)
+ return;
+
+ m_out_data_controls |= control;
+ }
+ else
+ {
+ if (!(m_out_data_controls & control))
+ return;
+
+ m_out_data_controls &= ~control;
+ }
+
+ send_cmd(CMD_EFFECTS, m_out_data_controls);
+}
+
+/**************************************************************************/
+void ct8000_midi_device::update_sustain(u8 value)
+{
+ if (value >= 0x40)
+ {
+ m_sustain = 1;
+ }
+ else if (m_sustain)
+ {
+ m_sustain = 0;
+ for (unsigned i = 0; i < sizeof(m_midi_notes_on); i++)
+ {
+ if (BIT(m_midi_notes_on[i], 7))
+ send_note(i, 0);
+ }
+ }
+}
diff --git a/src/mame/casio/ct8000_midi.h b/src/mame/casio/ct8000_midi.h
new file mode 100644
index 00000000000..8adfa7dbe12
--- /dev/null
+++ b/src/mame/casio/ct8000_midi.h
@@ -0,0 +1,89 @@
+// license:BSD-3-Clause
+// copyright-holders: Devin Acker
+
+#ifndef MAME_CASIO_CT8000_MIDI_H
+#define MAME_CASIO_CT8000_MIDI_H
+
+#pragma once
+
+#include "diserial.h"
+
+class ct8000_midi_device : public device_t, public device_serial_interface
+{
+public:
+ ct8000_midi_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock = 0);
+
+ auto &set_base_program(u8 base) { m_base_program = base; return *this; }
+
+ auto int_cb() { return m_int_cb.bind(); }
+
+ u8 data_r();
+ void data_w(u8 data) { m_in_data = data; }
+
+ int ack_r() { return m_in_strobe; }
+ void ack_w(int state);
+
+ void strobe_w(int state);
+
+protected:
+ virtual ioport_constructor device_input_ports() const override ATTR_COLD;
+ virtual void device_start() override ATTR_COLD;
+ virtual void device_reset() override ATTR_COLD;
+
+ virtual void rcv_complete() override;
+
+private:
+ static constexpr unsigned NOTE_MIN = 12; // C0
+ static constexpr unsigned NOTE_MAX = 107; // B7
+
+ enum
+ {
+ CMD_EFFECTS = 0x10,
+ CMD_STOP = 0x24,
+ CMD_NOTE = 0x28,
+ CMD_TONE = 0xe1,
+ };
+
+ enum
+ {
+ FX_REVERB = 0x01,
+ FX_VIBRATO = 0x02,
+ FX_PEDAL = 0x04, // only affects the local keyboard, so unused here
+ FX_CHORUS = 0x08,
+ FX_SUSTAIN = 0x10
+ };
+
+ void midi_message();
+
+ void send_nibble(u8 data);
+ void send_cmd(u8 cmd);
+ void send_cmd(u8 cmd, u8 data);
+ void send_note(u8 note, u8 on);
+ void update_control(u8 control);
+ void update_sustain(u8 value);
+
+ required_ioport m_channel;
+ devcb_write_line m_int_cb;
+
+ u8 m_base_program;
+
+ u8 m_status;
+ u8 m_midi_data[2];
+ u8 m_midi_data_pos, m_midi_data_size;
+ u8 m_midi_notes_on[NOTE_MAX - NOTE_MIN + 1];
+ u8 m_sustain;
+
+ u8 m_ack;
+ u8 m_int;
+ u8 m_out_data[0x1000];
+ u16 m_out_data_read, m_out_data_write;
+ u16 m_out_data_space;
+ u8 m_out_data_controls;
+
+ u8 m_in_data;
+ u8 m_in_strobe;
+};
+
+DECLARE_DEVICE_TYPE(CT8000_MIDI, ct8000_midi_device)
+
+#endif // MAME_CASIO_CT8000_MIDI_H
diff --git a/src/mame/layout/ct8000.lay b/src/mame/layout/ct8000.lay
new file mode 100644
index 00000000000..ac1f5b2c056
--- /dev/null
+++ b/src/mame/layout/ct8000.lay
@@ -0,0 +1,423 @@
+<?xml version="1.0"?>
+<!--
+license:CC0-1.0
+-->
+
+<mamelayout version="2">
+ <element name="bg"><rect><color red="0.75" green="0.75" blue="0.775" /></rect></element>
+ <element name="line"><rect><color red="0.55" green="0.55" blue="0.575" /></rect></element>
+ <element name="line_white"><rect><color red="1" green="1" blue="0.95" /></rect></element>
+ <element name="line_black"><rect><color red="0" green="0" blue="0" /></rect></element>
+
+ <element name="button_back">
+ <rect><bounds x="0" y="0" width="1" height="20" /><color red="0.55" green="0.55" blue="0.575" /></rect>
+ <rect><bounds x="0" y="1" width="1" height="18" /><color red="0.65" green="0.65" blue="0.675" /></rect>
+ </element>
+ <element name="panel_back"><rect><color red="0.7" green="0.7" blue="0.725" /></rect></element>
+
+ <!-- text elements -->
+ <element name="cancel"><text string="CANCEL"><color red="0" blue="0" green="0" /></text></element>
+ <element name="sustain"><text string="SUSTAIN"><color red="0" blue="0" green="0" /></text></element>
+ <element name="reverb"><text string="REVERB"><color red="0" blue="0" green="0" /></text></element>
+ <element name="vibrato"><text string="VIBRATO"><color red="0" blue="0" green="0" /></text></element>
+ <element name="stereo"><text string="STEREO"><color red="0" blue="0" green="0" /></text></element>
+ <element name="chorus"><text string="CHORUS"><color red="0" blue="0" green="0" /></text></element>
+ <element name="effect"><text string="EFFECT"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone_memory"><text string="TONE MEMORY"><color red="0" blue="0" green="0" /></text></element>
+ <element name="num1"><text string="1"><color red="0" blue="0" green="0" /></text></element>
+ <element name="num2"><text string="2"><color red="0" blue="0" green="0" /></text></element>
+ <element name="num3"><text string="3"><color red="0" blue="0" green="0" /></text></element>
+ <element name="num4"><text string="4"><color red="0" blue="0" green="0" /></text></element>
+ <element name="mode"><text string="MODE"><color red="0" blue="0" green="0" /></text></element>
+ <element name="set"><text string="SET"><color red="0" blue="0" green="0" /></text></element>
+ <element name="play"><text string="PLAY"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tune"><text string="TUNE"><color red="0" blue="0" green="0" /></text></element>
+
+ <element name="tone1"><text string="PIANO 1"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone2"><text string="PIPE ORGAN 1"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone3"><text string="PIANO 2"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone4"><text string="PIPE ORGAN 2"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone5"><text string="PIANO 3"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone6"><text string="PIANO 4"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone7"><text string="PIPE ORGAN 3"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone8"><text string="HONKY TONK"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone8b"><text string="PIANO"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone9"><text string="PIPE ORGAN 4"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone10"><text string="ZIMBALON"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone11"><text string="PIPE ORGAN 5"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone12"><text string="SYNTH"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone12b"><text string="SOUND"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone13"><text string="HARPSI-"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone13b"><text string="CHORD 1"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone14"><text string="ACCORDION"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone15"><text string="HARPSI-"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone15b"><text string="CHORD 2"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone16"><text string="BAGPIPE"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone17"><text string="CELESTA 1"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone18"><text string="CELESTA 2"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone19"><text string="OBOE"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone20"><text string="MARIMBA"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone21"><text string="CLARINET"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone22"><text string="HARP 1"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone23"><text string="FLUTE"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone24"><text string="HARP 2"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone25"><text string="HARP 3"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone26"><text string="SHAKUHACHI"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone27"><text string="KOTO 1"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone28"><text string="WA-WA"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone29"><text string="KOTO 2"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone30"><text string="TAISHO"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone30b"><text string="KOTO"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone31"><text string="HORN"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone32"><text string="BANJO"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone33"><text string="FLUEGELHORN"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone34"><text string="MANDOLIN"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone35"><text string="TRUMPET"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone36"><text string="GUITAR 1"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone37"><text string="GUITAR 2"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone38"><text string="BRASS ENSEMBLE"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone39"><text string="GUITAR 3"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone40"><text string="STRING ENSEMBLE"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone41"><text string="ELEC."><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone41b"><text string="GUITAR 1"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone42"><text string="ELEC."><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone42b"><text string="GUITAR 2"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone43"><text string="VIOLIN"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone44"><text string="ELEC."><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone44b"><text string="GUITAR 3"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone45"><text string="CELLO"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone46"><text string="ELEC."><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone46b"><text string="GUITAR 4"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone47"><text string="DOUBLE BASS"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone48"><text string="ELEC."><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone48b"><text string="GUITAR 5"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone49"><text string="ELEC. BASS"><color red="0" blue="0" green="0" /></text></element>
+
+ <!-- button primitives -->
+ <element name="button_light" defstate="0">
+ <rect><color red="0.1" green="0.1" blue="0.1" /><bounds x="0" y="0" width="5" height="10" /></rect>
+ <rect state="0"><color red="0.8" green="0.8" blue="0.8" /><bounds xc="2.5" yc="5" width="4" height="9" /></rect>
+ <rect state="1"><color red="0.5" green="0.5" blue="0.5" /><bounds xc="2.5" yc="5" width="4" height="9" /></rect>
+ </element>
+
+ <element name="button_dark" defstate="0">
+ <rect><color red="0.1" green="0.1" blue="0.1" /><bounds x="0" y="0" width="5" height="10" /></rect>
+ <rect state="0"><color red="0.3" green="0.3" blue="0.3" /><bounds xc="2.5" yc="5" width="4" height="9" /></rect>
+ <rect state="1"><color red="0.15" green="0.15" blue="0.15" /><bounds xc="2.5" yc="5" width="4" height="9" /></rect>
+ </element>
+
+ <element name="button_down" defstate="0">
+ <rect><color red="0.1" green="0.1" blue="0.1" /><bounds x="0" y="0" width="5" height="10" /></rect>
+ <rect state="0"><color red="0.3" green="0.3" blue="0.3" /><bounds xc="2.5" yc="5" width="4" height="9" /></rect>
+ <rect state="1"><color red="0.15" green="0.15" blue="0.15" /><bounds xc="2.5" yc="5" width="4" height="9" /></rect>
+ <image>
+ <bounds xc="2.5" yc="5" width="3" height="6" />
+ <data><![CDATA[
+ <?xml version="1.0" standalone="no"?>
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10" version="1.1">
+ <polygon points="2,2 5,8 8,2" fill="white" stroke="white" stroke-width="0.4" />
+ </svg>
+ ]]></data>
+ </image>
+ </element>
+
+ <element name="button_up" defstate="0">
+ <rect><color red="0.1" green="0.1" blue="0.1" /><bounds x="0" y="0" width="5" height="10" /></rect>
+ <rect state="0"><color red="0.3" green="0.3" blue="0.3" /><bounds xc="2.5" yc="5" width="4" height="9" /></rect>
+ <rect state="1"><color red="0.15" green="0.15" blue="0.15" /><bounds xc="2.5" yc="5" width="4" height="9" /></rect>
+ <image>
+ <bounds xc="2.5" yc="5" width="3" height="6" />
+ <data><![CDATA[
+ <?xml version="1.0" standalone="no"?>
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10" version="1.1">
+ <polygon points="2,8 5,2 8,8" fill="white" stroke="white" stroke-width="0.4" />
+ </svg>
+ ]]></data>
+ </image>
+ </element>
+
+ <element name="button_set" defstate="0">
+ <rect><color red="0.1" green="0.1" blue="0.1" /><bounds x="0" y="0" width="10" height="10" /></rect>
+ <rect state="0"><color red="1.0" green="0.45" blue="0.0" /><bounds xc="5" yc="5" width="9" height="9" /></rect>
+ <rect state="1"><color red="0.8" green="0.25" blue="0" /><bounds xc="5" yc="5" width="9" height="9" /></rect>
+ </element>
+
+ <element name="button_icon_up">
+ <rect><color red="0.7" green="0.7" blue="0.725" /><bounds x="0" y="0" width="5" height="5" /></rect>
+
+ <rect><color red="0" green="0" blue="0" /><bounds xc="2.5" y="4" width="5" height="1" /></rect>
+ <rect><color red="0" green="0" blue="0" /><bounds xc="2.5" y="1" width="3.5" height="4" /></rect>
+ </element>
+
+ <element name="button_icon_down">
+ <rect><color red="0.7" green="0.7" blue="0.725" /><bounds x="0" y="0" width="5" height="5" /></rect>
+
+ <rect><color red="0" green="0" blue="0" /><bounds xc="2.5" y="4" width="5" height="1" /></rect>
+ <rect><color red="0" green="0" blue="0" /><bounds xc="2.5" y="3" width="3.5" height="2" /></rect>
+ </element>
+
+ <!-- keyboard primitives -->
+ <element name="keyfill"><rect><color red="0.2" green="0.2" blue="0.2" /></rect></element>
+ <element name="whitekey" defstate="0">
+ <rect state="0"><bounds x="0" y="0" width="45" height="504" /><color red="1.0" green="1.0" blue="1.0" /></rect>
+ <rect state="1"><bounds x="0" y="0" width="45" height="504" /><color red="0.9" green="0.9" blue="0.9" /></rect>
+ </element>
+ <element name="whitekey-l" defstate="0">
+ <rect state="0"><bounds x="0" y="0" width="45" height="332" /><color red="1.0" green="1.0" blue="1.0" /></rect>
+ <rect state="0"><bounds x="0" y="332" width="79" height="172" /><color red="1.0" green="1.0" blue="1.0" /></rect>
+ <rect state="1"><bounds x="0" y="0" width="45" height="332" /><color red="0.9" green="0.9" blue="0.9" /></rect>
+ <rect state="1"><bounds x="0" y="332" width="79" height="172" /><color red="0.9" green="0.9" blue="0.9" /></rect>
+ </element>
+ <element name="whitekey-m" defstate="0">
+ <rect state="0"><bounds x="13" y="0" width="53" height="332" /><color red="1.0" green="1.0" blue="1.0" /></rect>
+ <rect state="0"><bounds x="0" y="332" width="79" height="172" /><color red="1.0" green="1.0" blue="1.0" /></rect>
+ <rect state="1"><bounds x="13" y="0" width="53" height="332" /><color red="0.9" green="0.9" blue="0.9" /></rect>
+ <rect state="1"><bounds x="0" y="332" width="79" height="172" /><color red="0.9" green="0.9" blue="0.9" /></rect>
+ </element>
+ <element name="whitekey-lm" defstate="0">
+ <rect state="0"><bounds x="10" y="0" width="44" height="332" /><color red="1.0" green="1.0" blue="1.0" /></rect>
+ <rect state="0"><bounds x="0" y="332" width="79" height="172" /><color red="1.0" green="1.0" blue="1.0" /></rect>
+ <rect state="1"><bounds x="10" y="0" width="44" height="332" /><color red="0.9" green="0.9" blue="0.9" /></rect>
+ <rect state="1"><bounds x="0" y="332" width="79" height="172" /><color red="0.9" green="0.9" blue="0.9" /></rect>
+ </element>
+ <element name="whitekey-rm" defstate="0">
+ <rect state="0"><bounds x="22" y="0" width="44" height="332" /><color red="1.0" green="1.0" blue="1.0" /></rect>
+ <rect state="0"><bounds x="0" y="332" width="79" height="172" /><color red="1.0" green="1.0" blue="1.0" /></rect>
+ <rect state="1"><bounds x="22" y="0" width="44" height="332" /><color red="0.9" green="0.9" blue="0.9" /></rect>
+ <rect state="1"><bounds x="0" y="332" width="79" height="172" /><color red="0.9" green="0.9" blue="0.9" /></rect>
+ </element>
+ <element name="whitekey-r" defstate="0">
+ <rect state="0"><bounds x="34" y="0" width="45" height="332" /><color red="1.0" green="1.0" blue="1.0" /></rect>
+ <rect state="0"><bounds x="0" y="332" width="79" height="172" /><color red="1.0" green="1.0" blue="1.0" /></rect>
+ <rect state="1"><bounds x="34" y="0" width="45" height="332" /><color red="0.9" green="0.9" blue="0.9" /></rect>
+ <rect state="1"><bounds x="0" y="332" width="79" height="172" /><color red="0.9" green="0.9" blue="0.9" /></rect>
+ </element>
+ <element name="blackkey" defstate="0">
+ <rect state="0"><bounds x="0" y="0" width="44" height="324" /><color red="0.0" green="0.0" blue="0.0" /></rect>
+ <rect state="0"><bounds x="4" y="0" width="36" height="320" /><color red="0.1" green="0.1" blue="0.1" /></rect>
+ <rect state="1"><bounds x="0" y="0" width="44" height="324" /><color red="0.1" green="0.1" blue="0.1" /></rect>
+ <rect state="1"><bounds x="4" y="0" width="36" height="320" /><color red="0.1" green="0.1" blue="0.1" /></rect>
+ </element>
+
+ <!-- left button group -->
+ <group name="left">
+ <bounds x="0" y="0" width="80" height="120" />
+ <element ref="panel_back"><bounds x="0" y="0" width="80" height="120" /></element>
+
+ <element ref="line_black"><bounds xc="26" y="25" width="28" height="4" /></element>
+ <element ref="panel_back"><bounds xc="19.25" y="26" width="12.5" height="4" /></element>
+ <element ref="panel_back"><bounds xc="32.75" y="26" width="12.5" height="4" /></element>
+
+ <element ref="button_back"><bounds x="0" y="35" width="80" height="20" /></element>
+ <element ref="cancel"><bounds xc="12" y="30" width="15" height="4" /></element>
+ <element ref="button_dark" inputtag="SWITCH" inputmask="0x01"><bounds xc="12" yc="45" width="9" height="18" /></element>
+ <element ref="sustain"><bounds xc="26" y="30" width="15" height="4" /></element>
+ <element ref="button_back" inputtag="SWITCH" inputmask="0x02"><bounds xc="26" yc="45" width="9" height="18" /></element>
+ <element ref="button_dark" inputtag="KCA" inputmask="0x01"><bounds xc="26" yc="45" width="9" height="18" /></element>
+ <element ref="reverb"><bounds xc="40" y="30" width="15" height="4" /></element>
+ <element ref="button_back" inputtag="SWITCH" inputmask="0x04"><bounds xc="40" yc="45" width="9" height="18" /></element>
+ <element ref="button_dark" inputtag="KCA" inputmask="0x04"><bounds xc="40" yc="45" width="9" height="18" /></element>
+ <element ref="vibrato"><bounds xc="54" y="30" width="15" height="4" /></element>
+ <element ref="button_dark" inputtag="KCC" inputmask="0x10"><bounds xc="54" yc="45" width="9" height="18" /></element>
+ <element ref="stereo"><bounds xc="68" y="26" width="15" height="4" /></element>
+ <element ref="chorus"><bounds xc="68" y="30" width="15" height="4" /></element>
+ <element ref="button_dark" inputtag="KC9" inputmask="0x08"><bounds xc="68" yc="45" width="9" height="18" /></element>
+ <element ref="effect"><bounds xc="40" y="55" width="80" height="4" /></element>
+
+ <element ref="tone_memory"><bounds xc="40" y="62" width="80" height="4" /></element>
+ <element ref="button_back"><bounds x="0" y="70" width="80" height="20" /></element>
+ <element ref="num1"><bounds xc="19" y="66" width="15" height="4" /></element>
+ <element ref="button_light" inputtag="KCB" inputmask="0x01"><bounds xc="19" yc="80" width="9" height="16" /></element>
+ <element ref="num2"><bounds xc="33" y="66" width="15" height="4" /></element>
+ <element ref="button_light" inputtag="KCB" inputmask="0x02"><bounds xc="33" yc="80" width="9" height="16" /></element>
+ <element ref="num3"><bounds xc="47" y="66" width="15" height="4" /></element>
+ <element ref="button_light" inputtag="KCB" inputmask="0x04"><bounds xc="47" yc="80" width="9" height="16" /></element>
+ <element ref="num4"><bounds xc="61" y="66" width="15" height="4" /></element>
+ <element ref="button_light" inputtag="KCB" inputmask="0x08"><bounds xc="61" yc="80" width="9" height="16" /></element>
+
+ <element ref="mode"><bounds xc="61" y="92" width="15" height="4" /></element>
+ <element ref="button_set" inputtag="KCB" inputmask="0x10"><bounds xc="61" yc="106" width="16" height="16" /></element>
+ <!-- these are supposed to be to the right of the button but here they are on the left for better readability -->
+ <element ref="button_icon_down"><bounds xc="38" yc="102" width="3" height="3" /></element>
+ <element ref="set"><bounds xc="46" yc="102" width="15" height="4" /></element>
+ <element ref="button_icon_up"><bounds xc="38" yc="110" width="3" height="3" /></element>
+ <element ref="play"><bounds xc="46" yc="110" width="15" height="4" /></element>
+ </group>
+
+ <!-- right button group -->
+ <group name="right">
+ <element ref="panel_back"><bounds x="0" y="0" width="40" height="120" /></element>
+ <element ref="tune"><bounds xc="20" y="15" width="40" height="4" /></element>
+ <element ref="button_back"><bounds x="0" y="20" width="40" height="20" /></element>
+ <element ref="button_down" inputtag="KC9" inputmask="0x04"><bounds xc="13" yc="30" width="9" height="18" /></element>
+ <element ref="button_up" inputtag="KC9" inputmask="0x02"><bounds xc="27" yc="30" width="9" height="18" /></element>
+ </group>
+
+ <!-- keyboard group -->
+ <group name="keyboard">
+ <element ref="keyfill"><bounds x="72" y="0" width="2396" height="524" /></element>
+
+ <!-- octave 1 -->
+ <element ref="blackkey" inputtag="KC0" inputmask="0x02"><bounds x="130" y="10" width="44" height="324" /></element>
+ <element ref="blackkey" inputtag="KC0" inputmask="0x08"><bounds x="233" y="10" width="44" height="324" /></element>
+ <element ref="blackkey" inputtag="KC1" inputmask="0x01"><bounds x="373" y="10" width="44" height="324" /></element>
+ <element ref="blackkey" inputtag="KC1" inputmask="0x04"><bounds x="467" y="10" width="44" height="324" /></element>
+ <element ref="blackkey" inputtag="KC1" inputmask="0x10"><bounds x="561" y="10" width="44" height="324" /></element>
+ <element ref="whitekey-l" inputtag="KC0" inputmask="0x01"><bounds x="82" y="10" width="79" height="504" /></element>
+ <element ref="whitekey-m" inputtag="KC0" inputmask="0x04"><bounds x="164" y="10" width="79" height="504" /></element>
+ <element ref="whitekey-r" inputtag="KC0" inputmask="0x10"><bounds x="246" y="10" width="79" height="504" /></element>
+ <element ref="whitekey-l" inputtag="KC0" inputmask="0x20"><bounds x="328" y="10" width="79" height="504" /></element>
+ <element ref="whitekey-lm" inputtag="KC1" inputmask="0x02"><bounds x="410" y="10" width="79" height="504" /></element>
+ <element ref="whitekey-rm" inputtag="KC1" inputmask="0x08"><bounds x="492" y="10" width="79" height="504" /></element>
+ <element ref="whitekey-r" inputtag="KC1" inputmask="0x20"><bounds x="574" y="10" width="79" height="504" /></element>
+
+ <!-- octave 2 -->
+ <element ref="blackkey" inputtag="KC2" inputmask="0x02"><bounds x="704" y="10" width="44" height="324" /></element>
+ <element ref="blackkey" inputtag="KC2" inputmask="0x08"><bounds x="807" y="10" width="44" height="324" /></element>
+ <element ref="blackkey" inputtag="KC3" inputmask="0x01"><bounds x="947" y="10" width="44" height="324" /></element>
+ <element ref="blackkey" inputtag="KC3" inputmask="0x04"><bounds x="1041" y="10" width="44" height="324" /></element>
+ <element ref="blackkey" inputtag="KC3" inputmask="0x10"><bounds x="1135" y="10" width="44" height="324" /></element>
+ <element ref="whitekey-l" inputtag="KC2" inputmask="0x01"><bounds x="656" y="10" width="79" height="504" /></element>
+ <element ref="whitekey-m" inputtag="KC2" inputmask="0x04"><bounds x="738" y="10" width="79" height="504" /></element>
+ <element ref="whitekey-r" inputtag="KC2" inputmask="0x10"><bounds x="820" y="10" width="79" height="504" /></element>
+ <element ref="whitekey-l" inputtag="KC2" inputmask="0x20"><bounds x="902" y="10" width="79" height="504" /></element>
+ <element ref="whitekey-lm" inputtag="KC3" inputmask="0x02"><bounds x="984" y="10" width="79" height="504" /></element>
+ <element ref="whitekey-rm" inputtag="KC3" inputmask="0x08"><bounds x="1066" y="10" width="79" height="504" /></element>
+ <element ref="whitekey-r" inputtag="KC3" inputmask="0x20"><bounds x="1148" y="10" width="79" height="504" /></element>
+
+ <!-- octave 3 -->
+ <element ref="blackkey" inputtag="KC4" inputmask="0x02"><bounds x="1278" y="10" width="44" height="324" /></element>
+ <element ref="blackkey" inputtag="KC4" inputmask="0x08"><bounds x="1381" y="10" width="44" height="324" /></element>
+ <element ref="blackkey" inputtag="KC5" inputmask="0x01"><bounds x="1521" y="10" width="44" height="324" /></element>
+ <element ref="blackkey" inputtag="KC5" inputmask="0x04"><bounds x="1615" y="10" width="44" height="324" /></element>
+ <element ref="blackkey" inputtag="KC5" inputmask="0x10"><bounds x="1709" y="10" width="44" height="324" /></element>
+ <element ref="whitekey-l" inputtag="KC4" inputmask="0x01"><bounds x="1230" y="10" width="79" height="504" /></element>
+ <element ref="whitekey-m" inputtag="KC4" inputmask="0x04"><bounds x="1312" y="10" width="79" height="504" /></element>
+ <element ref="whitekey-r" inputtag="KC4" inputmask="0x10"><bounds x="1394" y="10" width="79" height="504" /></element>
+ <element ref="whitekey-l" inputtag="KC4" inputmask="0x20"><bounds x="1476" y="10" width="79" height="504" /></element>
+ <element ref="whitekey-lm" inputtag="KC5" inputmask="0x02"><bounds x="1558" y="10" width="79" height="504" /></element>
+ <element ref="whitekey-rm" inputtag="KC5" inputmask="0x08"><bounds x="1640" y="10" width="79" height="504" /></element>
+ <element ref="whitekey-r" inputtag="KC5" inputmask="0x20"><bounds x="1722" y="10" width="79" height="504" /></element>
+
+ <!-- octave 4 -->
+ <element ref="blackkey" inputtag="KC6" inputmask="0x02"><bounds x="1852" y="10" width="44" height="324" /></element>
+ <element ref="blackkey" inputtag="KC6" inputmask="0x08"><bounds x="1955" y="10" width="44" height="324" /></element>
+ <element ref="blackkey" inputtag="KC7" inputmask="0x01"><bounds x="2095" y="10" width="44" height="324" /></element>
+ <element ref="blackkey" inputtag="KC7" inputmask="0x04"><bounds x="2189" y="10" width="44" height="324" /></element>
+ <element ref="blackkey" inputtag="KC7" inputmask="0x10"><bounds x="2283" y="10" width="44" height="324" /></element>
+ <element ref="whitekey-l" inputtag="KC6" inputmask="0x01"><bounds x="1804" y="10" width="79" height="504" /></element>
+ <element ref="whitekey-m" inputtag="KC6" inputmask="0x04"><bounds x="1886" y="10" width="79" height="504" /></element>
+ <element ref="whitekey-r" inputtag="KC6" inputmask="0x10"><bounds x="1968" y="10" width="79" height="504" /></element>
+ <element ref="whitekey-l" inputtag="KC6" inputmask="0x20"><bounds x="2050" y="10" width="79" height="504" /></element>
+ <element ref="whitekey-lm" inputtag="KC7" inputmask="0x02"><bounds x="2132" y="10" width="79" height="504" /></element>
+ <element ref="whitekey-rm" inputtag="KC7" inputmask="0x08"><bounds x="2214" y="10" width="79" height="504" /></element>
+ <element ref="whitekey-r" inputtag="KC7" inputmask="0x20"><bounds x="2296" y="10" width="79" height="504" /></element>
+
+ <!-- final key -->
+ <element ref="whitekey" inputtag="KC8" inputmask="0x01"><bounds x="2378" y="10" width="79" height="504" /></element>
+ </group>
+
+ <group name="key_labels">
+ <bounds x="0" y="0" width="580" height="20" />
+
+ <element ref="tone2"><bounds xc="18" y="2" width="30" height="4" /></element>
+ <element ref="line_white"><bounds xc="18" y="7" width="0.75" height="13" /></element>
+ <element ref="tone4"><bounds xc="43" y="2" width="30" height="4" /></element>
+ <element ref="line_white"><bounds xc="43" y="7" width="0.75" height="13" /></element>
+ <element ref="tone7"><bounds xc="77" y="2" width="30" height="4" /></element>
+ <element ref="line_white"><bounds xc="77" y="7" width="0.75" height="13" /></element>
+ <element ref="tone9"><bounds xc="100" y="2" width="30" height="4" /></element>
+ <element ref="line_white"><bounds xc="100" y="7" width="0.75" height="13" /></element>
+ <element ref="tone11"><bounds xc="123" y="2" width="30" height="4" /></element>
+ <element ref="line_white"><bounds xc="123" y="7" width="0.75" height="13" /></element>
+
+ <element ref="tone1"><bounds x="-1" y="15" width="20" height="4" /></element>
+ <element ref="tone3"><bounds x="20" y="15" width="20" height="4" /></element>
+ <element ref="tone5"><bounds x="41" y="15" width="20" height="4" /></element>
+ <element ref="tone6"><bounds x="59" y="15" width="20" height="4" /></element>
+ <element ref="tone8"><bounds x="79" y="11" width="20" height="4" /></element>
+ <element ref="tone8b"><bounds x="79" y="15" width="20" height="4" /></element>
+ <element ref="tone10"><bounds x="101" y="15" width="20" height="4" /></element>
+ <element ref="tone12"><bounds x="121" y="11" width="20" height="4" /></element>
+ <element ref="tone12b"><bounds x="121" y="15" width="20" height="4" /></element>
+
+ <element ref="tone14"><bounds xc="158" y="2" width="30" height="4" /></element>
+ <element ref="line_white"><bounds xc="158" y="7" width="0.75" height="13" /></element>
+ <element ref="tone16"><bounds xc="183" y="2" width="30" height="4" /></element>
+ <element ref="line_white"><bounds xc="183" y="7" width="0.75" height="13" /></element>
+ <element ref="tone19"><bounds xc="217" y="2" width="30" height="4" /></element>
+ <element ref="line_white"><bounds xc="217" y="7" width="0.75" height="13" /></element>
+ <element ref="tone21"><bounds xc="240" y="2" width="30" height="4" /></element>
+ <element ref="line_white"><bounds xc="240" y="7" width="0.75" height="13" /></element>
+ <element ref="tone23"><bounds xc="263" y="2" width="30" height="4" /></element>
+ <element ref="line_white"><bounds xc="263" y="7" width="0.75" height="13" /></element>
+
+ <element ref="tone13"><bounds x="140" y="11" width="20" height="4" /></element>
+ <element ref="tone13b"><bounds x="140" y="15" width="20" height="4" /></element>
+ <element ref="tone15"><bounds x="160" y="11" width="20" height="4" /></element>
+ <element ref="tone15b"><bounds x="160" y="15" width="20" height="4" /></element>
+ <element ref="tone17"><bounds x="182" y="15" width="20" height="4" /></element>
+ <element ref="tone18"><bounds x="199" y="15" width="20" height="4" /></element>
+ <element ref="tone20"><bounds x="220" y="15" width="20" height="4" /></element>
+ <element ref="tone22"><bounds x="240" y="15" width="20" height="4" /></element>
+ <element ref="tone24"><bounds x="261" y="15" width="20" height="4" /></element>
+
+ <element ref="tone26"><bounds xc="298" y="2" width="30" height="4" /></element>
+ <element ref="line_white"><bounds xc="298" y="7" width="0.75" height="13" /></element>
+ <element ref="tone28"><bounds xc="323" y="2" width="30" height="4" /></element>
+ <element ref="line_white"><bounds xc="323" y="7" width="0.75" height="13" /></element>
+ <element ref="tone31"><bounds xc="357" y="2" width="30" height="4" /></element>
+ <element ref="line_white"><bounds xc="357" y="7" width="0.75" height="13" /></element>
+ <element ref="tone33"><bounds xc="380" y="2" width="30" height="4" /></element>
+ <element ref="line_white"><bounds xc="380" y="7" width="0.75" height="13" /></element>
+ <element ref="tone35"><bounds xc="403" y="2" width="30" height="4" /></element>
+ <element ref="line_white"><bounds xc="403" y="7" width="0.75" height="13" /></element>
+
+ <element ref="tone25"><bounds x="280" y="15" width="20" height="4" /></element>
+ <element ref="tone27"><bounds x="300" y="15" width="20" height="4" /></element>
+ <element ref="tone29"><bounds x="321" y="15" width="20" height="4" /></element>
+ <element ref="tone30"><bounds x="339" y="11" width="20" height="4" /></element>
+ <element ref="tone30b"><bounds x="339" y="15" width="20" height="4" /></element>
+ <element ref="tone32"><bounds x="360" y="15" width="20" height="4" /></element>
+ <element ref="tone34"><bounds x="380" y="15" width="20" height="4" /></element>
+ <element ref="tone36"><bounds x="401" y="15" width="20" height="4" /></element>
+
+ <element ref="tone38"><bounds xc="432" y="2" width="30" height="4" /></element>
+ <element ref="line_white"><bounds xc="438" y="7" width="0.75" height="13" /></element>
+ <element ref="tone40"><bounds xc="466" y="2" width="30" height="4" /></element>
+ <element ref="line_white"><bounds xc="463" y="7" width="0.75" height="13" /></element>
+ <element ref="tone43"><bounds xc="497" y="2" width="30" height="4" /></element>
+ <element ref="line_white"><bounds xc="497" y="7" width="0.75" height="13" /></element>
+ <element ref="tone45"><bounds xc="520" y="2" width="30" height="4" /></element>
+ <element ref="line_white"><bounds xc="520" y="7" width="0.75" height="13" /></element>
+ <element ref="tone47"><bounds xc="543" y="2" width="30" height="4" /></element>
+ <element ref="line_white"><bounds xc="543" y="7" width="0.75" height="13" /></element>
+
+ <element ref="tone37"><bounds x="419" y="15" width="20" height="4" /></element>
+ <element ref="tone39"><bounds x="440" y="15" width="20" height="4" /></element>
+ <element ref="tone41"><bounds x="461" y="11" width="20" height="4" /></element>
+ <element ref="tone41b"><bounds x="461" y="15" width="20" height="4" /></element>
+ <element ref="tone42"><bounds x="479" y="11" width="20" height="4" /></element>
+ <element ref="tone42b"><bounds x="479" y="15" width="20" height="4" /></element>
+ <element ref="tone44"><bounds x="500" y="11" width="20" height="4" /></element>
+ <element ref="tone44b"><bounds x="500" y="15" width="20" height="4" /></element>
+ <element ref="tone46"><bounds x="520" y="11" width="20" height="4" /></element>
+ <element ref="tone46b"><bounds x="520" y="15" width="20" height="4" /></element>
+ <element ref="tone48"><bounds x="541" y="11" width="20" height="4" /></element>
+ <element ref="tone48b"><bounds x="541" y="15" width="20" height="4" /></element>
+ <element ref="tone49"><bounds x="560" y="15" width="20" height="4" /></element>
+
+ </group>
+
+ <view name="Keyboard">
+ <element ref="bg"><bounds x="0" y="0" width="704" height="170" /></element>
+
+ <element ref="line"><bounds x="80" y="0" width="1" height="50" /></element>
+ <element ref="line"><bounds x="663" y="0" width="1" height="50" /></element>
+ <group ref="left"><bounds x="0" y="50" width="80" height="120" /></group>
+ <group ref="right"><bounds x="664" y="50" width="40" height="120" /></group>
+ <element ref="line"><bounds x="0" y="50" width="704" height="1" /></element>
+ <group ref="key_labels"><bounds x="82" y="30" width="580" height="20" /></group>
+ <group ref="keyboard"><bounds x="80" y="50" width="584" height="120" /></group>
+ </view>
+</mamelayout>
diff --git a/src/mame/layout/ctfk1.lay b/src/mame/layout/ctfk1.lay
new file mode 100644
index 00000000000..412ec8025b1
--- /dev/null
+++ b/src/mame/layout/ctfk1.lay
@@ -0,0 +1,241 @@
+<?xml version="1.0"?>
+<!--
+license:CC0-1.0
+-->
+
+<mamelayout version="2">
+ <element name="bg"><rect><color red="0.75" green="0.75" blue="0.775" /></rect></element>
+ <element name="brown"><rect><color red="0.365" green="0.263" blue="0.17" /></rect></element>
+ <element name="line"><rect><color red="0.55" green="0.55" blue="0.575" /></rect></element>
+ <element name="line_white"><rect><color red="1" green="1" blue="0.95" /></rect></element>
+
+ <element name="button_back">
+ <rect><bounds x="0" y="0" width="1" height="20" /><color red="0.25" green="0.25" blue="0.275" /></rect>
+ <rect><bounds x="0" y="1" width="1" height="18" /><color red="0.3" green="0.3" blue="0.325" /></rect>
+ </element>
+ <element name="panel_back"><rect><color red="0.7" green="0.7" blue="0.725" /></rect></element>
+
+ <element name="top_back">
+ <disk><bounds x="0" y="0" width="10" height="10" /><color red="0.1" green="0.1" blue="0.1" /></disk>
+ <disk><bounds x="270" y="0" width="10" height="10" /><color red="0.1" green="0.1" blue="0.1" /></disk>
+ <disk><bounds x="0" y="35" width="10" height="10" /><color red="0.1" green="0.1" blue="0.1" /></disk>
+ <disk><bounds x="270" y="35" width="10" height="10" /><color red="0.1" green="0.1" blue="0.1" /></disk>
+ <rect><bounds x="5" y="0" width="270" height="45" /><color red="0.1" green="0.1" blue="0.1" /></rect>
+ <rect><bounds x="0" y="5" width="280" height="35" /><color red="0.1" green="0.1" blue="0.1" /></rect>
+ </element>
+
+ <!-- text elements -->
+ <element name="select"><text string="BASS SELECT"><color red="0" blue="0" green="0" /></text></element>
+ <element name="foot"><text string="FOOT" align="1"><color red="0" blue="0" green="0" /></text></element>
+ <element name="auto"><text string="AUTO" align="1"><color red="0" blue="0" green="0" /></text></element>
+ <element name="mode"><text string="MODE"><color red="0" blue="0" green="0" /></text></element>
+ <element name="set"><text string="SET" align="1"><color red="0" blue="0" green="0" /></text></element>
+ <element name="play"><text string="PLAY" align="1"><color red="0" blue="0" green="0" /></text></element>
+ <element name="sustain"><text string="SUSTAIN"><color red="0" blue="0" green="0" /></text></element>
+ <element name="on"><text string="ON" align="1"><color red="0" blue="0" green="0" /></text></element>
+ <element name="off"><text string="OFF" align="1"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tune"><text string="TUNE" /></element>
+
+ <element name="tone1"><text string="ORGAN 1"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone2"><text string="ORGAN 4"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone3"><text string="ORGAN 2"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone4"><text string="TUBA 2"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone5"><text string="ORGAN 3"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone6"><text string="TUBA 1"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone7"><text string="BASSOON 2"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone8"><text string="BASSOON 1"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone9"><text string="WOOD BASS"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone9b"><text string="(ARCO 2)"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone10"><text string="WOOD BASS"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone10b"><text string="(ARCO 1)"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone11"><text string="WOOD BASS"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone11b"><text string="(PIZZ 2)"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone12"><text string="WOOD BASS"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone12b"><text string="(PIZZ 1)"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone13"><text string="ELEC. BASS 1"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone14"><text string="ELEC. BASS 2"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone15"><text string="HARPSICHORD 1"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone16"><text string="HARPSICHORD 2"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone17"><text string="PIANO"><color red="0" blue="0" green="0" /></text></element>
+ <element name="tone18"><text string="SYNTH CHIME"><color red="0" blue="0" green="0" /></text></element>
+
+ <!-- button primitives -->
+ <element name="button_down" defstate="0">
+ <rect><color red="0.1" green="0.1" blue="0.1" /><bounds x="0" y="0" width="5" height="10" /></rect>
+ <rect state="0"><color red="0.8" green="0.8" blue="0.8" /><bounds xc="2.5" yc="5" width="4" height="9" /></rect>
+ <rect state="1"><color red="0.5" green="0.5" blue="0.5" /><bounds xc="2.5" yc="5" width="4" height="9" /></rect>
+ <image>
+ <bounds xc="2.5" yc="5" width="2" height="5" />
+ <data><![CDATA[
+ <?xml version="1.0" standalone="no"?>
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10" version="1.1">
+ <polygon points="2,2 5,8 8,2" fill="white" stroke="white" stroke-width="0.4" />
+ </svg>
+ ]]></data>
+ </image>
+ </element>
+
+ <element name="button_up" defstate="0">
+ <rect><color red="0.1" green="0.1" blue="0.1" /><bounds x="0" y="0" width="5" height="10" /></rect>
+ <rect state="0"><color red="0.8" green="0.8" blue="0.8" /><bounds xc="2.5" yc="5" width="4" height="9" /></rect>
+ <rect state="1"><color red="0.5" green="0.5" blue="0.5" /><bounds xc="2.5" yc="5" width="4" height="9" /></rect>
+ <image>
+ <bounds xc="2.5" yc="5" width="2" height="5" />
+ <data><![CDATA[
+ <?xml version="1.0" standalone="no"?>
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10" version="1.1">
+ <polygon points="2,8 5,2 8,8" fill="white" stroke="white" stroke-width="0.4" />
+ </svg>
+ ]]></data>
+ </image>
+ </element>
+
+ <element name="button" defstate="0">
+ <disk><color red="0.1" green="0.1" blue="0.1" /><bounds x="0" y="0" width="20" height="20" /></disk>
+ <rect><color red="0.1" green="0.1" blue="0.1" /><bounds x="0" y="10" width="20" height="42" /></rect>
+ <disk state="0"><color red="0.3" green="0.3" blue="0.3" /><bounds xc="10" y="1" width="18" height="18" /></disk>
+ <rect state="0"><color red="0.3" green="0.3" blue="0.3" /><bounds xc="10" y="10" width="18" height="40" /></rect>
+ <disk state="1"><color red="0.15" green="0.15" blue="0.15" /><bounds xc="10" y="1" width="18" height="18" /></disk>
+ <rect state="1"><color red="0.15" green="0.15" blue="0.15" /><bounds xc="10" y="10" width="18" height="40" /></rect>
+ </element>
+
+ <element name="button_icon_up">
+ <rect><color red="0.75" green="0.75" blue="0.775" /><bounds x="0" y="0" width="5" height="5" /></rect>
+
+ <rect><color red="0" green="0" blue="0" /><bounds xc="2.5" y="4" width="5" height="1" /></rect>
+ <rect><color red="0" green="0" blue="0" /><bounds xc="2.5" y="1" width="3.5" height="4" /></rect>
+ </element>
+
+ <element name="button_icon_down">
+ <rect><color red="0.75" green="0.75" blue="0.775" /><bounds x="0" y="0" width="5" height="5" /></rect>
+
+ <rect><color red="0" green="0" blue="0" /><bounds xc="2.5" y="4" width="5" height="1" /></rect>
+ <rect><color red="0" green="0" blue="0" /><bounds xc="2.5" y="3" width="3.5" height="2" /></rect>
+ </element>
+
+ <!-- keyboard primitives -->
+ <element name="keyfill"><rect><color red="0.08" green="0.08" blue="0.08" /></rect></element>
+ <element name="whitekey" defstate="0">
+ <rect><bounds xc="0" y="0" width="5" height="47.5" /><color red="0" green="0" blue="0" /></rect>
+ <disk><bounds xc="0" yc="47.5" width="5" height="5" /><color red="0" green="0" blue="0" /></disk>
+ <rect state="0"><bounds xc="0" y="0" width="4.25" height="47.5" /><color red="1" green="1" blue="1" /></rect>
+ <disk state="0"><bounds xc="0" yc="47.5" width="4.25" height="4.25" /><color red="1" green="1" blue="1" /></disk>
+ <rect state="1"><bounds xc="0" y="0" width="4.25" height="47.5" /><color red="0.8" green="0.8" blue="0.8" /></rect>
+ <disk state="1"><bounds xc="0" yc="47.5" width="4.25" height="4.25" /><color red="0.8" green="0.8" blue="0.8" /></disk>
+ </element>
+ <element name="blackkey" defstate="0">
+ <rect><bounds xc="0" y="0" width="5" height="25" /><color red="0" green="0" blue="0" /></rect>
+ <disk><bounds xc="0" yc="25" width="5" height="5" /><color red="0" green="0" blue="0" /></disk>
+ <rect state="0"><bounds xc="0" y="0" width="4.25" height="25" /><color red="0.25" green="0.25" blue="0.25" /></rect>
+ <disk state="0"><bounds xc="0" yc="25" width="4.25" height="4.25" /><color red="0.25" green="0.25" blue="0.25" /></disk>
+ <rect state="1"><bounds xc="0" y="0" width="4.25" height="25" /><color red="0.2" green="0.2" blue="0.2" /></rect>
+ <disk state="1"><bounds xc="0" yc="25" width="4.25" height="4.25" /><color red="0.2" green="0.2" blue="0.2" /></disk>
+ </element>
+
+ <!-- button group -->
+ <group name="buttons">
+ <element ref="bg"><bounds x="0" y="0" width="660" height="180" /></element>
+ <element ref="top_back"><bounds x="25" y="25" width="280" height="45" /></element>
+ <element ref="tune"><bounds xc="270" y="30" width="40" height="9" /></element>
+ <element ref="button_down" inputtag="KC9" inputmask="0x04"><bounds xc="260" y="42" width="10" height="20" /></element>
+ <element ref="button_up" inputtag="KC9" inputmask="0x02"><bounds xc="280" y="42" width="10" height="20" /></element>
+
+ <element ref="select"><bounds xc="90" y="75" width="60" height="11" /></element>
+ <element ref="button_icon_down"><bounds x="73" y="87" width="9" height="9" /></element>
+ <element ref="foot"><bounds x="85" y="87" width="60" height="11" /></element>
+ <element ref="button_icon_up"><bounds x="73" y="99" width="9" height="9" /></element>
+ <element ref="auto"><bounds x="85" y="99" width="60" height="11" /></element>
+ <element ref="button" inputtag="KC9" inputmask="0x10"><bounds xc="90" y="115" width="20" height="52" /></element>
+
+ <element ref="mode"><bounds xc="180" y="75" width="60" height="11" /></element>
+ <element ref="button_icon_down"><bounds x="163" y="87" width="9" height="9" /></element>
+ <element ref="set"><bounds x="175" y="87" width="60" height="11" /></element>
+ <element ref="button_icon_up"><bounds x="163" y="99" width="9" height="9" /></element>
+ <element ref="play"><bounds x="175" y="99" width="60" height="11" /></element>
+ <element ref="button" inputtag="KCB" inputmask="0x10"><bounds xc="180" y="115" width="20" height="52" /></element>
+
+ <element ref="sustain"><bounds xc="270" y="75" width="60" height="11" /></element>
+ <element ref="button_icon_down"><bounds x="253" y="87" width="9" height="9" /></element>
+ <element ref="on"><bounds x="265" y="87" width="60" height="11" /></element>
+ <element ref="button_icon_up"><bounds x="253" y="99" width="9" height="9" /></element>
+ <element ref="off"><bounds x="265" y="99" width="60" height="11" /></element>
+ <element ref="button" inputtag="KCA" inputmask="0x01"><bounds xc="270" y="115" width="20" height="52" /></element>
+ </group>
+
+ <!-- keyboard group -->
+ <group name="keyboard">
+ <element ref="keyfill"><bounds x="0" y="0" width="660" height="300" /></element>
+
+ <element ref="button_back"><bounds x="0" y="100" width="660" height="30" /></element>
+ <element ref="button_back"><bounds x="0" y="200" width="660" height="30" /></element>
+
+ <element ref="whitekey" inputtag="KC0" inputmask="0x01"><bounds xc="30" y="0" width="25" height="245" /></element>
+ <element ref="blackkey" inputtag="KC0" inputmask="0x02"><bounds xc="60" y="0" width="25" height="125" /></element>
+ <element ref="whitekey" inputtag="KC0" inputmask="0x04"><bounds xc="90" y="0" width="25" height="245" /></element>
+ <element ref="blackkey" inputtag="KC0" inputmask="0x08"><bounds xc="120" y="0" width="25" height="125" /></element>
+ <element ref="whitekey" inputtag="KC0" inputmask="0x10"><bounds xc="150" y="0" width="25" height="245" /></element>
+
+ <element ref="whitekey" inputtag="KC0" inputmask="0x20"><bounds xc="210" y="0" width="25" height="245" /></element>
+ <element ref="blackkey" inputtag="KC1" inputmask="0x01"><bounds xc="240" y="0" width="25" height="125" /></element>
+ <element ref="whitekey" inputtag="KC1" inputmask="0x02"><bounds xc="270" y="0" width="25" height="245" /></element>
+ <element ref="blackkey" inputtag="KC1" inputmask="0x04"><bounds xc="300" y="0" width="25" height="125" /></element>
+ <element ref="whitekey" inputtag="KC1" inputmask="0x08"><bounds xc="330" y="0" width="25" height="245" /></element>
+ <element ref="blackkey" inputtag="KC1" inputmask="0x10"><bounds xc="360" y="0" width="25" height="125" /></element>
+ <element ref="whitekey" inputtag="KC1" inputmask="0x20"><bounds xc="390" y="0" width="25" height="245" /></element>
+
+ <element ref="whitekey" inputtag="KC2" inputmask="0x01"><bounds xc="450" y="0" width="25" height="245" /></element>
+ <element ref="blackkey" inputtag="KC2" inputmask="0x02"><bounds xc="480" y="0" width="25" height="125" /></element>
+ <element ref="whitekey" inputtag="KC2" inputmask="0x04"><bounds xc="510" y="0" width="25" height="245" /></element>
+ <element ref="blackkey" inputtag="KC2" inputmask="0x08"><bounds xc="540" y="0" width="25" height="125" /></element>
+ <element ref="whitekey" inputtag="KC2" inputmask="0x10"><bounds xc="570" y="0" width="25" height="245" /></element>
+
+ <element ref="whitekey" inputtag="KC2" inputmask="0x20"><bounds xc="630" y="0" width="25" height="245" /></element>
+ </group>
+
+ <group name="key_labels">
+ <bounds x="0" y="0" width="660" height="40" />
+
+ <element ref="tone2"><bounds xc="60" y="11" width="60" height="9" /></element>
+ <element ref="line_white"><bounds xc="60" y="20" width="1.5" height="20" /></element>
+ <element ref="tone4"><bounds xc="120" y="11" width="60" height="9" /></element>
+ <element ref="line_white"><bounds xc="120" y="20" width="1.5" height="20" /></element>
+ <element ref="tone7"><bounds xc="240" y="11" width="60" height="9" /></element>
+ <element ref="line_white"><bounds xc="240" y="20" width="1.5" height="20" /></element>
+ <element ref="tone9"><bounds xc="300" y="3" width="55" height="9" /></element>
+ <element ref="tone9b"><bounds xc="300" y="11" width="55" height="9" /></element>
+ <element ref="line_white"><bounds xc="300" y="20" width="1.5" height="20" /></element>
+ <element ref="tone11"><bounds xc="360" y="3" width="55" height="9" /></element>
+ <element ref="tone11b"><bounds xc="360" y="11" width="55" height="9" /></element>
+ <element ref="line_white"><bounds xc="360" y="20" width="1.5" height="20" /></element>
+
+ <element ref="tone1"><bounds xc="30" y="30" width="60" height="9" /></element>
+ <element ref="tone3"><bounds xc="90" y="30" width="60" height="9" /></element>
+ <element ref="tone5"><bounds xc="150" y="30" width="60" height="9" /></element>
+ <element ref="tone6"><bounds xc="210" y="30" width="60" height="9" /></element>
+ <element ref="tone8"><bounds xc="270" y="30" width="60" height="9" /></element>
+ <element ref="tone10"><bounds xc="330" y="22" width="55" height="9" /></element>
+ <element ref="tone10b"><bounds xc="330" y="30" width="55" height="9" /></element>
+ <element ref="tone12"><bounds xc="390" y="22" width="55" height="9" /></element>
+ <element ref="tone12b"><bounds xc="390" y="30" width="55" height="9" /></element>
+
+ <element ref="tone14"><bounds xc="480" y="11" width="60" height="9" /></element>
+ <element ref="line_white"><bounds xc="480" y="20" width="1.5" height="20" /></element>
+ <element ref="tone16"><bounds xc="540" y="11" width="60" height="9" /></element>
+ <element ref="line_white"><bounds xc="540" y="20" width="1.5" height="20" /></element>
+
+ <element ref="tone13"><bounds xc="450" y="30" width="60" height="9" /></element>
+ <element ref="tone15"><bounds xc="510" y="30" width="60" height="9" /></element>
+ <element ref="tone17"><bounds xc="570" y="30" width="60" height="9" /></element>
+ <element ref="tone18"><bounds xc="630" y="30" width="60" height="9" /></element>
+ </group>
+
+ <view name="Keyboard">
+ <element ref="brown"><bounds x="0" y="0" width="690" height="520" /></element>
+ <group ref="buttons"><bounds xc="345" y="0" width="660" height="180" /></group>
+ <element ref="panel_back"><bounds xc="345" y="180" width="660" height="40" /></element>
+ <element ref="line"><bounds xc="345" y="180" width="660" height="3" /></element>
+
+ <group ref="key_labels"><bounds xc="345" y="180" width="660" height="40" /></group>
+ <group ref="keyboard"><bounds xc="345" y="220" width="660" height="300" /></group>
+ </view>
+</mamelayout>
diff --git a/src/mame/mame.lst b/src/mame/mame.lst
index 7606b545294..c65809c6471 100644
--- a/src/mame/mame.lst
+++ b/src/mame/mame.lst
@@ -16155,6 +16155,11 @@ casloopy //
cfx9850 // Casio CFX-9850
cfx9850gb // Casio CFX-9850GB Plus
+@source:casio/ct8000.cpp
+ct8000 //
+ctfk1 //
+ctmb1 //
+
@source:casio/ctk2000.cpp
ctk2100 //