Make music
Play music in tracks and change it as it plays, keep sounds on the beat, and give them the voices of early sound chips.
Play music in tracks
audio.music plays its patterns in the order of its sequence. An
entry of the sequence is one pattern, or a list of patterns played
together as tracks on one clock; the first track sets how long the entry
lasts. The Music it returns edits the music while it plays:
Music:set_patternrewrites a pattern from the point the music has reached, without restarting it.Music:set_sequencesets a new order, which goes on after the entry playing now.Music:seekjumps to an entry and a step.Music.tempoplays the written notes faster or slower, at the same pitch.
Music.slot and Music.step say where the music is as you hear it.
Music:on_step calls you as each step is heard, so what you draw for a
beat lands with its sound. If your app falls behind, the calls it missed
fold into one, which carries the latest step:
local audio = datumhue.audio
local function lead(hz)
return { waveform = "square", frequency = hz, duration = 0.2 }
end
local function bass(hz)
return { waveform = "triangle", frequency = hz, duration = 0.2 }
end
-- A note with no volume is a rest.
local rest = { waveform = "sine", volume = 0, duration = 0.2 }
local song = audio.music({
patterns = {
{ lead(523), lead(659), rest, lead(784) },
{ bass(131), rest, bass(196), rest },
},
-- One entry: patterns 1 and 2 at once, as two tracks.
sequence = { { 1, 2 } },
looping = true,
volume = 0.4,
})
-- Called as each step is heard.
song:on_step(function(music, slot, step)
print(("slot %d, step %d"):format(slot, step))
end)
-- A second in, a new tune takes over, a little faster.
datumhue.time.after(1, function()
song:set_pattern(1, { lead(784), rest, lead(659), rest })
song.tempo = 1.25
end)
In this example: datumhue.audio datumhue.audio.music Music:on_step datumhue.time.after Music:set_pattern Music.tempo
Start sounds on time
Callbacks run once a frame, so sounds started from a timer can each
land up to a frame late. For a steady beat, give each sound a
start_time on the clock audio.now reads, a little ahead of now. The
sound starts at exactly that time, and sounds given the same time start
on the same sample. This metronome looks a quarter of a second ahead and
queues each tick due by then:
local audio = datumhue.audio
local tick = { waveform = "noise", frequency = 6000, duration = 0.02 }
local beat = 0.5
local next_tick = audio.now() + 0.1
-- Queue the ticks due in the next quarter of a second, each at its
-- exact time: now, and then every tenth of a second.
local function queue()
while next_tick < audio.now() + 0.25 do
audio.sfx({ tick }, { start_time = next_tick })
next_tick = next_tick + beat
end
end
queue()
datumhue.time.every(0.1, queue)
In this example: datumhue.audio datumhue.audio.now datumhue.audio.sfx datumhue.time.every
audio.now runs a little ahead of what you hear, by audio.latency
seconds. Music.step, Audio.position and a sound's end already report
what you hear; subtract audio.latency from audio.now to draw in time
with a sound you gave a start_time.
Make chip sounds
The waveforms can sound like the sound chips of early game consoles and home computers:
dutysets the width of asquarewave's pulse, from a thin, nasal 0.125 to a hollow 0.5.Audio:duty_toand a note'sduty_slidesweep it while it plays.noise_modepicks the noise:"long"hisses, and"periodic"buzzes at the note's frequency.- A
Wavetableplays one cycle you write:audio.wavetabletakes levels from -1 to 1, andBytes:wavetabletakes bytes from 0 to 255. syncruns the wave that many times faster and restarts it at each cycle of the note's frequency; async_slidesweep tears through the harmonics.ringmultiplies the wave by a square that many times the frequency, for bells and metallic tones.arpeggio_tickssets how many 1/60 s ticks each step of an arpeggio lasts, and a vibrato'sdelayholds it back at the start of a note.
local audio = datumhue.audio
-- A 16-step wave of 4-bit levels, 0 to 15, scaled to -1..1.
local steps = { 0, 3, 6, 9, 12, 14, 15, 15, 14, 12, 9, 6, 3, 0, 0, 0 }
local levels = {}
for i, level in ipairs(steps) do
levels[i] = level / 7.5 - 1
end
local hump = audio.wavetable(levels)
audio.sfx({
-- A thin pulse that widens as it falls.
{
waveform = "square",
frequency = 440,
slide = 330,
duty = 0.125,
duty_slide = 0.5,
duration = 0.3,
},
-- A buzz at the note's own pitch.
{
waveform = "noise",
noise_mode = "periodic",
frequency = 220,
duration = 0.2,
},
-- A tearing lead: sync swept from 1 to 4 times the frequency.
{
waveform = "sawtooth",
frequency = 165,
sync = 1,
sync_slide = 4,
duration = 0.4,
},
-- A bell: the written wave, ring-modulated.
{ waveform = hump, frequency = 330, ring = 2.4, duration = 0.4 },
-- A chord as a fast arpeggio, a step every two ticks.
{
waveform = "square",
frequency = 262,
arpeggio = { 0, 4, 7 },
arpeggio_ticks = 2,
duration = 0.5,
},
}, { volume = 0.4 })
In this example: datumhue.audio datumhue.audio.wavetable datumhue.audio.sfx
See also
- Play sound, for starting, shaping and mixing sounds
- How your app runs, for when callbacks and timers run
Reference used on this page: datumhue.audio datumhue.audio.music Music:on_step datumhue.time.after Music:set_pattern Music.tempo datumhue.audio.now datumhue.audio.sfx datumhue.time.every datumhue.audio.wavetable Music:set_sequence Music:seek Music.slot Music.step datumhue.audio.latency Audio.position Audio:duty_to Bytes:wavetable