19 April

Waking up of the sleeping institution: how we taking off training-wheels from Python in REAPER

Open sourcePython
Original author: Levitanus

Image for taking attention


About a week I wonder around the idea of this article, mainly, because of missing the content here and tidiness of the coronavirus-themes. But, when robotic-harvester, hacking zip with bitcoins and other cool articles released — I decided not to publish crude material.


However, unexpectedly today the maintainer of this review hero showed up from the lockdown and, several hours ago reapy v0.6.0 has been published on PyPi. Under the cut — the last change log, which contains (to my pleasure) no line where I have not been involved one way, or another.


Finally: why reapy is needed and how Python works inside 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.

Exposition


REAPER


This is outstanding DAW, that brings unique user-experience. Many of my colleagues pathologically hate it, many — pathologically love. The matter is, as I think, interaction with REAPER looks like:


You don't like how thing works — tune it, thing does not work — code it.

Basically, major flexibility and wide API for the 4 PL allow growing of big open-source community around proprietary software.


Extensions can be written in C++, eel, lua and Python; moreover, every language integrated by its own way, so, depends on the situation is better to write with one or another.


For example: in the most cases C++ — overkill as for writing and maintaining, as well as for installing by the end-user. In fact the wide-user love touched only three: SWS, ReaPack (it is something like npm) and JS_ReaScript.


Eel in writing as expensive as C: almost manual control over the memory, almost no abstractions including the heigh-level functions and in addition — almost everything has to be written from scratch: little amount of «library» code present in the web. However, eel has three big advantages over the rest: efficient (unlike lua and Python), interpreted (unlike C++) and has better communication with Reaper through additional API functions and shared memory. Also, it allows writing of DSP and MIDI plugins (like VST, but JSFX) quite cheap.


Lua is de facto standard for writing common extension: it has a number of good libraries, its behavior is consistent on the all platforms, easy-installed from ReaPack without voodoo magic. However, considering me as not a big fan of the language, Reaper made it worse in several cases especially — in the area of dependency-management. Luarocks is not working, I still have not found a way to import something without *.lua extension, luasocket for example.


Python


Here should appear Python in shiny armor on the white horse as language with endless list of libraries and packages, yet without require for runaround of CMake. Pity, but inside Reaper the problem follows another one.


  • Python has to be installed separately, more than — dynamic library has to be found manually from the preferences, which is rising the entry threshold (we are, khm, musicians, aren't we?). In Linux python3.so has to be installed separately as dev-package which is not obvious.
  • script is imported also ass-backwards, leads to crash in case of importing for example numpy from the couple of scripts during the session.
  • missing bindings for GUI. Lua and eel have gfx* functions which are bindings to LICE(part of the Cokos WDL library), C++ can use WDL directly, but Python sucks. At the same time tkinter or pyqt usage is corrupted, through script has to be non-blocking and there is no such thing as main loop, just deferred execution of the function.
  • Honestly it is about every script-extension, but in Python it hurts me more: you have to write script, save the file, launch from Reaper, look at the traceback or catch a crash, rewrite in the editor or IDE and «once again». Also, no linting etc: in a couple of words — the nightmare of Z-generation :)

Time to time, coder to coder these problems attempted to be solved in different ways, which were close to the idea of separating the Reaper-instance of script and non-Reaper instance which communicates inside part through TCP. However, without a great success: maybe because of humans realizing that, maybe just the time has not come yet.


a history became a tale, tale — a myth
Ring of the power

reapy


If believe GitHub, in Febrary of 2019 (somewhere in that time I tried to make one more attempt to make TCP-helper) Romeo Despres from Paris published the first revision of reapy, which gently and with elegance wraps ReaScript API. More than, it runs the same as from inside as from outside of Reaper. The package attempts to wrap every call to API into the human-readable ORM (can I call it ORM?) but if the desired function hasn't been wrapped yet — it can be called «directly». It can be installed with pip and, at the moment needs to be initialized within a reascript which launches Reaper web-interface for handshake of outer and inner part of reapy.


When we launch «from the outside» the wrapper works this way: everything not attempted to run inside Reaper executes in normal way. reapy.core classes going to be serialized and remade from the inside, executes there and return the response. This solves not only the problem of uncomfortable script execution, but also prevents Reaper from crashes by the multiple imports. So I would recommend launching everything from the outside: this way the whole Python-code ought to run inside Reaper will have a single entry-point. BTW, in the last release we've added possibility of subclassing the reapy.core classes which led to the importing of the user modules from the inside.


Bug fixes and 3rd- party extensions


We don't really want to lean on the 3rd-party extensions. Not because of the trust, but because of user comfort: the most frequent questions are about scripts that are not working without extensions like SWS. The Linux-users are in worse condition, as many things have to be build from the source. Also, huge part of the SWS API is just another «more comfortable» wrapping of the original API, so we try to write everything we need from scratch. Independent of that — if SWS is installed it will be available from the reapy.reascript_api module.


We've discovered that vanilla C-bindings (reaper_python.py) also have bugs and it's open question will Justin fix 'em or not. So I've started to make our own bindings to replace the buggy one. Now I have a question about usage of JS_ReaScript, as it is not a wrap of vanilla API, but wrap of the WDL, which can be used for making native cross-platform GUI. Yep, we can try to deploy our own copy of WDL with our bindings, but this is the area with need of research.


When to use at the moment


Thanks to the resent additions, especially the connect(host) function, reapy become the first candidate for making micro-services. Sounds quite strange for the DAW, but, seriously, with the ability of connecting to any Reaper on the web the use-cases grow widely. The most obvious is the distant complex GUI for iOS or Android, why not? Reaper tries to make distant control not only with OSC (which, let's be honest means Liine Lemur for some bucks). But if be honest, web-interface is not as flexible as full-powered distant API without need for runover connection issues.


Also, interesting to consider cases with many of Reaper instances, which are doing «something». Maybe some audio-processing cloud service based on Reaper, I don't know. We also look at this side, but from another motive.


When you need tests. I can't imagine how to test extension on eel or lua. Yep, in Python, considering Reaper it is also not as simple as walk in the park, but not more complicated than test some REST API.


Roadmap


In the present time we are thinking of:


  • wrapping 100% of the API
  • cover project with tests. Yes, this is not a trivial task, especially no ops appeard on the project yet. On my side=project I run tests locally, but I dream of Reaper inside a cloud for usage in CI. Sounds as a good point to look at.
  • make a «one-click» installation. Actually, the steps are known, just need a time to implement. I hink that the reapy and other reapy-based extensions will not be installed from ReaPack, but from the PyPi and pip instead.
  • make native GUI

contributing


You can install the package, connect to the Reaper instance, start to code and find a place which is uncomfortable. Yes,the project is young and possibility of such case is heigh. Like so, in October I've realized that, despite human-readable wrap, every call to the reapy glows by all the colors of mypy. In sum: couple of evenings and I've had stubs, which went to the pull-request. Later I found the same glowing of «raw-API» calls on my side-project. So I've decided not to use «raw-API» in side project, but wrap it on the reapy side. So, in a process of making my side-project a half of code is written to the reapy which seemed to me as the idea of open-source: to use it, and push there fixes of everything was uncomfortable to You.

Tags:audioplugins
Hubs: Open source Python
0
313 0
Leave a comment
Top of the last 24 hours