9.3 KiB
Keyboard Controller — план реализации
План для приложения из readme.org: HID-контроллер (клавиатура, мышь, тачпад) поверх SuperCollider, управление Ndef/Tdef, минимальный визуальный фидбек. Целевая платформа — Linux, ноутбуки ThinkPad.
Решения (зафиксированы из questions.org)
- Приоритет устройств: клавиатура + тачпад. TrackPoint не приоритет.
- Кнопки мыши/тачпада: минимум две, по возможности захватить максимум.
- Оси тачпада: для начала абсолютные.
- Скетч самодостаточный — демо-Ndef внутри, не тянет готовые NodeDictionary-инструменты.
- Отдельный инструмент, но будет применён в перформансе (24_09_2026_gig).
- Минимальный набор действий достаточен: toggle / inc / dec / play/stop / free.
- «Держать-пока-нажата» (gate) — требование, KeyUp обязателен.
- Фидбек: GUI-окно с текстовым содержимым (не пост-окно).
- Форма: несколько файлов с одной точкой входа.
Принцип
Не «демо», а рабочий шаблон-каркас: одна точка входа, декларативная привязка HID-событий к действиям, слои разделены. Основа — HIDdef (аналог MIDIdef) плюс словари привязок. Всё состояние — в глобалах ~ (прокси-пространстве), чтобы быстро править из REPL.
Этапы
1. Доступ к HID-устройствам из-под Linux
Основная и часто незаметная проблема: sclang без привилегий не видит /dev/hidraw*. Нужно решить ДО кода:
- проверить, что устройства видны:
HID.findAvailableиHID.postAvailableиз REPL; - если пусто — udev-правило, разрешающее чтение hidraw для нужной группы (или запуск от рута для прототипа);
- зафиксировать точные ID устройств (vendor/product) ThinkPad, чтобы не открывать «всё подряд».
2. Слой открытия устройств
- Сканирование
HID.findAvailable; - выбор целевого устройства по vendor/product, а не по индексу;
HID.open(id)→ держим объект устройства в~device;- graceful-fallback: если устройства нет, скетч не падает, а печатает предупреждение и ждёт.
3. Слой привязок (словарь обработчиков)
Декларативная таблица: ключ → действие. Значение — либо event-словарь (структурированное действие над Ndef/Tdef), либо напрямую функция (произвольный обработчик). Примерная форма:
~bindings = IdentityDictionary[
// клавиша -> Ndef-цель + параметр (event-словарь)
\q -> (type: \toggle, ndef: \d, param: \gate),
\w -> (type: \inc, ndef: \d, param: \freq, step: 10),
\e -> (type: \dec, ndef: \d, param: \freq, step: 10),
\r -> (type: \playTdef, tdef: \loop),
// клавиша -> произвольная функция (просто выполняется)
\space -> { "space hit".postln; ~something.value },
];
Разделяем сами обработчики (механику) и карту привязок (настройку), чтобы менять раскладку не трогая логику. Диспетчер действия различает значение по типу: `isFunction` → вызвать напрямую; иначе — разобрать по `type` (event-словарь). Это даёт «сырой» побег: можно вешать на клавишу любую функцию без добавления нового типа действия.
4. HIDdef: KeyDown / KeyUp
- клавиатура: value
= 1 (нажатие) / value =0 (отпускание); - поддержка обеих фаз — это требование из readme (KeyUp нужен, например, для gate=0 у нот / для «держать-пока-нажата»);
- HIDdef c фильтром по usagePage/usage клавиатуры.
5. Мышь / тачпад
- кнопки мыши/тачпада — как клавиши (нажатие/отпускание);
- движение тачпада: оси X/Y (Generic Desktop usage 0x30/0x31), маппинг на параметры (например, pan, freq, амплитуда);
- прочие события тачпада (wheel, multitouch) — опционально, по наличию в device.elements конкретного устройства.
6. Действия над Ndef/Tdef (плюс «сырые» функции)
Типы действий из карты привязок (event-словари):
- toggle параметра (gate, bypass);
- inc/dec значения параметра с шагом и clamp по ControlSpec;
- play/stop Tdef;
- free Ndef;
- переключение пресетов.
Используем Ndef(<name>).set(...) и Tdef(<name>).play/stop.
Помимо структурированных действий, значение привязки может быть произвольной Function — диспетчер её просто вызывает (`~action.value`). Это «побег» для любых обработчиков, не влезающих в типы выше: запуск отдельных функций, посыл OSC, манипуляции с ~env, что угодно. Разбор в диспетчере: `if (action.isFunction) { action.value } { ~dispatch[action.type].(action) }`.
7. Минимальный визуальный фидбек
GUI-окно с текстовым содержимым (Window + StaticText): одна строка = один активный Ndef/Tdef и его текущие параметры; подсветка изменённого параметра. Без полноценных слайдеров — только текстовый фидбек состояния.
8. Структура файлов (внутри keyboard-controller/)
readme.org — описание (уже есть)
plan.org — этот план
controller.scd — точка входа, скелет скетча
bindings.scd — карта привязок (словарь)
actions.scd — реализация действий (toggle/inc/dec/...)
Либо всё в одном .scd на старте — разделить позже, когда вырастет.
Порядок работ
- Поднять доступ к HID (udev), подтвердить видимость устройств.
- Минимальный скетч: открыть устройство, повесить один HIDdef на одну клавишу, печатать нажатия.
- Добавить карту привязок + действия toggle/inc/dec.
- Подключить Ndef (например, из NodeDictionary) как управляемую цель.
- Добавить KeyUp-семантику (gate / держать-пока-нажата).
- Добавить оси тачпада и кнопки мыши.
- Визуальный фидбек.
- Отполировать fallback-пути (устройство отвалилось, нет HID).
Риски / на что смотреть
- TrackPoint на ThinkPad часто НЕ HID-тачпад, а PS/2-мышь через evdev — его HID-слой может не видеть. По решениям — не приоритет, можно не покрывать.
- Имена классов: в SC это HIDdef / HIDFunc (строчная d), HIDDeviceService, GeneralHID. В readme написано «HIDDef» — сверять с документацией конкретной версии SC 3.14.
- usagePage/usage клавиш могут отличаться от ожидаемых — печатать сырые event-поля при отладке (page, usage, value) прежде чем зашивать их в привязки.
- Несколько одинаковых устройств (внешняя клавиатура + встроенная) — отличать по vendor/product/id, не по индексу списка.
- Абсолютные оси тачпада на разных устройствах дают разные диапазоны — нормировать (делить на логический максимум) прежде чем маппить на ControlSpec параметра.