Revert "Revert "ctk551: implement sound, promote to working (#8960)" (#8980)" (#8982)

This reverts commit 04c0b4fbb2.
This commit is contained in:
R. Belmont 2021-12-14 11:24:17 -05:00 committed by GitHub
parent 04c0b4fbb2
commit 852b1f3d26
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 340 additions and 99 deletions

View File

@ -11,7 +11,7 @@
- Two timers, three 8-bit ports, two 8-bit ADCs
- Keyboard controller w/ key velocity detection
- MIDI UART
- 24-voice PCM sound (currently not emulated / fully understood)
- 24-voice DPCM sound
Earlier and later Casio keyboard models contain "uPD912" and "uPD914" chips,
which are presumably similar.
@ -52,9 +52,9 @@ void gt913_device::map(address_map &map)
map(0xfac0, 0xffbf).ram(); // CTK-551 zeroes out this range at $0418
/* ffc0-ffcb: sound */
map(0xffc0, 0xffc5).rw(m_sound, FUNC(gt913_sound_hle_device::data_r), FUNC(gt913_sound_hle_device::data_w));
map(0xffc6, 0xffc7).w(m_sound, FUNC(gt913_sound_hle_device::command_w));
map(0xffca, 0xffcb).r(m_sound, FUNC(gt913_sound_hle_device::status_r));
map(0xffc0, 0xffc5).rw(m_sound, FUNC(gt913_sound_device::data_r), FUNC(gt913_sound_device::data_w));
map(0xffc6, 0xffc7).w(m_sound, FUNC(gt913_sound_device::command_w));
map(0xffca, 0xffcb).r(m_sound, FUNC(gt913_sound_device::status_r));
/* ffd0-ffd5: key controller */
map(0xffd0, 0xffd1).r(m_kbd, FUNC(gt913_kbd_hle_device::read));
@ -88,7 +88,14 @@ void gt913_device::device_add_mconfig(machine_config &config)
{
GT913_INTC(config, "intc");
GT913_SOUND_HLE(config, m_sound, 0);
/*
generate sound at 104 cycles per sample (= 144.231 kHz sample clock to the DAC)
on keyboard models that include a DSP, this also results in a multiple
of the 36.058 kHz CPU->DSP sync signal shown in some schematics (WK-1200 and others)
*/
GT913_SOUND(config, m_sound, std::round(clock() / 104.0f));
m_sound->set_device_rom_tag(tag());
GT913_KBD_HLE(config, m_kbd, 0);
m_kbd->irq_cb().set([this](int val) { if (val) m_intc->internal_interrupt(5); });
GT913_IO_HLE(config, m_io_hle, "intc", 6, 7);
@ -102,7 +109,7 @@ void gt913_device::device_add_mconfig(machine_config &config)
void gt913_device::uart_rate_w(uint8_t data)
{
m_sci->brr_w(data >> 1);
m_sci->brr_w(data >> 2);
}
void gt913_device::uart_control_w(uint8_t data)

View File

@ -62,7 +62,7 @@ protected:
required_device<gt913_intc_device> m_intc;
/* sound */
required_device<gt913_sound_hle_device> m_sound;
required_device<gt913_sound_device> m_sound;
/* key controller */
required_device<gt913_kbd_hle_device> m_kbd;

View File

@ -107,7 +107,7 @@
5e00 ff00 0 bsr rel8 -
5f00 fff0 0 mov.w imm16 r16l
66000000 ff00ff8f 0 btst imm3 abs8
66000000 ff00ff8f 0 btst imm3 r16ihh
6800 ff80 0 mov.b r16ih r8l
6900 ff88 0 mov.w r16ih r16l

View File

@ -108,9 +108,9 @@ void gt913_io_hle_device::timer_adjust(offs_t num)
logerror("unknown timer %u prescaler %u (pc = %04x)\n", num, m_timer_control[num] & 0x7, m_cpu->pc());
[[fallthrough]];
case 0:
clocks <<= 1; break;
break;
case 2:
clocks <<= 10; break;
clocks <<= 9; break;
}
attotime period = m_cpu->clocks_to_attotime(clocks);

View File

@ -8,13 +8,13 @@
which is then input to either a serial DAC or a HG51B-based DSP,
depending on the model of keyboard.
Currently, the actual sample format in ROM is unknown.
The serial output is twos-complement 16-bit PCM, but the data in ROM
doesn't seem to be - reading it as such produces sounds that are
somewhat recognizable, but highly distorted.
The sample format, as well as other details such as the linear interpolation,
are covered in these two Japanese patents:
https://patents.google.com/patent/JP3603343B2/en
https://patents.google.com/patent/JPH07199996A/en
For now, all known (and unknown) register writes are just logged
without generating any sound.
TODO: Volume envelope rates still need adjusting.
(See comment in gt913_sound_device::command_w regarding command 6007)
***************************************************************************/
@ -26,140 +26,319 @@
// DEVICE DEFINITIONS
//**************************************************************************
DEFINE_DEVICE_TYPE(GT913_SOUND_HLE, gt913_sound_hle_device, "gt913_sound_hle", "Casio GT913F sound (HLE)")
DEFINE_DEVICE_TYPE(GT913_SOUND, gt913_sound_device, "gt913_sound_hle", "Casio GT913F sound")
gt913_sound_hle_device::gt913_sound_hle_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
device_t(mconfig, GT913_SOUND_HLE, tag, owner, clock)
// expand 2-bit exponent deltas
const u8 gt913_sound_device::exp_2_to_3[4] = { 0, 1, 2, 7 };
// sign-extend 7-bit sample deltas
const s8 gt913_sound_device::sample_7_to_8[128] =
{
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
-64, -63, -62, -61, -60, -59, -58, -57, -56, -55, -54, -53, -52, -51, -50, -49,
-48, -47, -46, -45, -44, -43, -42, -41, -40, -39, -38, -37, -36, -35, -34, -33,
-32, -31, -30, -29, -28, -27, -26, -25, -24, -23, -22, -21, -20, -19, -18, -17,
-16, -15, -14, -13, -12, -11, -10, -9, -8, -7, -6, -5, -4, -3, -2, -1
};
gt913_sound_device::gt913_sound_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock)
: device_t(mconfig, GT913_SOUND, tag, owner, clock)
, device_sound_interface(mconfig, *this)
, device_rom_interface(mconfig, *this)
{
}
void gt913_sound_hle_device::device_start()
void gt913_sound_device::device_start()
{
m_stream = stream_alloc(0, 2, clock());
save_item(NAME(m_gain));
save_item(NAME(m_data));
save_item(NAME(m_volume_target));
save_item(NAME(m_volume_rate));
save_item(STRUCT_MEMBER(m_voices, m_enable));
save_item(STRUCT_MEMBER(m_voices, m_addr_start));
save_item(STRUCT_MEMBER(m_voices, m_addr_end));
save_item(STRUCT_MEMBER(m_voices, m_addr_loop));
save_item(STRUCT_MEMBER(m_voices, m_addr_current));
save_item(STRUCT_MEMBER(m_voices, m_addr_frac));
save_item(STRUCT_MEMBER(m_voices, m_pitch));
save_item(STRUCT_MEMBER(m_voices, m_sample));
save_item(STRUCT_MEMBER(m_voices, m_sample_next));
save_item(STRUCT_MEMBER(m_voices, m_exp));
save_item(STRUCT_MEMBER(m_voices, m_volume_current));
save_item(STRUCT_MEMBER(m_voices, m_volume_target));
save_item(STRUCT_MEMBER(m_voices, m_volume_rate));
save_item(STRUCT_MEMBER(m_voices, m_volume_end));
save_item(STRUCT_MEMBER(m_voices, m_balance));
save_item(STRUCT_MEMBER(m_voices, m_gain));
}
void gt913_sound_hle_device::device_reset()
void gt913_sound_device::device_reset()
{
m_gain = 0;
std::memset(m_data, 0, sizeof(m_data));
std::memset(m_volume_target, 0, sizeof(m_volume_target));
std::memset(m_volume_rate, 0, sizeof(m_volume_rate));
std::memset(m_voices, 0, sizeof(m_voices));
}
void gt913_sound_hle_device::data_w(offs_t offset, uint16_t data)
void gt913_sound_device::sound_stream_update(sound_stream& stream, std::vector<read_stream_view> const& inputs, std::vector<write_stream_view>& outputs)
{
for (int i = 0; i < outputs[0].samples(); i++)
{
s64 left = 0, right = 0;
for (auto& voice : m_voices)
{
if (voice.m_enable)
mix_sample(voice, left, right);
}
outputs[0].put_int_clamp(i, (left * m_gain) >> 26, 32678);
outputs[1].put_int_clamp(i, (right * m_gain) >> 26, 32768);
}
}
void gt913_sound_device::rom_bank_updated()
{
m_stream->update();
}
void gt913_sound_device::mix_sample(voice_t& voice, s64& left, s64& right)
{
// update sample position
voice.m_addr_frac += voice.m_pitch;
while (voice.m_addr_frac >= (1 << 25))
{
voice.m_addr_frac -= (1 << 25);
update_sample(voice);
}
// update volume envelope
if (voice.m_volume_target > voice.m_volume_current
&& (voice.m_volume_target - voice.m_volume_current) > voice.m_volume_rate)
{
voice.m_volume_current += voice.m_volume_rate;
}
else if (voice.m_volume_target < voice.m_volume_current
&& (voice.m_volume_current - voice.m_volume_target) > voice.m_volume_rate)
{
voice.m_volume_current -= voice.m_volume_rate;
}
else
{
voice.m_volume_current = voice.m_volume_target;
}
// interpolate, apply envelope + channel gain, and mix into output
const u8 step = (voice.m_addr_frac >> 22) & 7;
const u8 env = (voice.m_volume_current >> 24);
/*
the current envelope level effects amplitude non-linearly, just apply the value twice
(this hardware family is branded as "A² (A-Square) Sound Source" in some of Casio's
promotional materials, possibly for this reason?)
*/
const s64 sample = ((s64)voice.m_sample + (voice.m_sample_next * step / 8)) * voice.m_gain * env * env;
left += sample * voice.m_balance[0];
right += sample * voice.m_balance[1];
}
void gt913_sound_device::update_sample(voice_t& voice)
{
voice.m_sample += voice.m_sample_next;
if (voice.m_addr_current == (voice.m_addr_loop | 1))
{
/*
The last 12 bytes of each sample are a table containing five sample and exponent value pairs
for the data words immediately after the loop point. The first pair corresponds to what the
sample and exponent value will be _after_ processing the first word after the loop,
so once we've reached that point, use those values to reload the current sample and exponent
*/
const u32 addr_loop_data = (voice.m_addr_end + 1) & ~1;
voice.m_sample_next = read_word(addr_loop_data) - voice.m_sample;
voice.m_exp = read_word(addr_loop_data + 10) & 7;
}
else
{
/*
For all other samples, just get the next sample delta value.
For even-numbered samples, also update the exponent/shift value.
*/
const u16 word = read_word(voice.m_addr_current & ~1);
s16 delta = 0;
if (!BIT(voice.m_addr_current, 0))
{
voice.m_exp += exp_2_to_3[word & 3];
voice.m_exp &= 7;
delta = sample_7_to_8[(word >> 2) & 0x7f];
}
else
{
delta = sample_7_to_8[word >> 9];
}
voice.m_sample_next = delta * (1 << voice.m_exp);
}
voice.m_addr_current++;
if (voice.m_addr_current == voice.m_addr_end)
{
voice.m_addr_current = voice.m_addr_loop;
if (voice.m_addr_loop == voice.m_addr_end)
voice.m_enable = false;
}
}
void gt913_sound_device::data_w(offs_t offset, u16 data)
{
assert(offset < 3);
m_data[offset] = data;
}
uint16_t gt913_sound_hle_device::data_r(offs_t offset)
u16 gt913_sound_device::data_r(offs_t offset)
{
assert(offset < 3);
return m_data[offset];
}
void gt913_sound_hle_device::command_w(uint16_t data)
void gt913_sound_device::command_w(u16 data)
{
uint8_t voicenum = (data & 0x1f00) >> 8;
uint16_t voicecmd = data & 0x60ff;
m_stream->update();
const uint8_t voicenum = (data & 0x1f00) >> 8;
const uint16_t voicecmd = data & 0x60ff;
if (data == 0x0012)
{
uint8_t gain = m_data[0] & 0x3f;
if (gain != m_gain)
logerror("gain %u\n", gain);
m_gain = gain;
m_gain = m_data[0] & 0x3f;
return;
}
else if (voicenum >= 24)
{
return;
}
else if (voicecmd == 0x0008)
auto& voice = m_voices[voicenum];
if (voicecmd == 0x0008)
{
/*
Set the voice's sample start point as a ROM address.
This is usually word-aligned, but not always
(e.g. ctk551's lowest piano sample is at address 0x5a801)
sample start addresses seem to need to be word-aligned to decode properly
(see: ctk551 "Trumpet" patch, which will have a bad exponent value otherwise)
this apparently doesn't apply to end/loop addresses, though, or else samples
may loop badly or even become noticeably detuned
TODO: is the LSB of start addresses supposed to indicate something else, then?
*/
uint32_t samplestart = (m_data[1] | (m_data[2] << 16)) & 0x1fffff;
logerror("voice %u sample start 0x%06x\n", voicenum, samplestart);
voice.m_addr_start = (m_data[1] | (m_data[2] << 16)) & 0x1ffffe;
}
else if (voicecmd == 0x0000)
{
/*
Set the voice's sample end point as a ROM address.
*/
uint32_t sampleend = (m_data[0] | (m_data[1] << 16)) & 0x1fffff;
logerror("voice %u sample end 0x%06x\n", voicenum, sampleend);
voice.m_addr_end = (m_data[0] | (m_data[1] << 16)) & 0x1fffff;
}
else if (voicecmd == 0x2000)
{
/*
Set the voice's sample loop point as a ROM address.
*/
uint32_t sampleloop = (m_data[0] | (m_data[1] << 16)) & 0x1fffff;
logerror("voice %u sample loop 0x%06x\n", voicenum, sampleloop);
voice.m_addr_loop = (m_data[0] | (m_data[1] << 16)) & 0x1fffff;
}
else if (voicecmd == 0x200a)
{
logerror("voice %u cmd 0x200a (data = %02x)\n", voicenum, m_data[2] & 0xff);
/* TODO: what does bit 4 of data[2] do? ctk551 sets it unconditionally */
voice.m_exp = m_data[2] & 7;
}
else if (voicecmd == 0x200b)
{
/*
Turn this voice on/off.
ctk551 turns output off before assigning a new note or instrument to this voice,
then turns output back on afterward
*/
logerror("voice %u output %s\n", voicenum, BIT(m_data[2], 7) ? "on" : "off");
bool enable = BIT(m_data[2], 7);
if (enable && !m_voices[voicenum].m_enable)
{
voice.m_addr_current = voice.m_addr_start;
voice.m_addr_frac = 0;
voice.m_sample = 0;
}
voice.m_enable = enable;
voice.m_volume_end &= enable;
}
else if (voicecmd == 0x4004)
{
/*
Set this voice's panning, in the form of left and right volume levels (3 bits each)
*/
uint8_t left = (m_data[1] & 0xe0) >> 5;
uint8_t right = (m_data[1] & 0x1c) >> 2;
logerror("voice %u left %u right %u\n", voicenum, left, right);
voice.m_balance[0] = (m_data[1] & 0xe0) >> 5;
voice.m_balance[1] = (m_data[1] & 0x1c) >> 2;
}
else if (voicecmd == 0x4005)
{
/*
Set the current pitch of this voice.
The actual format of the value is unknown, but presumably some kind of fixed point
for pitch, data[1] apparently contains both the most and least significant of 4 bytes,
with data0 in the middle. strange, but apparently correct (see higher octaves of ctk551 E.Piano2)
*/
uint32_t pitch = (m_data[0] << 8) | (m_data[1] >> 8);
logerror("voice %u pitch 0x%06x\n", voicenum, pitch);
voice.m_pitch = (m_data[1] << 24) | (m_data[0] << 8) | (m_data[1] >> 8);
}
else if (voicecmd == 0x6006)
{
logerror("voice %u cmd 0x6006 (data = %02x)\n", voicenum, m_data[1] & 0xff);
/*
per-voice gain used for normalizing samples
currently treated such that the lower 3 bits are fractional
*/
voice.m_gain = m_data[1] & 0xff;
}
else if (voicecmd == 0x6007)
{
logerror("voice %u volume %u rate %u\n", voicenum, (m_data[0] >> 8), m_data[0] & 0xff);
/*
Raise or lower the volume to a specified level at a specified rate.
The actual volume level is probably 7.8 fixed point or something like that, but this command
only sets the most significant bits.
only set a new volume level/rate if we haven't previously indicated the end of an envelope,
unless the new level also has the high bit set. otherwise, a timer irq may try to update the
normal envelope while other code is trying to force a note off
*/
logerror("voice %u volume %u rate %u\n", voicenum, (m_data[0] >> 8) & 0x7f, m_data[0] & 0xff);
m_volume_target[voicenum] = m_data[0] & 0x7f00;
m_volume_rate[voicenum] = m_data[0] & 0xff;
const bool end = BIT(m_data[0], 15);
if (!voice.m_volume_end || end)
{
voice.m_volume_end = end;
voice.m_volume_target = (m_data[0] & 0x7f00) << 16;
/*
In addition to volume levels applying non-linearly, envelope rates
are also non-linear. Unfortunately, with the ctk-551's limited patch set and
lack of editing features, figuring out the correct behavior isn't easy.
This is essentially a rough estimate until a higher-end model (ctk-601 series, etc)
can be dumped and used for more detailed testing.
*/
const u8 x = m_data[0] & 0xff;
if (x >= 127)
voice.m_volume_rate = x << 21;
else if (x >= 63)
voice.m_volume_rate = x << 16;
else if (x >= 47)
voice.m_volume_rate = x << 14;
else if (x >= 31)
voice.m_volume_rate = x << 11;
else if (x >= 23)
voice.m_volume_rate = x << 9;
else if (x >= 15)
voice.m_volume_rate = x << 7;
else
voice.m_volume_rate = x << 5;
}
}
else if (voicecmd == 0x2028)
{
/*
ctk551 issues this command and then reads the voice's current volume from data0
to determine if it's time to start the next part of the volume envelope or not.
For now, just return the "target" volume immediately
(TODO: also figure out what it expects to be returned in data1)
*/
m_data[0] = m_volume_target[voicenum];
m_data[1] = 0;
m_data[0] = voice.m_enable ? (voice.m_volume_current >> 16) : 0;
/*
data1 is used to read consecutive output sample and detect zero crossings when
applying volume or expression changes to a MIDI channel
*/
m_data[1] = voice.m_sample;
}
else
{
@ -167,9 +346,11 @@ void gt913_sound_hle_device::command_w(uint16_t data)
}
}
uint16_t gt913_sound_hle_device::status_r()
u16 gt913_sound_device::status_r()
{
/* ctk551 reads the current gain level out of the lower 6 bits and ignores the rest
it's unknown what, if anything, the other bits are supposed to contain */
/*
ctk551 reads the current gain level out of the lower 6 bits and ignores the rest
it's unknown what, if anything, the other bits are supposed to contain
*/
return m_gain & 0x3f;
}

View File

@ -9,37 +9,78 @@
#pragma once
#include "dirom.h"
//**************************************************************************
// TYPE DEFINITIONS
//**************************************************************************
// ======================> gt913_sound_hle_device
// ======================> gt913_sound_device
class gt913_sound_hle_device : public device_t
class gt913_sound_device : public device_t,
public device_sound_interface,
public device_rom_interface<21, 1, 0, ENDIANNESS_BIG>
{
public:
// construction/destruction
gt913_sound_hle_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock = 0);
static constexpr feature_type imperfect_features() { return feature::SOUND; }
void data_w(offs_t offset, uint16_t data);
uint16_t data_r(offs_t offset);
void command_w(uint16_t data);
uint16_t status_r();
// construction/destruction
gt913_sound_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock = 0);
void data_w(offs_t offset, u16 data);
u16 data_r(offs_t offset);
void command_w(u16 data);
u16 status_r();
protected:
// device_t overrides
virtual void device_start() override;
virtual void device_reset() override;
private:
uint8_t m_gain;
uint16_t m_data[3];
// device_sound_interface overrides
virtual void sound_stream_update(sound_stream &stream, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs) override;
uint16_t m_volume_target[24];
uint8_t m_volume_rate[24];
// device_rom_interface overrides
virtual void rom_bank_updated() override;
private:
sound_stream *m_stream;
u8 m_gain;
u16 m_data[3];
static const u8 exp_2_to_3[4];
static const s8 sample_7_to_8[128];
struct voice_t
{
bool m_enable;
u32 m_addr_start;
u32 m_addr_end;
u32 m_addr_loop;
u32 m_addr_current;
u32 m_addr_frac, m_pitch;
s16 m_sample, m_sample_next;
u8 m_exp;
u32 m_volume_current, m_volume_target;
u32 m_volume_rate;
bool m_volume_end;
u8 m_balance[2];
u8 m_gain;
};
void mix_sample(voice_t& voice, s64& left, s64& right);
void update_sample(voice_t& voice);
voice_t m_voices[24];
};
// device type definition
DECLARE_DEVICE_TYPE(GT913_SOUND_HLE, gt913_sound_hle_device)
DECLARE_DEVICE_TYPE(GT913_SOUND, gt913_sound_device)
#endif // MAME_AUDIO_GT913_H

View File

@ -53,6 +53,7 @@
#include "video/hd44780.h"
#include "emupal.h"
#include "screen.h"
#include "speaker.h"
namespace {
@ -64,6 +65,7 @@ public:
, m_maincpu(*this, "maincpu")
, m_lcdc(*this, "lcdc")
, m_led_touch(*this, "led_touch")
, m_led_power(*this, "led_power")
{
}
@ -90,6 +92,7 @@ private:
required_device<hd44780_device> m_lcdc;
output_finder<> m_led_touch;
output_finder<> m_led_power;
ioport_value m_switch;
};
@ -97,7 +100,6 @@ private:
INPUT_CHANGED_MEMBER(ctk551_state::switch_w)
{
logerror("switch_w: %x\n", param);
if (!oldval && newval)
{
if (m_switch == 0x1 && param != m_switch)
@ -112,8 +114,11 @@ INPUT_CHANGED_MEMBER(ctk551_state::switch_w)
WRITE_LINE_MEMBER(ctk551_state::apo_w)
{
logerror("apo_w: %x\n", state);
/* TODO: when 1, this should turn off the LCD, speakers, etc.
/* auto power off - disable the LCD
the CPU will go to sleep until the power switch triggers a NMI */
if (state)
m_lcdc->reset();
m_led_power = !state;
}
HD44780_PIXEL_UPDATE(ctk551_state::lcd_update)
@ -140,6 +145,7 @@ void ctk551_state::ctk551_io_map(address_map &map)
void ctk551_state::driver_start()
{
m_led_touch.resolve();
m_led_power.resolve();
m_switch = 0x2;
@ -149,8 +155,11 @@ void ctk551_state::driver_start()
void ctk551_state::ctk551(machine_config &config)
{
// CPU
GT913(config, m_maincpu, 30'000'000);
// 30MHz oscillator, divided down internally (otherwise the test mode's OK/NG sounds play at double speed)
GT913(config, m_maincpu, 30'000'000 / 2);
m_maincpu->set_addrmap(AS_IO, &ctk551_state::ctk551_io_map);
m_maincpu->subdevice<gt913_sound_device>("gt_sound")->add_route(0, "lspeaker", 1.0);
m_maincpu->subdevice<gt913_sound_device>("gt_sound")->add_route(1, "rspeaker", 1.0);
// MIDI
auto &mdin(MIDI_PORT(config, "mdin"));
@ -177,6 +186,9 @@ void ctk551_state::ctk551(machine_config &config)
screen.set_palette("palette");
PALETTE(config, "palette", FUNC(ctk551_state::palette_init), 2);
SPEAKER(config, "lspeaker").front_left();
SPEAKER(config, "rspeaker").front_right();
}
INPUT_PORTS_START(ctk551)
@ -356,4 +368,4 @@ ROM_END
} // anonymous namespace
// YEAR NAME PARENT COMPAT MACHINE INPUT CLASS INIT COMPANY FULLNAME FLAGS
SYST( 1999, ctk551, 0, 0, ctk551, ctk551, ctk551_state, empty_init, "Casio", "CTK-551", MACHINE_NO_SOUND | MACHINE_NOT_WORKING | MACHINE_SUPPORTS_SAVE )
SYST( 1999, ctk551, 0, 0, ctk551, ctk551, ctk551_state, empty_init, "Casio", "CTK-551", MACHINE_IMPERFECT_GRAPHICS | MACHINE_SUPPORTS_SAVE )