From b2fc2ab1b25746af566971d8fad54a53c9de5401 Mon Sep 17 00:00:00 2001 From: Christian Brunschen <6741909+cbrunschen@users.noreply.github.com> Date: Sun, 2 Nov 2025 14:40:06 +0000 Subject: [PATCH] Improve the Panel, and add three more views, including a working keyboard. The panel view is improved after re-measuring on a real keyboard. Colors have been changed a bit in order to improve contrast. Three new views have been added: 1. a Full view of the keyboard, including the controls to the left of the keyboard, specifically the Pitch-Bend and Modulation Wheels and the Patch Select buttons, as well as a representation of the floppy drive and the little "storage compartment" above it that is a bit of a characteristic shape. 2. a "Compact" view that includes all the controls and a 37-key (3-octave) keyboard that may be both legible and playable at the same time, on a reasonably-sized touch screen. 3. a "Tablet" view of the front-panel controls, with the display and buttons stacked vertically instead of stretched horizontally, as well as the "Patch Select" buttons. This view, together with a MIDI keyboard controller, this should give users a reasonably workable facsimile of the real thing: Keys, Pitch Bend and Modulation wheels on their MIDI keyboard controller; all the keyboard-specific controls and the Ensoniq-specific Patch Select buttons on the "Tablet" view. The Full and Compact views also include a keyboard: the full 61 keys and a reduced 37 keys respectively. These work, tracking pointer events, and generate not just key-down events with velocity, but also per-key "pressure" or "aftertouch". Velocity is simulated by position of the click/touch; Pressure by movement of the pointer on the key while down. Both are also animated: Velocity is shown as a colour between blue (1) and green (127), Pressure between yellow (1) and red (127). --- src/mame/ensoniq/esqpanel.cpp | 117 +- src/mame/ensoniq/esqpanel.h | 9 + src/mame/layout/sd1.lay | 4266 ++++++++++++++++++++++++--------- src/mame/layout/sd132.lay | 3273 +++++++++++++++++++++++++ src/mame/layout/vfx.lay | 3977 ++++++++++++++++++++++-------- src/mame/layout/vfxsd.lay | 4266 ++++++++++++++++++++++++--------- 6 files changed, 12724 insertions(+), 3184 deletions(-) create mode 100644 src/mame/layout/sd132.lay diff --git a/src/mame/ensoniq/esqpanel.cpp b/src/mame/ensoniq/esqpanel.cpp index ce0614064d94f..7d1009bb9bed2 100644 --- a/src/mame/ensoniq/esqpanel.cpp +++ b/src/mame/ensoniq/esqpanel.cpp @@ -10,9 +10,10 @@ #include "main.h" #include "esq2by40_vfx.lh" -#include "sd1.lh" #include "vfx.lh" #include "vfxsd.lh" +#include "sd1.lh" +#include "sd132.lh" #include @@ -679,6 +680,32 @@ void esqpanel_device::set_button(uint8_t button, bool pressed) } } +void esqpanel_device::key_down(uint8_t key, uint8_t velocity) +{ + if (velocity < 1) + velocity = 1; + else if (velocity > 127) + velocity = 127; + + xmit_char(0x80 | (key & 0x3f)); + xmit_char(velocity); +} + +void esqpanel_device::key_pressure(uint8_t key, uint8_t pressure) +{ + if (pressure > 127) + pressure = 127; + + xmit_char(0x40 | (key & 0x3f)); + xmit_char(pressure); +} + +void esqpanel_device::key_up(uint8_t key) +{ + xmit_char(key & 0x3f); + xmit_char(0x40); +} + /* panel with 1x22 VFD display used in the EPS-16 and EPS-16 Plus */ void esqpanel1x22_device::device_add_mconfig(machine_config &config) @@ -719,8 +746,10 @@ void esqpanel2x40_vfx_device::device_add_mconfig(machine_config &config) config.set_default_layout(layout_vfx); else if (m_panel_type == VFX_SD) config.set_default_layout(layout_vfxsd); - else if (m_panel_type == SD_1 || m_panel_type == SD_1_32) + else if (m_panel_type == SD_1) config.set_default_layout(layout_sd1); + else if (m_panel_type == SD_1_32) + config.set_default_layout(layout_sd132); else // lowest common demonimator as the default: just the VFD. config.set_default_layout(layout_esq2by40_vfx); } @@ -752,7 +781,7 @@ bool esqpanel2x40_vfx_device::write_contents(std::ostream &o) } void esqpanel2x40_vfx_device::update_lights() { - // set the lights according to their status and bllink phase. + // set the lights according to their status and blink phase. int32_t lights = 0; int32_t bit = 1; for (int i = 0; i < 16; i++) @@ -763,6 +792,9 @@ void esqpanel2x40_vfx_device::update_lights() { } bit <<= 1; } + // We use the next bit, 16, for the floppy LED + if (m_floppy_active) + lights |= 1 << 16; m_lights = lights; } @@ -810,6 +842,22 @@ static INPUT_PORTS_START(esqpanel2x40_vfx_device) PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(esqpanel2x40_vfx_device::button_change), 32 + i) } + PORT_START("patch_select") + PORT_BIT(0x01, IP_ACTIVE_HIGH, IPT_KEYBOARD); + PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(esqpanel2x40_vfx_device::patch_select_change), 1) + PORT_BIT(0x02, IP_ACTIVE_HIGH, IPT_KEYBOARD); + PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(esqpanel2x40_vfx_device::patch_select_change), 1) + + PORT_START("analog_pitch_bend") + PORT_BIT(0x3ff, 0x200, IPT_PADDLE) PORT_NAME("Pitch Bend") PORT_MINMAX(0, 0x3ff) PORT_SENSITIVITY(30) PORT_KEYDELTA(15) PORT_CENTERDELTA(128) + PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(esqpanel2x40_vfx_device::analog_value_change), 0) + + PORT_START("analog_mod_wheel") + // An adjuster, but with range 0 .. 1023, to match the 10 bit resolution of the OTIS ADC + configurer.field_alloc(IPT_ADJUSTER, 0x3ff, 0x3ff, "Modulation"); + configurer.field_set_min_max(0, 0x3ff); + PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(esqpanel2x40_vfx_device::analog_value_change), 2) + PORT_START("analog_data_entry") // An adjuster, but with range 0 .. 1023, to match the 10 bit resolution of the OTIS ADC configurer.field_alloc(IPT_ADJUSTER, 0x200, 0x3ff, "Data Entry"); @@ -822,6 +870,19 @@ static INPUT_PORTS_START(esqpanel2x40_vfx_device) configurer.field_set_min_max(0, 0x3ff); PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(esqpanel2x40_vfx_device::analog_value_change), 5) + for (int i = 0; i < 61; i++) { + std::string port_name = util::string_format("key_%d", i); + PORT_START(port_name.c_str()); + PORT_BIT(0x3fff, 0x0, IPT_PADDLE) + PORT_GM_NOTE(36 + i) + + // the following must be set ot MAME complains, but we don't use them: + // we always pass the values through explicitly, overriding anything else. + PORT_SENSITIVITY(1) PORT_KEYDELTA(1) PORT_CENTERDELTA(1) + + PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(esqpanel2x40_vfx_device::key_change), i) + } + INPUT_PORTS_END ioport_constructor esqpanel2x40_vfx_device::device_input_ports() const @@ -836,13 +897,61 @@ INPUT_CHANGED_MEMBER(esqpanel2x40_vfx_device::button_change) esqpanel_device::set_button(param, newval != 0); } +// A Patch Select button is pressed on the internal panel +INPUT_CHANGED_MEMBER(esqpanel2x40_vfx_device::patch_select_change) +{ + // Update the internal state from the full value of the port: presented as an analog value! + int value = (field.port().read() & 0x03) * 250; + set_analog_value(1, value << 6); +} + // An anlog value was changed on the internal panel INPUT_CHANGED_MEMBER(esqpanel2x40_vfx_device::analog_value_change) { int channel = param; int clamped = std::clamp((int)newval, 0, 1023); int value = clamped << 6; - esqpanel_device::set_analog_value(channel, value); + set_analog_value(channel, value); +} + +// An key changed on the internal panel +INPUT_CHANGED_MEMBER(esqpanel2x40_vfx_device::key_change) +{ + uint8_t key = param & 0x3f; + uint8_t velocity = newval & 0x7f; + + if (velocity == 0) + { + uint8_t old_pressure = (oldval >> 7) & 0x7f; + if (old_pressure != 0) + { + // there was pressure before; reset the pressure to zero before the key-up event. + key_pressure(key, 0); + } + key_up(key); + } + else + { + uint8_t old_velocity = oldval & 0x7f; + uint8_t pressure = (newval >> 7) & 0x7f; + + if (old_velocity == 0) + { + // this is a key down event. Might also include an ensuing pressure event. + key_down(key, velocity); + } + + if (pressure != 0) + { + // if we have pressure, then it is (also) a pressure event. + key_pressure(key, pressure); + } + } +} + +void esqpanel2x40_vfx_device::set_floppy_active(bool floppy_active) { + m_floppy_active = floppy_active; + update_lights(); } ioport_value esqpanel2x40_vfx_device::get_adjuster_value(required_ioport &ioport) diff --git a/src/mame/ensoniq/esqpanel.h b/src/mame/ensoniq/esqpanel.h index a63622611a64e..b411b49939d93 100644 --- a/src/mame/ensoniq/esqpanel.h +++ b/src/mame/ensoniq/esqpanel.h @@ -34,6 +34,10 @@ class esqpanel_device : public device_t, public device_serial_interface void xmit_char(uint8_t data); virtual void set_analog_value(offs_t offset, uint16_t value); virtual void set_button(uint8_t button, bool pressed); + virtual void key_down(uint8_t key, uint8_t velocity); + virtual void key_pressure(uint8_t key, uint8_t pressure); + virtual void key_up(uint8_t key); + virtual void set_floppy_active(bool floppy_active) { } protected: // construction/destruction @@ -108,7 +112,10 @@ class esqpanel2x40_vfx_device : public esqpanel_device { esqpanel2x40_vfx_device(const machine_config &mconfig, const char *tag, device_t *owner, int panel_type = UNKNOWN, uint32_t clock = 0); DECLARE_INPUT_CHANGED_MEMBER(button_change); + DECLARE_INPUT_CHANGED_MEMBER(patch_select_change); DECLARE_INPUT_CHANGED_MEMBER(analog_value_change); + DECLARE_INPUT_CHANGED_MEMBER(key_change); + void set_floppy_active(bool floppy_active) override; void set_family_member(int family_member); @@ -150,6 +157,8 @@ class esqpanel2x40_vfx_device : public esqpanel_device { required_ioport m_analog_data_entry; required_ioport m_analog_volume; + bool m_floppy_active = false; + TIMER_CALLBACK_MEMBER(update_blink); void update_lights(); diff --git a/src/mame/layout/sd1.lay b/src/mame/layout/sd1.lay index 4ed7f625ce72f..abf61a518f703 100644 --- a/src/mame/layout/sd1.lay +++ b/src/mame/layout/sd1.lay @@ -1,1197 +1,3263 @@ - - - - - - - - - ]]> - - - - - - - - - - ]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ]]> - - - - - - ]]> - - - - - - - - ]]> - - - - - - ]]> - - - - - - - - ]]> - - - - - - ]]> - - - - - - - - ]]> - - - - - - ]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + ]]> + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + ]]> + + + + + + + + + + + ]]> + + + + + + + + + + + + + + ]]> + + + + + + + + + + + + + ]]> + + + + + + + + + + + + + + + + ]]> + + + + + + + + + + + + ]]> + + + + + + + + + + + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ]]> + + + + + + + ]]> + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + + + + + + + + + + + ]]> + + + + + + ]]> + + + + + + + + ]]> + + + + + + ]]> + + + + + + + + ]]> + + + + + + ]]> + + + + + + + + ]]> + + + + + + ]]> + + + + + + + + ]]> + + + + + + ]]> + + + + + + + + ]]> + + + + + + ]]> + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ]]> + + + + + + + ]]> + + + + + + + ]]> + + + + + + + ]]> + + + + + + + ]]> + + + + + + + ]]> + + + + + + + ]]> + + + + + + + ]]> + + + + + + + ]]> + + + + + + + ]]> + + + + + + + ]]> + + + + + + + ]]> + + + + + + + ]]> + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - + + function PointerManager:releasePointer(id) + local pointer = self.pointers[id] + + if pointer ~= nil then + if pointer.active ~= nil then + pointer.active.handler:releasePointer(id, pointer.active) + end + pointer.active = nil + end + + self.pointers[id] = nil + end + + function PointerManager:pointerLeft(type, id, dev, x, y, up, cnt) + self:releasePointer(id) + end + + function PointerManager:pointerAborted(type, id, dev, x, y, up, cnt) + self:releasePointer(id) + end + + function PointerManager:forgetPointers() + for id, pointer in pairs(self.pointers) do + self:releasePointer(id) + end + self.pointers = {} + end + + function PointerManager:registerCallbacks() + self.view:set_pointer_updated_callback(function(type, id, dev, x, y, btn, dn, up, cnt) + self:pointerUpdated(type, id, dev, x, y, btn, dn, up, cnt) + end) + + self.view:set_pointer_left_callback(function(type, id, dev, x, y, up, cnt) + self:pointerLeft(type, id, dev, x, y, up, cnt) + end) + + self.view:set_pointer_aborted_callback(function(type, id, dev, x, y, up, cnt) + self:pointerAborted(type, id, dev, x, y, up, cnt) + end) + + self.view:set_forget_pointers_callback(function() + self:forgetPointers() + end) + end + + function PointerManager:unregisterCallbacks() + self.view:set_pointer_updated_callback(nil) + self.view:set_pointer_left_callback(nil) + self.view:set_pointer_aborted_callback(nil) + self.view:set_forget_pointers_callback(nil) + end + + -- + -- The Slider Handler + -- + + SliderHandler = {} + + -- The knob's Y position must be animated using . + -- The click area's vertical size must exactly span the range of the + -- knob's movement. + + function SliderHandler:create(view, clickarea_id, knob_id, port_name, invert, autocenter) + local instance = { + view = view, + invert = invert, + autocenter = autocenter, + clickarea = view.items[clickarea_id], + knob = view.items[knob_id], + field = find_ioport_field(port_name), + + -- No setmetatable in the layout plugin. Manually copy methods across. + pointerUpdated = self.pointerUpdated, + releasePointer = self.releasePointer, + } + + if instance.clickarea == nil then + emu.print_error("Slider element: '" .. clickarea_id .. "' not found.") + return nil + end + + if instance.knob == nil then + emu.print_error("Slider knob element: '" .. knob_id .. "' not found.") + return nil + end + + if instance.field == nil then + return nil + end + + return instance + end + + function SliderHandler:pointerUpdated(state, type, id, dev, x, y, btn, dn, up, cnt) + if dn & 1 ~= 0 then + -- It's a button press + if self.knob.bounds:includes(x, y) then + return { + handler = self, + dy = y - self.knob.bounds.y0 + } + end + else + if state == nil then + -- not a down event, and not already claimed - not for us to handle. + return nil + end + + -- not a button down (up is also handled elsewhere), so it must be a move + local yy = y - state.dy + local fraction = (yy - self.clickarea.bounds.y0) / (self.clickarea.bounds.height - self.knob.bounds.height) + local new_value = 1023 * (1 - fraction) + if self.invert then + new_value = 1023 - new_value + end + new_value = math.floor(new_value + 0.5) + clamped_value = clamp(new_value, 0, 1023) + set_field_value(self.field, clamped_value) + return state + end + return nil + end + + function SliderHandler:releasePointer(id, state) + if self.autocenter then + set_field_value(self.field, 512) + end + return nil + end + + -- + -- The Keyboard Handler + -- + + local octave_shift = 164.5 + local w_white = 22.5 + local f_white = w_white / (w_white + 1) + local l_black = 88 + local l_white = 138 + local y_12 = (l_black + 2) / l_white + local y_12_0 = l_black / l_white + + local strike_both_bottom = l_black - 3 + local strike_both_top = l_black - 43 + + local strike_white_low_bottom = l_white - 3 + local strike_white_low_top = l_white - 43 + + local strike_white_break = (strike_both_bottom + strike_white_low_top) / 2 + + local pressure_length = 25 + local pressure_hysteresis = 3 + + -- The ranges where we can find the tops of the 12 keys within an octave + local k12 = { + { key=0, x0=0, x1=79027/1000000, black=false, l = l_white }, + { key=1, x0=1769/20000, x1=807/5000, black=true, l = l_black }, + { key=2, x0=8541/50000, x1=4997/20000, black=false, l = l_white }, + { key=3, x0=25927/100000, x1=16611/50000, black=true, l = l_black }, + { key=4, x0=8541/25000, x1=42067/100000, black=false, l = l_white }, + { key=5, x0=1707/4000, x1=4997/10000, black=false, l = l_white }, + { key=6, x0=1591/3125, x1=58207/100000, black=true, l = l_black }, + { key=7, x0=59149/100000, x1=16611/25000, black=false, l = l_white }, + { key=8, x0=33693/50000, x1=74681/100000, black=true, l = l_black }, + { key=9, x0=75623/100000, x1=41459/50000, black=false, l = l_white }, + { key=10, x0=4193/5000, x1=18231/20000, black=true, l = l_black }, + { key=11, x0=92097/100000, x1=3106/3125, black=false, l = l_white }, + } + + -- 85 equally sized ranges that each contain exactly one key, and the key + -- thaty they contain, so we can check for being outside the edge + local x_to_k12 = { + k12[1], k12[1], k12[1], k12[1], k12[1], k12[1], k12[1], + k12[2], k12[2], k12[2], k12[2], k12[2], k12[2], k12[2], + k12[3], k12[3], k12[3], k12[3], k12[3], k12[3], k12[3], k12[3], + k12[4], k12[4], k12[4], k12[4], k12[4], k12[4], k12[4], + k12[5], k12[5], k12[5], k12[5], k12[5], k12[5], k12[5], + k12[6], k12[6], k12[6], k12[6], k12[6], k12[6], k12[6], + k12[7], k12[7], k12[7], k12[7], k12[7], k12[7], k12[7], + k12[8], k12[8], k12[8], k12[8], k12[8], k12[8], k12[8], + k12[9], k12[9], k12[9], k12[9], k12[9], k12[9], k12[9], + k12[10], k12[10], k12[10], k12[10], k12[10], k12[10], k12[10], + k12[11], k12[11], k12[11], k12[11], k12[11], k12[11], k12[11], + k12[12], k12[12], k12[12], k12[12], k12[12], k12[12], k12[12], + } + + local makeFindKey = function(n_octaves) + local octaves_width = n_octaves * octave_shift + local full_width = octaves_width + w_white + + local function find_12_key(x, y, w, h) + if x > octaves_width then + return 12 * n_octaves + end + + local octave, kx = math.modf((x / w) * (full_width / octave_shift)) + if octave == n_octaves then + return 12 * octave + end + + local ki = math.floor(85 * kx) + local candidate = x_to_k12[ki + 1] + if candidate == nil then + return nil + end + local ci = 12 * octave + candidate.key + if candidate.x0 <= kx and kx <= candidate.x1 then + if candidate.black then + rel_y = y / h + if rel_y <= y_12_0 then + return ci + else + return nil + end + else + return ci + end + else + return nil + end + end + + local function find_7_key(x, w) + local octave, kx = math.modf((x / w) * (full_width / octave_shift)) + local ki, kkx = math.modf(7 * kx) + if kkx <= f_white then + if ki < 3 then + return 12 * octave + 2 * ki + else + return 12 * octave + 2 * ki - 1 + end + end + return nil + end + + return function (x, y, w, h) + rel_y = y / h + if rel_y < 0 or 1 < rel_y then + return nil + elseif rel_y < y_12 then + return find_12_key(x, y, w, h) + else + return find_7_key(x, w) + end + end + end + + KeyboardHandler = {} + + function KeyboardHandler:create(view, clickarea_id, key_id_prefix, port_prefix, from_octave, n_octaves) + local instance = { + view = view, + clickarea = view.items[clickarea_id], + from_octave = from_octave, + n_octaves = n_octaves, + from_key = from_key, + n_keys = n_keys, + keys = {}, + findKey = makeFindKey(n_octaves), -- plain function call to create the function! + + -- No setmetatable in the layout plugin. Manually copy methods across. + pointerUpdated = self.pointerUpdated, + releasePointer = self.releasePointer, + keyDown = self.keyDown, + velocity = self.velocity, + pressure = self.pressure, + } + + if instance.clickarea == nil then + emu.print_error("Keyboard click area '"..clickarea_id.."' not found.") + return nil + end + + local from_key = 12 * from_octave + local n_keys = 12 * n_octaves + 1 + + for i = 0, n_keys -1 do + local key_info = k12[(i % 12) + 1] + local key = { + id = key_id_prefix .. i, + velocity = 0, + pressure = 0, + info = key_info, + black = key_info.black, + l = key_info.l, + } + + key.item = view.items[key.id] + if key.item == nil then + emu.print_error("Key item '"..key.id.."' not found.") + return nil + end + + key.field = find_ioport_field(port_prefix .. (from_key + i - 36)) + if key.field == nil then + return nil + end + + function key:setVelocity(velocity) + if velocity ~= self.velocity or self.pressure ~= 0 then + self.velocity = velocity & 0x7f + self.pressure = 0 + set_field_value(self.field, self.velocity) + end + end + + function key:setPressure(pressure) + if pressure ~= self.pressure then + self.pressure = pressure & 0x7f + set_field_value(self.field, (self.pressure << 7) | self.velocity) + end + end + + key.item:set_animation_state_callback(function() + new_state = 0 + if key.pressure > 0 then + new_state = 128 + key.pressure + elseif key.velocity > 0 then + new_state = key.velocity + else + new_state = 0 + end + if new_state ~= key.animation_state then + key.animation_state = new_state + end + return new_state + end) + + table.insert(instance.keys, key) + end + + return instance + end + + function KeyboardHandler:velocity(state) + + local function velocityWithin(pos, top, bottom) + local fraction = (bottom - pos) / (bottom - top) + local velocity = math.floor(1.5 + 126 * fraction) + velocity = clamp(velocity, 1, 127) + return velocity + end + + local key = state.key + local h = state.key.item.bounds.height + + local y = (state.y / h) * state.key.l + if state.key.black or y < strike_white_break then + return velocityWithin(y, strike_both_top, strike_both_bottom) + else + return velocityWithin(y, strike_white_low_top, strike_white_low_bottom) + end + end + + function KeyboardHandler:pressure(state) + local key = state.key + local h = state.key.item.bounds.height + + local y = (state.y / h) * key.l + local y_max = (state.max_y / h) * key.l + + local fraction = ((y_max - pressure_hysteresis) - y) / pressure_length + local pressure = math.floor(0.5 + 127 * fraction) + pressure = clamp(pressure, 0, 127) + return pressure + end + + function KeyboardHandler:keyDown(x, y) + local bounds = self.clickarea.bounds + local w = bounds.width + local h = bounds.height + local dx = x - bounds.x0 + local dy = y - bounds.y0 + + local state = { + handler = self, + } + local key_number = self.findKey(dx, dy, w, h) + + state.key_number = key_number + state.key = nil + if key_number ~= nil then + local key = self.keys[key_number + 1] + if key ~= nil then + state.key = key + -- coordinates within the key of the latest pointer position + state.x = x - key.item.bounds.x0 + state.y = y - key.item.bounds.y0 + state.max_y = state.y + state.key:setVelocity(self:velocity(state)) + end + end + + -- return the state to track this movement, claiming it as ours. + return state + end + + function KeyboardHandler:pointerUpdated(state, type, id, dev, x, y, btn, dn, up, cnt) + local bounds = self.clickarea.bounds + if bounds:includes(x, y) and (dn & 1 ~= 0) then + -- This is a key down event on the keyboard, claim the pointer. + return self:keyDown(x, y) + end + + if state ~= nil then + -- not a key down event, but a move after we claimed the pointer after a key down event + + local w = bounds.width + local h = bounds.height + local dx = x - bounds.x0 + local dy = y - bounds.y0 + + local new_key_number = self.findKey(dx, dy, w, h) + + if new_key_number ~= state.key_number then + -- this is a move across, or on, or off, keys + if state.key ~= nil then + state.key:setVelocity(0) + end + + -- store the new key number, which may be nil, in the state. + state.key_number = new_key_number + + if new_key_number ~= nil then + -- it's a move onto a new key, treat it as a key-down event + state.key = self.keys[new_key_number + 1] + + if state.key ~= nil then + state.x = x - state.key.item.bounds.x0 + state.y = y - state.key.item.bounds.y0 + state.max_y = state.y + state.key:setVelocity(self:velocity(state)) + end + else + -- set the key to nil as well. + state.key = nil + end + else + if state.key ~= nil then + -- we're remaining on the same key + state.x = x - state.key.item.bounds.x0 + state.y = y - state.key.item.bounds.y0 + if state.y > state.max_y then + state.max_y = state.y + end + + -- this is a potential change in key pressure. + state.key:setPressure(self:pressure(state)) + end + -- else we're remaining between keys and return the same state. + end + + -- return the same state, still claiming this pointer + return state + + end + + return nil + end + + function KeyboardHandler:releasePointer(id, state) + if state.key ~= nil then + state.key:setVelocity(0) + end + end + + ----------------------------------------------------------------------- + -- Slider library ends. + ----------------------------------------------------------------------- + + + + ]]> diff --git a/src/mame/layout/sd132.lay b/src/mame/layout/sd132.lay new file mode 100644 index 0000000000000..aa2ddf74f5043 --- /dev/null +++ b/src/mame/layout/sd132.lay @@ -0,0 +1,3273 @@ + + + + + + + + + + + + + ]]> + + + + + + + + + + + ]]> + + + + + + + + + + + ]]> + + + + + + + + + + + + + + ]]> + + + + + + + + + + + + + ]]> + + + + + + + + + + + + + + + + ]]> + + + + + + + + + + + + ]]> + + + + + + + + + + + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ]]> + + + + + + + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ]]> + + + + + + ]]> + + + + + + + + ]]> + + + + + + ]]> + + + + + + + + ]]> + + + + + + ]]> + + + + + + + + ]]> + + + + + + ]]> + + + + + + + + ]]> + + + + + + ]]> + + + + + + + + ]]> + + + + + + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ]]> + + + + + + + ]]> + + + + + + + ]]> + + + + + + + ]]> + + + + + + + ]]> + + + + + + + ]]> + + + + + + + ]]> + + + + + + + ]]> + + + + + + + ]]> + + + + + + + ]]> + + + + + + + ]]> + + + + + + + ]]> + + + + + + + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/mame/layout/vfx.lay b/src/mame/layout/vfx.lay index 748ed8423f305..80040a3b377e6 100644 --- a/src/mame/layout/vfx.lay +++ b/src/mame/layout/vfx.lay @@ -1,1064 +1,3081 @@ - - - - - - - - - ]]> - - - - - - - - - - ]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ]]> - - - - - - ]]> - - - - - - - - ]]> - - - - - - ]]> - - - - - - - - ]]> - - - - - - ]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + ]]> + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + ]]> + + + + + + + + + + + ]]> + + + + + + + + + + + + + + ]]> + + + + + + + + + + + + + ]]> + + + + + + + + + + + + ]]> + + + + + + + + + + + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ]]> + + + + + + + ]]> + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + + + + + + + + + + + ]]> + + + + + + ]]> + + + + + + + + ]]> + + + + + + ]]> + + + + + + + + ]]> + + + + + + ]]> + + + + + + + + ]]> + + + + + + ]]> + + + + + + + + ]]> + + + + + + ]]> + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ]]> + + + + + + + ]]> + + + + + + + ]]> + + + + + + + ]]> + + + + + + + ]]> + + + + + + + ]]> + + + + + + + ]]> + + + + + + + ]]> + + + + + + + ]]> + + + + + + + ]]> + + + + + + + ]]> + + + + + + + ]]> + + + + + + + ]]> + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - + + -- + -- The Keyboard Handler + -- + + local octave_shift = 164.5 + local w_white = 22.5 + local f_white = w_white / (w_white + 1) + local l_black = 88 + local l_white = 138 + local y_12 = (l_black + 2) / l_white + local y_12_0 = l_black / l_white + + local strike_both_bottom = l_black - 3 + local strike_both_top = l_black - 43 + + local strike_white_low_bottom = l_white - 3 + local strike_white_low_top = l_white - 43 + + local strike_white_break = (strike_both_bottom + strike_white_low_top) / 2 + + local pressure_length = 25 + local pressure_hysteresis = 3 + + -- The ranges where we can find the tops of the 12 keys within an octave + local k12 = { + { key=0, x0=0, x1=79027/1000000, black=false, l = l_white }, + { key=1, x0=1769/20000, x1=807/5000, black=true, l = l_black }, + { key=2, x0=8541/50000, x1=4997/20000, black=false, l = l_white }, + { key=3, x0=25927/100000, x1=16611/50000, black=true, l = l_black }, + { key=4, x0=8541/25000, x1=42067/100000, black=false, l = l_white }, + { key=5, x0=1707/4000, x1=4997/10000, black=false, l = l_white }, + { key=6, x0=1591/3125, x1=58207/100000, black=true, l = l_black }, + { key=7, x0=59149/100000, x1=16611/25000, black=false, l = l_white }, + { key=8, x0=33693/50000, x1=74681/100000, black=true, l = l_black }, + { key=9, x0=75623/100000, x1=41459/50000, black=false, l = l_white }, + { key=10, x0=4193/5000, x1=18231/20000, black=true, l = l_black }, + { key=11, x0=92097/100000, x1=3106/3125, black=false, l = l_white }, + } + + -- 85 equally sized ranges that each contain exactly one key, and the key + -- thaty they contain, so we can check for being outside the edge + local x_to_k12 = { + k12[1], k12[1], k12[1], k12[1], k12[1], k12[1], k12[1], + k12[2], k12[2], k12[2], k12[2], k12[2], k12[2], k12[2], + k12[3], k12[3], k12[3], k12[3], k12[3], k12[3], k12[3], k12[3], + k12[4], k12[4], k12[4], k12[4], k12[4], k12[4], k12[4], + k12[5], k12[5], k12[5], k12[5], k12[5], k12[5], k12[5], + k12[6], k12[6], k12[6], k12[6], k12[6], k12[6], k12[6], + k12[7], k12[7], k12[7], k12[7], k12[7], k12[7], k12[7], + k12[8], k12[8], k12[8], k12[8], k12[8], k12[8], k12[8], + k12[9], k12[9], k12[9], k12[9], k12[9], k12[9], k12[9], + k12[10], k12[10], k12[10], k12[10], k12[10], k12[10], k12[10], + k12[11], k12[11], k12[11], k12[11], k12[11], k12[11], k12[11], + k12[12], k12[12], k12[12], k12[12], k12[12], k12[12], k12[12], + } + + local makeFindKey = function(n_octaves) + local octaves_width = n_octaves * octave_shift + local full_width = octaves_width + w_white + + local function find_12_key(x, y, w, h) + if x > octaves_width then + return 12 * n_octaves + end + + local octave, kx = math.modf((x / w) * (full_width / octave_shift)) + if octave == n_octaves then + return 12 * octave + end + + local ki = math.floor(85 * kx) + local candidate = x_to_k12[ki + 1] + if candidate == nil then + return nil + end + local ci = 12 * octave + candidate.key + if candidate.x0 <= kx and kx <= candidate.x1 then + if candidate.black then + rel_y = y / h + if rel_y <= y_12_0 then + return ci + else + return nil + end + else + return ci + end + else + return nil + end + end + + local function find_7_key(x, w) + local octave, kx = math.modf((x / w) * (full_width / octave_shift)) + local ki, kkx = math.modf(7 * kx) + if kkx <= f_white then + if ki < 3 then + return 12 * octave + 2 * ki + else + return 12 * octave + 2 * ki - 1 + end + end + return nil + end + + return function (x, y, w, h) + rel_y = y / h + if rel_y < 0 or 1 < rel_y then + return nil + elseif rel_y < y_12 then + return find_12_key(x, y, w, h) + else + return find_7_key(x, w) + end + end + end + + KeyboardHandler = {} + + function KeyboardHandler:create(view, clickarea_id, key_id_prefix, port_prefix, from_octave, n_octaves) + local instance = { + view = view, + clickarea = view.items[clickarea_id], + from_octave = from_octave, + n_octaves = n_octaves, + from_key = from_key, + n_keys = n_keys, + keys = {}, + findKey = makeFindKey(n_octaves), -- plain function call to create the function! + + -- No setmetatable in the layout plugin. Manually copy methods across. + pointerUpdated = self.pointerUpdated, + releasePointer = self.releasePointer, + keyDown = self.keyDown, + velocity = self.velocity, + pressure = self.pressure, + } + + if instance.clickarea == nil then + emu.print_error("Keyboard click area '"..clickarea_id.."' not found.") + return nil + end + + local from_key = 12 * from_octave + local n_keys = 12 * n_octaves + 1 + + for i = 0, n_keys -1 do + local key_info = k12[(i % 12) + 1] + local key = { + id = key_id_prefix .. i, + velocity = 0, + pressure = 0, + info = key_info, + black = key_info.black, + l = key_info.l, + } + + key.item = view.items[key.id] + if key.item == nil then + emu.print_error("Key item '"..key.id.."' not found.") + return nil + end + + key.field = find_ioport_field(port_prefix .. (from_key + i - 36)) + if key.field == nil then + return nil + end + + function key:setVelocity(velocity) + if velocity ~= self.velocity or self.pressure ~= 0 then + self.velocity = velocity & 0x7f + self.pressure = 0 + set_field_value(self.field, self.velocity) + end + end + + function key:setPressure(pressure) + if pressure ~= self.pressure then + self.pressure = pressure & 0x7f + set_field_value(self.field, (self.pressure << 7) | self.velocity) + end + end + + key.item:set_animation_state_callback(function() + new_state = 0 + if key.pressure > 0 then + new_state = 128 + key.pressure + elseif key.velocity > 0 then + new_state = key.velocity + else + new_state = 0 + end + if new_state ~= key.animation_state then + key.animation_state = new_state + end + return new_state + end) + + table.insert(instance.keys, key) + end + + return instance + end + + function KeyboardHandler:velocity(state) + + local function velocityWithin(pos, top, bottom) + local fraction = (bottom - pos) / (bottom - top) + local velocity = math.floor(1.5 + 126 * fraction) + velocity = clamp(velocity, 1, 127) + return velocity + end + + local key = state.key + local h = state.key.item.bounds.height + + local y = (state.y / h) * state.key.l + if state.key.black or y < strike_white_break then + return velocityWithin(y, strike_both_top, strike_both_bottom) + else + return velocityWithin(y, strike_white_low_top, strike_white_low_bottom) + end + end + + function KeyboardHandler:pressure(state) + local key = state.key + local h = state.key.item.bounds.height + + local y = (state.y / h) * key.l + local y_max = (state.max_y / h) * key.l + + local fraction = ((y_max - pressure_hysteresis) - y) / pressure_length + local pressure = math.floor(0.5 + 127 * fraction) + pressure = clamp(pressure, 0, 127) + return pressure + end + + function KeyboardHandler:keyDown(x, y) + local bounds = self.clickarea.bounds + local w = bounds.width + local h = bounds.height + local dx = x - bounds.x0 + local dy = y - bounds.y0 + + local state = { + handler = self, + } + local key_number = self.findKey(dx, dy, w, h) + + state.key_number = key_number + state.key = nil + if key_number ~= nil then + local key = self.keys[key_number + 1] + if key ~= nil then + state.key = key + -- coordinates within the key of the latest pointer position + state.x = x - key.item.bounds.x0 + state.y = y - key.item.bounds.y0 + state.max_y = state.y + state.key:setVelocity(self:velocity(state)) + end + end + + -- return the state to track this movement, claiming it as ours. + return state + end + + function KeyboardHandler:pointerUpdated(state, type, id, dev, x, y, btn, dn, up, cnt) + local bounds = self.clickarea.bounds + if bounds:includes(x, y) and (dn & 1 ~= 0) then + -- This is a key down event on the keyboard, claim the pointer. + return self:keyDown(x, y) + end + + if state ~= nil then + -- not a key down event, but a move after we claimed the pointer after a key down event + + local w = bounds.width + local h = bounds.height + local dx = x - bounds.x0 + local dy = y - bounds.y0 + + local new_key_number = self.findKey(dx, dy, w, h) + + if new_key_number ~= state.key_number then + -- this is a move across, or on, or off, keys + if state.key ~= nil then + state.key:setVelocity(0) + end + + -- store the new key number, which may be nil, in the state. + state.key_number = new_key_number + + if new_key_number ~= nil then + -- it's a move onto a new key, treat it as a key-down event + state.key = self.keys[new_key_number + 1] + + if state.key ~= nil then + state.x = x - state.key.item.bounds.x0 + state.y = y - state.key.item.bounds.y0 + state.max_y = state.y + state.key:setVelocity(self:velocity(state)) + end + else + -- set the key to nil as well. + state.key = nil + end + else + if state.key ~= nil then + -- we're remaining on the same key + state.x = x - state.key.item.bounds.x0 + state.y = y - state.key.item.bounds.y0 + if state.y > state.max_y then + state.max_y = state.y + end + + -- this is a potential change in key pressure. + state.key:setPressure(self:pressure(state)) + end + -- else we're remaining between keys and return the same state. + end + + -- return the same state, still claiming this pointer + return state + + end + + return nil + end + + function KeyboardHandler:releasePointer(id, state) + if state.key ~= nil then + state.key:setVelocity(0) + end + end + + ----------------------------------------------------------------------- + -- Slider library ends. + ----------------------------------------------------------------------- + + + + ]]> diff --git a/src/mame/layout/vfxsd.lay b/src/mame/layout/vfxsd.lay index 2df847f26a864..7878b7e75b792 100644 --- a/src/mame/layout/vfxsd.lay +++ b/src/mame/layout/vfxsd.lay @@ -1,1197 +1,3263 @@ - - - - - - - - - ]]> - - - - - - - - - - ]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ]]> - - - - - - ]]> - - - - - - - - ]]> - - - - - - ]]> - - - - - - - - ]]> - - - - - - ]]> - - - - - - - - ]]> - - - - - - ]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + ]]> + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + ]]> + + + + + + + + + + + ]]> + + + + + + + + + + + + + + ]]> + + + + + + + + + + + + + ]]> + + + + + + + + + + + + + + + + ]]> + + + + + + + + + + + + ]]> + + + + + + + + + + + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ]]> + + + + + + + ]]> + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + + + + + + + + + + + ]]> + + + + + + ]]> + + + + + + + + ]]> + + + + + + ]]> + + + + + + + + ]]> + + + + + + ]]> + + + + + + + + ]]> + + + + + + ]]> + + + + + + + + ]]> + + + + + + ]]> + + + + + + + + ]]> + + + + + + ]]> + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ]]> + + + + + + + ]]> + + + + + + + ]]> + + + + + + + ]]> + + + + + + + ]]> + + + + + + + ]]> + + + + + + + ]]> + + + + + + + ]]> + + + + + + + ]]> + + + + + + + ]]> + + + + + + + ]]> + + + + + + + ]]> + + + + + + + ]]> + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - + + function PointerManager:releasePointer(id) + local pointer = self.pointers[id] + + if pointer ~= nil then + if pointer.active ~= nil then + pointer.active.handler:releasePointer(id, pointer.active) + end + pointer.active = nil + end + + self.pointers[id] = nil + end + + function PointerManager:pointerLeft(type, id, dev, x, y, up, cnt) + self:releasePointer(id) + end + + function PointerManager:pointerAborted(type, id, dev, x, y, up, cnt) + self:releasePointer(id) + end + + function PointerManager:forgetPointers() + for id, pointer in pairs(self.pointers) do + self:releasePointer(id) + end + self.pointers = {} + end + + function PointerManager:registerCallbacks() + self.view:set_pointer_updated_callback(function(type, id, dev, x, y, btn, dn, up, cnt) + self:pointerUpdated(type, id, dev, x, y, btn, dn, up, cnt) + end) + + self.view:set_pointer_left_callback(function(type, id, dev, x, y, up, cnt) + self:pointerLeft(type, id, dev, x, y, up, cnt) + end) + + self.view:set_pointer_aborted_callback(function(type, id, dev, x, y, up, cnt) + self:pointerAborted(type, id, dev, x, y, up, cnt) + end) + + self.view:set_forget_pointers_callback(function() + self:forgetPointers() + end) + end + + function PointerManager:unregisterCallbacks() + self.view:set_pointer_updated_callback(nil) + self.view:set_pointer_left_callback(nil) + self.view:set_pointer_aborted_callback(nil) + self.view:set_forget_pointers_callback(nil) + end + + -- + -- The Slider Handler + -- + + SliderHandler = {} + + -- The knob's Y position must be animated using . + -- The click area's vertical size must exactly span the range of the + -- knob's movement. + + function SliderHandler:create(view, clickarea_id, knob_id, port_name, invert, autocenter) + local instance = { + view = view, + invert = invert, + autocenter = autocenter, + clickarea = view.items[clickarea_id], + knob = view.items[knob_id], + field = find_ioport_field(port_name), + + -- No setmetatable in the layout plugin. Manually copy methods across. + pointerUpdated = self.pointerUpdated, + releasePointer = self.releasePointer, + } + + if instance.clickarea == nil then + emu.print_error("Slider element: '" .. clickarea_id .. "' not found.") + return nil + end + + if instance.knob == nil then + emu.print_error("Slider knob element: '" .. knob_id .. "' not found.") + return nil + end + + if instance.field == nil then + return nil + end + + return instance + end + + function SliderHandler:pointerUpdated(state, type, id, dev, x, y, btn, dn, up, cnt) + if dn & 1 ~= 0 then + -- It's a button press + if self.knob.bounds:includes(x, y) then + return { + handler = self, + dy = y - self.knob.bounds.y0 + } + end + else + if state == nil then + -- not a down event, and not already claimed - not for us to handle. + return nil + end + + -- not a button down (up is also handled elsewhere), so it must be a move + local yy = y - state.dy + local fraction = (yy - self.clickarea.bounds.y0) / (self.clickarea.bounds.height - self.knob.bounds.height) + local new_value = 1023 * (1 - fraction) + if self.invert then + new_value = 1023 - new_value + end + new_value = math.floor(new_value + 0.5) + clamped_value = clamp(new_value, 0, 1023) + set_field_value(self.field, clamped_value) + return state + end + return nil + end + + function SliderHandler:releasePointer(id, state) + if self.autocenter then + set_field_value(self.field, 512) + end + return nil + end + + -- + -- The Keyboard Handler + -- + + local octave_shift = 164.5 + local w_white = 22.5 + local f_white = w_white / (w_white + 1) + local l_black = 88 + local l_white = 138 + local y_12 = (l_black + 2) / l_white + local y_12_0 = l_black / l_white + + local strike_both_bottom = l_black - 3 + local strike_both_top = l_black - 43 + + local strike_white_low_bottom = l_white - 3 + local strike_white_low_top = l_white - 43 + + local strike_white_break = (strike_both_bottom + strike_white_low_top) / 2 + + local pressure_length = 25 + local pressure_hysteresis = 3 + + -- The ranges where we can find the tops of the 12 keys within an octave + local k12 = { + { key=0, x0=0, x1=79027/1000000, black=false, l = l_white }, + { key=1, x0=1769/20000, x1=807/5000, black=true, l = l_black }, + { key=2, x0=8541/50000, x1=4997/20000, black=false, l = l_white }, + { key=3, x0=25927/100000, x1=16611/50000, black=true, l = l_black }, + { key=4, x0=8541/25000, x1=42067/100000, black=false, l = l_white }, + { key=5, x0=1707/4000, x1=4997/10000, black=false, l = l_white }, + { key=6, x0=1591/3125, x1=58207/100000, black=true, l = l_black }, + { key=7, x0=59149/100000, x1=16611/25000, black=false, l = l_white }, + { key=8, x0=33693/50000, x1=74681/100000, black=true, l = l_black }, + { key=9, x0=75623/100000, x1=41459/50000, black=false, l = l_white }, + { key=10, x0=4193/5000, x1=18231/20000, black=true, l = l_black }, + { key=11, x0=92097/100000, x1=3106/3125, black=false, l = l_white }, + } + + -- 85 equally sized ranges that each contain exactly one key, and the key + -- thaty they contain, so we can check for being outside the edge + local x_to_k12 = { + k12[1], k12[1], k12[1], k12[1], k12[1], k12[1], k12[1], + k12[2], k12[2], k12[2], k12[2], k12[2], k12[2], k12[2], + k12[3], k12[3], k12[3], k12[3], k12[3], k12[3], k12[3], k12[3], + k12[4], k12[4], k12[4], k12[4], k12[4], k12[4], k12[4], + k12[5], k12[5], k12[5], k12[5], k12[5], k12[5], k12[5], + k12[6], k12[6], k12[6], k12[6], k12[6], k12[6], k12[6], + k12[7], k12[7], k12[7], k12[7], k12[7], k12[7], k12[7], + k12[8], k12[8], k12[8], k12[8], k12[8], k12[8], k12[8], + k12[9], k12[9], k12[9], k12[9], k12[9], k12[9], k12[9], + k12[10], k12[10], k12[10], k12[10], k12[10], k12[10], k12[10], + k12[11], k12[11], k12[11], k12[11], k12[11], k12[11], k12[11], + k12[12], k12[12], k12[12], k12[12], k12[12], k12[12], k12[12], + } + + local makeFindKey = function(n_octaves) + local octaves_width = n_octaves * octave_shift + local full_width = octaves_width + w_white + + local function find_12_key(x, y, w, h) + if x > octaves_width then + return 12 * n_octaves + end + + local octave, kx = math.modf((x / w) * (full_width / octave_shift)) + if octave == n_octaves then + return 12 * octave + end + + local ki = math.floor(85 * kx) + local candidate = x_to_k12[ki + 1] + if candidate == nil then + return nil + end + local ci = 12 * octave + candidate.key + if candidate.x0 <= kx and kx <= candidate.x1 then + if candidate.black then + rel_y = y / h + if rel_y <= y_12_0 then + return ci + else + return nil + end + else + return ci + end + else + return nil + end + end + + local function find_7_key(x, w) + local octave, kx = math.modf((x / w) * (full_width / octave_shift)) + local ki, kkx = math.modf(7 * kx) + if kkx <= f_white then + if ki < 3 then + return 12 * octave + 2 * ki + else + return 12 * octave + 2 * ki - 1 + end + end + return nil + end + + return function (x, y, w, h) + rel_y = y / h + if rel_y < 0 or 1 < rel_y then + return nil + elseif rel_y < y_12 then + return find_12_key(x, y, w, h) + else + return find_7_key(x, w) + end + end + end + + KeyboardHandler = {} + + function KeyboardHandler:create(view, clickarea_id, key_id_prefix, port_prefix, from_octave, n_octaves) + local instance = { + view = view, + clickarea = view.items[clickarea_id], + from_octave = from_octave, + n_octaves = n_octaves, + from_key = from_key, + n_keys = n_keys, + keys = {}, + findKey = makeFindKey(n_octaves), -- plain function call to create the function! + + -- No setmetatable in the layout plugin. Manually copy methods across. + pointerUpdated = self.pointerUpdated, + releasePointer = self.releasePointer, + keyDown = self.keyDown, + velocity = self.velocity, + pressure = self.pressure, + } + + if instance.clickarea == nil then + emu.print_error("Keyboard click area '"..clickarea_id.."' not found.") + return nil + end + + local from_key = 12 * from_octave + local n_keys = 12 * n_octaves + 1 + + for i = 0, n_keys -1 do + local key_info = k12[(i % 12) + 1] + local key = { + id = key_id_prefix .. i, + velocity = 0, + pressure = 0, + info = key_info, + black = key_info.black, + l = key_info.l, + } + + key.item = view.items[key.id] + if key.item == nil then + emu.print_error("Key item '"..key.id.."' not found.") + return nil + end + + key.field = find_ioport_field(port_prefix .. (from_key + i - 36)) + if key.field == nil then + return nil + end + + function key:setVelocity(velocity) + if velocity ~= self.velocity or self.pressure ~= 0 then + self.velocity = velocity & 0x7f + self.pressure = 0 + set_field_value(self.field, self.velocity) + end + end + + function key:setPressure(pressure) + if pressure ~= self.pressure then + self.pressure = pressure & 0x7f + set_field_value(self.field, (self.pressure << 7) | self.velocity) + end + end + + key.item:set_animation_state_callback(function() + new_state = 0 + if key.pressure > 0 then + new_state = 128 + key.pressure + elseif key.velocity > 0 then + new_state = key.velocity + else + new_state = 0 + end + if new_state ~= key.animation_state then + key.animation_state = new_state + end + return new_state + end) + + table.insert(instance.keys, key) + end + + return instance + end + + function KeyboardHandler:velocity(state) + + local function velocityWithin(pos, top, bottom) + local fraction = (bottom - pos) / (bottom - top) + local velocity = math.floor(1.5 + 126 * fraction) + velocity = clamp(velocity, 1, 127) + return velocity + end + + local key = state.key + local h = state.key.item.bounds.height + + local y = (state.y / h) * state.key.l + if state.key.black or y < strike_white_break then + return velocityWithin(y, strike_both_top, strike_both_bottom) + else + return velocityWithin(y, strike_white_low_top, strike_white_low_bottom) + end + end + + function KeyboardHandler:pressure(state) + local key = state.key + local h = state.key.item.bounds.height + + local y = (state.y / h) * key.l + local y_max = (state.max_y / h) * key.l + + local fraction = ((y_max - pressure_hysteresis) - y) / pressure_length + local pressure = math.floor(0.5 + 127 * fraction) + pressure = clamp(pressure, 0, 127) + return pressure + end + + function KeyboardHandler:keyDown(x, y) + local bounds = self.clickarea.bounds + local w = bounds.width + local h = bounds.height + local dx = x - bounds.x0 + local dy = y - bounds.y0 + + local state = { + handler = self, + } + local key_number = self.findKey(dx, dy, w, h) + + state.key_number = key_number + state.key = nil + if key_number ~= nil then + local key = self.keys[key_number + 1] + if key ~= nil then + state.key = key + -- coordinates within the key of the latest pointer position + state.x = x - key.item.bounds.x0 + state.y = y - key.item.bounds.y0 + state.max_y = state.y + state.key:setVelocity(self:velocity(state)) + end + end + + -- return the state to track this movement, claiming it as ours. + return state + end + + function KeyboardHandler:pointerUpdated(state, type, id, dev, x, y, btn, dn, up, cnt) + local bounds = self.clickarea.bounds + if bounds:includes(x, y) and (dn & 1 ~= 0) then + -- This is a key down event on the keyboard, claim the pointer. + return self:keyDown(x, y) + end + + if state ~= nil then + -- not a key down event, but a move after we claimed the pointer after a key down event + + local w = bounds.width + local h = bounds.height + local dx = x - bounds.x0 + local dy = y - bounds.y0 + + local new_key_number = self.findKey(dx, dy, w, h) + + if new_key_number ~= state.key_number then + -- this is a move across, or on, or off, keys + if state.key ~= nil then + state.key:setVelocity(0) + end + + -- store the new key number, which may be nil, in the state. + state.key_number = new_key_number + + if new_key_number ~= nil then + -- it's a move onto a new key, treat it as a key-down event + state.key = self.keys[new_key_number + 1] + + if state.key ~= nil then + state.x = x - state.key.item.bounds.x0 + state.y = y - state.key.item.bounds.y0 + state.max_y = state.y + state.key:setVelocity(self:velocity(state)) + end + else + -- set the key to nil as well. + state.key = nil + end + else + if state.key ~= nil then + -- we're remaining on the same key + state.x = x - state.key.item.bounds.x0 + state.y = y - state.key.item.bounds.y0 + if state.y > state.max_y then + state.max_y = state.y + end + + -- this is a potential change in key pressure. + state.key:setPressure(self:pressure(state)) + end + -- else we're remaining between keys and return the same state. + end + + -- return the same state, still claiming this pointer + return state + + end + + return nil + end + + function KeyboardHandler:releasePointer(id, state) + if state.key ~= nil then + state.key:setVelocity(0) + end + end + + ----------------------------------------------------------------------- + -- Slider library ends. + ----------------------------------------------------------------------- + + + + ]]>