Source code for scamp_extensions.interaction.key_plane

"""
Module containing the :class:`KeyPlane` class for treating the keys of the computer as a discrete, 2-dimensional
input plane.
"""

#  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++  #
#  This file is part of SCAMP (Suite for Computer-Assisted Music in Python)                      #
#  Copyright © 2020 Marc Evanstein <marc@marcevanstein.com>.                                     #
#                                                                                                #
#  This program is free software: you can redistribute it and/or modify it under the terms of    #
#  the GNU General Public License as published by the Free Software Foundation, either version   #
#  3 of the License, or (at your option) any later version.                                      #
#                                                                                                #
#  This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;     #
#  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.     #
#  See the GNU General Public License for more details.                                          #
#                                                                                                #
#  You should have received a copy of the GNU General Public License along with this program.    #
#  If not, see <http://www.gnu.org/licenses/>.                                                   #
#  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++  #

import scamp
from inspect import signature
from functools import partial
from typing import Callable


[docs]class KeyPlane: """ Abstraction used to transform keyboard input into a two-dimensional control space. Each key pressed corresponds to a vertical and horizontal position, which is passed to the callback function, along with (optionally) whether it was an up or down keystroke and what modifiers were present. :param callback: a function of the form "callback(coordinates, up_or_down, modifiers)". Can also have the signature "callback(coordinates, up_or_down)" or "callback(coordinates)", in which case it is simply not passed that information. :param normalize_coordinates: if True, the keys coordinates go from 0-1 horizontally and vertically. if False, they go from 0-3 (inclusive) vertically and 0-9, 10 or 11 horizontally (depending on row) :ivar modifiers_down: a list of modifier keys currently pressed """ _key_codes_by_row_and_column = [ [90, 88, 67, 86, 66, 78, 77, 188, 190, 191], [65, 83, 68, 70, 71, 72, 74, 75, 76, 186, 222], [81, 87, 69, 82, 84, 89, 85, 73, 79, 80, 219, 221], [49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 189, 187] ] #: tuple of all names of modifier keys all_modifiers = ("ctrl", "alt", "shift", "cmd", "caps_lock", "tab", "enter", "backspace", "up", "left", "down", "up") def __init__(self, callback: Callable, normalize_coordinates: bool = False): self.callback = callback self.normalize_coordinates = normalize_coordinates self.modifiers_down = [] @property def callback(self) -> Callable: """ Callback function as described in the constructor, which responds to keyboard presses. """ return self._callback @callback.setter def callback(self, value): assert callable(value) self._num_callback_arguments = len(signature(value).parameters) assert self._num_callback_arguments > 0, "KeyPlane callback must take from one to three arguments." self._callback = value
[docs] def start(self, suppress: bool = False, blocking: bool = False, session: bool = None) -> None: """ Starts up the KeyPlane listening to keyboard input. :param suppress: if True, suppresses all other keyboard events so that nothing gets triggered unintentionally. (Make sure you have a way of stopping the script with the mouse!) :param blocking: if True causes this call to block (by calling :func:`wait_forever` on the underlying :class:`scamp.session.Session`). :param session: a :class:`scamp.session.Session` on which to run the keyboard listener. If None, looks to see if there's a session running on the current thread. """ def key_handler(name, number, press_or_release): if name is None: # catches something weird that happens with shift-alt and shit-tab return if name.replace("_r", "") in KeyPlane.all_modifiers: modifier = name.replace("_r", "") if press_or_release == "press": if modifier not in self.modifiers_down: self.modifiers_down.append(modifier) else: if modifier in self.modifiers_down: self.modifiers_down.remove(modifier) for y, codes_row in enumerate(KeyPlane._key_codes_by_row_and_column): if number in codes_row: x = codes_row.index(number) if self.normalize_coordinates: x /= len(codes_row) - 1 y /= 3 if self._num_callback_arguments > 2: self.callback((x, y), press_or_release, self.modifiers_down) elif self._num_callback_arguments > 1: self.callback((x, y), press_or_release) else: self.callback((x, y)) return if session is None: if scamp.current_clock() is not None and isinstance(scamp.current_clock().master, scamp.Session): session = scamp.current_clock().master else: session = scamp.Session() session.register_keyboard_listener( on_press=partial(key_handler, press_or_release="press"), on_release=partial(key_handler, press_or_release="release"), suppress=suppress ) if blocking: session.wait_forever()