TuningFork.Composer (TuningForkComposer v0.1.3)

Copy Markdown View Source

A step sequencer as a struct and pure functions over it.

project =
  Composer.new(bpm: 100)
  |> Composer.add_track(kind: :drum, sound: "kick")
  |> Composer.toggle_step(0, 0)
  |> Composer.toggle_step(0, 4)

Composer.to_source(project)
Composer.to_score(project)

Summary

Functions

Add a track at the end.

The voice every instrument in the piece is derived from.

How long the whole composition runs, in beats.

Empty a track's steps, keeping the track.

Move a step one degree up (1) or down (-1), turning it off past either end.

A four-track demo composition: kick, snare, hat and a bass line.

The General MIDI note a named drum sounds on.

The drums a grid can reach, as {id, label}.

The General MIDI soundfont, by its gm_ name, a named instrument plays with.

Lengthen or shorten the note at step by one, within what room it has.

The instruments a grid can reach, as {id, label}.

The name TuningFork.Kit and the drum machine banks give a named drum.

Move a track up (-1) or down (1). A move past either end is a no-op.

A composition. Takes any field of the struct as an option.

Which repeats of the grid a track plays, one character per bar: x plays, anything else rests. A track's :plays is cycled to the piece's bars; nil or "" plays them all.

The General MIDI program a named instrument selects.

The notes a pitched track may reach, low to high.

The scales a grid can reach.

Change a setting. field must be one of settings/0; anything else is a no-op.

Hold a note over length steps.

Set a step to a scale degree, clamped to what the scale holds. 0 silences it.

The settings set/3 will accept.

How long one step lasts, in beats.

How many steps wide the grid is: beats to a bar times steps to a beat.

The composition as a TuningFork.Score, for playing without evaluating source.

The composition as TuningFork.Part source, or "" if nothing would sound.

Silence a track without losing it, or bring it back.

Turn a step on or off, keeping whatever length it had.

A track by position, or nil.

How many tracks there are.

Change a track's fields. changes takes any field TuningFork.Composer.Track.new/2 accepts.

The voice a track plays with: a TuningFork.Voice, or with a recorded :kit a TuningFork.Part instrument asked for one per note.

Types

t()

@type t() :: %TuningFork.Composer{
  bars: pos_integer(),
  bpm: pos_integer(),
  division: pos_integer(),
  gain: float(),
  kit: :synth | String.t(),
  meter: pos_integer(),
  name: String.t(),
  reverb: float(),
  root: atom(),
  scale: atom(),
  tracks: [TuningFork.Composer.Track.t()]
}

Functions

add_track(project, opts \\ [])

@spec add_track(t(), keyword()) :: t()

Add a track at the end.

base_voice(composer)

@spec base_voice(t()) :: TuningFork.Voice.t()

The voice every instrument in the piece is derived from.

beats(composer)

@spec beats(t()) :: pos_integer()

How long the whole composition runs, in beats.

clear_track(project, index)

@spec clear_track(t(), non_neg_integer()) :: t()

Empty a track's steps, keeping the track.

cycle_step(project, index, step, direction)

@spec cycle_step(t(), non_neg_integer(), non_neg_integer(), -1 | 1) :: t()

Move a step one degree up (1) or down (-1), turning it off past either end.

Composer.cycle_step(project, 0, 4, 1)

demo()

@spec demo() :: t()

A four-track demo composition: kick, snare, hat and a bass line.

drum_note(sound)

@spec drum_note(String.t() | nil) :: pos_integer()

The General MIDI note a named drum sounds on.

drums()

@spec drums() :: [{String.t(), String.t()}]

The drums a grid can reach, as {id, label}.

font_for(sound)

@spec font_for(String.t() | nil) :: String.t()

The General MIDI soundfont, by its gm_ name, a named instrument plays with.

grow(project, index, step, by)

@spec grow(t(), non_neg_integer(), non_neg_integer(), -1 | 1) :: t()

Lengthen or shorten the note at step by one, within what room it has.

instruments()

@spec instruments() :: [{String.t(), String.t()}]

The instruments a grid can reach, as {id, label}.

kit_sound(sound)

@spec kit_sound(String.t() | nil) :: String.t()

The name TuningFork.Kit and the drum machine banks give a named drum.

move_track(project, index, direction)

@spec move_track(t(), non_neg_integer(), -1 | 1) :: t()

Move a track up (-1) or down (1). A move past either end is a no-op.

new(opts \\ [])

@spec new(keyword()) :: t()

A composition. Takes any field of the struct as an option.

Fields and defaults: :bpm (96), :bars (2), :meter beats in a bar (4), :division steps in a beat (4), :root note name (:a2), :scale one of scales/0 (:minor_pentatonic), :gain 0.0–1.0 (0.5), :reverb room size 0.0–1.0 with 0.0 off (0.0), :kit :synth for the kit's own drums and General MIDI synth voices, or a drum machine name such as "RolandTR909" for its recordings and the General MIDI soundfonts (:synth), :name the variable to_source/1 binds the score to ("song"), :tracks a list of TuningFork.Composer.Track ([]). Every track given is resized to the grid width.

Composer.new()
Composer.new(bpm: 120, meter: 3, root: :c3)

passes(track, composer)

@spec passes(TuningFork.Composer.Track.t(), t()) :: String.t()

Which repeats of the grid a track plays, one character per bar: x plays, anything else rests. A track's :plays is cycled to the piece's bars; nil or "" plays them all.

iex> TuningFork.Composer.passes(%TuningFork.Composer.Track{plays: "x."}, TuningFork.Composer.new(bars: 5))
"x.x.x"

program_for(sound)

@spec program_for(String.t() | nil) :: 0..127

The General MIDI program a named instrument selects.

remove_track(project, index)

@spec remove_track(t(), non_neg_integer()) :: t()

Remove a track.

scale(composer)

@spec scale(t()) :: [atom()]

The notes a pitched track may reach, low to high.

scales()

@spec scales() :: [atom()]

The scales a grid can reach.

set(project, field, value)

@spec set(t(), atom(), term()) :: t()

Change a setting. field must be one of settings/0; anything else is a no-op.

value may be a number or a string. Nothing raises:

  • :bpm, :bars, :meter, :division are truncated to an integer and clamped to at least 1.
  • :gain and :reverb are clamped to 0.0–1.0.
  • A number that will not parse leaves the setting as it was.
  • :root and :scale take a string or an atom. A string becomes an atom unchecked.
  • :name takes anything to_string/1 accepts.

Setting :meter or :division resizes every track to the new grid width.

Composer.set(project, :bpm, 120)
Composer.set(project, :bpm, "120")
Composer.set(project, :meter, 3)

set_length(project, index, step, length)

@spec set_length(t(), non_neg_integer(), non_neg_integer(), pos_integer()) :: t()

Hold a note over length steps.

step may be anywhere inside the note, not only its first step. Clamped so the note stops where the next one starts. A step with no note is left alone.

set_step(project, index, step, degree)

@spec set_step(t(), non_neg_integer(), non_neg_integer(), non_neg_integer()) :: t()

Set a step to a scale degree, clamped to what the scale holds. 0 silences it.

settings()

@spec settings() :: [atom()]

The settings set/3 will accept.

step_beats(composer)

@spec step_beats(t()) :: float()

How long one step lasts, in beats.

steps_per_bar(composer)

@spec steps_per_bar(t()) :: pos_integer()

How many steps wide the grid is: beats to a bar times steps to a beat.

to_score(project)

@spec to_score(t()) :: TuningFork.Score.t()

The composition as a TuningFork.Score, for playing without evaluating source.

project |> Composer.to_score() |> TuningFork.Score.render(44_100)

to_source(project, opts \\ [])

@spec to_source(t(), keyword()) :: String.t()

The composition as TuningFork.Part source, or "" if nothing would sound.

Takes TuningFork.Composer.Source's options, notably :output.

toggle_mute(project, index)

@spec toggle_mute(t(), non_neg_integer()) :: t()

Silence a track without losing it, or bring it back.

toggle_step(project, index, step)

@spec toggle_step(t(), non_neg_integer(), non_neg_integer()) :: t()

Turn a step on or off, keeping whatever length it had.

track(composer, index)

@spec track(t(), non_neg_integer()) :: TuningFork.Composer.Track.t() | nil

A track by position, or nil.

track_count(composer)

@spec track_count(t()) :: non_neg_integer()

How many tracks there are.

update_track(project, index, changes)

@spec update_track(t(), non_neg_integer(), keyword() | map()) :: t()

Change a track's fields. changes takes any field TuningFork.Composer.Track.new/2 accepts.

Composer.update_track(project, 0, gain: 0.4, muted: true)

Switching :kind to :drum flattens every degree to a plain hit.

voice_for(track, project)

The voice a track plays with: a TuningFork.Voice, or with a recorded :kit a TuningFork.Part instrument asked for one per note.