Sound with pictures.js and Pyctures
Sound goes with drawing in the same program. A scene has update(dt) and draw() for its picture, and the sound names beside them.
- In a pictures.js scene there is nothing to import.
playSound,playNote,makeSynthand the rest are ready, ascircleandbackgroundare. - In a Pyctures scene the first line is
from synths import *.
Both play in Circuitry's preview, beside the code: see In Circuitry below.
Sound where something happens
A sound effect is one line at the place in update where the thing happens:
const basket = new Sprite("🧺", 200, 250, 64)
const star = new Sprite("⭐", 200, 0, 36)
star.vy = 160
let score = 0
function update(dt) {
if (mouse.down) basket.x = mouse.x
star.update(dt)
if (star.touches(basket)) {
score += 1
playSound("coin")
star.y = 0
star.x = random(40, width - 40)
}
if (star.y > height) {
playSound("lose")
star.y = 0
}
}
function draw() {
background("midnightblue")
basket.draw()
star.draw()
text("Score " + score, 20, 40, "white", 24)
}Drag the basket under the star. Play the sound once, at the moment it happens. A sound asked for in every frame while something is true plays sixty times a second.
A picture in time with the music
beat() is which beat it is now, with its fraction, so a picture can move in time with a tune. soundTime() is the sound's own clock, in seconds.
let playing = false
function onMouseDown() {
playing = !playing
if (!playing) return stopSound()
setTempo(132) // beat 0 is now
const step = beats(0.5) // every note below lasts half a beat
makeSynth("pluck").playNotes("E4 G4 A4 - A4 G4 E4 D4 C4 D4 E4 - E4:2 - -", step, { repeat: true })
makeSynth("bass").playNotes("A2:4 F2:4 C2:4 G2:4", step, { repeat: true })
makeSynth("kick").playNotes("C2 - - -", step, { repeat: true })
makeSynth("hat").playNotes("- - C5 -", step, { repeat: true, volume: 0.4 })
}
function draw() {
background("midnightblue")
// 1 on each beat, falling away until the next one.
const pulse = playing ? 1 - (beat() % 1) : 0
circle(width / 2, height / 2, 60 + 40 * pulse, "gold")
text(playing ? "Click to stop" : "Click to play", width / 2, height - 40, "white", 18, "centre")
}The circle is biggest on each beat. The tune does not wait for draw(): the sound engine keeps the time, away from your drawing, so sound does not slow a frame and a slow frame does not make the sound stutter.
Where to start a sound
| Where | What for |
|---|---|
onMouseDown, onKeyDown (on_mouse_down, on_key_down) | Music, and anything a click or a key begins. A click also turns the sound on |
update(dt) | A sound effect, at the moment something happens |
| The top of the file | Making synths and loading samples. A tune started here waits for the first click |
draw() | Nothing: draw is for the picture. Read beat() and soundTime() here |
Names a scene should not reuse
A variable with the name of a sound function hides it, as it does with a drawing function. In a scene that calls beat(), do not write let beat = 0; in one that calls note("C4"), do not call a variable note. Names that often collide: beat, beats, note, chord, rest.
When a scene stops
Sound stops by itself when the scene stops or is run again. It is paused while the scene is out of view or its tab is in the background, as the frames are, and carries on when you come back. Several scenes on one page share one sound card, and each is stopped and paused alone.
A video with its sound
Record Video, under the preview, makes an MP4 video of a scene. A scene that makes sound is recorded with its sound in the same file, exactly in step with the pictures, in JavaScript and in Python.
How it stays in step. A recorder films a scene one frame at a time, and a frame may take much longer to draw than the sixtieth of a second it stands for. Sound played by the loudspeaker could not stay with frames made like that. So while a scene is recorded its sound is not played at all: it is worked out for one frame at a time, on the same clock as the pictures, and put in the file. Every frame is at its exact time, and so is every note.
- Press and choose a size and 30 or 60 frames a second.
- If a JavaScript scene plays a song, tick Stop when the music ends to have the video end with the song.
- Press Record. The scene starts again from its beginning. You hear nothing while it records.
- Press the button again to stop and save, or Stop.
What you get:
| The file | One .mp4: H.264 pictures and AAC sound, 48,000 samples a second, in stereo. A scene with no sound gets no sound track |
| In step | Sixty frames are exactly 48,000 samples, so nothing drifts over a long video. A three-minute piece cut to the beat had every cut on its exact frame |
| The same every time | Two recordings of one scene in Chrome were identical, to the pixel and to the sample. A scene that uses random() differs where it uses it |
| What stops it | You do. With Stop when the music ends ticked, the song finishing or starting again does. A mistake in the scene stops it too, and the video up to that frame is kept |
| What is in it | Everything played through the sound library: game sounds, notes, tunes, synths, arpeggios, samples and songs, with their effects |
| A sound button, or a card that asks first | Neither is shown in the video, and the sound is on from the first frame: see A sound button, and asking first |
| What is not | The microphone (a recording of it comes out empty), and MIDI. Only sound a scene makes through the sound library is recorded: a web page's own sound is not |
Where it has been tried, as of October 2026:
| Where | A JavaScript scene | A Python scene |
|---|---|---|
| Chrome on a Mac | Works: measured | Works: measured, with every note on its frame |
| The iPad app | Works: a 1080p video with sound, made on an iPad, played in step in the iPad's own player | Not tried yet |
| An iPhone, and Safari on a Mac | Not tried yet | Not tried yet |
In a browser that cannot put sound in a video, the video is silent and the sound is saved beside it as a .wav file of the same length.
In Circuitry
A scene with sound runs in the preview beside its code, in JavaScript and in Python, on a computer, an iPad or an iPhone. As of October 2026 these are not connected yet:
| What | In Circuitry's preview |
|---|---|
| The microphone | Not connected: nothing comes in, and a recording of it is empty |
| MIDI | Not connected: no keyboard is found |
| Saving a sound | Not connected: nothing is saved |
| Export as Web Page | Works for a JavaScript scene with sound. A Python scene that uses synths or pydials cannot be exported yet |
In the iPad and iPhone app the sound is made by the app itself, on its own audio thread, so a busy scene does not make it click and no first tap is needed: see In the iPad and iPhone app.
A scene that uses one of them still runs, and the rest of it works. To use them today, run the scene outside Circuitry: see Outside Circuitry.
Controls on the scene
A dial, a slider, a scope or a piano keyboard goes on a scene in one line, and can be tied to a synth: see Dials, sliders and a keyboard.