Реализация HID контроллера

main
lambda 1 month ago
parent 2568249c9b
commit 72d1ab463a
No known key found for this signature in database
GPG Key ID: CF45A8EE158BBCB6

@ -0,0 +1,44 @@
~hidCtrl = (
defName: \hidCtrl,
// Array of HIDInfo objects
devices: [],
// List of HID objects. Maybe should be private later
connected: List.new(),
eventsMap: IdentityDictionary[
\X -> { | val | val.postln },
],
connect: {
|self|
self.devices.do {
|hidinfo|
var device = HID.open(
hidinfo.vendorID,
hidinfo.productID,
hidinfo.path
);
if(device.notNil) {
self.connected.add(device);
}
};
},
free: {
|self|
if(self.connected.size > 0) {
self.connected.do { |hid| hid.close };
self.connected.clear;
}
HIDdef(self.defName).free;
},
listen: {
|self|
HIDdef.usage(self.defName, {
|value, rawValue, usage, page, elid, ele, devid, device|
var name = HIDUsage.idsToName(page, usage);
var handler = self.eventsMap[name];
if(handler.notNil) { handler.(value) };
});
}
);

@ -1,141 +0,0 @@
#+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(<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/)
#+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 параметра.

@ -1,66 +0,0 @@
#+TITLE: Keyboard Controller — вопросы
Вопросы, на которые не ответить чтением репозитория. Ответы помогут
зафиксировать план точнее. Нумерую для удобства ответа.
* Устройство и события
1. Какое устройство — приоритет? Тачпад (HID), клавиатура (HID) или
TrackPoint? На ThinkPad TrackPoint обычно PS/2-мышь через evdev и
НЕ виден в HID-слое — если он важен, нужен отдельный обходной
путь (не HIDdef). Тачпад и клавиатура в HID есть.
В приоритете клавиатура + тачпад, так как это софт скорее для
живых выступлений.
2. Кнопки мыши/тачпада — нужны все (left/right/middle) или хватит
одной-двух под конкретные действия?
Хватит двух. Если возможно, конечно, стоит захватить максимум.
3. Оси тачпада: абсолютные координаты (как pad) или относительное
смещение (как трекпад-жесты)? От этого зависит маппинг на
параметры (pan по X, что по Y?).
Для начала абсолютные.
* Цели управления (Ndef/Tdef)
4. Какие конкретно Ndef/Tdef должны управляться? Это готовые
NodeDictionary-инструменты из bmSClib (какие .scd?), или скетч
должен быть самодостаточным с парой демо-Ndef внутри?
Скетч должен быть самодостаточным
5. Есть ли связь с 24_09_2026_gig (печатная машинка + live-electronics),
или это отдельный инструмент? Влияет на набор действий
(например, нужен ли freeze/hold, пресеты, «держать-пока-нажата»).
Это отдельный инмтрумент, но будет применён в этом перформансе
* Набор действий
6. Минимальный достаточный набор действий для первого работающего
скетча: toggle, inc/dec, play/stop, free — хватит? Или сразу
нужны пресеты/переключение сцен?
Хватит
7. «Держать-пока-нажата» (gate) — это требование (KeyUp важен для нот)
или KeyUp достаточно для анти-дребезга/фиксации состояния?
Требование
* Фидбек и форма
8. Фидбек: пост-окно (просто), текстовое окно со строками по
Ndef/Tdef, или GUI со слайдерами сразу? Readme говорит «минимальный
визуальный фидбек» — уточнить, где нижняя граница.
Это может быть gui окно с текстовым фидбеком
9. Форма результата: отдельный .scd-скелет (несколько файлов) или
один самодостаточный .scd? Или встроить как класс/расширение в
bmSClib (рядом с NodeDictionary)?
Несколько файлов с одной точкой входа

@ -21,3 +21,33 @@ HID устройств.
можно поддержать. можно поддержать.
Основные рабочие машины - ноутбуки ThinkPad. Основные рабочие машины - ноутбуки ThinkPad.
* Доступ к HID на Linux (udev)
sclang без привилегий видит HID-устройства только если может читать
/dev/hidraw*. По умолчанию они root:root 0600, поэтому нужно udev-правило.
Простое решение «без групп» (читать может любой пользователь):
#+begin_src sh
# /etc/udev/rules.d/99-hidraw-input.rules
KERNEL=="hidraw*", SUBSYSTEM=="hidraw", MODE="0666"
#+end_src
Затем применить:
#+begin_src sh
sudo udevadm control --reload-rules && sudo udevadm trigger
#+end_src
Проверить:
#+begin_src sh
ls -l /dev/hidraw* # должно стать crw-rw-rw-
head -c 0 /dev/hidraw0 # чтение от обычного пользователя должно пройти
#+end_src
Альтернатива «с группой» (безопаснее): MODE="0660", GROUP="input" +
пользователь в группе input (`sudo usermod -aG input $USER`, затем
перелогиниться). При MODE="0660" без членства в input доступа не будет.

Loading…
Cancel
Save