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.
One line makes a sound. Click or tap the picture for a coin; change "coin" to "laser", "jump" or "explosion" and press Run.
Open CircuitryA 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).
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.
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 })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 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.
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")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.
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.
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.4lead = 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.4All 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.
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.
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)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.
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.
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"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"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.
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")
}
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.
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.
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")
}
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.
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.
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")
}
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.
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.
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()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.
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.
<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.