synths
Sound and music in JavaScript and Python

synths is designed to bring the joy back into programming, for any age and any level, a first program or a thousandth. Type a line and hear something: a game sound, a note, a tune, a synth you shape yourself, a recorded sound, your own voice as a robot, a whole tracker song. It is synths.js in JavaScript and synths in Python, with the same names in both, and it is the sound sibling of pictures.js and Pyctures: in a scene the sound names are ready beside the drawing ones.

Free and open source, MIT licence. Made for projects in Circuitry Studio; runs anywhere a browser does.

Try synths in your browser

One line makes a sound. Click or tap the picture for a coin; change "coin" to "laser", "jump" or "explosion" and press Run.

Open Circuitry

A browser keeps a page silent until you click or tap it, so the first touch on the scene turns the sound on. Change the code and press Run (or Cmd/Ctrl+Enter).

Type a line, hear something

playSound plays one of fourteen ready-made game sounds, playNote plays a note, and playNotes plays a tune a step at a time: - is a rest and :2 makes a note last two steps. A note is a letter and a number: "C4" is middle C. There is no sound card to open and nothing to wait for.

JavaScript
playSound("coin")                           // one of fourteen game sounds
playNote("C4")                              // middle C, for half a second
playNotes("E4 G4 C5 - G4 C5:2", 0.25, { after: 1 })
Python
from synths import *

play_sound("coin")                           # one of fourteen game sounds
play_note("C4")                              # middle C, for half a second
play_notes("E4 G4 C5 - G4 C5:2", 0.25, after=1)

In a game the line goes where the thing happens: the ball lands, the star is caught, the ship is hit. Game sounds lists them all.

A tune, written two ways

A tune can be a line of text, or ordinary lines of code. In code, note plays the next note of the tune: each one begins where the one before it ended, so there is no arithmetic, and loops and ifs write music. A length is a name ("quarter", "eighth"), measured against the tempo.

One note a line
setTempo(100)

note("E4", "quarter")
note("G4", "quarter")
rest("quarter")
note("C5", "half")
chord("Cm", "whole")

for (const name of ["C4", "D4", "E4"]) note(name, "eighth")
The same tune as text
setTempo(100)
playNotes("E4:q G4:q -:q C5:h Cm:w C4:e D4:e E4:e")

The sound engine keeps the time, not your program, so a tune stays even when a frame is slow and several parts started together stay together. Notes, chords and tunes has both ways, chords by name, note lengths and the tempo.

A synth of your own

makeSynth gives you an instrument. Start from a ready-made one ("bass", "lead", "pad", "bell", the drums) or make one from nothing. Every setting is a plain property that reads back what you set, and a note that is sounding changes as you change it.

JavaScript
const lead = makeSynth({ wave: "saw", cutoff: 1200, attack: 0.01, release: 0.3 })

lead.play("E4", 0.5)                 // a note, for half a second
lead.play(["C4", "E4", "G4"], 1)     // three notes at once: a chord
lead.cutoff = 800                    // every setting is a plain property
lead.echo = 0.4
Python
lead = make_synth(wave="saw", cutoff=1200, attack=0.01, release=0.3)

lead.play("E4", 0.5)                 # a note, for half a second
lead.play(["C4", "E4", "G4"], 1)     # three notes at once: a chord
lead.cutoff = 800                    # every setting is a plain attribute
lead.echo = 0.4

All of them are settings of one engine, not recordings, so every one can be changed. Synths and their settings explains the ones you will use first, and Every setting lists them all with their ranges.

Chip sounds and C64 chords

An arpeggio is a chord played one note at a time, very fast, on a single voice: the shimmering chords of a Commodore 64 tune, which had only three voices to spend. The chip synths (chip, chipLead, chipBass, chipKick, chipSnare, chipHat) are the sounds of a home computer.

JavaScript
playArp("Cm7")                             // half a second of C minor seventh
playArp(["C4", "E4", "G4", "C5"], 1, { speed: 12, order: "up-down" })

const chord = startArp("Am")               // one that stays
chord.change("F")                          // another chord: the note carries on
chord.stop()

makeSynth("chipLead").playNotes("C5 E5 G5 C6", 0.125)
Python
play_arp("Cm7")                             # half a second of C minor seventh
play_arp(["C4", "E4", "G4", "C5"], 1, speed=12, order="up-down")

chord = start_arp("Am")                     # one that stays
chord.change("F")                           # another chord: the note carries on
chord.stop()

make_synth("chip_lead").play_notes("C5 E5 G5 C6", 0.125)

Chip sounds and arpeggios also has step macros, which is how a sound chip made a drum from one voice.

Recorded sounds and effects

A sample is a recorded sound in a file beside your program: a .wav, or an .mp3. It is a synth too, so it plays at any pitch and has the same settings. Echo, reverb and chorus are one word each, and the reverb is a room, a hall, a plate, a cathedral or a spring.

JavaScript
const meow = loadSample("meow.wav")
meow.play()                       // as it was recorded
meow.play("E4")                   // higher: a sample is a synth, and "C4" is its own pitch
meow.reverb = 0.3

setEffects({ echoTime: 0.3, echoFeedback: 0.5, reverbType: "hall" })   // "room", "hall", "plate", "cathedral" or "spring"
Python
meow = load_sample("meow.wav")
meow.play()                       # as it was recorded
meow.play("E4")                   # higher: a sample is a synth, and "C4" is its own pitch
meow.reverb = 0.3

set_effects(echo_time=0.3, echo_feedback=0.5, reverb_type="hall")   # "room", "hall", "plate", "cathedral" or "spring"

Samples and Effects.

Your voice, as a robot

The microphone is a synth whose sound comes in instead of being made, so it has a synth's settings, and a voice in one word: robot, chipmunk, monster, cave, telephone, alien or whisper. Nothing is heard until you switch on listen, and it is best with headphones: through loudspeakers a microphone that hears itself howls.

JavaScript
const mic = useMicrophone()            // the browser asks whether the page may use the microphone

select("voice", mic, "voice")          // robot, chipmunk, monster, cave, telephone, alien, whisper
dial("pitch", mic, "pitch")
dial("echo", mic, "echo")
toggle("listen", mic, "listening")     // wear headphones before you switch this on
scope(mic)

function draw() {
  background("midnightblue")
}
Python
from pyctures import *
from synths import *
from pydials import *

mic = use_microphone()                 # the browser asks whether the page may use the microphone

select("voice", mic, "voice")          # robot, chipmunk, monster, cave, telephone, alien, whisper
dial("pitch", mic, "pitch")
dial("echo", mic, "echo")
toggle("listen", mic, "listening")     # wear headphones before you switch this on
scope(mic)

def draw():
    background("midnightblue")

Where it works today: on a web page of your own and in a scene run with pictures run or pyctures run, in Chrome, Edge, Firefox and Safari; the browser asks once. In Circuitry's own preview, on a computer, an iPad or an iPhone, the microphone is not connected yet: the scene runs, and nothing comes in. Your voice has the voices and the settings behind them.

Record a word, play it on the keys

recordSound gives you a recording at once, and it fills up behind your program. A recording is a sample, so it plays at any pitch: middle C is the word as you said it, and several keys make a chord of you. recordOutput records what the program itself plays, with no microphone, and saveSound makes a WAV file.

JavaScript
const mic = useMicrophone()
let word = makeSynth("bell")           // what the keys play until a word is recorded

button("record", () => {
  const take = mic.record(1.5)         // a second and a half, starting now
  take.onDone(() => { if (take.seconds) word = take })
})

// The keys play whatever word is now.
keyboard({
  noteOn: (note, loud) => word.noteOn(note, loud),
  noteOff: (note) => word.noteOff(note),
  notes: () => word.notes(),
}, { octave: 4 })

function draw() {
  background("darkslategray")
}
Python
from pyctures import *
from synths import *
from pydials import *

mic = use_microphone()
word = make_synth("bell")              # what the keys play until a word is recorded
take = None

def recorded():
    global word
    if take.seconds:
        word = take

def record():
    global take
    take = mic.record(1.5)             # a second and a half, starting now
    take.on_done(recorded)

button("record", record)

# The keys play whatever word is now.
def key_down(note, loud):
    word.note_on(note, loud)

def key_up(note):
    word.note_off(note)

keyboard(octave=4, on_note_on=key_down, on_note_off=key_up)

def draw():
    background("darkslategray")

Where it works today: wherever the microphone does (above). With no microphone the keys go on playing the bell. Recording has the rest.

Play it from a MIDI keyboard

Plug a MIDI keyboard into your computer and one line makes it play a synth: the notes, how hard each key is hit, the pitch wheel and the sustain pedal.learn ties the next knob you turn to a setting, so a box of real knobs works a synth on the screen. Your own functions can hear the keys too, and a program can send notes out to a synth on your desk.

JavaScript
const lead = makeSynth("lead")

const cutoff = dial("cutoff", lead, "cutoff")
scope(lead)
keyboard(lead, { octave: 4 })                    // with nothing plugged in, play these

lead.playFromMidi()                              // a MIDI keyboard plays the synth
lead.learn("cutoff", (value) => { cutoff.value = value })    // the next knob you turn is the cutoff

function draw() {
  background("midnightblue")
}
Python
from pyctures import *
from synths import *
from pydials import *

lead = make_synth("lead")

dial("cutoff", lead, "cutoff")
scope(lead)
keyboard(lead, octave=4)                         # with nothing plugged in, play these

lead.play_from_midi()                            # a MIDI keyboard plays the synth
lead.learn("cutoff")                             # the next knob you turn is the cutoff, and the dial turns with it

def draw():
    background("midnightblue")

Where it works today: in Chrome, Edge and Firefox on a computer, on a web page of your own and in a scene run with pictures run or pyctures run. Safari has no MIDI for web pages, and so neither has any browser on an iPhone or an iPad. In Circuitry's own preview MIDI is not connected yet, on any device: the scene runs and plays from the keys on the screen. MIDI keyboards and knobs.

Tracker songs

A song is a whole piece of music in one file, played by the sound engine itself: a tracker's .mod or .xm file, or a song in synths' own format, which is text you can read and change. Your own notes and game sounds play over it, and each channel can be silenced or heard alone.

JavaScript
const song = playSong("tune.mod")          // it goes round and round until it is stopped

song.pause()
song.resume()
song.volume = 0.5
song.mute(2)                               // silence the third channel
song.solo(0)                               // hear only the first
song.stop()
Python
song = play_song("tune.mod")          # it goes round and round until it is stopped

song.pause()
song.resume()
song.volume = 0.5
song.mute(2)                          # silence the third channel
song.solo(0)                          # hear only the first
song.stop()

Songs has the rest, and how a song of synths' own is written.

How to get it

In Circuitry Studio there is nothing to install. A JavaScript scene has the sound names ready, with nothing to import. A Python scene starts with from synths import *. Both play in the preview beside your code, on a computer, an iPad or an iPhone. The microphone and MIDI are not connected in the preview yet.

In JavaScript, outside Circuitry, synths.js is four files you put in a folder of your site, and import from a module script. It needs no other library. It is not published to npm yet; when it is, npm install synths.js will do the same.

index.html
<button id="go">Play</button>
<script type="module">
  import { playSound } from "./synths/synths.js"
  document.getElementById("go").onclick = () => playSound("powerup")
</script>

In Python, outside Circuitry, synths comes with Pyctures, which plays its sound in your web browser: pyctures run tune.py. Neither library is published to PyPI or npm yet: until then it comes from a copy of the Pyctures repository. Outside Circuitry has both.