Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

pynotation

MIT licence

A song playing in a Pyctures scene with its tune and bass written out on two staves: the notes sounding now are orange and a line marks the place in the bar

pynotation writes music out as notes on a staff in a Pyctures scene, 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 the Python sibling of notation.js, and goes with synths for sound and pydials for a keyboard on the screen.

Start with four small scenes

Each one is a whole program. Run one with pyctures run scene.py.

1. A tune, written out.

from pyctures import *
from pynotation import *

show_notes("E4 D4 C4 D4 E4 E4 E4:2", "quarter", title="Mary had a little lamb")

def draw():
    background("midnightblue")

The notes are written the way play_notes takes them: 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.

2. Play it, and watch it go.

from pyctures import *
from synths import *
from pynotation import *

page = show_notes("C4 C4 G4 G4 A4 A4 G4:2 F4 F4 E4 E4 D4 D4 C4:2", "quarter", tempo=100)
page.on_note = lambda note: print("now playing", note.name)

def on_mouse_down(x, y):
    page.play()

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

page.play() plays the music through synths 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.on_note is called as each note begins. page.stop() stops it.

3. Follow a song.

from pyctures import *
from synths import *
from pynotation import *

song = play_song("songs/night_drive.json")
show_notes("songs/night_drive.json", channels=[3, 1], follow=song)     # the tune and the bass, a staff each

def draw():
    background("#0b1020")

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

4. Play along.

from pyctures import *
from synths import *
from pydials import *
from pynotation import *

lead = make_synth("keys")
page = show_notes("E4 D4 C4 D4 E4 E4 E4:2", "quarter")
page.play_along("wait", synth=lead)
keyboard(page, octave=4, octaves=1)        # a pydials keyboard plays the page

def draw():
    background("#12261e")
    text("%d right, %d wrong" % (page.result.right, page.result.wrong), width / 2, height / 2, "white", 20, "centre")

The play-along scene: the music at the top with a line at the first note, and piano keys below with the key to press lit

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. "time" in place of "wait" keeps going at the tempo whether you are ready or not.

The scenes in examples/ are ready to run: twinkle.py, song.py and play_along.py.

What you can show

You give It is
show_notes("C4 E4 G4 - C5:2", "quarter") a tune as text: notes, - for a rest, :2 for two steps, :q :h :e :s :w for a length by name, C4+E4+G4 or Cm7 for a chord
show_notes(["C4", None, ["C4", "E4"]], "quarter") the same as a list: None is a rest, a list inside is a chord
show_notes(sheet) a sheet you write a note at a time: sheet = make_sheet(key="G", time="3/4"), then sheet.add("G4", "quarter"), sheet.add(["B4", "D5"], "half"), sheet.rest("quarter")
show_notes("songs/tune.json", channels=[0, 1]) a tracker song beside the scene, or the song as a dictionary, or the packed bytes of a MOD or XM
show_notes([{"note": "C4", "start": 0, "length": 1}]) plain data, in beats

The options are those of notation.js, spelled Python's way: key, time, clef, tempo, title, at, x, y, width, height, zoom, paper, colours, scroll, lines, bar_numbers, follow, hear, seek, line, voice, synth, and for a song channels, instruments, drums, grand and rows_per_beat. How a tracker song becomes written music is told there too.

The page

page.play() play it and follow it. page.play(start=8) begins eight beats in; tempo=90
page.stop() stop, and put everything back to plain ink
page.playing is it playing?
page.follow(song) follow a song from play_song, or a function that gives how many beats have gone by
page.beat, page.bar, page.sounding where the music has got to, and the notes sounding, as the page last said
page.on_note = fn a note has just begun: fn(note) with note.name, note.note, note.notes, note.start, note.length, note.staff
page.on_bar = fn, page.on_tap = fn, page.on_done = fn a bar began; a note or a bar was tapped; the music ended
page.play_along("wait") the learner plays; also "time", and synth=, staff=, tempo=, hint=, any_octave=, count_in=
page.note_on(note), page.note_off(note) a key went down, or up, from anywhere
page.result right, wrong, missed, total, percent, timing, done; page.on_right, page.on_wrong and page.on_done are called as they happen
page.seek(5), page.show(music), page.place("bottom"), page.zoom = 1.5, page.remove() go to bar 5; other music; its place and size; take it away

How it is made

The page writes the music out and draws it. Python never touches the page (it may be in a worker, or on another computer). show_notes sends one command, which makes the page of music in the browser from notation.js (the package carries the very file that is the JavaScript library), and Python keeps a small object that stands for it. What happens there comes back with the next frame.

The sound library keeps the time. page.play() asks the page for the notes as play_notes takes them, plays them through synths at a moment on the sound clock, and tells the page that moment: the page then lights the notes and moves the line by itself, every frame, with no more commands. A song is followed from song.position, which Python hands on as it changes, and the page moves smoothly between.

A keyboard from pydials tied to the page (keyboard(page)) plays it in the page itself, so a key is marked the moment it is touched.

Tests

python -m pytest tests

tests/test_runtime.py is the Python half with no browser: the commands it sends and what it does with what the page reports. tests/test_browser.py runs scenes with pyctures run in a muted Chromium (it needs Playwright): every example draws its music, a tune Python plays is followed in the page and each note is told to Python in time, and a learner plays along on a pydials keyboard. The page's half is tested in notation.js.

Licence

MIT, © John Wylie. The music symbols' outlines are from the Leland font, © MuseScore BVBA, under the SIL Open Font Licence 1.1: see LICENSE-GLYPHS.

Metadata

Release files for pynotation 0.1.0b1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pynotation 0.1.0b1
File Size Uploaded Provenance
pynotation-0.1.0b1.tar.gz 62.7 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for pynotation 0.1.0b1
File Interpreter ABI Platform
pynotation-0.1.0b1-py3-none-any.whl Python 3 none any Details

Total release size: 123.1 kB

Release files / pynotation-0.1.0b1.tar.gz

Download URL pynotation-0.1.0b1.tar.gz
Size 62.7 kB
Tags Source
SHA-256 checksum
How to use checksums
a9c4a82cef66768bcb324f23203af1483fa23d20959319918d7e3bc20b2682a5
BLAKE2b-256 checksum
How to use checksums
c4022e8409c53b1f01eba48f2eccae7871474ef0d5597591f00697d453fb9548
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.5

Release files / pynotation-0.1.0b1-py3-none-any.whl

Download URL pynotation-0.1.0b1-py3-none-any.whl
Size 60.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
50dfc38cf037adf088ae8afd5895fabd2bf569289cfcc44f3a7d599a9eb2ea50
BLAKE2b-256 checksum
How to use checksums
5ad37e976e34f2e329224311d451f27a00816c22cf25c718899df0a00456788c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.5

Security for pynotation 0.1.0b1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release
Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page