From efee42e5e49a8ef12d7bf440ce6f09cb4b403c3a Mon Sep 17 00:00:00 2001 From: Devin Acker Date: Sat, 25 Jan 2025 12:47:44 -0500 Subject: casio/ct8000.cpp: Emulated Casiotone 8000 keyboard and related systems. (#13237) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * sound/flt_biquad.cpp: Added Sallen-Key high-pass filters. * sound/bbd.cpp: Added MN3207P variant. * sound/upd931.cpp: Emulated µPD931 synthesis chip. New working systems -------------------- Casio Casiotone 8000 [=CO=Windler, Devin Acker] Casio Casiotone FK-1 [BCM, Devin Acker] New systems marked not working ------------------------ Casio Casiotone MB-1 [=CO=Windler, Devin Acker] --- scripts/src/sound.lua | 12 + src/devices/sound/bbd.cpp | 16 +- src/devices/sound/bbd.h | 11 + src/devices/sound/flt_biquad.cpp | 253 +++++++----- src/devices/sound/flt_biquad.h | 12 +- src/devices/sound/upd931.cpp | 599 +++++++++++++++++++++++++++ src/devices/sound/upd931.h | 153 +++++++ src/emu/xtal.cpp | 1 + src/mame/casio/ct8000.cpp | 857 +++++++++++++++++++++++++++++++++++++++ src/mame/casio/ct8000_midi.cpp | 409 +++++++++++++++++++ src/mame/casio/ct8000_midi.h | 89 ++++ src/mame/layout/ct8000.lay | 423 +++++++++++++++++++ src/mame/layout/ctfk1.lay | 241 +++++++++++ src/mame/mame.lst | 5 + 14 files changed, 2978 insertions(+), 103 deletions(-) create mode 100644 src/devices/sound/upd931.cpp create mode 100644 src/devices/sound/upd931.h create mode 100644 src/mame/casio/ct8000.cpp create mode 100644 src/mame/casio/ct8000_midi.cpp create mode 100644 src/mame/casio/ct8000_midi.h create mode 100644 src/mame/layout/ct8000.lay create mode 100644 src/mame/layout/ctfk1.lay 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 @@ -1503,6 +1503,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::device_start() template void bbd_device_base::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 +#include + +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 const &inputs, std::vector &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 + +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 const &inputs, std::vector &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 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 m_maincpu; + required_device_array m_io; + + required_device m_midi; + + required_device m_931a; + optional_device m_931b; + required_device m_mixer; + optional_device_array m_filter_rc; + optional_device_array m_filter_bq; + optional_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 + +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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ]]> + + + + + + + + + + + + + + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ]]> + + + + + + + + + + + + + + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 // -- cgit v1.2.3