A Python tool that converts braille music notation (.brf/.brl) to LilyPond (.ly), giving blind composers a direct path from their native notation to PDF scores and MIDI audio.
Blind musicians who compose in braille music notation have no direct path from their native format to publication-quality scores or audio playback without sighted assistance. Existing tools go the opposite direction — they convert printed notation to braille, not the other way around.
DottedNotes closes that gap. A blind composer can write in braille on a BrailleNotetaker or any braille editor, then run one command to produce a LilyPond file that compiles to a PDF score and MIDI file — no sighted intermediary required.
Active development. The tool supports solo and ensemble scores (multiple parallel instruments, keyboard staves, and vocal scores with lyric alignment), key and time signatures, measure divisions, repeats, tuplets/triplets, and fingerings. The full sprint schedule is in TICKETS.md.
composer writes in braille → .brf file
→ dottednotes convert piece.brf piece.ly
→ lilypond piece.ly → piece.pdf + piece.midi
Requires Python 3.9 or later. It is highly recommended to install the package inside a virtual environment to avoid macOS code-signing issues and global permission conflicts (which can result in a kill -9 or Killed: 9 error in the terminal).
# Clone the repository
git clone https://github.com/judithhinlung/DottedNotes.git
cd DottedNotes
# Create a virtual environment
python3 -m venv .venv
# Activate the virtual environment
# On macOS/Linux:
source .venv/bin/activate
# On Windows:
# .venv\Scripts\activate
# Install in development mode with dev dependencies
pip install -e ".[dev]"To compile the generated LilyPond .ly files to print-ready PDF scores and MIDI audio, you need to install the lilypond binary:
- macOS:
Install via Homebrew:
brew install lilypond
- Linux (Debian/Ubuntu):
Install via apt:
sudo apt-get install lilypond
- Windows / Other:
Download the installer or archive for your system from the LilyPond download page and add the directory containing the
lilypondexecutable to your system'sPATH.
All commands use the convert subcommand to process input files. Both .brf (ASCII braille) and .brl (Unicode braille) input files are supported. The tool automatically detects the file encoding.
# Convert a braille music file to LilyPond and output to stdout
dottednotes convert piece.brf
# Convert and write the output directly to a file
dottednotes convert piece.brf piece.ly
# Convert and compile to PDF + MIDI (requires lilypond to be installed on your PATH)
dottednotes convert piece.brf piece.ly --compile
# Verbose output — prints diagnostic info (detected encoding, tokens, validation warnings) to stderr
dottednotes convert piece.brf piece.ly --verbose
# Override the layout category (defaults to auto-detection from staves and instruments)
dottednotes convert piece.brf piece.ly --category "Chamber"
# Override specific formatting options using key=value pairs
dottednotes convert piece.brf piece.ly --format "paper_size=a4,margin_mm=12,staff_size=18"
# Print BANA validation report (corrections list) to stderr
dottednotes convert piece.brf --report
# Render back to compressed braille instead of LilyPond: a .brf/.brl output
# path switches to BRF output. --compression controls shorthand carrying and
# measure-repeat compression (full, minimal, none).
dottednotes convert piece.brf piece.brf --compression minimal
# Show measure numbers in braille output, keeping the source MusicXML/
# LilyPond file's own numbering (e.g. a pickup measure numbered 0) instead
# of renumbering sequentially from 1
dottednotes convert piece.musicxml piece.brf --measure-numbers --measure-numbering print_score
# List all available parts (staves) in a multi-staff score
dottednotes convert piece.musicxml --list-parts
# Convert only a specific part (by 1-based index or name)
dottednotes convert piece.musicxml piece.ly --part 2
dottednotes convert piece.musicxml piece.ly --part "Soprano"
# Convert a BANA Sec. 24 single-line-format instrumental solo, naming its
# instrument (the braille never states this itself)
dottednotes convert piece.brf piece.ly --single-line --instrument violin
# List every instrument name --instrument accepts
dottednotes --list-instruments
# Show version
dottednotes --version-
--category <CategoryName>: Overrides the default layout template heuristics. The layout controls the default staff size, margins, and staff grouping. Supported categories:Solo Piano(Default for single/double keyboard instrument scores; staff size 20.0 pt, margins 20 mm)Art Song(Default for voice + keyboard scores; staff size 18.0 pt, margins 18 mm)Chamber(Default for 3–6 staves; staff size 16.0 pt, margins 15 mm)Orchestral(Default for >6 staves; staff size 14.1 pt, margins 12 mm)
[!NOTE] Overriding a score's category to a non-vocal category (e.g.
ChamberorSolo Piano) prevents the parser from extracting vocal lyrics from literary braille lines, suppressing lyric mapping and\addlyricsrendering. -
--format "key1=value1,key2=value2": Applies specific, comma-separated layout/formatting overrides on top of the template defaults. Supported format keys:paper_size(string, e.g.a4,letter)margin_mm(float, margin size in millimeters)staff_size(float, global staff size in points)basic_distance(float, basic distance between systems)padding(float, padding spacing between systems)
-
--report: Runs the BANA validator on the parsed music score and prints a line-by-line list of correction warnings (such as sign order violations, missing octave marks under register rules, and shorthand recommendations) tostderr. -
--compression <Level>: Sets the level of braille shorthand/compression used when theconvertoutput path ends in.brfor.brl(which renders back to braille instead of LilyPond). Has no effect on ordinary.lyoutput. Supported levels:full(Default; enables all shorthand carrying and measure repeat compression)minimal(Enables shorthand carrying and measure repeats, but disables long-span repeats if supported)none(Disables all shorthand carrying and repeat compression, rendering notes and articulations explicitly)
-
--measure-numbers: Shows measure numbers at all. Off by default. In.brf/.brloutput this is the BANA margin number (solo/keyboard) or heading (ensemble) that marks where each system starts; in.lyoutput it's a% Ncomment on each measure's line instead, for navigating a validation warning back to its source line. -
--measure-numbering <Mode>: Only matters when--measure-numbersis on, and only for.brf/.brloutput — controls which number gets shown, not whether one is shown.auto(Default; numbers measures sequentially from 1, ignoring whatever measure numbers the source file has — a plain BRF's own margin numbers, or a MusicXML/LilyPond file's<measure number="...">, are all disregarded in favor of a clean 1, 2, 3, ... count)print_score(Reads and keeps the source MusicXML/LilyPond file's own measure numbers instead of recalculating them — including an irregular pickup measure numbered 0, or a mid-piece renumbering that isn't strictly sequential. A LilyPond source currently has no way to carry that original numbering through the round trip, soprint_scoreandautobehave the same there; MusicXML and BRF sources aren't affected by that limitation.)
-
--octave-mark-every-measure: For.brf/.brloutput, forces the octave mark on every measure's first note, not just measures that start a new braille line. Off by default. This is a reader/regional preference on top of BANA's required rules (which already force the mark at each line's first note, and after a word sign or numeric indicator) — turning it on never removes a mark that would already be shown, it only adds more. Has no effect on.lyoutput. -
--full-measure-repeat <Mode>: Controls whole-measure repeat-sign compression for.brf/.brloutput, independent of--compression's (unrelated) articulation-carry shorthand. Has no effect if--compressionisnone(that remains a hard override disabling all compression) or on.lyoutput.single-voice(Default; compresses runs of identical measures, but never a measure containing in-accord/multi-voice content)off(Disables repeat-sign compression entirely)multi-voice(Also compresses in-accord-containing measures, when every voice matches)
-
--min-repeated-measures <N>: Minimum number of consecutive musically-identical measures required before they're compressed into a repeat sign, for.brf/.brloutput. Default2(the smallest possible repeat: one original plus one repetition). Has no effect if--full-measure-repeatisoffor on.lyoutput. -
--include-clef-sign: For.brf/.brloutput, includes the clef sign for a facsimile transcription (BANA Par. 4.1: clef signs are otherwise routinely omitted in braille music). Off by default. When on, the clef is stated once, right after the first measure's number — not glued onto the key/time signature line. Has no effect on.lyoutput. -
--list-parts: Prints a list of all available parts (staves) in the score and their indices, then exits. -
--part <IndexOrName>: Filters the score to include only the specified part before rendering. Accepts a 1-based index (e.g.2) or a case-insensitive part name (e.g.Soprano). -
--single-line: Parses.brf/.brlinput as BANA Sec. 24 single-line format (an instrumental solo or single ensemble part) — its braille never states which instrument it's written for (Secs. 24.1–24.5 only cover segment/ measure-number layout). Not valid with MusicXML/LilyPond input, which already carries its own instrument information. -
--instrument <Name>: Names the instrument for a--single-lineconversion, from LilyPond's General MIDI instrument list (rundottednotes --list-instrumentsto see every option — e.g.violin,flute,french horn). Sets the output staff's name and\set Staff.midiInstrument. Optional: if omitted, the instrument is inferred from the piece's title (e.g. "for Violin"), falling back to piano when nothing recognizable is found. The same inference (with a confirm/override prompt) drives the web UI's post-translation instrument popup — seeweb.py'sneeds_instrument_selection/POST /api/jobs/{id}/instrument, which also covers an extracted piano hand (right hand/left handare parser placeholders, not real instrument names). -
--key-mode <major|minor>: Choose whether key signatures in.brf/.brlinput are major or their relative minor keys before encoding to LilyPond (since braille key signatures only specify the number of sharps/flats, e.g. one flat for F major vs D minor). Defaults tomajor. The same selection (with a confirm/override prompt) drives the web UI's post-translation key signature mode popup. -
--list-instruments: Prints every instrument name--instrumentaccepts, one per line, and exits. Works standalone, withoutconvertor an input file (like--version).
Note
DottedNotes transcribes braille input faithfully rather than
auto-correcting it, so a few things are on the transcriber to get right
in the source .brf/.brl file:
- Capitalization: follow BANA's literary-braille capital-indicator convention for instrument names, titles, and tempo/mood markings (e.g. "Allegro moderato", not "allegro moderato") — DottedNotes reproduces whatever capitalization the braille actually encodes, it doesn't infer proper-noun or sentence casing on its own.
- Key signature mode: braille key signatures encode a sharp/flat
count only, with no way to distinguish a major key from its relative
minor (e.g. E minor and G major share one sharp) — DottedNotes always
emits the major spelling; if the piece is actually in the relative
minor, edit the generated
\keyline by hand.
A hosted version is available at www.dottednotes.net —
no installation required. Upload a .brf, .brl, .ly, .musicxml, .xml, or .mxl
file, choose the options described below, and download the converted result (and, for
LilyPond output, the compiled PDF/MIDI if compilation succeeds).
The web UI's options mirror the CLI's convert flags one-to-one — the "What do these
options mean?" link on the page points back to this section.
- Target Format: LilyPond (
.ly,.pdf,.midi), Braille Music (.brf), Braille Music (.brl), or MusicXML (.musicxml). Corresponds todottednotes convert's choice of output file extension. - Layout Category: same as
--category— Default (auto-detect), Solo Piano, Art Song, Chamber, Orchestral, or Lead Sheet. - Braille Compression: same as
--compression— Full Compression, Minimal Compression, or No Compression. Only affects Braille Music output. - BANA Validation Profile: Standard or Strict (extra checks). Controls which rules the BANA Formatting Rule Report (below the downloads) checks for.
- Include Measure Numbers: same as
--measure-numbers. - Measure Numbering: same as
--measure-numbering— Auto (renumber sequentially from- or Print Score (keep the source file's own numbers). Only matters when Include Measure Numbers is on.
- Include Page Numbers: adds the piece title and a braille page number to every page of Braille Music output. On by default; turn off for a plain continuous stream with no page breaks.
- Always Mark Octave at Start of Measure: same as
--octave-mark-every-measure. - Include Clef Sign (Facsimile): same as
--include-clef-sign. - Full-Measure Repeat: same as
--full-measure-repeat— Single-Voice Only (default), Off, or Single- and Multi-Voice. - Minimum Repeated Measures: same as
--min-repeated-measures. - Advanced Formatting Overrides: same as
--format, entered as the same comma-separatedkey=valuestring (e.g.paper_size=a4,margin_mm=12).
For a multi-staff score, a part selector appears next to the downloads so you can
fetch a single part (staff) instead of the full score — equivalent to the CLI's
--list-parts/--part.
If the uploaded file is a BANA Sec. 24 single-line-format .brf/.brl (an instrumental
solo or single ensemble part) — or an extracted piano hand from a keyboard score — the
result has no instrument name of its own: single-line braille never states which
instrument it's written for, and "right hand"/"left hand" are internal placeholders, not
real instrument names. In both cases the web UI pops up an instrument selection
dialog after translation, equivalent to the CLI's --instrument flag.
Before showing the dialog, DottedNotes makes a best-effort guess: it tries to infer the instrument from the piece's title (e.g. a title containing "for Violin"), and falls back to piano if nothing recognizable is found. That guess is pre-applied to the initial output and pre-selected in the dialog — you can accept it as-is, pick a different instrument from the list, or dismiss the dialog to keep the guessed/default instrument.
If the uploaded file is a Braille format file (.brf/.brl), the key signatures only indicate the number of sharps or flats (e.g. one flat), not whether the key is major or minor. When translating these scores to LilyPond, the web UI pops up a key signature mode selection dialog to let you choose between the indicated major key (e.g., F Major) and its relative minor (e.g., D Minor), equivalent to the CLI's --key-mode argument. This ensures that the generated LilyPond file uses the correct key spelling and mode.
This project is written by a blind composer who uses a BrailleNotetaker to compose and LilyPond for engraving and MIDI output. The developer previously contributed to Freedots, a project that converted MusicXML to braille. DottedNotes works in the reverse direction and is designed from the ground up for accessibility: all output is plain text, all error messages are screen-reader friendly, and no sighted assistance is required at any step.
pytest tests/
pytest tests/ --cov=dottednotes --cov-report=term-missingContributions are welcome, especially from blind or low-vision developers and musicians who use braille notation. Please open an issue before starting significant work so we can coordinate.
See docs/development.md for an architecture walkthrough of the BRF/BRL → LilyPond pipeline, a worked example tracing one measure through it end to end, and how to add a new BANA symbol or domain model class. See CONTRIBUTING.md for issue/PR conventions, dev environment setup, and dedicated guidance for blind and low-vision contributors.
All CLI output, error messages, and documentation must be screen-reader friendly: plain text, no ASCII art, no progress bars, no visual-only feedback.
GPL-3.0. See LICENSE.