notation: Sheet Music That Plays

notation writes music out as notes on a staff, and lights the notes as they play. It is for any age and any level: you give it a tune in one line and sheet music appears; you play the tune and each note lights as it sounds; you can play along on a keyboard and it tells you how you did.

It is two libraries with the same names. notation.js is for pictures.js scenes and pynotation is for Pyctures scenes. The sound comes from synths, and the keys on the screen from dials.

A tune, written out

showNotes("E4 D4 C4 D4 E4 E4 E4:2", "quarter", { title: "Mary had a little lamb" })

function draw() {
  background("midnightblue")
}

That is a whole program.

The notes are written the way playNotes takes them in Notes and tunes: a note is a letter and a number ("C4" is middle C, "F#3", "Bb5"), - is a rest, :2 makes a note last two steps, and "quarter" says how long a step is. The key, the clef and the bars are worked out for you.

It draws clefs, keys, time signatures, notes and rests of every usual length, dots, ties, beams, chords, ledger lines, bar lines and bar numbers, on as many lines as your screen needs.

  • In JavaScript there is nothing to import. showNotes and makeSheet are ready in every scene.
  • In Python a scene starts with from pyctures import * and from pynotation import *. That brings the sound with it, so nothing else is needed to hear the page; from synths import * is still wanted for play_song, make_synth and the rest of the sound names.

In Circuitry both run in the preview beside your code. Sheet music is loaded only by a scene that uses it: a scene that shows no music loads exactly what it always did.

Export as Web Page carries sheet music for a JavaScript scene.

In a recorded video of a scene with sheet music, each note is lit in the first frame at or after its sound begins. A Python scene's page of music in a video is the preview's page scaled to the video's width, so it is a little softer than a JavaScript one.

The first click turns the sound on, as browsers require. A tune that is asked for before that waits, and begins when the sound is on: the line never moves through notes nobody can hear.

The next three steps

Each of these is a whole program too.

Play it, and watch it go

const page = showNotes("C4 C4 G4 G4 A4 A4 G4:2 F4 F4 E4 E4 D4 D4 C4:2", "quarter", { tempo: 100 })
page.onNote = (note) => console.log("now playing", note.name)

function onMouseDown() {
  page.play()
}

function draw() {
  background("midnightblue")
  text("Click here to play", width / 2, height - 30, "white", 18, "centre")
}

page.play() plays the music and follows it: the notes that are sounding turn orange, a line moves through the bar, and the page turns when the music reaches the next line. page.onNote is called as each note begins, so your scene can do something on every note. page.stop() stops it. See Playing and following.

Two notes joined by a curved line are one note, held: that is a tie, and it is how a note is written when it lasts across a beat or a bar line. Both light together and you hear one sound.

Follow a song

const song = await loadJSON("songs/night_drive.json")
const page = showNotes(song, { channels: [3, 1] })      // the tune and the bass, a staff each

function onMouseDown() {
  page.play()                                           // the page plays the song itself, and follows it
}

function draw() {
  background("#0b1020")
  text(page.bar ? "bar " + page.bar : "Click to start the song", width / 2, height - 24, "white", 18, "centre")
}

A tracker song is turned into written music: you choose which channels to show. page.play() plays the song itself, and the page follows it. Tap a bar and the song jumps there. That is the picture at the top of this page. See A song as sheet music.

Play along

const lead = makeSynth("keys")
const page = showNotes("E4 D4 C4 D4 E4 E4 E4:2", "quarter")
page.playAlong({ mode: "wait", synth: lead })
keyboard(page, { octave: 4, octaves: 1 })        // a dials.js keyboard plays the page

function draw() {
  background("#12261e")
  text(`${page.result.right} right, ${page.result.wrong} wrong`, width / 2, height / 2, "white", 20, "centre")
}

The line waits at each note until you play it. The key to press is lit. A right note turns green; a wrong one shows in red for a moment where you played it. See Playing along.

Outside Circuitry

Inside Circuitry there is nothing to install. On your own:

  • notation.js is one file, from the notation.js repository. It comes with pictures.js, in a notation folder beside it, and is loaded only by a scene that uses it: save a scene as twinkle.js and run npx pictures run twinkle.js.
  • pynotation, from the pynotation repository, comes with Pyctures and carries notation.js inside it: pyctures run twinkle.py.

Neither is published on its own yet, to npm or to PyPI, and sheet music is in the next release of pictures.js and of Pyctures. Until then they are used from copies of the repositories.

On a page of your own with no pictures.js, notation.js fills any element:

import { notation } from "./notation.js"
const { showNotes } = notation(document.getElementById("music"))
showNotes("C4 E4 G4 C5:4", "quarter", { at: "fill" })

What's in this section