synths: Sound and Music in JavaScript and Python
synths is for making sounds and music in code, and for enjoying it: game sounds in one line, notes and tunes, synths you shape yourself, recorded samples, effects, chip sounds and whole tracker songs. It is for any age and any level, a first program or a thousandth, and it is designed to bring the joy back into programming: you type a line and hear something.
It is two libraries with the same names. synths.js is for JavaScript and synths is for Python. They are the sound siblings of pictures.js and Pyctures, so a scene that draws can also make sound, in the same file. You can change the programs on the synths page and run them in your browser.
function onMouseDown() {
playSound("coin")
}
function draw() {
background("midnightblue")
text("Click for a coin", width / 2, height / 2, "white", 30, "centre")
}That is a whole program. Run it and click the preview.
- In JavaScript there is nothing to import. The sound names are ready in every scene, beside the drawing ones.
- In Python a scene starts with
from synths import *.
In Circuitry both play in the preview beside your code, on a computer, an iPad or an iPhone. Two things are not connected in the preview yet: the microphone and MIDI.
Every name has both spellings: playSound in JavaScript is play_sound in Python, makeSynth is make_synth, and a setting such as vibratoRate is vibrato_rate. A program comes across from one language to the other with little more than its syntax changed.
The first click
Browsers keep a page silent until the person using it has clicked, tapped or pressed a key. You do not have to do anything about it: the first touch turns the sound on. A tune you started before it waits, and begins at that touch. A single note or sound effect from long before it is let go, because played late it would be wrong.
So in a game, where the player is clicking or pressing keys anyway, sound just works. For music, start it from a click, as the programs on these pages do.
What counts as the first touch. A click or a tap anywhere on the picture, a key of the computer, and a press on any control too: a dial, a button, a joystick, a key of the on-screen piano. Moving the pointer does not count. With a finger the sound comes on as the finger lifts, so the very first tap of a finger is best spent on a "tap to start".
The sound clock does not wait. soundTime() counts seconds from when the page opened, and goes on counting whether the sound is on yet or not (sound_time()). So do beat() and the frames.
The next three steps
Each of these is a whole program too.
A note, and a tune
function onMouseDown() {
playNote("C4") // middle C, for half a second
playNotes("E4 G4 C5 - G4 C5:2", 0.25, { after: 1 }) // then a tune, a second later
}
function draw() {
background("midnightblue")
text("Click for a tune", width / 2, height / 2, "white", 28, "centre")
}A note is a letter and a number: "C4" is middle C, "A4" is the A above it, "F#3" is F sharp and "Bb5" is B flat. playNotes plays a tune a step at a time, here a step every quarter of a second: - is a rest and :2 makes a note last two steps. See Notes, chords and tunes.
A synth of your own
const lead = makeSynth("lead", { cutoff: 1200 })
function onMouseDown(x, y) {
lead.cutoff = 200 + x * 6 // brighter to the right
lead.play(["C4", "E4", "G4"], 0.6)
}
function draw() {
background("midnightblue")
text("Click: left is dull, right is bright", width / 2, height / 2, "white", 22, "centre")
}makeSynth gives you an instrument. cutoff is how bright it is, and every setting is a plain property you can change while it plays. See Synths and their settings.
A beat that keeps going
let playing = false
function onMouseDown() {
playing = !playing
if (!playing) return stopSound()
makeSynth("kick").playNotes("C2 - - - C2 - - -", 0.125, { repeat: true })
makeSynth("hat").playNotes("C4 C4", 0.125, { repeat: true, volume: 0.4 })
makeSynth("bass").playNotes("C2 - C2 C3 - C2 - Eb2", 0.125, { repeat: true })
}
function draw() {
background("midnightblue")
text(playing ? "Click to stop" : "Click for a beat", width / 2, height / 2, "white", 28, "centre")
}repeat plays a tune round and round. The parts stay together, however busy the program is, because the sound engine keeps the time and not your program.
When things start
Everything that makes a sound can be told when:
playSound("coin", { after: 0.5 }) // half a second from now
playNote("C4", 1, { at: soundTime() + 2 }) // at a time on the sound clock, which soundTime() reads
lead.play("E4", beats(1), { beat: 8 }) // on beat 8| Word | What it is given |
|---|---|
after | How long from now. A number is seconds, whatever the tempo. A note length by name is so many beats at the tempo now: { after: "quarter" } is half a second at 120 beats a minute and a whole second at 60 |
at | A time on the sound clock, in seconds: the clock soundTime() reads |
beat | A beat, counted from the last setTempo. { beat: 2 } is one second after it at 120 beats a minute, and two seconds after it at 60 |
Give one of the three. With none, the sound is now.
Beats are counted from setTempo(120) (beats a minute; beat 0 is the moment you call it). beats(2) is how many seconds two beats last, and beat() is which beat it is now, so a picture can move in time:
const size = 60 + 40 * (1 - beat() % 1) // biggest on each beat
Sounds are placed on the exact moment they were asked for. That is why a tune stays in time even when your program is busy drawing.
Volume, mute and stop
setVolume(0.5) // everything half as loud. 1 is normal
muteSound() // silence, and unmuteSound() brings it back
stopSound() // stops everything that is sounding or waiting to soundstopSound() stops everything that is playing notes, and takes back everything that was still to come:
| It stops | |
|---|---|
| A tune, a repeating one too | playNotes(…, { repeat: true }), the lines written with note, and loopNotes |
| A note that is held | lead.start("C4") |
| An arpeggio that stays | startArp("Am") |
| A song | playSong(…) |
| A note or a sound that had not begun yet | one asked for with after, at or beat |
| Notes sent to a MIDI device | those sounding are let go, and those to come are taken back |
It leaves everything else as it was. Your synths and their settings are still there, and so are the tempo and the volume: the next note plays as usual. The microphone stays on, and you still hear yourself if you were listening: stopMicrophone() lets it go. A recording that was being made carries on to its end.
To stop one thing alone, use its own stop(): tune.stop(), held.stop(), arp.stop(), song.stop(), lead.stop() for every note of one synth.
Sound stops by itself when the scene stops or is run again.
A sound button, and asking first
Two lines, each for its own job. Neither happens unless you write it, and neither needs an import, in JavaScript or in Python.
The button
soundButton()
function onMouseDown() {
playSound("coin")
}
function draw() {
background("midnightblue")
if (!soundIsOn()) text("Sound is off: press M", 20, 30, "white", 16)
}A small speaker in a corner of the scene. A click or a tap on it switches all of the scene's sound off, and another switches it back on; the picture on it shows which. The M key does the same while the scene has the keyboard.
- It is remembered. The choice is kept for this scene on this device, so a scene that was switched off is still off the next time.
- It asks nothing. Your game starts as it always did.
- It is not drawn by your scene. It is laid over the picture, costs nothing in a frame, keeps off any dials and sliders, and goes when the scene stops. A tap on it is not a tap on your game:
onMouseDownis not called. - A scene that reads M itself (it has
"m"in its code) keeps M, and the button leaves it alone.
soundButton("bottom left") // a corner: "top right" unless you say, "bottom right", "top left"| Option | What it does |
|---|---|
corner | "top right", "bottom right", "top left" or "bottom left" |
size | How many points across. Unless you say: 34 with a mouse, 44 where a finger does the pointing |
inset | How far in from the two edges of its corner, in points: a number, or two numbers (across, down) to sit beside something your scene draws in that corner |
colour | "auto" looks at the scene under the button and is light on a dark scene and dark on a light one; or say "light" or "dark" |
key | The key that switches the sound: "m" unless you say, and false for none (None in Python) |
soundIsOn() is true or false (sound_is_on()). The button itself can be kept and asked: const sound = soundButton(), then sound.on is the same thing and can be set, sound.over(x, y) says whether a point of the scene is on the button, and sound.remove() takes it away.
muteSound() and unmuteSound() are the same switch, and the button shows what they did. Only what a person chose is remembered, not what your code switched. In Python the page holds the switch and tells Python with the next frame, so after a tap sound_is_on() is right one frame later.
Asking first, in a demo
A demo is something other people open, perhaps in a quiet room. One line has it ask before it makes a sound:
playNotes("C4 E4 G4 C5", 0.2, { repeat: true })
askAboutSound()
function draw() {
background("#0b1020")
circle(width / 2, height / 2, 40, "gold")
}Before anything is heard a card is laid over the scene: "This demo has sound", with two big buttons, "Play with sound" and "Play without sound". Nothing sounds until one is chosen, and the scene waits behind the card: its first frame is shown, and update and draw are not called again until the answer. "Play with sound" is also the click a browser needs before it lets a page make sound. The line puts the sound button up as well, so a demo needs only this one line.
It is for demos you publish. A game of your own does not need it: use the button alone.
askAboutSound("Music and sound effects") // say what kind of sound it is
askAboutSound({ withSound: "Sound on", withoutSound: "Quiet" })| Option | What it does |
|---|---|
title, withSound, withoutSound (with_sound, without_sound) | The card's words. The title can be given by itself, first |
remember | true asks only until there is an answer, and uses that answer from then on |
wait | false keeps the scene running behind the card, still silent |
corner, size, inset, colour, key | For the button it puts up, as above |
- When it asks. Every time the scene is opened afresh, with last time's answer ready under Enter. It does not ask again when the scene is run again within five minutes of an answer, so working on a demo is not a card at every run.
- Keys on the card. Enter or Space presses the button that has the keyboard, the arrows or Tab go between the two, and Escape is "Play without sound". No key and no tap reaches the scene while the card is up.
- What was asked for before the answer. A sound effect or a note is dropped, because played late it would be wrong. A tune that repeats, or a song, starts at "Play with sound".
- With "Play without sound" the scene's sound calls still run, silently. While the card is up
soundIsOn()is false.
In a recorded video neither the card nor the button is shown, and the sound is on from the first frame. So a demo that asks first still records as a video with its sound, with nothing to press. See A video with its sound.
Mistakes
A mistake is said in plain words, with what was probably meant:
playSound("lazer"): there is no sound called "lazer". Did you mean "laser"? The sounds are: coin, jump, laser, …
makeSynth(): there is no setting called "cutof". Did you mean "cutoff"?
"H4" is not a note. Write a note as a name like "C4", "F#3" or "Bb5", or as a number, where 60 is middle C
Things that are not your mistake do not stop the program. They are said once and the rest carries on: sound that is waiting for a click, a sample whose file is missing, a browser with no MIDI, no microphone.
What's in this section
- A sound button, and asking first, on this page: a speaker on the scene to switch its sound off and on, and a card for a demo that asks before it plays.
- Game sounds: the fourteen ready-made sounds, and how to change one.
- Notes, chords and tunes: notes, chords by name, a tune as text and a tune as code, note lengths and the tempo.
- Synths and their settings: the ready-made synths, making your own, and the settings you will use first.
- Every sound, synth and setting: all of them, with their ranges.
- Samples: recorded sounds from a file, a synth made from one, and an instrument made from several.
- Your voice: the microphone, as a robot, a chipmunk or a monster.
- Recording: record your voice or what you play, play it at any pitch, save it.
- Effects: echo, five kinds of reverb, chorus, distortion and crush, and a trance gate.
- Chip sounds and arpeggios: the chords and drums of a home computer, and arpeggios in time with the tempo.
- Songs: tracker songs, and synths' own song format.
- MIDI keyboards and knobs: play a synth from a MIDI keyboard, turn its settings from real knobs, and choose the device on the screen.
- With pictures.js and Pyctures: sound in a scene that draws.
- If sound clicks or stutters: why a page that is not on https is late and clicks, and what to do.
- Outside Circuitry: on your own web page, and with your own Python.
- Function index: every name, one line each, in both spellings.
Related
- Dials, sliders and a keyboard: controls on the scene, tied to a synth in one line
- JavaScript graphics (pictures.js)
- Python graphics (Pyctures)