mlua-pulse 0.1.0

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

local bpm = 104.0
local beat = 60.0 / bpm
local export_options = {
  sample_rate = 44100,
  normalize = "rms",
  target_db = -16.0,
  true_peak_db = -1.0,
  midi_velocity_gain = 1.8,
  midi_min_velocity = 0.58,
}

local function rep(value, count)
  local values = {}
  for index = 1, count do
    values[index] = value
  end
  return values
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 function sequence(notes, durations, instrument, volume, pan, velocity, at)
  local part = pulse.sequence()
    :notes(notes)
    :durations(durations)
    :instrument(instrument)
    :volume(volume)
    :pan(pan)
    :velocity(velocity)
  if at then
    part = part:at(at)
  end
  return part
end

local function chord_sequence(chords, duration, instrument, volume, pan, velocity, at)
  local part = pulse.sequence()
    :chords(chords)
    :durations(rep(duration, #chords))
    :instrument(instrument)
    :volume(volume)
    :pan(pan)
    :velocity(velocity)
  if at then
    part = part:at(at)
  end
  return part
end

local harmony = chord_sequence({
  voicing("C3", "maj7", {0, 12, 0, 12}),
  voicing("A2", "min7", {0, 12, 0, 12}),
  voicing("F2", "maj7", {0, 12, 0, 12}),
  voicing("G2", "dominant7", {0, 12, 0, 12}),
}, beat * 2.0, "electric_piano", 0.74, -0.18, 0.68)
  :effect("chorus", "subtle")
  :effect("reverb", "room")

local bass = sequence({
  voicing("C2", "major", {0})[1],
  voicing("C2", "major", {7})[1],
  voicing("A1", "minor", {0})[1],
  voicing("A1", "minor", {7})[1],
  voicing("F1", "major", {0})[1],
  voicing("F1", "major", {7})[1],
  voicing("G1", "major", {0})[1],
  voicing("G1", "major", {7})[1],
}, rep(beat, 8), "picked_bass", 0.86, -0.08, 0.78)

local motif = sequence(
  {pulse.scale("C4", "ionian")[3], pulse.scale("C4", "ionian")[5], pulse.scale("C4", "ionian")[6], pulse.scale("C4", "ionian")[8],
   pulse.scale("A3", "minor")[3], pulse.scale("A3", "minor")[5], pulse.scale("A3", "minor")[6], pulse.scale("A3", "minor")[8],
   pulse.scale("F4", "lydian")[2], pulse.scale("F4", "lydian")[4], pulse.scale("F4", "lydian")[6], pulse.scale("F4", "lydian")[8],
   pulse.scale("G3", "mixolydian")[5], pulse.scale("G3", "mixolydian")[7], pulse.scale("G3", "mixolydian")[8], pulse.scale("G3", "mixolydian")[7]},
  rep(beat / 2.0, 16),
  "synth_lead",
  0.42,
  0.22,
  0.56,
  beat / 2.0
):effect("delay", { time = beat * 0.75, feedback = 0.28, mix = 0.2 })

local beat_grid = pulse.drum_machine("808")
  :steps(16)
  :step_duration(beat / 4.0)
  :kick("x---x-----x-x---")
  :snare("----x-------x---")
  :hihat("x-x-x-xxx-x-x-xx")
  :clap("----x-------x---")
  :grid()
  :repeat_times(1)
  :volume(0.74)
  :pan(0.0)
  :effect("compressor", "drum_bus")

pulse.song()
  :tempo(bpm)
  :add(harmony)
  :add(bass)
  :add(motif)
  :add_drum_grid(beat_grid)
  :master_effect("eq", "warm")
  :master_effect("limiter", "transparent")
  :export_wav("output/foundation-loop.wav", export_options)