Skip to content

Commit 9d8d8dd

Browse files
authored
Merge pull request #4 from relic-se/keypad
Replace bespoke key input system with built-in `keypad`
2 parents 3e3607b + f7692f3 commit 9d8d8dd

File tree

2 files changed

+18
-89
lines changed

2 files changed

+18
-89
lines changed

examples/keymanager_keys.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,12 @@
66
import asyncio
77

88
import board
9-
from digitalio import DigitalInOut
9+
from keypad import Keys
1010

11-
from relic_keymanager import DebouncerKey, Keyboard
11+
from relic_keymanager import Keyboard
1212

1313
keyboard = Keyboard(
14-
keys=(
15-
DebouncerKey(
16-
DigitalInOut(board.USER_SW), inverted=True
17-
), # Pimoroni Pico Plus 2 BOOTSEL button
18-
)
14+
keys=Keys((board.USER_SW,), value_when_pressed=False, pull=False),
1915
)
2016

2117
keyboard.on_voice_press = lambda voice: print(f"Pressed: {voice.note.notenum:d}")

relic_keymanager.py

Lines changed: 15 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -31,81 +31,15 @@
3131
import random
3232
import time
3333

34+
import keypad
3435
from micropython import const
3536

3637
try:
3738
from typing import Callable
38-
39-
from circuitpython_typing.io import ROValueIO
4039
except ImportError:
4140
pass
4241

4342

44-
class KeyState:
45-
"""An enum-like class representing states used by :class:`Key` and :class:`Keyboard`."""
46-
47-
NONE: int = const(0)
48-
"""Indicates that the key hasn't been activated in any way"""
49-
50-
PRESS: int = const(1)
51-
"""Indicates that the key has been pressed"""
52-
53-
RELEASE: int = const(2)
54-
"""Indicates that the key has been released"""
55-
56-
57-
class Key:
58-
"""An abstract layer to interface with the :class:`Keyboard` class."""
59-
60-
def __init__(self):
61-
pass
62-
63-
@property
64-
def state(self) -> int:
65-
"""The current state as a constant value of :class:`KeyState`."""
66-
return KeyState.NONE
67-
68-
@property
69-
def velocity(self) -> float:
70-
"""Get the current velocity (0.0-1.0)."""
71-
return 1.0
72-
73-
74-
class DebouncerKey(Key):
75-
"""An abstract layer to debouncer sensor input to use physical key objects with the
76-
:class:`Keyboard` class. The Adafruit-CircuitPython-Debouncer module must be installed to use
77-
this class, else a ImportError will be thrown upon instantiation.
78-
79-
:param io_or_predicate: The input pin or arbitrary predicate to debounce
80-
:int inverted: Whether or not to invert the state of the input. When invert is `False`, the
81-
signal is active-high. When it is `True`, the signal is active-low.
82-
"""
83-
84-
def __init__(self, io_or_predicate: ROValueIO | Callable[[], bool], inverted: bool = False):
85-
from adafruit_debouncer import Debouncer
86-
87-
self._debouncer = Debouncer(io_or_predicate)
88-
self._inverted = inverted
89-
90-
inverted: bool = False
91-
"""Whether or not the state is inverted. When invert is `False`, the signal is active-high. When
92-
it is `True`, the signal is active-low.
93-
"""
94-
95-
@property
96-
def state(self) -> int:
97-
"""The current state as a constant value of :class:`KeyState`. When accessed, the input pin
98-
or arbitraary predicate will be updated with basic debouncing.
99-
"""
100-
self._debouncer.update()
101-
if self._debouncer.rose:
102-
return KeyState.PRESS if not self._inverted else KeyState.RELEASE
103-
elif self._debouncer.fell:
104-
return KeyState.RELEASE if not self._inverted else KeyState.PRESS
105-
else:
106-
return KeyState.NONE
107-
108-
10943
class Note:
11044
"""Object which represents the parameters of a note. Contains note number, velocity, key number
11145
(if evoked by a :class:`Key` object), and timestamp of when the note was created.
@@ -766,7 +700,7 @@ class Keyboard:
766700

767701
def __init__(
768702
self,
769-
keys: tuple[Key] = [],
703+
keys: keypad = None,
770704
max_voices: int = 1,
771705
root: int = 48,
772706
mode: int = KeyboardMode.HIGH,
@@ -799,11 +733,11 @@ def __init__(
799733
release(keynum, notenum):`.
800734
"""
801735

802-
_keys: tuple[Key] = None
736+
_keys: keypad = None
803737

804738
@property
805-
def keys(self) -> tuple[Key]:
806-
"""The :class:`Key` objects which will be used to update the keyboard state."""
739+
def keys(self) -> keypad:
740+
"""The :class:`keypad.Keys` object which will be used to update the keyboard state."""
807741
return self._keys
808742

809743
_arpeggiator: Arpeggiator = None
@@ -915,20 +849,19 @@ async def update(self, delay: float = 0.01) -> None:
915849
:param delay: The amount of time to sleep between polling in seconds.
916850
"""
917851
while self._keys:
918-
for i in range(len(self._keys)):
919-
state = self._keys[i].state
920-
if state == KeyState.NONE:
921-
continue
922-
notenum = self.root + i
923-
if state == KeyState.PRESS:
924-
velocity = self._keys[i].velocity
925-
self.append(notenum, velocity, i)
852+
while True:
853+
event = self._keys.events.get()
854+
if not event:
855+
break
856+
notenum = self.root + event.key_number
857+
if event.pressed:
858+
self.append(notenum, keynum=event.key_number)
926859
if callable(self.on_key_press):
927-
self.on_key_press(i, notenum, velocity)
928-
else: # KeyState.RELEASE
860+
self.on_key_press(event.key_number, notenum, 1.0)
861+
elif event.released:
929862
self.remove(notenum)
930863
if callable(self.on_key_release):
931-
self.on_key_release(i, notenum)
864+
self.on_key_release(event.key_number, notenum)
932865
await asyncio.sleep(delay)
933866

934867
def _update(self) -> None:

0 commit comments

Comments
 (0)