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 = 112.0
local beat = 60.0 / bpm
local export_options = {
  sample_rate = 44100,
  normalize = "rms",
  target_db = -17.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 fm_lead = pulse.sequence()
  :notes({440.0, 550.0, 660.0, 880.0, 660.0, 550.0, 440.0, 330.0})
  :durations(rep(beat / 2.0, 8))
  :instrument("electric_piano")
  :synth("fm", "bell")
  :volume(0.42)
  :pan(-0.22)
  :velocity(0.56)
  :effect("delay", { time = beat * 0.75, feedback = 0.35, mix = 0.25 })

local additive_pad = pulse.sequence()
  :chords({
    pulse.chord("C3", "maj7"),
    pulse.chord("A2", "min7"),
    pulse.chord("F2", "maj7"),
    pulse.chord("G2", "dominant7"),
  })
  :durations(rep(beat * 2.0, 4))
  :instrument("electric_piano")
  :synth("additive", {1.0, 0.5, 0.25, 0.125})
  :volume(0.42)
  :pan(0.0)
  :velocity(0.48)
  :effect("chorus", "subtle")

local wavetable_bass = pulse.sequence()
  :notes({110.0, 110.0, 164.81, 146.83, 98.0, 98.0, 130.81, 146.83})
  :durations(rep(beat, 8))
  :instrument("bass_808")
  :synth("wavetable")
  :volume(0.78)
  :pan(0.0)
  :velocity(0.82)
  :effect("compressor", "bass")

local plucked_string = pulse.sequence()
  :notes({261.63, 329.63, 392.0, 523.25, 392.0, 329.63, 293.66, 261.63})
  :durations(rep(beat / 2.0, 8))
  :instrument("electric_piano")
  :synth("karplus_strong", { decay = 0.996, brightness = 0.7, seed = 42 })
  :volume(0.34)
  :pan(0.28)
  :at(beat)

local source_path = "output/granular-source.wav"

pulse.song()
  :tempo(112)
  :add(
    pulse.sequence()
      :chords({pulse.chord("C3", "maj7"), pulse.chord("F3", "maj7")})
      :durations({beat * 2.0, beat * 2.0})
      :instrument("warm_pad")
      :synth("additive", "pad")
      :volume(0.45)
  )
  :export_wav(source_path, { sample_rate = 44100, normalize = true, target_db = -6.0 })

local texture = pulse.sequence()
  :synth("granular", {
    source = source_path,
    duration = beat * 4.0,
    preset = "texture",
    grain_size_ms = 25.0,
    density = 0.75,
    position = 0.4,
    position_spread = 0.08,
    pitch_variation = 0.02,
  })
  :volume(0.38)
  :pan(0.0)

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-xxx-x-x-xxxx")
  :grid()
  :repeat_times(1)
  :volume(0.62)
  :effect("compressor", "drum_bus")

local song = pulse.song()
  :tempo(112)
  :add(additive_pad)
  :add(wavetable_bass)
  :add(fm_lead)
  :add(plucked_string)
  :add(texture)
  :add_drum_grid(beat_grid)
  :master_effect("eq", "warm")
  :master_effect("limiter", "standard")

song:export_wav("output/synthesis-algorithms.wav", export_options)
song:export_flac("output/synthesis-algorithms.flac", export_options)