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.

Music words

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).

Shaping the sound

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).

Timing and voices

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.

Patterns and live loops

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.

MIDI

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

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).

Signals

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.

⚠️ **GitHub.com Fallback** ⚠️