Пробуждение спящего института: как мы убираем ходунки у Python в REAPER

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

КПДВ


Я уже больше недели брожу вокруг да около мыслей о написании этой статьи. Правда, основная мотивация сначала была в том, что мне последнее время не хватало контента и хотелось немного разбавить коронавирусную повестку. Однако, потом появились статьи про робо-комбайн, взлом архива с биткоинами и прочая годнота и я, было, решил, что не время еще пускать в ход недозрелый материал.


Однако, сегодня нежданно вырвался из локдауна ментейнер героя сегодняшнего обзора, и, буквально несколько часов назад в PyPi ушел reapy v0.6.0. Под катом — последний changelog, и мне особенно приятно, что в той или иной степени я поучаствовал в появлении каждой его строчки.


Итак: зачем нужен reapy, и что проиходит с Python в REAPER.


Changelog

0.6.0 — 2020-04-18


Added


  • New API inSend:
    • read-only property Send.dest_track -> Track
    • properties Send.midi_source, Send.midi_dest
  • Track.received read-only property
  • Support for Project external state:
    • Project.set_ext_state(section: str, key: str, value: Union[Any, str], pickled: bool) -> int size
    • Project.get_ext_state(section: str, key: str, pickled: bool) -> Union[Any, str
  • First argument id of Project can now be a project name (with or without extension) or an integer (the GUI index of the project).
  • Project extended with methods:
    • get_info_string(param_name: str) -> str
    • get_info_value(param_name: str) -> float
    • set_info_string(param_name: str, param_string: str)
    • set_info_value(param_name: str, param_value: float)
  • REAPER control over the network. reapy can be installed on a machine even if it does not have REAPER installed, and then control other instances by using reapy.connect.
  • Several ReaScript API bugs of reaper_python.py are fixed in reapy.reascript_api. Currently replaced:
    • MIDI_GetHash
    • MIDI_GetTrackHash
    • MIDI_InsertEvt
    • MIDI_InsertTextSysexEvt
    • MIDI_SetEvt
  • MIDIEvent and MIDIEventList classes now not only abstract, but can be used directly. MIDIEvent extended with:
    • delete() for deleting from the take. Works in every Event class.
    • set(self, message=None, position=None, selected=None, muted=None, unit="seconds", sort=True), currently works only in generic event.
  • Take extended with:
    • add_event(self, message: ty.Iterable[int], position, unit: str = "seconds") -> None.
    • add_sysex(self, message: ty.Iterable[int], position: float, unit: str = "seconds", evt_type: int = -1) -> None.
    • midi_events property (MIDIEventList).
    • midi_hash(self, notes_only: bool = False) -> str.
    • n_midi_events property.
  • Track extended with midi_hash(self, notes_only: bool = False) -> str:.

Deprecated


  • index argument in reapy.Project is deprecated in favor of id. reapy.Project(index=3) becomes reapy.Project(3).

Fixed


  • Typo in reapy.has_ext_state (issue #46).
  • Deferred script error in REAPER when subclassing reapy classes (issue #66).
  • Project.selected_items returning invalid items (issue #72).
  • sort boolean argument in Take.add_note behaving oppositely to expected.

Экспозиция


REAPER


Это DAW, который стоит несколько особняком от своих собратьев, доставляя достаточно уникальный пользовательский опыт. Многие мои коллеги испытывают к нему патологическое отвращение, многие — патологическую любовь. А дело все в том, что взаимодействие с REAPER выглядит примерно следующим образом:


Не нравится как работает — настрой. Вообще не работает — пиши код.

Собственно, огромная гибкость и широкий API для 4‑х ЯП позволили вырастить вокруг проприеритарного софта огромное open-source сообщество.


Писать можно на C++, eel, lua и Python; при этом, каждый язык интегрирован по-своему, и в зависимости от ситуации, предпочтительней писать то на одном, то на другом.


Допустим, в большинстве случаев плюсы — оверкилл, как в вопросах написания расширений, так и в вопросах их установки. По большому счету, в народе широко распространились только три расширения: SWS, ReaPack (это типа npm) и, с недавнего времени JS_ReaScript.


На eel писать почти также дорого, как и на C, т.к. там фактически ручное управление памятью, почти никаких абстракций, в т.ч. функций высшего порядка и вдобавок — почти все приходится писать самому: мало что можно использовать в качестве библиотек. Однако, eel обладает тремя шикарными свойствами: шустрый (в отличие от lua с Python), интерпретируемый (в отличие от C++), и общается с рипером лучше всех, за счет расширенного API и общего адресного пространства. Кроме того, на нем можно очень дешево писать DSP и MIDI плагины (типа VST, только JSFX).


Lua де-факто стандарт для написания расширения общего назначения: для него уже написан ряд хороших библиотек, он ведет себя одинаково на всех платформах, устанавливается через ReaPack без танцев с бубном. Однако, при том, что я небольшой фанат этого языка в целом, Reaper умудрился его еще и поломать в нескольких местах, особенно — в части управления зависимостями. То есть, luarocks не работает, и я так и не нашел способа установки чего-то без расширения *.lua, тех же сокетов, например.


Python


Вот тут-то по идее должен выйти на белом коне Python, как язык с бездонным количеством библиотек, при этом не требующий танцев с бубном вокруг CMake. Однако, с его использованием в рипере просто проблема на проблеме.


  • надо устанавливать отдельно, еще и искать динамическую библиотеку ручками в настройках, что сильно повышает порог входа для конечных пользователей (а мы, типа, музыканты). В Linux python3.so приходится устанавливать отдельно как dev-пакет, что вообще не очевидно.
  • импортируются расширения тоже немножко через жопу, вследствие чего два скрипта, использующие один и тот же, скажем, numpy нельзя запускать в одной сессии.
  • нет биндингов к GUI. Если в lua и eel есть дополнительные функции gfx*, которые оборачивают LICE (часть библиотеки Cokos WDL), C++ может использовать WDL напрямую; то Python sucks. В то же время, всякие tkinter, pyqt и иже с ними запустить достаточно сложно, т.к. нет такого понятия как main loop, а есть просто возможность повесить функцию на отложенное выполнение (defer).
  • Вообще это характерно для всех расширений, но при использовании Python, почему-то от этого больнее: надо написать скрипт, сохранить файл, запустить его через Reaper, посмотреть на всплывающее окно с ошибкой, а то и словить креш, опять переписать в редакторе, опять запустить. Естественно, линтинг API функций не работает и т.п. Короче, кошмар поколения Z :)

Собственно, в разное время эти проблемы пытались решить разными способами, которые так или иначе крутились вокруг идеи того, чтобы Python в рипере жил своей жизнью, общался по TCP с Python снаружи рипера и все были счастливы. Однако, как-то все не складывалось: возможно отчасти от человеческого фактора (в смысле неудачной реализации), возможно, просто время не пришло.


история стала легендой, легнда — мифом
Кольцо всевластья

reapy


Если верить GitHub, в феврале 2019 (как раз где-то в это время я очередной раз пытался на коленке сварганить костыль на сокетах) Romeo Desres из Парижа выкатил первую редакцию reapy, который надежно и элегантно оборачивает ReaScript API. Более того, запускается он как изнутри, так и снаружи рипера. Библиотека старается обернуть все вызовы к API в нормальную ORM (если это так можно назвать), но, если какие-то функции еще не обернуты — можно вызвать оригинальные функции «напрямую». Устанавливается он через pip, и, пока что, еще отдельно его надо проинициализировать из нужной копии Reaper скриптом, который добавит веб-интерфейс для первоначальных рукопожатий внешней и внутренней части пакета.


Когда мы запускаемся «снаружи» обертка работает так: все, что не должно исполняться внутри рипера исполняется в нормальном режиме. Классы же из reapy.core сериализуются и восстанавливаются на том конце, отрабатывают свой запрос и возвращают результат. Это не только решает проблему удобства запуска, но и нивелирует проблему падений от лишних импортов. По этому, за исключением ситуаций критичных к скорости реакции на события я бы рекомендовал все запускать снаружи: таким образом весь Python код, который исполняется внутри рипера, получает одну точку входа. Как раз в новом релизе мы добавили возможность наследования core классов, что попутно привело к импортированию пользовательских модулей изнутри.


багфиксы и сторонние расширения


Мы не очень хотим полагаться на сторонние расширения. Дело не в отсутствие доверия стороннему коду, а просто в удобстве конечного пользователя, которому придется устанавливать все отдельно: самые частые вопросы по скриптам сообщества относятся к части установки доп расширений, вроде тех же JS_ReaScript или SWS. Ситуация еще больше обостряется в Linux. В то же время, подавляющая часть SWS API — это точно такая же «более удобная» обертка ванильного реаскрипта. По этому мы стараемся писать все недостающее ручками. Тем не менее, при установленном SWS — он доступен точно также как и основное «raw» API как объекты модуля reapy.peascript_api.


Кроме того, оказалось, что сами C-биндинги к Python (peaper_python.py) тоже не без греха, и неизвестно, соберется ли Джастин это фиксить, или так и оставит висеть в баг-трекере. По этому я начал пилить свои C-биндинги в качестве надежного багфикса. Сейчас поставил вопрос о допустимости использования JS_ReaScript внутри reapy, т.к. там действительно оборачивается не сам реаскрипт, а Cokos WDL, которым можно рисовать корсс-платформенный нативный GUI. Конечно, можно попробовать доставлять свою копию WDL со своей оберткой, но это вопрос, с которым надо плотно разбираться.


где стоит использовать уже сейчас


Благодаря последним нововведениям, а именно функции connect(host), reapy стал первым кандидатом для реализации микросервисов. Это звучит немного странно в контексте Digital Audio Workstation, но, серьезно, с возможностью подключения к любой инстанции Reaper в сети варианты использования вырастают многократно. Самый очевидный — сложный GUI, который может работать, допустим, с iOS или Android, почему нет? Reaper и так старался дать какие-то опции по дистанционному управлению, кроме OSC (а де-факто, Liine Lemur за многие тыщщи), но, прямо скажем, веб-интерфейс не идет ни в какое сравнение с прямым доступом к API без необходимости мараковать с подключением.


Также заманчиво выглядят кейсы с большим количеством инстанций рипера, которые «что-то делают». Возможно, можно на базе рипера организовывать какой-нибудь суровый облачный аудио-процессинг, и мы, так получается, тоже сейчас смотрим куда-то в ту же сторону, хоть и немного из других соображений.


Там, где нужно писать тесты. Я понятия не имею, как можно протестировать расширение на eel иди lua. Да, с тестированием пакета, завязанного на рипер все еще есть трудности, но это в принципе реализуемо.


Роадмап


В настоящее время стоят следующие задачи:


  • обернуть весь оригинальный API
  • завести в проект тесты. Да, задача оказалась нетривиальная, особенно потому что пока что в проект не приходил ни один ops… На сайд-проекте у меня тесты крутятся локально. Запустить же рипер в облаке и научиться его использовать в цепочке CI — было бы здорово.
  • научиться устанавливаться «в один клик». Собственно, приблизительный план действий есть, но дело стоит за реализацией. Скорее, все-таки, установка reapy и расширений на нем основанных будет организована на уровне setuptools и pip, а не через ReaPack.
  • сделать нативный GUI

contributing


Вы можете установить пакет, привязать его к своей инстанции рипера, начать кодить и уткнуться в место, которое «неудобно». Да, проект молодой, и вероятность такого развития событий велика. Так вот я в октябре понял, что не смотря на довольно человеко-читаемую обертку, у меня все вызовы reapy светятся цветами ошибок mypy. В общем, два вечера, и у меня на руках были стабы, которые отправились в пул-реквест. Потом я заметил, что в моем сайд-проекте также противно (а главное — нефальсифицируемо) светятся вызовы к «raw-API», и, недолго думая, я решил, что не буду использовать «raw-API» в сайд-проекте, а буду все оборачивать на уровне reapy. Собственно, теперь, получается, что в процессе кодинга на пользу себе-любимому половину работы я выполняю на стороне библиотеки, которая так похорошела за последние полгода. Мне кажется, в этом и есть смысл open-source: пользуйтесь им, и пушьте туда все, что отозвалось вам неудобством.

Источник: https://habr.com/ru/post/497938/


Интересные статьи

Интересные статьи

Всем привет! Сегодня мы будем пытаться собрать Python 1.0.1 (1994 год) на современном железе при помощи современного компилятора. Даже если Вы, как Python разработчик, никогда не компилир...
Библиотеки и сервисы AutoML вошли в мир машинного обучения. Для дата-сайентиста это очень полезные инструменты, но иногда они должны быть адаптированы к потребностям бизнес-контекста, в к...
Python можно без сомнений называть языком, испытавшим в последнее десятилетие значительный рост, языком, который доказал свою мощь. Я создал множество Python-приложений — от интерактивных карт до...
Руководство для абсолютных чайновичков. (Прим. пер.: это советы от автора-индуса, но вроде дельные. Дополняйте в комментах.) Месяц — это много времени. Если тратить на обучение по 6-7 ча...
Сегодня мы публикуем историю перехода в IT Андрея Вуколова. Детское увлечение космосом когда-то привело его на ракетостроение в МГТУ. Суровая реальность заставила забыть о мечте, но все обернул...