Pi.js sound capabilities allow developers to create immersive audio experiences for their games and applications. Please note that as per browser restrictions sound functions will only work if they are in response to a user input. That is why all the following examples start with a onKey event.
One of the key functions in Pi.js is the play function, which allows developers to play music based on notes. This function takes a string of notes and commands and plays them back in sequence. Notes are written with their letters, A to G, and a number after a note sets its length (e.g., “C4” is a quarter-note C). Notes can also be given by number with N (e.g., “N49” for middle C). This makes it easy to create custom soundtracks for games and other interactive applications.
$.screen( "300x200" );
$.print( "Press any key to play sound." );
$.onKey( "any", "down", keyPress, true );
// Key press function
function keyPress() {
$.play( "T250 O3 C4 A4 D4 A4 E4 A4 F4 A4 G4 A4 A4 A4 B4 A4" );
}Another important function in Pi.js is loadAudio, which allows developers to load an audio file. The file is decoded into memory, so the same sound can be played many times at once. This is useful for creating sound effects that need to be played in quick succession, such as gunshots or explosions. For long files such as music, set the stream parameter to true and the file is streamed instead. Developers can then use the playAudio function to play the sound. It returns an instance ID that can be used with pauseAudio, resumeAudio, setAudio, and stopAudio to control that one sound while it plays.
let bomb = $.loadAudio( "media/bomb.wav" );
function keyPress() {
$.playAudio( bomb );
}
$.ready( function () {
$.screen( "300x200" );
$.print( "Press any key to play sound." );
$.onKey( "any", "down", keyPress );
} );The sound function in Pi.js allows developers to play a sound for a certain duration and frequency. This is useful for creating simple sound effects, such as beeps or clicks. The function takes a frequency value in hertz and a duration value in seconds, and generates a sound wave that is played back through the computer’s speakers. An attack, decay, sustain, and release envelope shapes the volume of the sound.
let data = [
392, 8, "My ", 659, 8, "Bon-", 587, 8, "nie ", 523, 8, "lies ", 587, 8,
"o-", 523, 8, "ver ", 440, 8, "the ", 392, 8, "o-", 330, 32, "ocean\n",
392, 8, "My ", 659, 8, "Bon-", 587, 8, "nie ", 523, 8, "lies ", 523, 8,
"o-", 494, 8, "ver ", 523, 8, "the ", 587, 40, "sea\n", 392, 8, "My ",
659, 8, "Bon-", 587, 8, "nie", 523, 8, " lies ", 587, 8, "o-", 523, 8,
"ver ", 440, 8, "the ", 392, 8, "o-", 330, 32, "ocean\n", 392, 8, "Oh ",
440, 8, "bring ", 587, 8, "back ", 523, 8, "my ", 494, 8, "Bon-", 440,
8, "nie ", 494, 8, "to ", 523, 32, "me"
];
let totalDuration = 0;
let volume = 0.15;
let attackRate = 0.01;
let releaseRate = 0.1;
let sustainRate = 0.89;
let printTimeouts = [];
$.screen( "300x200" );
$.print( "Press any key to play sound." );
$.onKey( "any", "down", keyPress, true );
function keyPress() {
for( let i = 0; i < data.length; i += 3 ) {
let freq = data[ i ];
let duration = data[ i + 1 ] / 18.2;
let sustain = duration * sustainRate;
let attack = duration * attackRate;
let release = duration * releaseRate;
let word = data[ i + 2 ];
$.sound( {
"frequency": freq, "duration": sustain, "volume": volume,
"oType": "sawtooth", "delay": totalDuration,
"attackTime": attack, "releaseTime": release
} );
printWord( word, totalDuration );
totalDuration += duration;
}
}
function printWord( word, delay ) {
printTimeouts.push( setTimeout( function () {
$.print( word, word.indexOf( "\n" ) === -1 );
}, delay * 1000 ) );
}Sound Advanced Plugin
The sound-advanced plugin builds on the sound functions above. It adds a synthesizer, ready-made retro sound effects, effects such as reverb and delay, instruments for the play function, and recording. Its sounds mix with the sounds of play, sound, and playAudio, and they share the same volume controls.
The plugin is not part of the full or lite version of Pi.js, so it has to be loaded separately. Load it after the full version of Pi.js, or after the lite version and the sound plugin. You can download it from the Plugins page.
<script src="https://cdn.jsdelivr.net/npm/pijs-web@2/dist/pi.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/pijs-web@2/dist/plugins/sound-advanced/sound-advanced.min.js"></script>Sound Effects
The sfx function plays a sound effect by name. The built-in sound effects are coin, laser, jump, hit, explosion, powerup, blip, and select. An optional second parameter, from 0 to 1, adds a random change in pitch and length so that a repeated sound effect does not sound the same every time.
let names = [
"coin", "laser", "jump", "hit", "explosion", "powerup", "blip", "select"
];
let index = 0;
$.screen( "300x200" );
$.print( "Press any key to play a sound effect." );
$.onKey( "any", "down", keyPress );
function keyPress() {
$.print( names[ index ] );
$.sfx( names[ index ], 0.3 );
index = ( index + 1 ) % names.length;
}You can add your own sound effects with the definePreset function, which stores a set of synth options under a name. The generateSfx function creates those options for you from a category and a seed number. The same category and seed always create the same sound, so you can try different seeds until you find a sound you like.
$.definePreset( "zap", {
"frequency": 1800, "frequencyEnd": 300, "duration": 0.12,
"oType": "pulse", "duty": 0.125
} );
$.definePreset( "enemyLaser", $.generateSfx( "laser", 42 ) );
$.screen( "300x200" );
$.print( "Press Z or L to play a sound effect." );
$.onKey( "z", "down", function () {
$.sfx( "zap" );
} );
$.onKey( "l", "down", function () {
$.sfx( "enemyLaser" );
} );Synthesizer
The synth function takes every parameter of the sound function and adds a filter with its own envelope, vibrato, tremolo, pulse waves, and arpeggios. Like the sound function, it returns a sound ID that can be used with stopSound.
$.screen( "300x200" );
$.print( "Press any key to play sound." );
$.onKey( "any", "down", keyPress );
function keyPress() {
// A bass note with a filter that closes as the note plays
$.synth( {
"frequency": 110, "duration": 0.5, "oType": "sawtooth",
"filterType": "lowpass", "filterCutoff": 200, "filterAmount": 4,
"filterDecayTime": 0.3, "filterSustainLevel": 0
} );
// A chord played one note at a time
$.synth( {
"frequency": 440, "duration": 0.5, "volume": 0.4, "oType": "pulse",
"duty": 0.25, "arpeggio": [ 0, 4, 7, 12 ], "arpeggioRate": 16,
"delay": 0.6
} );
}A sound played with the hold option set to true keeps playing until it is released with the releaseSound function. This is useful for sounds whose length is not known when they start, such as an engine or a laser beam. The setSynth function changes the volume, pitch, or filter of a held sound while it plays.
let engine = null;
$.screen( "300x200" );
$.print( "Hold the space bar to run the engine." );
$.print( "Press the up arrow for more power." );
$.onKey( "Space", "down", function () {
engine = $.synth( {
"oType": "pink", "volume": 0.6, "attackTime": 0.8,
"releaseTime": 1.2, "filterType": "lowpass", "filterCutoff": 300,
"hold": true
} );
} );
$.onKey( "Space", "up", function () {
$.releaseSound( engine );
} );
$.onKey( "ArrowUp", "down", function () {
$.setSynth( { "soundId": engine, "volume": 0.9, "filterCutoff": 900 } );
} );Effects
Pi.js mixes sound on three buses. The sound, synth, and sfx functions play on the sfx bus, the play function plays on the music bus, and the playAudio function plays on the audio bus. The master bus is the mix of all three. The setBusVolume function, which is part of the sound plugin, sets the volume of a bus.
The setBusEffect function places an effect on a bus. The effects are reverb, delay, filter, distortion, bitcrush, and chorus. Pass an array of effects to run up to four of them one after another, and pass null to remove the effect from a bus.
$.screen( "300x200" );
$.print( "Press any key to play sound." );
$.onKey( "any", "down", keyPress, true );
function keyPress() {
// Reverb on the music
$.setBusEffect( "music", "reverb", { "time": 2.5, "mix": 0.4 } );
// A muffled echo on the sound effects
$.setBusEffect( "sfx", [
{ "effect": "filter", "cutoff": 600 },
{ "effect": "delay", "time": 0.3, "mix": 0.4 }
] );
$.setBusVolume( "music", 0.5 );
$.play( "T140 O3 L8 C E G > C < G E C2" );
$.sfx( "coin" );
}Instruments
The plugin adds instruments to the play function. In a play string, @ followed by a number selects an instrument for the notes that follow, and @0 returns to the default sound. The built-in instruments are @1 square lead, @2 pluck bass, @3 pad, @4 noise snare, @5 hi-hat, and @6 kick drum. The defineInstrument function stores your own instrument under a number from 1 to 255, using the same options as the synth function. An instrument can also play an audio file loaded with loadAudio.
$.screen( "300x200" );
$.print( "Press any key to play sound." );
$.onKey( "any", "down", keyPress, true );
function keyPress() {
// A soft lead with vibrato
$.defineInstrument( 7, {
"oType": "sine", "attackTime": 0.05, "releaseTime": 0.3,
"vibratoRate": 6, "vibratoDepth": 20
} );
// Lead, bass, and kick drum tracks
$.play( "@7 T140 O4 L8 C E G > C4, @2 O2 L4 C G, @6 L4 C C" );
}Music Sync and Sound Levels
The onPlay function runs a function when each note of a song starts to sound, or when a song ends. This keeps animation in time with the music. The getSoundLevels function returns the peak and average level of a bus, and optionally its spectrum and waveform, for drawing volume meters and visualizers. Call it every frame.
let size = 0;
$.screen( "300x200" );
$.print( "Press any key to play sound." );
$.onKey( "any", "down", keyPress, true );
function keyPress() {
$.onPlay( "note", function ( note ) {
size = note.frequency / 10;
} );
$.onPlay( "end", function () {
size = 0;
} );
$.play( "T120 O3 L8 C E G > C < G E C4" );
requestAnimationFrame( draw );
}
function draw() {
let levels = $.getSoundLevels( "music" );
$.cls();
// Circle sized by the note that is playing
$.setColor( 10 );
$.circle( 150, 90, size );
// Volume meter
$.setColor( 12 );
$.rect( 10, 180, Math.round( levels.peak * 280 ), 10 );
requestAnimationFrame( draw );
}Recording
The startRecording function records the sound that Pi.js plays, and the stopRecording function returns the recording as a WAV file. Wait for startRecording to finish before playing anything that needs to be in the recording. The saveRecording function downloads the file, and the getRecordingState function reports how many seconds have been recorded so far. This example does not have a Run button because the code runner on this site cannot download files, so copy it into your own page to try it.
$.screen( "300x200" );
$.print( "Press any key to record a song." );
$.onKey( "any", "down", keyPress, true );
async function keyPress() {
// Record the output for up to 30 seconds
await $.startRecording( "output", 30 );
$.print( "Recording..." );
$.play( "T140 O3 L8 C D E F G A B > C2" );
// Stop and save when the song ends
$.onPlay( "end", async function () {
let wav = await $.stopRecording();
$.saveRecording( wav, "song.wav" );
$.print( "Saved song.wav" );
}, true );
}Overall, Pi.js provides a robust set of sound capabilities that can be used to create immersive audio experiences for games and other interactive applications. The library’s sound functions make it easy to create custom soundtracks, play sound effects, and manage multiple sounds simultaneously. Whether you’re a beginner or an experienced developer, Pi.js provides a powerful set of tools for creating engaging audio experiences on the web.
Please checkout the Pi.js Reference page to see a full listing of sound functions.