|(require rs)||package: rs|
rs - the Racket Sequencer - is a live coding tool that lets you sequence MIDI using Racket. A sequence is a simple list of events, you can play multiple sequences simultaneously and sequences can have different lengths and subdivisions so it’s easy to do complex polyrhythms and Euclidean sequencing.
Here’s an example in which a boom tsss loop is assigned to a track:
( track1 (list boom '() tsss '()))
A '() is an empty list or null, so this sequence has four steps, on the first of which an event called "boom" plays and on the third of which an event called "tsss" is played.
Sequences can have arbitrary lengths and the number of items in a sequence is independent of the loop length of a track so it is very easy to play with polymeter or polyrhythm. Sequences can even be nested!
You can have as many tracks, each running their own sequences, each of which can be a different length (theoretically they could even run at different speeds) as your system can handle (if it’s too much timing will become sloppier).
Programming happens in Racket, so if you can build it in Racket, you can use it to sequence your MIDI (soft) synths.
If you want to see how it works, there’s a short video (with awful audio, sorry my computer can’t do screencasts of audio applications very well) here.
rs is a hobby project, built to scratch an itch for myself. It does work - on my system at least, which is a Macbook with developer tools installed. If you’re not afraid of compiling some code you may very well get it to work on your system too. I’ve been told it works without problems on (Arch) Linux as well.
Sending MIDI messages is done with a Racket wrapper around the RtMidi library. I did not write either the wrapper or the library but did manage to get them to work on my system. Getting the wrapper to work took some elbow grease, however. See the installation instructions.
Racket is not built for real time computing and I am not a systems programmer so the timing of rs is a bit ... wobbly. If you examine its output in a DAW you will find events are close to where they should be but not exactly on the grid.
You will probably get the best results from running rs in a REPL on the command line. On my own computer, a 2015 Macbook pro with 8GB RAM and a 2,9 GHz Intel Core i5, this yields quite acceptable timing.
When running the REPL inside Emacs performance is slightly worse but still acceptable. This is what I do. rs *can* also run from DrRacket but I’ve found this causes noticeably sloppier timing.
This program needs RtMidi, a Racket wrapper around the RtMidi library. Install it using the Racket package manager (raco pkg install rtmidi).
RtMidi is not a standalone installation. You need download the RtMidi library into the directory where the RtMidi Racket package lives *and compile it*. This works on Linux and on Mac OS X if you have the developer tools installed.
This package is not self-contained. It depends on the RtMidi package, and also requires you to compile a dynamic library used to connect to the RtMidi code.
At this point, this means that installing this package will require you to locate the package directory where this package is installed.
The best way to do this is probably to install the package using raco pkg install or the package manager, and then to evaluate (collection-path "rtmidi") to see where the directory lives.
Once you’ve located the collection directory, you’ll need to extract http://www.music.mcgill.ca/~gary/rtmidi/release/rtmidi-2.1.0.tar.gz to the collection directory. Then run make $PLATFORM, where PLATFORM is one of linux, macosx, or windows.
The wrapper is C++98 and should compile with any modern C++ compiler.
I haven’t tried the Windows build with this Makefile; you might need to make some adjustments.
Installing rs itself is a simple matter of installing it using the racket package manager (raco pkg install rs).
You will also want to install the demos: https://github.com/mcdejonge/rs-demos , if only because this repository contains a starting template.
1. First, make sure you have at least one available MIDI port on your system that is connected to something that makes a sound. A loopback device to a DAW would be a good choice but hardware should also work.
2. Next, open one of the demo files (available from https://github.com/mcdejonge/rs-demos in your Racket editing environment of choice and run it. If you’ve set up everything correctly you should be hearing a simple sequence.
rs has been tested with DrRacket and with Emacs running racket-mode. When using DrRacket, make sure to disable debugging (Language -> Choose Language -> The Racket Language -> No debugging or profiling). Even with debugging disabled timing in DrRacket is quite sloppy (see "Performance" above) so your best bet is probably to run rs in a REPL in a terminal and to copy and paste commands into it from your editor of choice.
3. Time to start exploring. There are a couple of demo files you can examine to see how rs works. There’s also a file called rs-live.rkt that you can load and modify to start doing live coding.
This section lists all the functions that are available to do your live coding with.
These functions and values deal with the global environment and the main loop.
The following values contain the current settings for the main loop:
rs-main-bpm : natural?
rs-main-steps : natural?
These functions can be used to alter the settings for the main loop:
bpm : natural?
Set the global BPM.
div-length : positive?
Set the global division length. Division length is a multiplier of the main beat length, so a division length of 1/4 means there will be one step every quarter beat.
steps : natural?
Set the global number of steps per iteration of the main / global loop.
Use these functions to start and stop the main loop:
Start the main loop.
Stop the main loop.
These functions deal with creating, queueing and stopping tracks.
Starting (queueing) and stopping tracks:
track : natural-or-track?
Stops the given track, which can by either an rs-t struct or an index (useful when you want to stop the last track you started or somesuch). Stopping happens at the start of the next iteration of the main loop.
sequence : list?
Creates a new track that uses the global settings for BPM, number of steps and division length. sequence should be a list where each element is either null, an event (rs-e?) or a valid sequence.
(rs-t-create #:bpm bpm [ #:steps steps #:div-length div-length #:seq seq]) → rs-t? bpm : positive? steps : positive? = 16 div-length : positive? = 1/4 seq : list? = '()
Creates a new track but allows you to set some (or all) of the track settings manually.
Plays a sequence during the given time in ms. seq should be a list of null, rs-e? or sequences. Allows you to create events that play sequences (of events that play sequences (of events that play sequences (etc))).
Sequences consist of lists of events (null is also an event, albeit on in which nothing happens). An event in which something does happen is an rs-e structure, which combines a function to execute and an offset (optional, defaults to 0 but can be any number between -1 and +1 which is then multiplied by the length of the step it applies to and added from the length of the step).
It’s advisable to avoid creating rs-e events directly and instead use:
If you want to fire off multiple events simultaneously, use:
(rs-e-multiple procedures) → procedure
procedures : list?
Creates a function that will fire off multiple event procedures at the same time (and at the same offset).
These functions can be used to work with MIDI: create MIDI events, define MIDI instruments and determine which ports are available.
To check which ports are available use:
Returns a list of available MIDI ports on your system. This list contains the names of the ports.
To define a MIDI instrument use:
(rs-m-instr port [channel]) → rs-m-instr-struct
port : valid-midi-port? channel : valid-midi-channel? = 1
Creates a new MIDI instrument on the supplied MIDI port index and on the supplied MIDI channel number. It returns a struct that can be used in the MIDI event functions listed below.
To create events that play MIDI notes, use these functions:
(rs-m-event-play instr note [ note-length-ms velocity] #:offset valid-offset?) → rs-e instr : rs-m-instr-struct? note : rs-m-valid-midi-value? note-length-ms : natural? = 0 velocity : rs-m-valid-midi-value? = 127 valid-offset? : 0
Create a MIDI event for use in a sequence that plays the given note of the given length and with the given velocity using the given instrument and offset. If note-length-ms is 0 (the default) the note will play for the length of the step.
Should you want to play your MIDI note directly instead of using it in a sequence use this function:
(rs-m-event-play-chord instr notes [ note-length-ms velocity] #:offset valid-offset?) → rs-e instr : rs-m-instr-struct? notes : rs-m-valid-notes? note-length-ms : natural? = 0 velocity : rs-m-valid-midi-value? = 127 valid-offset? : 0
Returns a MIDI event for use in a sequence that plays all notes in the given list of notes simultaneously for the given amount of time, at the given velocity and using the given instrument (and at the given offset). If note-length-ms is 0 (the default) the note will play for the length of the step.
Should you want to play your chord directly instead of using it in a sequence use this function:
(rs-m-play-chord instr notes note-length-ms [ velocity]) → void instr : rs-m-instr-struct? notes : rs-m-valid-notes? note-length-ms : natural? velocity : rs-m-valid-midi-value? = 127
Directly play the given notes simultaneously for the given amount of time, at the given velocity and using the given instrument.
It is also possible to set MIDI cc values. Use these functions:
The rs-demos package at https://github.com/mcdejonge/rs-demos contains a tool for setting up MIDI cc listeners in your DAW / on your instument.
(rs-m-event-cc instr cc-no cc-val #:offset valid-offset?) → rs-e instr : rs-m-instr-struct? cc-no : rs-m-valid-midi-value? cc-val : rs-m-valid-midi-value? valid-offset? : 0
instr : rs-m-instr-struct? cc-no : rs-m-valid-midi-value? cc-val : rs-m-valid-midi-value?
Set the given cc number to the given value for the given instrument. Supply an offset as needed.
num-loops : natural? num-steps : zero-or-positive?
Pause (ie sleep) for the given number of beats and steps. Loop length and step length are calculated based on the global settings. The number of steps can be a fraction, so (rs-pause 2 1/4) would pause for two full loops and a quarter of a step / division length (meaning if the division length is 1/4 this would be two full loops and 1/16 of a beat).
Print a diagnostic message, but only if rs-util-diag-mode (see below) is #t. args are inserted into the message. Printing is done using printf. Basically this is a printf that only runs when diagnostic mode is activated.
NOTE: if you need to perform a function call in one of your args, make sure it only happens when diag-mode is #t, in other words supply a procedure object rather than the result of a procedure call. If you do not do this, performance will suffer greatly as your procedure calls will also be executed if they don’t need to be (namely when diag-mode is #f).
diag-mode : true-or-false?
Activate (true-or-false is #t) or deactivate (true-or-false is #f) diagnostic mode. Diagnostic mode is #f by default. When #t it prints out a *lot* of messages.
Exposed rs-t-play-seq! so you can play sequences manually.
Cleaned up loop playing code.
When supplied a note length of 0 (the default) rs-m-event-play and rs-m-event-play-chord will play for the duration of the step.
Added rs-pause function.
Some code cleanup.
Cleaned up documentation.
2020-05-17 Implemented offsets.
2020-05-14 Created Scribble documentation (with help from Stephen De Gabrielle because I’m a Racket noob).
2020-05-13 Turned rs into a package.
New feature: chords
Added polyrhythm demo.
New feature: sequences within sequences. A new demo, rs-demo-drums.rkt, shows off this feature.
Timing code has been slimmed down, which hopefully increases performance.
rs-util.rkt now exposes (rs-util-set-diag-mode #t|#f) and (rs-util-diag message . args) so you can have an idea what rs is doing.
Demos have been put in a separate directory.
2020-05-03 Some cleanup of the demos.
2020-05-01 Initial release