#+TITLE: 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), либо напрямую функция (произвольный обработчик). Примерная форма: #+begin_src sclang ~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 }, ]; #+end_src Разделяем сами обработчики (механику) и карту привязок (настройку), чтобы менять раскладку не трогая логику. Диспетчер действия различает значение по типу: `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().set(...)~ и ~Tdef().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/) #+begin_src text readme.org — описание (уже есть) plan.org — этот план controller.scd — точка входа, скелет скетча bindings.scd — карта привязок (словарь) actions.scd — реализация действий (toggle/inc/dec/...) #+end_src Либо всё в одном .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 параметра.