RTG is a music EDSL for the creation and manipulation of rhythmic patterns implementing a geometrically informed design to explore:
- The two way relation between musical rhythm and geometric structure.
- Computational abstractions and implementations of musical time.
It is part of a broad research project on the affordances of programming language abstractions in music language design and implementation, particularly in the context of live coding. As part of my PhD research, it addresses the following general question:
How to design a live coding language that leverages the geometric structure of rhythmic patterns via algebraic transformations/operations, ensures compositional coherence of these operations, and rigorously formalizes computational representation of musical time?
Important
This library is in a experimental state.
Watch some vide demos!
- Rhythm, Time and Geometry @ Algorithmic Pattern Salon (2023)
- Demo: A Geometric Approach to Generate Musical Rhythmic Patterns in Haskell @ FARM (2024)
- Install GHCup —the recommended tool
to administer the Haskell tool-chain— to get
cabalandghc. You may select all the default options of the installation script. - Run
ghcup tuiand install (i) GHC 9.10.1 and set it (withs) afterwards. - Clone this repository.
- Run
cabal buildfrom the root of the repository. In case of error, please open an issue with the output. - Follow the usage instructions below to use RTG.
- Install SuperDirt (audio engine):
- Install SuperCollider.
- In the SuperCollider IDE (or in a terminal at the
sclangshell prompt) run:Quarks.checkForUpdates({Quarks.install("SuperDirt", "v1.7.4"); thisProcess.recompile()}).
- Start SuperDirt: Open SuperCollider (or run
sclangfrom a terminal) and run (Ctrl+Enter)SuperDirt.start. This will load the audio engine and load its standard samples. - Open a terminal at the repository root (where this file is located)
- Run
cabal repl.
- Run
- Sample names available for pattern functions match those of the
DirtSamplesquark installed bySuperDirt. A list of them can be printed by running~dirt.postSampleInfo;in SuperCollider.
You can execute a pattern like so:
p :: Rhythmic a => PatternID -> [SampleName] -> a -> IO PatternBundle
p 1 ["cp", "bd"] $ claveWarning
To recover silence, use the command hush.
If it fails, quit the interpreter using :q.
Go with care, with current behavior long samples can easily blow things up.
The samples in the list are played simultaneously following the pattern of the given
rhythmic value. The library of patterns can be queried with the patternLibrary
command.
Alternatively, the operator a has the same structure, but assigns the samples in
a sequential fashion to the onset events of the pattern.
Both functions assign a pattern to an index (an integer value aliased as PatternID) in a mapping called a
PatternBundle, and upon first invocation they trigger the underlying sequencer.
New patterns are created by combination and transformation. Combination is
accomplished using the semigroup operator <>.
a 2 ["sn", "blip", "can"] $ bossa <> amiotScalePatterns can be transformed applying functions from the Rhythmic interface.
p 1 ["cp", "bd"] $ reflex $ co claveThe available transformations are: rotate n (rotation by n pulses) , reflex
(center reflection), rev (reverse) and co (complement). You can also use
euclidean rhythms
(another type of rhythmic value) and combine them with other patterns using the & operator.
p 3 ["rm"] $ shiko & (e(3,8) <> e(7,12))All timing is managed within the interpreter and there is no buffering (yet), so SuperDirt late messages are expected.
The sequencer gives continuous feedback on its current state, and its output pattern can grow quite large depending on pattern combinations. Also, there are three playing modes (triggered by some sequencer operations):
Global: all the patterns in the bundle are merged into the outputSolo: a pattern is selected as outputTransform: the sequencer stops reading the bundle to allow transformations on its current output.
A pattern can be soloed by index (solo 3), and unsoloed to
go back to the global pattern.
One of the design goals is leveraging combination and transformation at the bundle level.
You can apply a pattern to the global output pattern.
actionT $ fiveBalanceEntering into Transform mode, and go back to the Global pattern with.
resumeMore experimental is the bundle transformation, which applies the given pattern to all elements of the bundle before merging them on the output.
actionB amitoScaleAt the moment, this transformation modifies the state for good, so to recover you'll need to execute the patterns you had again.
You can clear all patterns from the bundle and make then inactive/active using
stop i/start i (where i is the pattern index).
Note
The project is being developed with GHC 9.10.1 using regular cabal commands.
The run.sh script and the nix-shell itself are intended for end users, but
is currently down.
The current default shell environment can be invoked directly by nix-shell nix/shell0.nix.
It depends on the files created by the following sequence of commands,
which should be called to update them any time the .cabal file is changed.
# Translate cabal file into a nix function
cabal2nix ./. > nix/project.nix
REV = nix-instantiate --eval --expr 'builtins.readFile <nixpkgs/.git-revision>'
# Version reference to pin packages
nix-prefetch-git https://github.com/NixOS/nixpkgs.git $REV > nix/nixpkgs.jsonFor comparison, a default shell-example.nix file can be created with
cabal2nix . --shell > nix/shell-example.nixTo build the project using nix:
nix-build --attr project nix/release.nixThe compiler version can be given as an argument for the build:
nix-build --argstr compiler ghc964 --attr project nix/release.nixBase implementation
- Fix names for clear spec and avoid redundancy
- Revisit euclidianZip in Rhythm Semigroup
- Revisit actionT and other global transformations. Implement ghost events?
- Make sequencer state updates atomic (STM)
- Add rhythmic pattern field to sequencer pattern: allow recovering the pattern
- Extend patterns beyond cycles (bundle sections might be here)
- Soloing many patterns together
- Fix MIDI functionality
- Decouple OSC messages from SuperDirt (refine interface for customization)
- Currently timing comes from the Haskell runtime (Timed IO Monad). Get detailed timing using timestamps on OSC messages
- Breakdown
Sequencermodule. Execution, PatterBundle, SequencerPattern and Output Values. Abstract over the Fiber Bundle structure - Fix Nix installation
- Have signals for parameter control (for morphing)
- Implement tests for bjorklund / LH specifications
- Add
doctest/doctest-extractfor automatic in-documentation property testing (QuickCheck)
Transformations and Patterns
- Implement pattern actions and transformations at the cycle level
- Global pattern operations and transformations
- Pattern action / product on fibers
- Take advantage of mnng
- Add hand-fan feature
- Report balance and evenness
- Implement pattern sync (projection) alternatives
- A syntax to make new scheduled patterns affect current playing patterns
- Add perfectly balance rhythm library (no efficient algorithm on-sight)
- Implement well-formed rhythms (¿3 parameters?)
- Continuous morphing of well-formed rhythms using ratio parameter
- Geometrically informed continuous morphing between two arbitrary rhythms
- Elaborate graph of execution model
- PatternBundle
Done
- Asynchronous evaluation of patterns
- Make patterns addressable so they can be stopped and updated(¿in global state?)
- Use HashMaps for sequences. (Now IntMap)
- Add cps to sequencer
- Make sequencer inform of current pattern length
- Preserve Sequencer State between
ghcireloads. - Fix haddocks (see Polygon module for example module header)
- Fix pattern update timing (per cycle to avoid jumps)
- Fix pattern resizing
- Homogenize rhythmic pattern types show function. Each type should be tagged appropriately.
- Fix implementation for simultaneous events.
- FRP implementations of patterns: make pattern composition scalable.
- Tidal Cycles pattern representation
- AFRP: Yampa, Euterpea MUI library
- Stand-alone interpreter (for
v.0.2.0.0?)- Parsing: parsec/megaparsec
- Embed MIDI functionality using
fluidsynth. - Create a minimal sampler for RTG in SuperCollider.