Reathon - create reaper sessions programatically in python

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’.

Source Code

4 Likes

Great stuff @jamesbradbury! I do hope you’re going to tout this loudly on the reaper forums as well, the world needs to know…

2 Likes

Whoa. This is heavy! Can’t wait to try it out.

Sam

Ooo, a bite! If you have any feedback, suggestions or what-not let me know. I made this quickly and in a vacuum so some perspective would be ace.

This looks great James. But why didn’t you do this 2 weeks ago when I needed it?!?!!11 :slight_smile:

2 Likes

It was actually your e-mail that prompted me :slight_smile:

It’s there now and ready to go!

2 Likes

I look forward to taking it for a spin :sunglasses:

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:

project = reathon.Project()
project.add(
     reathon.Track(
        reathon.Item()
    )
)
project.write()

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.

1 Like

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?

1 Like

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.

Sounds good! Let’s hook it up.

3 Likes

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
:arrow_down:
Track
:arrow_down:
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");
)

Once you write the file is made for you!

3 Likes

Where?

Are the available properties documented somewhere so we know what is valid (and useful) to specify in a ReaTrack?

one of us, one of us…

1 Like

Woops!

GitHub - jamesb93/ReaCollider (and ill edit the OP)

Not officially… but there is this pretty helpful document that covers 99% of use cases I think:

3 Likes

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 :slight_smile:

1 Like