sound - pannous/warp GitHub Wiki
play and beep work anywhere; the other sound words (melody, tone, pattern, live_loop, the MIDI words) come with
use sound. Without it, melody([…]) says "melody is in the standard module sound: write use sound".
use sound
beep
play(440Hz, 0.5s)
play 440Hz for: 0.5s // the same: the label `for:` names the duration
play(C4) // a note by name: C4, F#4, Bb3, C♯5
play([C4 E4 G4], 1s) // a chord: together
melody([C4 D4 E4], 200ms) // one after another
melody [C4 E4] each: 100ms
tone(220Hz, 300ms, "square") // sine, square, triangle, sawtooth
play "song.mp3" plays a music file, stop_sound stops everything, wait_sound() waits until all has played.
save sound as: "song.wav" (or save(sound, "song.wav")) writes what played since the last save into a file; save
writes any value by its type, save "notes" as: "notes.txt" a text with use file. samples/sound/music.warp.
kick = sample("kick.wav") reads an audio file as a sample (WAV; natively also mp3, ogg, flac, aiff, m4a): play(kick)
plays it as recorded, play_sample(kick, 2) twice as fast and an octave up, sample_slice(kick, 0.1s, 0.3s),
sample_slices(kick, 4) and sample_looped(kick, 4s, 0.5s, 1s) cut and loop it. samples/sound/sound_kick_samples.warp.
Notes are frequencies: A4 is 440, transpose(C4, 12) == C5, midi(A4) is 69, note_name(Eb4) is "D#4",
chord(C4, major) and scales by kind word. tempo = 90 sets the beat; durations take beat and bar
(each: 1/2 beat, for: 1 bar).
Settings are plain assignments; they hold for the sounds after them (samples/sound/synth.warp):
use sound
attack = 0.02
decay = 0.1
sustain = 0.4
release = 0.2
wave_shape = "sawtooth" // or noise, for drums
loudness = -9 dB
melody([A3 C4 E4 A4], 1/2 beat)
Filters (lowpass, highpass, bandpass with resonance), vibrato, tremolo and detuned unison voices are
settings too. Effects run between the voices and the speaker: distortion, delay_time / delay_feedback /
delay_mix, reverb_mix, all off by default (samples/sound/signal_graph.warp).
at 2 beats play C4 places a sound on the program's timeline. go { … } is a voice of its own that starts where
it stands and sounds along (samples/sound/voices.warp):
use sound
bass = go { melody([C3 G2 A2 F2], 1 beat) }
melody([E4 G4 C5 B4], 1/2 beat) // together with the bass
await bass
fade_sound(handle, gain, seconds) ramps a playing sound's volume, 0 as handle for all of them; the time is a number of
seconds or a duration: fade_sound(0, 0, 500ms) fades everything out in half a second.
A pattern is one bar of steps in mini-notation: ~ a rest, [ ] a group in one step, *2 twice in its step,
c4,e4,g4 a chord, c5(3,8) a Euclidean rhythm (3 of 8), <a b> one per cycle in turn, _ holds the step
before (@3 stretches a step to three), c5? drops a step about half the time (?0.3: 30 %; pattern_seed = 7
repeats the same choices). live_loop n { … } repeats a block as its own voice; a named one (live_loop bass 4 {…})
can be awaited (samples/sound/sound_patterns.warp):
use sound
tempo = 110
live_loop bass 4 { pattern "c3 ~ [c3 c3] <~ c3?>" }
live_loop 2 { pattern("c5(3,8) e5(2,8)", 2 bars) }
await bass
wait_sound()
swing = 0.6 delays every second step of a group.
write_midi(path, notes) and read_midi(path) write and read MIDI files of [start, note, length, velocity];
midi_on C4 / midi_off C4 play an instrument live (natively through sendmidi, in the browser through Web MIDI).
MIDI messages keep time like sounds: midi_note(C4, 1/2 beat) (pitch, duration, velocity, channel) sends its note
when the sounds before it have played, and at 3 beats midi_note(G4, 1 beat) places one in the bar. Firefox asks for
a site-permission add-on before Web MIDI; the playground explains a refusal and tries again once allowed.
samples/sound/midi.warp. MIDI input, midi_receive(), is planned (card midi-receive): today it is an error natively and in
the playground.
spectrum(samples, seconds, bands) gives how loud each frequency band sounds at a moment, 0 to 1, bands spaced
evenly in pitch from 60 Hz to 5 kHz. samples/sound/visualizer.warp paints them as Winamp-style bars with falling peaks;
samples/gpu/gpu_visualizer.warp hands them to a WGSL shader ($levels, $bass, $treble, $kick) that draws a
glowing tunnel on the GPU (graphics).
A function of the time is a sound: play calls it for every sample, t the sample's time in seconds, a moment ahead
of the speaker, so it plays in real time. Its values go from -1 to 1.
use sound
global phase = 0.0 as float
siren(t) := {
pitch = 440 + 110 * sin(pi * t) // glides between 330 and 550 Hz
phase = phase + 2 * pi * pitch / sample_rate
0.3 * sin(phase)
}
play(siren, 4s)
The phase adds up each sample's step, so the glide stays smooth; state between samples lives in globals.
A function that fills a whole block is faster: one call per 512 samples instead of one per sample, t the time of the
block's first sample. With two blocks it plays in stereo, left and right; chord(block, t) would be mono.
use sound
chord(left, right, t) := for i in 1 to #left {
time = t + (i - 1) / sample_rate
low = sin(2 * pi * 262 * time)
high = sin(2 * pi * 392 * time)
left#i = 0.4 * low + 0.1 * high
right#i = 0.1 * low + 0.4 * high
}
play(chord, 3s)
They run as samples: warp samples/sound/siren_sound.warp and warp samples/sound/stereo_sound.warp.
play([left, right], 2s) plays a pair of functions of the time in stereo. save sound as: "song.wav" writes what
played into a WAV file, signals included.