Skip to content

ninioArtillero/RTG

Repository files navigation

Rhythm, Time and Geometry

RTG is a music EDSL for the creation and manipulation of rhythmic patterns implementing a geometrically informed design to explore:

  1. The two way relation between musical rhythm and geometric structure.
  2. 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!

Related Papers

Installation

Haskell tool-chain

  1. Install GHCup —the recommended tool to administer the Haskell tool-chain— to get cabal and ghc. You may select all the default options of the installation script.
  2. Run ghcup tui and install (i) GHC 9.10.1 and set it (with s) afterwards.
  3. Clone this repository.
  4. Run cabal build from the root of the repository. In case of error, please open an issue with the output.
  5. Follow the usage instructions below to use RTG.

Audio dependencies

  1. Install SuperDirt (audio engine):
    1. Install SuperCollider.
    2. In the SuperCollider IDE (or in a terminal at the sclang shell prompt) run: Quarks.checkForUpdates({Quarks.install("SuperDirt", "v1.7.4"); thisProcess.recompile()}).

Usage

Setup

  • Start SuperDirt: Open SuperCollider (or run sclang from 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.
  • Sample names available for pattern functions match those of the DirtSamples quark installed by SuperDirt. A list of them can be printed by running ~dirt.postSampleInfo; in SuperCollider.

Mini tutorial

You can execute a pattern like so:

p :: Rhythmic a => PatternID -> [SampleName] -> a -> IO PatternBundle

p 1 ["cp", "bd"] $ clave

Warning

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 <> amiotScale

Patterns can be transformed applying functions from the Rhythmic interface.

p 1 ["cp", "bd"] $ reflex $ co clave

The 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 output
  • Solo: a pattern is selected as output
  • Transform: 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 $ fiveBalance

Entering into Transform mode, and go back to the Global pattern with.

resume

More experimental is the bundle transformation, which applies the given pattern to all elements of the bundle before merging them on the output.

actionB amitoScale

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

Development

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.

About the nix-shell environment

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

For comparison, a default shell-example.nix file can be created with

cabal2nix . --shell > nix/shell-example.nix

To build the project using nix:

nix-build --attr project nix/release.nix

The compiler version can be given as an argument for the build:

nix-build --argstr compiler ghc964 --attr project nix/release.nix

TODO

Base 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 Sequencer module. 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-extract for 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 ghci reloads.
  • 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.

Further Work

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

About

A library to generate rhythmic patterns using geometric techniques

Topics

Resources

License

Stars

Watchers

Forks

Contributors 2

  •  
  •