diff options
| author | 2025-11-03 04:05:25 -0300 | |
|---|---|---|
| committer | 2025-11-03 08:05:25 +0100 | |
| commit | 161f7ea9418e96883552257af983b7accd853b0c (patch) | |
| tree | 3b020a66bed4d5dd0c92946dabc904746cae5645 | |
| parent | 5e08544fef89da3313bf2ca1de9c261638c94414 (diff) | |
[Access Virus] Analog input knobs for Virus A & B (#14461)
| -rw-r--r-- | src/devices/cpu/mcs51/sab80c535.cpp | 20 | ||||
| -rw-r--r-- | src/devices/cpu/mcs51/sab80c535.h | 18 | ||||
| -rw-r--r-- | src/mame/access/acvirus.cpp | 135 | ||||
| -rw-r--r-- | src/mame/layout/virusb.lay | 284 |
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> |
