I decided to solve a long standing issue in my creative coding as a weekend coding sprint project. I’ve written a lot of unmaintainable code to make reaper session files in Python (mostly so that I can audition and observe the outputs of ml/analysis). @weefuzzy provided me with the tools to get started with this (his approach with jinja2 templates is genius) but as my needs grew I ended up having to make more complex one-shot snippets that would be rewritten over and over each time. As such, introduce reathon, a python module for constructing reaper sessions programatically using native python objects! I’ll copy some examples from the readme.
creating a session with 1 track
from reathon.nodes import * # import all of the reathon nodes
project = Project( # create an instance of a project
Track() # and pass a Track() object to the constructor
)
project.write("basic.rpp") # write the project out to the path
Using python constructs to automate manual work
from reathon.nodes import *
project = Project() # create an instance of a project
for x in range(1024):
project.add(Track()) # use the add method of the project to add a Track()
project.write("loops.rpp") # write the project out to the path
# Comprehensions
from reathon.nodes import *
tracks = [Track() for x in range(100)]
project = Project(*tracks)
project.write("comprehensions.rpp") # write the project out to the path
construct a granular style process
from reathon.nodes import Project, Track, Item, Source # note new nodes Item() and Source()
from pathlib import Path
import random
sources = []
# create a source object for each of the .wav files in a directory (can you tell I love comprehensions)
sources = [
Source(file=f'{str(x)}')
for x in Path('my-sounds').rglob("*.wav") # you would point it to an actual folder of sounds, not just 'my-sounds'
]
track = Track() # create a blank Track()
pos = 0.0 # set our initial position to 0
for x in range(1000): # 1000 grains
grain = random.choice(sources) # random file from our sources
length = random.uniform(0.1, 0.5) # random length of the item
track.add(
Item(
grain, # Item()'s have a child Source() node, which is randomly selected above
position = pos, # and we set the position
length = length # and we set the length
)
)
pos += length # increment the position by the length to create contiguous blocks
project = Project(track) # create the project with our composed track
project.write("granular.rpp") # write it out
For more info you can look at the GitHub which goes into detail on more elements of the module, as props, a generic way of setting the state of any reaper session ‘chunk’.
I’ve been thinking about how this workflow might map onto SuperCollider (which I am slowly but surely picking up).
What would the interface look like in SC ( lfo.connect(gran.loopStart)
) @tedmoore@francesco.cameli@tremblap@groma? Perhaps taking a small python snippet as a point of departure:
obviously this is very pseudocode, but I’d be interested to hear what you think a reasonable paradigm might be for instantiating these types of nested structures.
Is the end result here basically a text file that ends in “.RPP”?
It probably wouldn’t be too dissimilar from a Python implementation. Maybe we should have a chit chat and you can go over the architecture and I can suggest what would feel idiomatic?
Yep exactly. The object structure in Python is basically a link list, where each node knows what it has to write back to the ‘file’ which is stored at the first node of the chain (a Project() object). It also knows where it sits in the hierarchy by virtue of being a LL so you traverse from the top down till you hit the end and then write the result out to text with the correct extension.
Thanks to the wonderful and generous help of @tedmoore and @francesco.cameli I managed to bash my way through writing some supercollider code (which turned out to be more fun and intuitive than I first envisaged) and put together a relatively 1:1 implementation which in some cases is an improvement over the Python version.
The code is hosted on GitHub and be can be quarked or cloned into your extensions for easy install.
The interface revolves around constructing objects and adding them to the next hierarchical unit up.
Project
Track
Item
Each object accepts any number of ‘properties’ as a List/Array, and is unpacked with pairsDo internally. The example below gives the track a name this way, by passing ["name", "Glitchy Music"] to the ReaTrack constructor.
(
~project = ReaProj(); // create a ReaProj instance
~track = ReaTrack(["name", "Glitchy Music"]); // Create a ReaTrack
~item = ReaItem("~/glitchbox.wav", 0, 1); // file path, position (in timeline), length
~track.addItem(~item);
~project.addTrack(~track);
~project.write("~/path_to_project.rpp");
)
Man this is gold. I’m sure you should post that to the SC fora (there are 3 - mail, fb and discourse) - and did you post that to the reaper forum (there is one