summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
author Felipe CorrĂȘa da Silva Sanches <juca@members.fsf.org>2025-11-03 04:05:25 -0300
committer GitHub <noreply@github.com>2025-11-03 08:05:25 +0100
commit161f7ea9418e96883552257af983b7accd853b0c (patch)
tree3b020a66bed4d5dd0c92946dabc904746cae5645
parent5e08544fef89da3313bf2ca1de9c261638c94414 (diff)
[Access Virus] Analog input knobs for Virus A & B (#14461)
-rw-r--r--src/devices/cpu/mcs51/sab80c535.cpp20
-rw-r--r--src/devices/cpu/mcs51/sab80c535.h18
-rw-r--r--src/mame/access/acvirus.cpp135
-rw-r--r--src/mame/layout/virusb.lay284
4 files changed, 452 insertions, 5 deletions
diff --git a/src/devices/cpu/mcs51/sab80c535.cpp b/src/devices/cpu/mcs51/sab80c535.cpp
index 2908f418a31..c34c3372e5c 100644
--- a/src/devices/cpu/mcs51/sab80c535.cpp
+++ b/src/devices/cpu/mcs51/sab80c535.cpp
@@ -78,16 +78,34 @@ u8 sab80c535_device::p5_r()
return m_p5 & m_port_in_cb[5]();
}
+void sab80c535_device::adcon_w(u8 data)
+{
+ m_adcon = data;
+}
+
+u8 sab80c535_device::adcon_r()
+{
+ return m_adcon;
+}
+
+u8 sab80c535_device::addat_r()
+{
+ return m_an_func[m_adcon & 7]() * 2;
+}
+
void sab80c535_device::sfr_map(address_map &map)
{
i8052_device::sfr_map(map);
+ map(0xd8, 0xd8).rw(FUNC(sab80c535_device::adcon_r), FUNC(sab80c535_device::adcon_w));
+ map(0xd9, 0xd9).r(FUNC(sab80c535_device::addat_r));
map(0xe8, 0xe8).rw(FUNC(sab80c535_device::p4_r), FUNC(sab80c535_device::p4_w));
map(0xf8, 0xf8).rw(FUNC(sab80c535_device::p5_r), FUNC(sab80c535_device::p5_w));
}
sab80c535_device::sab80c535_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock)
: i8052_device(mconfig, SAB80C535, tag, owner, clock, 0)
+ , m_an_func(*this, 0)
{
}
@@ -96,6 +114,7 @@ void sab80c535_device::device_start()
i8052_device::device_start();
save_item(NAME(m_p4));
save_item(NAME(m_p5));
+ save_item(NAME(m_adcon));
}
void sab80c535_device::device_reset()
@@ -103,6 +122,7 @@ void sab80c535_device::device_reset()
i8052_device::device_reset();
m_p4 = 0xff;
m_p5 = 0xff;
+ m_adcon = 0x00;
}
std::unique_ptr<util::disasm_interface> sab80c535_device::create_disassembler()
diff --git a/src/devices/cpu/mcs51/sab80c535.h b/src/devices/cpu/mcs51/sab80c535.h
index 50b077207ec..5c0b2c1cef5 100644
--- a/src/devices/cpu/mcs51/sab80c535.h
+++ b/src/devices/cpu/mcs51/sab80c535.h
@@ -12,6 +12,15 @@ public:
// construction/destruction
sab80c535_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock);
+ auto an0_func() { return m_an_func[0].bind(); }
+ auto an1_func() { return m_an_func[1].bind(); }
+ auto an2_func() { return m_an_func[2].bind(); }
+ auto an3_func() { return m_an_func[3].bind(); }
+ auto an4_func() { return m_an_func[4].bind(); }
+ auto an5_func() { return m_an_func[5].bind(); }
+ auto an6_func() { return m_an_func[6].bind(); }
+ auto an7_func() { return m_an_func[7].bind(); }
+
protected:
virtual void device_start() override ATTR_COLD;
virtual void device_reset() override ATTR_COLD;
@@ -19,13 +28,20 @@ protected:
virtual std::unique_ptr<util::disasm_interface> create_disassembler() override;
virtual void sfr_map(address_map &map) override ATTR_COLD;
+ devcb_read8::array<8> m_an_func;
+
private:
- u8 m_p4, m_p5;
+ u8 m_p4;
+ u8 m_p5;
+ u8 m_adcon;
void p4_w(u8 data);
u8 p4_r();
void p5_w(u8 data);
u8 p5_r();
+ void adcon_w(u8 data);
+ u8 adcon_r();
+ u8 addat_r();
};
DECLARE_DEVICE_TYPE(SAB80C535, sab80c535_device)
diff --git a/src/mame/access/acvirus.cpp b/src/mame/access/acvirus.cpp
index a53a22cbe86..ae4339f71b5 100644
--- a/src/mame/access/acvirus.cpp
+++ b/src/mame/access/acvirus.cpp
@@ -90,7 +90,9 @@ public:
m_dsp(*this, "dsp"),
m_rombank(*this, "rombank"),
m_row(*this, "ROW%u", 0U),
- m_scan(0)
+ m_knob(*this, "knob_%u", 0U),
+ m_scan(0),
+ m_an_select(0)
{ }
void virusa(machine_config &config) ATTR_COLD;
@@ -119,13 +121,17 @@ private:
u8 p1_r();
u8 p4_r();
void p1_w(u8 data);
+ void p3_w(u8 data);
void p5_w(u8 data);
u8 p402_r();
void palette_init(palette_device &palette) ATTR_COLD;
+ required_ioport_array<32> m_knob;
+
u8 m_scan;
+ u8 m_an_select;
};
@@ -135,6 +141,7 @@ void acvirus_state::machine_start()
m_rombank->set_entry(3);
save_item(NAME(m_scan));
+ save_item(NAME(m_an_select));
}
void acvirus_state::machine_reset()
@@ -154,6 +161,11 @@ void acvirus_state::p1_w(u8 data)
m_lcdc->rs_w(BIT(data, 7));
}
+void acvirus_state::p3_w(u8 data)
+{
+ m_an_select = (data >> 4) & 3;
+}
+
u8 acvirus_state::p4_r()
{
return m_row[m_scan & 3]->read();
@@ -205,8 +217,17 @@ void acvirus_state::virusa(machine_config &config)
m_maincpu->set_addrmap(AS_DATA, &acvirus_state::data_map);
m_maincpu->port_in_cb<1>().set(FUNC(acvirus_state::p1_r));
m_maincpu->port_out_cb<1>().set(FUNC(acvirus_state::p1_w));
+ m_maincpu->port_out_cb<3>().set(FUNC(acvirus_state::p3_w));
m_maincpu->port_in_cb<4>().set(FUNC(acvirus_state::p4_r));
m_maincpu->port_out_cb<5>().set(FUNC(acvirus_state::p5_w));
+ m_maincpu->an0_func().set([this] { return m_knob[4*0 + m_an_select]->read(); });
+ m_maincpu->an1_func().set([this] { return m_knob[4*1 + m_an_select]->read(); });
+ m_maincpu->an2_func().set([this] { return m_knob[4*2 + m_an_select]->read(); });
+ m_maincpu->an3_func().set([this] { return m_knob[4*3 + m_an_select]->read(); });
+ m_maincpu->an4_func().set([this] { return m_knob[4*4 + m_an_select]->read(); });
+ m_maincpu->an5_func().set([this] { return m_knob[4*5 + m_an_select]->read(); });
+ m_maincpu->an6_func().set([this] { return m_knob[4*6 + m_an_select]->read(); });
+ m_maincpu->an7_func().set([this] { return m_knob[4*7 + m_an_select]->read(); });
screen_device &screen(SCREEN(config, "screen", SCREEN_TYPE_LCD));
screen.set_refresh_hz(60);
@@ -237,8 +258,17 @@ void acvirus_state::virusb(machine_config &config)
m_maincpu->set_addrmap(AS_DATA, &acvirus_state::data_map);
m_maincpu->port_in_cb<1>().set(FUNC(acvirus_state::p1_r));
m_maincpu->port_out_cb<1>().set(FUNC(acvirus_state::p1_w));
+ m_maincpu->port_out_cb<3>().set(FUNC(acvirus_state::p3_w));
m_maincpu->port_in_cb<4>().set(FUNC(acvirus_state::p4_r));
m_maincpu->port_out_cb<5>().set(FUNC(acvirus_state::p5_w));
+ m_maincpu->an0_func().set([this] { return m_knob[4*0 + m_an_select]->read(); });
+ m_maincpu->an1_func().set([this] { return m_knob[4*1 + m_an_select]->read(); });
+ m_maincpu->an2_func().set([this] { return m_knob[4*2 + m_an_select]->read(); });
+ m_maincpu->an3_func().set([this] { return m_knob[4*3 + m_an_select]->read(); });
+ m_maincpu->an4_func().set([this] { return m_knob[4*4 + m_an_select]->read(); });
+ m_maincpu->an5_func().set([this] { return m_knob[4*5 + m_an_select]->read(); });
+ m_maincpu->an6_func().set([this] { return m_knob[4*6 + m_an_select]->read(); });
+ m_maincpu->an7_func().set([this] { return m_knob[4*7 + m_an_select]->read(); });
screen_device &screen(SCREEN(config, "screen", SCREEN_TYPE_LCD));
screen.set_refresh_hz(60);
@@ -297,7 +327,108 @@ void acvirus_state::virusc(machine_config &config)
}
+INPUT_PORTS_START( virusa_knobs )
+ PORT_START("knob_0")
+ PORT_ADJUSTER(64, "Master Volume") PORT_MINMAX(0, 127)
+
+ PORT_START("knob_1")
+ PORT_ADJUSTER(64, "Definable 1") PORT_MINMAX(0, 127)
+
+ PORT_START("knob_2")
+ PORT_ADJUSTER(64, "Definable 2") PORT_MINMAX(0, 127)
+
+ PORT_START("knob_3")
+ PORT_ADJUSTER(64, "LFO 1: Rate") PORT_MINMAX(0, 127)
+
+ PORT_START("knob_4")
+ PORT_ADJUSTER(64, "LFO 2: Rate") PORT_MINMAX(0, 127)
+
+ PORT_START("knob_5")
+ PORT_ADJUSTER(64, "Osc 1: Shape") PORT_MINMAX(0, 127)
+
+ PORT_START("knob_6")
+ PORT_ADJUSTER(64, "Osc 1: Wave/PW") PORT_MINMAX(0, 127)
+
+ PORT_START("knob_7")
+ PORT_ADJUSTER(64, "Osc 2: Shape") PORT_MINMAX(0, 127)
+
+ PORT_START("knob_8")
+ PORT_ADJUSTER(64, "Osc 2: Wave/PW") PORT_MINMAX(0, 127)
+
+ PORT_START("knob_9")
+ PORT_ADJUSTER(64, "Osc 2 Semitone") PORT_MINMAX(0, 127)
+
+ PORT_START("knob_10")
+ PORT_ADJUSTER(64, "Osc 2 Detune") PORT_MINMAX(0, 127)
+
+ PORT_START("knob_11")
+ PORT_ADJUSTER(64, "Osc 2 FM Amount") PORT_MINMAX(0, 127)
+
+ PORT_START("knob_12")
+ PORT_ADJUSTER(64, "Mixer Osc Bal") PORT_MINMAX(0, 127)
+
+ PORT_START("knob_13")
+ PORT_ADJUSTER(64, "Mixer Sub Osc") PORT_MINMAX(0, 127)
+
+ PORT_START("knob_14")
+ PORT_ADJUSTER(64, "Mixer Osc Volume") PORT_MINMAX(0, 127)
+
+ PORT_START("knob_15")
+ PORT_ADJUSTER(64, "Value (Program)") PORT_MINMAX(0, 127)
+
+ PORT_START("knob_16")
+ PORT_ADJUSTER(64, "Cutoff") PORT_MINMAX(0, 127)
+
+ PORT_START("knob_17")
+ PORT_ADJUSTER(64, "Cutoff 2") PORT_MINMAX(0, 127)
+
+ PORT_START("knob_18")
+ PORT_ADJUSTER(64, "Filter Attack") PORT_MINMAX(0, 127)
+
+ PORT_START("knob_19")
+ PORT_ADJUSTER(64, "Amp Attack") PORT_MINMAX(0, 127)
+
+ PORT_START("knob_20")
+ PORT_ADJUSTER(64, "Filter Resonance") PORT_MINMAX(0, 127)
+
+ PORT_START("knob_21")
+ PORT_ADJUSTER(64, "Filter EnvAmount") PORT_MINMAX(0, 127)
+
+ PORT_START("knob_22")
+ PORT_ADJUSTER(64, "Filter Key Follow") PORT_MINMAX(0, 127)
+
+ PORT_START("knob_23")
+ PORT_ADJUSTER(64, "Filter Balance") PORT_MINMAX(0, 127)
+
+ PORT_START("knob_24")
+ PORT_ADJUSTER(64, "Filter Decay") PORT_MINMAX(0, 127)
+
+ PORT_START("knob_25")
+ PORT_ADJUSTER(64, "Filter Sustain") PORT_MINMAX(0, 127)
+
+ PORT_START("knob_26")
+ PORT_ADJUSTER(64, "Filter Time") PORT_MINMAX(0, 127)
+
+ PORT_START("knob_27")
+ PORT_ADJUSTER(64, "Filter Release") PORT_MINMAX(0, 127)
+
+ PORT_START("knob_28")
+ PORT_ADJUSTER(64, "Amp Decay") PORT_MINMAX(0, 127)
+
+ PORT_START("knob_29")
+ PORT_ADJUSTER(64, "Amp Sustain") PORT_MINMAX(0, 127)
+
+ PORT_START("knob_30")
+ PORT_ADJUSTER(64, "Amp Time") PORT_MINMAX(0, 127)
+
+ PORT_START("knob_31")
+ PORT_ADJUSTER(64, "Amp Release") PORT_MINMAX(0, 127)
+INPUT_PORTS_END
+
+
static INPUT_PORTS_START( virusa )
+ PORT_INCLUDE( virusa_knobs )
+
PORT_START("ROW0")
PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_CODE(KEYCODE_1) PORT_NAME("Key Follow")
PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_CODE(KEYCODE_V) PORT_NAME("Multi")
@@ -340,6 +471,8 @@ INPUT_PORTS_END
static INPUT_PORTS_START( virusb )
+ PORT_INCLUDE( virusa_knobs )
+
PORT_START("ROW0")
PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_CODE(KEYCODE_1) PORT_NAME("LFO 1: Edit")
PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_CODE(KEYCODE_V) PORT_NAME("Multi")
diff --git a/src/mame/layout/virusb.lay b/src/mame/layout/virusb.lay
index 1facfac9e88..c408792f1ff 100644
--- a/src/mame/layout/virusb.lay
+++ b/src/mame/layout/virusb.lay
@@ -60,7 +60,22 @@ license:CC0-1.0
<disk><color red="0.8" green="0.8" blue="0.8"/><bounds x="0" y="0" width="5" height="5"/></disk>
</element>
- <element name="knob">
+
+ <element name="transparent"><rect><color red="0.0" green="0.0" blue="0.0" alpha="0.0"/></rect></element>
+ <element name="knob_outer"><disk><color red="0.0" green="0.0" blue="0.0" alpha="0.0"/></disk></element>
+ <element name="knob_value">
+ <simplecounter maxstate="100" digits="1"><color red="0.8" green="0.8" blue="0.8"/></simplecounter>
+ </element>
+ <group name="knob">
+ <element ref="knob_outer" id="knob_~knob_input~"><bounds x="0" y="0" width="68" height="68"/></element>
+ <element ref="knob_value" inputtag="~knob_input~" inputmask="0xffff" inputraw="yes">
+ <bounds x="17" y="27" width="34" height="24"/>
+ </element>
+ <element ref="transparent" clickthrough="no"><bounds x="17" y="22" width="34" height="24"/></element>
+ </group>
+
+
+ <element name="rotating_knob">
<disk>
<color red="0" green="0" blue="0"/>
<bounds x="0" y="0" width="47" height="47"/>
@@ -69,10 +84,14 @@ license:CC0-1.0
<color red="0.13" green="0.13" blue="0.13"/>
<bounds x="2" y="2" width="43" height="43"/>
</disk>
+ <!-- For now ommit the tick. I'd like to animate it later using
+ currently unsupported element rotations with arbitrary angles:
+
<rect>
<color red="0.6" green="0.6" blue="0.6"/>
<bounds x="22" y="2" width="3" height="12"/>
</rect>
+ -->
<disk>
<color red="1" green="1" blue="1" alpha="0.05"/>
<bounds x="5" y="5" width="37" height="37"/>
@@ -81,7 +100,7 @@ license:CC0-1.0
<group name="knob_base">
<!-- body -->
- <element ref="knob">
+ <element ref="rotating_knob">
<bounds x="15" y="12" width="47" height="47"/>
</element>
@@ -562,10 +581,18 @@ license:CC0-1.0
<!-- FIXME: All input tags and input masks here are incorrect. They're just place holders for now -->
<group ref="knob_0_6_12"><!-- MASTER VOLUME --><bounds x="99" y="115" width="77" height="77"/></group>
+ <param name="knob_input" value="knob_0"/>
+ <group ref="knob"><bounds x="99" y="115" width="77" height="77"/></group>
<element ref="master_volume_text"><bounds x="90" y="196" width="96" height="12"/></element>
+
<group ref="knob_0_6_12"><!-- DEFINABLE 1 --><bounds x="99" y="223" width="77" height="77"/></group>
+ <param name="knob_input" value="knob_1"/>
+ <group ref="knob"><bounds x="99" y="223" width="77" height="77"/></group>
<element ref="definable_1_text"><bounds x="99" y="304" width="81" height="12"/></element>
+
<group ref="knob_0_6_12"><!-- DEFINABLE 2 --><bounds x="99" y="330" width="77" height="77"/></group>
+ <param name="knob_input" value="knob_2"/>
+ <group ref="knob"><bounds x="99" y="330" width="77" height="77"/></group>
<element ref="definable_2_text"><bounds x="99" y="411" width="81" height="12"/></element>
<element ref="minus2_text"><bounds x="98" y="462" width="12" height="12"/></element>
@@ -590,6 +617,8 @@ license:CC0-1.0
<element ref="lfo_1_text"><bounds x="209" y="84" width="47" height="16"/></element>
<group ref="knob_0_6_12"><!-- RATE --><bounds x="209" y="115" width="77" height="77"/></group>
+ <param name="knob_input" value="knob_3"/>
+ <group ref="knob"><bounds x="209" y="115" width="77" height="77"/></group>
<element ref="rate_text"><bounds x="235" y="196" width="28" height="12"/></element>
<element ref="led" name="LED5"><bounds x="292" y="108" width="11" height="11"/></element>
@@ -637,6 +666,8 @@ license:CC0-1.0
<!-- LFO 2 -->
<element ref="lfo_2_text"><bounds x="209" y="335" width="48" height="16"/></element>
<group ref="knob_0_6_12"><!-- RATE --><bounds x="209" y="360" width="77" height="77"/></group>
+ <param name="knob_input" value="knob_4"/>
+ <group ref="knob"><bounds x="209" y="360" width="77" height="77"/></group>
<element ref="rate_text"><bounds x="235" y="440" width="28" height="12"/></element>
<element ref="led" name="LED18"><bounds x="292" y="353" width="11" height="11"/></element>
@@ -687,9 +718,13 @@ license:CC0-1.0
<element ref="1_text"><bounds x="500" y="102" width="8" height="16"/></element>
<group ref="knob_wave"><!-- SHAPE --><bounds x="495" y="115" width="88" height="74"/></group>
+ <param name="knob_input" value="knob_5"/>
+ <group ref="knob"><bounds x="498" y="115" width="88" height="74"/></group>
<element ref="shape_text"><bounds x="525" y="196" width="36" height="12"/></element>
<group ref="knob_0_6_12"><!-- WAVE SEL / PW --><bounds x="504" y="223" width="77" height="77"/></group>
+ <param name="knob_input" value="knob_6"/>
+ <group ref="knob"><bounds x="504" y="223" width="77" height="77"/></group>
<group ref="wave_sel_pill_text"><bounds x="504" y="301" width="50" height="13"/></group>
<element ref="slash_pw_text"><bounds x="557" y="302" width="22" height="12"/></element>
@@ -697,15 +732,23 @@ license:CC0-1.0
<element ref="2_text"><bounds x="613" y="102" width="9" height="16"/></element>
<group ref="knob_wave"><!-- SHAPE --><bounds x="602" y="115" width="88" height="74"/></group>
+ <param name="knob_input" value="knob_7"/>
+ <group ref="knob"><bounds x="605" y="115" width="88" height="74"/></group>
<element ref="shape_text"><bounds x="633" y="196" width="36" height="12"/></element>
<group ref="knob_48"><!-- SEMITONE --><bounds x="720" y="115" width="77" height="77"/></group>
+ <param name="knob_input" value="knob_10"/>
+ <group ref="knob"><bounds x="720" y="115" width="77" height="77"/></group>
<element ref="semitone_text"><bounds x="730" y="196" width="59" height="12"/></element>
<group ref="knob_0_6_12"><!-- DETUNE --><bounds x="830" y="115" width="77" height="77"/></group>
+ <param name="knob_input" value="knob_9"/>
+ <group ref="knob"><bounds x="830" y="115" width="77" height="77"/></group>
<element ref="detune_text"><bounds x="845" y="196" width="44" height="12"/></element>
<group ref="knob_0_6_12"><!-- WAVE SEL / PW --><bounds x="613" y="223" width="77" height="77"/></group>
+ <param name="knob_input" value="knob_8"/>
+ <group ref="knob"><bounds x="613" y="223" width="77" height="77"/></group>
<group ref="wave_sel_pill_text"><bounds x="613" y="301" width="50" height="13"/></group>
<element ref="slash_pw_text"><bounds x="666" y="302" width="22" height="12"/></element>
@@ -719,16 +762,26 @@ license:CC0-1.0
<element ref="sync_text"><bounds x="769" y="300" width="29" height="12"/></element>
<group ref="knob_0_6_12"><!-- FM AMOUNT --><bounds x="830" y="223" width="77" height="77"/></group>
+ <param name="knob_input" value="knob_11"/>
+ <group ref="knob"><bounds x="830" y="223" width="77" height="77"/></group>
<element ref="fm_amount_text"><bounds x="834" y="300" width="66" height="12"/></element>
<!-- MIX -->
<element ref="mix_text"><bounds x="935" y="84" width="29" height="16"/></element>
<group ref="knob_1plus2"><!-- OSC BAL --><bounds x="935" y="115" width="77" height="77"/></group>
+ <param name="knob_input" value="knob_12"/>
+ <group ref="knob"><bounds x="935" y="115" width="77" height="77"/></group>
<element ref="osc_bal_text"><bounds x="947" y="196" width="52" height="12"/></element>
+
<group ref="knob_0_6_12"><!-- SUB OSC --><bounds x="935" y="223" width="77" height="77"/></group>
+ <param name="knob_input" value="knob_13"/>
+ <group ref="knob"><bounds x="935" y="223" width="77" height="77"/></group>
<element ref="sub_osc_text"><bounds x="948" y="300" width="51" height="12"/></element>
+
<group ref="knob_saturation"><!-- OSC VOL --><bounds x="935" y="330" width="77" height="77"/></group>
+ <param name="knob_input" value="knob_14"/>
+ <group ref="knob"><bounds x="935" y="330" width="77" height="77"/></group>
<element ref="osc_vol_text"><bounds x="947" y="405" width="52" height="12"/></element>
<!-- The display -->
@@ -801,7 +854,9 @@ license:CC0-1.0
<element ref="plus_text"><bounds x="890" y="547" width="6" height="12"/></element>
<element ref="program_text"><bounds x="836" y="558" width="34" height="9"/></element>
- <group ref="knob_0_6_12"><!-- PROGRAM --><bounds x="935" y="463" width="77" height="77"/></group>
+ <group ref="knob_0_6_12"><!-- Value (Program) --><bounds x="935" y="463" width="77" height="77"/></group>
+ <param name="knob_input" value="knob_15"/>
+ <group ref="knob"><bounds x="935" y="463" width="77" height="77"/></group>
<element ref="value_program_connector"><bounds x="876" y="538" width="98" height="27"/></element>
@@ -809,25 +864,37 @@ license:CC0-1.0
<element ref="filters_text"><bounds x="1102" y="84" width="68" height="16"/></element>
<group ref="knob_0_6_12"><!-- CUTOFF --><bounds x="1075" y="115" width="77" height="77"/></group>
+ <param name="knob_input" value="knob_16"/>
+ <group ref="knob"><bounds x="1075" y="115" width="77" height="77"/></group>
<element ref="cutoff_text"><bounds x="1093" y="196" width="40" height="12"/></element>
<group ref="knob_0_6_12"><!-- RESONANCE --><bounds x="1185" y="115" width="77" height="77"/></group>
+ <param name="knob_input" value="knob_20"/>
+ <group ref="knob"><bounds x="1185" y="115" width="77" height="77"/></group>
<element ref="resonance_text"><bounds x="1190" y="196" width="66" height="12"/></element>
<group ref="knob_0_6_12"><!-- ENV AMOUNT --><bounds x="1295" y="115" width="77" height="77"/></group>
+ <param name="knob_input" value="knob_21"/>
+ <group ref="knob"><bounds x="1295" y="115" width="77" height="77"/></group>
<element ref="env_amount_text"><bounds x="1299" y="196" width="70" height="12"/></element>
<group ref="knob_100pc"><!-- KEYFOLLOW --><bounds x="1405" y="115" width="77" height="77"/></group>
+ <param name="knob_input" value="knob_22"/>
+ <group ref="knob"><bounds x="1405" y="115" width="77" height="77"/></group>
<element ref="keyfollow_text"><bounds x="1412" y="196" width="63" height="12"/></element>
<element ref="filter_connector"><bounds x="1184" y="192" width="370" height="28"/></element>
<group ref="knob_1plus2"><!-- FILTER BALANCE --><bounds x="1515" y="115" width="77" height="77"/></group>
+ <param name="knob_input" value="knob_23"/>
+ <group ref="knob"><bounds x="1515" y="115" width="77" height="77"/></group>
<element ref="filter_balance_text"><bounds x="1511" y="196" width="84" height="12"/></element>
<element ref="filt_1_text"><bounds x="1512" y="212" width="27" height="12"/></element>
<element ref="filt_2_text"><bounds x="1558" y="212" width="28" height="12"/></element>
<group ref="knob_6"><!-- CUTOFF 2 --><bounds x="1075" y="223" width="77" height="77"/></group>
+ <param name="knob_input" value="knob_17"/>
+ <group ref="knob"><bounds x="1075" y="223" width="77" height="77"/></group>
<element ref="cutoff_2_text"><bounds x="1084" y="300" width="59" height="12"/></element>
<element ref="led" name="LED38"><bounds x="1197" y="230" width="11" height="11"/></element>
@@ -885,20 +952,30 @@ license:CC0-1.0
<element ref="select_text"><bounds x="1526" y="300" width="44" height="12"/></element>
<group ref="knob_0_6_12"><!-- ATTACK --><bounds x="1075" y="330" width="77" height="77"/></group>
+ <param name="knob_input" value="knob_18"/>
+ <group ref="knob"><bounds x="1075" y="330" width="77" height="77"/></group>
<element ref="attack_text"><bounds x="1091" y="405" width="44" height="12"/></element>
<group ref="knob_0_6_12"><!-- DECAY --><bounds x="1185" y="330" width="77" height="77"/></group>
+ <param name="knob_input" value="knob_24"/>
+ <group ref="knob"><bounds x="1185" y="330" width="77" height="77"/></group>
<element ref="decay_text"><bounds x="1207" y="405" width="37" height="12"/></element>
<group ref="knob_0_6_12"><!-- SUSTAIN --><bounds x="1295" y="330" width="77" height="77"/></group>
+ <param name="knob_input" value="knob_25"/>
+ <group ref="knob"><bounds x="1295" y="330" width="77" height="77"/></group>
<element ref="sustain_text"><bounds x="1308" y="405" width="51" height="12"/></element>
<element ref="gray_line"><bounds x="1360" y="413" width="71" height="1"/></element>
<group ref="knob_fall_rise"><!-- TIME --><bounds x="1405" y="326" width="77" height="81"/></group>
+ <param name="knob_input" value="knob_26"/>
+ <group ref="knob"><bounds x="1405" y="326" width="77" height="81"/></group>
<element ref="time_text"><bounds x="1431" y="405" width="29" height="12"/></element>
<group ref="knob_0_6_12"><!-- RELEASE --><bounds x="1515" y="330" width="77" height="77"/></group>
+ <param name="knob_input" value="knob_27"/>
+ <group ref="knob"><bounds x="1515" y="330" width="77" height="77"/></group>
<element ref="release_text"><bounds x="1526" y="405" width="51" height="12"/></element>
@@ -906,21 +983,222 @@ license:CC0-1.0
<element ref="amplifier_text"><bounds x="1086" y="437" width="99" height="16"/></element>
<group ref="knob_0_6_12"><!-- ATTACK --><bounds x="1075" y="463" width="77" height="77"/></group>
+ <param name="knob_input" value="knob_19"/>
+ <group ref="knob"><bounds x="1075" y="463" width="77" height="77"/></group>
<element ref="attack_text"><bounds x="1091" y="543" width="44" height="12"/></element>
<group ref="knob_0_6_12"><!-- DECAY --><bounds x="1185" y="463" width="77" height="77"/></group>
+ <param name="knob_input" value="knob_28"/>
+ <group ref="knob"><bounds x="1185" y="463" width="77" height="77"/></group>
<element ref="decay_text"><bounds x="1207" y="543" width="37" height="12"/></element>
<group ref="knob_0_6_12"><!-- SUSTAIN --><bounds x="1295" y="463" width="77" height="77"/></group>
+ <param name="knob_input" value="knob_29"/>
+ <group ref="knob"><bounds x="1295" y="463" width="77" height="77"/></group>
<element ref="sustain_text"><bounds x="1308" y="543" width="51" height="12"/></element>
<element ref="gray_line"><bounds x="1360" y="551" width="71" height="1"/></element>
<group ref="knob_fall_rise"><!-- TIME --><bounds x="1405" y="459" width="77" height="81"/></group>
+ <param name="knob_input" value="knob_30"/>
+ <group ref="knob"><bounds x="1405" y="459" width="77" height="81"/></group>
<element ref="time_text"><bounds x="1431" y="543" width="29" height="12"/></element>
<group ref="knob_0_6_12"><!-- RELEASE --><bounds x="1515" y="463" width="77" height="77"/></group>
+ <param name="knob_input" value="knob_31"/>
+ <group ref="knob"><bounds x="1515" y="463" width="77" height="77"/></group>
<element ref="release_text"><bounds x="1526" y="543" width="51" height="12"/></element>
</view>
+ <script><![CDATA[
+ file:set_resolve_tags_callback(
+ function()
+ local view = file.views["Default Layout"]
+ install_slider_callbacks(view)
+
+ for i = 0, 31 do
+ local knob_input = "knob_" .. i
+ add_knob(view, "knob_" .. knob_input, knob_input, 3.5, HORIZONTAL)
+ end
+ end)
+
+ -----------------------------------------------------------------------
+ -- Slider and knob library starts.
+ -- Can be copied as-is to other layouts.
+ -----------------------------------------------------------------------
+ VERTICAL = 0
+ HORIZONTAL = 1
+
+ local widgets = {} -- Stores slider and knob information.
+ local pointers = {} -- Tracks pointer state.
+
+ -- The knob's Y position must be animated using <animate inputtag="{port_name}">.
+ -- The click area's vertical size must exactly span the range of the
+ -- knob's movement.
+ function add_slider(view, clickarea_id, knob_id, port_name)
+ table.insert(widgets, {
+ clickarea = get_layout_item(view, clickarea_id),
+ slider_knob = get_layout_item(view, knob_id),
+ field = get_port_field(port_name),
+ is_knob = false })
+ end
+
+ -- A sweep between the attached field's min and max values requires
+ -- moving the pointer by `scale * clickarea.height` pixes.
+ function add_knob(view, clickarea_id, port_name, scale, drag_direction)
+ table.insert(widgets, {
+ clickarea = get_layout_item(view, clickarea_id),
+ field = get_port_field(port_name),
+ is_knob = true,
+ scale = scale,
+ is_horizontal = (drag_direction == HORIZONTAL) })
+ end
+
+ function get_layout_item(view, item_id)
+ local item = view.items[item_id]
+ if item == nil then
+ emu.print_error("Layout element: '" .. item_id .. "' not found.")
+ end
+ return item
+ end
+
+ function get_port_field(port_name)
+ local port = file.device:ioport(port_name)
+ if port == nil then
+ emu.print_error("Port: '" .. port_name .. "' not found.")
+ return nil
+ end
+ local field = nil
+ for k, val in pairs(port.fields) do
+ field = val
+ break
+ end
+ local field_msg = "Needs to either be an IPT_ADJUSTER, or an analog port with auto-center."
+ if field == nil then
+ emu.print_error("Port '" .. port_name .."' " .. field_msg)
+ return nil
+ end
+ if field.is_analog and (not field.centerdelta or field.centerdelta == 0) then
+ emu.print_error("Port '" .. port_name .."' " .. field_msg)
+ return nil
+ end
+ return field
+ end
+
+ local function release_pointer(pointer_id)
+ if pointers[pointer_id] then
+ local field = widgets[pointers[pointer_id].selected_widget].field
+ if field.is_analog then
+ field:clear_value()
+ end
+ end
+ pointers[pointer_id] = nil
+ end
+
+ local function pointer_updated(type, id, dev, x, y, btn, dn, up, cnt)
+ -- If a button is not pressed, reset the state of the current pointer.
+ if btn & 1 == 0 then
+ release_pointer(id)
+ return
+ end
+
+ -- If a button was just pressed, find the affected widget, if any.
+ if dn & 1 ~= 0 then
+ for i = 1, #widgets do
+ local found, relative
+ if widgets[i].slider_knob and widgets[i].slider_knob.bounds:includes(x, y) then
+ found = true
+ relative = true
+ elseif widgets[i].clickarea.bounds:includes(x, y) then
+ found = true
+ relative = false
+ end
+ if found then
+ pointers[id] = {
+ selected_widget = i,
+ relative = relative,
+ start_x = x,
+ start_y = y,
+ start_value = widgets[i].field.port:read() }
+ break
+ end
+ end
+ end
+
+ -- If there is no widget selected by the current pointer, we are done.
+ if pointers[id] == nil then
+ return
+ end
+
+ -- A widget is selected. Update its state based on the pointer's position.
+
+ local pointer = pointers[id]
+ local widget = widgets[pointer.selected_widget]
+
+ local min_value = widget.field.minvalue
+ local max_value = widget.field.maxvalue
+ local value_range = max_value - min_value
+
+ local new_value
+ if widget.is_knob then
+ if widget.is_horizontal then
+ local step_x = value_range / (widget.scale * widget.clickarea.bounds.width)
+ new_value = pointer.start_value + (x - pointer.start_x) * step_x
+ else
+ local step_y = value_range / (widget.scale * widget.clickarea.bounds.height)
+ new_value = pointer.start_value + (pointer.start_y - y) * step_y
+ end
+ else
+ local knob_half_height = widget.slider_knob.bounds.height / 2
+ local min_y = widget.clickarea.bounds.y0 + knob_half_height
+ local max_y = widget.clickarea.bounds.y1 - knob_half_height
+
+ if pointer.relative then
+ -- User clicked on the knob. The new value will depend on how
+ -- much the knob was dragged.
+ new_value = pointer.start_value - value_range * (y - pointer.start_y) / (max_y - min_y)
+ else
+ -- User clicked elsewhere on the slider. The new value will depend on
+ -- the absolute position of the click.
+ new_value = max_value - value_range * (y - min_y) / (max_y - min_y)
+ end
+ end
+
+ new_value = math.floor(new_value + 0.5)
+ if new_value < min_value then new_value = min_value end
+ if new_value > max_value then new_value = max_value end
+
+ if widget.field.is_analog then
+ widget.field:set_value(new_value)
+ else
+ widget.field.user_value = new_value
+ end
+ end
+
+ local function pointer_left(type, id, dev, x, y, up, cnt)
+ release_pointer(id)
+ end
+
+ local function pointer_aborted(type, id, dev, x, y, up, cnt)
+ release_pointer(id)
+ end
+
+ local function forget_pointers()
+ for id, pointer in pairs(pointers) do
+ release_pointer(id)
+ end
+ pointers = {}
+ end
+
+ function install_slider_callbacks(view)
+ view:set_pointer_updated_callback(pointer_updated)
+ view:set_pointer_left_callback(pointer_left)
+ view:set_pointer_aborted_callback(pointer_aborted)
+ view:set_forget_pointers_callback(forget_pointers)
+ end
+ -----------------------------------------------------------------------
+ -- Slider and knob library ends.
+ -----------------------------------------------------------------------
+ ]]></script>
+
</mamelayout>