Viz
Mix.install([
{:vega_lite, "~> 0.1.6"},
{:kino_vega_lite, "~> 0.1.11"}
])
Section
File.cd!("/Users/jayanthbagare/Projects/audio/dsp/elixir")
File.cwd()
Code.require_file("lib/globalvars.ex",__DIR__)
Code.require_file("lib/audio.ex",__DIR__)
# --- 1. DEFINE INSTRUMENTS ---
# KICK: Pitch slide from 150Hz to 50Hz + fast decay envelope
kick_fn = fn _, duration ->
Audio.generate_pitch_slide(150.0, 50.0, duration)
|> Audio.adsr(44100, 0.005, 0.3, 0.0, 0.0) # Sharp attack, fast decay
end
# HI-HAT: White Noise + very short envelope
hat_fn = fn _, duration ->
Audio.generate_noise(duration)
|> Audio.adsr(44100, 0.005, 0.05, 0.0, 0.0) # "Tsst" sound
end
# LEAD: Sawtooth + Filter (Your existing patch)
lead_fn = fn freq, duration ->
Audio.square(freq, duration)
|> Audio.adsr(44100, 0.01, 0.1, 0.5, 0.1)
|> Audio.apply_low_pass(2000)
end
# --- 2. DEFINE SCORES ---
bpm = 136
beat = 60 / bpm # Quarter note (Kick)
half_beat = beat / 2 # Eighth note (Hi-Hat)
t = 0.11 # 16th note (Lead) - approx
# The Drums (4 beats loop)
kick_score = for _ <- 1..4, do: %{note: :C, octave: 1, duration: beat}
# The Hi-Hats (Off-beats: Rest, Hat, Rest, Hat...)
# We sequence "silence" by using a dummy note with 0 volume or just careful timing.
# Simpler approach: We sequence 8 notes, playing on the even ones.
hat_score =
Enum.flat_map(1..4, fn _ ->
[
%{note: :rest, octave: 0, duration: half_beat}, # Silence
%{note: :C, octave: 0, duration: half_beat} # Hat
]
end)
# NOTE: To make "rest" work, your sequencer needs to handle it.
# Hack for now: We will just generate silence in the instrument function if note is :rest
# The Lead (Same as before, simplified for space)
lead_score = [
%{note: :B, octave: 4, duration: t}, %{note: :B, octave: 4, duration: t},
%{note: :B, octave: 4, duration: t}, %{note: :B, octave: 4, duration: t},
%{note: :B, octave: 4, duration: t}, # Da-da-da-da-da
%{note: :B, octave: 4, duration: t}, %{note: :E, octave: 4, duration: t},
%{note: :E, octave: 4, duration: t}, %{note: :E, octave: 4, duration: t},
%{note: :E, octave: 4, duration: t}, %{note: :E, octave: 4, duration: t},
%{note: :E, octave: 4, duration: t}, %{note: :E, octave: 4, duration: t},
# (Feel free to extend this pattern)
]
# --- 3. GENERATE AUDIO ---
# Helper to handle "rests" (silence)
safe_hat_fn = fn freq, dur ->
if freq == 0, do: Audio.sine(0, dur) |> Enum.map(fn _ -> 0.0 end), else: hat_fn.(freq, dur)
end
# Update sequencer to handle :rest (returning 0 Hz)
# You might need to tweak your get_freq to return 0 for :rest
# Or just manually use a frequency of 0 in the score for silence.
# GENERATE TRACKS
# Note: For mixing, tracks must be roughly same length.
# The Lead is shorter in this example, so the drums will continue after it stops.
kick_audio = Audio.sequence(kick_score, kick_fn)
hat_audio = Audio.sequence(hat_score, fn f, d -> if f < 20, do: Audio.sine(0, d) |> Enum.map(fn _->0.0 end), else: hat_fn.(f,d) end)
lead_audio = Audio.sequence(lead_score, lead_fn)
# --- 4. MIX & SAVE ---
# We mix Kick and Lead (and Hats if you fixed the rest logic)
full_track = Audio.mix([
kick_audio,
lead_audio,
# hat_audio # Uncomment if you implemented the silence logic!
])
|> Enum.map(fn x -> x * 0.4 end) # Master Gain to prevent clipping
|> Audio.encode_binary()
|> Audio.save_wav("sandstorm_full.wav")