Sound outside Circuitry
synths.js and synths are free and open source, under the MIT licence, and run anywhere a browser does. Inside Circuitry there is nothing to install. This page is for using them on your own.
JavaScript: on a page of your own
synths.js is four files: synths.js, synths_core.wasm, synths-worklet.js and synths-midi.js. They are the whole library, and it needs no other. The last is fetched only by a program that uses MIDI. Put them in a folder of your site and import what you use in a module script:
<button id="go">Play</button>
<script type="module">
import { playSound } from "./synths/synths.js"
document.getElementById("go").onclick = () => playSound("powerup")
</script>
The files are in the synths.js repository. The page has to come from a web server (npx serve, or any other), because browsers do not let a page opened straight from disk import a module.
synths.js is not published to npm yet. When it is, npm install synths.js and import { playSound } from "synths.js" will do the same.
Every function on these pages is imported by its name:
import { makeSynth, playNotes, setTempo } from "./synths/synths.js"
setTempo(120)
makeSynth("bass").playNotes("C2 - C2 C3 - C2 - Eb2", "eighth", { repeat: true })
JavaScript: in a pictures.js scene
pictures.js has the sound built in. The library sits in a sound folder beside pictures.js, and is loaded only by a scene that uses one of the sound names: a scene with no sound loads nothing for it.
npx pictures run tune.js
npx pictures build tune.js -o site/
run opens your browser on the scene, and runs it again each time you save the file. build makes a folder of plain files for any static host, with the sound folder and the sound files the scene names.
Python
synths comes with Pyctures, which plays its sound in your web browser. Nothing else is installed: no sound drivers, and no other library.
pip install pyctures
pyctures run tune.py
pyctures build tune.py -o site/
run opens your browser and runs the program in your own Python. build writes a folder that plays in any browser, with nothing to install there.
Sound is in the next release of Pyctures: the version on PyPI today does not have it yet. Until then, install Pyctures and synths from copies of their repositories with pip install -e . in each.
A program with sound in it starts as any Pyctures scene does:
from synths import *
def on_mouse_down(x, y):
play_notes("E4 G4 C5 - G4 C5:2", 0.25)
def draw():
background("midnightblue")
text("Click for a tune", width / 2, height / 2, "white", 28, "centre")
Plain python tune.py works too, when Pyctures is installed: synths starts the program again with pyctures run for you, and says so. Without Pyctures it says what to type and carries on in silence, so a program with sound in it never stops because of the sound.
The microphone and MIDI
The microphone, recording and MIDI work on a page of your own, and in a scene run with pictures run or pyctures run. The browser asks the person once for each.
- A page has to be served over
httpsor fromlocalhostto be given a microphone or MIDI.pictures runandpyctures runare. - A page inside a frame needs
allow="microphone"orallow="midi"on the frame. - MIDI is in Chrome, Edge and Firefox on a computer. Safari has none, and so neither has any browser on an iPhone or an iPad.
saveSounddownloads the file in JavaScript. Withpyctures runthe file is written beside your program.
Where the sound is made
The engine runs on the browser's audio thread, away from your program. A page that is not served over https (or from localhost) has no audio thread to use: the same engine then runs on the page's own thread, which sounds the same, is heard a little later, and clicks when the page is busy. The console says so once. If sound clicks or stutters says what to do.
The sound buffer
How much sound is made at a time is yours to set, as it is in a scene. On a slow device that clicks, go larger; where a keyboard feels late, go smaller.
import { setSoundBuffer, soundBuffer } from "./synths/synths.js"
setSoundBuffer("large") // "small" (1024 frames), "medium" (2048), "large" (4096), "auto", or a number of frames
const now = soundBuffer() // { frames, delay, thread, underruns, setting }
console.log(now.delay, now.thread)
In Python it is set_sound_buffer("large") and sound_buffer().
It may be called before the first sound and at any time after. In what soundBuffer() gives back, delay is the thousandths of a second from a key to its note, underruns is how many times a buffer came too late (each is a click), and thread is "page" on the slow way, "audio" on the audio thread, and nothing before the first sound.
On the audio thread the engine always makes 128 frames at a time, which nothing can change. There the same call sets how far ahead everything is scheduled instead, and the console says once that this is what it did.
Sound with no sound card
In JavaScript the same program can be made into samples, or a WAV file, with nothing playing. It works in node too, and is how a test can listen to a program.
import { renderSound, toWav } from "./synths/synths.js"
const sound = await renderSound((s) => {
s.playNotes("C4 E4 G4 C5", 0.25)
}, { seconds: 1.5 })
For an app that shows scenes
synths.js is built so that the sound can be made somewhere other than where the program runs: in another frame, from a worker, or from Python. The functions only write commands, plain data; a transport carries them; a player owns the sound card and carries them out. Python's synths writes the same commands. The format is described in the repository.