summaryrefslogtreecommitdiffstatshomepage
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/devices/bus/cbus/sb16_ct2720.cpp34
-rw-r--r--src/devices/bus/cbus/sb16_ct2720.h2
-rw-r--r--src/devices/bus/isa/sb16.cpp49
-rw-r--r--src/devices/bus/isa/sb16.h8
-rw-r--r--src/devices/sound/ct1745.cpp324
-rw-r--r--src/devices/sound/ct1745.h69
6 files changed, 449 insertions, 37 deletions
diff --git a/src/devices/bus/cbus/sb16_ct2720.cpp b/src/devices/bus/cbus/sb16_ct2720.cpp
index 31823f6b35f..f5f47abae7c 100644
--- a/src/devices/bus/cbus/sb16_ct2720.cpp
+++ b/src/devices/bus/cbus/sb16_ct2720.cpp
@@ -31,6 +31,7 @@ sb16_ct2720_device::sb16_ct2720_device(const machine_config &mconfig, const char
: device_t(mconfig, SB16_CT2720, tag, owner, clock)
, m_bus(*this, DEVICE_SELF_OWNER)
, m_opl3(*this, "opl3")
+ , m_mixer(*this, "mixer")
, m_ldac(*this, "ldac")
, m_rdac(*this, "rdac")
{
@@ -41,19 +42,30 @@ void sb16_ct2720_device::device_add_mconfig(machine_config &config)
SPEAKER(config, "lspeaker").front_left();
SPEAKER(config, "rspeaker").front_right();
- DAC_16BIT_R2R(config, m_ldac, 0).add_route(ALL_OUTPUTS, "lspeaker", 0.5); // unknown DAC
- DAC_16BIT_R2R(config, m_rdac, 0).add_route(ALL_OUTPUTS, "rspeaker", 0.5); // unknown DAC
+ CT1745(config, m_mixer);
+ m_mixer->set_fm_tag(m_opl3);
+ m_mixer->set_ldac_tag(m_ldac);
+ m_mixer->set_rdac_tag(m_rdac);
+ m_mixer->add_route(0, "lspeaker", 1.0);
+ m_mixer->add_route(1, "rspeaker", 1.0);
+ m_mixer->irq_status_cb().set([this] () {
+ return 0;
+ //return (m_irq8 << 0) | (m_irq16 << 1) | (m_irq_midi << 2) | (0x8 << 4);
+ });
// PC_JOY(config, m_joy);
// MIDI_PORT(config, "mdin", midiin_slot, "midiin").rxd_handler().set(FUNC(sb_device::midi_rx_w));
// MIDI_PORT(config, "mdout", midiout_slot, "midiout");
+ DAC_16BIT_R2R(config, m_ldac, 0).add_route(ALL_OUTPUTS, m_mixer, 0.5, AUTO_ALLOC_INPUT, 0); // unknown DAC
+ DAC_16BIT_R2R(config, m_rdac, 0).add_route(ALL_OUTPUTS, m_mixer, 0.5, AUTO_ALLOC_INPUT, 1); // unknown DAC
+
YMF262(config, m_opl3, XTAL(14'318'181));
- m_opl3->add_route(0, "lspeaker", 1.0);
- m_opl3->add_route(1, "rspeaker", 1.0);
- m_opl3->add_route(2, "lspeaker", 1.0);
- m_opl3->add_route(3, "rspeaker", 1.0);
+ m_opl3->add_route(0, m_mixer, 1.00, AUTO_ALLOC_INPUT, 0);
+ m_opl3->add_route(1, m_mixer, 1.00, AUTO_ALLOC_INPUT, 1);
+ m_opl3->add_route(2, m_mixer, 1.00, AUTO_ALLOC_INPUT, 0);
+ m_opl3->add_route(3, m_mixer, 1.00, AUTO_ALLOC_INPUT, 1);
}
void sb16_ct2720_device::device_start()
@@ -77,7 +89,15 @@ void sb16_ct2720_device::io_map(address_map &map)
m_opl3->write(offset >> 8, data);
})
);
-// map(0x2400 | base, 0x2400 | base).select(0x100) CT1745 mixer ($224-$225 on AT)
+ // ($224-$225 on AT)
+ map(0x2400 | base, 0x2400 | base).select(0x100).lrw8(
+ NAME([this] (offs_t offset) {
+ return m_mixer->read(offset >> 8);
+ }),
+ NAME([this] (offs_t offset, u8 data) {
+ m_mixer->write(offset >> 8, data);
+ })
+ );
// map(0x2600 | base, 0x2600 | base) CT1741 DSP reset
map(0x2800 | base, 0x2800 | base).select(0x100).lrw8(
NAME([this] (offs_t offset) {
diff --git a/src/devices/bus/cbus/sb16_ct2720.h b/src/devices/bus/cbus/sb16_ct2720.h
index ac775191f00..48178a9313a 100644
--- a/src/devices/bus/cbus/sb16_ct2720.h
+++ b/src/devices/bus/cbus/sb16_ct2720.h
@@ -9,6 +9,7 @@
#include "bus/cbus/pc9801_cbus.h"
//#include "bus/midi/midi.h"
//#include "bus/pc_joy/pc_joy.h"
+#include "sound/ct1745.h"
#include "sound/dac.h"
#include "sound/ymopl.h"
//#include "diserial.h"
@@ -39,6 +40,7 @@ private:
required_device<pc9801_slot_device> m_bus;
required_device<ymf262_device> m_opl3;
+ required_device<ct1745_mixer_device> m_mixer;
required_device<dac_16bit_r2r_device> m_ldac;
required_device<dac_16bit_r2r_device> m_rdac;
};
diff --git a/src/devices/bus/isa/sb16.cpp b/src/devices/bus/isa/sb16.cpp
index 2cfc035baad..16c759c951d 100644
--- a/src/devices/bus/isa/sb16.cpp
+++ b/src/devices/bus/isa/sb16.cpp
@@ -240,7 +240,7 @@ void sb16_lle_device::dac_fifo_ctrl_w(uint8_t data)
{
/* port 0x0E
* bit0 - reset fifo
- * bit1 - ?
+ * bit1 - DAC output sw off?
* bit2 - disable fifo
* bit3 -
* bit4 -
@@ -255,6 +255,7 @@ void sb16_lle_device::dac_fifo_ctrl_w(uint8_t data)
m_dac_r = false;
m_dac_h = false;
}
+ m_mixer->dac_speaker_off_cb(BIT(data, 1));
m_dac_fifo_ctrl = data;
}
@@ -406,24 +407,7 @@ void sb16_lle_device::sb16_io(address_map &map)
void sb16_lle_device::host_io(address_map &map)
{
- // TODO: move to own mixer core
- map(0x4, 0x4).lrw8(
- NAME([this] (offs_t offset) {
- return m_mixer_index;
- }),
- NAME([this] (offs_t offset, u8 data) {
- m_mixer_index = data;
- })
- );
- map(0x5, 0x5).lr8(
- NAME([this] (offs_t offset) {
- if (m_mixer_index == 0x80)
- return 0x12;
- if (m_mixer_index == 0x81)
- return 0x22;
- return 0;
- })
- );
+ map(0x4, 0x5).rw(m_mixer, FUNC(ct1745_mixer_device::read), FUNC(ct1745_mixer_device::write));
map(0x6, 0x7).w(FUNC(sb16_lle_device::dsp_reset_w));
map(0xa, 0xb).r(FUNC(sb16_lle_device::host_data_r));
map(0xc, 0xd).rw(FUNC(sb16_lle_device::dsp_wbuf_status_r), FUNC(sb16_lle_device::host_cmd_w));
@@ -446,14 +430,25 @@ void sb16_lle_device::device_add_mconfig(machine_config &config)
SPEAKER(config, "lspeaker").front_left();
SPEAKER(config, "rspeaker").front_right();
- ymf262_device &ymf262(YMF262(config, "ymf262", XTAL(14'318'181)));
- ymf262.add_route(0, "lspeaker", 1.00);
- ymf262.add_route(1, "rspeaker", 1.00);
- ymf262.add_route(2, "lspeaker", 1.00);
- ymf262.add_route(3, "rspeaker", 1.00);
- DAC_16BIT_R2R(config, m_ldac, 0).add_route(ALL_OUTPUTS, "lspeaker", 0.5); // unknown DAC
- DAC_16BIT_R2R(config, m_rdac, 0).add_route(ALL_OUTPUTS, "rspeaker", 0.5); // unknown DAC
+ CT1745(config, m_mixer);
+ m_mixer->set_fm_tag("ymf262");
+ m_mixer->set_ldac_tag(m_ldac);
+ m_mixer->set_rdac_tag(m_rdac);
+ m_mixer->add_route(0, "lspeaker", 1.0);
+ m_mixer->add_route(1, "rspeaker", 1.0);
+ m_mixer->irq_status_cb().set([this] () {
+ return (m_irq8 << 0) | (m_irq16 << 1) | (m_irq_midi << 2) | (0x8 << 4);
+ });
+
+ DAC_16BIT_R2R(config, m_ldac, 0).add_route(ALL_OUTPUTS, m_mixer, 0.5, AUTO_ALLOC_INPUT, 0); // unknown DAC
+ DAC_16BIT_R2R(config, m_rdac, 0).add_route(ALL_OUTPUTS, m_mixer, 0.5, AUTO_ALLOC_INPUT, 1); // unknown DAC
+
+ ymf262_device &ymf262(YMF262(config, "ymf262", XTAL(14'318'181)));
+ ymf262.add_route(0, m_mixer, 1.00, AUTO_ALLOC_INPUT, 0);
+ ymf262.add_route(1, m_mixer, 1.00, AUTO_ALLOC_INPUT, 1);
+ ymf262.add_route(2, m_mixer, 1.00, AUTO_ALLOC_INPUT, 0);
+ ymf262.add_route(3, m_mixer, 1.00, AUTO_ALLOC_INPUT, 1);
PC_JOY(config, m_joy);
}
@@ -705,6 +700,7 @@ void sb16_lle_device::mpu401_w(offs_t offset, uint8_t data)
sb16_lle_device::sb16_lle_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
device_t(mconfig, ISA16_SB16, tag, owner, clock),
device_isa16_card_interface(mconfig, *this),
+ m_mixer(*this, "mixer"),
m_ldac(*this, "ldac"),
m_rdac(*this, "rdac"),
m_joy(*this, "pc_joy"),
@@ -770,6 +766,7 @@ TIMER_CALLBACK_MEMBER(sb16_lle_device::timer_tick)
m_cpu->set_input_line(MCS51_INT0_LINE, ASSERT_LINE);
return;
}
+
if(m_mode & 1)
{
switch(m_mode & 0xa0) // dac 16
diff --git a/src/devices/bus/isa/sb16.h b/src/devices/bus/isa/sb16.h
index 8f9553d2f8c..a54fe0ae489 100644
--- a/src/devices/bus/isa/sb16.h
+++ b/src/devices/bus/isa/sb16.h
@@ -7,6 +7,7 @@
#include "isa.h"
#include "bus/pc_joy/pc_joy.h"
#include "cpu/mcs51/mcs51.h"
+#include "sound/ct1745.h"
#include "sound/dac.h"
#include "sound/ymopl.h"
@@ -88,8 +89,9 @@ private:
TIMER_CALLBACK_MEMBER(timer_tick);
- required_device<dac_word_interface> m_ldac;
- required_device<dac_word_interface> m_rdac;
+ required_device<ct1745_mixer_device> m_mixer;
+ required_device<dac_16bit_r2r_device> m_ldac;
+ required_device<dac_16bit_r2r_device> m_rdac;
required_device<pc_joy_device> m_joy;
required_device<i80c52_device> m_cpu;
@@ -112,8 +114,6 @@ private:
bool m_dma8_done, m_dma16_done;
emu_timer *m_timer;
-
- u8 m_mixer_index;
};
// device type definition
diff --git a/src/devices/sound/ct1745.cpp b/src/devices/sound/ct1745.cpp
new file mode 100644
index 00000000000..520274dd709
--- /dev/null
+++ b/src/devices/sound/ct1745.cpp
@@ -0,0 +1,324 @@
+// license:BSD-3-Clause
+// copyright-holders:Angelo Salese
+/**************************************************************************************************
+
+Creative Labs CT1745 SB16 Mixer chip
+
+References:
+- https://the.earth.li/~tfm/oldpage/sb_mixer.html
+
+TODO:
+- Pinout reference;
+- IRQ select & mask for card host;
+- Get a tag reference for CD and (ISA/C-Bus) buzzer speaker volume controls;
+- Mic/Line In;
+- Stereo control and low-pass filter;
+- Input/Output Control (left/right rerouting);
+- Gain Control + AGC;
+- Bass/Treble;
+- Track down really unavailable regs, return 0x80 in index status;
+- Track down interactions with LLE DSP (UART?);
+
+**************************************************************************************************/
+
+#include "emu.h"
+#include "ct1745.h"
+
+DEFINE_DEVICE_TYPE(CT1745, ct1745_mixer_device, "ct1745", "Creative Labs CT1745 SB16 Mixer")
+
+ct1745_mixer_device::ct1745_mixer_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : device_t(mconfig, CT1745, tag, owner, clock)
+ , device_memory_interface(mconfig, *this)
+ , device_mixer_interface(mconfig, *this, 2)
+ , m_irq_status_cb(*this, 0)
+ , m_fm(*this, finder_base::DUMMY_TAG)
+ , m_ldac(*this, finder_base::DUMMY_TAG)
+ , m_rdac(*this, finder_base::DUMMY_TAG)
+{
+ m_space_config = address_space_config("regs", ENDIANNESS_LITTLE, 8, 8, 0, address_map_constructor(FUNC(ct1745_mixer_device::map), this));
+}
+
+device_memory_interface::space_config_vector ct1745_mixer_device::memory_space_config() const
+{
+ return space_config_vector {
+ std::make_pair(0, &m_space_config)
+ };
+}
+
+void ct1745_mixer_device::device_start()
+{
+ save_item(NAME(m_index));
+ save_pointer(NAME(m_master_level), 2);
+ save_pointer(NAME(m_dac_level), 2);
+ save_pointer(NAME(m_fm_level), 2);
+ save_pointer(NAME(m_cd_level), 2);
+ save_pointer(NAME(m_linein_level), 2);
+ save_item(NAME(m_mic_level));
+ save_item(NAME(m_pc_speaker_level));
+ save_item(NAME(m_output_sw));
+ save_pointer(NAME(m_input_sw), 2);
+ save_pointer(NAME(m_input_gain), 2);
+ save_pointer(NAME(m_output_gain), 2);
+ save_item(NAME(m_agc));
+ save_pointer(NAME(m_treble), 2);
+ save_pointer(NAME(m_bass), 2);
+}
+
+void ct1745_mixer_device::device_reset()
+{
+ m_index = 0;
+
+ reset_state();
+}
+
+void ct1745_mixer_device::reset_state()
+{
+ m_master_level[0] = m_master_level[1] = 0xc0;
+ m_dac_level[0] = m_dac_level[1] = 0xc0;
+ m_fm_level[0] = m_fm_level[1] = 0xc0;
+ m_cd_level[0] = m_cd_level[1] = 0xc0;
+ m_linein_level[0] = m_linein_level[1] = 0;
+ m_mic_level = 0;
+ m_pc_speaker_level = 0x80;
+ m_output_sw = 0x1f;
+ m_input_sw[0] = 0x15;
+ m_input_sw[1] = 0x0b;
+ m_input_gain[0] = m_input_gain[1] = 0x00;
+ m_output_gain[0] = m_output_gain[1] = 0x00;
+ m_treble[0] = m_treble[1] = 0x80;
+ m_bass[0] = m_bass[1] = 0x80;
+ m_agc = false;
+
+ //m_dac_speaker_off = true;
+ update_gain_levels();
+}
+
+void ct1745_mixer_device::dac_speaker_off_cb(int state)
+{
+ m_dac_speaker_off = !!state;
+ update_gain_levels();
+}
+
+u8 ct1745_mixer_device::read(offs_t offset)
+{
+ if (offset == 0)
+ return m_index;
+
+ return space().read_byte(m_index);
+}
+
+void ct1745_mixer_device::write(offs_t offset, u8 data)
+{
+ if (offset == 0)
+ {
+ m_index = data;
+ return;
+ }
+
+ space().write_byte(m_index, data);
+ if (!BIT(m_index, 7))
+ update_gain_levels();
+}
+
+// TODO: bare copy of HLE version, convert to sound_stream_update instead
+void ct1745_mixer_device::update_gain_levels()
+{
+ float lmain = m_master_level[0]/248.0;
+ float rmain = m_master_level[1]/248.0;
+
+ m_ldac->set_output_gain(ALL_OUTPUTS, m_dac_speaker_off ? 0.0 : lmain*(m_dac_level[0]/248.0f));
+ m_rdac->set_output_gain(ALL_OUTPUTS, m_dac_speaker_off ? 0.0 : rmain*(m_dac_level[1]/248.0f));
+ m_fm->set_output_gain(0, lmain*(m_fm_level[0]/248.0f));
+ m_fm->set_output_gain(1, rmain*(m_fm_level[1]/248.0f));
+ m_fm->set_output_gain(2, lmain*(m_fm_level[0]/248.0f));
+ m_fm->set_output_gain(3, rmain*(m_fm_level[1]/248.0f));
+
+ // TODO: convert to LIVE_AUDIO_VIEW
+ //popmessage("%f %f (%02x %02x) | %02x %02x | %02x %02x", lmain, rmain, m_master_level[0], m_master_level[1], m_dac_level[0], m_dac_level[1], m_fm_level[0], m_fm_level[1]);
+}
+
+void ct1745_mixer_device::map(address_map &map)
+{
+ map(0x00, 0x00).lw8(
+ NAME([this] (offs_t offset, u8 data) {
+ // TODO: requires that the index is loaded by a set time first.
+ if (!BIT(data, 0))
+ reset_state();
+ })
+ );
+// SB1/2 compatibility section
+ map(0x04, 0x04).lrw8(
+ NAME([this] (offs_t offset) {
+ return (m_dac_level[0] & 0xf0) | (m_dac_level[1] >> 4);
+ }),
+ NAME([this] (offs_t offset, u8 data) {
+ m_dac_level[0] = (data & 0xf0) | 8;
+ m_dac_level[1] = ((data & 0x0f) << 4) | 8;
+ })
+ );
+
+// map(0x06, 0x06) FM Output Control?
+// map(0x0a, 0x0a) Microphone level?
+// map(0x0c, 0x0c) Input/Filter Select
+ // RMW (to the index!?) by ibm5170_cdrom:zyclunt, should be "Output Filter/Stereo Select"
+// map(0x0e, 0x0e).lr8(NAME([] () { return 0x02; })); // Output/Stereo Select
+ map(0x22, 0x22).lrw8(
+ NAME([this] (offs_t offset) {
+ return (m_master_level[0] & 0xf0) | (m_master_level[1] >> 4);
+ }),
+ NAME([this] (offs_t offset, u8 data) {
+ m_master_level[0] = (data & 0xf0) | 8;
+ m_master_level[1] = ((data & 0x0f) << 4) | 8;
+ })
+ );
+ map(0x26, 0x26).lrw8(
+ NAME([this] (offs_t offset) {
+ return (m_fm_level[0] & 0xf0) | (m_fm_level[1] >> 4);
+ }),
+ NAME([this] (offs_t offset, u8 data) {
+ m_fm_level[0] = (data & 0xf0) | 8;
+ m_fm_level[1] = ((data & 0x0f) << 4) | 8;
+ })
+ );
+ map(0x26, 0x26).lrw8(
+ NAME([this] (offs_t offset) {
+ return (m_cd_level[0] & 0xf0) | (m_cd_level[1] >> 4);
+ }),
+ NAME([this] (offs_t offset, u8 data) {
+ m_cd_level[0] = (data & 0xf0) | 8;
+ m_cd_level[1] = ((data & 0x0f) << 4) | 8;
+ })
+ );
+ map(0x2e, 0x2e).lrw8(
+ NAME([this] (offs_t offset) {
+ return (m_linein_level[0] & 0xf0) | (m_linein_level[1] >> 4);
+ }),
+ NAME([this] (offs_t offset, u8 data) {
+ m_linein_level[0] = (data & 0xf0) | 8;
+ m_linein_level[1] = ((data & 0x0f) << 4) | 8;
+ })
+ );
+ map(0x30, 0x31).lrw8(
+ NAME([this] (offs_t offset) {
+ return m_master_level[offset & 1];
+ }),
+ NAME([this] (offs_t offset, u8 data) {
+ m_master_level[offset & 1] = data & 0xf8;
+ })
+ );
+ map(0x32, 0x33).lrw8(
+ NAME([this] (offs_t offset) {
+ return m_dac_level[offset & 1];
+ }),
+ NAME([this] (offs_t offset, u8 data) {
+ m_dac_level[offset & 1] = data & 0xf8;
+ })
+ );
+ map(0x34, 0x35).lrw8(
+ NAME([this] (offs_t offset) {
+ return m_fm_level[offset & 1];
+ }),
+ NAME([this] (offs_t offset, u8 data) {
+ m_fm_level[offset & 1] = data & 0xf8;
+ })
+ );
+ map(0x36, 0x37).lrw8(
+ NAME([this] (offs_t offset) {
+ return m_cd_level[offset & 1];
+ }),
+ NAME([this] (offs_t offset, u8 data) {
+ m_cd_level[offset & 1] = data & 0xf8;
+ })
+ );
+ map(0x38, 0x39).lrw8(
+ NAME([this] (offs_t offset) {
+ return m_linein_level[offset & 1];
+ }),
+ NAME([this] (offs_t offset, u8 data) {
+ m_linein_level[offset & 1] = data & 0xf8;
+ })
+ );
+ map(0x3a, 0x3a).lrw8(
+ NAME([this] (offs_t offset) {
+ return m_mic_level;
+ }),
+ NAME([this] (offs_t offset, u8 data) {
+ m_mic_level = data & 0xf8;
+ })
+ );
+ map(0x3b, 0x3b).lrw8(
+ NAME([this] (offs_t offset) {
+ return m_pc_speaker_level;
+ }),
+ NAME([this] (offs_t offset, u8 data) {
+ m_pc_speaker_level = data & 0xc0;
+ })
+ );
+ map(0x3c, 0x3c).lrw8(
+ NAME([this] (offs_t offset) {
+ return m_output_sw;
+ }),
+ NAME([this] (offs_t offset, u8 data) {
+ m_output_sw = data & 0x1f;
+ })
+ );
+ // NOTE: start section where L/R are A0 swapped ...
+ map(0x3d, 0x3e).lrw8(
+ NAME([this] (offs_t offset) {
+ return m_input_sw[(offset & 1) ^ 1];
+ }),
+ NAME([this] (offs_t offset, u8 data) {
+ m_input_sw[(offset & 1) ^ 1] = data & 0x7f;
+ })
+ );
+ map(0x3f, 0x40).lrw8(
+ NAME([this] (offs_t offset) {
+ return m_input_gain[(offset & 1) ^ 1];
+ }),
+ NAME([this] (offs_t offset, u8 data) {
+ m_input_gain[(offset & 1) ^ 1] = data & 0xc0;
+ })
+ );
+ map(0x41, 0x42).lrw8(
+ NAME([this] (offs_t offset) {
+ return m_output_gain[(offset & 1) ^ 1];
+ }),
+ NAME([this] (offs_t offset, u8 data) {
+ m_output_gain[(offset & 1) ^ 1] = data & 0xc0;
+ })
+ );
+ // ... end section
+ map(0x43, 0x43).lrw8(
+ NAME([this] (offs_t offset) {
+ return m_agc;
+ }),
+ NAME([this] (offs_t offset, u8 data) {
+ m_agc = !!BIT(data, 0);
+ })
+ );
+ map(0x44, 0x45).lrw8(
+ NAME([this] (offs_t offset) {
+ return m_treble[offset & 1];
+ }),
+ NAME([this] (offs_t offset, u8 data) {
+ m_treble[offset & 1] = data & 0xf0;
+ })
+ );
+ map(0x46, 0x47).lrw8(
+ NAME([this] (offs_t offset) {
+ return m_bass[offset & 1];
+ }),
+ NAME([this] (offs_t offset, u8 data) {
+ m_bass[offset & 1] = data & 0xf0;
+ })
+ );
+
+ // PnP ports
+ // IRQ/DMA select
+ map(0x80, 0x80).lr8(NAME([] () { return 0x12; }));
+ map(0x81, 0x81).lr8(NAME([] () { return 0x22; }));
+ // IRQ Status
+ map(0x82, 0x82).lr8(NAME([this] () { return m_irq_status_cb(); }));
+
+ // ...
+}
diff --git a/src/devices/sound/ct1745.h b/src/devices/sound/ct1745.h
new file mode 100644
index 00000000000..ce619171e18
--- /dev/null
+++ b/src/devices/sound/ct1745.h
@@ -0,0 +1,69 @@
+// license:BSD-3-Clause
+// copyright-holders:Angelo Salese
+
+#ifndef DEVICE_SOUND_CT1745_H
+#define DEVICE_SOUND_CT1745_H
+
+#pragma once
+
+class ct1745_mixer_device : public device_t, public device_memory_interface, public device_mixer_interface
+{
+public:
+ ct1745_mixer_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock = 0);
+
+ // reg $82, depends on DSP + irq stuff coming from host
+ auto irq_status_cb() { return m_irq_status_cb.bind(); }
+
+ // from DSP commands $d3/$d8 TODO: actual pin name
+ void dac_speaker_off_cb(int state);
+
+ u8 read(offs_t offset);
+ void write(offs_t offset, u8 data);
+
+ template <typename T> void set_fm_tag(T &&tag) { m_fm.set_tag(std::forward<T>(tag)); }
+ template <typename T> void set_ldac_tag(T &&tag) { m_ldac.set_tag(std::forward<T>(tag)); }
+ template <typename T> void set_rdac_tag(T &&tag) { m_rdac.set_tag(std::forward<T>(tag)); }
+
+protected:
+ virtual void device_start() override ATTR_COLD;
+ virtual void device_reset() override ATTR_COLD;
+
+ virtual space_config_vector memory_space_config() const override;
+
+ void map(address_map &map) ATTR_COLD;
+
+private:
+ address_space_config m_space_config;
+ devcb_read8 m_irq_status_cb;
+
+ required_device<device_sound_interface> m_fm;
+ required_device<device_sound_interface> m_ldac;
+ required_device<device_sound_interface> m_rdac;
+
+ void reset_state();
+ void update_gain_levels();
+
+ u8 m_index;
+
+ u8 m_master_level[2];
+ u8 m_dac_level[2];
+ u8 m_fm_level[2];
+ u8 m_cd_level[2];
+ u8 m_linein_level[2];
+ u8 m_mic_level;
+ u8 m_pc_speaker_level;
+ u8 m_output_sw;
+ u8 m_input_sw[2];
+ u8 m_input_gain[2];
+ u8 m_output_gain[2];
+ // Automatic Gain Control
+ bool m_agc;
+ u8 m_treble[2];
+ u8 m_bass[2];
+
+ bool m_dac_speaker_off;
+};
+
+DECLARE_DEVICE_TYPE(CT1745, ct1745_mixer_device)
+
+#endif // DEVICE_SOUND_CT1745_H