You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

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 на старте — разделить позже, когда вырастет.

Порядок работ

  1. Поднять доступ к HID (udev), подтвердить видимость устройств.
  2. Минимальный скетч: открыть устройство, повесить один HIDdef на одну клавишу, печатать нажатия.
  3. Добавить карту привязок + действия toggle/inc/dec.
  4. Подключить Ndef (например, из NodeDictionary) как управляемую цель.
  5. Добавить KeyUp-семантику (gate / держать-пока-нажата).
  6. Добавить оси тачпада и кнопки мыши.
  7. Визуальный фидбек.
  8. Отполировать 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 параметра.