.cesure format

Open specification for .cesure — the native tablature format of Cesure. This is version 2. Version 1 is frozen and described in v1.md; every reader keeps reading it.

What it is

.cesure is a UTF-8 text file for tablature and scores. It is:

Why not MusicXML, MIDI, or AlphaTex?

FormatHuman-readableTablatureRound-trip losslessVersioned
.cesure✓✓✓✓
MusicXML✓~~✗
AlphaTex✓✓~✗
MIDI✗✗✗✗
GP5✗✓~✗

The notes of a .cesure file are written in a notation modelled on the AlphaTex of alphaTab, but defined and frozen here: it does not follow alphaTab's evolutions, and what this page says is the whole of it.

A first file

{"version":2,"score":{"title":"Simple Example","tempo":96,"tracks":[{"measures":[
  {"id":"p1","staves":[{"voices":["0.1.4 1.2.4 (0.3 2.4).4 r.4"]}]},
  {"id":"p2","numerator":3,"staves":[{"voices":["3.1.8{d} 2.1.16 0.1.2","0.6.2{d}"]}]}
]}]}}

Two measures on a standard six-string guitar: the first in 4/4 (the default), four quarter beats — open high E, first fret on the B string, a chord, a rest; the second in 3/4, a melody over a bass held for the whole measure (a second voice).

File structure

score.cesure
└── (UTF-8 JSON)
    ├── version   2
    └── score     Score
        ├── tracks[]         Track
        │   └── measures[]   Measure  (id, time signature)
        │       └── staves[] Staff
        │           └── voices[]  string in the compact notation
        ├── tempoChanges[]   TempoChange  (by measure id)
        └── repeats{}        Repeat       (by measure id)

A writer omits every field that holds its default, and a reader gives an absent field its default. Measure i of every track is the same column of the piece: same id, played at the same time.

Root

FieldTypeRequiredDescription
versioninteger✓Format version: 2. A reader refuses a higher one.
scoreScore✓The piece.

The root holds nothing else: a key placed there belongs to nothing a reader could keep.


Score

FieldTypeDefaultDescription
titlestring"Untitled"Title of the piece.
artiststring""Composer or performer.
albumstring""Album, when the piece comes from one.
temponumber120Initial tempo in quarter notes per minute (20 to 400), whatever the time signature, in force until the first tempo change. Whole or not: 92.5 is kept, played and written back as it is.
tempoChangesTempoChange[][]Tempo changes. Empty = constant tempo.
repeats{id: Repeat}{}Repeat signs, keyed by the id of the measure that carries them. See Repeat.
licensestring""License of the published work (e.g. Public Domain, CC BY 4.0). Empty = the file says nothing.
attributionstring""Attribution of the edition — year and source URL included when applicable. It travels with the file because CC-BY requires it when a copy is kept.
tracksTrack[][]The instrument tracks.

TempoChange

FieldTypeRequiredDescription
measurestring✓Id of the measure at whose start the tempo takes effect.
temponumber✓Tempo from that measure on, quarter-note beats per minute (20 to 400), whole or not.

Tempo belongs to the piece, not to a track, and is always counted in quarter notes per minute: a 6/8 felt at 60 dotted quarters per minute has tempo 90. A change is named by the id of its measure, so it stays on that measure when measures are inserted or deleted before it. A change naming no measure of the piece is ignored, and not written back: a version 1 file with a change past its last measure loses it at its first save in version 2, as is a change whose tempo is not positive, which applies to nothing.


Repeat

The repeat signs of one measure — of its column, on every track. A repeat keyed by an id no measure carries is ignored, and not written back.

FieldTypeDefaultDescription
startbooleanfalseA repeated section opens at the start of the measure (|:).
endinteger—A repeated section closes at the end of the measure (:|): how many times the section is played in all, 2 to 99. Absent = no close.
endingsinteger[][]Alternative ending (volta): the passes on which the measure is played — [1] first ending, [2] second, [1, 2] both. A bracket longer than one measure repeats its passes on each of them.

Playback unrolls the repeats as in Guitar Pro and MuseScore, from the structure of the whole piece. A section opens at a start, or right after the previous section, or at the first measure. Its body runs to its close or to its first bracket. Consecutive measures with the same endings make one bracket, a close ends it, and the brackets run on up to the first that does not close. On pass p the body is played, then the brackets whose endings hold p; a bracket that closes sends playback back to the start of the body while passes remain. So a [3. after a section played twice is never played. A bracket carrying a start after other brackets opens the next section: it is also an ending of its own section, and the start of the next section's body, when its own section still waits for every pass it names; otherwise it is the first ending of a next section with no body. A section that no close repeats is played once, in order, its endings not read. Da capo, dal segno, coda and fine are not part of the format yet.

A complete example, |: A [1. B C :| [2. |: D :| E — a first ending two measures long, and a second ending that opens a repeat of its own, played A B C A D D E:

"repeats":{"p1":{"start":true},"p2":{"endings":[1]},"p3":{"end":2,"endings":[1]},
           "p4":{"start":true,"end":2,"endings":[2]}}

Limit, which no editing gesture makes audible: a section with no body whose first ending names only passes the section before it still waits for reads as an ending of that section.


Track

FieldTypeDefaultDescription
namestring"Guitar"Track name.
tuningNote[]standard tuningOpen strings from string 1 (the highest) to string N (the lowest); its length is the number of strings. Standard 6-string: E4 B3 G3 D3 A2 E2.
capointeger0Capo position in frets. Fret numbers stay relative to the capo; only the sounding pitch moves.
volumenumber1Volume in the mix, 0..1.
instrumentstring \nullnullTimbre: GUITAR, BASS or PIANO. Absent is not "guitar": the file says nothing, and a player falls back to its own preference.
measuresMeasure[][]The measures of the track, in order.

Measure

Nothing carries over from one measure to the next: a measure without numerator and denominator is in 4/4, not in the time signature of the measure before it. Write them on every measure that is not in 4/4.

FieldTypeDefaultDescription
idstring—Stable id of the measure's column, at most 64 characters. See Measure ids.
numeratorinteger4Beats per measure (time signature numerator), 1 to 128. Cesure refuses a file that writes a value outside the range, naming it.
denominatorinteger4Note value of a beat: 1, 2, 4, 8, 16 or 32.
stavesStaff[]—The staves of the track in this measure, top to bottom. Cesure writes one.

Staff

FieldTypeRequiredDescription
voicesstring[]✓The voices of the staff, each in the compact notation and with its own rhythm: the first, then a second one (a bass under a melody) when there is one. Cesure writes at most two.

The voices of a staff are played at the same time, each over the whole measure: each should fill its time signature exactly. A voice too short or too long is shown as a wrong measure, not refused. A voice with no beat is written as an empty string.

A pickup measure (anacrusis) is not part of the format yet: a short first measure is a wrong measure. It is reserved as an additive key of a measure.

Measure ids

An id names a measure from outside the file — a saved loop, a range an agent replaces — and survives the insertion or deletion of other measures, which a measure number does not. Ids are opaque. Measure i of every track carries the same id, no two columns share one, and an id is never reused for another column. A writer keeps the ids it read.

A measure written without an id gets one from the reader: a file with no id at all is read with positional ids, p1..pN; a measure missing among identified ones takes its column's id from another track, or one the reader derives from the file alone, so that every reader of the same file agrees.


Note

An absolute pitch, used by Track.tuning.

FieldTypeDescription
notestringPitch class: "C", "Cs", "D", "Ds", "E", "F", "Fs", "G", "Gs", "A", "As", "B" (s = sharp).
octaveintegerOctave number (scientific pitch notation): middle C is {"note":"C","octave":4}.

The compact notation

Each voice of a staff, in each measure, is one string: its beats, separated by spaces.

voice    = [ beat { " " beat } ]
beat     = content "." duration [ props ]
content  = note | "(" note { " " note } ")" | "r"
note     = fret "." string [ props ] | pitch [ props ]
fret     = integer | "x"
duration = "1" | "2" | "4" | "8" | "16" | "32" | "64"
props    = "{" prop { " " prop } "}"
prop     = name [ " " value ]
value    = integer | quoted string | "(" value { " " value } ")"
Beat propertyMeaning
dDotted: the duration × 1.5.
ddDouble-dotted: the duration × 1.75. Written alone, never with d; a beat that carries both is double-dotted.
tu 3Triplet: three beats in the time of two of the same value.
tu (n m)Tuplet: n beats in the time of m — tu (5 4) a quintuplet.
ttEvery note of the beat is tapped.
Note propertyMeaning
hHammer-on or pull-off to the next note on the same string (which one follows from the frets).
slSlide to the next note on the same string.
b (0 n)Bend of n quarter tones (b (0 4) is a whole tone), n even and read as written, negative included. Any other shape (b (0 3), b (0 4 0), be) is an error: the model holds whole semitones.
vVibrato.
lhtTapped note.
tTied to the note it continues (see above).
gGhost note: played very softly (Cesure strikes it at half the loudness of the same note played full) and drawn in parentheses, (5). A tied note is as soft as the note it continues.
ba nA bend amplitude of n semitones kept on a note whose bend was taken off: no bend is played.
kept "…"Everything a reader keeps on that beat or note and has no property for — the keys of a version 1 file, a technique or a hand it cannot name — as percent-encoded JSON. Write it back as it is.
nhNatural harmonic: the open string touched over the note's own fret (12 an octave up, 7 an octave and a fifth, 5 two octaves); on a fret that rings none, the octave, as in alphaTab.
ah n, th n, ph n, sh n, fh nArtificial, tapped, pinched, semi and feedback harmonic: the node touched n frets above the fretted note, a whole number (ah 12 an octave up, ah 7 an octave and a fifth); ah alone is ah 0. The pitch a node rings is alphaTab's.
hand A / hand BThe note is set to the low (A) or high (B) hand of a two-hand part. Absent = derived by the reader, not stored: the simplest rule puts a note below a split point in A (Cesure's default is middle C, C4); Cesure also offers the most playable hand, worked out over the whole track. The rule and its split point are reader preferences, never part of the file, and a written hand always wins over them.

Examples: 5.3.4 (fret 5 on string 3, a quarter); (3.6 2.5 0.4).2{d} (a dotted half chord); 7.2{b (0 2)}.8 (a half-tone bend, an eighth); 5.1.8{tu 3} 7.1.8{tu 3} 8.1.8{tu 3} (a triplet of eighths); x.6.16 (a muted note).

Reserved forms

These forms are part of the grammar so that they can be added later without a new version. A reader that does not represent them keeps them as written (see below); Cesure writes none today. The grammar of a pitch, which a note by pitch and a tuning share the octave of, and of a drum note:

pitch = letter [ accidental ] octave
letter = "A" | … | "G" | "a" | … | "g"
accidental = "#" | "##" | "x" | "b" | "bb"
octave = [ "-" ] integer
drum = "p" integer

A pitch is the sounding pitch, as a tuning is (scientific pitch notation: middle C is C4); ## and x are a double sharp, bb a double flat, so C##4, Cx4 and Ebb4 are all D4. A drum note takes a place among the notes of a beat or a chord, with its own properties, and a beat holding only such notes is silent for a reader that does not represent them.

FormMeaning
{lyrics "text"} on a beatThe syllable sung on that beat.
{ch "Am7"} on a beatThe chord symbol shown above that beat.
a drum note, p42, (p36 p42).8A drum or percussion strike, by its General MIDI key from p0 to p127 (p36 a bass drum, p42 a closed hi-hat). A key past 127 is an error.
Staff past the first, voices past the secondMore staves and voices than Cesure shows.

What a reader does not understand, it keeps

A reader must keep what it does not recognise and write it back unchanged, at the place it read it — in the skeleton as in the notation:

What cannot be kept is refused: an unknown duration, an unknown pitch class in a tuning, or a beat or note the grammar cannot read, or a document whose JSON nests more than 64 levels deep. The reader then refuses the file by naming what it could not read — guessing would change the rhythm or the tuning.

This is what lets one account use an older and a newer build at once, and an agent add what a build does not know yet, without either ever silently losing a part of the piece.

For agents: working on a range of measures

Transport

A .cesure file is plain text that compresses about five times over: a server should compress it in transit (Content-Encoding: gzip or br), and a client should accept it so. The file itself is never compressed.

Versioning policy

ChangeAction
A field with a default, a beat or note property, a reserved form put to useNo new version: a reader that does not know it keeps it
A value added to an optional enum (instrument)No new version: kept by a reader that cannot name it
A new duration, a new pitch class, a removed, renamed or retyped fieldNew version, with a migration from every older one

The durations of version 2 go down to the sixty-fourth (64): version 1 stopped at the thirty-second. The tempo became a number within version 2: every file read before reads the same, and a reader that still takes it as an integer refuses only a file whose tempo is not whole.

A reader refuses a file whose version is above the ones it reads, rather than misread it. Version 1 files (v1.md) are read forever and migrated in memory; nothing writes them. A key that a version 1 file held on a beat or a note, unknown to the reader, is kept: saved in version 2, it goes into the kept property of that beat or note, its name and value unchanged — a name opening with { included: read back, a key of kept is kept exactly as the same key of the file would be. A kept whose value is not percent-encoded JSON (%+1, %FF alone, a value that is not an object) is an error, named by the reader. A key that a version 1 file held on another object keeps its name the same way, including a name version 2 gives a field of that object (measure on a tempo change, staves on a measure, repeats on the score): it is written {measure, and read back as the key measure, never as the field. A version 1 file escaped nothing and is read as written: its key {repeats is the key {repeats, saved as {{repeats.

JSON Schema

schema/v2.json (JSON Schema Draft 2020-12), served at https://cesure.app/format/schema/v2.json, describes the skeleton; the version 1 schema stays at schema/v1.json. Validate the schemas and every example with:

pip install jsonschema
python3 scripts/validate.py

Examples

Maintenance

The format is developed and regression-tested inside the Cesure application; this section documents how it stays stable (the tooling lives in the app repository, not here).

A .cesure file that a user saved must open identically forever. Before changing the model or the notation:

  1. Additive (a field with a default, a property, a reserved form put to use) → no new version. Update this README, schema/v2.json and CHANGELOG.md, and add the addition to a file of the corpus so that it is actually exercised.
  2. Structural (see the table above) → raise CURRENT_VERSION in CesureSerializer, add the migration, and keep a frozen file of every older version: it must still load.
  3. Never edit a frozen file to make a test pass: an old file that no longer reads the same is the bug.
  4. Run the guards — all in :score: ./gradlew :score:jvmTest. They hold the schema against the model, the round trip of every example and catalog piece, the frozen v1 corpus, the refusal of a newer version, and the keeping of unknown keys, properties and values.

The application behind the format

.cesure is written and read by Cesure — a guitar tablature editor and practice toolkit that runs in the browser, on Android and on iOS. It imports Guitar Pro, MusicXML, MIDI and AlphaTex, transcribes audio to tablature, and plays scores back with a tuner, a metronome and a piano roll. The schema in this repository is checked against its model on every build, so a field added on one side and not the other fails the build.

License

This specification is released under the MIT License.