rs - the Racket Sequencer
1 Overview
2 Status
3 Performance
4 Installation
4.1 Installing Rt  Midi
4.2 Installing rs
5 Getting started
6 Functions
6.1 Global /   Main
rs-main-bpm
rs-main-div-length
rs-main-steps
rs-set-global-bpm!
rs-set-global-div-length!
rs-set-global-steps!
rs-start-main-loop!
rs-stop-main-loop!
6.2 Tracks
rs-queue-track!
rs-stop-track!
rs-track
rs-t-create
rs-t-play-seq!
rs-t
6.3 Events
rs-e
rs-e-create
rs-e-multiple
6.4 MIDI functions
rs-m-list-ports
rs-m-instr
rs-m-event-play
rs-m-play
rs-m-event-play-chord
rs-m-play-chord
rs-m-event-cc
rs-m-cc
6.5 Utility functions
rs-pause
rs-util-diag
rs-util-set-diag-mode
7 Changelog
7.8

rs - the Racket Sequencer

mcdejonge

 (require rs) package: rs

1 Overview

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:

(set-rs-t-seq! 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.

You will probably also want to install the rs-l package. It contains utilities for sequencing, such as tools for conditional triggering of events and for rotating sequences. See the rs-l docs for more info.

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.

2 Status

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.

3 Performance

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.

4 Installation

4.1 Installing RtMidi

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.

Instructions copied and pasted from the RtMidi documentation:

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.

4.2 Installing rs

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.

5 Getting started

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.

Have fun!

6 Functions

This section lists all the functions that are available to do your live coding with.

6.1 Global / Main

These functions and values deal with the global environment and the main loop.

The following values contain the current settings for the main loop:

value

rs-main-bpm : natural?

The currently set main loop BPM.

The currently set main loop division length.

value

rs-main-steps : natural?

The currently set number of steps in the main loop.

These functions can be used to alter the settings for the main loop:

procedure

(rs-set-global-bpm! bpm)  void

  bpm : natural?

Set the global BPM.

procedure

(rs-set-global-div-length! div-length)  void

  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.

procedure

(rs-set-global-steps! steps)  void

  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:

procedure

(rs-start-main-loop!)  void

Start the main loop.

procedure

(rs-stop-main-loop!)  void

Stop the main loop.

6.2 Tracks

These functions deal with creating, queueing and stopping tracks.

Starting (queueing) and stopping tracks:

procedure

(rs-queue-track! track)  void

  track : rs-t?
Enqueues the given track. It will be started on the next iteration of the main loop.

procedure

(rs-stop-track! track)  void

  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.

Creating tracks:

procedure

(rs-track sequence)  rs-t

  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.

procedure

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

procedure

(rs-t-play-seq! seq loop-length)  void

  seq : list?
  loop-length : positive?

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

struct

(struct rs-t (bpm steps div-length seq)
    #:extra-constructor-name make-rs-t)
  bpm : positive?
  steps : positive?
  div-length : positive?
  seq : list?
Represents a track. Stores tempo, number of steps and division length as well as a sequence, which is a list of elements that can be either null, an event (rs-e) or another sequence.

6.3 Events

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

struct

(struct rs-e (fn offset)
    #:extra-constructor-name make-rs-e)
  fn : procedure-list-or-null
  offset : number?
The structure that contains an event. Offset should be a number between -1 and +1 and represents how far the event will take place from its regular position in the sequence. -1 is the start of the previous item in the sequence and +1 is the start of the next item in the sequence. fn can be either null, a sequence (that is a list of null, events or, gasp, sequences) or a function that should accept one argument. That one argument is the length of the step on which the event is executed, in milliseconds.

It’s advisable to avoid creating rs-e events directly and instead use:

procedure

(rs-e-create #:fn fn [#:offset offset])  rs-e

  fn : procedure?
  offset : number? = 0
Create a new event. Does sanity checking.

If you want to fire off multiple events simultaneously, use:

procedure

(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).

6.4 MIDI functions

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:

procedure

(rs-m-list-ports)  list

Returns a list of available MIDI ports on your system. This list contains the names of the ports.

To define a MIDI instrument use:

procedure

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

procedure

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

procedure

(rs-m-play instr    
  note    
  note-length-ms    
  [velocity])  void
  instr : rs-m-instr-struct?
  note : rs-m-valid-midi-value?
  note-length-ms : natural?
  velocity : rs-m-valid-midi-value? = 127
Directly play the given note for the given length using the given velocity on the given instrument.

procedure

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

procedure

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

procedure

(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
Returns a MIDI event for use in a sequence that sets the given cc number to the given value for the given instrument. Supply an offset as needed.

Should you want to set the cc value directly instead of doing so in a sequence

procedure

(rs-m-cc instr cc-no cc-val)  rs-e

  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.

6.5 Utility functions

procedure

(rs-pause num-loops num-steps)  void

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

procedure

(rs-util-diag message message-args)  void

  message : string?
  message-args : string?

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

procedure

(rs-util-set-diag-mode diag-mode)  void

  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.

7 Changelog