mlua-pulse 0.1.0

Lua-friendly music composition and audio export bindings built on tunes and mlua
Documentation
local pulse = require("pulse")

local title = "farewell-ensemble"
local bpm = 58.0
local beat = 60.0 / bpm
local export_options = {
  sample_rate = 44100,
  normalize = "rms",
  target_db = -20.0,
  true_peak_db = -1.5,
  midi_velocity_gain = 3.0,
  midi_min_velocity = 0.55,
}

local function rep(value, count)
  local values = {}
  for index = 1, count do
    values[index] = value
  end
  return values
end

local function degree(scale, value)
  local unique = #scale - 1
  local zero = value - 1
  local octave = math.floor(zero / unique)
  local position = (zero % unique) + 1
  return pulse.transpose(scale[position], octave * 12)
end

local function line(scale, degrees)
  local notes = {}
  for index, value in ipairs(degrees) do
    notes[index] = degree(scale, value)
  end
  return notes
end

local function voicing(root, kind, shifts)
  local source = pulse.chord(root, kind)
  local chord = {}
  for index, shift in ipairs(shifts) do
    chord[index] = pulse.transpose(source[((index - 1) % #source) + 1], shift)
  end
  return chord
end

local d4 = pulse.scale("D4", "minor")
local f4 = pulse.scale("F4", "ionian")
local a3 = pulse.scale("A3", "phrygian")
local d2 = pulse.scale("D2", "minor")

local mellow_filter = { type = "low_pass", cutoff = 3600.0, resonance = 0.08 }
local string_filter = { type = "low_pass", cutoff = 3200.0, resonance = 0.08 }

local intro = pulse.phrase()
  :add(
    pulse.sequence()
      :chords({
        voicing("D3", "minor", {0, 12, 0}),
        voicing("Bb2", "maj7", {0, 12, 0, 12}),
        voicing("G2", "min7", {0, 12, 0, 12}),
        voicing("A2", "dominant7", {0, 12, 0, 12}),
      })
      :durations(rep(beat * 2.0, 4))
      :instrument("acoustic_piano")
      :volume(0.32)
      :pan(-0.08)
      :velocity(0.32)
      :effect("reverb", "chamber")
  )
  :add(
    pulse.sequence()
      :notes(line(d2, {1, 1, 6, 6, 4, 4, 5, 5}))
      :durations(rep(beat, 8))
      :instrument("double_bass")
      :volume(0.3)
      :pan(-0.18)
      :velocity(0.34)
  )

local first_lament = pulse.phrase()
  :add(
    pulse.sequence()
      :chords({
        voicing("D3", "min7", {0, 12, 0, 12}),
        voicing("F3", "maj7", {0, 12, 0, 12}),
        voicing("Bb2", "maj7", {0, 12, 0, 12}),
        voicing("A2", "dominant7", {0, 12, 0, 12}),
      })
      :durations(rep(beat * 2.0, 4))
      :instrument("slow_strings")
      :volume(0.32)
      :velocity(0.34)
      :effect("filter", string_filter)
      :effect("reverb", "hall")
  )
  :add(
    pulse.sequence()
      :notes(line(d4, {5, 4, 3, 2, 1, 2, 3, 5}))
      :durations(rep(beat, 8))
      :instrument("oboe")
      :volume(0.2)
      :pan(0.18)
      :velocity(0.28)
      :at(beat)
      :effect("filter", mellow_filter)
      :effect("reverb", "chamber")
  )
  :add(
    pulse.sequence()
      :notes(line(d2, {1, 5, 6, 3, 4, 1, 5, 5}))
      :durations(rep(beat, 8))
      :instrument("cello")
      :volume(0.32)
      :pan(-0.14)
      :velocity(0.36)
      :effect("filter", string_filter)
  )
  :repeat_times(2)

local remembrance = pulse.phrase()
  :add(
    pulse.sequence()
      :chords({
        voicing("Bb2", "maj7", {0, 12, 0, 12}),
        voicing("C3", "dominant7", {0, 12, 0, 12}),
        voicing("F3", "maj7", {0, 12, 0, 12}),
        voicing("A2", "dominant7", {0, 12, 0, 12}),
      })
      :durations(rep(beat * 2.0, 4))
      :instrument("strings")
      :volume(0.34)
      :velocity(0.38)
      :effect("filter", string_filter)
      :effect("reverb", "hall")
  )
  :add(
    pulse.sequence()
      :notes(line(f4, {3, 5, 6, 8, 10, 8, 6, 5, 3, 5, 6, 8, 10, 8, 6, 5}))
      :durations(rep(beat / 2.0, 16))
      :instrument("violin")
      :volume(0.24)
      :pan(-0.16)
      :velocity(0.34)
      :effect("filter", string_filter)
      :effect("reverb", "chamber")
  )
  :add(
    pulse.sequence()
      :notes(line(f4, {8, 6, 5, 3, 5, 6, 8, 10}))
      :durations(rep(beat, 8))
      :instrument("flute")
      :volume(0.16)
      :pan(0.22)
      :velocity(0.26)
      :at(beat * 2.0)
      :effect("filter", mellow_filter)
      :effect("reverb", "chamber")
  )
  :add(
    pulse.sequence()
      :notes(line(d2, {6, 6, 7, 7, 3, 3, 5, 5}))
      :durations(rep(beat, 8))
      :instrument("double_bass")
      :volume(0.32)
      :velocity(0.36)
  )
  :repeat_times(2)

local quiet_climax = pulse.phrase()
  :add(
    pulse.sequence()
      :chords({
        voicing("G2", "min7", {0, 12, 0, 12}),
        voicing("A2", "dominant7", {0, 12, 0, 12}),
        voicing("D3", "min7", {0, 12, 0, 12}),
        voicing("D3", "minor", {0, 0, 12}),
      })
      :durations(rep(beat * 2.0, 4))
      :instrument("slow_strings")
      :volume(0.42)
      :velocity(0.46)
      :effect("filter", { type = "low_pass", cutoff = 3800.0, resonance = 0.08 })
      :effect("reverb", "hall")
  )
  :add(
    pulse.sequence()
      :notes(line(a3, {8, 7, 5, 4, 2, 1, 2, 4, 5, 4, 2, 1, 2, 1, 1, 1}))
      :durations(rep(beat / 2.0, 16))
      :instrument("oboe")
      :volume(0.24)
      :pan(0.16)
      :velocity(0.34)
      :effect("filter", mellow_filter)
      :effect("reverb", "chamber")
  )
  :add(
    pulse.sequence()
      :notes(line(d2, {4, 4, 5, 5, 1, 1, 1, 1}))
      :durations(rep(beat, 8))
      :instrument("cello")
      :volume(0.38)
      :pan(-0.14)
      :velocity(0.42)
      :effect("filter", string_filter)
  )
  :repeat_times(2)

local release = pulse.phrase()
  :add(
    pulse.sequence()
      :chords({
        voicing("Bb2", "maj7", {0, 12, 0, 12}),
        voicing("G2", "min7", {0, 12, 0, 12}),
        voicing("E2", "dim", {0, 12, 0}),
        voicing("A2", "dominant7", {0, 12, 0, 12}),
      })
      :durations(rep(beat * 2.0, 4))
      :instrument("acoustic_piano")
      :volume(0.26)
      :velocity(0.28)
      :effect("reverb", "chamber")
  )
  :add(
    pulse.sequence()
      :notes(line(d4, {8, 7, 5, 4, 3, 2, 1, 1}))
      :durations(rep(beat, 8))
      :instrument("flute")
      :volume(0.14)
      :pan(0.2)
      :velocity(0.22)
      :effect("filter", mellow_filter)
      :effect("reverb", "chamber")
  )

local epilogue = pulse.phrase()
  :add(
    pulse.sequence()
      :chords({
        voicing("D3", "minor", {0, 12, 0}),
        voicing("D3", "minor", {0, 12, 0}),
        voicing("D3", "minor", {0, 12, 0}),
        voicing("D3", "minor", {0, 0, 12}),
      })
      :durations(rep(beat * 2.0, 4))
      :instrument("acoustic_piano")
      :volume(0.2)
      :pan(-0.08)
      :velocity(0.22)
      :effect("reverb", "chamber")
  )
  :add(
    pulse.sequence()
      :notes(line(d2, {1, 1, 1, 1}))
      :durations(rep(beat * 2.0, 4))
      :instrument("double_bass")
      :volume(0.18)
      :velocity(0.22)
  )

local song = pulse.song()
  :tempo(bpm)
  :add_phrase(intro)
  :add_phrase(first_lament)
  :add_phrase(remembrance)
  :add_phrase(quiet_climax)
  :add_phrase(release)
  :add_phrase(epilogue)
  :master_effect("reverb", "hall")
  :master_effect("eq", "warm")
  :master_effect("limiter", "transparent")

assert(title == "farewell-ensemble")
song:export_wav("output/farewell-ensemble.wav", export_options)
song:export_flac("output/farewell-ensemble.flac", export_options)
song:export_midi("output/farewell-ensemble.mid", export_options)