Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Я уже больше недели брожу вокруг да около мыслей о написании этой статьи. Правда, основная мотивация сначала была в том, что мне последнее время не хватало контента и хотелось немного разбавить коронавирусную повестку. Однако, потом появились статьи про робо-комбайн, взлом архива с биткоинами и прочая годнота и я, было, решил, что не время еще пускать в ход недозрелый материал.
Однако, сегодня нежданно вырвался из локдауна ментейнер героя сегодняшнего обзора, и, буквально несколько часов назад в PyPi ушел reapy v0.6.0. Под катом — последний changelog, и мне особенно приятно, что в той или иной степени я поучаствовал в появлении каждой его строчки.
Итак: зачем нужен reapy, и что проиходит с Python в REAPER.
0.6.0 — 2020-04-18
Added
- New API in
Send
:
- read-only property
Send.dest_track -> Track
- properties
Send.midi_source
,Send.midi_dest
- read-only property
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
ofProject
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 usingreapy.connect
. - Several ReaScript API bugs of
reaper_python.py
are fixed inreapy.reascript_api
. Currently replaced:
MIDI_GetHash
MIDI_GetTrackHash
MIDI_InsertEvt
MIDI_InsertTextSysexEvt
MIDI_SetEvt
MIDIEvent
andMIDIEventList
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 withmidi_hash(self, notes_only: bool = False) -> str:
.
Deprecated
index
argument inreapy.Project
is deprecated in favor ofid
.reapy.Project(index=3)
becomesreapy.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 inTake.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: пользуйтесь им, и пушьте туда все, что отозвалось вам неудобством.