16000 lines
641 KiB
TeX
16000 lines
641 KiB
TeX
% !TEX program = xelatex
|
|
%
|
|
% Epiphany --- Core Specification
|
|
% A specification document for the Epiphany music notation platform.
|
|
% Compile with XeLaTeX.
|
|
|
|
\documentclass[11pt,letterpaper]{report}
|
|
|
|
% ---------------------------------------------------------------------------
|
|
% Packages
|
|
% ---------------------------------------------------------------------------
|
|
\usepackage{fontspec}
|
|
\usepackage{geometry}
|
|
\geometry{
|
|
letterpaper,
|
|
top=1.05in,
|
|
bottom=1.05in,
|
|
left=1.15in,
|
|
right=1.15in,
|
|
headheight=15pt
|
|
}
|
|
|
|
\usepackage[english]{babel}
|
|
\usepackage{microtype}
|
|
\usepackage{parskip}
|
|
\usepackage{xcolor}
|
|
\usepackage{hyperref}
|
|
\usepackage{enumitem}
|
|
\usepackage{titlesec}
|
|
\usepackage{fancyhdr}
|
|
\usepackage{booktabs}
|
|
\usepackage{array}
|
|
\usepackage{longtable}
|
|
\usepackage{listings}
|
|
\usepackage{amsmath}
|
|
\usepackage{amssymb}
|
|
\usepackage{graphicx}
|
|
\usepackage{caption}
|
|
\usepackage{subcaption}
|
|
\usepackage{tikz}
|
|
\usepackage{float}
|
|
\usepackage{tcolorbox}
|
|
\tcbuselibrary{breakable, skins}
|
|
|
|
% ---------------------------------------------------------------------------
|
|
% Color palette: refined classical-modern
|
|
% - Deep teal as the primary structural color
|
|
% - Antique gold as the accent and titling color
|
|
% - Warm parchment-tinted neutrals
|
|
% ---------------------------------------------------------------------------
|
|
\definecolor{epiphanyteal}{HTML}{1A4044} % deep teal --- primary
|
|
\definecolor{epiphanygold}{HTML}{8E6E2E} % antique gold --- accent
|
|
\definecolor{epiphanyink}{HTML}{1F1B16} % warm near-black for body text
|
|
\definecolor{epiphanyslate}{HTML}{6B6660} % warm slate gray
|
|
\definecolor{epiphanycream}{HTML}{F8F4ED} % parchment cream
|
|
\definecolor{epiphanymist}{HTML}{ECE8E0} % slightly deeper parchment
|
|
\definecolor{epiphanycode}{HTML}{2A2520} % warm dark for code
|
|
\definecolor{epiphanycrimson}{HTML}{7A2424} % deep crimson for warnings
|
|
|
|
\hypersetup{
|
|
colorlinks=true,
|
|
linkcolor=epiphanyteal,
|
|
citecolor=epiphanyteal,
|
|
urlcolor=epiphanygold,
|
|
pdftitle={Epiphany --- Core Specification},
|
|
pdfauthor={The Epiphany Project},
|
|
pdfsubject={Core specification for the Epiphany music notation platform},
|
|
pdfkeywords={music notation, engraving, FOSS, Rust, microtonality, CRDT},
|
|
bookmarksnumbered=true,
|
|
bookmarksopen=true
|
|
}
|
|
|
|
% ---------------------------------------------------------------------------
|
|
% Typography: classical serif with refined sans for chapter titles
|
|
% - TeX Gyre Pagella: Palatino-derived; warm, classical, highly legible
|
|
% - TeX Gyre Heros: clean grotesque for sans elements
|
|
% - TeX Gyre Cursor: monospace for code
|
|
% ---------------------------------------------------------------------------
|
|
\setmainfont{TeX Gyre Pagella}[
|
|
Numbers={OldStyle, Proportional},
|
|
Ligatures={TeX, Common}
|
|
]
|
|
\setsansfont{TeX Gyre Heros}[
|
|
Scale=0.94,
|
|
Ligatures={TeX, Common}
|
|
]
|
|
\setmonofont{TeX Gyre Cursor}[
|
|
Scale=0.88,
|
|
Ligatures={TeX}
|
|
]
|
|
|
|
% Lining figures for tables (oldstyle for body, lining for technical)
|
|
\newcommand{\tablenums}[1]{{\addfontfeatures{Numbers={Lining,Tabular}}#1}}
|
|
|
|
% A display-weight version of Pagella for the title page
|
|
\newfontfamily\titlefont{TeX Gyre Pagella}[
|
|
Numbers={OldStyle},
|
|
Ligatures={TeX, Common}
|
|
]
|
|
|
|
% Small-caps helpers
|
|
\newcommand{\sectionsc}[1]{{\addfontfeatures{Letters=SmallCaps}#1}}
|
|
|
|
% ---------------------------------------------------------------------------
|
|
% Section styling
|
|
% ---------------------------------------------------------------------------
|
|
\titleformat{\chapter}[display]
|
|
{\normalfont\filright}
|
|
{\raggedright\color{epiphanygold}\fontsize{14pt}{16pt}\selectfont
|
|
\scshape Chapter\ \thechapter}
|
|
{16pt}
|
|
{\raggedright\color{epiphanyteal}\fontsize{32pt}{36pt}\selectfont\bfseries}
|
|
[\vspace{4pt}{\color{epiphanygold}\rule{2in}{0.6pt}}]
|
|
\titlespacing*{\chapter}{0pt}{-20pt}{30pt}
|
|
|
|
\titleformat{\section}
|
|
{\normalfont\Large\bfseries\color{epiphanyteal}}
|
|
{\color{epiphanygold}\thesection}{1em}{}
|
|
\titleformat{\subsection}
|
|
{\normalfont\large\bfseries\color{epiphanyteal}}
|
|
{\color{epiphanygold}\thesubsection}{1em}{}
|
|
\titleformat{\subsubsection}
|
|
{\normalfont\normalsize\bfseries\color{epiphanyink}}
|
|
{\thesubsubsection}{1em}{}
|
|
|
|
% ---------------------------------------------------------------------------
|
|
% Headers and footers
|
|
% ---------------------------------------------------------------------------
|
|
\pagestyle{fancy}
|
|
\fancyhf{}
|
|
\renewcommand{\headrulewidth}{0pt}
|
|
\renewcommand{\footrulewidth}{0pt}
|
|
\fancyhead[L]{\small\scshape\color{epiphanyslate}Epiphany}
|
|
\fancyhead[R]{\small\itshape\color{epiphanyslate}\leftmark}
|
|
\fancyfoot[C]{\small\color{epiphanyslate}\thepage}
|
|
|
|
% Subtle ornamental rule under the page header
|
|
\renewcommand{\headrule}{
|
|
\color{epiphanygold!50}\hrule width\headwidth height 0.4pt
|
|
\vspace{1pt}
|
|
\color{epiphanygold!30}\hrule width\headwidth height 0.2pt
|
|
}
|
|
|
|
% ---------------------------------------------------------------------------
|
|
% Code listing style
|
|
% ---------------------------------------------------------------------------
|
|
\lstdefinelanguage{Rust}{
|
|
keywords={fn,let,mut,pub,struct,enum,impl,trait,for,in,if,else,match,return,
|
|
use,mod,crate,self,Self,as,where,move,async,await,const,static,
|
|
ref,type,unsafe,extern,dyn,box,break,continue,loop,while},
|
|
keywordstyle=\color{epiphanyteal}\bfseries,
|
|
ndkeywords={i8,i16,i32,i64,i128,u8,u16,u32,u64,u128,f32,f64,bool,char,str,
|
|
String,Vec,Option,Result,Box,Rc,Arc,HashMap,BTreeMap,
|
|
NonZeroU16,NonZeroU32,NonZeroU64,Duration,Timestamp},
|
|
ndkeywordstyle=\color{epiphanygold}\bfseries,
|
|
sensitive=true,
|
|
comment=[l]{//},
|
|
morecomment=[s]{/*}{*/},
|
|
commentstyle=\color{epiphanyslate}\itshape,
|
|
stringstyle=\color{epiphanycrimson},
|
|
morestring=[b]",
|
|
morestring=[b]'
|
|
}
|
|
|
|
\lstset{
|
|
basicstyle=\ttfamily\small\color{epiphanycode},
|
|
backgroundcolor=\color{epiphanycream},
|
|
frame=leftline,
|
|
rulecolor=\color{epiphanygold!60},
|
|
framesep=8pt,
|
|
framerule=1.5pt,
|
|
xleftmargin=10pt,
|
|
xrightmargin=4pt,
|
|
breaklines=true,
|
|
showstringspaces=false,
|
|
numberstyle=\tiny\color{epiphanyslate},
|
|
numbersep=10pt,
|
|
captionpos=b,
|
|
aboveskip=10pt,
|
|
belowskip=10pt,
|
|
language=Rust
|
|
}
|
|
|
|
% ---------------------------------------------------------------------------
|
|
% Custom environments
|
|
% ---------------------------------------------------------------------------
|
|
|
|
% Open question or unresolved design issue
|
|
\newtcolorbox{openquestion}[1][]{
|
|
enhanced, breakable,
|
|
colback=epiphanymist, colframe=epiphanycrimson,
|
|
fonttitle=\bfseries\color{white},
|
|
title={\scshape\hspace{2pt}Open Question},
|
|
coltitle=white,
|
|
colbacktitle=epiphanycrimson,
|
|
arc=1pt, boxrule=0pt,
|
|
leftrule=2pt,
|
|
left=10pt, right=10pt, top=8pt, bottom=8pt,
|
|
attach boxed title to top left={xshift=0pt, yshift=0pt},
|
|
boxed title style={
|
|
arc=0pt, sharp corners, boxrule=0pt,
|
|
left=6pt, right=8pt, top=2pt, bottom=2pt,
|
|
},
|
|
#1
|
|
}
|
|
|
|
% Design rationale
|
|
\newtcolorbox{rationale}[1][]{
|
|
enhanced, breakable,
|
|
colback=epiphanymist, colframe=epiphanyteal,
|
|
fonttitle=\bfseries\color{white},
|
|
title={\scshape\hspace{2pt}Rationale},
|
|
coltitle=white,
|
|
colbacktitle=epiphanyteal,
|
|
arc=1pt, boxrule=0pt,
|
|
leftrule=2pt,
|
|
left=10pt, right=10pt, top=8pt, bottom=8pt,
|
|
attach boxed title to top left={xshift=0pt, yshift=0pt},
|
|
boxed title style={
|
|
arc=0pt, sharp corners, boxrule=0pt,
|
|
left=6pt, right=8pt, top=2pt, bottom=2pt,
|
|
},
|
|
#1
|
|
}
|
|
|
|
% Normative requirement
|
|
%
|
|
% Numbered within chapter (this document has chapters) so that every
|
|
% requirement carries a unique, human-citable number. tcolorbox's own
|
|
% "auto counter, number within=..." keys are not used: their counter-step
|
|
% for \label purposes runs inside an internal \sbox ("phantom") whose local
|
|
% \@currentlabel assignment is discarded when that box closes, so a plain
|
|
% \label written in the box body (as every requirement's is, and per this
|
|
% pass's rules must remain) binds to the counter of whatever sectioning
|
|
% unit last really stepped -- reproducing the very bug this counter is
|
|
% meant to fix. Stepping a plain counter via a `code=` key runs directly in
|
|
% the environment's own group, so \@currentlabel sticks for the body's
|
|
% \label to see.
|
|
\newcounter{requirement}[chapter]
|
|
\renewcommand{\therequirement}{\thechapter.\arabic{requirement}}
|
|
\newtcolorbox{requirement}[1][]{
|
|
enhanced, breakable,
|
|
colback=white, colframe=epiphanygold,
|
|
fonttitle=\bfseries\color{white},
|
|
code={\refstepcounter{requirement}},
|
|
title={\scshape\hspace{2pt}Requirement~\therequirement},
|
|
coltitle=white,
|
|
colbacktitle=epiphanygold,
|
|
arc=1pt, boxrule=0pt,
|
|
leftrule=2pt,
|
|
left=10pt, right=10pt, top=8pt, bottom=8pt,
|
|
attach boxed title to top left={xshift=0pt, yshift=0pt},
|
|
boxed title style={
|
|
arc=0pt, sharp corners, boxrule=0pt,
|
|
left=6pt, right=8pt, top=2pt, bottom=2pt,
|
|
},
|
|
#1
|
|
}
|
|
|
|
% Non-goal: explicitly out of scope
|
|
\newtcolorbox{nongoal}[1][]{
|
|
enhanced, breakable,
|
|
colback=epiphanymist, colframe=epiphanyslate,
|
|
fonttitle=\bfseries\color{white},
|
|
title={\scshape\hspace{2pt}Non-Goal},
|
|
coltitle=white,
|
|
colbacktitle=epiphanyslate,
|
|
arc=1pt, boxrule=0pt,
|
|
leftrule=2pt,
|
|
left=10pt, right=10pt, top=8pt, bottom=8pt,
|
|
attach boxed title to top left={xshift=0pt, yshift=0pt},
|
|
boxed title style={
|
|
arc=0pt, sharp corners, boxrule=0pt,
|
|
left=6pt, right=8pt, top=2pt, bottom=2pt,
|
|
},
|
|
#1
|
|
}
|
|
|
|
% Normative-language helpers (RFC 2119 style). MUST NOT and SHOULD NOT
|
|
% are emitted as two adjacent textbf groups separated by a regular
|
|
% control space; this prevents line breaks between the words while
|
|
% still producing a standard ASCII space character in the PDF that
|
|
% pdftotext extracts cleanly. The \nobreak prevents the line-break
|
|
% the regular space would otherwise allow.
|
|
\newcommand{\MUST}{\textbf{MUST}}
|
|
\newcommand{\MUSTNOT}{\textbf{MUST}\nobreak\ \textbf{NOT}}
|
|
\newcommand{\SHOULD}{\textbf{SHOULD}}
|
|
\newcommand{\SHOULDNOT}{\textbf{SHOULD}\nobreak\ \textbf{NOT}}
|
|
\newcommand{\MAY}{\textbf{MAY}}
|
|
|
|
% List spacing
|
|
\setlist[itemize]{topsep=2pt, itemsep=3pt, parsep=0pt}
|
|
\setlist[enumerate]{topsep=2pt, itemsep=3pt, parsep=0pt}
|
|
\setlist[description]{topsep=2pt, itemsep=5pt, parsep=0pt}
|
|
|
|
% Body text color
|
|
\AtBeginDocument{\color{epiphanyink}}
|
|
|
|
% ---------------------------------------------------------------------------
|
|
% Document
|
|
% ---------------------------------------------------------------------------
|
|
\begin{document}
|
|
|
|
% ----- Title page -----
|
|
\begin{titlepage}
|
|
\thispagestyle{empty}
|
|
\begin{tikzpicture}[remember picture, overlay]
|
|
% Top ornamental rule
|
|
\draw[epiphanygold, line width=1.2pt]
|
|
([yshift=-0.85in, xshift=1.15in]current page.north west) --
|
|
([yshift=-0.85in, xshift=-1.15in]current page.north east);
|
|
\draw[epiphanygold, line width=0.4pt]
|
|
([yshift=-0.93in, xshift=1.15in]current page.north west) --
|
|
([yshift=-0.93in, xshift=-1.15in]current page.north east);
|
|
|
|
% Bottom ornamental rule
|
|
\draw[epiphanygold, line width=0.4pt]
|
|
([yshift=0.93in, xshift=1.15in]current page.south west) --
|
|
([yshift=0.93in, xshift=-1.15in]current page.south east);
|
|
\draw[epiphanygold, line width=1.2pt]
|
|
([yshift=0.85in, xshift=1.15in]current page.south west) --
|
|
([yshift=0.85in, xshift=-1.15in]current page.south east);
|
|
\end{tikzpicture}
|
|
|
|
\centering
|
|
\vspace*{0.7in}
|
|
|
|
% Small superscript framing
|
|
{\color{epiphanyslate}\scshape\fontsize{11pt}{13pt}\selectfont
|
|
A music notation platform\par}
|
|
\vspace{0.3in}
|
|
|
|
% The main title --- Epiphany
|
|
{\titlefont\fontsize{72pt}{76pt}\selectfont
|
|
\color{epiphanyteal}
|
|
Epiphany\par}
|
|
|
|
\vspace{0.12in}
|
|
|
|
% Subtle ornamental separator
|
|
\begin{tikzpicture}
|
|
\draw[epiphanygold, line width=0.5pt] (-1.3,0) -- (-0.3,0);
|
|
\node at (0,0) {\color{epiphanygold}\large$\diamond$};
|
|
\draw[epiphanygold, line width=0.5pt] (0.3,0) -- (1.3,0);
|
|
\end{tikzpicture}
|
|
|
|
\vspace{0.2in}
|
|
|
|
{\color{epiphanyink}\fontsize{18pt}{22pt}\selectfont
|
|
\itshape Core Specification\par}
|
|
|
|
\vspace{0.9in}
|
|
|
|
% Tagline
|
|
\begin{minipage}{0.78\textwidth}
|
|
\centering
|
|
\color{epiphanyink}
|
|
\fontsize{12pt}{17pt}\selectfont
|
|
A foundational specification for an open, Rust-native music
|
|
notation platform: pitch and time primitives, the score graph,
|
|
the layout intermediate representation, the file format, and
|
|
the constraint-solver interface.
|
|
\end{minipage}
|
|
|
|
\vspace{0.7in}
|
|
|
|
% Status banner
|
|
\begin{tikzpicture}
|
|
\node[
|
|
draw=epiphanygold,
|
|
line width=0.5pt,
|
|
fill=epiphanycream,
|
|
inner sep=10pt,
|
|
minimum width=3.5in,
|
|
] (status) {
|
|
\color{epiphanyteal}
|
|
\scshape\fontsize{10pt}{12pt}\selectfont
|
|
Working Draft \quad$\cdot$\quad \today
|
|
};
|
|
\end{tikzpicture}
|
|
|
|
\vspace{0.5in}
|
|
|
|
\begin{minipage}{0.72\textwidth}
|
|
\centering
|
|
\itshape\color{epiphanyslate}
|
|
\footnotesize
|
|
This specification defines the foundational layer of the
|
|
Epiphany platform: the data model, engraving engine, and file
|
|
format on which all higher-level features are built. The user
|
|
interface, audio engine, plugin runtime, and platform
|
|
integrations are layered above the core and are addressed in
|
|
separate specifications.
|
|
\end{minipage}
|
|
|
|
\vfill
|
|
\end{titlepage}
|
|
|
|
% ----- Front matter -----
|
|
\pagenumbering{roman}
|
|
|
|
\chapter*{Status of This Document}
|
|
\addcontentsline{toc}{chapter}{Status of This Document}
|
|
|
|
This draft is \textbf{structurally stabilized}. Substantive
|
|
review passes through the core type model, graph invariants,
|
|
concurrent reduction semantics, file-format atomicity, solver
|
|
conformance, and the determinism contract have been completed.
|
|
The architecture is no longer under active redesign; further
|
|
revisions are expected to be additive (companion specifications,
|
|
normative algorithm choices for the open canonical algorithms,
|
|
extension registry contents) rather than structural.
|
|
|
|
The remaining blockers to full conformance are companion
|
|
specifications and the canonical algorithms identified in
|
|
Appendix~\ref{app:determinism}
|
|
Section~\ref{sec:det:open}:
|
|
|
|
\begin{itemize}
|
|
\item Several companion specifications referenced throughout
|
|
the document have not yet been delivered:
|
|
Appendix~\ref{app:deferred} catalogs them and distinguishes
|
|
them from accidentally missing content.
|
|
\item Two algorithms affect canonical state and have not
|
|
yet received one of the dispositions in
|
|
Appendix~\ref{app:determinism}
|
|
Section~\ref{sec:det:open}: the tempo curve
|
|
integration algorithm and the
|
|
\texttt{wallclock\_to\_musical} root-finding algorithm.
|
|
Cross-implementation byte equality of canonical score state
|
|
\MUSTNOT{} be claimed for outputs derived from these
|
|
algorithms until they are normatively specified or
|
|
profile-declared (Appendix~\ref{app:determinism}
|
|
Section~\ref{sec:det:layers}). The spelling pre-pass and the
|
|
notational-decomposition algorithm received the
|
|
profile-declared-by-versioned-identifier disposition in
|
|
Pass~12
|
|
(Requirements~\ref{req:pitch:spelling-algorithm}
|
|
and~\ref{req:time:decomposition-algorithm}).
|
|
\item Extension registry catalogs are referenced by typed
|
|
identifier but not enumerated here; they are delivered as
|
|
separate, versioned registry documents.
|
|
\end{itemize}
|
|
|
|
What conformance can be established now:
|
|
|
|
\begin{itemize}
|
|
\item The core data model can be structurally implemented and
|
|
tested against the invariants in
|
|
Chapter~\ref{ch:graph} Section~\ref{sec:graph:invariants}.
|
|
\item The operation framework
|
|
(Chapter~\ref{ch:semops}) can be implemented end-to-end
|
|
against the canonical reduction algorithm.
|
|
\item The file format
|
|
(Chapter~\ref{ch:format}) can be implemented atomically and
|
|
crash-recoverably against the fixed prelude and superblock
|
|
protocol.
|
|
\item Solver implementations can be developed against the
|
|
interface contract (Chapter~\ref{ch:solver}); reference-suite
|
|
conformance becomes checkable when the Reference Suite
|
|
companion is delivered.
|
|
\end{itemize}
|
|
|
|
The normative keywords \MUST, \MUSTNOT, \SHOULD, \SHOULDNOT, and \MAY{} are
|
|
to be interpreted as described in RFC 2119 when they appear in all
|
|
capital letters.
|
|
|
|
\chapter*{Conventions}
|
|
\addcontentsline{toc}{chapter}{Conventions}
|
|
|
|
\begin{description}
|
|
\item[Code samples] Rust syntax is used for type signatures, struct
|
|
definitions, and algorithmic illustrations. Code samples are
|
|
illustrative unless explicitly labeled normative.
|
|
\item[Normative requirements] Statements containing \MUST, \MUSTNOT,
|
|
\SHOULD, \SHOULDNOT, or \MAY{} are normative. Conforming
|
|
implementations must respect them.
|
|
\item[Non-normative content] Discussion, rationale, examples, and
|
|
diagrams are non-normative unless explicitly stated.
|
|
\item[Forward references] The specification builds bottom-up. Later
|
|
chapters depend on earlier ones; forward references are minimized but
|
|
occasionally unavoidable and are marked when they occur.
|
|
\end{description}
|
|
|
|
\tableofcontents
|
|
|
|
\clearpage
|
|
|
|
\chapter*{Reading Guide}
|
|
\addcontentsline{toc}{chapter}{Reading Guide}
|
|
|
|
This document is long. The following tables summarize the most
|
|
load-bearing concepts and where they are defined. They are
|
|
non-normative; the chapters they reference are authoritative.
|
|
|
|
\section*{Score Graph Identity Hierarchy}
|
|
|
|
The score graph is organized as a containment hierarchy of
|
|
identified objects. Each level introduces a distinct concept of
|
|
identity.
|
|
|
|
\begin{longtable}{p{2.8cm} p{2.6cm} p{8.2cm}}
|
|
\toprule
|
|
\textbf{Level} & \textbf{Identifier} & \textbf{Role} \\
|
|
\midrule
|
|
\endhead
|
|
Score & --- & Document root. Carries metadata, instruments,
|
|
global staves, staff groups, tuning context, tempo map,
|
|
canvas. \\
|
|
Canvas & --- & Spatial root. Partitioned into regions. \\
|
|
Region & \texttt{RegionId} & Spatial-temporal container.
|
|
Declares time model (metric/proportional/aleatoric) and
|
|
content model (staff-based/free-graphic/hybrid). \\
|
|
Staff & \texttt{StaffId} & Global, abstract staff identity
|
|
persisting across regions. Declared once at score level. \\
|
|
Staff instance & \texttt{StaffInstanceId} & Region-local
|
|
manifestation of a staff. One \texttt{Staff} may have
|
|
multiple instances across regions. \\
|
|
Voice & \texttt{VoiceId} & Polyphonic line within a staff
|
|
instance. Carries \texttt{VoiceOrigin}: user-declared,
|
|
imported, or system-promoted. \\
|
|
Event & \texttt{EventId} & Rhythmic atom: pitched, unpitched,
|
|
rest, indeterminate, trajectory, graphic, cue. Lives in the
|
|
score's event arena. \\
|
|
Identified pitch & \texttt{PitchId} & Stable identity for a
|
|
pitch within an event. Enables spelling attachments,
|
|
respelling, stable tie pairing through chord reordering. \\
|
|
\bottomrule
|
|
\end{longtable}
|
|
|
|
\section*{Canonical versus Non-Canonical}
|
|
|
|
Many file-format and runtime objects exist in two forms.
|
|
Canonical objects define the document; non-canonical objects
|
|
accelerate access but never affect canonical state.
|
|
|
|
\begin{longtable}{p{4.4cm} p{4.0cm} p{5.2cm}}
|
|
\toprule
|
|
\textbf{Object} & \textbf{Status} & \textbf{Notes} \\
|
|
\midrule
|
|
\endhead
|
|
Operation envelope set & Canonical &
|
|
The replicated grow-only CRDT defining the document
|
|
(Chapter~\ref{ch:semops}). \\
|
|
Operation envelope block & Canonical (storage) &
|
|
Soft 1\,MiB blocks on disk
|
|
(Chapter~\ref{ch:format}). \\
|
|
Operation index & Non-canonical &
|
|
Acceleration of operation-id lookup. Rebuildable. \\
|
|
Canonical-base snapshot & Canonical &
|
|
The manifest's \texttt{canonical\_base}: at most one active,
|
|
with declared causal frontier and reduction algorithm
|
|
version. Required for pruned documents. \\
|
|
Acceleration snapshot & Non-canonical &
|
|
Listed in \texttt{acceleration\_snapshots}. Cache only.
|
|
Discardable. \\
|
|
Manifest & Canonical (index) &
|
|
Table of roots; itself a chunk; referenced from the active
|
|
superblock. \\
|
|
Materialized score graph & Derivative &
|
|
Deterministic reduction of the operation set under the
|
|
active reduction algorithm. \\
|
|
Layout cache, glyph cache, render artifacts &
|
|
Non-canonical & Always discardable
|
|
(Section~\ref{sec:semops:caches}). \\
|
|
Conflict registry & Canonical &
|
|
Stable, addressable, user-visible records of operations
|
|
that could not apply cleanly. \\
|
|
\bottomrule
|
|
\end{longtable}
|
|
|
|
\section*{Operation Lifecycle}
|
|
|
|
Every operation traverses four phases between user intent and
|
|
its visible effect.
|
|
|
|
\begin{longtable}{p{2.2cm} p{12.5cm}}
|
|
\toprule
|
|
\textbf{Phase} & \textbf{Description} \\
|
|
\midrule
|
|
\endhead
|
|
Prepare & Local UI command validates enough to construct a
|
|
well-formed envelope. Advisory only; failure here means the
|
|
UI rejects the user's action. Does not bind canonical
|
|
semantics. \\
|
|
Commit & The envelope is added to the replicated operation
|
|
set. After commit, the operation is part of canonical state
|
|
and \MUSTNOT{} be discarded outside the pruning protocol. \\
|
|
Reduce & During materialization, the operation is processed
|
|
by the canonical reduction algorithm against working state.
|
|
Deterministic: every replica with the same operation set
|
|
produces the same output. \\
|
|
Report & The reduction produces a deterministic
|
|
\texttt{OperationEffect}: \texttt{Applied},
|
|
\texttt{AppliedWithRepair}, \texttt{Conflicted},
|
|
\texttt{TombstonedTarget}, or \texttt{NoOp}. Effects are
|
|
visible graph facts. \\
|
|
\bottomrule
|
|
\end{longtable}
|
|
|
|
\section*{Determinism Layers}
|
|
|
|
The specification distinguishes five layers of determinism.
|
|
Conflating them is the most common source of impossible
|
|
requirements.
|
|
|
|
\begin{longtable}{p{3.8cm} p{10.0cm}}
|
|
\toprule
|
|
\textbf{Layer} & \textbf{Contract} \\
|
|
\midrule
|
|
\endhead
|
|
Canonical score determinism &
|
|
Identical operation sets produce identical materialized
|
|
score states across all conforming implementations, subject
|
|
to all canonical algorithms having received the dispositions
|
|
in Appendix~\ref{app:determinism}. \\
|
|
Canonical serialization &
|
|
Same canonical state $\to$ same chunk hashes, same text
|
|
projection, same canonical iteration order. \\
|
|
Layout determinism &
|
|
Within one solver implementation at a fixed version:
|
|
byte-equal output for identical input. Across
|
|
implementations: reference-suite thresholds, not byte equal. \\
|
|
Conformance determinism &
|
|
Reference-suite-based. Cross-implementation byte equality
|
|
is not required for layout. \\
|
|
Non-canonical caches &
|
|
May vary freely across runs, platforms, implementations.
|
|
\MUSTNOT{} affect canonical state. \\
|
|
\bottomrule
|
|
\end{longtable}
|
|
|
|
\section*{Solver Tiers}
|
|
|
|
Solver implementations declare a conformance tier. Tiers are
|
|
orthogonal to file-format profiles
|
|
(Chapter~\ref{ch:format}).
|
|
|
|
\begin{longtable}{p{3.0cm} p{11.0cm}}
|
|
\toprule
|
|
\textbf{Tier} & \textbf{Obligations} \\
|
|
\midrule
|
|
\endhead
|
|
Minimal &
|
|
Hard-constraint validity, within-implementation determinism,
|
|
well-formed \texttt{SolveReport}, CMN + metric regions,
|
|
Minimal reference-suite subset. Aesthetic quality may be
|
|
mediocre. \\
|
|
Standard &
|
|
Minimal $+$ Standard-tier thresholds on the full reference
|
|
suite, Measure-local invalidation under incremental solving,
|
|
all Standard constraint families. Professional engraving
|
|
quality for common-practice notation. \\
|
|
Advanced &
|
|
Standard $+$ proportional and aleatoric regions, microtonal
|
|
accidentals, extension-contributed constraints, System-local
|
|
invalidation under incremental solving. \\
|
|
\bottomrule
|
|
\end{longtable}
|
|
|
|
\section*{Chapter Map}
|
|
|
|
\begin{longtable}{p{1.6cm} p{12.4cm}}
|
|
\toprule
|
|
\textbf{Ch.} & \textbf{Content} \\
|
|
\midrule
|
|
\endhead
|
|
\ref{ch:intro} & Scope, design principles, glossary of key
|
|
terms used throughout. \\
|
|
\ref{ch:pitch} & Pitch as two intrinsic layers (scale
|
|
position and acoustic), spelling subsystem with stacked
|
|
accidentals, analytical layers. \\
|
|
\ref{ch:time} & Rational time, time anchors with
|
|
\texttt{AnchorOffset}, three region time models, tempo map
|
|
with explicit segment endpoints, sounding duration vs.
|
|
notational decomposition. \\
|
|
\ref{ch:tuning} & Pitch spaces (including CMN and JI), tuning
|
|
systems, score tuning context, accidental registries, glyph
|
|
references. \\
|
|
\ref{ch:graph} & Score graph, event arena, cross-cutting
|
|
structures, regions, staff identity (Staff vs.
|
|
StaffInstance), polymeter via per-staff metric grids, voice
|
|
origin, graph invariants. \\
|
|
\ref{ch:semops} & Operation framework, canonical reduction,
|
|
DVV causal contexts, operation effects, tombstones, conflict
|
|
records, transactions, re-anchoring, undo, LWW discipline. \\
|
|
\ref{ch:layout-ir} & The four-stage layout intermediate
|
|
representation; provenance and incremental invalidation. \\
|
|
\ref{ch:format} & Fixed prelude, two superblock slots, atomic
|
|
commit, content-addressed BLAKE3 chunks,
|
|
operation-envelope blocks, snapshots as canonical bases,
|
|
edit barriers, text projection. \\
|
|
\ref{ch:solver} & Solver interface, SolveReport, deterministic
|
|
budgets, NormalizedMetric, three conformance tiers,
|
|
reference-suite conformance, six invalidation scopes. \\
|
|
\ref{ch:perf} & Performance requirements: frame budgets, cold
|
|
open, memory, audio-thread discipline, measurement. \\
|
|
\ref{ch:extension} & Consolidated catalog of extension points
|
|
by chapter of origin. \\
|
|
\ref{app:deferred} & Intentionally deferred types: companion
|
|
specifications, externally provided components, open
|
|
canonical algorithms. \\
|
|
\ref{app:glossary} & Glossary of defined terms with
|
|
chapter cross-references. \\
|
|
\ref{app:refs} & Bibliography and references. \\
|
|
\ref{app:determinism} & Determinism contract: the legal code
|
|
for reproducibility. \\
|
|
\ref{app:bytes} & Canonical byte-layout reference: every ratified
|
|
discriminant, derivation preimage, and primitive encoding in one
|
|
place, for the Binary Format companion to import. \\
|
|
\ref{app:history} & Revision history. \\
|
|
\bottomrule
|
|
\end{longtable}
|
|
|
|
% ----- Main matter -----
|
|
\clearpage
|
|
\pagenumbering{arabic}
|
|
|
|
% ===========================================================================
|
|
\chapter{Introduction and Scope}
|
|
\label{ch:intro}
|
|
|
|
\section{Purpose}
|
|
|
|
This document specifies the core layer of \textit{Epiphany}, a music
|
|
notation platform. The core comprises the abstract musical data
|
|
model, the engraving engine that transforms that data into a typeset
|
|
layout, and the on-disk file format that serializes both. Everything
|
|
else built atop the platform---editing UI, audio engine, plugin
|
|
runtime, collaboration, export pipelines---depends on this core and
|
|
must conform to its interfaces.
|
|
|
|
The name \textit{Epiphany} evokes both the original sense of the
|
|
word---a manifestation made visible---and the experience of music
|
|
itself, which strikes the listener and the creator alike. Music
|
|
notation is, in a literal sense, the act of making the inaudible
|
|
visible: a body of work this central to that task deserves a name
|
|
of corresponding weight.
|
|
|
|
\section{Scope}
|
|
|
|
\textbf{In scope:}
|
|
|
|
\begin{itemize}
|
|
\item Pitch, time, and duration primitives.
|
|
\item The score graph: the in-memory representation of musical content.
|
|
\item The layout intermediate representation produced by the engraving
|
|
engine.
|
|
\item The file format: the canonical on-disk projection of the score
|
|
graph.
|
|
\item The constraint-solver interface consumed by the engraving engine.
|
|
\item Extension points for non-Common-Music-Notation systems, including
|
|
microtonal pitch spaces and graphic-notation regions.
|
|
\end{itemize}
|
|
|
|
\textbf{Out of scope for this document:}
|
|
|
|
\begin{nongoal}
|
|
The following are explicitly out of scope for this core specification.
|
|
Each is addressed by a separate specification layered atop the core.
|
|
\begin{itemize}
|
|
\item User interface, input handling, and editing operations.
|
|
\item Audio synthesis, playback, and plugin (VST3/AU/CLAP/LV2) hosting.
|
|
\item The plugin runtime (WebAssembly capability-based extensions).
|
|
\item Real-time collaboration transport and federation protocols.
|
|
\item Import and export pipelines for foreign formats (MusicXML, MEI,
|
|
LilyPond, MIDI, etc.), although the core defines the data
|
|
sufficient to make lossless interchange feasible.
|
|
\item Machine-learning components (transcription, OMR, engraving
|
|
assistance).
|
|
\item Platform-specific integrations (file pickers, sharing, accessibility
|
|
APIs).
|
|
\end{itemize}
|
|
\end{nongoal}
|
|
|
|
\section{Design Goals}
|
|
|
|
\begin{description}
|
|
\item[Correctness over convenience.] The data model must represent
|
|
music accurately, even when this is harder than a convenient
|
|
approximation. MIDI-style pitch numbers, for example, are inadequate
|
|
and rejected; see Chapter~\ref{ch:pitch}.
|
|
\item[Extensibility without bolt-ons.] Microtonality, graphic notation,
|
|
historical notations, and future notational systems must be
|
|
expressible within the core data model, not as escape hatches.
|
|
\item[Determinism.] Given identical input, the engraving engine must
|
|
produce identical output. The on-disk format must serialize
|
|
deterministically.
|
|
\item[Incrementality.] The data model and layout engine must support
|
|
fine-grained incremental edits without full recomputation.
|
|
\item[Reducibility.] All editing operations on the score graph
|
|
must have well-defined reduction rules under canonical
|
|
deterministic reduction (Chapter~\ref{ch:semops}), enabling
|
|
collaboration and version control as natural consequences of
|
|
the data model rather than bolt-on features.
|
|
\item[Performance.] Layout and rendering of a 100-page orchestral score
|
|
must remain interactive on commodity hardware. See
|
|
Chapter~\ref{ch:perf} for budgets.
|
|
\item[Privacy.] The core must operate entirely offline. No core
|
|
functionality may depend on network access.
|
|
\end{description}
|
|
|
|
\section{Document Structure}
|
|
|
|
The remainder of this document is organized bottom-up:
|
|
|
|
\begin{enumerate}
|
|
\item Chapter~\ref{ch:pitch} defines pitch: its layered structure, the
|
|
spelling subsystem, scale positions, and acoustic realization.
|
|
\item Chapter~\ref{ch:time} defines time and duration primitives.
|
|
\item Chapter~\ref{ch:tuning} defines tuning systems, pitch spaces, and
|
|
accidental registries.
|
|
\item Chapter~\ref{ch:graph} defines the score graph: how primitives
|
|
compose into staves, voices, regions, and the overall score
|
|
structure, including the canvas/region model that accommodates
|
|
non-CMN notations.
|
|
\item Chapter~\ref{ch:semops} defines the semantic operations on the
|
|
score graph: the canonical set of mutations and their reduction
|
|
semantics under concurrent edits.
|
|
\item Chapter~\ref{ch:layout-ir} defines the layout intermediate
|
|
representation: the contract between the score graph (input) and the
|
|
engraving engine (consumer).
|
|
\item Chapter~\ref{ch:format} defines the on-disk file format, including
|
|
serialization, chunking, the operation-envelope set and its
|
|
deterministic reduction, and schema evolution.
|
|
\item Chapter~\ref{ch:solver} defines the constraint-solver interface
|
|
consumed by the engraving engine.
|
|
\item Chapter~\ref{ch:perf} states performance requirements and
|
|
measurement methodology.
|
|
\item Chapter~\ref{ch:extension} describes the extension points by
|
|
which the core supports notation grammars beyond Common Music
|
|
Notation.
|
|
\end{enumerate}
|
|
|
|
% ===========================================================================
|
|
\chapter{Pitch}
|
|
\label{ch:pitch}
|
|
|
|
This chapter specifies the pitch primitive: the data type representing
|
|
a single sounding (or to-be-sounded) note's identity in the score graph.
|
|
Pitch is the most consequential primitive in the core because nearly every
|
|
higher-level operation---transposition, key analysis, accidental rendering,
|
|
playback frequency, interchange, voice-leading analysis---is defined in
|
|
terms of it. The definition is therefore deliberately verbose.
|
|
|
|
\section{Design Principles}
|
|
\label{sec:pitch:principles}
|
|
|
|
\begin{description}
|
|
\item[Separation of identity layers.] Pitch comprises three
|
|
independent semantic layers: scale position (analytical identity),
|
|
acoustic realization (sounding frequency), and spelling (notational
|
|
appearance). Each is a first-class concern and \MUST{} be
|
|
representable independently. Conflating them is a design error.
|
|
\item[Spelling is attachment, not field.] A pitch carries its scale
|
|
position and acoustic identity intrinsically. Spelling is attached
|
|
externally through a separate indexed subsystem (see
|
|
Section~\ref{sec:pitch:spelling}). A pitch may have zero, one, or
|
|
multiple spelling attachments.
|
|
\item[Grammar neutrality.] The pitch type \MUST{} support Common Music
|
|
Notation as a fast path while remaining capable of representing
|
|
pitches in arbitrary user-defined notation grammars. CMN is a
|
|
privileged default, not a hardcoded ceiling.
|
|
\item[Microtonality is not an extension.] Microtonal pitches are
|
|
representable using the same primitive as 12-tone equal-tempered
|
|
pitches. No separate type, no escape hatch.
|
|
\item[Unpitched events are not pitches.] Unpitched percussion and
|
|
similar events \MUSTNOT{} be represented using the pitch type.
|
|
See Section~\ref{sec:pitch:not-pitch}.
|
|
\end{description}
|
|
|
|
\section{The \texttt{Pitch} Type}
|
|
\label{sec:pitch:type}
|
|
|
|
A pitch is a record of two intrinsic layers:
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
/// A pitch's intrinsic identity. Spellings are attached externally;
|
|
/// see Section~\ref{sec:pitch:spelling}.
|
|
pub struct Pitch {
|
|
/// The analytical identity within a pitch space. Determines
|
|
/// transposition, key analysis, and Roman-numeral behavior.
|
|
pub scale_position: ScalePosition,
|
|
|
|
/// The acoustic identity: what frequency this pitch produces when
|
|
/// sounded, expressed relative to a tuning system.
|
|
pub acoustic: AcousticPitch,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
The two layers are independent. A given scale position can be acoustically
|
|
realized in many ways depending on the active tuning system; a given
|
|
acoustic realization can correspond to many scale positions in different
|
|
analytical contexts. The pitch carries both; neither is derivable from
|
|
the other in general.
|
|
|
|
\begin{rationale}
|
|
Storing both layers, rather than deriving one from the other, costs
|
|
a small number of bytes per pitch and saves a large amount of complexity
|
|
downstream. Derivation requires the renderer, the analyzer, the
|
|
transcriber, and the importer to each agree on a derivation procedure;
|
|
storage makes the pitch self-describing.
|
|
\end{rationale}
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
/// A closed range of pitches, used for advisory declarations: an
|
|
/// instrument's playable compass and an indeterminate event's pitch hint.
|
|
/// Both bounds are full pitches.
|
|
pub struct PitchRange {
|
|
pub lowest: Pitch,
|
|
pub highest: Pitch,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
A \texttt{PitchRange} is \emph{advisory}. Whether a given pitch lies within
|
|
it is decidable only when the pitch and both bounds share a pitch space that
|
|
defines an order (the common CMN case); cross-space or partially-ordered
|
|
comparisons are treated as ``not out of range'' --- a sound-but-incomplete
|
|
check, as with region time overlap. A range is well-formed when
|
|
\texttt{lowest} does not sort above \texttt{highest} in a common ordered
|
|
space. A solver \MAY{} flag an out-of-range pitch but \MUSTNOT{} treat the
|
|
range as a hard constraint. The type is defined with schema major~1, closing
|
|
the gap in which \texttt{IndeterminacyHints} and \texttt{Instrument} named a
|
|
\texttt{PitchRange} that no section defined.
|
|
|
|
\section{Scale Position}
|
|
\label{sec:pitch:scale-position}
|
|
|
|
A scale position locates the pitch within a \emph{pitch space}. A pitch
|
|
space is the analytical universe in which scale-degree and interval
|
|
relationships are defined.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct ScalePosition {
|
|
/// The pitch space this position is defined within. References an
|
|
/// entry in the score's pitch-space registry.
|
|
pub space: PitchSpaceId,
|
|
|
|
/// The position within that space. Interpretation is space-defined.
|
|
pub position: PitchSpacePosition,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
The \texttt{PitchSpacePosition} type is a tagged union supporting the
|
|
common cases with a fast representation, plus a registry-id escape hatch
|
|
for arbitrary grammars:
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub enum PitchSpacePosition {
|
|
/// Common Music Notation: a diatonic nominal plus chromatic
|
|
/// alteration plus octave. The fast path for tonal Western music.
|
|
Cmn {
|
|
nominal: CmnNominal, // C, D, E, F, G, A, B
|
|
alteration: i8, // -2..=+2 in semitones
|
|
octave: i8, // scientific pitch notation
|
|
},
|
|
|
|
/// N-tone integer position. Used for 12-tone serial music, EDO
|
|
/// systems, and any space where pitches are indexed as integers.
|
|
Integer {
|
|
space_size: u16, // e.g., 12, 19, 31, 53
|
|
index: i32, // absolute index; octave is implicit
|
|
},
|
|
|
|
/// Just-intonation lattice position. Used for JI pitch spaces.
|
|
/// Each component is the integer power of the corresponding prime
|
|
/// in the space's prime basis. The pitch space declares the basis
|
|
/// ordering; by convention prime 2 is first, so the leading
|
|
/// component carries octave information.
|
|
JiVector {
|
|
components: Vec<i32>,
|
|
},
|
|
|
|
/// A registered position whose interpretation is defined by the
|
|
/// pitch space's grammar plugin. Used for maqamat, gamelan,
|
|
/// historical modes, and other non-lattice, non-CMN grammars.
|
|
Registered(PositionRegistryId),
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\begin{requirement}
|
|
\label{req:pitch:ji-vector-basis}
|
|
For \texttt{PitchSpacePosition::JiVector}:
|
|
\begin{itemize}
|
|
\item \texttt{components.len()} \MUST{} equal the number of primes
|
|
in the enclosing pitch space's declared prime basis.
|
|
\item The basis ordering is established by the pitch space; the
|
|
built-in JI pitch spaces (\texttt{ji-5limit},
|
|
\texttt{ji-7limit}, \texttt{ji-11limit}) order primes
|
|
ascending starting with 2.
|
|
\item The vector $[a_0, a_1, a_2, \ldots]$ denotes the ratio
|
|
$p_0^{a_0} \cdot p_1^{a_1} \cdot p_2^{a_2} \cdots$ where
|
|
$p_i$ is the $i$-th prime in the basis. With the conventional
|
|
basis $\{2, 3, 5, \ldots\}$, the first component holds
|
|
octave information.
|
|
\item Pitch spaces \MAY{} declare octave-reduced equivalence,
|
|
in which case the first component is normalized to a canonical
|
|
range. Without such a declaration, full register is preserved.
|
|
\end{itemize}
|
|
\end{requirement}
|
|
|
|
\begin{requirement}
|
|
\label{req:pitch:default-pitch-space}
|
|
Every score \MUST{} define at least one pitch space. The default
|
|
pitch space for CMN scores is \texttt{cmn-12}, which is
|
|
diatonic-over-chromatic in structure (seven diatonic nominals
|
|
A--G over twelve chromatic positions), anchored to the score's
|
|
reference pitch as resolved through the score tuning context
|
|
(Chapter~\ref{ch:tuning}). Scores \MAY{} define additional pitch
|
|
spaces; pitches in different spaces cannot be directly compared
|
|
and operations between them \MUST{} go through an explicit
|
|
space-conversion mechanism.
|
|
\end{requirement}
|
|
|
|
\subsection{Octave Convention}
|
|
\label{sec:pitch:octave}
|
|
|
|
For the \texttt{Cmn} variant, the octave field uses Scientific Pitch
|
|
Notation: middle C is C4. This convention is normative throughout the
|
|
specification.
|
|
|
|
\begin{requirement}
|
|
\label{req:pitch:scientific-pitch-octaves}
|
|
The \texttt{octave} field of \texttt{PitchSpacePosition::Cmn} \MUST{}
|
|
follow Scientific Pitch Notation. Middle C is C4. The lowest A on a
|
|
standard piano is A0. C-1 is one octave below the lowest C on a
|
|
standard piano.
|
|
\end{requirement}
|
|
|
|
\begin{rationale}
|
|
Scientific Pitch Notation is the dominant modern convention in
|
|
English-language music theory and notation software. MIDI octave
|
|
numbering, where middle C is sometimes C3 and sometimes C5 depending
|
|
on vendor, is rejected as a source of foot-guns. Helmholtz notation is
|
|
expressible at the display layer; the data model commits to Scientific.
|
|
\end{rationale}
|
|
|
|
\subsection{The \texttt{CmnNominal} Type}
|
|
\label{sec:pitch:nominal}
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
#[repr(u8)]
|
|
pub enum CmnNominal { C = 0, D = 1, E = 2, F = 3, G = 4, A = 5, B = 6 }
|
|
\end{lstlisting}
|
|
|
|
The discriminants are normative: they define the diatonic step ordering
|
|
used by transposition algorithms.
|
|
|
|
\subsection{Transposition and the \texttt{Interval} Type}
|
|
\label{sec:pitch:interval}
|
|
|
|
Transposition acts on \emph{scale position}, not on acoustic realization.
|
|
It moves a pitch's analytical identity through the pitch space and leaves
|
|
the tuning context that sounds it untouched. A transposition therefore
|
|
requires no tuning catalog (Chapter~\ref{ch:tuning}) --- tuning decides what
|
|
frequency a scale position sounds at, not how to add an interval to one. It
|
|
requires an interval:
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
/// A signed interval: the transposition primitive. Defined here and
|
|
/// referenced by Instrument.transposition (Chapter 5), which is the
|
|
/// written-versus-sounding interval of a transposing instrument
|
|
/// (a B-flat clarinet is -1 diatonic, -2 chromatic).
|
|
pub struct TranspositionInterval {
|
|
/// Signed diatonic steps: how far the nominal moves along C..B.
|
|
/// A perfect fifth up is +4 (C -> G); an octave up is +7.
|
|
pub diatonic_steps: i32,
|
|
|
|
/// Signed chromatic steps, in semitones. A perfect fifth up
|
|
/// is +7; an octave up is +12.
|
|
pub chromatic_steps: i32,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
Both components are load-bearing. The diatonic component fixes the
|
|
\emph{spelling} --- which nominal, hence which staff line the notehead
|
|
occupies --- and the chromatic component fixes the \emph{sound}. Neither
|
|
determines the other: an augmented second $(+1, +3)$ and a minor third
|
|
$(+2, +3)$ sound alike and are written differently, while a diminished
|
|
sixth $(+5, +7)$ and a perfect fifth $(+4, +7)$ are written a step apart
|
|
and sound the same. A scalar semitone count cannot express the difference,
|
|
and an implementation carrying only one is not transposing but altering.
|
|
|
|
\begin{requirement}
|
|
\label{req:pitch:transposition}
|
|
Let $n$ be the \texttt{nominal}'s normative discriminant
|
|
(Section~\ref{sec:pitch:nominal}), and let a
|
|
\texttt{PitchSpacePosition::Cmn} position's \emph{absolute semitone} be
|
|
\[
|
|
s \;=\; \texttt{nominal.chromatic()} \;+\; \texttt{alteration}
|
|
\;+\; 12 \cdot \texttt{octave}.
|
|
\]
|
|
Transposing that position by \texttt{TranspositionInterval \{ d, c \}}
|
|
\MUST{} produce
|
|
\begin{align*}
|
|
\texttt{nominal}' &= \textsc{CmnNominal}\big((n + d) \bmod 7\big), \\
|
|
\texttt{octave}' &= \texttt{octave} + \lfloor (n + d) / 7 \rfloor, \\
|
|
\texttt{alteration}' &= (s + c) - \big(\texttt{nominal}'\texttt{.chromatic()}
|
|
+ 12 \cdot \texttt{octave}'\big),
|
|
\end{align*}
|
|
where $\bmod$ and $\lfloor \cdot \rfloor$ are Euclidean (the remainder is
|
|
non-negative). The diatonic component alone selects the nominal and the
|
|
octave; the alteration absorbs exactly the residue.
|
|
|
|
A transposition \MUST{} be refused --- atomically, changing nothing --- when
|
|
any target satisfies any of:
|
|
\begin{itemize}
|
|
\item its \texttt{scale\_position.position} is not \texttt{Cmn}, so the
|
|
\texttt{Interval} above has no defined action on it;
|
|
\item its \texttt{acoustic.realization} is
|
|
\texttt{AcousticRealization::AbsoluteHz}, which overrides the tuning
|
|
system, so moving the scale position would move the notehead without
|
|
moving the sound;
|
|
\item the computed $\texttt{alteration}'$ or $\texttt{octave}'$ does not
|
|
fit its field.
|
|
\end{itemize}
|
|
An implementation \MUSTNOT{} saturate, clamp, or partially apply a
|
|
transposition in any of these cases.
|
|
\end{requirement}
|
|
|
|
\begin{rationale}
|
|
The refusal cases are the three ways a transposition can silently lie. A
|
|
non-CMN position has no nominal to move, so a semitone shift would have to
|
|
invent one. An \texttt{AbsoluteHz} pitch declares its own frequency: it can
|
|
be respelled or it can be resounded, but a transposition that claims to do
|
|
both does one and pretends to the other. And saturation is the worst of the
|
|
three, because it is invisible --- a transposition that clamps has produced
|
|
a pitch nobody asked for, reported success, and destroyed the information
|
|
needed to notice.
|
|
|
|
Refusing atomically, rather than skipping the offending target, follows from
|
|
the same reasoning: a chord transposed except for one note is not a
|
|
transposed chord, it is a different chord. Contrast the treatment of
|
|
\emph{tombstoned} targets, which are skipped rather than refused --- a
|
|
deleted pitch is not an untransposable pitch, it is a pitch the operation
|
|
has nothing to say about.
|
|
\end{rationale}
|
|
|
|
\section{Acoustic Realization}
|
|
\label{sec:pitch:acoustic}
|
|
|
|
The acoustic layer specifies the sounding frequency. It does not carry
|
|
a frequency directly; instead it references a tuning system that, given
|
|
the pitch's scale position, resolves to a frequency.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct AcousticPitch {
|
|
/// The tuning system governing this pitch's frequency.
|
|
/// May be inherited; see Section~\ref{sec:pitch:tuning-inherit}.
|
|
pub tuning: TuningReference,
|
|
|
|
/// How the tuning system resolves to a frequency.
|
|
pub realization: AcousticRealization,
|
|
}
|
|
|
|
pub enum AcousticRealization {
|
|
/// Resolve through the tuning system using the scale position alone.
|
|
/// This is the default case for ordinary CMN.
|
|
Implicit,
|
|
|
|
/// An explicit offset in cents from what the tuning system would
|
|
/// otherwise produce. For inflections, expressive retuning, or
|
|
/// just-intonation adjustments within a tempered piece.
|
|
CentsOffset(f64),
|
|
|
|
/// An explicit absolute frequency in Hertz, overriding the tuning
|
|
/// system. For spectral music or fixed-frequency electronic parts.
|
|
AbsoluteHz(f64),
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsection{Cents as the Offset Unit}
|
|
|
|
Cents (one cent equals one hundredth of an equal-tempered semitone) are
|
|
the normative offset unit. \texttt{f64} provides far more precision than
|
|
the ear can resolve in any context.
|
|
|
|
\subsection{Tuning Reference and Inheritance}
|
|
\label{sec:pitch:tuning-inherit}
|
|
|
|
A \texttt{TuningReference} is either an explicit tuning-system identifier
|
|
or an inheritance marker:
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub enum TuningReference {
|
|
/// Inherit the tuning system from the enclosing scope.
|
|
Inherit,
|
|
|
|
/// Explicitly named tuning system.
|
|
Explicit(TuningSystemId),
|
|
}
|
|
\end{lstlisting}
|
|
|
|
Resolution proceeds by walking outward: pitch, then voice, then staff,
|
|
then region, then score. The score's tuning system \MUST{} be explicit;
|
|
\texttt{Inherit} at the score level is a malformed document. Tuning
|
|
systems and their resolution rules are defined in detail in
|
|
Chapter~\ref{ch:tuning}.
|
|
|
|
\subsection{Reference Pitch}
|
|
\label{sec:pitch:reference}
|
|
|
|
A pitch's \texttt{TuningReference} resolves, through the hierarchical
|
|
walk, to a tuning system. The \emph{reference pitch} (the position
|
|
and frequency that anchor that tuning system's structure to absolute
|
|
Hertz) is resolved separately through the same hierarchical walk, and
|
|
is a property of the score's tuning context, not of the tuning system
|
|
itself.
|
|
|
|
The pitch type does not own or imply a reference frequency. Two
|
|
scores using the same tuning system (12-TET) but different references
|
|
(A4 = 440\,Hz versus A4 = 415\,Hz) share their tuning structure
|
|
entirely; only their reference pitch differs.
|
|
|
|
The full reference-pitch model, including the \texttt{ReferencePitch}
|
|
type and the score tuning context that owns it, is specified in
|
|
Chapter~\ref{ch:tuning} (Section~\ref{sec:tuning:reference}).
|
|
|
|
\section{Spelling}
|
|
\label{sec:pitch:spelling}
|
|
|
|
Spelling determines what the performer sees on the page for a given
|
|
pitch: the staff position, the accidental glyph (if any), and any
|
|
ancillary notational marks (parenthesized accidentals, courtesy
|
|
accidentals, microtonal accidentals).
|
|
|
|
Spelling \MUSTNOT{} be a field on the pitch type. Spellings are stored
|
|
externally, indexed by pitch identifier, and managed by the spelling
|
|
subsystem defined in this section.
|
|
|
|
\begin{rationale}
|
|
Spelling as a field on pitch causes destructive overwrites on
|
|
respelling, prevents partial spelling (where only some pitches have
|
|
user-chosen spellings and the rest are inferred), prevents multiple
|
|
spellings per pitch (analytical layers), and prevents provenance
|
|
tracking. Externalizing spelling resolves all four.
|
|
\end{rationale}
|
|
|
|
\subsection{The Spelling Attachment}
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct SpellingAttachment {
|
|
/// What this attachment applies to: a single pitch or a scope.
|
|
pub scope: SpellingScope,
|
|
|
|
/// The directive: explicit spelling or a rule for inference.
|
|
pub directive: SpellingDirective,
|
|
|
|
/// Provenance of this attachment.
|
|
pub source: SpellingSource,
|
|
|
|
/// Tie-break priority among attachments. Higher wins. See
|
|
/// Section~\ref{sec:pitch:precedence} for the configurable
|
|
/// precedence model.
|
|
pub priority: i32,
|
|
|
|
/// Optional analytical layer. None means the engraved layer;
|
|
/// Some(id) means an alternative analytical layer that does not
|
|
/// affect the engraved score.
|
|
pub layer: Option<AnalysisLayerId>,
|
|
}
|
|
|
|
pub enum SpellingScope {
|
|
/// Applies to one specific pitch.
|
|
Pitch(PitchId),
|
|
|
|
/// Applies to all pitches matching the selector within a time range.
|
|
Range {
|
|
start: TimeAnchor,
|
|
end: TimeAnchor,
|
|
voices: VoiceSelector,
|
|
},
|
|
}
|
|
|
|
pub enum SpellingDirective {
|
|
/// An explicit spelling for a single pitch. Only valid with
|
|
/// SpellingScope::Pitch.
|
|
Explicit(PitchSpelling),
|
|
|
|
/// A rule for inferring spellings within a scope.
|
|
Rule(SpellingRule),
|
|
}
|
|
|
|
pub struct PitchSpelling {
|
|
/// The nominal: the staff position. For CMN, this is one of A-G.
|
|
/// For other grammars, an identifier into the grammar's nominal
|
|
/// registry.
|
|
pub nominal: SpellingNominal,
|
|
|
|
/// Stack of accidentals applied to the nominal, in stacking
|
|
/// order: innermost (closest to the notehead) first.
|
|
/// Empty vector means no accidental glyph is drawn, which does
|
|
/// not necessarily mean "natural"; see
|
|
/// Section~\ref{sec:pitch:absent-accidental}.
|
|
pub accidentals: Vec<AccidentalId>,
|
|
|
|
/// The octave designation.
|
|
pub octave: i8,
|
|
|
|
/// Optional rendering hints: parenthesized, cautionary, editorial,
|
|
/// small-print, etc. Non-normative for sounding pitch.
|
|
pub render_hints: SpellingRenderHints,
|
|
}
|
|
|
|
pub enum SpellingNominal {
|
|
/// CMN nominals: the fast path.
|
|
Cmn(CmnNominal),
|
|
|
|
/// Integer nominals for N-tone systems.
|
|
Integer(i32),
|
|
|
|
/// Registered nominals for grammar-specific systems.
|
|
Registered(NominalRegistryId),
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsubsection{Accidental Stack Semantics}
|
|
\label{sec:pitch:accidental-stack}
|
|
|
|
\begin{requirement}
|
|
\label{req:pitch:accidental-stack}
|
|
The \texttt{accidentals} vector \MUST{} be interpreted as follows:
|
|
|
|
\begin{itemize}
|
|
\item The stored order is normative for engraving: index 0 is
|
|
the innermost glyph (closest to the notehead), with subsequent
|
|
indices placed further outward.
|
|
\item For built-in additive accidental systems, the pitch-space
|
|
modification contributed by the stack is the sum of the
|
|
modifications of its members, and is independent of stored
|
|
order. Reordering the stack changes engraving appearance but
|
|
\MUSTNOT{} change sounding pitch.
|
|
\item Custom accidental systems \MAY{} declare non-additive
|
|
combination semantics only through their registered
|
|
\texttt{AccidentalCombination} (Section~\ref{sec:tuning:accidentals}).
|
|
Otherwise, accidentals are treated as commutative under their
|
|
pitch effect.
|
|
\item Two accidentals with the same \texttt{AccidentalId} in one
|
|
stack constitute a malformed spelling, unless the accidental's
|
|
registered combination semantics explicitly permits repetition.
|
|
The conventional double-sharp and double-flat are represented
|
|
as single accidental definitions, not as repeated singles.
|
|
\item An empty \texttt{accidentals} vector means no glyph is
|
|
drawn; it is distinct from a vector containing only a natural
|
|
sign (which explicitly cancels prior alterations and is drawn).
|
|
\end{itemize}
|
|
\end{requirement}
|
|
|
|
\subsection{Spelling Sources}
|
|
|
|
Every spelling attachment carries provenance:
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub enum SpellingSource {
|
|
/// The user explicitly chose this spelling.
|
|
UserChosen,
|
|
|
|
/// Inferred by the spelling pre-pass from key signature and
|
|
/// context. Lowest default precedence. The pre-pass does not
|
|
/// store attachments (its output is a derived annotation);
|
|
/// this variant ranks that output in the precedence order.
|
|
Inferred,
|
|
|
|
/// Imported from a foreign format.
|
|
Imported { format: ForeignFormatId },
|
|
|
|
/// Propagated from a transposition or other edit operation.
|
|
Propagated { from: PitchId },
|
|
|
|
/// An analytical spelling on a non-engraved layer.
|
|
Analytical,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\begin{requirement}
|
|
\label{req:pitch:spelling-provenance}
|
|
Editing operations that respell a pitch \MUST{} produce attachments
|
|
with source \texttt{UserChosen}. Editing operations that move or
|
|
transpose a pitch \MUST{} produce attachments with source
|
|
\texttt{Propagated}. Foreign-format importers \MUST{} produce
|
|
attachments with source \texttt{Imported}. The spelling pre-pass
|
|
(Section~\ref{sec:pitch:prepass}) \MUSTNOT{} mint attachments: its
|
|
inferred spellings are derived annotations reported with
|
|
\texttt{Inferred} provenance, promoted to a stored
|
|
\texttt{UserChosen} attachment only by an explicit editing
|
|
operation.
|
|
\end{requirement}
|
|
|
|
\subsection{Configurable Precedence}
|
|
\label{sec:pitch:precedence}
|
|
|
|
When multiple attachments target the same pitch on the same analysis
|
|
layer, the renderer resolves the conflict by precedence. The default
|
|
precedence is:
|
|
|
|
\begin{enumerate}
|
|
\item \texttt{UserChosen} (highest)
|
|
\item \texttt{Imported}
|
|
\item \texttt{Propagated}
|
|
\item \texttt{Inferred} (lowest)
|
|
\end{enumerate}
|
|
|
|
\begin{requirement}
|
|
\label{req:pitch:spelling-precedence}
|
|
Every score \MUST{} carry a \texttt{SpellingPrecedence} configuration.
|
|
The configuration \MUST{} assign a total ordering over the variants of
|
|
\texttt{SpellingSource}. The configuration \MAY{} differ from the
|
|
default; for example, a workflow that treats MusicXML imports as
|
|
authoritative may rank \texttt{Imported} above \texttt{UserChosen}.
|
|
Conflicts are broken first by precedence, then by the \texttt{priority}
|
|
field of the attachment (higher wins), then by canonical attachment
|
|
order (the attachment appearing earliest in the score's canonical
|
|
serialization wins). Attachments carry no creation timestamp: a
|
|
timestamp tie-break would hang resolution on non-canonical state.
|
|
\end{requirement}
|
|
|
|
\subsection{The Spelling Pre-Pass}
|
|
\label{sec:pitch:prepass}
|
|
|
|
When a score is materialized (on load, after an edit, or ahead of
|
|
rendering), the spelling pre-pass computes an inferred spelling for
|
|
every spelling-eligible pitch. Its output is a \emph{canonical
|
|
derived annotation}: a deterministic function of the materialized
|
|
score graph, the active profile, and the versioned
|
|
\texttt{SpellingAlgorithmId}, recomputed on materialization and
|
|
never stored as graph state or serialized into canonical chunks.
|
|
Where a stored attachment targets the pitch, the resolved annotation
|
|
reports that attachment's spelling and provenance per the precedence
|
|
configuration (Section~\ref{sec:pitch:precedence}); the pre-pass
|
|
supplies the \texttt{Inferred}-provenance spelling that applies when
|
|
no attachment outranks it. The pre-pass \MUST{} be deterministic:
|
|
given the same materialized graph, precedence configuration, and
|
|
algorithm version, it \MUST{} produce identical annotations.
|
|
|
|
The pre-pass operates as follows:
|
|
|
|
\begin{enumerate}
|
|
\item Collect all pitches in the score, partitioned by voice and
|
|
measure.
|
|
\item For each partition, in time order, resolve each pitch's spelling
|
|
using the applicable context inputs (which of these a given
|
|
algorithm version consumes is part of its versioned definition;
|
|
see Requirement~\ref{req:pitch:spelling-algorithm}):
|
|
\begin{enumerate}
|
|
\item The active key signature for the staff.
|
|
\item The accidental context of prior pitches in the same measure
|
|
and voice.
|
|
\item The melodic context (preceding interval).
|
|
\item The harmonic context (concurrent pitches in other voices).
|
|
\item Any active scope-level \texttt{Rule} attachments.
|
|
\end{enumerate}
|
|
\item Report an \texttt{Inferred}-provenance derived annotation for
|
|
each pitch.
|
|
\end{enumerate}
|
|
|
|
Because derived annotations are not stored, a change to the algorithm
|
|
version deterministically invalidates them without any state
|
|
migration. Implementations \MAY{} cache derived annotations and
|
|
\MAY{} recompute incrementally (re-running only the affected measure
|
|
and voice, and forward through measures whose context is altered,
|
|
until the context stabilizes); a cache \MUST{} be invalidated when
|
|
the derivation key --- graph, profile, algorithm version --- changes,
|
|
and neither caching nor incrementality may be observable in the
|
|
annotations produced.
|
|
|
|
\begin{rationale}
|
|
Deriving inferred spellings---rather than storing them as
|
|
attachments---keeps algorithm output out of canonical state: two
|
|
replicas at the same (graph, profile, algorithm version) agree on
|
|
annotations byte-for-byte without ever exchanging them, an
|
|
algorithm upgrade cannot strand stale inferred state, and
|
|
serialization never churns on re-inference. Inferred spellings
|
|
remain inspectable in the UI and editable by promotion: accepting
|
|
one mints a \texttt{UserChosen} attachment through an editing
|
|
operation, which then outranks the pre-pass.
|
|
\end{rationale}
|
|
|
|
\begin{requirement}
|
|
\label{req:pitch:spelling-algorithm}
|
|
\textbf{Spelling algorithm disposition (ratified Pass 12).}
|
|
The spelling pre-pass receives the \emph{profile-declared by
|
|
versioned identifier} disposition of Appendix~\ref{app:determinism}
|
|
Section~\ref{sec:det:open}. The reserved identifier
|
|
\texttt{SpellingAlgorithmId} \texttt{"default"} denotes, at
|
|
version~1, a Temperley-style line-of-fifths preference
|
|
algorithm: each partition's tonal context is estimated as a
|
|
centre of gravity on the line of fifths from the accidental,
|
|
melodic, and harmonic context, and each pitch takes the
|
|
enharmonic spelling nearest that centre. The ascending-runs-take-
|
|
sharps / descending-runs-take-flats convention participates
|
|
\emph{only as a tiebreak} within the centre-of-gravity rule; an
|
|
isolated chromatic run with no tonal context may therefore
|
|
receive the enharmonic the convention alone would not pick, and
|
|
a voice-leading refinement is a future algorithm version, not a
|
|
deviation. Which of the context inputs enumerated above an
|
|
algorithm version consumes is part of its versioned definition:
|
|
\texttt{"default"} version~1 does \emph{not} consult declared
|
|
key signatures (a key-aware refinement is a future version) and
|
|
is region-time-model-independent --- pitches in aleatoric and
|
|
proportional regions are spelled by the same rule as metric
|
|
ones, and no region-specific spelling pass exists. A profile
|
|
requesting any other identifier \MUST{} error;
|
|
implementations \MUSTNOT{} silently substitute a different
|
|
algorithm.
|
|
\end{requirement}
|
|
|
|
\begin{requirement}
|
|
\label{req:pitch:authored-uninferred}
|
|
\textbf{Authored annotations for inference-ineligible targets
|
|
(ratified Pass 12).}
|
|
A stored spelling attachment whose target the pre-pass produces
|
|
\emph{no} inferred spelling for (an inference-ineligible pitch)
|
|
\MUST{} still surface in the resolved derived annotations: the
|
|
winning attachment under the precedence configuration
|
|
(Section~\ref{sec:pitch:precedence}) is reported with its
|
|
authored provenance, exactly as it would have been had it
|
|
outranked an inferred spelling. An authored attachment is
|
|
precisely how a user notates what the algorithm cannot infer;
|
|
the derived-annotation surface \MUSTNOT{} render it invisible.
|
|
Annotation taxonomy counts these authored-only resolutions
|
|
distinctly from authored-over-inferred overrides. The same rule
|
|
applies to the decomposition pre-pass
|
|
(Section~\ref{sec:time:notrhythm}).
|
|
\end{requirement}
|
|
|
|
\subsection{Absent Accidentals}
|
|
\label{sec:pitch:absent-accidental}
|
|
|
|
A \texttt{PitchSpelling} with an empty \texttt{accidentals} vector
|
|
means no accidental glyph is drawn at this position. This is distinct
|
|
from a vector containing an explicit natural sign. The distinction
|
|
matters in two cases:
|
|
|
|
\begin{itemize}
|
|
\item A pitch on a line affected by the key signature carries no
|
|
accidental and sounds the key-signature pitch; drawing a natural
|
|
would be a respelling.
|
|
\item A pitch that follows an in-measure accidental on the same line
|
|
and octave inherits that accidental; an empty vector means
|
|
inheritance applies, while a vector containing a natural cancels
|
|
the inheritance.
|
|
\end{itemize}
|
|
|
|
\begin{requirement}
|
|
\label{req:pitch:absent-versus-natural}
|
|
The renderer \MUST{} distinguish between an empty
|
|
\texttt{accidentals} vector (no glyph) and a vector containing only
|
|
a natural (explicit natural glyph) when materializing a spelling.
|
|
Promotion from empty to natural-only, or vice versa, is a
|
|
respelling operation.
|
|
\end{requirement}
|
|
|
|
\subsection{Analytical Layers}
|
|
\label{sec:pitch:layers}
|
|
|
|
A pitch may carry multiple spelling attachments on different analytical
|
|
layers. The engraved layer (\texttt{layer: None}) governs what the
|
|
performer sees. Other layers (\texttt{layer: Some(AnalysisLayerId)})
|
|
record alternative spellings used by analytical views, theory tools,
|
|
or pedagogical overlays.
|
|
|
|
The \texttt{AnalysisLayer} type itself is defined in
|
|
Chapter~\ref{ch:graph} (Section~\ref{sec:graph:layers}); a layer
|
|
identifier here refers to that authoritative definition.
|
|
|
|
\begin{requirement}
|
|
\label{req:pitch:spelling-layer-fallback}
|
|
Resolution of a pitch's spelling for a given view \MUST{} consider
|
|
only attachments whose \texttt{layer} matches the view's active layer.
|
|
The engraved view's active layer is \texttt{None}. Analytical views
|
|
may set an explicit layer; if no attachment on that layer matches,
|
|
the view \MUST{} fall back to the engraved layer.
|
|
\end{requirement}
|
|
|
|
\section{Pitch Identifiers}
|
|
\label{sec:pitch:ids}
|
|
|
|
Every pitch in the score graph carries a stable identifier:
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct PitchId(pub u128);
|
|
\end{lstlisting}
|
|
|
|
\begin{requirement}
|
|
\label{req:pitch:pitch-id-stability}
|
|
Pitch identifiers \MUST{} be stable across edits: a pitch's identifier
|
|
is assigned at creation and never reassigned. Pitch identifiers
|
|
\MUST{} be unique within a score. Pitch identifiers \MUST{} be
|
|
collision-resistant across distributed editing scenarios; the
|
|
recommended generation strategy is a 128-bit value combining a
|
|
per-replica identifier and a monotonic counter, with the exact format
|
|
defined in Chapter~\ref{ch:semops}.
|
|
\end{requirement}
|
|
|
|
\section{What Pitch Is Not}
|
|
\label{sec:pitch:not-pitch}
|
|
|
|
Several musical phenomena resemble pitches but are not represented by
|
|
the pitch type:
|
|
|
|
\begin{description}
|
|
\item[Unpitched events.] Snare drum hits, claves, cymbal strokes, and
|
|
other unpitched percussion are sounding events with staff positions
|
|
but no pitched identity. They are represented by a separate
|
|
\texttt{UnpitchedEvent} type (see Chapter~\ref{ch:graph}). They have
|
|
no scale position and no acoustic pitch in the sense defined here.
|
|
\item[Rests.] A rest is a duration without a sounding event. Rests are
|
|
represented by a separate \texttt{Rest} type. They carry timing and
|
|
voice information but no pitch.
|
|
\item[Indeterminate-pitch events.] Events whose pitch is deliberately
|
|
indeterminate (text instructions like ``highest possible note,''
|
|
aleatoric pitch material, certain graphic-notation events) are
|
|
represented by an \texttt{IndeterminatePitchEvent} type, which may
|
|
carry hints (range, contour, density) but no concrete pitch.
|
|
\item[Pitch trajectories.] Glissandi, portamenti, and continuous pitch
|
|
bends are not single pitches but trajectories between or around
|
|
pitches. They are represented as relationships between pitched
|
|
events plus a trajectory shape, defined in Chapter~\ref{ch:graph}.
|
|
\end{description}
|
|
|
|
\begin{rationale}
|
|
Forcing every sounding event into a pitch-shaped hole creates
|
|
invariant-breaking edge cases throughout the codebase. Sibling types
|
|
under a common \texttt{SoundingEvent} (or similar) abstraction keep
|
|
each type coherent and let operations branch cleanly.
|
|
\end{rationale}
|
|
|
|
\section{Equality and Comparison}
|
|
\label{sec:pitch:eq}
|
|
|
|
Two pitches are \emph{structurally equal} if and only if their scale
|
|
positions and acoustic realizations are equal field-by-field. Structural
|
|
equality is the default \texttt{Eq} implementation.
|
|
|
|
Three additional equivalence relations are defined:
|
|
|
|
\begin{description}
|
|
\item[Sounding equivalence.] Two pitches are sounding-equivalent if
|
|
they resolve to the same frequency under their respective tuning
|
|
systems. Computed, not derived from the type.
|
|
\item[Scale-position equivalence.] Two pitches are scale-position-
|
|
equivalent if their \texttt{ScalePosition} fields are equal, ignoring
|
|
acoustic realization.
|
|
\item[Enharmonic equivalence.] Two pitches are enharmonically
|
|
equivalent if they are sounding-equivalent under 12-tone equal
|
|
temperament, regardless of their actual tuning system. Used for
|
|
foreign-format export and certain analytical operations.
|
|
\end{description}
|
|
|
|
\begin{requirement}
|
|
\label{req:pitch:pitch-equivalence-functions}
|
|
Implementations \MUST{} provide functions for each of the three
|
|
computed equivalences. Implementations \MUSTNOT{} conflate structural
|
|
equality with sounding or enharmonic equivalence.
|
|
\end{requirement}
|
|
|
|
\section{Forward References}
|
|
|
|
\begin{itemize}
|
|
\item Tuning systems, pitch spaces, and accidental registries are
|
|
defined in Chapter~\ref{ch:tuning}.
|
|
\item The score graph, including how pitches are embedded in events,
|
|
voices, and staves, is defined in Chapter~\ref{ch:graph}.
|
|
\item \texttt{TimeAnchor}, \texttt{VoiceSelector}, \texttt{PitchId}
|
|
generation, and the reduction rules governing spelling
|
|
attachments under concurrent edits are defined in
|
|
Chapter~\ref{ch:semops}.
|
|
\end{itemize}
|
|
|
|
% ===========================================================================
|
|
\chapter{Time and Duration}
|
|
\label{ch:time}
|
|
|
|
This chapter specifies the time and duration primitives: how positions
|
|
and durations are represented, how time signatures and tempo maps are
|
|
modelled, how notational rhythm is decomposed from sounding duration,
|
|
how tuplets and other groupings work, and how the core supports metric,
|
|
proportional, and aleatoric time models simultaneously.
|
|
|
|
\section{Design Principles}
|
|
\label{sec:time:principles}
|
|
|
|
\begin{description}
|
|
\item[Exact rational arithmetic.] Musical positions and durations
|
|
\MUST{} be exact rationals. Floating-point time is rejected as a
|
|
source of accumulated error; integer-tick time is rejected as
|
|
incompatible with arbitrary tuplet nesting. Exactness is
|
|
non-negotiable.
|
|
\item[Two clocks.] Musical time (measured in whole-note rationals)
|
|
and wall-clock time (measured in fixed-point seconds) are distinct
|
|
primitives. Conversion between them is a pure function of the
|
|
tempo map.
|
|
\item[Position and duration are distinct types.] A position and a
|
|
duration share an underlying representation but are not
|
|
interchangeable. The algebra (position $+$ duration $\rightarrow$ position;
|
|
position $-$ position $\rightarrow$ duration; position $+$ position is
|
|
undefined) is enforced at the type level.
|
|
\item[Sounding duration is data; notational rhythm is derived.]
|
|
A note's sounding duration is intrinsic; its notated decomposition
|
|
(notehead values, ties, dots, tuplet membership) is produced by a
|
|
deterministic pre-pass analogous to the spelling pre-pass and is
|
|
overridable per-event by an authored attachment.
|
|
\item[Multiple time models coexist.] A score \MUST{} support metric,
|
|
proportional, and aleatoric regions, possibly concurrent on
|
|
different staves. The region's declared time model determines the
|
|
interpretation of positions within it.
|
|
\item[Time anchors, not absolute positions.] Stored references to
|
|
points in time \MUST{} anchor to identified objects (events,
|
|
measures, regions) plus offsets. Absolute positions \MUSTNOT{}
|
|
be stored where they could shift under edits.
|
|
\end{description}
|
|
|
|
\section{The Rational Time Type}
|
|
\label{sec:time:rational}
|
|
|
|
The fundamental time primitive is an exact rational number. The
|
|
specification states the contract; implementations are free to choose
|
|
representations meeting it.
|
|
|
|
\begin{requirement}
|
|
\label{req:time:exact-rational-time}
|
|
The rational time type \MUST{} behave as an exact rational with at
|
|
least 32-bit numerator and 32-bit denominator range in normalized
|
|
form. Operations \MUST{} produce exact results; on representation
|
|
overflow, the implementation \MUST{} either widen to an exact
|
|
unbounded representation or return an error. Silent loss of precision
|
|
is forbidden. Implementations \MAY{} use any representation that
|
|
meets this contract, including packed inline-or-heap-promoted designs.
|
|
\end{requirement}
|
|
|
|
\subsection{Recommended Implementation: Inline-or-Promoted}
|
|
\label{sec:time:impl}
|
|
|
|
The recommended implementation packs the common case (denominators
|
|
under $2^{32}$) into 8 bytes and promotes to a heap-allocated
|
|
arbitrary-precision rational on overflow. This is normative for
|
|
conformance only inasmuch as the observable behavior must match;
|
|
it is illustrative as a reference design.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
/// A musical time value: position or duration. Exact rational.
|
|
pub enum RationalTime {
|
|
/// Inline case: fits in 8 bytes.
|
|
Small(SmallRational),
|
|
|
|
/// Promoted case: heap-allocated arbitrary-precision rational.
|
|
/// Used only when arithmetic overflows the inline range.
|
|
Large(Arc<BigRational>),
|
|
}
|
|
|
|
/// The inline rational: numerator i32, denominator NonZeroU32.
|
|
/// Always normalized: gcd(|numerator|, denominator) == 1.
|
|
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
|
|
pub struct SmallRational {
|
|
numerator: i32,
|
|
denominator: NonZeroU32,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\begin{requirement}
|
|
\label{req:time:rational-normalization}
|
|
All instances of the small rational \MUST{} be normalized: the
|
|
greatest common divisor of the absolute value of the numerator and
|
|
the denominator \MUST{} equal one, and the denominator \MUST{} be
|
|
strictly positive. The constructor \MUST{} enforce normalization.
|
|
\end{requirement}
|
|
|
|
\subsection{Promotion and Demotion}
|
|
|
|
Arithmetic operations on two small rationals are performed in widened
|
|
intermediate types ($i64$ numerator, $u64$ denominator) and the
|
|
normalized result is examined. If the result fits in the small
|
|
representation, it is returned as \texttt{Small}; otherwise it is
|
|
returned as \texttt{Large}.
|
|
|
|
\begin{requirement}
|
|
\label{req:time:rational-promotion}
|
|
Arithmetic that exceeds the small representation's range \MUST{}
|
|
silently promote to the large representation. The user of the type
|
|
\MUST{} observe no behavioral difference between small-only and
|
|
small-or-large arithmetic; the contract is exact arithmetic on
|
|
exact rationals.
|
|
|
|
Implementations \MAY{} demote a \texttt{Large} value back to
|
|
\texttt{Small} when its normalized representation fits, but
|
|
\MUSTNOT{} make demotion observable (e.g., by changing
|
|
equality or hash behavior across the variant boundary).
|
|
\end{requirement}
|
|
|
|
\subsection{Equality and Ordering}
|
|
|
|
Two rational time values are equal if and only if their normalized
|
|
representations represent the same rational number. Equality between
|
|
\texttt{Small} and \texttt{Large} variants \MUST{} compare the
|
|
underlying values, not the variants.
|
|
|
|
Ordering is the standard rational ordering. Comparison between rationals
|
|
in the small representation is performed by cross-multiplication in
|
|
widened intermediate types to avoid overflow.
|
|
|
|
\subsection{Units}
|
|
|
|
The unit of musical time is the \emph{whole note}. A quarter note has
|
|
duration $\frac{1}{4}$. A triplet eighth has duration $\frac{1}{12}$.
|
|
A quintuplet sixteenth inside a duplet half (an unusual but legal
|
|
construct) has duration $\frac{1}{2} \cdot \frac{1}{2} \cdot \frac{1}{5}
|
|
= \frac{1}{20}$.
|
|
|
|
\begin{requirement}
|
|
\label{req:time:whole-note-unit}
|
|
The whole note \MUST{} be the unit of musical time. The duration of
|
|
a whole note is the rational $\frac{1}{1}$. All other durations are
|
|
expressed as rationals relative to the whole note.
|
|
\end{requirement}
|
|
|
|
\begin{rationale}
|
|
The whole note is the only unit under which durations form a
|
|
multiplicative algebra with $1$ as identity. Quarter-note units would
|
|
make ``the duration of a whole note'' equal to $4$, which is
|
|
awkward; PPQ-tick units would force a choice of resolution. The
|
|
whole-note unit also matches the etymology of note-value names: a
|
|
half note is half of a whole note.
|
|
\end{rationale}
|
|
|
|
\section{Position and Duration as Distinct Types}
|
|
\label{sec:time:newtypes}
|
|
|
|
The rational time representation is shared between two distinct types:
|
|
\texttt{MusicalPosition} and \texttt{MusicalDuration}. Both wrap a
|
|
\texttt{RationalTime}. Their distinction is enforced at the type level
|
|
to prevent algebraic errors.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
/// A point in musical time, relative to the origin of a time region.
|
|
#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
|
pub struct MusicalPosition(RationalTime);
|
|
|
|
/// A span of musical time.
|
|
#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
|
pub struct MusicalDuration(RationalTime);
|
|
|
|
impl Add<MusicalDuration> for MusicalPosition {
|
|
type Output = MusicalPosition;
|
|
// ...
|
|
}
|
|
|
|
impl Add<MusicalDuration> for MusicalDuration {
|
|
type Output = MusicalDuration;
|
|
// ...
|
|
}
|
|
|
|
impl Sub<MusicalPosition> for MusicalPosition {
|
|
type Output = MusicalDuration;
|
|
// ...
|
|
}
|
|
|
|
// MusicalPosition + MusicalPosition is intentionally not implemented.
|
|
\end{lstlisting}
|
|
|
|
\begin{requirement}
|
|
\label{req:time:position-duration-types}
|
|
Implementations \MUST{} provide distinct types for position and
|
|
duration. The type system \MUST{} prevent adding two positions. The
|
|
difference of two positions \MUST{} yield a duration; the sum of a
|
|
position and a duration \MUST{} yield a position.
|
|
\end{requirement}
|
|
|
|
\begin{rationale}
|
|
The illustrative Rust signatures derive \texttt{Clone}, not
|
|
\texttt{Copy}, because \texttt{RationalTime} contains an
|
|
\texttt{Arc<BigRational>} variant that cannot satisfy
|
|
\texttt{Copy}. Implementations \MAY{} provide \texttt{Copy}
|
|
semantics through a compact inline-or-pointer-tagged representation
|
|
whose \texttt{Large} case is a non-owning interior pointer rather
|
|
than an \texttt{Arc}; such implementations satisfy the contract
|
|
here. Conformance does not require \texttt{MusicalPosition} or
|
|
\texttt{MusicalDuration} to be \texttt{Copy}.
|
|
\end{rationale}
|
|
|
|
\section{Wall-Clock Time}
|
|
\label{sec:time:wallclock}
|
|
|
|
Wall-clock time is the time measured by the playback engine, by SMPTE
|
|
timecode, and by external audio and video sources. It is distinct from
|
|
musical time and is represented as a fixed-point integer count of a
|
|
sub-second unit.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
/// A point in wall-clock time, in nanoseconds from a region origin.
|
|
/// 64-bit signed: range +/- ~292 years.
|
|
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
|
pub struct WallClockTime(i64); // nanoseconds
|
|
|
|
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
|
pub struct WallClockDuration(i64); // nanoseconds
|
|
\end{lstlisting}
|
|
|
|
\begin{requirement}
|
|
\label{req:time:nanosecond-wallclock}
|
|
Wall-clock time \MUST{} be a fixed-point integer count of
|
|
nanoseconds. Floating-point wall-clock time is forbidden in stored
|
|
data. Implementations \MAY{} convert to floating-point for transient
|
|
computation provided no stored or serialized value loses precision.
|
|
\end{requirement}
|
|
|
|
\begin{rationale}
|
|
Nanosecond resolution provides over four orders of magnitude more
|
|
precision than the human ear can resolve and aligns with the
|
|
resolution conventions of professional audio interfaces and
|
|
SMPTE-derived clocks. The 292-year range is sufficient for any
|
|
conceivable musical work.
|
|
\end{rationale}
|
|
|
|
\subsection{Wall-Clock and Musical Time Coexist}
|
|
|
|
A score event may be anchored in either musical or wall-clock time.
|
|
Musical-time anchors are the default for ordinary notation; wall-clock
|
|
anchors are used for film hit points, audio reference markers, fixed
|
|
electronic parts in scores with live ensemble, and similar
|
|
sync-critical content. Both anchor types may coexist in the same
|
|
score, possibly on the same staff.
|
|
|
|
Conversion between musical and wall-clock time is performed through
|
|
the tempo map (Section~\ref{sec:time:tempomap}).
|
|
|
|
\section{Time Anchors}
|
|
\label{sec:time:anchors}
|
|
|
|
Stored references to points in time \MUST{} be expressed as
|
|
\emph{time anchors}: references to identified objects plus offsets.
|
|
Absolute positions \MUSTNOT{} be stored where they could be invalidated
|
|
by edits to the score.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub enum TimeAnchor {
|
|
/// Anchored to a specific event. Survives edits that do not
|
|
/// delete the event.
|
|
Event { id: EventId, offset: AnchorOffset },
|
|
|
|
/// Anchored to a measure boundary. Survives measure reordering
|
|
/// (the anchor follows the measure).
|
|
Measure {
|
|
id: MeasureId,
|
|
position: MeasurePosition,
|
|
offset: AnchorOffset,
|
|
},
|
|
|
|
/// Anchored to the start or end of a region.
|
|
Region { id: RegionId, edge: RegionEdge, offset: AnchorOffset },
|
|
|
|
/// Anchored to absolute wall-clock time within the score.
|
|
/// Used for film and audio sync.
|
|
WallClock { time: WallClockTime },
|
|
}
|
|
|
|
/// An offset applied to an anchor target. The variant kind is
|
|
/// constrained by the target's enclosing region's time model.
|
|
pub enum AnchorOffset {
|
|
/// Offset in musical time. Valid for targets in metric regions
|
|
/// and for aleatoric regions whose anchoring discipline
|
|
/// admits musical coordinates.
|
|
Musical(MusicalDuration),
|
|
|
|
/// Offset in wall-clock time. Valid for targets in proportional
|
|
/// regions and for aleatoric regions whose anchoring discipline
|
|
/// admits wall-clock coordinates.
|
|
WallClock(WallClockDuration),
|
|
|
|
/// No offset; the anchor refers to the target's reference point
|
|
/// exactly. Valid in any region.
|
|
Zero,
|
|
}
|
|
|
|
pub enum MeasurePosition { Start, End }
|
|
pub enum RegionEdge { Start, End }
|
|
\end{lstlisting}
|
|
|
|
\begin{requirement}
|
|
\label{req:time:anchor-references}
|
|
Stored \emph{references to external time points} (cross-cutting
|
|
endpoints, marker locations, attachment anchors, anything
|
|
pointing at an object outside the referencing object's own
|
|
voice and region) \MUST{} use the \texttt{TimeAnchor} type.
|
|
Absolute musical positions \MUSTNOT{} appear as such references
|
|
in stored data: a reference to ``measure 47, beat 3'' that does
|
|
not survive a measure insertion in measure 12 is a broken
|
|
reference.
|
|
|
|
An event's own position within its owning voice and region
|
|
\MAY{} be stored as an \texttt{EventPosition} (carrying a
|
|
\texttt{MusicalPosition} for events in metric regions, or a
|
|
\texttt{WallClockTime} for events in proportional regions,
|
|
etc.). Event-local positions are part of the event's intrinsic
|
|
data, not references to other objects. Operations that shift a
|
|
voice or region update the contained events' positions
|
|
directly; they do not need anchor indirection because the
|
|
containment relationship makes the dependency explicit.
|
|
|
|
Implementations \MAY{} compute and cache absolute positions
|
|
from anchors for performance; such caches \MUST{} be
|
|
invalidated when their underlying anchored objects change.
|
|
\end{requirement}
|
|
|
|
\begin{requirement}
|
|
\label{req:time:anchor-offset-kind}
|
|
The variant of an \texttt{AnchorOffset} \MUST{} agree with the
|
|
time model of the anchor target's enclosing region:
|
|
|
|
\begin{itemize}
|
|
\item For targets in metric regions: \texttt{AnchorOffset::Musical}
|
|
or \texttt{AnchorOffset::Zero}.
|
|
\item For targets in proportional regions:
|
|
\texttt{AnchorOffset::WallClock} or \texttt{AnchorOffset::Zero}.
|
|
\item For targets in aleatoric regions: any offset variant
|
|
consistent with the region's
|
|
\texttt{AleatoricAnchoringDiscipline}
|
|
(Section~\ref{sec:graph:aleatoric-discipline}).
|
|
\end{itemize}
|
|
|
|
Implementations \MUST{} reject anchors whose offset variant
|
|
contradicts the target region's time model.
|
|
\end{requirement}
|
|
|
|
\subsection{Anchor Resolution}
|
|
|
|
Resolving a time anchor to an absolute position is a pure function of
|
|
the score graph: locate the anchored object, query its position within
|
|
its region, and add the offset. Resolution \MUST{} be deterministic.
|
|
|
|
\subsection{Anchor Stability Under Edits}
|
|
|
|
The chosen anchor type determines what edits the anchor survives:
|
|
|
|
\begin{itemize}
|
|
\item An \texttt{Event} anchor survives any edit that does not delete
|
|
the anchored event. Edits that move the event (insertions before
|
|
it, tempo changes) move the anchor with it.
|
|
\item A \texttt{Measure} anchor survives insertions and deletions of
|
|
other measures, and survives reordering. It is invalidated only by
|
|
deletion of the anchored measure.
|
|
\item A \texttt{Region} anchor survives edits within the region. It
|
|
is invalidated only by deletion of the region.
|
|
\item A \texttt{WallClock} anchor is unaffected by musical edits but
|
|
may correspond to different musical positions after tempo changes.
|
|
\end{itemize}
|
|
|
|
\begin{requirement}
|
|
\label{req:time:orphaned-anchor-handling}
|
|
When an anchored object is deleted, anchors targeting it \MUST{} be
|
|
either re-anchored to a surviving object or marked as orphaned. The
|
|
semantic operations chapter (Chapter~\ref{ch:semops}) specifies
|
|
re-anchoring rules per operation.
|
|
\end{requirement}
|
|
|
|
\section{Time Signatures and Meter}
|
|
\label{sec:time:meter}
|
|
|
|
A time signature defines the metric organization of a passage: where
|
|
bar lines fall, where beats group, and what accent pattern is implied.
|
|
Time signatures are objects in the score graph; they are not merely
|
|
display numerator/denominator pairs.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct TimeSignature {
|
|
/// Display form. Determines what the performer sees.
|
|
pub display: TimeSignatureDisplay,
|
|
|
|
/// The total duration of one measure under this signature.
|
|
pub measure_duration: MusicalDuration,
|
|
|
|
/// Beat groupings within the measure. Drives beaming, accent
|
|
/// inference, and the notational-decomposition pre-pass.
|
|
pub beat_groups: Vec<BeatGroup>,
|
|
}
|
|
|
|
pub enum TimeSignatureDisplay {
|
|
/// Standard: a numerator over a power-of-two denominator.
|
|
Standard { numerator: u16, denominator: PowerOfTwo },
|
|
|
|
/// Compound display: e.g., 3+2+3/8.
|
|
Compound { numerators: Vec<u16>, denominator: PowerOfTwo },
|
|
|
|
/// Irrational meter: denominator is not a power of two.
|
|
/// e.g., 4/6, 5/12. The denominator names a non-power-of-two
|
|
/// note value.
|
|
Irrational { numerator: u16, denominator: NonZeroU16 },
|
|
|
|
/// Mixed denominators: e.g., 1/4 + 1/8 + 1/16 displayed additively.
|
|
MixedDenominators { components: Vec<(u16, NonZeroU16)> },
|
|
|
|
/// No visible time signature (free meter or proportional).
|
|
None,
|
|
|
|
/// Custom symbol (cut time, common time, etc.) or
|
|
/// grammar-specific.
|
|
Symbolic(SymbolId),
|
|
}
|
|
|
|
pub struct BeatGroup {
|
|
/// Duration of this beat group.
|
|
pub duration: MusicalDuration,
|
|
|
|
/// Subdivision hint for beaming and accent.
|
|
pub subdivision: Option<MusicalDuration>,
|
|
|
|
/// Accent strength relative to other beat groups in the measure.
|
|
/// Higher values indicate stronger accents.
|
|
pub accent: u8,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\begin{requirement}
|
|
\label{req:time:beat-group-sum}
|
|
The sum of the durations of a time signature's beat groups \MUST{}
|
|
equal the measure duration. Implementations \MUST{} reject time
|
|
signatures whose beat groups do not sum to the measure duration.
|
|
\end{requirement}
|
|
|
|
\subsection{Irrational Meters}
|
|
|
|
Time signatures whose denominator is not a power of two
|
|
(e.g., 4/6, 5/12) are first-class. Their measure duration is computed
|
|
as a rational: a 4/6 measure has duration $\frac{4}{6} = \frac{2}{3}$
|
|
whole notes. No special case in the time model; only the display layer
|
|
treats the unusual denominator.
|
|
|
|
\subsection{Polymetric and Polytemporal Scores}
|
|
|
|
A score \MAY{} have different time signatures concurrent on different
|
|
staves. Each staff carries its own meter sequence; bar lines align where
|
|
the rational positions coincide, and may misalign otherwise. The score
|
|
graph chapter (Chapter~\ref{ch:graph}) defines how concurrent meters
|
|
are represented; this chapter establishes only that the time model
|
|
supports them.
|
|
|
|
\section{Tempo and the Tempo Map}
|
|
\label{sec:time:tempomap}
|
|
|
|
The tempo map is the function mapping musical positions to wall-clock
|
|
positions. It is a piecewise definition over musical time:
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct TempoMap {
|
|
/// Piecewise segments, in non-overlapping musical-time order.
|
|
pub segments: Vec<TempoSegment>,
|
|
|
|
/// Tempo before the first segment, if any. None means the first
|
|
/// segment's start_tempo applies from time zero.
|
|
pub initial: Option<Tempo>,
|
|
}
|
|
|
|
pub struct TempoSegment {
|
|
/// Start of this segment, as a TimeAnchor.
|
|
pub start: TimeAnchor,
|
|
|
|
/// End of this segment. None means "valid until the next segment
|
|
/// in segments order, or until the end of the score if this is
|
|
/// the final segment." Implementations resolve None lazily.
|
|
pub end: Option<TimeAnchor>,
|
|
|
|
/// Tempo at the start of this segment.
|
|
pub start_tempo: Tempo,
|
|
|
|
/// Tempo at the end of this segment.
|
|
/// Required if shape != Constant; ignored (and SHOULD be None)
|
|
/// if shape == Constant. A Constant segment holds start_tempo
|
|
/// throughout.
|
|
pub end_tempo: Option<Tempo>,
|
|
|
|
/// Shape of the tempo change over this segment.
|
|
pub shape: TempoShape,
|
|
}
|
|
|
|
pub enum TempoShape {
|
|
/// Constant tempo throughout: end_tempo MUST be None or equal to
|
|
/// start_tempo.
|
|
Constant,
|
|
|
|
/// Linear interpolation from start_tempo to end_tempo.
|
|
Linear,
|
|
|
|
/// Exponential (continuous-rate) interpolation from start_tempo
|
|
/// to end_tempo.
|
|
Exponential,
|
|
|
|
/// Arbitrary curve, parameterized by control points. The control
|
|
/// points define intermediate tempos between start_tempo and
|
|
/// end_tempo.
|
|
Curve(TempoCurve),
|
|
}
|
|
|
|
pub struct Tempo {
|
|
/// Beats per minute. The beat unit is part of the tempo
|
|
/// specification: a quarter-note BPM and a dotted-quarter BPM
|
|
/// at the same numeric value differ in actual rate.
|
|
pub bpm: f64,
|
|
|
|
/// Note value taken as one beat.
|
|
pub beat_unit: MusicalDuration,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\begin{requirement}
|
|
\label{req:time:tempo-segment-order}
|
|
The tempo map's segments \MUST{} be non-overlapping when resolved
|
|
to absolute musical positions, and \MUST{} appear in
|
|
monotonically-increasing start order. Two adjacent segments where
|
|
the earlier segment's resolved end equals the later segment's
|
|
resolved start form a continuous tempo curve; the later segment's
|
|
\texttt{start\_tempo} \SHOULD{} equal the earlier segment's resolved
|
|
end tempo when continuity is intended.
|
|
|
|
A segment with \texttt{end == None} extends until the next
|
|
segment's resolved start or, if no later segment exists, until the
|
|
end of the score. Implementations \MUST{} resolve open-ended
|
|
segments deterministically.
|
|
|
|
At musical positions not covered by any segment (gaps between
|
|
segments, or before the first segment when \texttt{initial} is
|
|
\texttt{None}), the tempo \MUST{} be the most recent applicable
|
|
segment's terminating tempo, or the next applicable segment's
|
|
\texttt{start\_tempo} if no earlier segment exists.
|
|
\end{requirement}
|
|
|
|
\subsection{Conversion}
|
|
|
|
Conversion between musical and wall-clock time integrates the tempo map.
|
|
For constant segments, conversion is multiplication. For linear and
|
|
exponential segments, conversion has a closed-form solution. For curve
|
|
segments, conversion is numerical.
|
|
|
|
\begin{requirement}
|
|
\label{req:time:linear-interpolates-speed}
|
|
For a \texttt{TempoShape::Linear} segment, the quantity that varies
|
|
linearly across the segment is \emph{speed} --- whole notes per
|
|
second --- as a function of fractional position within the segment,
|
|
\emph{not} beats-per-minute and \emph{not} beat period. Speed is
|
|
derived from a \texttt{Tempo} as
|
|
$\text{speed} = \text{bpm} \times w(\text{beat\_unit}) / 60$, where
|
|
$w(\cdot)$ is the beat unit's value in whole notes. Writing
|
|
$s_0, s_1$ for the segment's start and end speeds and
|
|
$u \in [0,1]$ for fractional position, $s(u) = s_0 + (s_1 - s_0)\,u$,
|
|
and the wall-clock duration of the segment of musical length
|
|
$\ell$ is the closed form
|
|
$\int_0^1 \ell\,\mathrm{d}u / s(u) = (\ell / (s_1 - s_0))\,
|
|
\ln(s_1 / s_0)$ (and $\ell / s_0$ when $s_1 = s_0$).
|
|
|
|
A \texttt{TempoShape::Exponential} segment interpolates speed
|
|
\emph{geometrically}: $s(u) = s_0\,(s_1/s_0)^{u}$, i.e. a constant
|
|
continuous rate of change of speed.
|
|
\end{requirement}
|
|
|
|
\begin{rationale}
|
|
``Linear interpolation from \texttt{start\_tempo} to
|
|
\texttt{end\_tempo}'' is ambiguous about which quantity is linear:
|
|
bpm, period, or speed. We pin \emph{speed} because it is the only
|
|
beat-unit-agnostic choice. A tempo map may change beat unit across
|
|
segments (a \texttt{quarter = 120} segment abutting a
|
|
\texttt{dotted-quarter = 80} segment); interpolating bpm would make
|
|
the wall-clock schedule depend on the arbitrary beat unit chosen to
|
|
notate each endpoint, whereas interpolating speed depends only on
|
|
the sounding rate. When both endpoints share a beat unit, speed-linear
|
|
and bpm-linear coincide, so the common case matches a conductor's
|
|
``linear accelerando'' intuition. Because this choice fixes the
|
|
derived wall-clock schedule for every playback engine, it is stated
|
|
normatively rather than left to the implementation.
|
|
\end{rationale}
|
|
|
|
\begin{requirement}
|
|
\label{req:time:tempo-map-conversion}
|
|
The tempo map \MUST{} expose two conversion functions:
|
|
\texttt{musical\_to\_wallclock} and \texttt{wallclock\_to\_musical}.
|
|
Both \MUST{} be deterministic: given identical tempo maps and inputs,
|
|
they \MUST{} produce identical outputs across runs and platforms.
|
|
Numerical integration and inversion, where used, \MUST{} use a
|
|
deterministic algorithm with documented tolerance and iteration
|
|
bounds.
|
|
|
|
Tempo curves \MUST{} be monotonically increasing in musical time
|
|
(i.e., tempo may vary but musical time always advances) so that
|
|
\texttt{wallclock\_to\_musical} is single-valued. Implementations
|
|
\MUST{} reject tempo segments whose shape produces a non-monotonic
|
|
mapping (such curves are musically meaningless).
|
|
\end{requirement}
|
|
|
|
\begin{openquestion}
|
|
The specific numerical-integration algorithm for
|
|
\texttt{TempoShape::Curve} (Romberg integration, adaptive
|
|
Gauss-Kronrod, or a normative method) and the specific
|
|
root-finding algorithm for \texttt{wallclock\_to\_musical} are
|
|
unresolved. Both algorithms affect canonical state and must
|
|
therefore receive one of the dispositions in
|
|
Appendix~\ref{app:determinism}
|
|
Section~\ref{sec:det:open}: normatively specified in this
|
|
document or a named companion, profile-declared by versioned
|
|
identifier, or explicitly marked non-canonical. Until that
|
|
disposition is made, cross-implementation byte equality of
|
|
derived canonical state (notably tempo-driven audio
|
|
scheduling) \MUSTNOT{} be claimed.
|
|
\end{openquestion}
|
|
|
|
\begin{rationale}
|
|
Determinism of tempo conversion is necessary for reproducible audio
|
|
output, click-track stability, and SMPTE sync. Non-determinism here
|
|
would produce playback drift that compounds over long pieces.
|
|
\end{rationale}
|
|
|
|
\section{Sounding Duration and Notational Decomposition}
|
|
\label{sec:time:notrhythm}
|
|
|
|
An event has a single \emph{sounding duration} (an exact rational).
|
|
Its \emph{notational decomposition} is the sequence of notehead values,
|
|
augmentation dots, and ties used to draw that duration on the staff.
|
|
|
|
The decomposition is computed from the sounding duration by a
|
|
deterministic pre-pass as a \emph{canonical derived annotation} ---
|
|
recomputed on materialization, never stored graph state --- under the
|
|
same output model as the spelling pre-pass
|
|
(Section~\ref{sec:pitch:prepass}). The user may accept the inferred
|
|
decomposition or override it by authoring a decomposition attachment,
|
|
which takes precedence.
|
|
|
|
\subsection{The Notational Decomposition}
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct NotationalDecomposition {
|
|
/// Sequence of notated components. Their durations sum to the
|
|
/// event's sounding duration.
|
|
pub components: Vec<NotatedComponent>,
|
|
|
|
/// Provenance, analogous to SpellingSource.
|
|
pub source: DecompositionSource,
|
|
}
|
|
|
|
pub struct NotatedComponent {
|
|
/// Base note value: whole, half, quarter, eighth, etc.
|
|
pub base_value: NoteValue,
|
|
|
|
/// Number of augmentation dots (0..=4 typical).
|
|
pub dots: u8,
|
|
|
|
/// Tuplet membership, if any.
|
|
pub tuplet: Option<TupletId>,
|
|
|
|
/// Whether this component is tied to the next.
|
|
pub tied_to_next: bool,
|
|
}
|
|
|
|
pub enum DecompositionSource {
|
|
UserChosen,
|
|
Inferred,
|
|
Imported { format: ForeignFormatId },
|
|
Propagated { from: EventId },
|
|
}
|
|
\end{lstlisting}
|
|
|
|
The structure deliberately mirrors the spelling attachment model
|
|
(Section~\ref{sec:pitch:spelling}): same sources, same source-rank
|
|
discipline, same pre-pass output model. Unlike spelling, decomposition
|
|
precedence is \emph{not configurable} (ratified Pass~12): the fixed
|
|
default source order \texttt{UserChosen} $>$ \texttt{Imported} $>$
|
|
\texttt{Propagated} $>$ \texttt{Inferred} applies, with the score's
|
|
canonical attachment order as tie-break among equal-rank authored
|
|
attachments; \texttt{DecompositionAttachment} carries no
|
|
\texttt{priority} field and the graph carries no
|
|
\texttt{DecompositionPrecedence} configuration. Making precedence
|
|
configurable would add a canonical \texttt{Score} field (a
|
|
schema-major change under the Binary Format companion's evolution
|
|
rule) for which no consumer exists; the decision may be revisited at
|
|
a future schema major if a use case appears.
|
|
|
|
\subsection{The Decomposition Pre-Pass}
|
|
|
|
When a score is materialized, the decomposition pre-pass computes
|
|
inferred decompositions, reported as derived annotations; an authored
|
|
higher-precedence attachment overrides the inferred value in the
|
|
resolved annotation. An authored attachment targeting an event the
|
|
pre-pass produces \emph{no} inferred decomposition for (ungriddable,
|
|
non-metric, or an inapplicable event kind) surfaces as the resolved
|
|
annotation on its own, per
|
|
Requirement~\ref{req:pitch:authored-uninferred} --- authoring is how
|
|
a user notates exactly what the algorithm cannot infer. The
|
|
algorithm is, in outline:
|
|
|
|
\begin{enumerate}
|
|
\item For each event in time order within a voice and measure,
|
|
examine the sounding duration and the current beat-group context.
|
|
\item Decompose the duration into the longest leading note value
|
|
that fits within the current beat group without crossing a stronger
|
|
accent boundary.
|
|
\item If the duration is not exhausted, produce a tie and continue
|
|
decomposition from the next beat-group boundary.
|
|
\item Augmentation dots are applied where they shorten the
|
|
decomposition without crossing an accent boundary that the dot would
|
|
obscure.
|
|
\item Tuplet membership is recorded for events that fall within a
|
|
tuplet group (Section~\ref{sec:time:tuplets}).
|
|
\end{enumerate}
|
|
|
|
\begin{requirement}
|
|
\label{req:time:decomposition-determinism}
|
|
The decomposition pre-pass \MUST{} be deterministic: given
|
|
identical materialized graph, configuration, and algorithm
|
|
version, it \MUST{} produce identical decompositions. Its output
|
|
is a derived annotation, never stored graph state. Implementations
|
|
\MAY{} cache and \MAY{} recompute incrementally (re-running only
|
|
the affected voice and measure, with propagation only as far as
|
|
the context is altered); neither may be observable in the
|
|
annotations produced.
|
|
\end{requirement}
|
|
|
|
\begin{requirement}
|
|
\label{req:time:decomposition-algorithm}
|
|
\textbf{Decomposition algorithm disposition (ratified Pass 12).}
|
|
The decomposition pre-pass receives the \emph{profile-declared
|
|
by versioned identifier} disposition of
|
|
Appendix~\ref{app:determinism} Section~\ref{sec:det:open}. The
|
|
reserved identifier \texttt{DecompositionAlgorithmId}
|
|
\texttt{"default"} denotes, at version~1, the integer-grid
|
|
metric splitter, whose scope bounds are part of its normative
|
|
definition:
|
|
\begin{itemize}
|
|
\item a single governing meter per region --- a mid-region
|
|
meter change reduces cleanly into the region's grid (the
|
|
Operation Catalog pins those semantics), but derived
|
|
notation honours only the first governing meter until a
|
|
wider algorithm version lands;
|
|
\item the region origin is assumed to fall on a barline
|
|
(anacrusis/pickup handling deferred);
|
|
\item compound-meter beat grouping uses the dyadic default;
|
|
\item tuplet nesting and cross-beat tuplet members are out of
|
|
scope;
|
|
\item at most one augmentation dot
|
|
(\texttt{MAX\_DOTS}~$=$~1); a double-dotted value is
|
|
written as tied components --- correct, if not the most
|
|
compact.
|
|
\end{itemize}
|
|
A wider algorithm is a \emph{version bump}, which
|
|
deterministically invalidates derived output under the
|
|
derived-annotation model --- no state migration. Style-choice
|
|
selection among multiple valid decompositions (e.g.,
|
|
$\frac{3}{8}$ in $\frac{4}{4}$ as a dotted quarter versus a
|
|
quarter tied to an eighth) is fixed by the versioned algorithm;
|
|
user-configurable alternate rule sets, if ever wanted, are new
|
|
registered identifiers. A profile requesting any identifier
|
|
other than a registered one \MUST{} error; implementations
|
|
\MUSTNOT{} silently substitute.
|
|
\end{requirement}
|
|
|
|
\section{Tuplets as Grouping Objects}
|
|
\label{sec:time:tuplets}
|
|
|
|
A tuplet is a grouping object in the score graph that records
|
|
notational intent: that a span of events forms a triplet, quintuplet,
|
|
or other irregular grouping with a visible bracket and number. Tuplets
|
|
do not modify the sounding durations of their members; sounding
|
|
durations are always concrete rationals.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct Tuplet {
|
|
pub id: TupletId,
|
|
|
|
/// The actual:notated ratio. A triplet is 3:2; a quintuplet is
|
|
/// 5:4; an irregular grouping like 7:4 has ratio 7 over 4.
|
|
pub ratio: TupletRatio,
|
|
|
|
/// Range of events in this tuplet, by event ID.
|
|
pub members: Vec<EventId>,
|
|
|
|
/// Display: bracket style, number placement, parentheses, etc.
|
|
pub display: TupletDisplay,
|
|
|
|
/// Nesting: if this tuplet is itself inside another, the parent.
|
|
pub parent: Option<TupletId>,
|
|
}
|
|
|
|
pub struct TupletRatio {
|
|
// Fields are private: a TupletRatio is constructed only through the
|
|
// checked `new`, so a degenerate ratio is never representable (see
|
|
// the requirement below). The fields are read via `actual()`/`notated()`.
|
|
actual: u32,
|
|
notated: u32,
|
|
}
|
|
|
|
impl TupletRatio {
|
|
/// Returns `None` for a degenerate ratio (either term zero, or
|
|
/// `actual == notated`); otherwise the well-formed ratio.
|
|
pub fn new(actual: u32, notated: u32) -> Option<TupletRatio> { /* checked */ }
|
|
pub fn actual(&self) -> u32 { /* ... */ }
|
|
pub fn notated(&self) -> u32 { /* ... */ }
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\begin{requirement}
|
|
\label{req:time:tuplet-ratio-construction}
|
|
A \texttt{TupletRatio} is \emph{degenerate} if either term is zero
|
|
or if \texttt{actual == notated} (a ratio that expresses no
|
|
augmentation or diminution). Degenerate ratios \MUST{} be rejected
|
|
at construction: a conforming constructor of \texttt{TupletRatio}
|
|
(and therefore of any \texttt{Tuplet}) \MUST{} fail rather than
|
|
produce a value with \texttt{actual == 0}, \texttt{notated == 0}, or
|
|
\texttt{actual == notated}. This is a construction-time rejection,
|
|
not a runtime graph invariant (Section~\ref{sec:graph:invariants}):
|
|
a degenerate ratio is never a representable graph state, so no
|
|
invariant restores it after the fact.
|
|
\end{requirement}
|
|
|
|
\subsection{Tuplet Nesting}
|
|
|
|
Tuplets \MAY{} nest to arbitrary depth. The sounding duration of an
|
|
event inside nested tuplets is computed directly from its rational
|
|
duration, not from the tuplet ratios; the ratios are notational and
|
|
analytical.
|
|
|
|
\begin{rationale}
|
|
Storing sounding durations directly (rather than as base-value times
|
|
a stack of tuplet ratios) keeps the time model regular: every event
|
|
has a single rational duration, full stop. Moving a tuplet, splitting
|
|
it, or deleting it never alters the durations of its members. Tuplets
|
|
thus become a pure annotation layer, which dramatically simplifies
|
|
edit operations.
|
|
\end{rationale}
|
|
|
|
\subsection{Tuplet Consistency}
|
|
|
|
\begin{requirement}
|
|
\label{req:time:tuplet-duration-consistency}
|
|
A tuplet's notated ratio \MUST{} be consistent with the sounding
|
|
durations of its members: the sum of member durations \MUST{} equal
|
|
the duration that the tuplet's notated value indicates when scaled
|
|
by the ratio. For example, a 3:2 eighth-note triplet of three members
|
|
has total sounding duration $\frac{1}{4}$; the sum of the three
|
|
member durations \MUST{} equal $\frac{1}{4}$.
|
|
|
|
This is a structural invariant. Edit operations that would violate
|
|
it \MUST{} either adjust member durations or be rejected. The
|
|
semantic operations chapter specifies which.
|
|
\end{requirement}
|
|
|
|
\section{Region Time Models}
|
|
\label{sec:time:regions}
|
|
|
|
A region of the score declares one of three time models. The declared
|
|
model determines how positions within the region are interpreted,
|
|
displayed, and played back.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub enum RegionTimeModel {
|
|
/// Metric time: measures, beats, exact rational positions.
|
|
/// The dominant model for Western notated music.
|
|
Metric(MetricTimeModel),
|
|
|
|
/// Proportional time: events placed in space, where horizontal
|
|
/// position is wall-clock time. No measures, no beats.
|
|
Proportional(ProportionalTimeModel),
|
|
|
|
/// Aleatoric time: events with partial or unspecified ordering.
|
|
Aleatoric(AleatoricTimeModel),
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsection{Metric Time}
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct MetricTimeModel {
|
|
/// Sequence of meter changes within this region.
|
|
pub meters: Vec<MeterChange>,
|
|
|
|
/// Tempo map for this region. May inherit from the score.
|
|
pub tempo: TempoMapReference,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
Metric regions have measures, beats, and time signatures as described
|
|
in Sections~\ref{sec:time:meter} and~\ref{sec:time:tempomap}. Positions
|
|
within them are \texttt{MusicalPosition} values.
|
|
|
|
\subsection{Proportional Time}
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct ProportionalTimeModel {
|
|
/// Total wall-clock duration of the region.
|
|
pub duration: WallClockDuration,
|
|
|
|
/// Horizontal space per second. Drives the engraver's spacing.
|
|
/// Stored as a layout hint; the actual spacing is decided by the
|
|
/// constraint solver.
|
|
pub space_per_second: SpaceUnit,
|
|
|
|
/// Optional reference grid markings (vertical lines every N
|
|
/// seconds, for example) for performer reference.
|
|
pub grid: Option<ProportionalGrid>,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
Events in proportional regions carry \texttt{WallClockTime} positions
|
|
directly. There are no measures, no beats, no rational durations.
|
|
Notational duration is computed from wall-clock duration by the
|
|
notational decomposition pre-pass using a proportional-mode rule set,
|
|
which generally produces unbeamed events with explicit duration
|
|
notations or graphic-duration symbols.
|
|
|
|
\begin{requirement}
|
|
\label{req:time:proportional-region-time}
|
|
Events in a proportional region \MUST{} use \texttt{WallClockTime}
|
|
for positions and \texttt{WallClockDuration} for durations. The
|
|
tempo map \MUSTNOT{} be applied to convert proportional-region
|
|
positions; they are already in wall-clock time.
|
|
\end{requirement}
|
|
|
|
\subsection{Aleatoric Time}
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct AleatoricTimeModel {
|
|
/// Ordering constraints among events: a directed acyclic graph
|
|
/// where an edge a -> b means "a precedes b."
|
|
pub ordering: EventOrderingDAG,
|
|
|
|
/// Anchoring discipline: declares what event coordinate kinds
|
|
/// this region permits and whether duration-bound kinds may
|
|
/// be mixed. See Section~\ref{sec:time:aleatoric-anchoring}.
|
|
pub anchoring: AleatoricAnchoringDiscipline,
|
|
|
|
/// Optional per-event interval bounds. An event with start
|
|
/// bounds [t1, t2] may begin at any time in that window.
|
|
/// Bound kinds are constrained by the anchoring discipline.
|
|
pub bounds: HashMap<EventId, EventBounds>,
|
|
|
|
/// Approximate or maximum total duration, for layout.
|
|
pub duration_hint: WallClockDuration,
|
|
}
|
|
|
|
pub enum AleatoricAnchoringDiscipline {
|
|
/// Events in this region carry musical-time coordinates only.
|
|
Musical,
|
|
|
|
/// Events in this region carry wall-clock coordinates only.
|
|
WallClock,
|
|
|
|
/// Events may carry either kind, but each event's position and
|
|
/// duration kinds must agree with each other. Mixed-kind
|
|
/// duration bounds are not permitted.
|
|
EitherPerEvent,
|
|
|
|
/// Events may carry either kind freely, and duration bounds
|
|
/// may mix kinds (one bound musical, the other wall-clock).
|
|
/// Used for free-time scores synchronized to external media.
|
|
FreelyMixed,
|
|
}
|
|
|
|
pub struct EventBounds {
|
|
pub start: Option<TimeBounds>,
|
|
pub end: Option<TimeBounds>,
|
|
}
|
|
|
|
pub enum TimeBounds {
|
|
MusicalRange { min: MusicalPosition, max: MusicalPosition },
|
|
WallClockRange { min: WallClockTime, max: WallClockTime },
|
|
Unbounded,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
Aleatoric regions support both unordered event collections (any
|
|
permutation of events is valid) and time-bracket notation (events with
|
|
specified start and end windows, à la Cage's later notation). The DAG
|
|
encoding admits both as cases.
|
|
|
|
\subsubsection{Anchoring Discipline}
|
|
\label{sec:time:aleatoric-anchoring}
|
|
\label{sec:graph:aleatoric-discipline}
|
|
|
|
\begin{requirement}
|
|
\label{req:time:aleatoric-anchoring-discipline}
|
|
Every aleatoric region \MUST{} declare an
|
|
\texttt{AleatoricAnchoringDiscipline}. Events within the region
|
|
\MUST{} carry coordinate kinds consistent with the discipline:
|
|
|
|
\begin{itemize}
|
|
\item \texttt{Musical}: events use \texttt{EventPosition::Musical}
|
|
and \texttt{EventDuration::Musical} (or
|
|
\texttt{EventDuration::Indeterminate} whose bounds are musical).
|
|
\item \texttt{WallClock}: events use
|
|
\texttt{EventPosition::WallClock} and
|
|
\texttt{EventDuration::WallClock} (or
|
|
\texttt{EventDuration::Indeterminate} whose bounds are
|
|
wall-clock).
|
|
\item \texttt{EitherPerEvent}: events choose per-event, but an
|
|
event's position and duration kinds \MUST{} agree, and
|
|
\texttt{DurationBounds} \MUST{} use a single
|
|
\texttt{ConcreteDuration} variant.
|
|
\item \texttt{FreelyMixed}: no agreement required;
|
|
\texttt{DurationBounds} \MAY{} mix kinds.
|
|
\end{itemize}
|
|
|
|
Implementations \MUST{} reject events whose coordinate kinds
|
|
violate the region's declared discipline.
|
|
\end{requirement}
|
|
|
|
\begin{requirement}
|
|
\label{req:time:ordering-dag-acyclic}
|
|
The ordering DAG \MUST{} be acyclic. Cycles \MUST{} be rejected at
|
|
construction. Two events with no path between them in the DAG are
|
|
unordered.
|
|
\end{requirement}
|
|
|
|
\begin{rationale}
|
|
A DAG with optional interval bounds expresses every aleatoric form
|
|
the project intends to support: unordered collections (empty DAG),
|
|
partial orderings (sparse DAG), strict orderings (chain DAG), and
|
|
time-brackets (DAG plus bounds). Higher-level constructs
|
|
(mobile-form notation, statistical scores) can be built atop this
|
|
primitive in later specifications without altering the core.
|
|
\end{rationale}
|
|
|
|
\subsection{Concurrent Time Models}
|
|
|
|
Different staves in the same score \MAY{} declare different time
|
|
models. A metric solo line over a proportional aleatoric texture, for
|
|
example, is expressible: each staff's region declares its own model.
|
|
Synchronization between concurrent time models is by wall-clock time
|
|
or by explicit cue points (see Chapter~\ref{ch:graph}).
|
|
|
|
\section{The Notion of ``Now''}
|
|
\label{sec:time:now}
|
|
|
|
The core does not define playback state; it does not have a notion of
|
|
``current position'' or ``transport time.'' Such state belongs to the
|
|
audio engine, which consumes the core's time model. The core defines
|
|
only the static time structure of the score.
|
|
|
|
\section{Forward References}
|
|
|
|
\begin{itemize}
|
|
\item Event identifiers (\texttt{EventId}), measure identifiers
|
|
(\texttt{MeasureId}), and region identifiers (\texttt{RegionId})
|
|
are defined in Chapter~\ref{ch:graph}.
|
|
\item The full event taxonomy (pitched, unpitched, rest, indeterminate,
|
|
trajectory) is defined in Chapter~\ref{ch:graph}.
|
|
\item Re-anchoring rules for time anchors when their targets are
|
|
deleted are defined per-operation in Chapter~\ref{ch:semops}.
|
|
\item \texttt{SpaceUnit} is defined normatively in
|
|
Chapter~\ref{ch:graph} as of schema major~2 (it entered canonical
|
|
state through \texttt{StaffLineConfiguration}).
|
|
\texttt{TupletDisplay} and other purely display-oriented types are
|
|
defined in Chapter~\ref{ch:layout-ir}.
|
|
\end{itemize}
|
|
|
|
% ===========================================================================
|
|
\chapter{Tuning Systems and Pitch Spaces}
|
|
\label{ch:tuning}
|
|
|
|
This chapter specifies the three intertwined registries that complete
|
|
the primitives layer: \emph{pitch spaces} (the analytical universes in
|
|
which scale-position arithmetic lives), \emph{accidental registries}
|
|
(the catalogs of position-modifying glyphs), and \emph{tuning systems}
|
|
(the frequency-resolution mechanisms). It also specifies the
|
|
hierarchical resolution rules that determine which tuning applies to
|
|
each pitch in a score.
|
|
|
|
\section{Design Principles}
|
|
\label{sec:tuning:principles}
|
|
|
|
\begin{description}
|
|
\item[Pitch space and tuning system are distinct.] A pitch space
|
|
defines what pitches exist and how they relate analytically. A
|
|
tuning system defines what frequencies those pitches produce.
|
|
The two are independent: a single pitch space (e.g., CMN) admits
|
|
many tuning systems (12-TET, meantone, well-temperaments, just
|
|
intonation, Pythagorean); a single tuning structure may be anchored
|
|
at different reference frequencies.
|
|
\item[Accidentals belong to pitch spaces.] An accidental defines a
|
|
\emph{position modification} within a pitch space. The tuning system
|
|
handles the frequency consequences of that modification. The chromatic
|
|
semitone implied by a sharp has different cent-values in 12-TET,
|
|
quarter-comma meantone, and Pythagorean tuning, but it is the same
|
|
accidental denoting the same position.
|
|
\item[Reference pitch is score-level, not tuning-level.] A score
|
|
declares a reference pitch (position plus frequency); the tuning
|
|
system defines structure relative to that reference. ``12-TET at
|
|
A=440'' and ``12-TET at A=415'' share the same tuning system; the
|
|
reference differs.
|
|
\item[Resolution is hierarchical.] Tuning, pitch space, and accidental
|
|
registry are resolved by walking outward from the pitch: pitch,
|
|
voice, staff, region, score. Inheritance markers permit any level
|
|
to defer to the enclosing scope.
|
|
\item[Built-in defaults, extensible by users.] The core ships a
|
|
baseline catalog of pitch spaces, tuning systems, and accidental
|
|
registries sufficient for the common 95\% case. Scores and grammar
|
|
plugins extend these without forking.
|
|
\item[SMuFL primary, custom fallback.] Glyph references default to
|
|
SMuFL codepoints. Custom glyphs are supported as fallback. SMuFL
|
|
version is recorded per score.
|
|
\end{description}
|
|
|
|
\section{Pitch Spaces}
|
|
\label{sec:tuning:space}
|
|
|
|
A pitch space is the analytical universe in which scale-degree and
|
|
interval relationships are defined. It is the algebra; the tuning
|
|
system is the physics.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct PitchSpace {
|
|
pub id: PitchSpaceId,
|
|
|
|
/// Human-readable name (e.g., "CMN 12-tone chromatic").
|
|
pub name: String,
|
|
|
|
/// Optional description and provenance notes.
|
|
pub description: Option<String>,
|
|
|
|
/// What positions exist and how they are structured.
|
|
pub positions: PositionStructure,
|
|
|
|
/// How intervals between positions are defined.
|
|
pub interval_algebra: IntervalAlgebra,
|
|
|
|
/// The accidental registry valid in this pitch space.
|
|
pub accidental_registry: AccidentalRegistryId,
|
|
|
|
/// The nominal registry: named position-letters
|
|
/// (e.g., A-G for CMN).
|
|
pub nominal_registry: NominalRegistryId,
|
|
|
|
/// Rules governing how pitches transpose within this space.
|
|
pub transposition: TranspositionBehavior,
|
|
|
|
/// Default rules for the spelling pre-pass when this pitch space
|
|
/// is active.
|
|
pub spelling_rules: SpellingRuleSet,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsection{Position Structure}
|
|
\label{sec:tuning:positions}
|
|
|
|
The \texttt{PositionStructure} declares the shape of the pitch space.
|
|
Three families cover the vast majority of useful cases; a fourth is the
|
|
escape hatch for grammars defined entirely by plugin.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub enum PositionStructure {
|
|
/// Chromatic: a fixed number of equally-numbered positions per
|
|
/// octave, with no hierarchical diatonic substructure.
|
|
/// Includes EDOs and serial 12-tone.
|
|
Chromatic { positions_per_octave: u16 },
|
|
|
|
/// Diatonic over chromatic: a smaller set of named nominals
|
|
/// (e.g., A-G for CMN) plus accidental modifications reaching
|
|
/// the full chromatic range. CMN's structure.
|
|
DiatonicOverChromatic {
|
|
nominals_per_octave: u16,
|
|
chromatic_positions_per_octave: u16,
|
|
/// Mapping from each nominal to its position in the
|
|
/// chromatic layer (e.g., C=0, D=2, E=4, F=5, G=7, A=9, B=11
|
|
/// for CMN).
|
|
nominal_to_chromatic: Vec<u16>,
|
|
},
|
|
|
|
/// Just intonation lattice: positions defined by exact rational
|
|
/// ratios from a tonic, organized along prime-axis dimensions
|
|
/// (3-limit, 5-limit, 7-limit, etc.).
|
|
JiLattice {
|
|
limit: u8, // prime limit
|
|
generators: Vec<JiRatio>, // one per prime dimension
|
|
},
|
|
|
|
/// Grammar-defined: structure is opaque to the core and resolved
|
|
/// by a grammar plugin. Used for maqam, gamelan, raga frameworks,
|
|
/// and other systems whose position structure is not flat or
|
|
/// hierarchical in the above senses.
|
|
Registered(PositionStructureRegistryId),
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\begin{requirement}
|
|
\label{req:tuning:diatonic-chromatic-mapping}
|
|
The \texttt{DiatonicOverChromatic} variant \MUST{} have a
|
|
\texttt{nominal\_to\_chromatic} mapping of length equal to
|
|
\texttt{nominals\_per\_octave}, with each entry strictly less than
|
|
\texttt{chromatic\_positions\_per\_octave}. The mapping \MUST{} be
|
|
strictly increasing.
|
|
\end{requirement}
|
|
|
|
\subsection{Interval Algebra}
|
|
|
|
An interval algebra defines how distances between positions are
|
|
computed and how transposition is realized. For chromatic spaces the
|
|
algebra is integer arithmetic modulo the position count; for
|
|
diatonic-over-chromatic the algebra distinguishes diatonic from
|
|
chromatic intervals (a diatonic third may correspond to three or four
|
|
chromatic semitones depending on quality).
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub enum IntervalAlgebra {
|
|
/// Single-axis integer arithmetic. Two positions are subtracted
|
|
/// to yield a single integer interval.
|
|
Chromatic,
|
|
|
|
/// Two-axis arithmetic distinguishing diatonic and chromatic
|
|
/// components. CMN's algebra: a major third is (3 diatonic steps,
|
|
/// 4 chromatic steps); a diminished fourth is (3 diatonic steps,
|
|
/// 4 chromatic steps) -- distinguishable from the major third only
|
|
/// by spelling intent.
|
|
DiatonicChromatic,
|
|
|
|
/// Multi-axis JI lattice arithmetic.
|
|
JiVector { dimensions: u8 },
|
|
|
|
/// Grammar-defined algebra.
|
|
Registered(IntervalAlgebraRegistryId),
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsection{Nominal Registry}
|
|
|
|
A nominal registry catalogs the named position-letters of a pitch
|
|
space. For CMN this is the seven-element set $\{C, D, E, F, G, A, B\}$.
|
|
For 12-tone serial music the nominal registry may simply be the
|
|
integers $0$ through $11$. For maqam the nominals are the names of
|
|
maqam degrees.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct NominalRegistry {
|
|
pub id: NominalRegistryId,
|
|
pub nominals: Vec<NominalDefinition>,
|
|
}
|
|
|
|
pub struct NominalDefinition {
|
|
pub id: NominalId,
|
|
|
|
/// Canonical name.
|
|
pub name: String,
|
|
|
|
/// Display glyph, typically a letter or symbol.
|
|
pub display: GlyphReference,
|
|
|
|
/// Default position in the pitch space's chromatic layer, if
|
|
/// applicable.
|
|
pub default_position: Option<u16>,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsection{Transposition Behavior}
|
|
|
|
Transposition behavior governs how scale positions move under interval
|
|
operations and how spellings follow.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub enum TranspositionBehavior {
|
|
/// Diatonic transposition: shift by N diatonic steps within a
|
|
/// chosen key. Accidentals adjust to fit the destination key.
|
|
Diatonic,
|
|
|
|
/// Chromatic transposition: shift by N chromatic steps. Spellings
|
|
/// follow rules of the SpellingRuleSet.
|
|
Chromatic,
|
|
|
|
/// Compound: support both, parameterized at the operation site.
|
|
Compound,
|
|
|
|
/// Grammar-defined.
|
|
Registered(TranspositionRegistryId),
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsection{Spelling Rule Sets}
|
|
|
|
A pitch space carries a default spelling rule set that the spelling
|
|
pre-pass (Section~\ref{sec:pitch:prepass}) consults when inferring
|
|
spellings for unspelled pitches.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct SpellingRuleSet {
|
|
pub id: SpellingRuleSetId,
|
|
pub name: String,
|
|
|
|
/// Algorithmic family this rule set belongs to. The specific
|
|
/// algorithm is referenced by id and resolved against the
|
|
/// implementation's registered spelling algorithms.
|
|
pub algorithm: SpellingAlgorithmId,
|
|
|
|
/// Algorithm-specific parameters.
|
|
pub parameters: SpellingParameters,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\begin{openquestion}
|
|
The default spelling algorithm is now ratified
|
|
(Requirement~\ref{req:pitch:spelling-algorithm}:
|
|
\texttt{"default"} = Temperley-style line-of-fifths preference,
|
|
version~1). Still open: the catalog of \emph{additional}
|
|
registered spelling algorithms (candidates include
|
|
Longuet-Higgins line-of-fifths distance, Cambouropoulos
|
|
pitch-spelling, and Meredith PS13) and their parameter schemas,
|
|
which are normative once registered.
|
|
\end{openquestion}
|
|
|
|
\section{Accidental Registries}
|
|
\label{sec:tuning:accidentals}
|
|
|
|
An accidental registry catalogs the position-modifying glyphs valid
|
|
within a pitch space. Each accidental defines a glyph, a position
|
|
modification, and engraving metadata.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct AccidentalRegistry {
|
|
pub id: AccidentalRegistryId,
|
|
pub name: String,
|
|
pub accidentals: Vec<AccidentalDefinition>,
|
|
|
|
/// Whether scores may extend this registry with score-local
|
|
/// accidentals. Most registries permit this; some (e.g., a
|
|
/// closed conformance profile) may not.
|
|
pub extensible: bool,
|
|
}
|
|
|
|
pub struct AccidentalDefinition {
|
|
pub id: AccidentalId,
|
|
|
|
/// Canonical name. Used for serialization, accessibility, and
|
|
/// foreign-format export.
|
|
pub name: String,
|
|
|
|
/// Glyph reference, typically SMuFL.
|
|
pub glyph: GlyphReference,
|
|
|
|
/// The position modification this accidental applies.
|
|
pub modification: PitchSpaceModification,
|
|
|
|
/// Engraving metadata.
|
|
pub engraving: AccidentalEngraving,
|
|
|
|
/// Combination behavior with other accidentals.
|
|
pub combination: AccidentalCombination,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsection{Pitch Space Modifications}
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub enum PitchSpaceModification {
|
|
/// CMN-style integer chromatic alteration. -2 = double flat,
|
|
/// -1 = flat, +1 = sharp, +2 = double sharp, etc.
|
|
CmnChromatic(i8),
|
|
|
|
/// Integer step offset in an EDO position space.
|
|
EdoSteps(i16),
|
|
|
|
/// Modification expressed as an exact rational ratio. Used for
|
|
/// JI accidentals such as the HEJI syntonic-comma symbols
|
|
/// (81/80) and septimal-comma symbols (64/63).
|
|
JiRatio { numerator: i32, denominator: NonZeroU32 },
|
|
|
|
/// Modification expressed in cents. Used for Sagittal-style
|
|
/// precise microtonal accidentals.
|
|
Cents(f64),
|
|
|
|
/// Modification defined by a grammar plugin.
|
|
Registered(ModificationRegistryId),
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\begin{requirement}
|
|
\label{req:tuning:accidental-modification-compatibility}
|
|
An accidental's modification \MUST{} be expressible in the interval
|
|
algebra of every pitch space that references its registry. A
|
|
\texttt{CmnChromatic} modification is valid only in spaces with
|
|
\texttt{DiatonicChromatic} or compatible algebra; an \texttt{EdoSteps}
|
|
modification is valid only in \texttt{Chromatic} or \texttt{Registered}
|
|
spaces with appropriate compatibility. Implementations \MUST{} reject
|
|
scores referencing an accidental in a space whose algebra does not
|
|
admit the modification.
|
|
\end{requirement}
|
|
|
|
\subsection{Accidental Engraving Metadata}
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct AccidentalEngraving {
|
|
/// Bounding box in staff-space units, relative to the glyph's
|
|
/// anchor point.
|
|
pub bounding_box: BoundingBox,
|
|
|
|
/// Where the glyph attaches to the note: typically the geometric
|
|
/// center or a custom anchor for compound glyphs.
|
|
pub anchor: AnchorPoint,
|
|
|
|
/// Advance width for horizontal spacing computations.
|
|
pub advance_width: SpaceUnit,
|
|
|
|
/// Stacking order when multiple accidentals attach to one note.
|
|
/// Lower values are placed closer to the notehead.
|
|
pub stacking_order: i32,
|
|
|
|
/// Whether this glyph should be drawn with parentheses by default
|
|
/// (e.g., editorial or cautionary accidentals).
|
|
pub default_parenthesized: bool,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsection{Combination Behavior}
|
|
|
|
Most accidentals do not combine; a note carries one accidental at a
|
|
time. Some systems (HEJI, certain microtonal notations) permit stacking
|
|
multiple accidental glyphs on a single note to express compound
|
|
modifications.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub enum AccidentalCombination {
|
|
/// Stands alone; replaces any prior accidental on the same note.
|
|
Solitary,
|
|
|
|
/// May stack with members of the listed compatibility groups.
|
|
/// Stacking order is determined by the engraving metadata.
|
|
Stacking { compatible_groups: Vec<AccidentalGroupId> },
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsection{Score-Local Extensions}
|
|
|
|
A score \MAY{} extend a referenced accidental registry with additional
|
|
accidental definitions, provided the registry's \texttt{extensible}
|
|
flag is true. Extensions are stored on the score and override or
|
|
augment the base registry during resolution.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct ScoreAccidentalExtensions {
|
|
pub base: AccidentalRegistryId,
|
|
pub additions: Vec<AccidentalDefinition>,
|
|
pub overrides: Vec<AccidentalDefinition>,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\section{Glyph References and SMuFL}
|
|
\label{sec:tuning:smufl}
|
|
|
|
The Standard Music Font Layout (SMuFL) is the normative source for
|
|
glyph references in the core. Every standard accidental, notehead,
|
|
articulation, dynamic, and ornament has a SMuFL codepoint. Non-standard
|
|
glyphs are supported via custom glyph references.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub enum GlyphReference {
|
|
/// SMuFL codepoint, resolved against the active SMuFL font.
|
|
Smufl(u32),
|
|
|
|
/// Custom glyph defined by the score or a plugin.
|
|
Custom(CustomGlyphId),
|
|
|
|
/// Composite glyph: multiple glyph references rendered as a
|
|
/// single accidental. Used for compound HEJI symbols and similar.
|
|
Composite(Vec<GlyphReference>),
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsection{SMuFL Versioning}
|
|
|
|
\begin{requirement}
|
|
\label{req:tuning:smufl-version-fallback}
|
|
Every score \MUST{} declare the SMuFL version it targets. Resolving a
|
|
SMuFL glyph reference whose codepoint is not present in the active
|
|
font's SMuFL version \MUST{} produce a deterministic fallback (the
|
|
reference SMuFL substitution glyph, or a clearly-marked missing-glyph
|
|
indicator); it \MUSTNOT{} silently fail.
|
|
\end{requirement}
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct SmuflVersionRequirement {
|
|
/// Minimum SMuFL version required by this score.
|
|
pub minimum: SmuflVersion,
|
|
|
|
/// SMuFL version this score was authored against. Used for
|
|
/// detecting whether newer glyphs are in use.
|
|
pub authored_against: SmuflVersion,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\section{Tuning Systems}
|
|
\label{sec:tuning:system}
|
|
|
|
A tuning system maps positions in a pitch space to frequencies. Given
|
|
a reference pitch (position plus frequency) and the system's resolution
|
|
rules, every position in the space resolves to a determinate frequency.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct TuningSystem {
|
|
pub id: TuningSystemId,
|
|
|
|
/// Human-readable name.
|
|
pub name: String,
|
|
|
|
/// The pitch space whose positions this tuning resolves.
|
|
pub pitch_space: PitchSpaceId,
|
|
|
|
/// How the tuning resolves positions to frequencies, given a
|
|
/// reference pitch. The reference itself is supplied separately
|
|
/// (see Section~\ref{sec:tuning:reference}).
|
|
pub resolution: TuningResolution,
|
|
|
|
/// Optional historical or provenance notes.
|
|
pub description: Option<String>,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsection{Tuning Resolution}
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub enum TuningResolution {
|
|
/// N-tone equal temperament: each step is the Nth root of the
|
|
/// octave ratio. Includes 12-TET when N=12.
|
|
EqualTemperament { divisions_per_octave: u16 },
|
|
|
|
/// Explicit per-position ratios. Each entry is a ratio relative
|
|
/// to the reference position.
|
|
PerPositionRatios(Vec<PositionRatio>),
|
|
|
|
/// Procedural definition: a registered tuning function that
|
|
/// computes frequencies from a reference. Historical temperaments
|
|
/// (Werckmeister, Vallotti, meantone variants) live here.
|
|
Function {
|
|
function: TuningFunctionId,
|
|
parameters: TuningParameters,
|
|
},
|
|
|
|
/// A base resolution with per-position overrides. Used for
|
|
/// split-accidental keyboards and similar irregular tunings.
|
|
Overlay {
|
|
base: Box<TuningResolution>,
|
|
overrides: Vec<PositionOverride>,
|
|
},
|
|
|
|
/// Imported from a tuning file (Scala .scl, MIDI Tuning Standard,
|
|
/// etc.). The data is captured at import; the original file is
|
|
/// referenced for provenance but is not required at runtime.
|
|
Imported {
|
|
format: TuningFileFormat,
|
|
data: Arc<ImportedTuningData>,
|
|
},
|
|
|
|
/// Adaptive tuning: resolution depends on the harmonic context
|
|
/// at the moment of sounding. Used for adaptive JI and similar
|
|
/// systems.
|
|
Adaptive {
|
|
function: AdaptiveTuningFunctionId,
|
|
parameters: AdaptiveTuningParameters,
|
|
},
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsection{The Resolution Contract}
|
|
|
|
\begin{requirement}
|
|
\label{req:tuning:tuning-resolution-determinism}
|
|
Every tuning resolution \MUST{} expose a deterministic function from
|
|
pitch-space position (plus, where applicable, harmonic context) to
|
|
frequency in Hertz, given a reference pitch. Determinism \MUST{}
|
|
hold across runs and platforms; numerical computations \MUSTNOT{}
|
|
depend on floating-point rounding modes.
|
|
\end{requirement}
|
|
|
|
\subsection{Adaptive Tuning}
|
|
|
|
Adaptive tuning systems compute frequency from position \emph{plus
|
|
harmonic context}. In a 5-limit adaptive JI system, for example, the
|
|
frequency of a given E depends on whether it is the major third of a C
|
|
chord, the perfect fifth of an A chord, or a passing tone between
|
|
other harmonic interpretations.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct HarmonicContext {
|
|
/// Concurrently sounding pitches at the moment of resolution.
|
|
pub concurrent: Vec<PitchId>,
|
|
|
|
/// Recently sounding pitches, with decay weighting.
|
|
pub recent: Vec<(PitchId, f64)>,
|
|
|
|
/// The active key or modal center, if known.
|
|
pub key_context: Option<KeyContext>,
|
|
|
|
/// User-supplied context hints attached to the score.
|
|
pub hints: Vec<ContextHint>,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
The harmonic context is constructed by the audio engine (or by analysis
|
|
tooling) and passed to the tuning resolution function. Static tuning
|
|
systems ignore it; adaptive tuning systems consume it.
|
|
|
|
\begin{requirement}
|
|
\label{req:tuning:adaptive-tuning-purity}
|
|
Adaptive tuning resolution \MUST{} be a pure function of position
|
|
and harmonic context. Implementations \MAY{} cache resolution
|
|
results, but \MUST{} invalidate caches when the harmonic context
|
|
changes.
|
|
\end{requirement}
|
|
|
|
\section{Reference Pitch}
|
|
\label{sec:tuning:reference}
|
|
|
|
The reference pitch is the anchor that converts the tuning system's
|
|
structural ratios into absolute frequencies. It is a property of the
|
|
score, not of the tuning system.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct ReferencePitch {
|
|
/// The pitch-space position chosen as the reference.
|
|
/// Conventionally A4 in CMN, but configurable.
|
|
pub position: PitchSpacePosition,
|
|
|
|
/// The frequency in Hertz assigned to that position.
|
|
/// Conventional values: 440 Hz (modern standard), 415 Hz (Baroque),
|
|
/// 430 Hz (early Classical), 442 Hz (some modern orchestras).
|
|
pub frequency_hz: f64,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\begin{requirement}
|
|
\label{req:tuning:reference-pitch}
|
|
Every score \MUST{} declare a reference pitch. The reference pitch
|
|
\MUST{} be expressible as a valid position within the score's default
|
|
pitch space. The frequency \MUST{} be positive and finite.
|
|
\end{requirement}
|
|
|
|
\subsection{Multiple Reference Pitches}
|
|
|
|
A score \MAY{} declare additional reference pitches at the staff or
|
|
region level, overriding the score default. This supports
|
|
mixed-instrumentation scores in which different instruments use
|
|
different concert-pitch references (e.g., a piece for period and modern
|
|
instruments with different A frequencies).
|
|
|
|
\section{Score Tuning Context and Hierarchical Resolution}
|
|
\label{sec:tuning:context}
|
|
|
|
The \emph{score tuning context} is the top-level structure declaring
|
|
the tuning environment for a score. Per-scope overrides extend it.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct ScoreTuningContext {
|
|
/// Default pitch space for the score.
|
|
pub default_pitch_space: PitchSpaceId,
|
|
|
|
/// Default tuning system. Its pitch_space must match (or be
|
|
/// compatible with) the default_pitch_space.
|
|
pub default_tuning_system: TuningSystemId,
|
|
|
|
/// The reference pitch for the score.
|
|
pub reference: ReferencePitch,
|
|
|
|
/// Score-local accidental registry extensions.
|
|
pub accidental_extensions: Vec<ScoreAccidentalExtensions>,
|
|
|
|
/// SMuFL version targeting.
|
|
pub smufl: SmuflVersionRequirement,
|
|
|
|
/// Per-scope overrides, in the order they apply.
|
|
pub overrides: Vec<TuningOverride>,
|
|
}
|
|
|
|
pub struct TuningOverride {
|
|
pub scope: TuningScope,
|
|
pub pitch_space: Option<PitchSpaceId>,
|
|
pub tuning_system: Option<TuningSystemId>,
|
|
pub reference: Option<ReferencePitch>,
|
|
}
|
|
|
|
pub enum TuningScope {
|
|
Voice(VoiceId),
|
|
Staff(StaffId),
|
|
Region(RegionId),
|
|
Range { start: TimeAnchor, end: TimeAnchor, voices: VoiceSelector },
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsection{Resolution Order}
|
|
|
|
For a given pitch, resolution proceeds by walking outward through
|
|
scopes from most specific to most general until each component
|
|
(pitch space, tuning system, reference) is determined.
|
|
|
|
\begin{requirement}
|
|
\label{req:tuning:tuning-resolution-order}
|
|
Resolution \MUST{} proceed in the following order, halting at the
|
|
first scope that supplies a non-inherited value for each component:
|
|
|
|
\begin{enumerate}
|
|
\item The pitch's own \texttt{AcousticPitch} (an explicit
|
|
\texttt{TuningReference::Explicit} or
|
|
\texttt{AcousticRealization::AbsoluteHz} short-circuits the
|
|
walk).
|
|
\item The voice containing the pitch.
|
|
\item The staff containing the voice.
|
|
\item Each region enclosing the pitch, from innermost to outermost.
|
|
\item The score's default tuning context.
|
|
\end{enumerate}
|
|
|
|
Each of the three components (pitch space, tuning system, reference)
|
|
is resolved independently along this chain.
|
|
\end{requirement}
|
|
|
|
\begin{rationale}
|
|
Independent per-component resolution allows a passage to override
|
|
only the parts that differ. A period-instrument inset in a modern
|
|
orchestra score may override only the reference frequency (415 Hz
|
|
instead of 440 Hz) while inheriting the pitch space and tuning
|
|
structure from the parent context. Conversely, a microtonal passage
|
|
in an otherwise 12-TET score may override the pitch space and
|
|
tuning system but inherit the reference.
|
|
\end{rationale}
|
|
|
|
\subsection{Compatibility Constraints}
|
|
|
|
\begin{requirement}
|
|
\label{req:tuning:tuning-system-compatibility}
|
|
When a tuning override is applied, the resolved tuning system's
|
|
declared \texttt{pitch\_space} \MUST{} either equal the resolved
|
|
pitch space or be declared compatible with it via a registered
|
|
compatibility mapping. Implementations \MUST{} reject configurations
|
|
in which the resolved pitch space and the resolved tuning system's
|
|
pitch space disagree without a compatibility mapping.
|
|
\end{requirement}
|
|
|
|
\section{Built-in Catalog}
|
|
\label{sec:tuning:builtin}
|
|
|
|
The core ships with a baseline catalog of pitch spaces, tuning systems,
|
|
and accidental registries sufficient for the common case. The catalog
|
|
is normative: a conforming implementation \MUST{} provide these
|
|
identifiers with the specified semantics.
|
|
|
|
\subsection{Built-in Pitch Spaces}
|
|
|
|
\begin{longtable}{p{3.5cm} p{10cm}}
|
|
\toprule
|
|
\textbf{Identifier} & \textbf{Description} \\
|
|
\midrule
|
|
\endhead
|
|
\texttt{cmn-12} & Common Music Notation, 7 diatonic nominals (A--G)
|
|
over 12 chromatic positions. Standard sharps, flats, double-sharps,
|
|
and double-flats. The default for Western tonal and post-tonal music. \\
|
|
\texttt{cmn-24} & CMN extended with 24-EDO quarter-tone accidentals.
|
|
Used for Arabic-derived microtonal practice in CMN-compatible
|
|
notation. \\
|
|
\texttt{edo-19} & 19-tone equal division of the octave.
|
|
Notable for its closer-to-just major thirds. \\
|
|
\texttt{edo-22} & 22-tone equal division. Distinguishes harmonic
|
|
intervals collapsed in 12-TET. \\
|
|
\texttt{edo-31} & 31-tone equal division. Approximates quarter-comma
|
|
meantone almost exactly. \\
|
|
\texttt{edo-53} & 53-tone equal division. Excellent approximation
|
|
of 5-limit JI. \\
|
|
\texttt{edo-72} & 72-tone equal division. Common in research and
|
|
contemporary microtonal practice. \\
|
|
\texttt{ji-5limit} & 5-limit just intonation lattice with HEJI
|
|
accidentals. Two-dimensional (prime axes 3, 5). \\
|
|
\texttt{ji-7limit} & 7-limit JI lattice with HEJI accidentals.
|
|
Three-dimensional. \\
|
|
\texttt{ji-11limit} & 11-limit JI lattice with HEJI accidentals.
|
|
Four-dimensional. \\
|
|
\texttt{maqam-base} & Skeletal maqam framework with quarter-flat
|
|
and quarter-sharp accidentals. Deep coverage of specific maqamat
|
|
is the province of grammar plugins. \\
|
|
\texttt{gamelan-slendro} & Five-tone slendro framework. \\
|
|
\texttt{gamelan-pelog} & Seven-tone pelog framework. \\
|
|
\bottomrule
|
|
\end{longtable}
|
|
|
|
\subsection{Built-in Tuning Systems}
|
|
|
|
\begin{longtable}{p{4cm} p{9.5cm}}
|
|
\toprule
|
|
\textbf{Identifier} & \textbf{Description} \\
|
|
\midrule
|
|
\endhead
|
|
\texttt{tet-12} & 12-tone equal temperament. The default. \\
|
|
\texttt{tet-19} & 19-tone equal temperament. \\
|
|
\texttt{tet-22} & 22-tone equal temperament. \\
|
|
\texttt{tet-31} & 31-tone equal temperament. \\
|
|
\texttt{tet-53} & 53-tone equal temperament. \\
|
|
\texttt{tet-72} & 72-tone equal temperament. \\
|
|
\texttt{pythagorean} & Pure-fifth (3:2) Pythagorean tuning. \\
|
|
\texttt{meantone-1/4-comma} & Quarter-comma meantone (pure major
|
|
thirds). \\
|
|
\texttt{meantone-1/6-comma} & Sixth-comma meantone. \\
|
|
\texttt{meantone-1/5-comma} & Fifth-comma meantone. \\
|
|
\texttt{werckmeister-iii} & Werckmeister III well temperament. \\
|
|
\texttt{werckmeister-iv} & Werckmeister IV well temperament. \\
|
|
\texttt{vallotti} & Vallotti well temperament. \\
|
|
\texttt{kirnberger-ii} & Kirnberger II well temperament. \\
|
|
\texttt{kirnberger-iii} & Kirnberger III well temperament. \\
|
|
\texttt{young-ii} & Thomas Young's second temperament. \\
|
|
\texttt{ji-static-5limit-C} & Static 5-limit JI anchored to C tonic. \\
|
|
\texttt{ji-static-5limit-G} & Static 5-limit JI anchored to G. \\
|
|
\texttt{ji-static-5limit-D} & Static 5-limit JI anchored to D. \\
|
|
\texttt{ji-adaptive-5limit} & Adaptive 5-limit JI; resolves based on
|
|
harmonic context. \\
|
|
\bottomrule
|
|
\end{longtable}
|
|
|
|
\begin{requirement}
|
|
\label{req:tuning:builtin-tuning-catalog}
|
|
All built-in catalog identifiers \MUST{} resolve in a conforming
|
|
implementation. The semantics specified above are normative.
|
|
Implementations \MAY{} provide additional built-ins; they \MUSTNOT{}
|
|
redefine the semantics of those listed.
|
|
\end{requirement}
|
|
|
|
\subsection{Default Score Configuration}
|
|
|
|
A newly-created score with no explicit tuning context \MUST{} default to:
|
|
|
|
\begin{itemize}
|
|
\item Pitch space: \texttt{cmn-12}.
|
|
\item Tuning system: \texttt{tet-12}.
|
|
\item Reference pitch: A4 = 440\,Hz.
|
|
\item SMuFL version: the highest SMuFL version supported by the
|
|
implementation.
|
|
\end{itemize}
|
|
|
|
\section{Forward References}
|
|
|
|
\begin{itemize}
|
|
\item \texttt{VoiceId}, \texttt{StaffId}, \texttt{RegionId},
|
|
\texttt{VoiceSelector}, and the score graph's hierarchical
|
|
organization are defined in Chapter~\ref{ch:graph}.
|
|
\item \texttt{KeyContext} and the harmonic-context construction
|
|
machinery are partially defined in Chapter~\ref{ch:graph} and
|
|
completed in the audio engine specification (out of scope for this
|
|
document).
|
|
\item The specific spelling algorithms referenced by
|
|
\texttt{SpellingAlgorithmId} remain an open question; see the
|
|
related open question in Section~\ref{sec:pitch:prepass}.
|
|
\end{itemize}
|
|
|
|
% ===========================================================================
|
|
\chapter{The Score Graph}
|
|
\label{ch:graph}
|
|
|
|
This chapter specifies the score graph: the in-memory representation of
|
|
all musical content in a score. The graph is the canonical truth about
|
|
the music; layout, serialization, and editing operations are downstream
|
|
projections and consumers of it. This chapter is the largest in the
|
|
specification because it threads together every primitive defined in
|
|
Chapters~\ref{ch:pitch}--\ref{ch:tuning} into a coherent whole.
|
|
|
|
\section{Design Principles}
|
|
\label{sec:graph:principles}
|
|
|
|
\begin{description}
|
|
\item[Hybrid topology.] The graph is a tree of containment overlaid
|
|
with cross-cutting structures that hold references. The tree
|
|
determines existence: deleting a parent deletes its descendants.
|
|
Cross-cutting structures hold references that may become dangling
|
|
and require re-anchoring per the rules defined in this chapter
|
|
and in Chapter~\ref{ch:semops}.
|
|
\item[Canvas before staff.] The spatial root of a score is the
|
|
canvas, a 2D space partitioned into regions. Staves exist inside
|
|
regions, not as a top-level concept. This permits regions of
|
|
free graphic content to coexist with regions of staff-based CMN
|
|
without forcing one paradigm to subsume the other.
|
|
\item[Arena storage.] Events are stored in a flat arena owned by
|
|
the score. Voices, cross-cutting structures, and analysis layers
|
|
hold event identifiers, not events. Arena storage enables fast
|
|
bulk iteration, simple cross-cutting reference, and clean CRDT
|
|
semantics.
|
|
\item[Parts are projections.] A part is a view onto the score
|
|
defined by a part-extraction specification. Parts do not live
|
|
in the canvas; they are separate objects that select and reshape
|
|
canvas content for printed extraction.
|
|
\item[Typed identifiers.] Every named object kind has its own
|
|
identifier type. Cross-kind confusion is a compile-time error.
|
|
\item[Identifiers are stable and deterministic.] Identifiers are
|
|
assigned at creation, never reassigned, and generated by a scheme
|
|
that is deterministic per replica.
|
|
\end{description}
|
|
|
|
\section{Top-Level Score Structure}
|
|
\label{sec:graph:score}
|
|
|
|
The score is the root object. All other objects in the specification
|
|
exist within or reference into a score. Conflict records are
|
|
deliberately \emph{not} part of the score graph: the
|
|
\texttt{ConflictRegistry} produced by canonical reduction is a
|
|
component of the canonical \emph{materialized state}
|
|
(Chapter~\ref{ch:semops}), recomputed with it, and persisting there
|
|
until resolved or dismissed by explicit operations.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct Score {
|
|
/// Bibliographic and authorship metadata.
|
|
pub metadata: ScoreMetadata,
|
|
|
|
/// The spatial root: regions and their staff/graphic content.
|
|
pub canvas: Canvas,
|
|
|
|
/// Abstract instrument definitions used by staves.
|
|
pub instruments: Vec<Instrument>,
|
|
|
|
/// Globally-identified staves of the score. Each region's
|
|
/// staff instances reference these by StaffId.
|
|
pub staves: Vec<Staff>,
|
|
|
|
/// Optional staff groupings (grand staves, choral groups,
|
|
/// bracketed orchestral sections). Used by engraving and parts.
|
|
pub staff_groups: Vec<StaffGroup>,
|
|
|
|
/// Per-part view definitions for extraction.
|
|
pub parts: Vec<PartDefinition>,
|
|
|
|
/// Cross-cutting structures: slurs, ties, hairpins, markers,
|
|
/// graphic gestures, comments, and analytical annotations.
|
|
pub cross_cutting: CrossCuttingRegistry,
|
|
|
|
/// Tuning systems, pitch spaces, and reference pitch.
|
|
pub tuning_context: ScoreTuningContext,
|
|
|
|
/// The score-level tempo map. Regions may declare local tempo
|
|
/// maps that override this.
|
|
pub tempo_map: TempoMap,
|
|
|
|
/// Flat arena of all events in the score.
|
|
pub events: EventArena,
|
|
|
|
/// Spelling attachments index (see Chapter 2).
|
|
pub spelling_attachments: SpellingAttachmentIndex,
|
|
|
|
/// Notational decomposition attachments index (see Chapter 3).
|
|
pub decomposition_attachments: DecompositionAttachmentIndex,
|
|
|
|
/// Analysis layers and view definitions.
|
|
pub analysis_layers: Vec<AnalysisLayer>,
|
|
pub views: Vec<ViewDefinition>,
|
|
|
|
/// Replica identifier and identifier generation state.
|
|
pub identity: IdentityContext,
|
|
}
|
|
|
|
pub struct StaffGroup {
|
|
pub id: StaffGroupId,
|
|
pub name: Option<String>,
|
|
pub kind: StaffGroupKind,
|
|
pub members: Vec<StaffId>,
|
|
}
|
|
|
|
pub enum StaffGroupKind {
|
|
/// Grand staff: typically piano, organ, harp.
|
|
GrandStaff,
|
|
/// Bracketed group: orchestral sections.
|
|
Bracket,
|
|
/// Sub-bracket: divisi within a section.
|
|
SubBracket,
|
|
/// Choral group: SATB and similar.
|
|
Choral,
|
|
/// Custom group defined by a layout extension.
|
|
Registered(StaffGroupKindRegistryId),
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsection{Score Metadata}
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct ScoreMetadata {
|
|
pub title: Option<String>,
|
|
pub subtitle: Option<String>,
|
|
pub composer: Option<String>,
|
|
pub lyricist: Option<String>,
|
|
pub arranger: Option<String>,
|
|
pub copyright: Option<String>,
|
|
pub creation_timestamp: Timestamp,
|
|
pub modification_timestamp: Timestamp,
|
|
pub additional: Vec<MetadataEntry>,
|
|
}
|
|
|
|
pub struct MetadataEntry {
|
|
pub key: String,
|
|
pub value: MetadataValue,
|
|
}
|
|
|
|
/// The value of an additional metadata entry (defined schema major 2).
|
|
/// Closed small union; growth is by appended variant.
|
|
pub enum MetadataValue {
|
|
Text(String),
|
|
Integer(i64),
|
|
Flag(bool),
|
|
}
|
|
|
|
/// A calendar timestamp (defined schema major 2): nanoseconds since the
|
|
/// Unix epoch, UTC, no zone. Distinct from WallClockTime, which is
|
|
/// *performance* time within a score. The zero value is the "unset"
|
|
/// convention (genesis without a declared creation time).
|
|
pub struct Timestamp(pub i64);
|
|
\end{lstlisting}
|
|
|
|
The metadata structure is deliberately small. Extended bibliographic,
|
|
catalog, and editorial-apparatus metadata is the province of foreign-
|
|
format mappings and analytical layers, not the core.
|
|
\texttt{additional} is an \emph{ordered authored list}, not a map
|
|
(defined schema major 2): entry order is preserved verbatim in
|
|
canonical bytes, and duplicate keys are permitted --- foreign formats
|
|
legitimately carry repeated keys (multiple contributors, multiple
|
|
identifiers of one scheme). Consumers wanting map semantics take the
|
|
first entry per key.
|
|
|
|
\begin{requirement}
|
|
\label{req:graph:metadata-timestamps}
|
|
\textbf{Timestamps are strictly authored (ratified with schema
|
|
major 2).} \texttt{creation\_timestamp} and
|
|
\texttt{modification\_timestamp} are stored, \emph{authored} values:
|
|
genesis takes the creation timestamp explicitly (the empty-document
|
|
constructor defaults it to the epoch-zero unset convention), and a
|
|
\texttt{SetMetadata} operation carries both verbatim as part of the
|
|
metadata value. Implementations \MUSTNOT{} write either field
|
|
implicitly --- an automatic modification-time update would rewrite
|
|
canonical bytes on every edit, churning content addresses and breaking
|
|
replica byte-agreement for identical operation histories. A client
|
|
that wants a live modification time surfaces one from non-canonical
|
|
state (the operation log's stamps); it does not store it here.
|
|
\end{requirement}
|
|
|
|
\section{Identifiers}
|
|
\label{sec:graph:ids}
|
|
|
|
Every named object in the graph carries a typed 128-bit identifier.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct EventId(u128);
|
|
pub struct VoiceId(u128);
|
|
pub struct StaffId(u128);
|
|
pub struct StaffInstanceId(u128);
|
|
pub struct StaffGroupId(u128);
|
|
pub struct RegionId(u128);
|
|
pub struct InstrumentId(u128);
|
|
pub struct PartDefinitionId(u128);
|
|
pub struct MeasureId(u128);
|
|
pub struct BarlineAlignmentGroupId(u128);
|
|
pub struct SlurId(u128);
|
|
pub struct TieId(u128);
|
|
pub struct BeamId(u128);
|
|
pub struct SpannerId(u128);
|
|
pub struct MarkerId(u128);
|
|
pub struct AnalyticalAnnotationId(u128);
|
|
pub struct CommentId(u128);
|
|
pub struct GraphicObjectId(u128);
|
|
pub struct GraphicGestureId(u128);
|
|
pub struct TimeSignatureId(u128);
|
|
pub struct ConflictId(u128);
|
|
pub struct TransactionId(u128);
|
|
// ... and so on for every named object kind
|
|
|
|
/// A tagged identifier over every typed identifier kind in the
|
|
/// score graph. Used wherever an object is referenced generically
|
|
/// (cross-cutting endpoints, conflict affected_objects, repair
|
|
/// records, edit barriers). The variant tag is part of canonical
|
|
/// content: distinct variants with the same underlying u128 are
|
|
/// distinct TypedObjectIds.
|
|
pub enum TypedObjectId {
|
|
Event(EventId),
|
|
Pitch(PitchId),
|
|
Voice(VoiceId),
|
|
Staff(StaffId),
|
|
StaffInstance(StaffInstanceId),
|
|
StaffGroup(StaffGroupId),
|
|
Region(RegionId),
|
|
Instrument(InstrumentId),
|
|
PartDefinition(PartDefinitionId),
|
|
Measure(MeasureId),
|
|
BarlineAlignmentGroup(BarlineAlignmentGroupId),
|
|
Slur(SlurId),
|
|
Tie(TieId),
|
|
Beam(BeamId),
|
|
Spanner(SpannerId),
|
|
Marker(MarkerId),
|
|
AnalyticalAnnotation(AnalyticalAnnotationId),
|
|
Comment(CommentId),
|
|
GraphicObject(GraphicObjectId),
|
|
GraphicGesture(GraphicGestureId),
|
|
TimeSignature(TimeSignatureId),
|
|
AnalysisLayer(AnalysisLayerId),
|
|
Tuplet(TupletId),
|
|
RepeatStructure(RepeatStructureId),
|
|
LyricLine(LyricLineId),
|
|
ChordSymbol(ChordSymbolId),
|
|
View(ViewId),
|
|
/// Extension-defined object kind, identified by registry id
|
|
/// plus the extension's own 128-bit identifier.
|
|
Registered(ObjectKindRegistryId, u128),
|
|
}
|
|
|
|
impl TypedObjectId {
|
|
/// Canonical byte form for hashing, ordering, and equality.
|
|
/// Variant tag is encoded as a 16-bit big-endian discriminant
|
|
/// followed by the variant payload's canonical bytes.
|
|
pub fn canonical_bytes(&self) -> Vec<u8> { /* ... */ }
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\begin{requirement}
|
|
\label{req:graph:typed-object-id-discriminants}
|
|
The \texttt{TypedObjectId} variant tag \MUST{} be encoded as a
|
|
16-bit big-endian discriminant followed by the variant payload's
|
|
canonical bytes. The discriminant of each variant is its
|
|
declaration-order index in the following closed table; this
|
|
assignment is normative and stable, and \MUSTNOT{} be reordered:
|
|
|
|
\begin{center}
|
|
\begin{tabular}{r l @{\qquad} r l}
|
|
\toprule
|
|
\textbf{Disc.} & \textbf{Variant} &
|
|
\textbf{Disc.} & \textbf{Variant} \\
|
|
\midrule
|
|
0 & \texttt{Event} & 14 & \texttt{Spanner} \\
|
|
1 & \texttt{Pitch} & 15 & \texttt{Marker} \\
|
|
2 & \texttt{Voice} & 16 & \texttt{AnalyticalAnnotation} \\
|
|
3 & \texttt{Staff} & 17 & \texttt{Comment} \\
|
|
4 & \texttt{StaffInstance} & 18 & \texttt{GraphicObject} \\
|
|
5 & \texttt{StaffGroup} & 19 & \texttt{GraphicGesture} \\
|
|
6 & \texttt{Region} & 20 & \texttt{TimeSignature} \\
|
|
7 & \texttt{Instrument} & 21 & \texttt{AnalysisLayer} \\
|
|
8 & \texttt{PartDefinition} & 22 & \texttt{Tuplet} \\
|
|
9 & \texttt{Measure} & 23 & \texttt{RepeatStructure} \\
|
|
10 & \texttt{BarlineAlignmentGroup} & 24 & \texttt{LyricLine} \\
|
|
11 & \texttt{Slur} & 25 & \texttt{ChordSymbol} \\
|
|
12 & \texttt{Tie} & 26 & \texttt{View} \\
|
|
13 & \texttt{Beam} & 27 & \texttt{Registered} \\
|
|
\bottomrule
|
|
\end{tabular}
|
|
\end{center}
|
|
|
|
The object-kind set is closed at these 28 discriminants
|
|
(\texttt{0}--\texttt{27}). Every named object kind in the score
|
|
graph has a dedicated discriminant; kinds introduced by registered
|
|
extensions \MUST{} use discriminant \texttt{27}
|
|
(\texttt{Registered}) and are distinguished by their
|
|
\texttt{ObjectKindRegistryId}, never by extending this table. The
|
|
\texttt{Registered(reg, raw)} payload is encoded as the 128-bit
|
|
\texttt{reg.canonical\_bytes()} (16 big-endian bytes) followed by
|
|
the 16 big-endian bytes of the \texttt{raw} \texttt{u128}, for a
|
|
total canonical form of $2 + 16 + 16 = 34$ bytes.
|
|
\texttt{ObjectKindRegistryId} is a 128-bit value.
|
|
\end{requirement}
|
|
|
|
\subsection{Identifier Generation}
|
|
|
|
\begin{requirement}
|
|
\label{req:graph:replica-counter-identifiers}
|
|
Identifiers \MUST{} be 128-bit values composed of a 64-bit replica
|
|
identifier and a 64-bit monotonic counter local to that replica.
|
|
The replica identifier \MUST{} be unique among all replicas of the
|
|
score; collision resistance is achieved by random initialization of
|
|
the replica identifier at creation time with at least 64 bits of
|
|
entropy from a cryptographically appropriate source. The counter
|
|
\MUST{} be monotonically increasing within a replica and \MUSTNOT{}
|
|
be reused, even if the corresponding object is deleted.
|
|
\end{requirement}
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct IdentityContext {
|
|
/// This replica's identifier. Generated at score creation.
|
|
pub replica_id: ReplicaId,
|
|
|
|
/// Monotonic counter for new identifiers. Per-kind counters
|
|
/// optional; a single counter suffices.
|
|
pub next_counter: u64,
|
|
}
|
|
|
|
pub struct ReplicaId(u64);
|
|
|
|
impl ReplicaId {
|
|
/// Reserved replica identifier for deterministically-derived
|
|
/// system identifiers (system-promoted voices, content-derived
|
|
/// conflict IDs, future deterministic synthetic identifiers).
|
|
/// User-authored replicas MUST NOT use this value.
|
|
pub const SYSTEM_DERIVED: ReplicaId = ReplicaId(0xffff_ffff_ffff_ffff);
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\begin{rationale}
|
|
Replica-plus-counter identifiers are deterministic given a replica
|
|
and a creation order, which simplifies reasoning about CRDT merge
|
|
semantics and reproducible builds of generated scores. UUIDv7 was
|
|
considered and rejected: the timestamp embedding leaks ordering
|
|
information that the CRDT layer would have to reconstruct anyway,
|
|
and the random portion gives less collision resistance per bit than
|
|
a properly initialized replica identifier.
|
|
\end{rationale}
|
|
|
|
\subsection{System-Derived Identifier Namespace}
|
|
\label{sec:graph:system-derived}
|
|
|
|
Some identifiers are not author-authored: they arise during
|
|
deterministic reduction (system-promoted voices, content-derived
|
|
conflict identifiers, attachment-deduplication identifiers). To
|
|
prevent collisions with user-authored identifiers, the spec
|
|
reserves a dedicated replica namespace.
|
|
|
|
\begin{requirement}
|
|
\label{req:graph:system-derived-namespace}
|
|
The replica identifier \texttt{ReplicaId::SYSTEM\_DERIVED}
|
|
(\texttt{0xffff\_ffff\_ffff\_ffff}) is reserved for
|
|
deterministically-derived system identifiers. User-authored
|
|
replicas \MUSTNOT{} use this value as their \texttt{ReplicaId}.
|
|
Implementations creating a new score \MUST{} reject this value
|
|
when generating the local replica identifier and \MUST{}
|
|
regenerate the random portion until a non-reserved value is
|
|
obtained.
|
|
|
|
Because a system-derived identifier is content-derived, the
|
|
identified object's intrinsic content is \emph{immutable}: a
|
|
reduction \MUST{} refuse an operation that would rewrite the
|
|
intrinsic content of a \texttt{SYSTEM\_DERIVED}-namespace object
|
|
in place (ratified Pass~12; the Operation Catalog pins the
|
|
precondition and its failure reason). Rewriting the content would
|
|
silently invalidate the id's content derivation; the sanctioned
|
|
path is minting a replacement object.
|
|
|
|
System-derived identifiers within the
|
|
\texttt{ReplicaId::SYSTEM\_DERIVED} namespace \MUST{} have their
|
|
64-bit counter portion derived deterministically by BLAKE3
|
|
truncation:
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
fn derive_system_counter(
|
|
domain_tag: &[u8; 8],
|
|
canonical_inputs: &[u8],
|
|
) -> u64 {
|
|
let mut preimage = Vec::new();
|
|
preimage.extend_from_slice(domain_tag);
|
|
preimage.extend_from_slice(canonical_inputs);
|
|
let hash = blake3(&preimage);
|
|
u64::from_be_bytes(hash[0..8].try_into().unwrap())
|
|
}
|
|
\end{lstlisting}
|
|
|
|
The built-in (reserved) domain tags for system-derived identifiers
|
|
are a closed set of three:
|
|
\begin{itemize}
|
|
\item \texttt{"MUSCSVCE"} for system-promoted voices
|
|
(Section~\ref{sec:graph:promoted-voices}).
|
|
\item \texttt{"MUSCSPCH"} for system-derived pitches (rare;
|
|
only when promotion logic introduces a synthetic pitch;
|
|
Section~\ref{sec:graph:system-derived-pitch}).
|
|
\item \texttt{"MUSCSANM"} for integrity-anomaly identifiers
|
|
(Section~\ref{sec:graph:system-collisions}). Anomalies are
|
|
core, not an extension concern, so their tag is built in
|
|
alongside the other two.
|
|
\item Additional domain tags introduced by registered
|
|
extensions \MUST{} begin with \texttt{"MUSCS"}, have length
|
|
exactly 8 bytes, and \MUSTNOT{} collide with the three
|
|
reserved tags above.
|
|
\end{itemize}
|
|
|
|
Two replicas reducing the same operation set \MUST{} derive
|
|
identical system identifiers from identical canonical inputs.
|
|
\end{requirement}
|
|
|
|
\subsection{System-Derived Counter Collisions}
|
|
\label{sec:graph:system-collisions}
|
|
|
|
The 64-bit counter portion of a system-derived identifier is
|
|
derived by BLAKE3 truncation. Truncation to 64 bits offers
|
|
sufficient collision resistance against accidental collision
|
|
(roughly $2^{32}$ system-derived identifiers of the same kind
|
|
before a 50\% chance of any pair colliding), but is weaker than
|
|
the rest of the identifier story and is potentially vulnerable
|
|
to deliberate construction of colliding canonical inputs. A
|
|
collision means the identity layer has failed: canonical state
|
|
derived from a colliding identifier cannot be trusted.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
/// An integrity anomaly that takes the document out of ordinary
|
|
/// canonical operation. Distinct from ConflictRecord (an
|
|
/// ordinary canonical-state fact): integrity anomalies indicate
|
|
/// that the structural assumptions underlying canonical state
|
|
/// have failed.
|
|
pub struct IntegrityAnomaly {
|
|
pub id: IntegrityAnomalyId,
|
|
pub kind: IntegrityAnomalyKind,
|
|
}
|
|
|
|
pub enum IntegrityAnomalyKind {
|
|
/// A system-derived identifier counter collision.
|
|
SystemIdentifierCollision {
|
|
kind: ObjectKind,
|
|
colliding_counter: u64,
|
|
input_set_a: SerializedCanonicalInputs,
|
|
input_set_b: SerializedCanonicalInputs,
|
|
},
|
|
/// An OperationSlot is Equivocated. The slot's candidates
|
|
/// are referenced; reduction has excluded the operation.
|
|
OperationSlotEquivocated {
|
|
operation_id: OperationId,
|
|
},
|
|
/// A replica's stream has been quarantined per the HLC
|
|
/// monotonicity rule. The AnomalousReplicaSegment is
|
|
/// referenced.
|
|
ReplicaStreamQuarantined {
|
|
replica: ReplicaId,
|
|
first_bad_counter: u64,
|
|
},
|
|
/// A registered anomaly defined by an extension or
|
|
/// transport.
|
|
Registered(IntegrityAnomalyRegistryId),
|
|
}
|
|
|
|
/// The kind of object whose system-derived identifier collided.
|
|
/// Open vocabulary, but intentionally narrower than TypedObjectId:
|
|
/// only the kinds actually minted into the system-derived namespace
|
|
/// appear here.
|
|
pub enum ObjectKind {
|
|
Voice, // system-promoted voices (MUSCSVCE)
|
|
Pitch, // content-derived synthetic pitches (MUSCSPCH)
|
|
Registered(OperationKindRegistryId),
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\begin{requirement}
|
|
\label{req:graph:integrity-anomaly-id}
|
|
An \texttt{IntegrityAnomalyId} \MUST{} be derived by the
|
|
system-derived identifier function of
|
|
Section~\ref{sec:graph:system-derived} with the reserved domain tag
|
|
\texttt{"MUSCSANM"} over the anomaly kind's canonical bytes:
|
|
\texttt{derive\_system\_id::<IntegrityAnomalyId>(b"MUSCSANM",
|
|
\&kind.to\_canonical\_bytes())}. Because the identity is content-derived
|
|
from the kind, two replicas observing the same structural failure
|
|
derive the same anomaly identifier and therefore agree on anomaly
|
|
identity across the network.
|
|
\end{requirement}
|
|
|
|
\begin{requirement}
|
|
\label{req:graph:object-kind-vocab}
|
|
\texttt{ObjectKind} (the kind field of
|
|
\texttt{SystemIdentifierCollision}) is an \emph{open} vocabulary
|
|
whose normative core set is exactly \texttt{Voice} and
|
|
\texttt{Pitch}, with a \texttt{Registered} escape for
|
|
extension-introduced system-derived kinds. It is deliberately
|
|
narrower than \texttt{TypedObjectId}'s 28-kind table
|
|
(Requirement~\ref{req:graph:typed-object-id-discriminants}):
|
|
\texttt{ObjectKind} enumerates only the kinds that are actually
|
|
minted into the \texttt{ReplicaId::SYSTEM\_DERIVED} namespace ---
|
|
today, promoted voices and synthetic pitches --- because those are
|
|
the only kinds for which a system-derived counter collision is
|
|
possible. A new core variant is added here only when a new object
|
|
kind begins being minted into the system-derived namespace.
|
|
|
|
The discriminant byte of \texttt{ObjectKind} is its declaration-order
|
|
index in the listing above: \texttt{Voice} $= 0$, \texttt{Pitch}
|
|
$= 1$, \texttt{Registered} $= 2$. Because \texttt{ObjectKind} is the
|
|
\texttt{kind} field of a \texttt{SystemIdentifierCollision} anomaly,
|
|
this byte enters the canonical-input preimage of the resulting
|
|
\texttt{IntegrityAnomalyId}
|
|
(Requirement~\ref{req:graph:integrity-anomaly-id}); two replicas
|
|
observing the same structural failure agree on its identity only if
|
|
they agree on this byte. The assignment is therefore normative and
|
|
stable and \MUSTNOT{} be reordered.
|
|
\end{requirement}
|
|
|
|
\begin{requirement}
|
|
\label{req:graph:system-id-collision}
|
|
Implementations \MUST{} perform a collision check during
|
|
reduction whenever a new system-derived identifier is minted.
|
|
If a newly derived 64-bit counter, within the same typed
|
|
identifier kind and within the
|
|
\texttt{ReplicaId::SYSTEM\_DERIVED} namespace, collides with a
|
|
previously-derived counter from \emph{different} canonical
|
|
inputs:
|
|
|
|
\begin{itemize}
|
|
\item An \texttt{IntegrityAnomaly} of kind
|
|
\texttt{SystemIdentifierCollision} is recorded in the
|
|
document's diagnostic integrity-anomaly register. This
|
|
register is distinct from the canonical conflict registry:
|
|
conflict records are ordinary canonical-state facts;
|
|
integrity anomalies indicate the structural assumptions
|
|
underlying canonical state have failed.
|
|
\item Canonical reduction \MUSTNOT{} continue past the
|
|
collision. The colliding object's identifier is held
|
|
pending recovery: neither input set is allowed to occupy
|
|
the collided counter in canonical state.
|
|
\item Readers \MUST{} open such a bundle in diagnostic
|
|
recovery mode, surfacing the collision prominently and
|
|
prohibiting ordinary editing of the affected portion of
|
|
canonical state.
|
|
\item Recovery is by external action: a profile-declared
|
|
deterministic policy, a transport-level credential
|
|
revocation, or an explicit user reconciliation operation.
|
|
No automatic in-spec resolution is defined.
|
|
\end{itemize}
|
|
|
|
Authoring implementations \SHOULD{} detect predictable
|
|
collisions before authoring (e.g., by canonicalizing inputs
|
|
with additional disambiguating data such as the
|
|
causally-greatest operation visible at authoring time) and
|
|
\SHOULDNOT{} rely on collision frequency being negligible in
|
|
adversarial scenarios.
|
|
\end{requirement}
|
|
|
|
\begin{rationale}
|
|
Moving system-derived collisions out of \texttt{ConflictKind}
|
|
reflects the difference in kind between the two. A
|
|
\texttt{ConflictRecord} is an ordinary canonical-state fact:
|
|
the user has two valid musical edits that conflict, and the
|
|
resolution is a normal editing action. A system-derived
|
|
identifier collision is not a musical conflict; it is a
|
|
structural failure of the identity layer. Treating it as a
|
|
conflict would let canonical state grow under the collision,
|
|
which is precisely the wrong behavior for an integrity
|
|
failure. Treating it as a diagnostic anomaly halts canonical
|
|
growth until external recovery resolves the structural issue.
|
|
\end{rationale}
|
|
|
|
\subsection{System-Derived Pitch Identity}
|
|
\label{sec:graph:system-derived-pitch}
|
|
|
|
When promotion logic introduces a synthetic pitch (rare), its
|
|
\texttt{PitchId} is content-addressed from the pitch's intrinsic
|
|
identity rather than allocated from a replica counter, so that all
|
|
replicas mint the same identifier for the same synthetic pitch.
|
|
|
|
\begin{requirement}
|
|
\label{req:graph:system-derived-pitch-id}
|
|
A system-derived \texttt{PitchId} \MUST{} be derived by the
|
|
function of Section~\ref{sec:graph:system-derived} with domain tag
|
|
\texttt{"MUSCSPCH"} over a fixed canonical byte form of the pitch's
|
|
\emph{intrinsic identity}, defined as the pair (\emph{scale
|
|
position}, \emph{acoustic realization}). All variable-width string
|
|
components of this preimage (nominal names, accidental and tuning
|
|
catalog references) \MUST{} be length-prefixed and \MUST{} be
|
|
NFC-normalized at the derivation boundary, so that
|
|
canonically-equivalent Unicode spellings derive the same identifier.
|
|
|
|
The acoustic realization's \emph{tuning reference} is always part of
|
|
intrinsic identity, including the \texttt{TuningReference::Inherit}
|
|
case: \texttt{Inherit} is encoded as a distinct presence marker, not
|
|
as the absence of a field. Consequently a synthetic pitch that
|
|
inherits its tuning and one that names an explicit tuning are
|
|
\emph{distinct} identities even when they would resolve to the same
|
|
frequency in context. This makes synthetic-pitch identity stable
|
|
under later changes to the inherited tuning environment, and aligns
|
|
Invariant~11's uniqueness check with a fixed, context-free preimage.
|
|
\end{requirement}
|
|
|
|
\subsection{Identifier Stability}
|
|
|
|
\begin{requirement}
|
|
\label{req:graph:identifier-non-reuse}
|
|
An identifier, once assigned, \MUST{} never be reassigned, even
|
|
after deletion of the corresponding object. Stale identifiers
|
|
encountered in stored references \MUST{} be detected and reported
|
|
by re-anchoring rules (see Chapter~\ref{ch:semops}); they
|
|
\MUSTNOT{} silently match a different object.
|
|
\end{requirement}
|
|
|
|
\section{The Event Arena}
|
|
\label{sec:graph:arena}
|
|
|
|
Events are the rhythmic atoms of the score: pitched events, unpitched
|
|
events, rests, indeterminate events, trajectories, graphic events,
|
|
and cues. All events of all kinds in a score are stored in a single
|
|
arena.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct EventArena {
|
|
/// Storage backing for events. Implementation-defined; the
|
|
/// observable contract is O(1) lookup by EventId and stable
|
|
/// addresses for the lifetime of the event.
|
|
storage: ArenaStorage<Event>,
|
|
|
|
/// Auxiliary index for fast time-range queries.
|
|
time_index: EventTimeIndex,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\begin{requirement}
|
|
\label{req:graph:constant-time-event-lookup}
|
|
Event lookup by \texttt{EventId} \MUST{} be $O(1)$ amortized.
|
|
Implementations \MAY{} use any storage layout meeting this contract;
|
|
vector-of-events with stable indices, slot-allocator schemes, and
|
|
generational arenas are all acceptable.
|
|
\end{requirement}
|
|
|
|
\subsection{The Event Type}
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub enum Event {
|
|
Pitched(PitchedEvent),
|
|
Unpitched(UnpitchedEvent),
|
|
Rest(Rest),
|
|
Indeterminate(IndeterminateEvent),
|
|
Trajectory(TrajectoryEvent),
|
|
Graphic(GraphicEvent),
|
|
Cue(CueEvent),
|
|
}
|
|
\end{lstlisting}
|
|
|
|
Every event variant carries:
|
|
|
|
\begin{itemize}
|
|
\item Its own \texttt{EventId}.
|
|
\item Its \emph{voice membership}: a \texttt{VoiceId} identifying
|
|
the unique voice that owns it.
|
|
\item Its \emph{position} within that voice (either a
|
|
\texttt{MusicalPosition} or \texttt{WallClockTime}, depending on
|
|
the enclosing region's time model).
|
|
\end{itemize}
|
|
|
|
Voice membership and position are stored on the event so that
|
|
operations addressing an event can resolve its context without
|
|
traversing parent containers.
|
|
|
|
\subsubsection{Event Position and Duration}
|
|
\label{sec:graph:event-time}
|
|
|
|
An event's temporal coordinates use union types matching its enclosing
|
|
region's time model. Position is unioned over musical and wall-clock
|
|
forms; duration is unioned over musical, wall-clock, and indeterminate
|
|
forms.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub enum EventPosition {
|
|
Musical(MusicalPosition),
|
|
WallClock(WallClockTime),
|
|
}
|
|
|
|
pub enum EventDuration {
|
|
Musical(MusicalDuration),
|
|
WallClock(WallClockDuration),
|
|
Indeterminate(DurationBounds),
|
|
}
|
|
|
|
/// Bounded interval expressing an indeterminate duration. Bounds are
|
|
/// concrete (not themselves indeterminate); this prevents recursive
|
|
/// indeterminacy in the type system.
|
|
pub struct DurationBounds {
|
|
pub lower: Option<ConcreteDuration>,
|
|
pub upper: Option<ConcreteDuration>,
|
|
}
|
|
|
|
pub enum ConcreteDuration {
|
|
Musical(MusicalDuration),
|
|
WallClock(WallClockDuration),
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\begin{requirement}
|
|
\label{req:graph:region-coordinate-kinds}
|
|
Event position and duration variants \MUST{} agree with the time
|
|
model of the enclosing region:
|
|
|
|
\begin{itemize}
|
|
\item Metric regions accept \texttt{EventPosition::Musical} and
|
|
\texttt{EventDuration::Musical} only.
|
|
\item Proportional regions accept \texttt{EventPosition::WallClock}
|
|
and \texttt{EventDuration::WallClock} only.
|
|
\item Aleatoric regions \MAY{} accept any combination consistent
|
|
with the region's declared anchoring discipline (see
|
|
Section~\ref{sec:graph:aleatoric-discipline}).
|
|
\end{itemize}
|
|
|
|
Implementations \MUST{} reject events whose coordinate variants
|
|
contradict their enclosing region's time model.
|
|
\end{requirement}
|
|
|
|
\begin{requirement}
|
|
\label{req:graph:duration-bound-kinds}
|
|
If both bounds of a \texttt{DurationBounds} are present, they
|
|
\MUST{} use the same \texttt{ConcreteDuration} variant, unless the
|
|
enclosing aleatoric region's anchoring discipline explicitly
|
|
permits mixed-kind bounds.
|
|
\end{requirement}
|
|
|
|
\subsubsection{Identified Pitches}
|
|
\label{sec:graph:identified-pitch}
|
|
|
|
Pitches embedded in events are wrapped in \texttt{IdentifiedPitch}
|
|
records that pair each pitch with a stable \texttt{PitchId}
|
|
(Section~\ref{sec:pitch:ids}). The identifier enables spelling
|
|
attachments, respelling operations, pitch-level reduction rules
|
|
under concurrent edits, and stable tie pairing through chord
|
|
reordering.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct IdentifiedPitch {
|
|
pub id: PitchId,
|
|
pub pitch: Pitch,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\begin{requirement}
|
|
\label{req:graph:identified-pitch-ownership}
|
|
Every pitch embedded in an event \MUST{} be wrapped in an
|
|
\texttt{IdentifiedPitch}. The contained \texttt{PitchId} \MUST{} be
|
|
unique within the score's pitch-identity index. Pitch identifiers
|
|
\MUST{} be minted atomically with their containing event: the
|
|
operation creating an event mints \texttt{EventId} and the
|
|
\texttt{PitchId}s of all embedded pitches in a single causal step.
|
|
\end{requirement}
|
|
|
|
\subsubsection{Pitched Events}
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct PitchedEvent {
|
|
pub id: EventId,
|
|
pub voice: VoiceId,
|
|
pub position: EventPosition,
|
|
pub duration: EventDuration,
|
|
|
|
/// One or more identified pitches. A single pitch is a
|
|
/// single-note event; multiple pitches are a chord.
|
|
pub pitches: Vec<IdentifiedPitch>,
|
|
|
|
/// Articulations attached directly to the event.
|
|
pub articulations: Vec<ArticulationMark>,
|
|
|
|
/// Single-event dynamic marking (e.g., sfz, fp). Spanning
|
|
/// dynamics are cross-cutting structures, not event fields.
|
|
pub dynamic: Option<DynamicMark>,
|
|
|
|
/// Ornaments attached to the event. Ornament resolutions (the
|
|
/// realized notes of a trill, mordent, etc.) are cross-cutting
|
|
/// structures.
|
|
pub ornaments: Vec<OrnamentMark>,
|
|
|
|
/// Stem configuration: direction, length adjustment, hidden.
|
|
pub stem: StemConfiguration,
|
|
|
|
/// Whether this event is a grace note, and if so its kind.
|
|
pub grace: Option<GraceKind>,
|
|
}
|
|
|
|
pub enum GraceKind {
|
|
Acciaccatura,
|
|
Appoggiatura,
|
|
Unmeasured,
|
|
MeasuredFraction(MusicalDuration),
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\begin{requirement}
|
|
\label{req:graph:pitched-event-nonempty}
|
|
A \texttt{PitchedEvent} \MUST{} have at least one pitch. Empty
|
|
pitch lists are forbidden; use \texttt{Rest} for the no-pitch case.
|
|
\end{requirement}
|
|
|
|
\subsubsection{Unpitched Events}
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct UnpitchedEvent {
|
|
pub id: EventId,
|
|
pub voice: VoiceId,
|
|
pub position: EventPosition,
|
|
pub duration: EventDuration,
|
|
|
|
/// Staff line position for the unpitched event (e.g., snare
|
|
/// drum at the middle line of a 5-line staff, or position 0
|
|
/// for a single-line staff).
|
|
pub staff_position: StaffPosition,
|
|
|
|
/// The unpitched instrument identifier, for sound mapping.
|
|
pub instrument_member: UnpitchedMemberId,
|
|
|
|
pub articulations: Vec<ArticulationMark>,
|
|
pub dynamic: Option<DynamicMark>,
|
|
pub stem: StemConfiguration,
|
|
pub grace: Option<GraceKind>,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsubsection{Rests}
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct Rest {
|
|
pub id: EventId,
|
|
pub voice: VoiceId,
|
|
pub position: EventPosition,
|
|
pub duration: EventDuration,
|
|
|
|
/// Optional explicit vertical positioning. If None, the engraver
|
|
/// chooses based on voice and meter context.
|
|
pub vertical_position: Option<StaffPosition>,
|
|
|
|
/// Whether this rest is visible. Invisible rests preserve timing
|
|
/// but do not render.
|
|
pub visible: bool,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsubsection{Indeterminate Events}
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct IndeterminateEvent {
|
|
pub id: EventId,
|
|
pub voice: VoiceId,
|
|
pub position: EventPosition,
|
|
pub duration: EventDuration,
|
|
|
|
/// What aspect of the event is indeterminate.
|
|
pub indeterminacy: IndeterminacyKind,
|
|
|
|
/// Optional hints constraining the indeterminacy.
|
|
pub hints: IndeterminacyHints,
|
|
}
|
|
|
|
pub enum IndeterminacyKind {
|
|
/// "Any pitch in this range/contour."
|
|
Pitch,
|
|
|
|
/// "Any duration within these bounds." (Realized via
|
|
/// EventDuration::Indeterminate on the event itself.)
|
|
Duration,
|
|
|
|
/// "Any of these alternatives."
|
|
Choice,
|
|
|
|
/// Combination of multiple indeterminacies.
|
|
Compound(Vec<IndeterminacyKind>),
|
|
}
|
|
|
|
pub struct IndeterminacyHints {
|
|
pub pitch_range: Option<PitchRange>,
|
|
pub pitch_contour: Option<ContourHint>,
|
|
pub density: Option<DensityHint>,
|
|
pub textual_instruction: Option<String>,
|
|
pub duration_bounds: Option<DurationBounds>,
|
|
pub alternatives: Vec<EventId>,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsubsection{Trajectory Events}
|
|
|
|
A trajectory event represents a continuous pitch motion: a glissando,
|
|
portamento, or pitch bend.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct TrajectoryEvent {
|
|
pub id: EventId,
|
|
pub voice: VoiceId,
|
|
pub position: EventPosition,
|
|
pub duration: EventDuration,
|
|
|
|
/// Start and end pitch endpoints. Either may reference another
|
|
/// event's pitch by id, or may carry an explicit identified
|
|
/// pitch local to the trajectory.
|
|
pub start: TrajectoryEndpoint,
|
|
pub end: TrajectoryEndpoint,
|
|
|
|
/// Shape of the trajectory between endpoints.
|
|
pub shape: TrajectoryShape,
|
|
|
|
/// Visual representation.
|
|
pub display: TrajectoryDisplay,
|
|
}
|
|
|
|
pub enum TrajectoryEndpoint {
|
|
/// Reference to a pitch belonging to another event.
|
|
EventPitch(PitchId),
|
|
|
|
/// An explicit identified pitch local to this trajectory.
|
|
/// Such pitches participate in spelling, identity, and editing
|
|
/// the same way embedded pitches do.
|
|
ExplicitPitch(IdentifiedPitch),
|
|
}
|
|
|
|
pub enum TrajectoryShape {
|
|
Linear,
|
|
Exponential,
|
|
Curve(Vec<TrajectoryControlPoint>),
|
|
Stepwise(Vec<IdentifiedPitch>),
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsubsection{Graphic Events}
|
|
|
|
A graphic event is an event whose primary content is graphic (drawn
|
|
or constructed) rather than notated through the standard staff system.
|
|
Used within hybrid and free-graphic regions for events that nevertheless
|
|
occupy a rhythmic slot in a voice.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct GraphicEvent {
|
|
pub id: EventId,
|
|
pub voice: VoiceId,
|
|
pub position: EventPosition,
|
|
pub duration: EventDuration,
|
|
|
|
/// Reference to the graphic object(s) realizing this event.
|
|
pub graphics: Vec<GraphicObjectId>,
|
|
|
|
/// Optional playback parameter bindings (see
|
|
/// Section~\ref{sec:graph:timebinding}).
|
|
pub playback_bindings: Vec<PlaybackBinding>,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsubsection{Cue Events}
|
|
|
|
A cue is a small-print rendering of music from another voice or
|
|
instrument, displayed as a guide.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct CueEvent {
|
|
pub id: EventId,
|
|
pub voice: VoiceId,
|
|
pub position: EventPosition,
|
|
pub duration: EventDuration,
|
|
|
|
/// The source events being cued.
|
|
pub source: Vec<EventId>,
|
|
|
|
/// Rendering hints: how the cue should be drawn (size, label,
|
|
/// abbreviation of the source instrument's name, etc.).
|
|
pub rendering: CueRendering,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\section{The Canvas}
|
|
\label{sec:graph:canvas}
|
|
|
|
The canvas is the spatial root of the score. It contains regions.
|
|
|
|
The score root and the canvas are \emph{structural givens}, not
|
|
operation products (ratified Pass~12): genesis is the creation of an
|
|
empty score together with its bundle, outside the operation set, and
|
|
neither object is ever minted, addressed, or deleted by an operation
|
|
(there is no \texttt{TypedObjectId} kind for either). This is
|
|
deliberate --- a document that could mint its own root would admit a
|
|
genesis race under concurrent editing, and every operation would need
|
|
a defined semantics against a not-yet-existing root. The decision may
|
|
be revisited only if an addressable multi-canvas model is adopted at
|
|
a future schema major.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct Canvas {
|
|
/// Regions in this canvas. Each region declares its time and
|
|
/// content models and occupies a time and staff extent.
|
|
pub regions: Vec<Region>,
|
|
|
|
/// Canvas-level configuration: paper size, margins, default
|
|
/// system layout, page-break behavior.
|
|
pub layout_defaults: CanvasLayoutDefaults,
|
|
}
|
|
|
|
/// Canvas-level layout defaults. Page size and margins are in staff
|
|
/// spaces; a solver reads them as its default page geometry and MAY
|
|
/// override per solve. The type is defined with schema major 1 (its field
|
|
/// was named on the canvas from the first draft; only its type was
|
|
/// undefined).
|
|
pub struct CanvasLayoutDefaults {
|
|
pub page_size: CanvasSize,
|
|
pub margins: CanvasMargins,
|
|
}
|
|
|
|
/// A page size in staff spaces (1 staff space = staff height / 4).
|
|
pub struct CanvasSize {
|
|
pub width: f64,
|
|
pub height: f64,
|
|
}
|
|
|
|
/// Page margins in staff spaces.
|
|
pub struct CanvasMargins {
|
|
pub top: f64,
|
|
pub right: f64,
|
|
pub bottom: f64,
|
|
pub left: f64,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
The default is \textbf{A4 portrait at an 8\,mm staff} (1 staff space =
|
|
2\,mm): page $105 \times 148.5$ staff spaces, $7.5$-staff-space margins,
|
|
hence a $90 \times 133.5$ content area. Each staff-space scalar is realized
|
|
canonically as a \texttt{CanonicalF64} leaf; the reference engraver maps a
|
|
\texttt{CanvasLayoutDefaults} onto its internal page geometry. This closes
|
|
the long-standing gap in which the canvas named \texttt{layout\_defaults} but
|
|
no chapter defined \texttt{CanvasLayoutDefaults}.
|
|
|
|
\subsection{Regions}
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct Region {
|
|
pub id: RegionId,
|
|
|
|
/// Time model: metric, proportional, or aleatoric.
|
|
pub time_model: RegionTimeModel,
|
|
|
|
/// Content model: staff-based, free graphic, or hybrid.
|
|
pub content: RegionContent,
|
|
|
|
/// Range in time that this region occupies.
|
|
pub time_extent: TimeExtent,
|
|
|
|
/// Range in vertical staff space that this region occupies.
|
|
/// A region may cover all staves (full-score region) or a subset
|
|
/// (a free-graphic gesture occupying only one staff's vertical
|
|
/// band, for example).
|
|
pub staff_extent: StaffExtent,
|
|
|
|
/// Optional tempo and tuning overrides scoped to this region.
|
|
pub local_tempo_map: Option<TempoMap>,
|
|
pub local_tuning_overrides: Vec<TuningOverride>,
|
|
|
|
/// Whether spanners (slurs first of all) may cross this region's
|
|
/// boundary into an adjacent region. Default false: cross-region
|
|
/// spanning is not permitted unless a region opts in. Added with
|
|
/// schema major 1.
|
|
pub permits_spanning_slurs: bool,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
For a slur (or other spanner) whose endpoints lie in \emph{different}
|
|
regions, permission is governed conjunctively (ratified Pass~12):
|
|
the boundary is permeable only when \emph{both} endpoint regions set
|
|
\texttt{permits\_spanning\_slurs} --- a region that forbids spanning
|
|
is never crossed against its declaration, regardless of which side
|
|
the spanner starts on. The check is an authoring-time advisory only;
|
|
it never alters canonical reduction.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
|
|
pub enum RegionContent {
|
|
/// Staff-based notation: one or more staves carrying voices and
|
|
/// events.
|
|
StaffBased(StaffBasedContent),
|
|
|
|
/// Free graphic content: no staves, only graphic objects placed
|
|
/// in the region's coordinate space.
|
|
FreeGraphic(GraphicContent),
|
|
|
|
/// Hybrid: staves with overlaid graphic content. The graphic
|
|
/// layer is rendered above (or below, configurably) the staff
|
|
/// layer.
|
|
Hybrid {
|
|
staves: StaffBasedContent,
|
|
overlay: GraphicContent,
|
|
overlay_below_staves: bool,
|
|
},
|
|
}
|
|
|
|
pub struct TimeExtent {
|
|
pub start: TimeAnchor,
|
|
pub end: TimeAnchor,
|
|
}
|
|
|
|
pub struct StaffExtent {
|
|
/// Identifiers of the globally-identified staves whose content
|
|
/// this region carries. A region carries one StaffInstance per
|
|
/// listed StaffId. The same StaffId may appear in the
|
|
/// staff_extent of adjacent regions; this is the mechanism by
|
|
/// which a staff's identity persists across regions.
|
|
pub staves: Vec<StaffId>,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsection{Region Overlap and Concurrency}
|
|
|
|
\begin{requirement}
|
|
\label{req:graph:region-overlap-constraint}
|
|
Regions \MAY{} overlap in time if their staff extents are disjoint.
|
|
Regions \MUSTNOT{} overlap in both time and staff extent. The
|
|
graph's construction \MUST{} reject configurations violating this
|
|
constraint.
|
|
\end{requirement}
|
|
|
|
\begin{rationale}
|
|
Concurrent regions on disjoint staves are essential for the hybrid
|
|
notation feature: a metric staff and a proportional staff sounding
|
|
simultaneously are two regions with overlapping time extents but
|
|
disjoint staff extents. Overlapping time \emph{and} staff would mean
|
|
two regions claim the same content, which is ambiguous.
|
|
\end{rationale}
|
|
|
|
\subsection{Staff-Based Content}
|
|
|
|
A staff-based region carries one or more staff \emph{instances}, each
|
|
of which manifests a globally-identified staff for the duration of the
|
|
region. The score's metric organization is per-staff (admitting
|
|
polymeter); barline alignment between staves is recorded as an
|
|
explicit relationship when meters coincide.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct StaffBasedContent {
|
|
/// Staff instances appearing in this region. Each references a
|
|
/// globally-identified Staff and provides region-local content.
|
|
pub staff_instances: Vec<StaffInstance>,
|
|
|
|
/// Default metric grid for this region. Staves without their own
|
|
/// local_metric_grid inherit this grid. None means the region
|
|
/// has no default metric organization (acceptable for transitional
|
|
/// regions or for regions where every staff declares its own).
|
|
pub default_metric_grid: Option<MetricGrid>,
|
|
|
|
/// Explicit barline alignment groups: places where measures
|
|
/// across multiple staves are declared to coincide. Used for
|
|
/// engraving barlines that span multiple staves under polymeter
|
|
/// and for analytical purposes. Implementations MAY derive
|
|
/// some alignment groups automatically from coincident measure
|
|
/// starts; explicit groups are authoritative.
|
|
pub barline_alignment_groups: Vec<BarlineAlignmentGroup>,
|
|
|
|
/// System breaks declared by the user. The engraver may
|
|
/// override these if region settings permit (see Chapter 6).
|
|
pub user_system_breaks: Vec<TimeAnchor>,
|
|
|
|
/// Page breaks declared by the user. Same override rule.
|
|
pub user_page_breaks: Vec<TimeAnchor>,
|
|
}
|
|
|
|
/// A region-local manifestation of a globally-identified Staff.
|
|
pub struct StaffInstance {
|
|
pub id: StaffInstanceId,
|
|
|
|
/// The globally-identified staff this instance manifests.
|
|
pub staff: StaffId,
|
|
|
|
/// Voices appearing on this staff instance.
|
|
pub voices: Vec<Voice>,
|
|
|
|
/// Clef changes within this instance.
|
|
pub clef_sequence: Vec<ClefChange>,
|
|
|
|
/// Key signature changes within this instance.
|
|
pub key_sequence: Vec<KeySignatureChange>,
|
|
|
|
/// Optional local metric grid: if Some, overrides the region's
|
|
/// default_metric_grid for this staff (polymetric case).
|
|
/// If None, the staff inherits the region's default_metric_grid.
|
|
pub local_metric_grid: Option<MetricGrid>,
|
|
|
|
/// Measures belonging to this staff instance, in order. Measures
|
|
/// are per-staff (admitting polymeter); alignment across staves
|
|
/// is recorded in barline_alignment_groups.
|
|
pub measures: Vec<Measure>,
|
|
|
|
/// Optional per-instance instrument override. None means inherit
|
|
/// from the global Staff's declared instrument.
|
|
pub instrument_override: Option<InstrumentId>,
|
|
|
|
/// Optional per-instance staff line configuration override.
|
|
/// None means inherit from the global Staff's default.
|
|
pub staff_lines_override: Option<StaffLineConfiguration>,
|
|
|
|
/// Whether this instance is visible. Hidden instances preserve
|
|
/// content but are not rendered (used for empty-staff suppression).
|
|
pub visible: bool,
|
|
}
|
|
|
|
/// The clef content model (ratified schema major 2; the value types
|
|
/// landed with the visible slice and supersede the earlier ClefId
|
|
/// identifier sketch). A clef is a value, not a registry reference.
|
|
pub enum ClefShape {
|
|
/// G clef (treble family) -- reference pitch G4.
|
|
G,
|
|
/// F clef (bass family) -- reference pitch F3.
|
|
F,
|
|
/// C clef (alto / tenor family) -- reference pitch middle C (C4).
|
|
C,
|
|
/// Unpitched percussion clef -- no diatonic reference.
|
|
Percussion,
|
|
}
|
|
|
|
pub struct Clef {
|
|
pub shape: ClefShape,
|
|
/// The staff line (1 = bottom) the shape's reference pitch sits on.
|
|
pub line: i8,
|
|
/// Signed octave displacement (e.g., -1 for the vocal tenor G clef).
|
|
pub octave_shift: i8,
|
|
}
|
|
|
|
/// A key signature on the circle of fifths, validated to the
|
|
/// conventional CMN range -7 (seven flats) ..= +7 (seven sharps).
|
|
/// Constructed only through a checked constructor; decoders reject
|
|
/// out-of-range values.
|
|
pub struct KeySignature {
|
|
fifths: i8, // private; KeySignature::new rejects |fifths| > 7
|
|
}
|
|
|
|
/// A clef change at a point in a staff instance.
|
|
pub struct ClefChange {
|
|
pub anchor: TimeAnchor,
|
|
pub clef: Clef,
|
|
}
|
|
|
|
/// A key-signature change at a point in a staff instance.
|
|
pub struct KeySignatureChange {
|
|
pub anchor: TimeAnchor,
|
|
pub key: KeySignature,
|
|
}
|
|
|
|
/// A per-staff (or per-region-default) metric organization.
|
|
pub struct MetricGrid {
|
|
/// Sequence of meter changes within the scope of this grid.
|
|
pub meter_sequence: Vec<MeterChange>,
|
|
}
|
|
|
|
pub struct MeterChange {
|
|
/// Where this meter takes effect, expressed as an anchor within
|
|
/// the enclosing staff or region.
|
|
pub anchor: TimeAnchor,
|
|
|
|
/// The active time signature from this point until the next
|
|
/// MeterChange or the end of the grid's scope.
|
|
pub time_signature: TimeSignatureId,
|
|
}
|
|
|
|
/// A declaration that the measure boundaries of two or more staff
|
|
/// instances coincide at a particular point.
|
|
pub struct BarlineAlignmentGroup {
|
|
pub id: BarlineAlignmentGroupId,
|
|
|
|
/// The staff instances participating in this alignment, paired
|
|
/// with the measures whose boundaries coincide.
|
|
pub members: Vec<BarlineAlignmentMember>,
|
|
}
|
|
|
|
pub struct BarlineAlignmentMember {
|
|
pub staff_instance: StaffInstanceId,
|
|
pub measure: MeasureId,
|
|
pub position: MeasurePosition,
|
|
}
|
|
|
|
pub struct Measure {
|
|
pub id: MeasureId,
|
|
pub start: TimeAnchor,
|
|
pub time_signature: Option<TimeSignatureId>,
|
|
pub explicit_number: Option<u32>,
|
|
pub number_visibility: MeasureNumberVisibility,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\begin{requirement}
|
|
\label{req:graph:staff-owned-measures}
|
|
Measures \MUST{} belong to a single \texttt{StaffInstance}, not to
|
|
the enclosing region. This admits polymeter: different staves in
|
|
the same region \MAY{} carry different meter sequences and
|
|
consequently different measure boundaries. Barline coincidences
|
|
across staves \MUST{} be expressed via
|
|
\texttt{BarlineAlignmentGroup} entries when explicit alignment is
|
|
authoritative; otherwise alignment is derived at engraving time
|
|
from coincident measure-start positions.
|
|
\end{requirement}
|
|
|
|
\section{Staves, Voices, and Instruments}
|
|
\label{sec:graph:staves}
|
|
|
|
\subsection{Instruments}
|
|
|
|
An instrument is an abstract definition of a sounding entity.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct Instrument {
|
|
pub id: InstrumentId,
|
|
pub name: String,
|
|
pub abbreviation: Option<String>,
|
|
|
|
/// MIDI program, sound library mapping, or unpitched instrument
|
|
/// definitions. The structure is detailed in the audio engine
|
|
/// specification (out of scope here); the core stores the
|
|
/// configuration opaquely.
|
|
pub sound_config: SoundConfiguration,
|
|
|
|
/// Default transposition (e.g., B-flat clarinet sounds a major
|
|
/// second below written).
|
|
pub transposition: Option<TranspositionInterval>,
|
|
|
|
/// Default playable range. Engraving may flag out-of-range
|
|
/// pitches; not a hard constraint.
|
|
pub range: Option<PitchRange>,
|
|
|
|
/// Default clef and staff line configuration for staves of this
|
|
/// instrument. The clef is the content-bearing Clef value defined
|
|
/// in this chapter (shape, line, octave shift) -- the value model
|
|
/// ratified with the visible slice supersedes the earlier ClefId
|
|
/// identifier sketch (reconciled schema major 2).
|
|
pub default_clef: Clef,
|
|
pub default_staff_lines: StaffLineConfiguration,
|
|
|
|
/// For unpitched instruments: definitions of each playable
|
|
/// member (snare, kick, ride bell, etc.) and their staff
|
|
/// positions.
|
|
pub unpitched_members: Vec<UnpitchedMember>,
|
|
}
|
|
|
|
/// Opaque sound configuration (defined schema major 2). The audio
|
|
/// engine specification owns the structure; the core stores the bytes
|
|
/// verbatim and never interprets them. Empty is the default.
|
|
pub struct SoundConfiguration(pub Vec<u8>);
|
|
|
|
// TranspositionInterval (defined schema major 2) is specified in
|
|
// Chapter 2, "Transposition and the Interval Type": the diatonic and
|
|
// chromatic step counts of an interval, and its action on a scale
|
|
// position. Instrument.transposition is the written-versus-sounding
|
|
// interval of a transposing instrument (a B-flat clarinet is
|
|
// -1 diatonic, -2 chromatic).
|
|
//
|
|
// Its ACTION is pinned (Chapter 2). What remains unimplemented is its
|
|
// AUTOMATIC APPLICATION at the instrument boundary: nothing in the core
|
|
// yet respells a written part into a sounding one, or resolves either to
|
|
// a frequency. Instrument.transposition is therefore still advisory --
|
|
// but for that reason, not for want of interval algebra.
|
|
|
|
/// One playable member of an unpitched instrument (defined schema
|
|
/// major 2): the identifier UnpitchedEvent.instrument_member
|
|
/// references, its display name, and its default staff position.
|
|
pub struct UnpitchedMember {
|
|
pub member: UnpitchedMemberId,
|
|
pub name: String,
|
|
pub staff_position: StaffPosition,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\texttt{UnpitchedMemberId} is an \emph{instrument-scoped small value}
|
|
(a plain 32-bit integer), not a member of the 128-bit typed-identifier
|
|
family --- it is minted by the author of the instrument definition, not
|
|
by replicas, and participates in no content derivation. Member values
|
|
\emph{should} be unique within one instrument's
|
|
\texttt{unpitched\_members}; the authoring advisory layer flags a
|
|
duplicate, and resolution is by first match in list order (canonical
|
|
bytes preserve the authored order), so a duplicate is deterministic,
|
|
merely shadowed. An \texttt{UnpitchedEvent.instrument\_member} with no
|
|
matching entry resolves to \emph{no member definition}: the event still
|
|
renders (its own \texttt{staff\_position} field governs placement ---
|
|
the member's \texttt{staff\_position} is the \emph{authoring default}
|
|
copied onto new events, not a render-time override), and sound mapping
|
|
falls back to the instrument's \texttt{sound\_config}. This
|
|
no-match tolerance is load-bearing: every pre-major-2 instrument has an
|
|
empty member list while its events carry member values.
|
|
\texttt{StaffPosition}'s vertical convention (which integer is which
|
|
line, and its orientation) remains \emph{deliberately deferred} to the
|
|
Chapter~\ref{ch:layout-ir} vertical model, with the unpitched-rendering
|
|
tranche as its landing site; until then the field is preserved and
|
|
compared verbatim, and no implementation may claim conformant unpitched
|
|
\emph{rendering}.
|
|
|
|
\subsection{Staves: Identity Versus Instance}
|
|
\label{sec:graph:staff-identity}
|
|
|
|
The specification distinguishes two staff concepts:
|
|
|
|
\begin{description}
|
|
\item[\texttt{Staff}] is the \emph{global, abstract} staff identity
|
|
persisting across the entire score. A staff is the thing a
|
|
performer is reading: the "Flute 1" staff, the "Piano upper
|
|
staff." A staff is declared once in the score and is referenced
|
|
by staff instances in any region where it appears.
|
|
\item[\texttt{StaffInstance}] is the \emph{region-local manifestation}
|
|
of a staff: the voices, measures, clef changes, key changes, and
|
|
metric grid that belong to that staff for the duration of one
|
|
region. A single \texttt{Staff} may have multiple
|
|
\texttt{StaffInstance}s across the score; each instance belongs
|
|
to exactly one region.
|
|
\end{description}
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct Staff {
|
|
pub id: StaffId,
|
|
|
|
/// Human-readable name (e.g., "Flute 1", "Violin II").
|
|
pub name: String,
|
|
|
|
/// Optional abbreviated name for subsequent systems.
|
|
pub abbreviation: Option<String>,
|
|
|
|
/// The instrument this staff renders by default. May be
|
|
/// overridden per-instance.
|
|
pub instrument: InstrumentId,
|
|
|
|
/// Default clef for new instances of this staff (the
|
|
/// content-bearing Clef value defined in this chapter; reconciled
|
|
/// schema major 2).
|
|
pub default_clef: Clef,
|
|
|
|
/// Default staff line configuration.
|
|
pub default_staff_lines: StaffLineConfiguration,
|
|
|
|
/// Visual grouping: which staff group (if any) this staff
|
|
/// belongs to (e.g., piano grand staff, choral group).
|
|
pub group: Option<StaffGroupId>,
|
|
}
|
|
|
|
pub struct StaffLineConfiguration {
|
|
pub line_count: u8,
|
|
pub line_spacing: SpaceUnit,
|
|
pub line_style: LineStyle,
|
|
pub bracket: Option<StaffBracketKind>,
|
|
}
|
|
|
|
/// A dimension in staff spaces (defined schema major 2). Staff line
|
|
/// spacing is relative to the global staff space; 1.0 is a
|
|
/// normal-size staff, smaller values yield cue/ossia staves.
|
|
pub struct SpaceUnit(pub CanonicalF64);
|
|
|
|
/// A line drawing style (defined schema major 2). Shared by staff
|
|
/// lines and the slur/tie/spanner style records below.
|
|
pub enum LineStyle {
|
|
Solid,
|
|
Dashed,
|
|
Dotted,
|
|
}
|
|
|
|
/// A per-staff bracket adornment (defined schema major 2), distinct
|
|
/// from StaffGroup-level bracketing.
|
|
pub enum StaffBracketKind {
|
|
Brace,
|
|
Bracket,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
The score's top-level structure carries a list of \texttt{Staff}
|
|
objects alongside its list of \texttt{Instrument} objects; each
|
|
region's staff instances reference these by \texttt{StaffId}.
|
|
|
|
\begin{requirement}
|
|
\label{req:graph:staff-instance-ownership}
|
|
Every \texttt{StaffInstance.staff} \MUST{} resolve to a
|
|
\texttt{Staff} declared at the score level. A single
|
|
\texttt{StaffId} \MAY{} be referenced by multiple
|
|
\texttt{StaffInstance}s in different regions: this is how a staff
|
|
maintains continuous identity across the score's regions.
|
|
|
|
A \texttt{StaffInstance} \MUST{} belong to exactly one region. The
|
|
region's \texttt{staff\_instances} list \MUST{} contain the
|
|
instance, and the instance's content is owned by that region.
|
|
|
|
Continuous attachments (e.g., key signatures, clef changes, metric
|
|
grids) \MAY{} appear on multiple staff instances of the same
|
|
\texttt{Staff} when those instances are adjacent in time; the
|
|
engraving pipeline reconstructs continuous staff appearance from
|
|
per-instance content.
|
|
\end{requirement}
|
|
|
|
\subsection{Voices}
|
|
|
|
A voice is a polyphonic line within a staff instance. Voices hold
|
|
ordered references to events; the events themselves live in the
|
|
score's arena.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct Voice {
|
|
pub id: VoiceId,
|
|
|
|
/// Ordered references to events in this voice, sorted by
|
|
/// position. Each event has voice == this voice's id.
|
|
pub events: Vec<EventId>,
|
|
|
|
/// Default stem direction for this voice. Common conventions:
|
|
/// voice 1 stems up, voice 2 stems down on the same staff.
|
|
pub default_stem_direction: Option<StemDirection>,
|
|
|
|
/// Whether this voice is the staff's primary voice (for purposes
|
|
/// of rest placement, beam direction defaults, and similar
|
|
/// engraving decisions).
|
|
pub is_primary: bool,
|
|
|
|
/// Provenance of this voice: user-declared, imported, or
|
|
/// system-promoted by concurrent-edit conflict resolution.
|
|
pub origin: VoiceOrigin,
|
|
}
|
|
|
|
pub enum VoiceOrigin {
|
|
/// Created by an explicit user action.
|
|
UserDeclared,
|
|
|
|
/// Imported from a foreign format.
|
|
Imported { format: ForeignFormatId },
|
|
|
|
/// System-promoted to resolve a concurrent-edit collision.
|
|
/// The original_voice is the voice into which the user had
|
|
/// intended to insert; the promoted voice carries the event
|
|
/// authored by losing_operation.
|
|
SystemPromoted {
|
|
winning_operation: OperationId,
|
|
losing_operation: OperationId,
|
|
original_voice: VoiceId,
|
|
},
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\begin{requirement}
|
|
\label{req:graph:unbounded-voice-count}
|
|
The number of voices per staff instance \MUST{} be unbounded in
|
|
the data model. Engraving heuristics (Chapter~\ref{ch:layout-ir}
|
|
and beyond) may flag visual issues with high voice counts; the
|
|
graph admits any number.
|
|
\end{requirement}
|
|
|
|
\begin{requirement}
|
|
\label{req:graph:event-voice-membership}
|
|
Every event \MUST{} belong to exactly one voice. Voice membership
|
|
is stored on the event (the \texttt{voice} field) and \MUST{} agree
|
|
with the membership of the voice's \texttt{events} list:
|
|
for any event $e$ in voice $v$'s events list, $e\texttt{.voice}$
|
|
\MUST{} equal $v\texttt{.id}$.
|
|
\end{requirement}
|
|
|
|
\subsubsection{System-Promoted Voices}
|
|
\label{sec:graph:promoted-voices}
|
|
|
|
When concurrent operations cause an \texttt{InsertEvent} collision
|
|
that cannot be resolved by ordering alone (because both events
|
|
have positive duration and would overlap within the target voice),
|
|
the deterministically-losing event is placed in a newly allocated
|
|
voice with \texttt{origin: VoiceOrigin::SystemPromoted}. This
|
|
preserves the non-overlap invariant without rejecting user intent.
|
|
|
|
\begin{requirement}
|
|
\label{req:graph:promoted-voice-id}
|
|
The identifier of a system-promoted voice \MUST{} be derived
|
|
deterministically from a fixed function of:
|
|
|
|
\begin{enumerate}
|
|
\item The enclosing staff instance's \texttt{StaffInstanceId}.
|
|
\item The original target voice's \texttt{VoiceId}.
|
|
\item The winning operation's \texttt{OperationId}.
|
|
\item The losing operation's \texttt{OperationId}.
|
|
\end{enumerate}
|
|
|
|
The derivation is the system-derived identifier function of
|
|
Section~\ref{sec:graph:system-derived} with domain tag
|
|
\texttt{"MUSCSVCE"} over a fixed 64-byte preimage formed by
|
|
concatenating the four identifiers in the order listed above, each
|
|
as its 16-byte big-endian canonical form:
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
fn derive_promoted_voice_id(
|
|
staff_instance: StaffInstanceId,
|
|
original_voice: VoiceId,
|
|
winning_op: OperationId,
|
|
losing_op: OperationId,
|
|
) -> VoiceId {
|
|
// 4 * 16 = 64 bytes, each component big-endian.
|
|
let mut inputs = Vec::with_capacity(64);
|
|
inputs.extend_from_slice(&staff_instance.canonical_bytes());
|
|
inputs.extend_from_slice(&original_voice.canonical_bytes());
|
|
inputs.extend_from_slice(&winning_op.canonical_bytes());
|
|
inputs.extend_from_slice(&losing_op.canonical_bytes());
|
|
derive_system_id::<VoiceId>(b"MUSCSVCE", &inputs)
|
|
}
|
|
\end{lstlisting}
|
|
|
|
The enclosing staff instance is recovered from graph containment at
|
|
reduction time; the operation does not store it redundantly.
|
|
Determinism is required so that all replicas independently arrive at
|
|
the same promoted voice identity. This derivation is the same one
|
|
whose result Invariant~18 (Section~\ref{sec:graph:invariants})
|
|
verifies; the reducer that mints the voice and the verifier that
|
|
checks it consume one derivation.
|
|
\end{requirement}
|
|
|
|
\begin{requirement}
|
|
\label{req:graph:promotion-generalization}
|
|
The promotion rule generalizes from the pairwise case to any number
|
|
of concurrent overlapping inserts into the same voice by an
|
|
order-independent pre-pass:
|
|
|
|
\begin{enumerate}
|
|
\item Bucket the concurrent \texttt{InsertEvent} operations by
|
|
their target \texttt{(staff\_instance, original\_voice)}.
|
|
\item Within a bucket, walk the operations in ascending
|
|
\texttt{OperationId} order, maintaining a \emph{retained set} of
|
|
mutually non-overlapping inserts that stay in the original voice.
|
|
\item An operation whose inserted interval does not overlap any
|
|
member of the retained set joins the retained set. An operation
|
|
whose interval overlaps some retained member is a \emph{loser}
|
|
and is promoted; the lowest-\texttt{OperationId} retained member
|
|
it overlaps is its \texttt{winning\_operation}.
|
|
\end{enumerate}
|
|
|
|
Because the walk is by \texttt{OperationId} and the retained set is
|
|
built deterministically, every replica promotes the same operations
|
|
and derives the same promoted-voice identities regardless of
|
|
delivery order. ``Overlap'' is interval overlap of the half-open
|
|
inserted spans \texttt{[start,\,end)}: the rule applies to
|
|
\emph{partial} overlaps, not only to inserts that share an identical
|
|
start position. This is a deliberate widening of the pairwise rule's
|
|
identical-start-position language; it is the correct reading because
|
|
any positive-duration overlap breaks the voice non-overlap invariant
|
|
(Invariant~3), not just a shared onset.
|
|
\end{requirement}
|
|
|
|
\begin{requirement}
|
|
\label{req:graph:promoted-voice-visibility}
|
|
System-promoted voices are first-class graph objects: they appear
|
|
in the staff instance's \texttt{voices} list, they participate in
|
|
cross-cutting structures, and they are visible to users. They are
|
|
\emph{not} hidden or transient. The layout pipeline
|
|
(Chapter~\ref{ch:layout-ir}) \SHOULD{} provide a visual indication
|
|
that a voice was system-promoted until the user normalizes it
|
|
(via a later \texttt{NormalizePromotedVoice} or
|
|
\texttt{MergeVoices} operation, specified in the operation
|
|
catalog).
|
|
|
|
Promoted voices \MUSTNOT{} silently become user-authored voices;
|
|
doing so would convert a CRDT conflict resolution into musical
|
|
authorship without the user's consent.
|
|
\end{requirement}
|
|
|
|
\begin{requirement}
|
|
\label{req:graph:voice-event-nonoverlap}
|
|
Within a voice, events \MUST{} be sorted by position and \MUSTNOT{}
|
|
overlap in time. Concurrent material on the same staff \MUST{} be
|
|
expressed via multiple voices.
|
|
\end{requirement}
|
|
|
|
\section{Cross-Cutting Structures}
|
|
\label{sec:graph:cross}
|
|
|
|
Cross-cutting structures hold references into the tree (typically
|
|
event identifiers) and represent musical phenomena that span multiple
|
|
events. The registry is partitioned by type for both type safety
|
|
and fast indexed lookup.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct CrossCuttingRegistry {
|
|
pub slurs: Vec<Slur>,
|
|
pub ties: Vec<Tie>,
|
|
pub beams: Vec<Beam>,
|
|
pub tuplets: Vec<Tuplet>,
|
|
pub spanners: Vec<Spanner>,
|
|
pub markers: Vec<Marker>,
|
|
pub repeats: Vec<RepeatStructure>,
|
|
pub analytical: Vec<AnalyticalAnnotation>,
|
|
pub comments: Vec<Comment>,
|
|
pub graphic_gestures: Vec<GraphicGesture>,
|
|
pub lyrics: Vec<LyricLine>,
|
|
pub chord_symbols: Vec<ChordSymbol>,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsection{Slurs and Phrase Marks}
|
|
\label{sec:graph:slurs}
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct Slur {
|
|
pub id: SlurId,
|
|
|
|
/// First and last events under the slur.
|
|
pub start_event: EventId,
|
|
pub end_event: EventId,
|
|
|
|
/// Slur kind: legato slur, phrase mark, articulation slur,
|
|
/// editorial.
|
|
pub kind: SlurKind,
|
|
|
|
/// Optional curvature override. The engraver computes default
|
|
/// curvature; this field overrides it.
|
|
pub curvature_override: Option<CurvatureOverride>,
|
|
|
|
/// Visual style: solid, dashed, dotted; line thickness curve.
|
|
pub style: SpanStyle,
|
|
}
|
|
|
|
/// The class of a slur (defined schema major 2). The v1-to-v2
|
|
/// migration default is Legato.
|
|
pub enum SlurKind {
|
|
/// An ordinary legato slur.
|
|
Legato,
|
|
/// A phrase mark (typically longer, over sub-phrases).
|
|
Phrase,
|
|
/// An articulation slur (e.g., over a two-note sigh figure).
|
|
Articulation,
|
|
/// An editorial slur (rendered distinctly, e.g., dashed).
|
|
Editorial,
|
|
}
|
|
|
|
/// Which side of the notes a curve arcs toward (defined schema
|
|
/// major 2).
|
|
pub enum CurveDirection {
|
|
Above,
|
|
Below,
|
|
}
|
|
|
|
/// An authored curvature override (defined schema major 2). The
|
|
/// engraver computes default curvature; each present field overrides
|
|
/// that component of it. Consumed by the Standard engraving tier;
|
|
/// stored and preserved at every tier.
|
|
pub struct CurvatureOverride {
|
|
pub direction: Option<CurveDirection>,
|
|
/// Arc height at the apex.
|
|
pub height: Option<SpaceUnit>,
|
|
}
|
|
|
|
/// The visual style of a spanning mark (defined schema major 2): one
|
|
/// shared record for Slur.style, Tie.style, and Spanner.style.
|
|
/// Defaults: solid, engraver-chosen thickness.
|
|
pub struct SpanStyle {
|
|
pub line: LineStyle,
|
|
/// Line thickness; None = the engraver's default.
|
|
pub thickness: Option<SpaceUnit>,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsection{Ties}
|
|
\label{sec:graph:ties}
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct Tie {
|
|
pub id: TieId,
|
|
|
|
/// The two events being tied. The end event's pitch(es) must
|
|
/// match the corresponding pitches in the start event.
|
|
pub start_event: EventId,
|
|
pub end_event: EventId,
|
|
|
|
/// For chord ties: which pitches in the start event are tied
|
|
/// to which in the end event, expressed by stable PitchId pairs.
|
|
/// None means all pitches are tied by enharmonic matching in
|
|
/// pitch-id-ascending order.
|
|
pub pitch_pairing: Option<Vec<(PitchId, PitchId)>>,
|
|
|
|
/// Tie class: determines the validation profile applied to this
|
|
/// tie. The default Standard class requires same-voice immediate
|
|
/// adjacency; other classes relax the rule for editorial,
|
|
/// cross-voice, or notation-specific use.
|
|
pub class: TieClass,
|
|
|
|
/// Visual style (the shared SpanStyle record, schema major 2).
|
|
pub style: SpanStyle,
|
|
}
|
|
|
|
pub enum TieClass {
|
|
/// Standard CMN tie: same voice, immediate adjacency in voice
|
|
/// order, pitches enharmonically equivalent.
|
|
Standard,
|
|
|
|
/// Editorial tie: connects pitches the editor considers
|
|
/// continuous, spanning intervening rests or invisible events.
|
|
/// Drawn with editorial style (dashed or bracketed) by default.
|
|
Editorial,
|
|
|
|
/// Cross-voice tie: connects pitches across voice boundaries
|
|
/// within the same staff instance. Used for keyboard divisi,
|
|
/// re-voicing across hands, and similar constructions.
|
|
CrossVoice,
|
|
|
|
/// Laissez-vibrer tie: a tie with no defined end event, drawn
|
|
/// trailing from the start event. The end_event field is
|
|
/// permitted to equal the start_event for this class, with
|
|
/// pitch_pairing self-referential.
|
|
LaissezVibrer,
|
|
|
|
/// Registered class with custom validation behavior, defined
|
|
/// by a notation grammar plugin.
|
|
Registered(TieClassRegistryId),
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\begin{rationale}
|
|
Pairing by stable \texttt{PitchId} rather than by positional index
|
|
preserves tie identity through chord-reordering edits and through
|
|
CRDT-style concurrent modification of the chord's pitch list.
|
|
Index-based pairing would silently retarget when pitches were
|
|
inserted, deleted, or reordered within a chord.
|
|
|
|
The tie class permits common editorial and contemporary-notation
|
|
practices (editorial dashed ties spanning rests, cross-voice ties
|
|
in keyboard scores, laissez-vibrer ties without a notated end) to
|
|
be expressed without relaxing the structural invariants of the
|
|
standard tie.
|
|
\end{rationale}
|
|
|
|
\begin{requirement}
|
|
\label{req:graph:tie-class-validation}
|
|
Tie validation is class-specific:
|
|
|
|
\begin{itemize}
|
|
\item \texttt{TieClass::Standard}: the start and end events
|
|
\MUST{} be in the same voice; the start event \MUST{}
|
|
immediately precede the end event in that voice's event
|
|
sequence; the paired pitches \MUST{} be enharmonically
|
|
equivalent under the active tuning system.
|
|
\item \texttt{TieClass::Editorial}: the start and end events
|
|
\MUST{} be in the same voice and the start \MUST{} precede the
|
|
end in voice order, but intervening events are permitted.
|
|
Paired pitches \MUST{} be enharmonically equivalent.
|
|
\item \texttt{TieClass::CrossVoice}: the start and end events
|
|
\MAY{} be in different voices on the same staff instance. The
|
|
start's resolved position \MUST{} be less than or equal to
|
|
the end's. Paired pitches \MUST{} be enharmonically equivalent.
|
|
\item \texttt{TieClass::LaissezVibrer}: the end event \MAY{}
|
|
equal the start event, in which case the tie has no notated
|
|
destination. No adjacency requirement applies.
|
|
\item \texttt{TieClass::Registered}: validation is defined by
|
|
the registered class's contract; the registry \MUST{} specify
|
|
adjacency and pitch-equivalence requirements for the class.
|
|
\end{itemize}
|
|
\end{requirement}
|
|
|
|
\subsection{Beams}
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct Beam {
|
|
pub id: BeamId,
|
|
pub events: Vec<EventId>,
|
|
pub level: u8, // primary beam level
|
|
|
|
/// Sub-beam structure for inner subdivisions. Each entry
|
|
/// describes a beam segment at a deeper level.
|
|
pub sub_beams: Vec<SubBeam>,
|
|
|
|
/// Beam angle and stem-length overrides.
|
|
pub geometry_override: Option<BeamGeometryOverride>,
|
|
}
|
|
|
|
/// A beam segment at a deeper subdivision level (defined schema
|
|
/// major 2): a contiguous subset of the owning beam's events beamed
|
|
/// together at `level` (strictly deeper than the owning beam's
|
|
/// primary level).
|
|
pub struct SubBeam {
|
|
pub level: u8,
|
|
pub events: Vec<EventId>,
|
|
}
|
|
|
|
/// An authored beam-geometry override (defined schema major 2). Each
|
|
/// present field overrides the engraver's computed geometry. Consumed
|
|
/// by the Standard engraving tier; stored and preserved at every tier.
|
|
pub struct BeamGeometryOverride {
|
|
/// Beam slope: staff spaces of rise per staff space of run
|
|
/// (dimensionless, hence not a SpaceUnit).
|
|
pub slope: Option<CanonicalF64>,
|
|
/// Vertical displacement of the beam from its default placement
|
|
/// (positive = up).
|
|
pub offset: Option<SpaceUnit>,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsection{Spanners}
|
|
|
|
A spanner is a generic spanning mark: hairpins, octave lines, pedal
|
|
lines, trill extensions, and similar.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct Spanner {
|
|
pub id: SpannerId,
|
|
pub kind: SpannerKind,
|
|
pub start: TimeAnchor,
|
|
pub end: TimeAnchor,
|
|
|
|
/// Which staff or staves this spanner attaches to. Most spanners
|
|
/// target a single staff; some (system-level dynamics, pedal
|
|
/// lines spanning grand staff) target multiple.
|
|
pub staves: Vec<StaffId>,
|
|
|
|
/// Visual style (the shared SpanStyle record, schema major 2).
|
|
pub style: SpanStyle,
|
|
}
|
|
|
|
pub enum SpannerKind {
|
|
/// An unclassified spanning mark: renders as a plain line or
|
|
/// bracket. First variant deliberately (schema major 2): it is
|
|
/// the v1-to-v2 migration default -- a v1 spanner carried no
|
|
/// kind, and Generic is the honest translation of that absence.
|
|
Generic,
|
|
Hairpin(HairpinDirection),
|
|
OctaveLine(OctaveOffset),
|
|
PedalLine(PedalKind),
|
|
TrillExtension,
|
|
Glissando,
|
|
Portamento,
|
|
TextLine(TextLineDefinition),
|
|
Bracket(BracketKind),
|
|
}
|
|
|
|
/// Hairpin orientation (defined schema major 2).
|
|
pub enum HairpinDirection {
|
|
Crescendo,
|
|
Diminuendo,
|
|
}
|
|
|
|
/// Octave-line displacement in signed octaves (defined schema
|
|
/// major 2): +1 = 8va, -1 = 8vb, +2 = 15ma, -2 = 15mb. Zero is
|
|
/// representable but degenerate; the authoring advisory layer flags
|
|
/// it, reduction does not.
|
|
pub struct OctaveOffset(pub i8);
|
|
|
|
/// Pedal-line kind (defined schema major 2).
|
|
pub enum PedalKind {
|
|
Sustain,
|
|
Sostenuto,
|
|
UnaCorda,
|
|
}
|
|
|
|
/// A text line's content (defined schema major 2); the dash pattern
|
|
/// comes from the spanner's style.
|
|
pub struct TextLineDefinition {
|
|
pub text: String,
|
|
}
|
|
|
|
/// A bracket spanner's shape (defined schema major 2). Growth is by
|
|
/// appended variant.
|
|
pub enum BracketKind {
|
|
Square,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsection{Markers}
|
|
|
|
A marker is a point annotation: rehearsal marks, section labels,
|
|
tempo text, fermatas (which technically attach to events but may
|
|
also be region-level), and similar.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct Marker {
|
|
pub id: MarkerId,
|
|
pub anchor: TimeAnchor,
|
|
pub kind: MarkerKind,
|
|
pub display: MarkerDisplay,
|
|
}
|
|
|
|
pub enum MarkerKind {
|
|
Rehearsal(RehearsalMark),
|
|
Section(SectionLabel),
|
|
Tempo(TempoMarking),
|
|
Coda,
|
|
Segno,
|
|
DalSegno,
|
|
DaCapo,
|
|
Fine,
|
|
Custom(CustomMarkerId),
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsection{Repeat Structures}
|
|
\label{sec:graph:repeats}
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct RepeatStructure {
|
|
pub id: RepeatStructureId,
|
|
pub kind: RepeatKind,
|
|
pub start: TimeAnchor,
|
|
pub end: TimeAnchor,
|
|
|
|
/// For voltas: which endings apply on which passes.
|
|
pub voltas: Vec<Volta>,
|
|
}
|
|
|
|
pub enum RepeatKind {
|
|
SimpleRepeat { count: u32 },
|
|
DaCapo { end_target: TimeAnchor },
|
|
DalSegno { segno: TimeAnchor, end_target: TimeAnchor },
|
|
Volta,
|
|
}
|
|
|
|
/// One volta bracket (defined schema major 2): the passes it applies
|
|
/// on and the time span it covers. A RepeatStructure of kind Volta
|
|
/// carries one entry per ending (e.g., a first ending Volta with
|
|
/// endings = [1] and a second with endings = [2]).
|
|
pub struct Volta {
|
|
/// The pass numbers this ending plays on (1-based), ascending.
|
|
pub endings: Vec<u32>,
|
|
pub start: TimeAnchor,
|
|
pub end: TimeAnchor,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
The \texttt{endings} constraints (non-empty, 1-based, strictly
|
|
ascending) are \emph{advisory}, on the \texttt{OctaveOffset} pattern:
|
|
a violating value is representable, decoders and reduction accept it,
|
|
and the authoring validation layer flags it. Rendering treats an
|
|
unlistable ending set as a plain bracket.
|
|
|
|
The v1-to-v2 migration default for a kindless v1 repeat structure is
|
|
\texttt{SimpleRepeat} with \texttt{count}~$=$~2: a v1
|
|
\texttt{RepeatStructure} \emph{meant} a repeat, and playing the span
|
|
twice is the conventional semantics of an unadorned repeat sign.
|
|
Voltas default empty.
|
|
|
|
\subsection{Analytical Annotations}
|
|
|
|
Analytical annotations record analyses of the music: Roman numerals,
|
|
form labels, motivic brackets, set-theory analyses, Schenkerian
|
|
graphs. They are first-class and may appear on dedicated analysis
|
|
layers.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct AnalyticalAnnotation {
|
|
pub id: AnalyticalAnnotationId,
|
|
pub kind: AnalyticalKind,
|
|
pub anchor: AnnotationAnchor,
|
|
|
|
/// Optional analysis layer. None means the engraved layer;
|
|
/// Some(id) targets a specific analysis layer.
|
|
pub layer: Option<AnalysisLayerId>,
|
|
|
|
pub content: AnalyticalContent,
|
|
}
|
|
|
|
pub enum AnalyticalKind {
|
|
RomanNumeral,
|
|
FunctionalLabel,
|
|
SetTheoryLabel,
|
|
SchenkerianGraph,
|
|
FormSection,
|
|
MotivicBracket,
|
|
Custom(CustomAnalyticalKindId),
|
|
}
|
|
|
|
pub enum AnnotationAnchor {
|
|
Event(EventId),
|
|
Range { start: TimeAnchor, end: TimeAnchor },
|
|
Region(RegionId),
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsection{Comments}
|
|
|
|
Comments are review-mode annotations: discussion threads anchored to
|
|
points or ranges in the score.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct Comment {
|
|
pub id: CommentId,
|
|
pub anchor: AnnotationAnchor,
|
|
pub thread: Vec<CommentMessage>,
|
|
pub resolved: bool,
|
|
}
|
|
|
|
pub struct CommentMessage {
|
|
pub author: AuthorId,
|
|
pub timestamp: Timestamp,
|
|
pub body: String,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsection{Graphic Gestures}
|
|
|
|
A graphic gesture is a drawn or constructed graphic element that
|
|
spans events, staves, or regions and is not contained within a single
|
|
graphic region. Used for performance-direction sweeps,
|
|
multi-staff curved gestures, and similar.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct GraphicGesture {
|
|
pub id: GraphicGestureId,
|
|
|
|
/// The graphic objects composing this gesture. Stored in the
|
|
/// canvas's graphic-object storage.
|
|
pub objects: Vec<GraphicObjectId>,
|
|
|
|
/// Anchoring: how this gesture is positioned relative to score
|
|
/// content.
|
|
pub anchoring: GestureAnchoring,
|
|
|
|
/// Optional playback parameter bindings.
|
|
pub playback_bindings: Vec<PlaybackBinding>,
|
|
}
|
|
|
|
pub enum GestureAnchoring {
|
|
/// Anchored to events: the gesture moves with them.
|
|
Events(Vec<EventId>),
|
|
|
|
/// Anchored to a time and staff range.
|
|
Range {
|
|
start: TimeAnchor,
|
|
end: TimeAnchor,
|
|
staves: Vec<StaffId>,
|
|
},
|
|
|
|
/// Free canvas coordinates: does not follow score edits.
|
|
Free(CanvasCoordinates),
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\section{Graphic Content}
|
|
\label{sec:graph:graphic}
|
|
|
|
Free-graphic and hybrid regions carry graphic content: vector
|
|
primitives, drawn strokes, text, and images. Graphic content is
|
|
expressed in the region's local coordinate space.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct GraphicContent {
|
|
pub objects: Vec<GraphicObject>,
|
|
|
|
/// The region's local coordinate system. Region transforms
|
|
/// map this to canvas coordinates.
|
|
pub coordinate_system: LocalCoordinateSystem,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsection{Graphic Objects}
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct GraphicObject {
|
|
pub id: GraphicObjectId,
|
|
|
|
/// The object's geometric kind.
|
|
pub kind: GraphicObjectKind,
|
|
|
|
/// Transform applied within the region's coordinate space.
|
|
pub transform: Transform2D,
|
|
|
|
/// Layer ordering: higher values draw on top of lower.
|
|
pub layer: i32,
|
|
|
|
/// Visual style: stroke color, fill, line weight, opacity.
|
|
pub style: GraphicStyle,
|
|
|
|
/// Optional time binding. See Section~\ref{sec:graph:timebinding}.
|
|
pub time_binding: Option<TimeBinding>,
|
|
}
|
|
|
|
pub enum GraphicObjectKind {
|
|
Path(PathData),
|
|
Shape(ShapePrimitive),
|
|
Text(TextData),
|
|
Image(ImageData),
|
|
Group(Vec<GraphicObjectId>),
|
|
Stroke(StrokeData),
|
|
}
|
|
|
|
pub enum ShapePrimitive {
|
|
Line { start: Point2D, end: Point2D },
|
|
Rectangle { origin: Point2D, size: Size2D },
|
|
Ellipse { center: Point2D, radii: Size2D },
|
|
Polygon(Vec<Point2D>),
|
|
Bezier(Vec<BezierSegment>),
|
|
}
|
|
|
|
pub struct StrokeData {
|
|
/// Pressure-sensitive stylus stroke samples: position, pressure,
|
|
/// tilt, and time per sample.
|
|
pub samples: Vec<StrokeSample>,
|
|
}
|
|
|
|
pub struct StrokeSample {
|
|
pub position: Point2D,
|
|
pub pressure: f32, // 0.0..=1.0
|
|
pub tilt_xy: (f32, f32), // radians
|
|
pub timestamp: WallClockTime,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsection{Coordinates}
|
|
|
|
\begin{requirement}
|
|
\label{req:graph:region-local-coordinates}
|
|
Graphic objects \MUST{} be stored in region-local coordinates. The
|
|
region's transform maps local coordinates to canvas coordinates.
|
|
This permits regions to be moved, resized, or otherwise transformed
|
|
without rewriting the coordinates of every contained object.
|
|
\end{requirement}
|
|
|
|
\subsection{Time Bindings}
|
|
\label{sec:graph:timebinding}
|
|
|
|
A graphic object \MAY{} carry an optional time binding, which assigns
|
|
a temporal meaning to the object beyond its visual appearance. Time
|
|
bindings are the mechanism by which a drawn curve becomes a performance
|
|
instruction (a filter sweep, an amplitude envelope, a density
|
|
trajectory).
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct TimeBinding {
|
|
/// What musical or wall-clock range this graphic spans.
|
|
pub time_extent: TimeExtent,
|
|
|
|
/// What parameter, if any, this graphic drives. None means the
|
|
/// graphic is decorative; Some specifies a named parameter.
|
|
pub parameter: Option<ParameterBinding>,
|
|
|
|
/// How the graphic's geometry maps to parameter values.
|
|
pub mapping: ParameterMapping,
|
|
}
|
|
|
|
pub struct ParameterBinding {
|
|
pub target: ParameterTarget,
|
|
pub parameter_name: String,
|
|
pub value_range: ParameterRange,
|
|
}
|
|
|
|
pub enum ParameterTarget {
|
|
/// A specific event's parameter (e.g., this event's velocity).
|
|
Event(EventId),
|
|
|
|
/// A voice-level parameter (e.g., voice expression CC).
|
|
Voice(VoiceId),
|
|
|
|
/// A staff-level parameter.
|
|
Staff(StaffId),
|
|
|
|
/// A region-level parameter.
|
|
Region(RegionId),
|
|
|
|
/// A score-global parameter (e.g., master gain).
|
|
Score,
|
|
}
|
|
|
|
pub enum ParameterMapping {
|
|
/// Vertical position in region coordinates maps linearly to
|
|
/// parameter value.
|
|
VerticalLinear,
|
|
|
|
/// Vertical position with exponential mapping.
|
|
VerticalExponential,
|
|
|
|
/// Stroke pressure maps to parameter value.
|
|
StrokePressure,
|
|
|
|
/// Stroke velocity maps to parameter value.
|
|
StrokeVelocity,
|
|
|
|
/// Custom mapping defined by a plugin.
|
|
Registered(MappingRegistryId),
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\begin{rationale}
|
|
Time bindings make graphic-notation playback expressible in the core
|
|
data model without committing to specific audio engine semantics.
|
|
The core stores what parameter a graphic drives and how its
|
|
geometry maps to values; the audio engine spec consumes this
|
|
binding and applies it. Decorative graphics (binding is \texttt{None})
|
|
remain pure visual content.
|
|
\end{rationale}
|
|
|
|
\section{Parts}
|
|
\label{sec:graph:parts}
|
|
|
|
A part is a per-instrument or per-section view onto the score,
|
|
intended for printed extraction or focused performer use. Parts live
|
|
separately from the canvas; they are projections defined by a
|
|
part-extraction specification.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct PartDefinition {
|
|
pub id: PartDefinitionId,
|
|
|
|
/// Human-readable name (e.g., "Flute 1", "Violins II").
|
|
pub name: String,
|
|
|
|
/// Which staves are included in this part, in order from top
|
|
/// to bottom.
|
|
pub staves: Vec<StaffId>,
|
|
|
|
/// Part-specific layout overrides: page size, system count,
|
|
/// cue insertions, multirest consolidation, page-turn placement.
|
|
pub layout_overrides: PartLayoutOverrides,
|
|
|
|
/// Per-event visibility overrides specific to this part (e.g.,
|
|
/// hide a cue in the full score but show it in the part).
|
|
pub visibility_overrides: Vec<VisibilityOverride>,
|
|
|
|
/// Auto-cue insertions: source staves to draw cues from when
|
|
/// the part has long rests.
|
|
pub auto_cue_sources: Vec<StaffId>,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsection{Parts Are Projections, Not Storage}
|
|
|
|
\begin{requirement}
|
|
\label{req:graph:part-content-projection}
|
|
A part \MUSTNOT{} store musical content directly; it \MUST{} consist
|
|
only of references to score content and overrides. Edits to score
|
|
content \MUST{} propagate to all parts that reference that content.
|
|
\end{requirement}
|
|
|
|
\section{Analysis Layers and Views}
|
|
\label{sec:graph:layers}
|
|
|
|
\subsection{Analysis Layers}
|
|
|
|
Analysis layers, introduced for spelling in Section~\ref{sec:pitch:layers}
|
|
and used by analytical annotations, are first-class objects on the
|
|
score.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct AnalysisLayer {
|
|
pub id: AnalysisLayerId,
|
|
pub name: String,
|
|
pub description: Option<String>,
|
|
|
|
/// Which views display this layer.
|
|
pub visible_in_views: Vec<ViewId>,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsection{Views}
|
|
|
|
A view is a recipe for displaying a configuration of the score:
|
|
which analysis layers are active, which parts are rendered, what
|
|
overrides apply. Views are how the system supports condensed scores,
|
|
analytical views, study scores, and lead-sheet projections.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct ViewDefinition {
|
|
pub id: ViewId,
|
|
pub name: String,
|
|
pub kind: ViewKind,
|
|
|
|
/// Which analysis layers are active in this view.
|
|
pub active_layers: Vec<AnalysisLayerId>,
|
|
|
|
/// View-specific overrides.
|
|
pub overrides: ViewOverrides,
|
|
}
|
|
|
|
pub enum ViewKind {
|
|
FullScore,
|
|
Part(PartDefinitionId),
|
|
CondensedScore(CondensationSpec),
|
|
LeadSheet(LeadSheetSpec),
|
|
Analysis(AnalysisViewSpec),
|
|
Custom(CustomViewKindId),
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\section{Graph Invariants}
|
|
\label{sec:graph:invariants}
|
|
|
|
The score graph maintains a set of structural invariants. Implementations
|
|
\MUST{} preserve these invariants across all edits.
|
|
|
|
\begin{requirement}
|
|
\label{req:graph:score-graph-invariants}
|
|
The following invariants hold over every well-formed score graph:
|
|
|
|
\begin{enumerate}
|
|
\item Every event in the arena has a valid \texttt{voice} field
|
|
pointing to a voice that lists the event in its \texttt{events}
|
|
vector.
|
|
\item Every event in a voice's events list has its \texttt{voice}
|
|
field pointing back to that voice.
|
|
\item Events within a voice are sorted by position and do not
|
|
overlap in time.
|
|
\item Every event's \texttt{EventPosition} and
|
|
\texttt{EventDuration} variants agree with the time model of
|
|
its enclosing region (Section~\ref{sec:graph:event-time}). For
|
|
aleatoric regions, agreement is governed by the region's
|
|
declared \texttt{AleatoricAnchoringDiscipline}.
|
|
\item Every voice belongs to exactly one \texttt{StaffInstance};
|
|
every \texttt{StaffInstance} belongs to exactly one region;
|
|
every region belongs to the canvas.
|
|
\item Every \texttt{StaffInstance.staff} resolves to a
|
|
\texttt{Staff} declared at the score level. A single
|
|
\texttt{StaffId} \MAY{} be referenced by multiple
|
|
\texttt{StaffInstance}s in different regions (this is how
|
|
continuous staff identity is expressed across regions); a
|
|
\texttt{StaffId} \MUSTNOT{} be referenced by two
|
|
\texttt{StaffInstance}s in the same region.
|
|
\item Region time and staff extents do not simultaneously overlap
|
|
(Section~\ref{sec:graph:canvas}). A region's
|
|
\texttt{staff\_extent} lists exactly the \texttt{StaffId}s
|
|
manifested by the region's \texttt{staff\_instances}, with no
|
|
duplicates.
|
|
\item Measures belong to exactly one \texttt{StaffInstance}. A
|
|
region's metric organization is per-staff (admitting
|
|
polymeter); barline alignment across staves is recorded via
|
|
\texttt{BarlineAlignmentGroup} entries when explicit alignment
|
|
is authoritative.
|
|
\item Every \texttt{TimeAnchor}'s \texttt{AnchorOffset} variant
|
|
agrees with the time model of the target object's enclosing
|
|
region (Section~\ref{sec:time:anchors}).
|
|
\item Every cross-cutting structure's references resolve to extant
|
|
objects in the graph, except where explicit re-anchoring rules
|
|
permit transient dangling states during edits (see
|
|
Chapter~\ref{ch:semops}).
|
|
\item Every identifier in the graph is unique within its kind.
|
|
Identifiers reserved for system-derived objects (notably
|
|
\texttt{VoiceId}s of system-promoted voices) \MUST{} be derived
|
|
from the deterministic function specified for their kind and
|
|
\MUSTNOT{} collide with user-authored identifiers.
|
|
\item Every \texttt{IdentifiedPitch} in the arena has a
|
|
\texttt{PitchId} that is unique within the score's
|
|
pitch-identity index.
|
|
\item Every \texttt{SpellingScope::Pitch(PitchId)} resolves to a
|
|
live or tombstoned \texttt{PitchId} in the pitch-identity
|
|
index. A live target resolves to exactly one
|
|
\texttt{IdentifiedPitch}; a tombstoned target is preserved for
|
|
operation-log replay, undo, conflict reporting, and historical
|
|
annotation, and \MUSTNOT{} be silently re-matched to a
|
|
different live pitch.
|
|
\item Every notational decomposition attachment's target
|
|
\texttt{EventId} resolves to a live or tombstoned event;
|
|
attachments to tombstoned events follow the same preservation
|
|
discipline as for tombstoned pitches.
|
|
\item For live decomposition attachments, the targeted event's
|
|
duration matches the decomposition's component sum.
|
|
\item Every tuplet's member event durations sum to the tuplet's
|
|
structurally-required total
|
|
(Section~\ref{sec:time:tuplets}). Operations that would break
|
|
this invariant \MUST{} include compensating changes
|
|
(Section~\ref{sec:semops:deleteevent}).
|
|
\item Every \texttt{Tie}'s \texttt{pitch\_pairing} entries
|
|
reference \texttt{PitchId}s that resolve to identified pitches
|
|
belonging to the start and end events respectively. The
|
|
class-specific adjacency, voice-membership, and
|
|
pitch-equivalence rules of Section~\ref{sec:graph:ties} hold
|
|
for the tie's declared \texttt{TieClass}.
|
|
\item Every \texttt{Voice} declares an \texttt{origin} consistent
|
|
with how it was created: \texttt{UserDeclared} for explicit
|
|
user actions, \texttt{Imported} for foreign-format imports,
|
|
\texttt{SystemPromoted} for voices allocated by concurrent-edit
|
|
conflict resolution. The \texttt{VoiceId} of a
|
|
\texttt{SystemPromoted} voice \MUST{} be the deterministic
|
|
derivation specified in
|
|
Section~\ref{sec:graph:promoted-voices}.
|
|
\item Every \texttt{BarlineAlignmentGroup}'s members reference
|
|
\texttt{StaffInstance}s within the same region and
|
|
\texttt{Measure}s belonging to those instances. Group members
|
|
\MUSTNOT{} reference staff instances or measures across
|
|
region boundaries.
|
|
\end{enumerate}
|
|
|
|
Implementations \MUST{} reject graph configurations that violate any
|
|
of the above invariants. Edits proposed in Chapter~\ref{ch:semops}
|
|
must either preserve invariants directly or include compensating
|
|
changes that restore them within the same operation.
|
|
\end{requirement}
|
|
|
|
This enumeration contains exactly \textbf{19} invariants. (Earlier
|
|
summary material, including the QUICKSTART, referred to ``18 graph
|
|
invariants''; the authoritative count is 19, matching this
|
|
enumeration and the reference implementation.) These 19 are
|
|
\emph{runtime} invariants: they hold over every well-formed graph and
|
|
are restored by compensating changes when an edit would break them.
|
|
|
|
Distinct from them are three \emph{construction-time} rejections
|
|
defined in Chapter~\ref{ch:time}, which a conforming constructor
|
|
\MUST{} enforce before a value ever enters the graph, and which no
|
|
runtime invariant restores because the offending value is never a
|
|
representable graph state:
|
|
|
|
\begin{enumerate}
|
|
\item A \texttt{TimeSignature}'s beat groups \MUST{} sum to the
|
|
measure duration (Section~\ref{sec:time:meter}).
|
|
\item An \texttt{EventOrderingDAG} \MUST{} be acyclic
|
|
(Requirement~\ref{req:time:ordering-dag-acyclic}).
|
|
\item A \texttt{TupletRatio} \MUSTNOT{} be degenerate
|
|
(Section~\ref{sec:time:tuplets},
|
|
Requirement~\ref{req:time:tuplet-ratio-construction}).
|
|
\end{enumerate}
|
|
|
|
\section{Indexes and Auxiliary Structures}
|
|
\label{sec:graph:indexes}
|
|
|
|
Performance-critical operations require indexed access. The
|
|
specification states the required indexes; implementations \MAY{}
|
|
construct additional indexes provided they are kept consistent.
|
|
|
|
\begin{requirement}
|
|
\label{req:graph:required-indexes}
|
|
Implementations \MUST{} maintain at least the following indexes:
|
|
|
|
\begin{description}
|
|
\item[Event time index.] Maps (region, voice) to a sorted view
|
|
of event positions, supporting $O(\log n)$ time-range queries.
|
|
\item[Cross-cutting reference index.] Maps each object identifier
|
|
to the cross-cutting structures referencing it, supporting
|
|
$O(1)$ amortized lookup for re-anchoring.
|
|
\item[Measure index.] Maps measure number (and measure id) to
|
|
\texttt{MeasureId} for fast navigation.
|
|
\item[Spelling attachment index.] Maps each \texttt{PitchId} to
|
|
its attachments, partitioned by analysis layer.
|
|
\end{description}
|
|
|
|
Indexes \MUST{} be invalidated and rebuilt (or incrementally
|
|
updated) on the corresponding edits. The semantic operations
|
|
chapter specifies which operations invalidate which indexes.
|
|
\end{requirement}
|
|
|
|
\section{Forward References}
|
|
|
|
\begin{itemize}
|
|
\item The complete catalog of semantic operations, their
|
|
preconditions, postconditions, and re-anchoring rules is in
|
|
Chapter~\ref{ch:semops}.
|
|
\item The mapping from score graph to layout intermediate
|
|
representation is defined in Chapter~\ref{ch:layout-ir}.
|
|
\item The on-disk serialization of every type introduced in this
|
|
chapter is defined in Chapter~\ref{ch:format}.
|
|
\item Some engraving-specific types (\texttt{StemConfiguration},
|
|
\texttt{ArticulationMark}, and similar) are introduced informally
|
|
here and fully defined in Chapter~\ref{ch:layout-ir} where their
|
|
interaction with the engraver is specified. The clef/key content
|
|
model (\texttt{Clef}, \texttt{ClefShape}, \texttt{KeySignature},
|
|
\texttt{ClefChange}, \texttt{KeySignatureChange}) and
|
|
\texttt{LineStyle} are defined normatively \emph{in this chapter}
|
|
as of schema major~2; the former \texttt{ClefId} identifier sketch
|
|
is retired in favour of embedded \texttt{Clef} values.
|
|
\end{itemize}
|
|
|
|
% ===========================================================================
|
|
\chapter{Semantic Operations and Concurrent Reduction}
|
|
\label{ch:semops}
|
|
|
|
This chapter specifies the semantic operations on the score graph: the
|
|
mutations through which the data model becomes a live model. It defines
|
|
the operation framework, the canonical reduction procedure by which a
|
|
set of operations becomes a materialized score state, the object
|
|
existence model with tombstones, the conflict-record machinery, the
|
|
re-anchoring rules, transactions, and undo. The chapter ends with
|
|
representative operation specifications.
|
|
|
|
The collaboration mechanics specified here are the foundation on which
|
|
Chapter~\ref{ch:format}'s synchronization protocols build. The
|
|
materialized score is the deterministic reduction of an operation set;
|
|
the file format stores that operation set together with optional
|
|
acceleration snapshots.
|
|
|
|
\section{Design Principles}
|
|
\label{sec:semops:principles}
|
|
|
|
\begin{description}
|
|
\item[The operation set is canonical.] A score's state is defined
|
|
by the set of operations that have been committed to it. Any
|
|
materialized graph is a deterministic reduction of that set;
|
|
caches, snapshots, and partial reductions are acceleration
|
|
structures, never the source of truth.
|
|
\item[The replicated operation set is a CRDT; the materialized
|
|
graph is not.] The grow-only set of operation envelopes with
|
|
compact causal metadata is a true CRDT: replicas independently
|
|
accumulate envelopes and converge on the same set. The
|
|
materialized score graph is not itself claimed to be a CRDT; it
|
|
is the deterministic reduction of that set, defined by the
|
|
reduction algorithm specified here.
|
|
\item[Pairwise operation merge is rejected.] Musical operations
|
|
do not, in general, commute in intention: transpose-then-respell
|
|
differs musically from respell-then-transpose; delete-then-tie
|
|
differs from tie-then-delete. The framework therefore does not
|
|
attempt to make every pair of operations algebraically commute.
|
|
Instead, operations are reduced in a canonical order, and that
|
|
order determines the outcome.
|
|
\item[Conflicts, no-ops, and rejections are replicated facts.] No
|
|
operation silently disappears because it could not apply. Every
|
|
operation has a deterministic \texttt{OperationEffect} recorded
|
|
as part of the materialized state; every replica reducing the
|
|
same operation set records the same effects with the same
|
|
reasons.
|
|
\item[Identifier-based targeting.] Operations target objects by
|
|
identifier, never by structural path. Identifiers are stable
|
|
under concurrent edits; paths are not.
|
|
\item[Tombstones preserve identity.] Deleted objects retain their
|
|
identifiers as tombstones. References to tombstoned identifiers
|
|
are not silently invalidated; they enter a tombstoned-target
|
|
state unless the operation kind specifies deterministic repair.
|
|
\item[Transactions materialize atomically.] Primitive members of a
|
|
transaction \MUSTNOT{} be independently visible as committed
|
|
score states. Either the whole transaction reduces successfully,
|
|
or the transaction transitions to a conflict effect with a
|
|
recorded conflict.
|
|
\item[Undo is forward, not backward.] Undo is a new operation that
|
|
computes a compensating edit against the current materialized
|
|
state; it is not literal time travel. History remains
|
|
append-only.
|
|
\item[Two validation modes for advisory checking.] Operations are
|
|
subject to strict precondition checking in \emph{authoring mode}
|
|
(interactive edits) and lenient checking in \emph{replay mode}
|
|
(loading historical operations or applying remote operations).
|
|
Validation modes apply only to advisory preconditions;
|
|
invariant preconditions are enforced unconditionally.
|
|
\end{description}
|
|
|
|
\section{The Operation Framework}
|
|
\label{sec:semops:framework}
|
|
|
|
\subsection{Operation Identity and Stamps}
|
|
|
|
Operations carry two related but distinct concepts: \emph{identity}
|
|
(who authored the operation, with what counter) and \emph{stamp}
|
|
(when the operation was committed, for ordering purposes).
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
/// Stable identity of an operation. Determined at authoring time.
|
|
pub struct OperationId {
|
|
pub replica: ReplicaId,
|
|
pub counter: u64,
|
|
}
|
|
|
|
/// Ordering metadata for an operation. Used to derive the canonical
|
|
/// reduction order; not part of the operation's identity.
|
|
pub struct OperationStamp {
|
|
pub hlc: HybridLogicalClock,
|
|
pub id: OperationId,
|
|
}
|
|
|
|
/// A hybrid logical clock combines wall-clock and logical components.
|
|
pub struct HybridLogicalClock {
|
|
/// Physical time component, in canonical units (see Chapter 8).
|
|
pub physical_time: WallClockTime,
|
|
/// Logical counter, advanced when physical time does not.
|
|
pub logical_counter: u32,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\begin{requirement}
|
|
\label{req:semops:operation-stamp-stability}
|
|
\texttt{OperationId} \MUST{} be stable: an operation's identity is
|
|
fixed at the moment of authoring and \MUSTNOT{} change with
|
|
reordering, retransmission, or merging into operation sets at
|
|
other replicas.
|
|
|
|
\texttt{OperationStamp} \MUST{} be set by the authoring replica
|
|
using a hybrid logical clock that respects causal order: for any
|
|
operation $A$ that is in operation $B$'s causal predecessor set,
|
|
$A$'s stamp \MUST{} be strictly less than $B$'s under the
|
|
canonical comparison (Section~\ref{sec:semops:reduction-order}).
|
|
\end{requirement}
|
|
|
|
\subsection{Causal Context via Dotted Version Vectors}
|
|
|
|
Operations carry a compact causal context, not an exhaustive
|
|
predecessor list. This is essential: exhaustive predecessor lists
|
|
scale linearly with history and become untenable.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
/// A compact causal context: for each replica known to the authoring
|
|
/// replica, the highest contiguous counter observed, plus any
|
|
/// individual operation identifiers known but not contiguous (the
|
|
/// "dots").
|
|
pub struct CausalContext {
|
|
/// Contiguous counter highest known per replica.
|
|
pub vector: BTreeMap<ReplicaId, u64>,
|
|
|
|
/// Individual operations known but not yet contiguous in the
|
|
/// vector. Used when an authoring replica has observed an
|
|
/// operation whose causal predecessors include operations not
|
|
/// yet present.
|
|
pub dots: BTreeSet<OperationId>,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\begin{requirement}
|
|
\label{req:semops:dotted-version-vectors}
|
|
Causal contexts \MUST{} be expressed as dotted version vectors as
|
|
defined above. Exhaustive predecessor lists \MUSTNOT{} be used as
|
|
the canonical causal representation; they may appear in debugging
|
|
output or in the operation log's expanded form, but the wire and
|
|
storage representation of causal context is the DVV.
|
|
|
|
The per-replica counter floor is \textbf{zero-based} and
|
|
normative: \texttt{vector[r] == n} asserts that \emph{every}
|
|
operation \texttt{(r,\,0..=n)} is a causal predecessor --- the
|
|
contiguous range begins at counter \texttt{0}, not \texttt{1}. A
|
|
conforming implementation \MUSTNOT{} adopt a one-based floor: doing
|
|
so would shift the contiguous/dot boundary and make two
|
|
implementations disagree about which operations are pending versus
|
|
ready. The membership check (``is operation \texttt{(r,\,c)}
|
|
covered?'') is \texttt{c <= vector[r]} or \texttt{(r,\,c) in dots};
|
|
an implementation \MAY{} evaluate it by walking the known operation
|
|
ids rather than materializing the full \texttt{0..=n} range, so a
|
|
sparse high-counter context does not force work proportional to the
|
|
counter value.
|
|
|
|
Interval tree clocks and other compact causal representations
|
|
\MAY{} appear in future revisions as performance optimizations but
|
|
are non-normative in this specification.
|
|
\end{requirement}
|
|
|
|
\begin{rationale}
|
|
Dotted version vectors are well-understood, well-tested, and the
|
|
right complexity tradeoff for the human-scale collaboration model
|
|
Epiphany targets. Their normative use here closes the high-priority
|
|
review item about exhaustive predecessor lists scaling badly with
|
|
history.
|
|
\end{rationale}
|
|
|
|
\subsection{Operation Envelopes}
|
|
|
|
An operation is transmitted, stored, and reduced as an
|
|
\emph{envelope}: the operation's payload together with its identity,
|
|
ordering stamp, causal context, and optional transaction grouping.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct OperationEnvelope {
|
|
/// Stable identifier of this operation.
|
|
pub id: OperationId,
|
|
|
|
/// Author of this operation (may differ from replica in shared
|
|
/// authoring sessions where multiple authors share a replica).
|
|
pub author: AuthorId,
|
|
|
|
/// Ordering stamp. Used for canonical reduction order; never
|
|
/// for identity.
|
|
pub stamp: OperationStamp,
|
|
|
|
/// Compact causal context: what operations were visible to the
|
|
/// authoring replica when this operation was created.
|
|
pub causal_context: CausalContext,
|
|
|
|
/// Optional transaction grouping. Multiple operations sharing a
|
|
/// TransactionId are reduced atomically.
|
|
pub transaction: Option<TransactionId>,
|
|
|
|
/// The mutation itself.
|
|
pub payload: OperationPayload,
|
|
}
|
|
|
|
pub enum OperationPayload {
|
|
Primitive(OperationKind),
|
|
|
|
/// A meta-operation that explicitly resolves a previously-recorded
|
|
/// conflict (see Section~\ref{sec:semops:conflicts}).
|
|
ResolveConflict(ResolveConflictPayload),
|
|
|
|
/// A meta-operation that compensates for a previously-committed
|
|
/// transaction; the realization of "undo" (see
|
|
/// Section~\ref{sec:semops:undo}).
|
|
UndoTransaction(UndoTransactionPayload),
|
|
|
|
/// A meta-operation that resolves an equivocated operation slot
|
|
/// by naming the chosen candidate envelope (see
|
|
/// Section~\ref{sec:semops:equivocation}; payload schema in the
|
|
/// Operation Catalog companion).
|
|
ResolveEquivocation(ResolveEquivocationPayload),
|
|
}
|
|
|
|
/// The catalog of primitive operation kinds. The variants listed
|
|
/// here are normative for the core; additional kinds are introduced
|
|
/// by registered extensions via the Registered variant. The full
|
|
/// catalog including detailed payload schemas is the Operation
|
|
/// Catalog companion specification
|
|
/// (Appendix~\ref{app:deferred}).
|
|
pub enum OperationKind {
|
|
// Event operations
|
|
InsertEvent(InsertEventOp),
|
|
DeleteEvent(DeleteEventOp),
|
|
ModifyEvent(ModifyEventOp),
|
|
|
|
// Pitch and tuning operations
|
|
RespellPitch(RespellPitchOp),
|
|
Transpose(TransposeOp),
|
|
InsertIdentifiedPitch(InsertIdentifiedPitchOp),
|
|
DeleteIdentifiedPitch(DeleteIdentifiedPitchOp),
|
|
ModifyIdentifiedPitch(ModifyIdentifiedPitchOp),
|
|
|
|
// Cross-cutting operations
|
|
CreateCrossCutting(CreateCrossCuttingPayload),
|
|
DeleteCrossCutting(DeleteCrossCuttingPayload),
|
|
ModifyCrossCutting(ModifyCrossCuttingPayload),
|
|
|
|
// Repeat-structure operations
|
|
CreateRepeatStructure(CreateRepeatStructureOp),
|
|
DeleteRepeatStructure(DeleteRepeatStructureOp),
|
|
|
|
// Region and structure operations
|
|
ChangeRegionTimeModel(ChangeRegionTimeModelOp),
|
|
InsertRegion(InsertRegionOp),
|
|
DeleteRegion(DeleteRegionOp),
|
|
InsertStaffInstance(InsertStaffInstanceOp),
|
|
DeleteStaffInstance(DeleteStaffInstanceOp),
|
|
InsertStaff(InsertStaffOp),
|
|
CreateVoice(CreateVoiceOp),
|
|
DeleteVoice(DeleteVoiceOp),
|
|
|
|
// Metric-model operations
|
|
SetTimeSignature(SetTimeSignatureOp),
|
|
SetTempoSegment(SetTempoSegmentOp),
|
|
SetMetricGrid(SetMetricGridOp),
|
|
|
|
// Score settings
|
|
SetMetadata(SetMetadataOp),
|
|
|
|
// Layout-semantic operations
|
|
SetUserSystemBreak(SetUserSystemBreakOp),
|
|
SetUserPageBreak(SetUserPageBreakOp),
|
|
SetStaffLayout(SetStaffLayoutOp),
|
|
|
|
// Transaction declaration. Carries metadata; member primitives
|
|
// reference the transaction by id in their envelopes.
|
|
DeclareTransaction(TransactionDescriptor),
|
|
|
|
// Extension-defined primitive operation.
|
|
Registered(OperationKindRegistryId, SerializedRegisteredOp),
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsection{The Operation Set as CRDT}
|
|
|
|
\begin{requirement}
|
|
\label{req:semops:grow-only-operation-set}
|
|
The replicated operation set is a grow-only CRDT: replicas
|
|
accumulate envelopes by gossip, broadcast, or any other
|
|
delivery mechanism, and converge on the same set when they have
|
|
observed the same envelopes. The set is set-valued, not
|
|
multiset-valued: an envelope is a member of the set or it is
|
|
not, with no count.
|
|
|
|
A replica \MUSTNOT{} discard envelopes from the operation set
|
|
except by an explicit pruning operation (Chapter~\ref{ch:format}),
|
|
which preserves canonical state through retained base snapshots
|
|
and the post-pruning operation set.
|
|
\end{requirement}
|
|
|
|
\subsection{OperationId Collisions and Replica Equivocation}
|
|
\label{sec:semops:equivocation}
|
|
|
|
Two received envelopes may carry the same \texttt{OperationId}.
|
|
Acceptance must not depend on arrival order: if it did, two
|
|
replicas observing the same envelopes in different orders could
|
|
diverge on which one is canonical. The operation set therefore
|
|
maps each \texttt{OperationId} to an \texttt{OperationSlot}
|
|
rather than directly to an envelope.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub enum OperationSlot {
|
|
/// Exactly one canonical envelope is known for this
|
|
/// OperationId. The envelope reduces normally.
|
|
Single(OperationEnvelope),
|
|
|
|
/// Two or more distinct canonical envelopes have been
|
|
/// observed for this OperationId. The slot is equivocated.
|
|
/// The operation produces no canonical effect until external
|
|
/// recovery resolves the equivocation.
|
|
Equivocated {
|
|
operation_id: OperationId,
|
|
/// Every distinct canonical envelope observed for this
|
|
/// OperationId. Identified by content hash to avoid
|
|
/// duplicating large payloads. Sorted lexicographically by
|
|
/// hash for deterministic enumeration.
|
|
candidates: BTreeSet<EnvelopeHash>,
|
|
},
|
|
}
|
|
|
|
/// BLAKE3-256 hash of a canonical envelope serialization with
|
|
/// domain tag "MUSCENVH".
|
|
pub struct EnvelopeHash(pub [u8; 32]);
|
|
\end{lstlisting}
|
|
|
|
\begin{requirement}
|
|
\label{req:semops:operation-equivocation}
|
|
Slot transitions are determined as follows, independently of
|
|
arrival order:
|
|
|
|
\begin{itemize}
|
|
\item If no slot exists for the incoming envelope's
|
|
\texttt{OperationId}: the incoming envelope is
|
|
well-formedness checked
|
|
(Section~\ref{sec:semops:acceptance}) and, if well-formed,
|
|
a \texttt{Single} slot is created.
|
|
\item If a \texttt{Single} slot exists and the incoming
|
|
envelope is byte-identical (canonical serialization) to
|
|
the slot's envelope: the duplicate is dropped silently;
|
|
the slot is unchanged.
|
|
\item If a \texttt{Single} slot exists and the incoming
|
|
envelope's canonical bytes differ from the slot's
|
|
envelope: the slot transitions to \texttt{Equivocated} with
|
|
both envelopes' hashes in its \texttt{candidates} set. The
|
|
previously-canonical envelope is retained in a diagnostic
|
|
candidate store keyed by its hash; the incoming envelope is
|
|
likewise retained. Neither envelope contributes to canonical
|
|
reduction.
|
|
\item If an \texttt{Equivocated} slot exists: the incoming
|
|
envelope's hash is added to its \texttt{candidates} set if
|
|
not already present; otherwise the duplicate is dropped.
|
|
The envelope is retained in the candidate store. The slot
|
|
remains \texttt{Equivocated}.
|
|
\end{itemize}
|
|
|
|
An equivocated slot \MUSTNOT{} contribute to canonical
|
|
reduction. Reduction proceeds as if the operation does not
|
|
exist: any operation that causally depends on the equivocated
|
|
\texttt{OperationId} is reduced under the same rule as a
|
|
missing causal predecessor (the dependent operation is
|
|
retained but produces no effect until the equivocation is
|
|
resolved).
|
|
|
|
Resolution is by explicit recovery action, not by arrival
|
|
order or content. Three resolution mechanisms are permitted:
|
|
|
|
\begin{itemize}
|
|
\item \emph{Transport-level credential revocation}: the
|
|
collaboration transport revokes the equivocating replica's
|
|
credentials and the user accepts one envelope timeline as
|
|
canonical via an explicit reconciliation operation.
|
|
\item \emph{Local user choice}: the user (or an authorized
|
|
administrator) issues an explicit
|
|
\texttt{ResolveEquivocation} operation naming the
|
|
\texttt{OperationId} and the chosen
|
|
\texttt{EnvelopeHash}, promoting the slot back to
|
|
\texttt{Single}.
|
|
\item \emph{Profile-declared deterministic policy}: a
|
|
conformance profile \MAY{} declare a deterministic
|
|
selection function (e.g., ``lowest canonical envelope
|
|
hash wins''). This is a policy choice, not a default. A
|
|
bundle reduced under a profile with a declared resolution
|
|
policy is canonical under that profile; the same bundle
|
|
reduced under a profile without such a policy remains
|
|
anomalous.
|
|
\end{itemize}
|
|
|
|
If a bundle on disk contains an equivocated slot without a
|
|
resolution policy or pending resolution operation, the bundle
|
|
is anomalous. Readers \MUST{} open it, if at all, in a
|
|
diagnostic recovery mode that surfaces the equivocation
|
|
prominently and that prohibits ordinary editing of the
|
|
affected portion of canonical state.
|
|
\end{requirement}
|
|
|
|
\begin{rationale}
|
|
Replica equivocation (the same replica producing two distinct
|
|
envelopes with the same \texttt{OperationId}) is a real
|
|
failure mode: replica-key compromise, software bug, replay
|
|
attack, or filesystem corruption can all produce it. The
|
|
\texttt{OperationSlot} model makes equivocation a
|
|
representable, order-independent state: two replicas that
|
|
have observed the same set of envelopes converge on the same
|
|
slot states regardless of the order of observation. Treating
|
|
the first arrival as canonical would let a malicious
|
|
early-arriver pin canonical state at one replica while a
|
|
late-arriver pins different state at another replica; the
|
|
slot model avoids that asymmetry by refusing to elevate
|
|
either candidate without an explicit resolution action.
|
|
|
|
Recovery is a policy choice rather than a built-in default
|
|
because the right answer depends on context: a collaborating
|
|
team with credential revocation will resolve differently
|
|
from a single-user backup-restore scenario, which differs
|
|
again from an adversarial-document forensic scenario.
|
|
\end{rationale}
|
|
|
|
\subsection{Envelope Acceptance and Malformed-Envelope Behavior}
|
|
\label{sec:semops:acceptance}
|
|
|
|
Operation envelopes arrive from local authoring and from remote
|
|
replicas. Acceptance rules determine when an envelope enters the
|
|
operation set.
|
|
|
|
\begin{requirement}
|
|
\label{req:semops:envelope-well-formedness}
|
|
An incoming envelope is well-formed if and only if all of the
|
|
following hold:
|
|
\begin{itemize}
|
|
\item Its \texttt{OperationId} consists of a 64-bit
|
|
\texttt{ReplicaId} that is not
|
|
\texttt{ReplicaId::SYSTEM\_DERIVED} (operations are not
|
|
authored by the system namespace) and a 64-bit counter.
|
|
\item Its \texttt{stamp.id} is byte-identical to its
|
|
top-level \texttt{id} field: an envelope's stamp \MUST{}
|
|
address the same operation as the envelope itself. This
|
|
eliminates an ambiguity where an authoring replica could
|
|
otherwise stamp an envelope as belonging to a different
|
|
operation than its identity declares.
|
|
\item Its \texttt{stamp.hlc.physical\_time} is a finite,
|
|
non-negative integer value, expressed in the canonical
|
|
time unit (Chapter~\ref{ch:format}).
|
|
\item Its causal context's DVV references only well-formed
|
|
replica identifiers and non-negative integer counter
|
|
values; dots are well-formed \texttt{OperationId}s.
|
|
\item Its payload deserializes successfully against the
|
|
profile's declared operation catalog.
|
|
\end{itemize}
|
|
|
|
An envelope that is not well-formed \MUST{} be rejected at
|
|
reception. Rejection is recorded in the local diagnostic log
|
|
but does \emph{not} enter the canonical operation set. Remote
|
|
replicas \MAY{} retransmit; persistent rejection indicates a
|
|
non-conforming peer.
|
|
|
|
Acceptance \MUSTNOT{} require trust in the envelope's
|
|
\texttt{stamp.hlc} content: a malicious or misconfigured peer
|
|
may emit envelopes with implausible future physical times or
|
|
zero/extreme logical counters. The canonical reduction order
|
|
(Section~\ref{sec:semops:reduction-order}) consumes the stamp
|
|
as ordering metadata; it does not validate plausibility.
|
|
Implementations \MAY{} surface clock-skew warnings in
|
|
diagnostics but \MUSTNOT{} reorder envelopes for plausibility
|
|
reasons that would deviate from the canonical reduction order.
|
|
\end{requirement}
|
|
|
|
\subsection{Per-Replica HLC Monotonicity}
|
|
\label{sec:semops:hlc-monotonicity}
|
|
|
|
Per-replica monotonicity is a property of the \emph{set} of
|
|
accepted envelopes, not of arrival order. Out-of-order delivery
|
|
is normal in gossip systems and is not a violation.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
/// A range of envelopes from a single replica that have been
|
|
/// identified as monotonicity-violating and excluded from
|
|
/// ordinary canonical reduction. Retained for diagnostic and
|
|
/// recovery purposes.
|
|
pub struct AnomalousReplicaSegment {
|
|
pub replica: ReplicaId,
|
|
/// The smallest counter at or after which the replica's
|
|
/// stream is anomalous. All envelopes from this replica
|
|
/// with counter >= first_bad_counter are excluded from
|
|
/// canonical reduction.
|
|
pub first_bad_counter: u64,
|
|
/// The detected anomaly reason.
|
|
pub reason: ReplicaAnomalyReason,
|
|
/// Operation ids in the excluded segment, retained for
|
|
/// diagnostic recovery. Sorted by counter.
|
|
pub excluded: Vec<OperationId>,
|
|
}
|
|
|
|
pub enum ReplicaAnomalyReason {
|
|
/// The replica produced an envelope at counter c2 with a
|
|
/// stamp tuple strictly less than a previously-known
|
|
/// envelope at counter c1 < c2.
|
|
HlcMonotonicityViolation {
|
|
violating_pair: (OperationId, OperationId),
|
|
},
|
|
/// A registered anomaly reason defined by an extension or
|
|
/// transport.
|
|
Registered(ReplicaAnomalyRegistryId),
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\begin{requirement}
|
|
\label{req:semops:per-replica-hlc-monotonicity}
|
|
For any two envelopes accepted into the operation set that
|
|
share the same authoring \texttt{ReplicaId}, with operation-
|
|
counter values $c_1 < c_2$:
|
|
|
|
\begin{quote}
|
|
The stamp tuple
|
|
\texttt{(stamp.hlc.physical\_time, stamp.hlc.logical\_counter,
|
|
id.counter)} of the envelope with counter $c_1$ \MUST{} be
|
|
lexicographically less than or equal to the corresponding
|
|
tuple of the envelope with counter $c_2$.
|
|
\end{quote}
|
|
|
|
Arrival order \MUSTNOT{} be used to determine whether this
|
|
property holds. If a replica receives counter 10 before
|
|
counter 9, that is a property of delivery, not of monotonicity.
|
|
|
|
If two envelopes from the same replica violate this property
|
|
(their stamp tuples are inconsistent with their counter order),
|
|
the document carries a \textbf{replica equivocation anomaly}.
|
|
An \texttt{AnomalousReplicaSegment} is created (or extended)
|
|
for the offending replica. The segment's
|
|
\texttt{first\_bad\_counter} is the smaller of the two
|
|
counters in the violating pair.
|
|
|
|
Effects on canonical reduction:
|
|
|
|
\begin{itemize}
|
|
\item Every envelope from the offending replica with counter
|
|
$\geq$ \texttt{first\_bad\_counter} is excluded from
|
|
canonical reduction. Such envelopes are retained on disk
|
|
and in memory (the CRDT property does not allow silent
|
|
removal) but are held in the
|
|
\texttt{AnomalousReplicaSegment}'s \texttt{excluded} list,
|
|
not in the canonical operation set.
|
|
\item Envelopes from the offending replica with counter
|
|
$<$ \texttt{first\_bad\_counter} remain in the canonical
|
|
operation set and reduce normally.
|
|
\item Any operation in the canonical set that causally
|
|
depends on an excluded envelope reduces under the
|
|
missing-causal-predecessor rule: the dependent operation
|
|
is retained, produces no canonical effect, and is held
|
|
pending resolution.
|
|
\item The document is marked as containing a replica-
|
|
equivocation anomaly. Ordinary editing \MAY{} continue on
|
|
the unaffected portion of canonical state; collaborative
|
|
synchronization with the equivocating replica \MUST{} be
|
|
suspended until external resolution.
|
|
\end{itemize}
|
|
|
|
Resolution paths mirror the \texttt{OperationSlot} equivocation
|
|
rules (Section~\ref{sec:semops:equivocation}): transport-level
|
|
credential revocation followed by an explicit reconciliation
|
|
operation, an explicit local recovery action, or a
|
|
profile-declared deterministic policy.
|
|
|
|
Authentication of replica identities and revocation of
|
|
compromised replicas are the responsibility of the
|
|
collaboration transport (Appendix~\ref{app:deferred}). This
|
|
chapter specifies only local detection, segment formation,
|
|
exclusion from canonical reduction, and the structural shape
|
|
of resolution.
|
|
\end{requirement}
|
|
|
|
\begin{rationale}
|
|
Excluding the violating segment from canonical reduction
|
|
rather than letting it reduce-but-mark-tainted is the safer
|
|
choice: a tainted reduction still produces canonical-looking
|
|
state derived from operations the spec has flagged as
|
|
untrustworthy. Excluding the segment keeps canonical state
|
|
derived only from operations that satisfy the monotonicity
|
|
invariant, at the cost of holding pending operations that
|
|
causally depended on the excluded segment.
|
|
\end{rationale}
|
|
|
|
\section{The Canonical Reduction}
|
|
\label{sec:semops:reduction}
|
|
|
|
The materialized score graph is the deterministic reduction of the
|
|
operation set. This section specifies how that reduction is performed.
|
|
|
|
\subsection{Operation Lifecycle}
|
|
\label{sec:semops:lifecycle}
|
|
|
|
Every operation traverses four phases:
|
|
|
|
\begin{description}
|
|
\item[Prepare.] The local UI command validates enough to construct
|
|
a well-formed operation envelope. \emph{Prepare is advisory
|
|
validation only}: failure here means the UI rejects the user's
|
|
action and never authors the operation. Prepare does not bind
|
|
semantics; an operation that successfully prepares at one replica
|
|
\MAY{} be reduced to a conflict effect at another.
|
|
\item[Commit.] The operation envelope is added to the replicated
|
|
operation set. After commit, the operation is part of canonical
|
|
state and \MUSTNOT{} be discarded outside the pruning protocol.
|
|
\item[Reduce.] During materialization, the operation is processed
|
|
by the canonical reduction algorithm against the current
|
|
materialized state. Reduction is deterministic: every replica
|
|
with the same operation set produces the same materialized
|
|
state.
|
|
\item[Report.] The reduction produces a deterministic
|
|
\texttt{OperationEffect} for each operation. Effects are visible
|
|
graph facts: users can inspect what each operation did, and
|
|
subsequent operations can address conflicts and no-ops directly.
|
|
\end{description}
|
|
|
|
\subsection{The Reduction Algorithm}
|
|
|
|
\begin{requirement}
|
|
\label{req:semops:reduction-algorithm}
|
|
The materialized score state is computed from the operation set as
|
|
follows:
|
|
|
|
\begin{enumerate}
|
|
\item Sort the operation set into the canonical reduction order
|
|
(Section~\ref{sec:semops:reduction-order}).
|
|
\item Group consecutive operations that share a transaction id
|
|
into transaction blocks.
|
|
\item For each non-transaction operation, apply the operation to
|
|
the working state, computing its \texttt{OperationEffect}.
|
|
\item For each transaction block, apply all member operations
|
|
atomically: either all succeed and the transaction reduces to
|
|
\texttt{Applied} (with each member's individual effect
|
|
recorded), or some member fails its invariant preconditions,
|
|
in which case the whole transaction reduces to a
|
|
\texttt{Conflicted} effect with a recorded conflict.
|
|
\item Record every effect in the materialized state's effect log
|
|
(which is part of canonical state, not implementation detail).
|
|
\end{enumerate}
|
|
|
|
Given the same operation set, conformant implementations \MUST{}
|
|
produce score states with byte-identical structural content and
|
|
byte-identical effect logs. Differences in implementation language,
|
|
storage layout, threading model, or hardware \MUSTNOT{} produce
|
|
divergent reductions.
|
|
\end{requirement}
|
|
|
|
\subsection{Canonical Reduction Order}
|
|
\label{sec:semops:reduction-order}
|
|
|
|
\begin{requirement}
|
|
\label{req:semops:canonical-reduction-order}
|
|
The canonical reduction order is the lexicographic ordering by:
|
|
|
|
\begin{enumerate}
|
|
\item Causal order: for operations $A$ and $B$, if $A$ is in
|
|
$B$'s causal context (transitively, via DVV closure), then
|
|
$A$ precedes $B$.
|
|
\item For operations not ordered by causal order (i.e.,
|
|
concurrent operations), lexicographic order by:
|
|
\begin{enumerate}
|
|
\item \texttt{stamp.hlc.physical\_time} (ascending)
|
|
\item \texttt{stamp.hlc.logical\_counter} (ascending)
|
|
\item \texttt{stamp.id.replica} (ascending, lexicographic on
|
|
the replica identifier's canonical byte form)
|
|
\item \texttt{stamp.id.counter} (ascending)
|
|
\end{enumerate}
|
|
\end{enumerate}
|
|
|
|
Causal order strictly dominates HLC order: wall-clock skew may
|
|
affect the ordering of concurrent operations but \MUSTNOT{} cause
|
|
a causal predecessor to sort after a causal successor. The HLC
|
|
authoring rule (Section~\ref{sec:semops:framework}) guarantees this
|
|
by construction.
|
|
\end{requirement}
|
|
|
|
\subsection{Operation Effects}
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
/// The deterministic effect of an operation under canonical
|
|
/// reduction. Recorded as part of materialized state.
|
|
pub enum OperationEffect {
|
|
/// The operation applied cleanly with no compensating changes.
|
|
Applied,
|
|
|
|
/// The operation applied with deterministic compensating changes
|
|
/// (e.g., re-anchoring records, attachment migration).
|
|
AppliedWithRepair { repairs: Vec<RepairRecord> },
|
|
|
|
/// The operation could not apply cleanly; a conflict record was
|
|
/// created in the conflict registry.
|
|
Conflicted { conflict: ConflictId },
|
|
|
|
/// The operation's target was tombstoned. The operation is
|
|
/// preserved in the operation set but has no effect on the
|
|
/// materialized graph beyond the recorded effect.
|
|
TombstonedTarget { target: TypedObjectId },
|
|
|
|
/// The operation reduces to no effect. The reason is recorded
|
|
/// and is part of canonical state.
|
|
NoOp { reason: NoOpReason },
|
|
}
|
|
|
|
pub enum NoOpReason {
|
|
/// The operation's target was tombstoned by a causally-prior
|
|
/// operation, and the target operation has no compensating
|
|
/// repair rule.
|
|
TargetTombstoned,
|
|
|
|
/// The operation duplicates a causally-prior operation's effect.
|
|
AlreadyApplied,
|
|
|
|
/// A later operation in the canonical order subsumed this
|
|
/// operation's effect (e.g., a later DeleteEvent on the same
|
|
/// target supersedes an earlier RespellPitch).
|
|
SupersededByLaterOperation { superseder: OperationId },
|
|
|
|
/// An invariant precondition that was satisfied at authoring
|
|
/// time fails when the operation is reduced under concurrent
|
|
/// edits. Distinguished from Conflicted because the operation's
|
|
/// intent is not preserved: it simply has no applicable effect.
|
|
/// The failure reason is a typed enum, not a free-form string:
|
|
/// canonical effects MUST NOT contain free-form text, since two
|
|
/// implementations could otherwise produce divergent canonical
|
|
/// state while agreeing semantically.
|
|
PreconditionFailedUnderReduction {
|
|
reason: PreconditionFailureReason,
|
|
},
|
|
|
|
/// The operation belonged to a transaction whose other members
|
|
/// failed; the transaction was conflicted, and this operation
|
|
/// produced no individual effect.
|
|
TransactionConflict,
|
|
}
|
|
|
|
pub enum PreconditionFailureReason {
|
|
/// The target object identifier did not exist in the working
|
|
/// state at the moment of reduction.
|
|
TargetMissing,
|
|
|
|
/// The target object was tombstoned by a causally-prior
|
|
/// operation. Distinguished from NoOpReason::TargetTombstoned
|
|
/// in that the kind's rules permitted an attempt at
|
|
/// repair-on-tombstone, but the repair itself failed precondition.
|
|
TargetTombstoned,
|
|
|
|
/// The operation requires a region time model (e.g., metric)
|
|
/// different from the one currently in effect at the target
|
|
/// position.
|
|
WrongRegionTimeModel,
|
|
|
|
/// A tuplet compensation declared by the operation is invalid
|
|
/// against the current tuplet structure at the target.
|
|
TupletCompensationInvalid,
|
|
|
|
/// An event duration the operation specifies is invalid in
|
|
/// the target voice or region (e.g., overlapping a tied
|
|
/// chord-onset boundary in an incompatible way).
|
|
EventDurationInvalid,
|
|
|
|
/// The operation's target position falls outside the region
|
|
/// declared by its envelope, or addresses a region that does
|
|
/// not exist.
|
|
PositionOutsideRegion,
|
|
|
|
/// A pitch-space or tuning-context precondition failed: e.g.,
|
|
/// the operation declared a pitch in a pitch space that is not
|
|
/// active at the target region.
|
|
PitchSpaceMismatch,
|
|
|
|
/// A voice precondition failed: the operation targeted a
|
|
/// voice that does not exist or has been tombstoned.
|
|
VoiceMissing,
|
|
|
|
/// An extension-declared precondition failed. The extension
|
|
/// is identified; the extension's own catalog enumerates the
|
|
/// specific precondition codes.
|
|
ExtensionPrecondition(ExtensionPreconditionId),
|
|
|
|
/// Registered precondition code defined by a versioned
|
|
/// registry.
|
|
Registered(PreconditionFailureRegistryId),
|
|
}
|
|
|
|
/// A deterministic compensating change made during reduction. Each
|
|
/// RepairRecord describes one repair the reduction performed to
|
|
/// preserve graph invariants in the presence of a tombstoning or
|
|
/// migrating operation. Recorded as part of canonical state.
|
|
pub struct RepairRecord {
|
|
/// The kind of repair performed.
|
|
pub kind: RepairKind,
|
|
|
|
/// The object affected by this repair.
|
|
pub target: TypedObjectId,
|
|
}
|
|
|
|
pub enum RepairKind {
|
|
/// A reference was re-anchored to a surviving target per the
|
|
/// re-anchoring rule table (Section~\ref{sec:semops:reanchor}).
|
|
Reanchored {
|
|
from: TypedObjectId,
|
|
to: TypedObjectId,
|
|
reason: ReanchorReason,
|
|
},
|
|
|
|
/// A spanner or beam had members removed but enough remained
|
|
/// for the structure to survive.
|
|
SpannerTruncated {
|
|
removed_members: Vec<TypedObjectId>,
|
|
},
|
|
|
|
/// A user-content reference was orphaned (target lost; reference
|
|
/// preserved for diagnostic purposes).
|
|
Orphaned,
|
|
|
|
/// A reference whose existence required its target was
|
|
/// cascade-deleted.
|
|
CascadeDeleted,
|
|
|
|
/// An attachment transitioned to tombstoned-target state.
|
|
AttachmentTombstoned,
|
|
|
|
/// A voice was promoted to a system-derived voice identity due
|
|
/// to concurrent insertion collision.
|
|
VoicePromoted {
|
|
from: VoiceId,
|
|
to: VoiceId,
|
|
},
|
|
|
|
/// A tuplet was compensated according to the operation's
|
|
/// declared TupletCompensation.
|
|
TupletCompensated {
|
|
compensation_kind: TupletCompensationKind,
|
|
},
|
|
|
|
/// A registered extension-defined repair kind.
|
|
Registered(RepairKindRegistryId),
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\begin{requirement}
|
|
\label{req:semops:total-operation-effects}
|
|
Every operation in the operation set \MUST{} produce exactly one
|
|
\texttt{OperationEffect} under reduction. Effects \MUST{} be
|
|
deterministic: every replica reducing the same operation set in
|
|
the canonical order produces the same effect for each operation,
|
|
including identical \texttt{NoOpReason} values where applicable.
|
|
\end{requirement}
|
|
|
|
\subsection{Cache and Snapshot Discipline}
|
|
\label{sec:semops:caches}
|
|
|
|
\begin{requirement}
|
|
\label{req:semops:cache-transparency}
|
|
Caches, snapshots, and partial reductions are acceleration
|
|
structures only. They \MUSTNOT{} affect the canonical materialized
|
|
state, which is defined solely by the operation set, the active
|
|
conformance profile, and the normative reduction algorithm.
|
|
|
|
Implementations \MUST{} invalidate or recompute caches whenever any
|
|
of the following change:
|
|
\begin{itemize}
|
|
\item The visible operation set (additions, or pruning operations
|
|
under the file-format protocol).
|
|
\item The causal order derived from any envelope's causal
|
|
context.
|
|
\item The reduction algorithm version declared by the active
|
|
conformance profile.
|
|
\item The active conformance profile itself.
|
|
\end{itemize}
|
|
|
|
Cache hits \MUST{} return data that is bit-identical to the data a
|
|
fresh reduction would produce. Caches that cannot guarantee this
|
|
property \MUST{} be invalidated.
|
|
\end{requirement}
|
|
|
|
\section{Object Existence and Tombstones}
|
|
\label{sec:semops:tombstones}
|
|
|
|
\subsection{Add-Tombstone Semantics}
|
|
|
|
Object lifetimes in the materialized graph follow add-tombstone
|
|
semantics: an operation creates an object by minting a stable
|
|
identifier, and an operation deletes an object by tombstoning that
|
|
identifier. Tombstoned identifiers are retained; their resolution
|
|
yields a deletion record rather than the original object.
|
|
|
|
\begin{requirement}
|
|
\label{req:semops:tombstone-state-machine}
|
|
Identifiers \MUST{} have one of three states in the materialized
|
|
graph:
|
|
|
|
\begin{itemize}
|
|
\item \textbf{Live.} The identifier resolves to a current object.
|
|
\item \textbf{Tombstoned.} The identifier resolves to a deletion
|
|
record carrying the operation that performed the deletion and
|
|
the operation that originally minted the identifier.
|
|
\item \textbf{Unknown.} The identifier has not been seen by this
|
|
replica. This state is observable in collaborative scenarios
|
|
where envelopes arrive out of order; it resolves to either Live
|
|
or Tombstoned once causal predecessors arrive.
|
|
\end{itemize}
|
|
|
|
Implementations \MUSTNOT{} resurrect tombstoned identifiers
|
|
(recreating an object with a previously-tombstoned id) except
|
|
through an explicit inverse-of-delete operation in the operation
|
|
set. Independent re-creation is forbidden.
|
|
\end{requirement}
|
|
|
|
\subsection{Tombstone Retention}
|
|
|
|
\begin{requirement}
|
|
\label{req:semops:tombstone-retention}
|
|
Tombstones \MUST{} be retained while any of the following hold:
|
|
\begin{itemize}
|
|
\item Pending replication: operations referencing the tombstoned
|
|
identifier may not yet have been observed by all replicas.
|
|
\item Pending undo: a transaction containing the deletion is
|
|
within undo range.
|
|
\item Active conflict resolution: a conflict record references
|
|
the tombstoned identifier and has not been resolved.
|
|
\item Attachment resolution: a tombstoned-target attachment
|
|
remains in the score for historical or diagnostic purposes.
|
|
\end{itemize}
|
|
|
|
Tombstone garbage collection is governed by the pruning protocol
|
|
in Chapter~\ref{ch:format}. Tombstones \MUSTNOT{} be discarded
|
|
outside that protocol.
|
|
\end{requirement}
|
|
|
|
\subsection{Reference Resolution Across Tombstones}
|
|
|
|
\begin{requirement}
|
|
\label{req:semops:tombstoned-reference-resolution}
|
|
A reference to a tombstoned identifier resolves to a
|
|
\texttt{TombstonedTarget} state rather than being silently
|
|
invalidated. Operations targeting tombstoned identifiers reduce to
|
|
\texttt{TombstonedTarget} or \texttt{NoOp} effects according to
|
|
their kind's declared rules (Section~\ref{sec:semops:examples}).
|
|
|
|
Re-anchoring rules (Section~\ref{sec:semops:reanchor}) define how
|
|
cross-cutting structures and attachments respond to tombstoned
|
|
references at reduction time.
|
|
\end{requirement}
|
|
|
|
\section{Conflict Records}
|
|
\label{sec:semops:conflicts}
|
|
|
|
When an operation cannot apply cleanly, the reduction produces a
|
|
\texttt{Conflicted} effect referencing a \texttt{ConflictRecord} in
|
|
the canonical conflict registry (a component of materialized state;
|
|
see below). Conflict records are first-class: stable, addressable,
|
|
visible to users, and addressable by subsequent operations.
|
|
|
|
\subsection{Conflict Record Type}
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct ConflictRecord {
|
|
/// Content-derived identifier (Section below).
|
|
pub id: ConflictId,
|
|
|
|
/// The operations that participated in producing this conflict.
|
|
/// At least two for a true conflict; one for an operation that
|
|
/// failed precondition checking under reduction.
|
|
pub caused_by: Vec<OperationId>,
|
|
|
|
/// The kind of conflict, governing what resolution options
|
|
/// exist.
|
|
pub kind: ConflictKind,
|
|
|
|
/// Objects affected by this conflict, for diagnostic and UI
|
|
/// navigation purposes.
|
|
pub affected_objects: Vec<TypedObjectId>,
|
|
|
|
/// Current resolution state. Conflicts begin Unresolved; a
|
|
/// later ResolveConflict operation transitions them to Resolved
|
|
/// or Dismissed.
|
|
pub resolution_state: ConflictResolutionState,
|
|
}
|
|
|
|
pub enum ConflictKind {
|
|
/// Two concurrent operations attempted to write the same
|
|
/// non-LWW field. The winning operation's effect is in the
|
|
/// materialized state; the losing operation's effect is in the
|
|
/// conflict record for user inspection.
|
|
StructuralFieldCollision {
|
|
winner: OperationId,
|
|
loser: OperationId,
|
|
field: FieldPath,
|
|
},
|
|
|
|
/// A transaction's primitive members were partially applicable
|
|
/// under reduction; the whole transaction was rejected.
|
|
TransactionConflict {
|
|
transaction: TransactionId,
|
|
failed_members: Vec<OperationId>,
|
|
},
|
|
|
|
/// An operation targeted a tombstoned object and could not be
|
|
/// repaired by the kind's declared rules.
|
|
TombstonedTarget {
|
|
target: TypedObjectId,
|
|
operation: OperationId,
|
|
},
|
|
|
|
/// Re-anchoring failed: no deterministic target could be found.
|
|
ReanchorFailure {
|
|
original_referent: TypedObjectId,
|
|
referencing_object: TypedObjectId,
|
|
},
|
|
|
|
/// A change-region-time-model operation produced contained
|
|
/// events whose coordinate kinds are incompatible with the new
|
|
/// time model; the migration could not be performed
|
|
/// deterministically.
|
|
TimeModelMigrationFailure {
|
|
region: RegionId,
|
|
incompatible_events: Vec<EventId>,
|
|
},
|
|
|
|
/// A registered extension operation's reduction failed in a
|
|
/// way described by the extension. Opaque to the core.
|
|
ExtensionConflict {
|
|
kind_id: ConflictKindRegistryId,
|
|
details: SerializedExtensionConflict,
|
|
},
|
|
}
|
|
|
|
pub enum ConflictResolutionState {
|
|
Unresolved,
|
|
Resolved { by: OperationId, action: ResolutionAction },
|
|
Dismissed { by: OperationId },
|
|
}
|
|
|
|
// See Requirement (field-collision effect tag): for a
|
|
// StructuralFieldCollision the winner (later op) reads Conflicted;
|
|
// the earlier op retains Applied.
|
|
|
|
pub enum ResolutionAction {
|
|
/// Accept the losing operation's effect (replacing the winner).
|
|
AcceptLoser,
|
|
/// Reapply the winner explicitly (no semantic change; clears the
|
|
/// conflict).
|
|
KeepWinner,
|
|
/// User-authored replacement that supersedes both.
|
|
Override { override_operation: OperationId },
|
|
/// Re-anchor to a user-chosen target.
|
|
Reanchor { new_target: TypedObjectId },
|
|
/// Dismiss the conflict without changing the materialized graph:
|
|
/// the user acknowledges it and accepts the current (winner)
|
|
/// state. Selects the Dismissed resolution state.
|
|
Dismiss,
|
|
/// Custom resolution for registered conflict kinds.
|
|
Registered(ResolutionRegistryId),
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\begin{requirement}
|
|
\label{req:semops:resolution-action-discriminants}
|
|
The discriminant byte of \texttt{ResolutionAction} is its
|
|
declaration-order index in the listing above: \texttt{AcceptLoser}
|
|
$= 0$, \texttt{KeepWinner} $= 1$, \texttt{Override} $= 2$,
|
|
\texttt{Reanchor} $= 3$, \texttt{Dismiss} $= 4$, \texttt{Registered}
|
|
$= 5$. A \texttt{ResolutionAction} is encoded into the
|
|
\texttt{ResolveConflict} operation payload and therefore into that
|
|
operation's content hash, so this assignment is normative and stable
|
|
and \MUSTNOT{} be reordered. \texttt{Dismiss} was assigned \texttt{4}
|
|
ahead of \texttt{Registered} (\texttt{5}) when the \texttt{Dismissed}
|
|
resolution state was made reachable by an authored operation
|
|
(Section~\ref{sec:semops:conflict-resolution} below), fixing the wire
|
|
form against a silent shift.
|
|
\end{requirement}
|
|
|
|
\begin{requirement}
|
|
\label{req:semops:field-collision-effect}
|
|
When two concurrent operations write the same non-LWW field and
|
|
reduction records a \texttt{StructuralFieldCollision}, the
|
|
per-operation \texttt{OperationEffect} is assigned as follows: the
|
|
\emph{winner} --- the operation later in canonical order, whose
|
|
choice materializes and whose processing created the conflict record
|
|
--- reads \texttt{Conflicted\{conflict\}}; the \emph{earlier}
|
|
(losing) operation retains the \texttt{Applied} effect it received
|
|
when it was first reduced. The winner carries the flag because it is
|
|
the single operation that both materializes the field and observes
|
|
the collision, which makes the assignment independent of the order
|
|
in which envelopes arrive at any replica.
|
|
\end{requirement}
|
|
|
|
\begin{rationale}
|
|
The alternative --- tagging the loser \texttt{Conflicted} (or
|
|
\texttt{NoOp\{SupersededByLaterOperation\}}) because its intent did
|
|
not survive --- is defensible and arguably more intuitive for a UI
|
|
that surfaces ``your edit was overridden.'' We pin
|
|
\emph{winner-carries} instead because it is computed at the moment
|
|
the collision is detected (when the winning operation is reduced
|
|
against already-present state), needs no second pass to retroactively
|
|
re-tag an earlier operation, and is manifestly order-independent. A
|
|
UI that wishes to tell the losing author their edit was overridden
|
|
reads the \texttt{StructuralFieldCollision} record's \texttt{loser}
|
|
field rather than the loser operation's effect.
|
|
\end{rationale}
|
|
|
|
\subsection{The Conflict Registry}
|
|
|
|
Canonical materialized state carries a top-level conflict registry
|
|
(the score graph does not --- conflict records are a reduction
|
|
product, not authored content):
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct ConflictRegistry {
|
|
pub records: Vec<ConflictRecord>,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\begin{requirement}
|
|
\label{req:semops:conflict-registry-persistence}
|
|
The conflict registry is part of canonical materialized state.
|
|
Conflict records persist until they are resolved or dismissed by
|
|
explicit \texttt{ResolveConflict} operations. Conflict records
|
|
\MUSTNOT{} be silently removed; their lifetime is governed by
|
|
the operation set, not by UI state.
|
|
|
|
The layout pipeline (Chapter~\ref{ch:layout-ir}) \SHOULD{} provide
|
|
visual indication of unresolved conflicts at their affected
|
|
objects; this is the user-facing manifestation of the conflict
|
|
registry's contents.
|
|
\end{requirement}
|
|
|
|
\subsection{ConflictId Derivation}
|
|
\label{sec:semops:conflict-id}
|
|
|
|
Conflict records are produced during deterministic reduction, not
|
|
authored by replicas. Their identifiers \MUST{} therefore be
|
|
content-derived, not minted from a local replica-plus-counter
|
|
sequence. Otherwise, two replicas reducing the same operation set
|
|
would produce conflict registries with disagreeing identifiers,
|
|
breaking canonical state.
|
|
|
|
\begin{requirement}
|
|
\label{req:semops:conflict-id-derivation}
|
|
Each \texttt{ConflictId} \MUST{} be derived deterministically from
|
|
the conflict's content via BLAKE3 truncation:
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
fn derive_conflict_id(
|
|
kind: &ConflictKind,
|
|
causing_operations: &[OperationId],
|
|
affected_objects: &[TypedObjectId],
|
|
) -> ConflictId {
|
|
let mut preimage = Vec::new();
|
|
preimage.extend_from_slice(b"MUSCCONF");
|
|
preimage.extend_from_slice(&kind.canonical_bytes());
|
|
|
|
// Sort causing operations lexicographically by canonical bytes
|
|
let mut sorted_ops = causing_operations.to_vec();
|
|
sorted_ops.sort_by(|a, b| a.canonical_bytes().cmp(&b.canonical_bytes()));
|
|
for op in &sorted_ops {
|
|
preimage.extend_from_slice(&op.canonical_bytes());
|
|
}
|
|
|
|
// Sort affected objects lexicographically by canonical bytes
|
|
let mut sorted_objs = affected_objects.to_vec();
|
|
sorted_objs.sort_by(|a, b| a.canonical_bytes().cmp(&b.canonical_bytes()));
|
|
for obj in &sorted_objs {
|
|
preimage.extend_from_slice(&obj.canonical_bytes());
|
|
}
|
|
|
|
let hash = blake3(&preimage);
|
|
ConflictId(u128::from_be_bytes(
|
|
hash[0..16].try_into().unwrap()
|
|
))
|
|
}
|
|
\end{lstlisting}
|
|
|
|
The canonical \texttt{ConflictKind} byte representation \MUST{}
|
|
include the kind discriminant and all kind payload (winning
|
|
operation, losing operation, field path, transaction id,
|
|
tombstoned target, original referent, region, extension kind
|
|
identifier, etc.), so that distinct conflicts have distinct
|
|
preimages by construction. Implementations \MUSTNOT{} introduce
|
|
an ordinal or local counter into the preimage; conflicts that
|
|
share kind, causing operations, and affected objects are by
|
|
definition the same conflict.
|
|
\end{requirement}
|
|
|
|
\begin{rationale}
|
|
Content-derived identifiers are the only mechanism that makes
|
|
the conflict registry a deterministic materialized fact. Every
|
|
replica reducing the same operation set produces the same
|
|
conflict records with the same identifiers, addressable by
|
|
subsequent \texttt{ResolveConflict} operations whose payloads
|
|
carry those identifiers by reference.
|
|
|
|
Truncation from 256 to 128 bits is safe: the birthday-collision
|
|
probability for any practical conflict count is vanishingly
|
|
small, and the same truncation discipline already governs other
|
|
128-bit identifiers in the format.
|
|
\end{rationale}
|
|
|
|
\subsection{Conflict Resolution Operations}
|
|
\label{sec:semops:conflict-resolution}
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct ResolveConflictPayload {
|
|
pub target: ConflictId,
|
|
pub action: ResolutionAction,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\begin{requirement}
|
|
\label{req:semops:conflict-resolution-state}
|
|
A \texttt{ResolveConflict} operation transitions a conflict's
|
|
state from \texttt{Unresolved} to one of two states selected by its
|
|
\texttt{action}. The \texttt{ResolutionAction::Dismiss} action
|
|
transitions the conflict to \texttt{Dismissed} (no semantic change
|
|
to the materialized graph beyond the conflict record's state: the
|
|
user acknowledges the conflict and accepts the current winner
|
|
state). Every other \texttt{action} transitions the conflict to
|
|
\texttt{Resolved} with that action applied. Both resolution states
|
|
are thus reachable by an authored operation; \texttt{Dismiss} is the
|
|
authored selector for \texttt{Dismissed}, closing the prior gap in
|
|
which \texttt{Dismissed} was a representable state with no operation
|
|
to reach it.
|
|
|
|
Concurrent \texttt{ResolveConflict} operations on the same
|
|
conflict are themselves subject to canonical reduction: the
|
|
earlier operation under the canonical order is applied; the
|
|
later operation reduces to \texttt{NoOp} with reason
|
|
\texttt{AlreadyApplied} if its action is equivalent, or to
|
|
\texttt{Conflicted} with a meta-conflict record if its action
|
|
differs.
|
|
\end{requirement}
|
|
|
|
\section{Transactions}
|
|
\label{sec:semops:transactions}
|
|
|
|
Transactions are first-class replicated operation groups. They reduce
|
|
atomically: either every primitive member applies, or the whole
|
|
transaction conflicts.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct TransactionDescriptor {
|
|
pub id: TransactionId,
|
|
|
|
/// Human-readable label, for the undo history and diagnostics.
|
|
pub label: String,
|
|
|
|
/// Optional categorization (used by UIs and analytics).
|
|
pub category: Option<TransactionCategory>,
|
|
}
|
|
|
|
pub struct TransactionId(pub u128);
|
|
|
|
/// Open vocabulary: a minimal core set plus a Registered escape for
|
|
/// extension-defined categories.
|
|
pub enum TransactionCategory {
|
|
NoteEntry,
|
|
Structural,
|
|
Layout,
|
|
Import,
|
|
Registered(OperationKindRegistryId),
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\begin{requirement}
|
|
\label{req:semops:transaction-category}
|
|
\texttt{TransactionCategory} is an \emph{open} vocabulary. Its
|
|
normative core set is exactly \texttt{NoteEntry}, \texttt{Structural},
|
|
\texttt{Layout}, and \texttt{Import}; any other category \MUST{} be
|
|
expressed as \texttt{Registered(OperationKindRegistryId)} rather than
|
|
by extending the core set. The field is advisory (consumed by UIs and
|
|
analytics, never by canonical reduction), so the minimal core set is
|
|
the right tradeoff: under-specifying is cheap to extend through
|
|
\texttt{Registered}, whereas baking speculative categories into the
|
|
core vocabulary is not. The discriminants follow declaration order
|
|
(\texttt{NoteEntry} $= 0$ through \texttt{Registered} $= 4$).
|
|
\end{requirement}
|
|
|
|
Each primitive operation belonging to a transaction carries the
|
|
transaction's identifier in its envelope's \texttt{transaction} field.
|
|
The transaction descriptor itself is a separate operation envelope
|
|
(\texttt{DeclareTransaction}) whose payload contains the
|
|
\texttt{TransactionDescriptor}.
|
|
|
|
\subsection{Transaction Descriptor Causal Ordering}
|
|
\label{sec:semops:transaction-causal}
|
|
|
|
The canonical reduction order is causal-first, then HLC, then
|
|
operation id (Section~\ref{sec:semops:reduction-order}). For the
|
|
descriptor to be available when its members are reduced, the
|
|
ordering between descriptor and members must be guaranteed by
|
|
causal dependency, not by HLC stamps.
|
|
|
|
\begin{requirement}
|
|
\label{req:semops:transaction-descriptor-causality}
|
|
Every primitive operation member of a transaction \MUST{} causally
|
|
depend on the transaction's \texttt{DeclareTransaction} envelope:
|
|
the descriptor's \texttt{OperationId} \MUST{} appear in the
|
|
member's \texttt{causal\_context} (either in the per-replica DVV
|
|
range or among its dots). Authoring implementations \MUST{}
|
|
observe and include the descriptor in the causal context of each
|
|
member they author.
|
|
|
|
Delivery order need not respect this dependency: an out-of-order
|
|
delivery in which a member arrives before its descriptor is
|
|
normal and is handled by the canonical reduction algorithm,
|
|
which sorts by causal order before reducing.
|
|
|
|
At reduction time:
|
|
|
|
\begin{itemize}
|
|
\item If the descriptor is present in the operation set and
|
|
causally precedes every member: the transaction is
|
|
well-formed; it is reduced atomically per the rule below.
|
|
\item If a primitive operation declares membership in a
|
|
transaction whose \texttt{DeclareTransaction} envelope is
|
|
absent from the operation set, or whose envelope is present
|
|
but does not causally precede the member: the member is
|
|
malformed against the transaction model. It reduces to
|
|
\texttt{NoOp\{reason: TransactionConflict\}} and contributes
|
|
to a synthesized conflict record of kind
|
|
\texttt{ConflictKind::TransactionConflict} naming the
|
|
malformed member and the missing-or-misordered descriptor.
|
|
The member is preserved in the operation set (the CRDT
|
|
property prohibits removal) but produces no effect on
|
|
canonical state beyond the recorded conflict.
|
|
\end{itemize}
|
|
\end{requirement}
|
|
|
|
\begin{rationale}
|
|
Requiring causal dependency rather than HLC ordering matches
|
|
how transactions actually arise at the authoring replica: a
|
|
user begins an edit (descriptor), then commits its primitives.
|
|
The primitives can causally observe the descriptor because they
|
|
are authored after it on the same replica. The canonical
|
|
reduction order then handles them correctly without any special
|
|
transaction-extraction phase.
|
|
|
|
This rule also makes equivocating descriptors impossible:
|
|
a member cannot claim membership in a transaction whose
|
|
descriptor it has not seen.
|
|
\end{rationale}
|
|
|
|
\begin{requirement}
|
|
\label{req:semops:atomic-transaction-reduction}
|
|
Transactions reduce atomically. During reduction:
|
|
|
|
\begin{itemize}
|
|
\item All primitive members of a transaction are gathered into
|
|
a transaction block at the point of the earliest member's
|
|
canonical position.
|
|
\item The block is reduced as a single unit. Each member is
|
|
evaluated against the working state produced by prior members
|
|
within the same block.
|
|
\item If every member's invariant preconditions are satisfied,
|
|
the transaction reduces to \texttt{Applied}; each member's
|
|
individual effect is recorded as part of the transaction's
|
|
composite effect.
|
|
\item If any member's invariant preconditions fail, the entire
|
|
transaction reduces to \texttt{Conflicted}: a conflict record
|
|
is created with kind
|
|
\texttt{ConflictKind::TransactionConflict}, listing the
|
|
failed members; no member's effect is applied; each member's
|
|
individual effect is recorded as
|
|
\texttt{NoOp\{reason: TransactionConflict\}}.
|
|
\end{itemize}
|
|
|
|
Primitive members of a transaction \MUSTNOT{} be observable as
|
|
independently committed score states. Observers see either the
|
|
pre-transaction state or the fully applied post-transaction
|
|
state; they never see a partial intermediate.
|
|
\end{requirement}
|
|
|
|
\begin{rationale}
|
|
Atomic transaction reduction is essential for the tuplet
|
|
compensation model (Section~\ref{sec:semops:deleteevent}): the
|
|
delete operation and its compensating insert (or rewrite, or
|
|
cascade) are committed together as a single transaction, so the
|
|
score is never observably broken between them.
|
|
|
|
Local materialization may stream a transaction's primitives to the
|
|
UI for responsive feedback, but the materialized canonical state
|
|
visible to remote observers transitions only at transaction
|
|
boundaries.
|
|
\end{rationale}
|
|
|
|
\section{Re-Anchoring}
|
|
\label{sec:semops:reanchor}
|
|
|
|
When an operation tombstones an object, every other object
|
|
referencing it must respond deterministically. The response is
|
|
specified as a total deterministic function.
|
|
|
|
\subsection{The Re-Anchor Function}
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub enum ReanchorResult {
|
|
/// The reference is replaced with a reference to a new target.
|
|
Reanchored {
|
|
new_target: TypedObjectId,
|
|
reason: ReanchorReason,
|
|
},
|
|
|
|
/// The reference is preserved but its target is tombstoned.
|
|
/// The referencing object remains in the graph in a
|
|
/// tombstoned-target state.
|
|
TombstonedTarget,
|
|
|
|
/// The referencing object is marked orphaned but retained.
|
|
Orphaned,
|
|
|
|
/// Re-anchoring cannot be performed deterministically; a
|
|
/// conflict is recorded.
|
|
Conflicted { conflict: ConflictId },
|
|
|
|
/// The referencing object is cascade-deleted (tombstoned).
|
|
CascadeDeleted,
|
|
}
|
|
|
|
pub enum ReanchorReason {
|
|
SameVoiceNearer,
|
|
SameStaffInstanceNearer,
|
|
SameStaffNearer,
|
|
SameRegionNearer,
|
|
ExplicitFallback,
|
|
DeclaredByExtension(ReanchorReasonRegistryId),
|
|
/// A rank-4 (same-canvas) proximity survivor. Appended in
|
|
/// Pass 12 (P12-C4); its wire discriminant is 6 because
|
|
/// DeclaredByExtension already owned 5 when it was appended.
|
|
SameCanvasNearer,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
A re-anchor that selects a rank-4 (same-canvas) survivor records
|
|
\texttt{SameCanvasNearer} (ratified Pass~12); before the variant was
|
|
appended, such repairs were recorded \texttt{ExplicitFallback}, a
|
|
recording this revision supersedes.
|
|
|
|
\subsection{Total Ordering for "Nearest"}
|
|
|
|
The notion of ``nearest surviving anchor'' is defined as a total
|
|
ordering. There is no discretionary search and no implementation
|
|
freedom to choose among ``equally near'' candidates.
|
|
|
|
\begin{requirement}
|
|
\label{req:semops:nearest-reanchor-order}
|
|
For an object kind $K$ requiring re-anchoring to the nearest
|
|
surviving $K$, ``nearest'' is computed as the strict lexicographic
|
|
minimum over the surviving candidates of:
|
|
|
|
\begin{enumerate}
|
|
\item Containment proximity (smaller is closer): same voice
|
|
$(0)$, same staff instance $(1)$, same staff $(2)$, same
|
|
region $(3)$, same canvas $(4)$. Candidates further away than
|
|
the kind's declared maximum proximity are excluded.
|
|
\item Absolute time distance from the tombstoned referent's
|
|
resolved position, measured under a canonical conversion
|
|
(musical time within metric regions, wall-clock time within
|
|
proportional regions, declared per-region for aleatoric
|
|
regions).
|
|
\item Direction preference: forward $(0)$ before backward $(1)$,
|
|
unless the kind declares the opposite preference.
|
|
\item Object identifier (lexicographic on the typed identifier's
|
|
canonical byte form, ascending) as the final tie-break.
|
|
\end{enumerate}
|
|
|
|
If no candidate satisfies the kind's declared proximity bound, the
|
|
result is \texttt{Orphaned} (for user-content kinds such as
|
|
comments and analytical annotations) or \texttt{CascadeDeleted}
|
|
(for kinds whose existence requires their target, such as ties
|
|
and beams).
|
|
|
|
Numeric proximity bounds, direction preferences, and the
|
|
orphaned-versus-cascade choice are declared per kind in the
|
|
re-anchoring rule table below; they are part of the kind's
|
|
conformance contract and \MUSTNOT{} be reinterpreted by
|
|
implementations.
|
|
\end{requirement}
|
|
|
|
\subsection{The Re-Anchoring Rule Table}
|
|
|
|
The following table specifies, for each (referencing kind, referent
|
|
kind) pair, the re-anchoring action under canonical reduction. The
|
|
table is normative.
|
|
|
|
\begin{longtable}{p{3.0cm} p{2.6cm} p{2.4cm} p{4.4cm}}
|
|
\toprule
|
|
\textbf{Referencing} & \textbf{Referent} & \textbf{Action} &
|
|
\textbf{Parameters} \\
|
|
\midrule
|
|
\endhead
|
|
|
|
Slur & Endpoint event &
|
|
Re-anchor to the nearest surviving endpoint; cascade-delete only
|
|
when no endpoint survives &
|
|
a two-endpoint slur collapses onto its sole survivor (a
|
|
reference-clean degenerate form); proximity-aware re-targeting
|
|
is a deferred refinement \\
|
|
|
|
Slur & Interior event &
|
|
No action &
|
|
--- \\
|
|
|
|
Tie & Either endpoint &
|
|
Cascade-delete &
|
|
Tie's existence requires both endpoints. \\
|
|
|
|
Beam & Member event &
|
|
Truncate. Cascade-delete if fewer than two members remain &
|
|
proximity max: same voice \\
|
|
|
|
Tuplet & Member event &
|
|
Operation \MUST{} provide tuplet compensation
|
|
(Section~\ref{sec:semops:deleteevent}); otherwise the operation
|
|
conflicts at precondition check &
|
|
--- \\
|
|
|
|
Spanner & Anchor &
|
|
Re-anchor to the nearest surviving anchor; cascade-delete only
|
|
when no anchor survives &
|
|
as for slurs (surviving-endpoint collapse); per-spanner-kind
|
|
proximity bounds are a deferred refinement \\
|
|
|
|
Repeat structure & Anchor &
|
|
Re-anchor to the nearest surviving anchor; cascade-delete only
|
|
when no anchor survives &
|
|
as for spanners; the rule spans \emph{every} event-referencing
|
|
anchor site of the structure --- \texttt{start}/\texttt{end},
|
|
the kind's jump targets (\texttt{DaCapo.end\_target},
|
|
\texttt{DalSegno.segno}/\texttt{end\_target}), and each
|
|
volta's span. Among multiple surviving candidates ``nearest''
|
|
is currently the deterministic identifier-order minimum;
|
|
proximity-aware (four-key) selection is a deferred refinement,
|
|
as for spanners (ratified with the repeat-authoring pair,
|
|
schema-major-2 revision) \\
|
|
|
|
Marker & Anchor &
|
|
Re-anchor to nearest event in same staff instance &
|
|
proximity max: same staff instance; orphan on failure \\
|
|
|
|
Comment & Anchor &
|
|
Orphan &
|
|
User content never silently deleted. \\
|
|
|
|
Cue event & Source event &
|
|
Cascade-delete on any source deletion &
|
|
a multi-source cue cascades when \emph{any} source dies
|
|
(ratified Pass~12): losing any source breaks the cue's
|
|
quotation integrity; truncate-while-any-source-survives was
|
|
considered and rejected \\
|
|
|
|
Graphic gesture & Anchor event &
|
|
Re-anchor to nearest surviving event of same staff instance;
|
|
for Free anchoring, no action; for Range anchoring, truncate &
|
|
proximity max: same staff instance. Truncate (ratified
|
|
Pass~12): a dead event-anchored range endpoint moves to its
|
|
containing region's edge --- a start endpoint to the region
|
|
start, an end endpoint to the region end, zero offset \\
|
|
|
|
Trajectory event & Endpoint pitch &
|
|
Re-anchor: replace \texttt{EventPitch(PitchId)} with
|
|
\texttt{ExplicitPitch} capturing the tombstoned pitch's last
|
|
known value &
|
|
--- \\
|
|
|
|
Analytical annotation & Anchor &
|
|
Re-anchor to time range preserving original extent; orphan if
|
|
range cannot be reconstructed &
|
|
orphaning is the sanctioned outcome (ratified Pass~12) for a
|
|
wall-clock (region-relative) or indeterminate event span that
|
|
no stored \texttt{Range} anchor form can express without
|
|
region-origin resolution \\
|
|
|
|
Spelling attachment & Target pitch &
|
|
Transition to tombstoned-target state &
|
|
Attachment is preserved for diagnostic and undo purposes. \\
|
|
|
|
Decomposition attachment & Target event &
|
|
Transition to tombstoned-target state &
|
|
Attachment preserved for diagnostic and undo purposes. \\
|
|
|
|
Voice & Containing staff instance &
|
|
Cascade-delete &
|
|
--- \\
|
|
|
|
Event & Containing voice &
|
|
Cascade-delete &
|
|
--- \\
|
|
|
|
Region & Canvas &
|
|
Refuse (canvas is not deletable) &
|
|
--- \\
|
|
|
|
Part definition & Referenced staff &
|
|
Remove staff from part's staff list; orphan if part becomes
|
|
empty &
|
|
--- \\
|
|
|
|
\bottomrule
|
|
\end{longtable}
|
|
|
|
\begin{requirement}
|
|
\label{req:semops:synchronous-reanchoring}
|
|
Re-anchoring \MUST{} be performed as part of the same reduction
|
|
step that tombstones the referent. The resulting graph state
|
|
\MUST{} satisfy every invariant in
|
|
Section~\ref{sec:graph:invariants}. Re-anchoring actions \MUST{}
|
|
be recorded as \texttt{RepairRecord} entries in the triggering
|
|
operation's effect.
|
|
\end{requirement}
|
|
|
|
\section{Undo}
|
|
\label{sec:semops:undo}
|
|
|
|
Undo is not literal time travel: it is the authoring of a new
|
|
operation that compensates for a previously-committed transaction
|
|
against the current materialized state. This preserves the append-
|
|
only operation set and is correct under concurrent edits.
|
|
|
|
\subsection{UndoTransaction Payload}
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct UndoTransactionPayload {
|
|
/// The transaction being undone.
|
|
pub target: TransactionId,
|
|
|
|
/// Policy governing how to undo when the target's effects
|
|
/// have been partially superseded by later operations.
|
|
pub policy: UndoPolicy,
|
|
}
|
|
|
|
pub enum UndoPolicy {
|
|
/// Compute the inverse semantic operations against the current
|
|
/// materialized state. If any cannot apply cleanly (because the
|
|
/// target objects have been tombstoned, fields have been
|
|
/// further modified, etc.), the entire undo conflicts.
|
|
StrictInverse,
|
|
|
|
/// Undo what remains valid; record conflicts or no-ops for the
|
|
/// rest. The undo operation succeeds, but partial.
|
|
BestEffort,
|
|
|
|
/// Also undo operations that are causally or semantically
|
|
/// dependent on the target transaction. Dependency is computed
|
|
/// from causal contexts and explicit dependency links;
|
|
/// implementations MUST NOT infer broad musical dependence
|
|
/// heuristically.
|
|
Cascade,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsection{Undo Semantics}
|
|
|
|
\begin{requirement}
|
|
\label{req:semops:compensating-undo}
|
|
An \texttt{UndoTransaction} operation is committed to the operation
|
|
set like any other operation; it does not modify history. Its
|
|
reduction computes a compensating edit against the materialized
|
|
state at the point of its canonical position:
|
|
|
|
\begin{itemize}
|
|
\item For \texttt{StrictInverse}: the inverse of each primitive
|
|
in the target transaction is computed against the current
|
|
working state. If every inverse applies cleanly, the undo
|
|
transaction reduces to \texttt{Applied} with all compensating
|
|
effects recorded. If any inverse cannot apply (target
|
|
tombstoned, field modified, etc.), the undo transaction
|
|
reduces to \texttt{Conflicted} with a conflict record.
|
|
\item For \texttt{BestEffort}: inverses that apply cleanly are
|
|
applied; inverses that cannot are recorded as \texttt{NoOp}
|
|
effects with their reasons. The undo transaction reduces to
|
|
\texttt{AppliedWithRepair}.
|
|
\item For \texttt{Cascade}: the dependency set is computed first
|
|
(causal closure plus explicit dependency links). The
|
|
transaction's inverses, plus inverses of dependents in
|
|
reverse order, are applied per the chosen sub-policy
|
|
(typically \texttt{StrictInverse}).
|
|
\end{itemize}
|
|
|
|
Equality of the resulting graph state to the pre-target state is
|
|
defined as \emph{content equivalence}: every live object's fields,
|
|
every attachment, every cross-cutting structure matches. Operation
|
|
identifiers, stamps, undo stacks, caches, and storage layout are
|
|
\emph{not} required to match.
|
|
\end{requirement}
|
|
|
|
\begin{rationale}
|
|
Defining undo as a forward operation eliminates the contradiction
|
|
in earlier passes where undo had to ``restore previous state''
|
|
while remote edits had been integrated. The compensating-edit
|
|
approach respects the operation set's append-only nature and
|
|
generalizes cleanly to concurrent collaboration: undoing a
|
|
transaction whose effects have been entwined with remote edits is
|
|
expressible as a deterministic compensating operation, possibly
|
|
partial.
|
|
\end{rationale}
|
|
|
|
\section{LWW Discipline}
|
|
\label{sec:semops:lww}
|
|
|
|
Last-writer-wins reduction is permitted only for explicitly-marked
|
|
advisory scalar fields. Structural musical objects are governed by
|
|
their kind's operation-specific reduction rules.
|
|
|
|
\subsection{The LwwAdvisory Marker}
|
|
|
|
Fields eligible for LWW reduction are explicitly marked as
|
|
\texttt{LwwAdvisory}. No unmarked field may use LWW semantics.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
/// Marker attribute, applied to fields whose concurrent modifications
|
|
/// are resolved by last-writer-wins under the canonical reduction
|
|
/// order.
|
|
#[attribute_only]
|
|
pub struct LwwAdvisory;
|
|
|
|
// Example field declarations:
|
|
//
|
|
// #[lww_advisory]
|
|
// pub title: Option<String>
|
|
//
|
|
// #[lww_advisory]
|
|
// pub composer: Option<String>
|
|
\end{lstlisting}
|
|
|
|
\subsection{Eligible Fields}
|
|
|
|
\begin{requirement}
|
|
\label{req:semops:lww-advisory-fields}
|
|
The following classes of field \MAY{} be marked
|
|
\texttt{LwwAdvisory} and reduced by last-writer-wins:
|
|
|
|
\begin{itemize}
|
|
\item Score metadata: title, subtitle, composer, lyricist,
|
|
copyright, work number, opus, dedication, free-form
|
|
annotations.
|
|
\item View preferences: which layers are active, which parts
|
|
are shown, viewport, zoom level (per-replica state in
|
|
addition; only the explicitly shared portions are LWW).
|
|
\item Layout hint preferences: default page size, default
|
|
margins, default staff spacing recommendations.
|
|
\item Page-break and system-break preferences on layout-semantic
|
|
structures (the user's stated preference, distinct from the
|
|
engraver's decision).
|
|
\item Optional advisory display hints on cross-cutting
|
|
structures: line color preference, line-style preference,
|
|
label-position preference. Endpoints, target identities,
|
|
semantic class, and attachment ownership \MUSTNOT{} be
|
|
LWW.
|
|
\end{itemize}
|
|
|
|
All other fields, including every field carrying musical
|
|
structure, identity, ownership, or semantics, \MUST{} use
|
|
operation-specific reduction rules defined per operation kind.
|
|
Implementations \MUSTNOT{} default to LWW for unmarked fields.
|
|
\end{requirement}
|
|
|
|
\begin{rationale}
|
|
LWW is acceptable for fields where the worst case of arbitrary
|
|
deterministic resolution is benign: two authors editing the
|
|
composer's name concurrently is a metadata collision, not a
|
|
musical loss. LWW is catastrophic for fields where wholesale
|
|
replacement destroys musical meaning: two authors editing a
|
|
chord's pitch list concurrently must not produce ``one author's
|
|
chord wins.'' The marker discipline makes the boundary explicit
|
|
and auditable.
|
|
\end{rationale}
|
|
|
|
\section{Validation Modes}
|
|
\label{sec:semops:validation}
|
|
|
|
Operations are subject to precondition checking. Two modes
|
|
distinguish authoring contexts:
|
|
|
|
\begin{description}
|
|
\item[Authoring mode.] Interactive edits. All preconditions are
|
|
enforced: invariant preconditions (which preserve graph
|
|
invariants) and advisory preconditions (which encode user-intent
|
|
constraints, range checks, and style policy).
|
|
\item[Replay mode.] Replaying historical operations or applying
|
|
remote operations under reduction. Only invariant preconditions
|
|
are enforced; advisory preconditions are skipped, since they
|
|
represent the authoring replica's local policy at the moment of
|
|
authoring, not invariants of the canonical state.
|
|
\end{description}
|
|
|
|
\begin{requirement}
|
|
\label{req:semops:precondition-validation-modes}
|
|
Every operation specification \MUST{} classify each precondition
|
|
as invariant or advisory. Invariant preconditions \MUST{} hold in
|
|
all modes; advisory preconditions \MUST{} hold in authoring mode
|
|
and \MAY{} fail silently in replay mode.
|
|
|
|
Implementations \MUSTNOT{} reclassify a precondition without a
|
|
corresponding revision to this specification.
|
|
\end{requirement}
|
|
|
|
\section{Per-Kind Reduction Summary}
|
|
\label{sec:semops:per-kind-summary}
|
|
|
|
The following table summarizes the reduction discipline for each
|
|
representative operation kind. Detailed specifications appear in
|
|
the next section.
|
|
|
|
\begin{longtable}{p{3.4cm} p{4.0cm} p{5.0cm}}
|
|
\toprule
|
|
\textbf{Operation} & \textbf{Reduction Rule} &
|
|
\textbf{Concurrency Outcomes} \\
|
|
\midrule
|
|
\endhead
|
|
|
|
InsertEvent &
|
|
Position-keyed insertion. Concurrent same-position inserts whose
|
|
durations overlap promote the lexicographically-greater operation
|
|
to a system-promoted voice
|
|
(Section~\ref{sec:graph:promoted-voices}) &
|
|
Voice promotion; non-overlap invariant preserved by allocation. \\
|
|
|
|
DeleteEvent &
|
|
Tombstone the event and contained pitch identifiers. Apply
|
|
tuplet compensation from the operation. Re-anchor referencing
|
|
structures &
|
|
Delete-wins against concurrent field modifications on the same
|
|
event; concurrent same-target deletes are idempotent. \\
|
|
|
|
RespellPitch &
|
|
Field overwrite by canonical reduction order; the later operation
|
|
wins. Earlier respellings of the same pitch are preserved in the
|
|
conflict record if they differ &
|
|
Conflict record records the losing spelling for user inspection. \\
|
|
|
|
Transpose &
|
|
Apply in canonical reduction order. Pitch identifiers preserved.
|
|
Operations do not commute &
|
|
Reduction order decides; conflict record entries for non-trivial
|
|
intervals only when explicit dependency rules require. \\
|
|
|
|
CreateCrossCutting &
|
|
Set union. Concurrent additions of distinct cross-cutting
|
|
structures all succeed &
|
|
Tombstoned endpoints cause re-anchoring or cascade-delete per
|
|
kind. \\
|
|
|
|
ChangeRegionTimeModel &
|
|
Structural migration. Contained events whose coordinate kinds
|
|
become incompatible cause the operation to conflict &
|
|
Concurrent contained edits authored before the migration may
|
|
conflict; explicit rebase rules per migration kind. \\
|
|
|
|
SetUserSystemBreak &
|
|
LwwAdvisory on per-location break preference &
|
|
Last-writer-wins on the break state at the same anchor. \\
|
|
|
|
\bottomrule
|
|
\end{longtable}
|
|
|
|
\section{Representative Operations}
|
|
\label{sec:semops:examples}
|
|
|
|
The remainder of the chapter specifies a representative selection of
|
|
operations, one from each major category. The full catalog is
|
|
delivered as a separate conformance specification.
|
|
|
|
These examples illustrate the contract that every operation in the
|
|
catalog \MUST{} satisfy. Each operation specification includes its
|
|
reduction rule under the canonical reduction model
|
|
(Section~\ref{sec:semops:reduction}).
|
|
|
|
\subsection{InsertEvent}
|
|
\label{sec:semops:insertevent}
|
|
|
|
\textbf{Category:} Event operations.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct InsertEventOp {
|
|
/// The voice the event is inserted into.
|
|
pub voice: VoiceId,
|
|
|
|
/// Position within the voice.
|
|
pub position: EventPosition,
|
|
|
|
/// The event to insert. Its EventId and any contained PitchIds
|
|
/// are minted at operation-authoring time as a single causal
|
|
/// step. The event's voice field must match the target voice.
|
|
pub event: Event,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\textbf{Invariant preconditions:}
|
|
\begin{itemize}
|
|
\item The target voice exists.
|
|
\item The event's \texttt{voice} field equals the target voice's id.
|
|
\item The event's \texttt{id} is not present elsewhere in the arena.
|
|
\item Every \texttt{PitchId} contained in the event (in an
|
|
\texttt{IdentifiedPitch}, a trajectory endpoint, or a stepwise
|
|
trajectory shape) is unique within the score's pitch-identity
|
|
index.
|
|
\item The position is within the enclosing region's time extent.
|
|
\item The event's position and duration variants agree with the
|
|
enclosing region's time model.
|
|
\item Inserting at the position does not produce an event overlap
|
|
within the voice. Concurrent inserts that would otherwise produce
|
|
overlap are resolved per the reduction rule below.
|
|
\end{itemize}
|
|
|
|
\textbf{Advisory preconditions (authoring mode only):}
|
|
\begin{itemize}
|
|
\item For pitched events, every pitch is within the instrument's
|
|
declared range, if any.
|
|
\item The event's duration does not extend past a region boundary
|
|
in a way that would require splitting.
|
|
\end{itemize}
|
|
|
|
\textbf{Effect:} The event is added to the arena; the voice's event
|
|
list is updated to include the event in sorted position. Every
|
|
contained \texttt{PitchId} is registered as live in the
|
|
pitch-identity index. Any attachments declared on the event
|
|
(spellings, decompositions) are added to their respective indexes.
|
|
|
|
\textbf{Postconditions:}
|
|
\begin{itemize}
|
|
\item The voice's events list remains sorted and non-overlapping.
|
|
\item The event arena's lookup index resolves the new event.
|
|
\item The event time index includes the new event for its region
|
|
and voice.
|
|
\item Every contained pitch is resolvable through the
|
|
pitch-identity index.
|
|
\end{itemize}
|
|
|
|
\textbf{Inverse:} \texttt{DeleteEvent} of the inserted event, with
|
|
captured state equal to the event itself plus any attachments added.
|
|
|
|
\textbf{Reduction rule:} Position-keyed insertion with voice promotion
|
|
on collision. Concurrent inserts at the same position whose durations
|
|
would overlap resolve as follows: the operation with the
|
|
lexicographically-greater \texttt{OperationId} is placed in a newly
|
|
allocated voice on the same staff instance, with
|
|
\texttt{VoiceOrigin::SystemPromoted} (Section~\ref{sec:graph:promoted-voices}).
|
|
The new voice's \texttt{VoiceId} is derived deterministically from the
|
|
staff instance, the original target voice, and the two operations'
|
|
identifiers, so all replicas independently allocate the same promoted
|
|
voice. The non-overlap invariant is preserved by allocation rather
|
|
than by rejection. Subsequent user normalization operations can merge
|
|
the promoted voice back into the target, rename it, or accept it as
|
|
an intentional second voice.
|
|
|
|
\subsection{DeleteEvent}
|
|
\label{sec:semops:deleteevent}
|
|
|
|
\textbf{Category:} Event operations.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct DeleteEventOp {
|
|
pub event: EventId,
|
|
|
|
/// Compensation for tuplet membership, if any. Required when the
|
|
/// target event is a member of one or more tuplets.
|
|
pub tuplet_compensation: TupletCompensation,
|
|
}
|
|
|
|
pub enum TupletCompensation {
|
|
/// Target is not in any tuplet; no compensation required.
|
|
NotInTuplet,
|
|
|
|
/// Replace the deleted event with a rest of the same duration.
|
|
/// The rest receives a freshly-minted EventId; the deleted
|
|
/// event's EventId and PitchIds are tombstoned.
|
|
ReplaceWithRest { new_rest: Rest },
|
|
|
|
/// Rewrite the enclosing tuplet(s) to remain structurally
|
|
/// consistent. The v1 payload names the affected tuplets by
|
|
/// id only; it does not carry rewritten values (see the
|
|
/// precondition below).
|
|
RewriteTuplets { tuplets: Vec<TupletId> },
|
|
|
|
/// Cascade-delete the entire tuplet group(s) containing the
|
|
/// target event. The listed tuplet ids are cascaded.
|
|
CascadeDeleteTuplets { tuplets: Vec<TupletId> },
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\textbf{Invariant preconditions:}
|
|
\begin{itemize}
|
|
\item The target event exists in the arena (i.e., is live, not
|
|
tombstoned).
|
|
\item If the target event belongs to one or more tuplets, the
|
|
\texttt{tuplet\_compensation} field \MUST{} be a variant other
|
|
than \texttt{NotInTuplet}, and the chosen compensation \MUST{}
|
|
preserve the tuplet structural consistency invariant
|
|
(Section~\ref{sec:time:tuplets}) after the delete is applied.
|
|
\item If the target event does not belong to any tuplet, the
|
|
\texttt{tuplet\_compensation} field \MUST{} be
|
|
\texttt{NotInTuplet}.
|
|
\item For \texttt{ReplaceWithRest}: the replacement rest's
|
|
duration \MUST{} equal the deleted event's duration; the rest's
|
|
voice and position \MUST{} match the deleted event.
|
|
\item For \texttt{RewriteTuplets}: the v1 payload carries only
|
|
tuplet ids, not the rewritten ratio and member values a reducer
|
|
would need to demonstrate that the resulting structure preserves
|
|
the tuplet consistency invariant. Graph-aware reduction \MUST{}
|
|
therefore refuse the variant as an ill-formed compensation (a
|
|
precondition no-op) rather than fabricate rewritten values. A
|
|
future payload revision carrying full rewrites (per-tuplet ratio
|
|
and members, each of whose member-duration sums \MUST{} match
|
|
the tuplet's structurally-required total) reopens the variant;
|
|
until then \texttt{ReplaceWithRest} and
|
|
\texttt{CascadeDeleteTuplets} are the applicable compensations.
|
|
\item For \texttt{CascadeDeleteTuplets}: every tuplet listed
|
|
\MUST{} contain the target event as a member.
|
|
\end{itemize}
|
|
|
|
\textbf{Effect:} The event is removed from the arena and from its
|
|
voice's event list. The event's \texttt{EventId} and every contained
|
|
\texttt{PitchId} are tombstoned in their respective identity
|
|
indexes: they remain resolvable to ``deleted'' markers and \MUSTNOT{}
|
|
be reused for new objects. The chosen tuplet compensation is applied
|
|
in the same delta: a replacement rest is inserted or the tuplets are
|
|
cascade-deleted as specified (a \texttt{RewriteTuplets} compensation
|
|
is refused at precondition check; see above).
|
|
Cross-cutting structures referencing the event or its pitches are
|
|
re-anchored per the rule table (Section~\ref{sec:semops:reanchor}).
|
|
Live attachments targeting the event or its pitches transition to
|
|
tombstoned-target state and are preserved for replay, undo, and
|
|
historical reference; they are not silently deleted.
|
|
|
|
\textbf{Postconditions:}
|
|
\begin{itemize}
|
|
\item The event is no longer live in the arena.
|
|
\item Every cross-cutting structure that referenced the event has
|
|
been re-anchored or cascade-deleted.
|
|
\item The event's identifier and every contained pitch identifier
|
|
are tombstoned and resolvable only to a deletion record.
|
|
\item The tuplet structural consistency invariant holds for every
|
|
tuplet that was previously consistent.
|
|
\end{itemize}
|
|
|
|
\textbf{Inverse:} \texttt{InsertEvent} reconstructing the event from
|
|
captured state, re-promoting the tombstoned identifiers to live,
|
|
reversing the tuplet compensation (delete the replacement rest;
|
|
restore the original tuplet structure; un-cascade tuplet deletions),
|
|
and re-creating any cross-cutting structures that were
|
|
cascade-deleted as a consequence. Re-promotion preserves all stable
|
|
identifiers; new identifiers \MUSTNOT{} be minted by the inverse.
|
|
|
|
\textbf{Reduction rule:} Set intersection with delete-wins. Concurrent
|
|
deletion and modification of the same event resolves to deletion;
|
|
the modification is dropped. Concurrent re-anchoring on the same
|
|
referent resolves by the deterministic total ordering specified in
|
|
Section~\ref{sec:semops:reanchor}.
|
|
|
|
\begin{rationale}
|
|
Requiring explicit tuplet compensation prevents primitive deletion
|
|
from leaving the score in a state that violates the tuplet
|
|
structural consistency invariant. The compensation is part of the
|
|
same delta as the deletion, so concurrent observers see either
|
|
the pre-delete state or a fully consistent post-delete state, never
|
|
an intermediate broken state.
|
|
\end{rationale}
|
|
|
|
\subsection{CreateCrossCutting (Slur)}
|
|
|
|
\textbf{Category:} Cross-cutting structure operations.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct CreateCrossCuttingOp {
|
|
pub structure: CrossCuttingStructure,
|
|
}
|
|
|
|
pub enum CrossCuttingStructure {
|
|
Slur(Slur),
|
|
Tie(Tie),
|
|
Beam(Beam),
|
|
Spanner(Spanner),
|
|
Marker(Marker),
|
|
Repeat(RepeatStructure),
|
|
Analytical(AnalyticalAnnotation),
|
|
Comment(Comment),
|
|
GraphicGesture(GraphicGesture),
|
|
}
|
|
\end{lstlisting}
|
|
|
|
The enum above is the data model's full cross-cutting family; the
|
|
\emph{wire} vocabulary the cross-cutting operations admit at this
|
|
revision (\texttt{CrossCuttingValue}, Binary Format companion) is the
|
|
four-kind subset \texttt{Tie}/\texttt{Slur}/\texttt{Beam}/%
|
|
\texttt{Spanner}. A \texttt{RepeatStructure} is authored by the
|
|
dedicated \texttt{CreateRepeatStructure}/%
|
|
\texttt{DeleteRepeatStructure} pair (Operation Catalog
|
|
\sectionsc{Repeat Structures}; ratified schema-major-2 revision), not
|
|
through \texttt{CreateCrossCutting}; the remaining variants await
|
|
their own authoring primitives.
|
|
|
|
\textbf{Invariant preconditions} (Slur case):
|
|
\begin{itemize}
|
|
\item The slur's id is unique within the cross-cutting registry.
|
|
\item The slur's start and end events both exist.
|
|
\item The start and end events are in the same voice.
|
|
\item The start event precedes (or equals) the end event in voice order.
|
|
\end{itemize}
|
|
|
|
\textbf{Advisory preconditions} (Slur case):
|
|
\begin{itemize}
|
|
\item The slur does not span a region boundary, unless explicitly
|
|
permitted by region configuration.
|
|
\end{itemize}
|
|
|
|
\textbf{Effect:} The slur is added to the cross-cutting registry's
|
|
slur list. The cross-cutting reference index is updated.
|
|
|
|
\textbf{Postconditions:}
|
|
\begin{itemize}
|
|
\item The slur is queryable by id and by referenced events.
|
|
\end{itemize}
|
|
|
|
\textbf{Inverse:} \texttt{DeleteCrossCutting} of the created slur.
|
|
|
|
\textbf{Reduction rule:} Set union. Concurrent creation of multiple
|
|
slurs over overlapping ranges is valid: both slurs exist.
|
|
|
|
\subsection{RespellPitch}
|
|
|
|
\textbf{Category:} Pitch and tuning operations.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct RespellPitchOp {
|
|
pub pitch: PitchId,
|
|
pub new_spelling: PitchSpelling,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\textbf{Invariant preconditions:}
|
|
\begin{itemize}
|
|
\item The target pitch exists in some event in the arena (i.e.,
|
|
is live, not tombstoned).
|
|
\item The new spelling is valid in the active pitch space: every
|
|
accidental in the spelling's \texttt{accidentals} stack belongs
|
|
to the active accidental registry, the nominal exists in the
|
|
space's nominal registry, and the octave is in range.
|
|
\item Duplicate accidentals in the spelling's stack are absent
|
|
unless the accidental's registered combination semantics
|
|
explicitly permit repetition.
|
|
\item The new spelling represents the same scale position as the
|
|
pitch's current spelling, or is enharmonically equivalent under
|
|
the active tuning system. (Respelling does not change sounding
|
|
pitch.)
|
|
\end{itemize}
|
|
|
|
\textbf{Effect:} A spelling attachment with source
|
|
\texttt{UserChosen} is created (or replaces an existing
|
|
\texttt{UserChosen} attachment) targeting the pitch. The spelling
|
|
attachment index is updated. The pitch's \texttt{PitchId} is
|
|
unaffected.
|
|
|
|
\textbf{Postconditions:}
|
|
\begin{itemize}
|
|
\item Spelling resolution for the pitch yields the new spelling,
|
|
subject to the precedence configuration.
|
|
\item The pitch's scale position is unchanged.
|
|
\item The pitch's acoustic realization is unchanged.
|
|
\item The pitch's \texttt{PitchId} is unchanged.
|
|
\end{itemize}
|
|
|
|
\textbf{Inverse:} A \texttt{RespellPitch} restoring the prior
|
|
spelling (captured), or a \texttt{RemoveAttachment} if no prior
|
|
\texttt{UserChosen} attachment existed.
|
|
|
|
\textbf{Reduction rule:} Field overwrite by canonical reduction
|
|
order. Concurrent respellings of the same live pitch resolve to the
|
|
operation later in canonical order; the materialized spelling is
|
|
that operation's choice. The losing respelling is recorded in a
|
|
\texttt{ConflictRecord} of kind
|
|
\texttt{StructuralFieldCollision} if the two respellings represent
|
|
different spellings (i.e., differ in nominal, accidental stack, or
|
|
octave). The per-operation effect assignment follows the general
|
|
field-collision rule
|
|
(Requirement~\ref{req:semops:field-collision-effect}): the later
|
|
operation --- the winner, which materializes --- reads
|
|
\texttt{Conflicted}, and the earlier operation retains
|
|
\texttt{Applied}. Identical concurrent respellings reduce
|
|
idempotently: the later operation produces
|
|
\texttt{NoOp\{reason: AlreadyApplied\}} with no conflict recorded.
|
|
|
|
\subsection{ChangeRegionTimeModel}
|
|
|
|
\textbf{Category:} Region operations.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct ChangeRegionTimeModelOp {
|
|
pub region: RegionId,
|
|
pub new_time_model: RegionTimeModel,
|
|
pub position_remapping: PositionRemapping,
|
|
}
|
|
|
|
pub enum PositionRemapping {
|
|
/// Preserve absolute time positions where possible. Events that
|
|
/// cannot be remapped (e.g., metric events under a proportional
|
|
/// model with no implied tempo) are converted by the supplied
|
|
/// conversion function.
|
|
PreserveTime { fallback: PositionConverter },
|
|
|
|
/// Discard event positions and reinsert by user-provided
|
|
/// position mapping.
|
|
Reassign(HashMap<EventId, EventPosition>),
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\textbf{Invariant preconditions:}
|
|
\begin{itemize}
|
|
\item The target region exists.
|
|
\item The new time model is internally consistent.
|
|
\item The position remapping covers every event in the region, or
|
|
the remapping is \texttt{PreserveTime} with a defined fallback.
|
|
\end{itemize}
|
|
|
|
\textbf{Effect:} The region's \texttt{time\_model} is replaced. Every
|
|
event in the region has its \texttt{position} field updated according
|
|
to the remapping. The tempo map's applicability is recomputed.
|
|
|
|
\textbf{Postconditions:}
|
|
\begin{itemize}
|
|
\item Every event in the region has a valid position in the new
|
|
time model.
|
|
\item The region's invariants (events sorted, non-overlapping,
|
|
within time extent) hold.
|
|
\end{itemize}
|
|
|
|
\textbf{Inverse:} \texttt{ChangeRegionTimeModel} restoring the prior
|
|
time model, with a remapping captured from the pre-state.
|
|
|
|
\textbf{Reduction rule:} Structural migration. The operation is
|
|
applied at its canonical position. Contained events whose coordinate
|
|
kinds become incompatible with the new time model after migration
|
|
cause the operation to reduce to \texttt{Conflicted} with a
|
|
\texttt{ConflictKind::TimeModelMigrationFailure} record listing the
|
|
incompatible events; the migration does not apply. Concurrent
|
|
operations on the same region that author events in the prior time
|
|
model and reduce after the migration may themselves reduce to
|
|
\texttt{Conflicted} if their coordinate kinds disagree with the new
|
|
model. Concurrent same-target migrations resolve by canonical order;
|
|
the earlier operation applies and the later operation reduces to
|
|
\texttt{Conflicted} with a structural-field-collision conflict (the
|
|
two migrations are not generally commutable).
|
|
|
|
\begin{rationale}
|
|
Time-model migration is a structural rebase, not a field set.
|
|
Treating it as LWW would silently destroy contained musical
|
|
content whose coordinate kinds depend on the prior time model.
|
|
Conflicted reductions force the user to address the incompatibility
|
|
explicitly.
|
|
\end{rationale}
|
|
|
|
\subsection{Transpose}
|
|
|
|
\textbf{Category:} Pitch and tuning operations. This is a compound
|
|
operation, defined as a sequence of primitives, listed here as an
|
|
illustration of compounds.
|
|
|
|
\textbf{User-facing parameters:}
|
|
\begin{itemize}
|
|
\item Target scope (events, voice, staff, region, or selection).
|
|
\item Interval (diatonic, chromatic, or compound).
|
|
\item Spelling policy (preserve user spellings, respell to fit a
|
|
target key, or apply a custom rule).
|
|
\item Tuning policy (preserve, recompute under new tuning, or
|
|
apply offsets).
|
|
\end{itemize}
|
|
|
|
\textbf{Primitive decomposition:} A transpose operation expands into:
|
|
|
|
\begin{enumerate}
|
|
\item For each target pitch, a \texttt{ReplaceEvent} updating the
|
|
inner \texttt{Pitch} values while preserving every
|
|
\texttt{IdentifiedPitch}'s \texttt{PitchId}. Transposition does
|
|
not mint new pitch identifiers; it rewrites the contents of
|
|
existing identified pitches.
|
|
\item For each pitch whose spelling is recomputed, a
|
|
\texttt{RespellPitch} targeting the unchanged \texttt{PitchId}
|
|
with source \texttt{Propagated}.
|
|
\item Where the transposition crosses a clef boundary, optional
|
|
\texttt{ChangeStaffConfiguration} operations to insert clef
|
|
changes.
|
|
\end{enumerate}
|
|
|
|
The compound operation is recorded as a single transaction. Each
|
|
primitive within the transaction has its own re-anchoring and
|
|
reduction semantics. Identifier preservation ensures that
|
|
attachments, analytical layers, and cross-cutting references
|
|
targeting the transposed pitches continue to resolve correctly
|
|
after the operation.
|
|
|
|
\subsection{SetUserSystemBreak}
|
|
|
|
\textbf{Category:} Layout-semantic operations.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct SetUserSystemBreakOp {
|
|
pub anchor: TimeAnchor,
|
|
pub region: RegionId,
|
|
pub present: bool,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\textbf{Invariant preconditions:}
|
|
\begin{itemize}
|
|
\item The region exists and is staff-based.
|
|
\item The anchor resolves within the region.
|
|
\end{itemize}
|
|
|
|
\textbf{Effect:} The region's \texttt{user\_system\_breaks} list is
|
|
updated to include or exclude the anchor. Layout caches that depend
|
|
on system breaks are invalidated.
|
|
|
|
\textbf{Postconditions:}
|
|
\begin{itemize}
|
|
\item The region's user system breaks list reflects the change.
|
|
\item Layout output, if produced subsequently, accounts for the
|
|
change subject to the engraver's override rules
|
|
(Chapter~\ref{ch:layout-ir}).
|
|
\end{itemize}
|
|
|
|
\textbf{Inverse:} \texttt{SetUserSystemBreak} with the opposite
|
|
\texttt{present} value.
|
|
|
|
\textbf{Reduction rule:} LwwAdvisory on per-anchor break preference.
|
|
Two concurrent operations setting the same break to \texttt{true}
|
|
reduce idempotently. Concurrent set-true and set-false on the same
|
|
anchor resolve by canonical reduction order; the later operation's
|
|
preference is materialized. No conflict record is produced; the
|
|
break preference is an advisory field eligible for LWW reduction
|
|
(Section~\ref{sec:semops:lww}).
|
|
|
|
\section{Operation Catalog Conformance}
|
|
\label{sec:semops:catalog}
|
|
|
|
This chapter specifies the framework. The full catalog of operations
|
|
is delivered as a separate conformance specification that extends
|
|
this chapter.
|
|
|
|
\begin{requirement}
|
|
\label{req:semops:operation-catalog-coverage}
|
|
A conforming implementation \MUST{} provide every operation
|
|
enumerated in the conformance catalog. Each operation \MUST{}
|
|
satisfy the framework requirements stated in this chapter:
|
|
|
|
\begin{itemize}
|
|
\item Declared preconditions classified as invariant or advisory.
|
|
\item Specified effect on the score graph.
|
|
\item Specified postconditions, including which invariants are
|
|
preserved.
|
|
\item A defined inverse operation.
|
|
\item A declared reduction rule under the canonical reduction
|
|
model (Section~\ref{sec:semops:reduction}).
|
|
\item Specified re-anchoring behavior for any deletions or
|
|
moves it triggers.
|
|
\end{itemize}
|
|
\end{requirement}
|
|
|
|
\begin{openquestion}
|
|
The conformance catalog is presently under development as a separate
|
|
document. It is expected to comprise approximately 60--80 primitive
|
|
operations and an open-ended set of compound operations. The catalog
|
|
is normative once published; this specification is non-final until
|
|
the catalog is delivered.
|
|
\end{openquestion}
|
|
|
|
\section{Forward References}
|
|
|
|
\begin{itemize}
|
|
\item Operation envelope storage on disk (envelope block layout,
|
|
causal-summary metadata, the operation index, snapshot
|
|
canonical-base semantics) is specified in
|
|
Chapter~\ref{ch:format}.
|
|
\item Conflict-resolution algorithms (how a
|
|
\texttt{ResolveConflict} operation transitions a conflict's
|
|
state, and the full conflict-record contract) are specified
|
|
in this chapter (Section~\ref{sec:semops:conflicts}) and in
|
|
the Operation Catalog companion
|
|
(Appendix~\ref{app:deferred}).
|
|
\item The interaction between layout-semantic operations (system
|
|
breaks, page breaks, engraving overrides) and the layout pipeline
|
|
is specified in Chapter~\ref{ch:layout-ir}.
|
|
\item The conformance catalog of all operations is a separate
|
|
deliverable that extends this chapter.
|
|
\end{itemize}
|
|
|
|
% ===========================================================================
|
|
\chapter{Layout Intermediate Representation}
|
|
\label{ch:layout-ir}
|
|
|
|
This chapter specifies the layout intermediate representation (Layout
|
|
IR): the data structures and pipeline that transform the score graph
|
|
into a typeset visual layout. The IR sits between the score graph and
|
|
two downstream consumers: the constraint solver
|
|
(Chapter~\ref{ch:solver}), which resolves spacing and positioning,
|
|
and the renderer (specified outside this document), which produces
|
|
final visual output (pixels, PDF, SVG).
|
|
|
|
The IR is a pipeline of distinct stages, each with its own type and
|
|
each with a well-defined contract for the next. The pipeline is the
|
|
spine of the engraving system; the engraving algorithms themselves
|
|
(beam grouping, slur curvature, accidental ordering, casting-off) are
|
|
the consumers and producers of stages and are specified outside this
|
|
document as engraving-algorithm specifications layered atop this IR.
|
|
|
|
\section{Design Principles}
|
|
\label{sec:layoutir:principles}
|
|
|
|
\begin{description}
|
|
\item[Pipeline of stages.] The transformation from score graph to
|
|
visual output is a pipeline of four IR stages:
|
|
\texttt{LogicalLayoutIR} (structural projection without spatial
|
|
information), \texttt{ConstrainedLayoutIR} (with constraint inputs
|
|
but unresolved positions), \texttt{ResolvedLayoutIR} (with all
|
|
positions resolved), and \texttt{RenderIR} (renderer-bound
|
|
primitives, defined only at the interface level here). Each stage
|
|
has its own type. Compilation between stages is unidirectional.
|
|
\item[Shared provenance.] Every IR object at every stage carries a
|
|
reference back to the score graph object that originated it. This
|
|
enables selection, editing back-references, error reporting, and
|
|
incremental layout invalidation.
|
|
\item[Engraving decisions are explicit.] When the engraver makes a
|
|
decision (stem direction, accidental ordering, beam consolidation),
|
|
the decision is recorded in the IR. The decision can be inspected,
|
|
overridden, and traced.
|
|
\item[Overrides exist in both worlds.] User-set engraving overrides
|
|
are authoritative in the score graph and projected into the IR
|
|
during the logical stage. The IR may also generate transient
|
|
overrides for downstream stages.
|
|
\item[Spring-based spacing.] Time-to-space projection produces
|
|
elastic spring parameters per time slot. The constraint solver
|
|
resolves springs system-wide. This applies to both horizontal
|
|
and vertical layout.
|
|
\item[Staff-space units, f32 precision.] Spatial coordinates within
|
|
the IR are expressed in staff spaces using single-precision
|
|
floating point. Conversion to absolute units (points, millimeters)
|
|
occurs only at the render boundary.
|
|
\item[Glyph metrics live elsewhere.] The IR references glyphs by
|
|
identifier and queries metrics from a font catalog. Metrics are
|
|
not embedded in IR objects.
|
|
\item[Incremental layout, fine-grained.] The IR supports incremental
|
|
re-engraving via per-object dependency tracking on score graph
|
|
objects. Edits invalidate only the layout objects whose
|
|
dependencies changed.
|
|
\item[Uniform region containers.] Staff-based, free-graphic, and
|
|
hybrid regions produce structurally identical \texttt{LayoutRegion}
|
|
containers with differing time projections and content kinds.
|
|
\end{description}
|
|
|
|
\section{Spatial Primitives}
|
|
\label{sec:layoutir:spatial}
|
|
|
|
\subsection{Units}
|
|
|
|
The IR uses two unit types:
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
/// Staff space: the fundamental unit of music engraving.
|
|
/// One staff space equals the distance between adjacent staff lines.
|
|
#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)]
|
|
pub struct StaffSpace(pub f32);
|
|
|
|
/// Point: 1/72 inch. Used at the render boundary for absolute
|
|
/// dimensions (page sizes, font sizes, output coordinates).
|
|
#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)]
|
|
pub struct Point(pub f32);
|
|
|
|
/// Scaling context for converting between staff spaces and points.
|
|
pub struct ScaleContext {
|
|
/// Points per staff space. Depends on the staff size chosen for
|
|
/// the score; typical values are 4--10 points per staff space.
|
|
pub points_per_staff_space: f32,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\begin{requirement}
|
|
\label{req:layoutir:staff-space-coordinates}
|
|
Spatial coordinates within the IR (in stages
|
|
\texttt{LogicalLayoutIR}, \texttt{ConstrainedLayoutIR}, and
|
|
\texttt{ResolvedLayoutIR}) \MUST{} be expressed in staff spaces.
|
|
Conversion to points occurs at the \texttt{RenderIR} boundary.
|
|
|
|
Single-precision floating point (\texttt{f32}) \MUST{} be used for
|
|
IR coordinates. Engraving precision requirements are bounded by
|
|
printer resolution (typically 600--2400 DPI); single precision
|
|
provides over 7 significant figures, comfortably exceeding any
|
|
engraving precision requirement.
|
|
\end{requirement}
|
|
|
|
\subsection{Geometric Types}
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
#[derive(Copy, Clone, Debug)]
|
|
pub struct Point2D {
|
|
pub x: StaffSpace,
|
|
pub y: StaffSpace,
|
|
}
|
|
|
|
#[derive(Copy, Clone, Debug)]
|
|
pub struct Size2D {
|
|
pub width: StaffSpace,
|
|
pub height: StaffSpace,
|
|
}
|
|
|
|
#[derive(Copy, Clone, Debug)]
|
|
pub struct Rect {
|
|
pub origin: Point2D,
|
|
pub size: Size2D,
|
|
}
|
|
|
|
#[derive(Copy, Clone, Debug)]
|
|
pub struct BoundingBox {
|
|
pub left: StaffSpace,
|
|
pub right: StaffSpace,
|
|
pub top: StaffSpace,
|
|
pub bottom: StaffSpace,
|
|
}
|
|
|
|
#[derive(Copy, Clone, Debug)]
|
|
pub struct Transform2D {
|
|
pub matrix: [[f32; 3]; 3],
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\section{Provenance}
|
|
\label{sec:layoutir:provenance}
|
|
|
|
Every IR object carries a provenance record tracing it back to the
|
|
score graph object that originated it. Provenance is uniform across
|
|
all four stages; provenance records are shared structurally between
|
|
stages so that an object passing through the pipeline retains a
|
|
consistent identity.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct Provenance {
|
|
/// The primary source: the score graph object this IR object
|
|
/// represents or derives from.
|
|
pub source: TypedObjectId,
|
|
|
|
/// For IR objects that derive from synthesizing engraving
|
|
/// decisions rather than directly from score graph objects, the
|
|
/// synthesis kind. Examples: an automatically inserted natural
|
|
/// sign, a generated rest, a system break.
|
|
pub synthesis: Option<SynthesisKind>,
|
|
|
|
/// Additional source dependencies. An IR object may depend on
|
|
/// multiple score graph objects (e.g., a beam depends on its
|
|
/// member events). All dependencies are tracked for
|
|
/// incremental layout.
|
|
pub dependencies: Vec<TypedObjectId>,
|
|
|
|
/// IR object's stable identifier across re-layouts where the
|
|
/// underlying source is unchanged.
|
|
pub stable_id: LayoutObjectId,
|
|
}
|
|
|
|
pub enum SynthesisKind {
|
|
/// An accidental inserted to cancel a prior alteration.
|
|
CancellationAccidental,
|
|
|
|
/// A natural sign generated by key signature rules.
|
|
KeySignatureNatural,
|
|
|
|
/// A rest inserted to fill a voice gap.
|
|
GeneratedRest,
|
|
|
|
/// A system or page break selected by the engraver.
|
|
EngravedBreak,
|
|
|
|
/// A multimeasure rest combining consecutive empty measures.
|
|
MultimeasureRest,
|
|
|
|
/// A cautionary key or time signature at a system break.
|
|
Cautionary,
|
|
|
|
/// A custom synthesis declared by a layout extension.
|
|
Registered(SynthesisRegistryId),
|
|
}
|
|
|
|
pub struct LayoutObjectId(pub u128);
|
|
\end{lstlisting}
|
|
|
|
\begin{requirement}
|
|
\label{req:layoutir:provenance-completeness}
|
|
Every layout object at every IR stage \MUST{} carry a non-empty
|
|
\texttt{Provenance} record. Objects whose direct \texttt{source}
|
|
does not correspond to a score graph object (engraver-synthesized
|
|
objects) \MUST{} declare a \texttt{synthesis} kind. The
|
|
\texttt{dependencies} list \MUST{} include every score graph object
|
|
whose change should invalidate this layout object.
|
|
\end{requirement}
|
|
|
|
\begin{requirement}
|
|
\label{req:layoutir:continuation-synthesis}
|
|
\textbf{System-spanning continuation.} A line primitive (stroke) that spans a
|
|
system boundary is split into per-system segments; the segments after the
|
|
first are engraver-synthesized and \MUST{} declare a \texttt{synthesis} kind.
|
|
The reserved registered kind
|
|
\texttt{SynthesisKind::Registered(SYSTEM\_CONTINUATION\_SYNTHESIS)} names this
|
|
continuation. Its \texttt{stable\_semantic\_instance\_key} (per
|
|
Requirement~\ref{req:layoutir:object-id-derivation}) is the pair
|
|
\texttt{(original,\,ordinal)} --- the pre-split stroke's object and the
|
|
segment's order within the split --- which is stable for a fixed set of system
|
|
breaks. Because \texttt{LayoutObjectId}s are non-canonical and re-derived per
|
|
layout, this key need only be stable within a layout, not across relayouts
|
|
that move the breaks.
|
|
\end{requirement}
|
|
|
|
\begin{requirement}
|
|
\label{req:layoutir:object-id-derivation}
|
|
A \texttt{LayoutObjectId} \MUST{} be stable across re-layouts whose
|
|
underlying source is unchanged. It \MUST{} be derived by
|
|
domain-separated BLAKE3 truncation with the reserved layout domain
|
|
tag \texttt{"MUSCLOID"} over a key that depends on how the object is
|
|
manifested:
|
|
|
|
\begin{itemize}
|
|
\item A layout object that manifests a single score-graph object
|
|
exactly once is keyed on \texttt{source.canonical\_bytes()}.
|
|
\item A multiply-manifested object --- one score-graph object that
|
|
appears in more than one layout context, e.g. a staff manifested
|
|
in two regions --- is keyed on the pair
|
|
\texttt{(source,\,region)}, so each manifestation receives a
|
|
distinct, stable id.
|
|
\item A synthesized object (no direct score-graph
|
|
\texttt{source}; \texttt{synthesis} is \texttt{Some}) is keyed
|
|
on the triple \texttt{(source,\,synthesis\_kind,\,
|
|
stable\_semantic\_instance\_key)}, where the
|
|
\texttt{stable\_semantic\_instance\_key} distinguishes multiple
|
|
synthesized objects sharing a source and synthesis kind by a
|
|
semantically stable discriminator (not a layout-position
|
|
ordinal, which would not survive relayout).
|
|
\end{itemize}
|
|
|
|
\texttt{LayoutObjectId}s are \emph{non-canonical}: they are not part
|
|
of document state and do not enter any content hash, so the
|
|
\texttt{"MUSCLOID"} tag lives in the layout namespace and is not one
|
|
of the reserved \emph{canonical} system tags of
|
|
Section~\ref{sec:graph:system-derived}. The fixed derivation is
|
|
pinned for incremental-relayout correctness and provenance
|
|
back-reference stability; its consumers are the solver and renderer
|
|
(Track~A), not the interchange track. Because these ids never enter
|
|
document state, no stored or interchanged artifact depends on this
|
|
derivation. The v0 reference implementation realizes this derivation
|
|
as of Pass-12 item P12-I2: \texttt{epiphany-determinism} reserves the
|
|
built-in \texttt{"MUSCLOID"} layout tag and the \texttt{layout-ir}
|
|
provenance derivations (single, multiply-manifested, and synthesized)
|
|
route through it, the synthesized case no longer borrowing
|
|
\texttt{"MUSCCONF"}. Because the ids are non-canonical, doing so
|
|
changed layout-id values but no stored or interchanged artifact.
|
|
\end{requirement}
|
|
|
|
\section{The Stage Pipeline}
|
|
\label{sec:layoutir:pipeline}
|
|
|
|
\subsection{Pipeline Overview}
|
|
|
|
\begin{lstlisting}
|
|
ScoreGraph
|
|
|
|
|
| Engraving pass:
|
|
| - Project structure
|
|
| - Make engraving decisions (stems, beams, accidentals)
|
|
| - Apply user overrides
|
|
v
|
|
LogicalLayoutIR
|
|
|
|
|
| Spacing pass:
|
|
| - Compute spring parameters per time slot
|
|
| - Compute glyph bounding boxes
|
|
| - Build collision constraints
|
|
v
|
|
ConstrainedLayoutIR
|
|
|
|
|
| Constraint solving (Chapter 9):
|
|
| - Resolve horizontal positions
|
|
| - Resolve vertical positions
|
|
| - Resolve page and system breaks
|
|
v
|
|
ResolvedLayoutIR
|
|
|
|
|
| Render projection (out of scope):
|
|
| - Convert to renderer primitives
|
|
| - Apply page templates
|
|
| - Generate draw calls
|
|
v
|
|
RenderIR
|
|
\end{lstlisting}
|
|
|
|
\subsection{Stage Contracts}
|
|
|
|
\begin{requirement}
|
|
\label{req:layoutir:deterministic-stage-transitions}
|
|
Each pipeline stage \MUST{} accept its input stage as a pure
|
|
function: identical inputs (plus identical configuration) \MUST{}
|
|
produce identical outputs. The stages are deterministic by
|
|
contract.
|
|
|
|
Stage transitions \MUST{} preserve provenance: every output object
|
|
\MUST{} carry a \texttt{Provenance} record either inherited from its
|
|
input or newly synthesized with appropriate \texttt{synthesis} kind.
|
|
\end{requirement}
|
|
|
|
\section{LogicalLayoutIR}
|
|
\label{sec:layoutir:logical}
|
|
|
|
\texttt{LogicalLayoutIR} is the structural projection of the score
|
|
graph into layout objects, with all engraving decisions made but
|
|
spatial positions unresolved. It is the output of the engraving pass
|
|
and the input to the spacing pass.
|
|
|
|
\subsection{Top-Level Structure}
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct LogicalLayoutIR {
|
|
/// Source score graph reference (typically a snapshot pointer
|
|
/// or version identifier).
|
|
pub source: ScoreVersion,
|
|
|
|
/// Layout regions, one per score graph region.
|
|
pub regions: Vec<LayoutRegion>,
|
|
|
|
/// Engraving decisions made during this pass.
|
|
pub engraving_decisions: Vec<EngravingDecision>,
|
|
|
|
/// User overrides projected from the score graph.
|
|
pub overrides: Vec<EngravingOverride>,
|
|
|
|
/// Cross-region layout objects (slurs spanning regions, etc.).
|
|
pub cross_region: Vec<CrossRegionObject>,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsection{Layout Regions}
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct LayoutRegion {
|
|
pub provenance: Provenance,
|
|
|
|
/// Coordinate system local to this region. The region's
|
|
/// transform maps local coordinates to canvas coordinates.
|
|
pub coordinate_system: LocalCoordinateSystem,
|
|
|
|
/// Time axis: how time positions map to horizontal positions.
|
|
/// A tagged union over the three built-in axis kinds plus a
|
|
/// registered variant for extension-defined axes. The enum
|
|
/// form is canonical for serialization, hashing, and
|
|
/// conformance comparison; implementations MAY use a trait
|
|
/// object internally for dynamic dispatch.
|
|
pub time_axis: TimeAxisModel,
|
|
|
|
/// Vertical extent: the set of staff bands this region occupies.
|
|
pub vertical_extent: VerticalExtent,
|
|
|
|
/// Layout objects within this region.
|
|
pub objects: Vec<LayoutObject>,
|
|
}
|
|
|
|
/// Canonical representation of a region's time axis. Variants
|
|
/// correspond to the three built-in region time models; the
|
|
/// Registered variant carries extension-defined axes by typed
|
|
/// registry id and serialized payload.
|
|
pub enum TimeAxisModel {
|
|
Metric(MetricTimeAxis),
|
|
Proportional(ProportionalTimeAxis),
|
|
Aleatoric(AleatoricTimeAxis),
|
|
Registered(TimeAxisRegistryId, SerializedRegisteredAxis),
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsection{The Time Axis Trait}
|
|
|
|
For dynamic dispatch and ergonomic implementation, the
|
|
\texttt{TimeAxisModel} enum's payload types share a trait:
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub trait TimeAxis: Send + Sync {
|
|
/// The time model kind this axis implements.
|
|
fn kind(&self) -> TimeAxisKind;
|
|
|
|
/// Project a time position to a horizontal spring slot.
|
|
/// Returns a slot identifier; spring parameters are queried
|
|
/// separately.
|
|
fn project(&self, time: TimePoint) -> SpringSlotId;
|
|
|
|
/// Enumerate the spring slots in time order.
|
|
fn slots(&self) -> &[SpringSlotId];
|
|
|
|
/// For incremental layout: which slots are affected by a change
|
|
/// to the given time range?
|
|
fn affected_slots(&self, range: TimeRange) -> Vec<SpringSlotId>;
|
|
}
|
|
|
|
pub enum TimeAxisKind {
|
|
Metric,
|
|
Proportional,
|
|
Aleatoric,
|
|
Registered(TimeAxisRegistryId),
|
|
}
|
|
|
|
pub enum TimePoint {
|
|
Musical(MusicalPosition),
|
|
WallClock(WallClockTime),
|
|
}
|
|
|
|
pub enum TimeRange {
|
|
Musical { start: MusicalPosition, end: MusicalPosition },
|
|
WallClock { start: WallClockTime, end: WallClockTime },
|
|
}
|
|
\end{lstlisting}
|
|
|
|
The three built-in implementations:
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct MetricTimeAxis {
|
|
/// Measure boundaries and their projected spring slots.
|
|
pub measures: Vec<MeasureProjection>,
|
|
|
|
/// Per-time-slot spring parameters, indexed by slot id.
|
|
pub slots: Vec<SpringSlot>,
|
|
}
|
|
|
|
pub struct ProportionalTimeAxis {
|
|
/// Wall-clock duration of the region.
|
|
pub duration: WallClockDuration,
|
|
|
|
/// Linear horizontal projection: space per second.
|
|
pub space_per_second: StaffSpace,
|
|
|
|
pub slots: Vec<SpringSlot>,
|
|
}
|
|
|
|
pub struct AleatoricTimeAxis {
|
|
/// DAG of event ordering.
|
|
pub ordering: EventOrderingDAG,
|
|
|
|
/// Spring slots are organized by topological-order layers.
|
|
pub slots: Vec<SpringSlot>,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsection{Layout Objects}
|
|
|
|
A \texttt{LayoutObject} is the unit of engraving. At the
|
|
\texttt{LogicalLayoutIR} stage, layout objects are composite: a
|
|
single \texttt{Note} object contains its notehead, stem, flag,
|
|
accidental, articulations, and so on. At the
|
|
\texttt{ConstrainedLayoutIR} stage these are flattened into
|
|
individual glyph objects.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub enum LayoutObject {
|
|
/// Composite note object: notehead(s), stem, flag, accidental(s),
|
|
/// articulations, ornaments, dot(s).
|
|
Note(NoteLayout),
|
|
|
|
/// Composite chord object: multiple noteheads sharing stem and
|
|
/// articulations.
|
|
Chord(ChordLayout),
|
|
|
|
/// Rest with its glyph kind (whole, half, quarter, ...).
|
|
Rest(RestLayout),
|
|
|
|
/// Beam group: multiple notes/chords beamed together.
|
|
BeamGroup(BeamGroupLayout),
|
|
|
|
/// Tuplet visual: bracket and number.
|
|
TupletDisplay(TupletDisplayLayout),
|
|
|
|
/// Slur or phrase mark: control points and style.
|
|
Slur(SlurLayout),
|
|
|
|
/// Tie: same as slur but connecting same-pitch events.
|
|
Tie(TieLayout),
|
|
|
|
/// Spanner: hairpins, octave lines, pedal lines, etc.
|
|
Spanner(SpannerLayout),
|
|
|
|
/// Marker: rehearsal marks, section labels, tempo marks.
|
|
Marker(MarkerLayout),
|
|
|
|
/// Bar line: simple, double, final, repeat, etc.
|
|
BarLine(BarLineLayout),
|
|
|
|
/// Clef, key signature, time signature glyphs.
|
|
Clef(ClefLayout),
|
|
KeySignature(KeySignatureLayout),
|
|
TimeSignatureDisplay(TimeSignatureDisplayLayout),
|
|
|
|
/// Staff: the staff lines themselves.
|
|
Staff(StaffLayout),
|
|
|
|
/// Text: lyrics, dynamics text, expressive text.
|
|
Text(TextLayout),
|
|
|
|
/// Free graphic: path, shape, image, stroke.
|
|
Graphic(GraphicLayout),
|
|
|
|
/// Multimeasure rest: consolidated empty measures.
|
|
MultimeasureRest(MultimeasureRestLayout),
|
|
|
|
/// Cue: small-print rendering of source material.
|
|
Cue(CueLayout),
|
|
|
|
/// Trajectory: glissando, portamento line.
|
|
Trajectory(TrajectoryLayout),
|
|
|
|
/// Generic group: implementation-defined.
|
|
Group(GroupLayout),
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsection{Note Layout: A Worked Example}
|
|
|
|
To illustrate the composite-object pattern at the logical stage:
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct NoteLayout {
|
|
pub provenance: Provenance,
|
|
|
|
/// Reference to the score graph event.
|
|
pub event: EventId,
|
|
|
|
/// The notehead glyph and its staff position.
|
|
pub notehead: NoteheadLayout,
|
|
|
|
/// The stem, if any.
|
|
pub stem: Option<StemLayout>,
|
|
|
|
/// Flag, if any (single-note events with eighth-or-shorter
|
|
/// duration that are not part of a beam group).
|
|
pub flag: Option<FlagLayout>,
|
|
|
|
/// Accidental glyphs attached to the note, in stacking order.
|
|
pub accidentals: Vec<AccidentalLayout>,
|
|
|
|
/// Augmentation dots.
|
|
pub dots: Vec<DotLayout>,
|
|
|
|
/// Articulations attached to the note.
|
|
pub articulations: Vec<ArticulationLayout>,
|
|
|
|
/// Ornament glyphs.
|
|
pub ornaments: Vec<OrnamentLayout>,
|
|
|
|
/// Ledger lines required to display the note's staff position.
|
|
pub ledger_lines: Vec<LedgerLineLayout>,
|
|
|
|
/// Engraving decisions made for this note.
|
|
pub decisions: NoteDecisions,
|
|
}
|
|
|
|
pub struct NoteDecisions {
|
|
pub stem_direction: StemDirection,
|
|
pub accidental_visible: bool,
|
|
pub accidental_parenthesized: bool,
|
|
pub notehead_shape: NoteheadShape,
|
|
/// Voice membership influences default stem direction;
|
|
/// overrides may change it.
|
|
pub stem_direction_source: DecisionSource,
|
|
}
|
|
|
|
pub enum DecisionSource {
|
|
/// Decision derived from automatic engraving rules.
|
|
Automatic,
|
|
|
|
/// Decision derived from a user override in the score graph.
|
|
UserOverride(EngravingOverrideId),
|
|
|
|
/// Decision derived from an IR-stage override.
|
|
IrOverride,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsection{Engraving Decisions}
|
|
|
|
The pipeline records engraving decisions explicitly so they can be
|
|
inspected, overridden, and replayed.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct EngravingDecision {
|
|
pub id: EngravingDecisionId,
|
|
pub target: LayoutObjectId,
|
|
pub kind: EngravingDecisionKind,
|
|
pub source: DecisionSource,
|
|
}
|
|
|
|
pub enum EngravingDecisionKind {
|
|
StemDirection(StemDirection),
|
|
BeamGrouping(BeamGroupingChoice),
|
|
AccidentalOrdering(Vec<AccidentalId>),
|
|
SlurDirection(SlurDirection),
|
|
LedgerLineCount(u8),
|
|
EnharmonicSpelling(PitchSpelling),
|
|
SystemBreak,
|
|
PageBreak,
|
|
NoteheadShape(NoteheadShape),
|
|
RestPosition(StaffPosition),
|
|
// ... extensible
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\section{Engraving Overrides}
|
|
\label{sec:layoutir:overrides}
|
|
|
|
Engraving overrides are user assertions that the engraver's default
|
|
decision should be replaced. Overrides are authoritative in the score
|
|
graph and projected into the IR during the logical stage.
|
|
|
|
\subsection{Override Definition}
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct EngravingOverride {
|
|
pub id: EngravingOverrideId,
|
|
|
|
/// What this override targets. May reference a score graph object
|
|
/// (typical) or an IR-synthesized object (transient overrides).
|
|
pub target: OverrideTarget,
|
|
|
|
/// The override itself.
|
|
pub kind: OverrideKind,
|
|
|
|
/// Binding strength.
|
|
pub priority: OverridePriority,
|
|
|
|
/// Provenance: who set the override and when.
|
|
pub origin: OverrideOrigin,
|
|
}
|
|
|
|
pub enum OverrideTarget {
|
|
/// Targets a score graph object. Authoritative; survives reload.
|
|
ScoreGraph(TypedObjectId),
|
|
|
|
/// Targets an IR-synthesized object. Transient; cleared on
|
|
/// re-engraving unless explicitly re-applied.
|
|
IrSynthesized(LayoutObjectId),
|
|
}
|
|
|
|
pub enum OverrideKind {
|
|
StemDirection(StemDirection),
|
|
SlurDirection(SlurDirection),
|
|
SlurCurvature(CurvatureOverride),
|
|
AccidentalParenthesized(bool),
|
|
AccidentalVisible(bool),
|
|
NoteheadShape(NoteheadShape),
|
|
BeamGeometry(BeamGeometryOverride),
|
|
SystemBreak { anchor: TimeAnchor },
|
|
PageBreak { anchor: TimeAnchor },
|
|
HiddenObject,
|
|
CustomPosition(Point2D),
|
|
EnharmonicSpelling(PitchSpelling),
|
|
LedgerLineSuppression,
|
|
DotPlacement(DotPlacement),
|
|
// ... extensible
|
|
}
|
|
|
|
// System- and page-break overrides address a *position*, not an
|
|
// object: the break kind carries the break's TimeAnchor, while the
|
|
// override's ScoreGraph target names the owning region. They are
|
|
// projected during the logical stage from the score graph's
|
|
// authoritative user_system_breaks / user_page_breaks lists
|
|
// (Chapter 5). Break authorship (author, timestamp) lives in the
|
|
// operation log, not the materialized break lists, so projected
|
|
// break overrides carry OverrideOrigin::Internal until the
|
|
// snapshot-undo refinement (P11-C8) surfaces authorship.
|
|
|
|
pub enum OverridePriority {
|
|
/// The engraver MUST honor this override. Failure produces an
|
|
/// error rather than a silent override-of-the-override.
|
|
Hard,
|
|
|
|
/// The engraver SHOULD honor this override. The engraver may
|
|
/// override it only under documented exceptional conditions
|
|
/// (system overflow, unresolvable collision); when it does, the
|
|
/// override is recorded in the resulting decisions with source
|
|
/// IrOverride.
|
|
Soft,
|
|
}
|
|
|
|
pub enum OverrideOrigin {
|
|
User { author: AuthorId, timestamp: Timestamp },
|
|
Import { format: ForeignFormatId },
|
|
Plugin { plugin: PluginId },
|
|
Internal,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsection{Override Resolution}
|
|
|
|
\begin{requirement}
|
|
\label{req:layoutir:override-enforcement}
|
|
Overrides \MUST{} be applied during the engraving pass producing
|
|
\texttt{LogicalLayoutIR}. Hard overrides \MUST{} be honored or the
|
|
pass \MUST{} report an error. Soft overrides \MUST{} be honored
|
|
except where doing so would produce an invalid layout; when a soft
|
|
override is not honored, the resulting decision \MUST{} record the
|
|
override and the reason for its non-application.
|
|
\end{requirement}
|
|
|
|
\begin{requirement}
|
|
\label{req:layoutir:override-target-lifetime}
|
|
Overrides whose targets reference objects in the score graph (the
|
|
\texttt{ScoreGraph} variant) \MUST{} be preserved across
|
|
re-engraving. Overrides targeting IR-synthesized objects (the
|
|
\texttt{IrSynthesized} variant) are transient; they are cleared on
|
|
re-engraving unless explicitly re-applied. Implementations \MUST{}
|
|
not silently promote IR-synthesized overrides to score-graph
|
|
overrides without explicit user action.
|
|
\end{requirement}
|
|
|
|
\begin{requirement}
|
|
\label{req:layoutir:break-origin-attribution}
|
|
\textbf{Break-override attribution.} When the engraver honours a user break
|
|
override, the resulting decision \MUST{} carry
|
|
\texttt{DecisionSource::UserOverride(id)} naming the override. Because a
|
|
normalized break \emph{constraint} (\texttt{SystemBreakAt} /
|
|
\texttt{PageBreakAt}, Section~\ref{sec:layoutir:constrained}) carries no
|
|
override identity, this attribution is threaded through the projection: the
|
|
\texttt{ConstrainedLayoutIR} carries a \texttt{break\_origins} sidecar,
|
|
populated when the logical stage projects the break, mapping each user-anchored
|
|
break slot to its override id. The normalized constraint record is
|
|
deliberately \emph{not} widened to carry override identity --- attribution is a
|
|
projection concern, not a constraint-solver input.
|
|
\end{requirement}
|
|
|
|
\section{ConstrainedLayoutIR}
|
|
\label{sec:layoutir:constrained}
|
|
|
|
\texttt{ConstrainedLayoutIR} is the output of the spacing pass: the
|
|
logical IR with composite objects flattened to individual glyphs,
|
|
each glyph carrying its bounding box, anchor, and constraint inputs
|
|
to the solver.
|
|
|
|
\begin{requirement}
|
|
\label{req:layoutir:constraint-floor}
|
|
\textbf{Minimal-tier constraint-emission floor (ratified Pass 12).}
|
|
The spacing pass \MUST{} emit at least the following constraint
|
|
set --- the \emph{Minimal-tier floor}, the testable acceptance
|
|
surface for the \texttt{Minimal} conformance tier
|
|
(Chapter~\ref{ch:solver}): no-collision chains between
|
|
successive notehead-bearing columns; per-glyph containment
|
|
within the owning region's frame; and one constraint per user
|
|
break override (system and page). Richer emission sets
|
|
(inter-band collision, kerned optical adjacency, duration-
|
|
proportional spring preferences) belong to higher tiers, whose
|
|
floors are defined when those tiers are ratified.
|
|
\end{requirement}
|
|
|
|
\subsection{Glyph-Level Objects}
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct GlyphObject {
|
|
pub provenance: Provenance,
|
|
|
|
/// The glyph to render. Reference into a font catalog; metrics
|
|
/// are queried from the catalog rather than embedded here.
|
|
pub glyph: GlyphReference,
|
|
|
|
/// The spring slot this glyph belongs to (horizontal time slot).
|
|
pub horizontal_slot: SpringSlotId,
|
|
|
|
/// The vertical band this glyph belongs to.
|
|
pub vertical_band: VerticalBandId,
|
|
|
|
/// Bounding box of the glyph relative to its anchor, in staff
|
|
/// spaces. Looked up from the font catalog at IR construction
|
|
/// time and cached here.
|
|
pub bounding_box: BoundingBox,
|
|
|
|
/// Anchor point: where the glyph's reference position sits
|
|
/// within its bounding box.
|
|
pub anchor: Point2D,
|
|
|
|
/// Layer ordering: higher draws on top.
|
|
pub layer: i32,
|
|
|
|
/// Visual style applied to the glyph.
|
|
pub style: GlyphStyle,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsection{Spring Slots}
|
|
|
|
A spring slot is a time slot's spacing parameters: the constraint
|
|
solver's input for resolving horizontal positions.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct SpringSlot {
|
|
pub id: SpringSlotId,
|
|
pub time: TimePoint,
|
|
|
|
/// Minimum width: the smallest the slot may compress to. The
|
|
/// constraint solver MUST NOT compress below this.
|
|
pub min_width: StaffSpace,
|
|
|
|
/// Preferred width: the natural width under unconstrained
|
|
/// spacing. Derived from Gourlay-style proportional rules.
|
|
pub preferred_width: StaffSpace,
|
|
|
|
/// Maximum width, if bounded. None means unbounded above.
|
|
pub max_width: Option<StaffSpace>,
|
|
|
|
/// Stretch factor: how readily this slot stretches when more
|
|
/// width is available than preferred. Higher = more elastic.
|
|
pub stretch_factor: f32,
|
|
|
|
/// Compress factor: how readily this slot compresses when less
|
|
/// width is available than preferred. Higher = more compressible.
|
|
pub compress_factor: f32,
|
|
|
|
/// Glyphs belonging to this slot, ordered by stacking
|
|
/// considerations (accidentals to left of notehead, etc.).
|
|
pub members: Vec<GlyphObjectId>,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsection{Vertical Bands}
|
|
|
|
Vertical layout uses the same spring model. A \emph{vertical band}
|
|
is a horizontal slice of the canvas (typically a staff or an
|
|
inter-staff gap) with its own spring parameters.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct VerticalBand {
|
|
pub id: VerticalBandId,
|
|
pub kind: VerticalBandKind,
|
|
|
|
pub min_height: StaffSpace,
|
|
pub preferred_height: StaffSpace,
|
|
pub max_height: Option<StaffSpace>,
|
|
pub stretch_factor: f32,
|
|
pub compress_factor: f32,
|
|
|
|
/// Glyphs belonging to this band.
|
|
pub members: Vec<GlyphObjectId>,
|
|
}
|
|
|
|
pub enum VerticalBandKind {
|
|
Staff(StaffId),
|
|
InterStaffGap,
|
|
InterSystemGap,
|
|
MarginBand,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\begin{requirement}
|
|
\label{req:layoutir:coverage-diagnostics}
|
|
\textbf{Engraving-coverage gaps are surfaced, not guessed.} A projection
|
|
that cannot engrave an object faithfully --- a pitch with no resolved
|
|
spelling, a glyph the bound catalog
|
|
(\ref{sec:layoutir:catalog}) does not carry --- \MUST{} record a
|
|
\texttt{LayoutDiagnostic} naming the score-graph object and the kind of
|
|
gap, and \MUST{} still place the object: a fallback notehead on the clef
|
|
reference line, or a zero-extent traced anchor that keeps its provenance.
|
|
It \MUSTNOT{} silently substitute a plausible shape, and it \MUSTNOT{}
|
|
drop the object.
|
|
|
|
Both halves matter. Dropping the object breaks the round-trip surjection
|
|
(\ref{sec:layoutir:provenance}) --- a hit-test can no longer find it, and
|
|
an editor can no longer select what the author wrote. Guessing a shape
|
|
produces a score that looks engraved and is wrong, with nothing in the IR
|
|
to say so. A diagnostic is not an error: the layout is still
|
|
\emph{renderable}, and a conformance claim is unaffected. It is the record
|
|
that the engraving is incomplete at a named place.
|
|
\end{requirement}
|
|
|
|
\begin{requirement}
|
|
\label{req:layoutir:primitive-band-ownership}
|
|
\textbf{Primitive band ownership is declared, not inferred.} Every
|
|
primitive the projection presents to the solver in
|
|
\texttt{ConstrainedLayoutIR} --- \texttt{GlyphObject},
|
|
\texttt{Stroke}, and \texttt{Curve} alike --- \MUST{} declare the
|
|
\texttt{VerticalBandId} of the band that owns it, and that band
|
|
\MUST{} exist in \texttt{vertical\_bands}. A vertical solver \MUST{}
|
|
take a primitive's owning staff from that declaration and
|
|
\MUSTNOT{} infer it from the primitive's geometry. Content owned by
|
|
no staff (a page-margin annotation, a structure spanning several
|
|
staves) names a band whose kind is not \texttt{Staff}.
|
|
|
|
This is a correctness requirement, not a convenience. A primitive's
|
|
drawn position does not determine its owner: a stem shares its
|
|
horizontal column with the staff above, and a slur's endpoints are
|
|
\emph{lifted clear} of its own staff by construction, landing in the
|
|
inter-staff zone where the nearest notehead routinely belongs to the
|
|
neighbouring staff. A solver that guesses from proximity will
|
|
attribute such a primitive to the wrong staff and, on renegotiating
|
|
the gaps between a system's staves, tear it off the notes it belongs
|
|
to.
|
|
|
|
A band \MUST{} therefore exist for every staff of a region, whether
|
|
or not any glyph landed in it, and for the region's margin ---
|
|
otherwise a primitive that names one has nowhere to point. A band
|
|
MAY carry no members.
|
|
|
|
Only glyphs are \emph{members} of a band
|
|
(\texttt{VerticalBand::members}), which is what realizes the spring
|
|
solve; a stroke's or curve's band reference is one-way. What becomes
|
|
of the declaration downstream is
|
|
\ref{req:layoutir:resolved-band-ownership}.
|
|
\end{requirement}
|
|
|
|
\subsection{Constraints}
|
|
|
|
The IR carries explicit constraint inputs for the solver. Beyond
|
|
glyphs it presents the non-glyph primitives --- \texttt{Stroke} and
|
|
\texttt{Curve}, whose shapes are listed with
|
|
\ref{req:layoutir:resolved-primitives} --- since the solver positions
|
|
those too:
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct ConstrainedLayoutIR {
|
|
pub source: ScoreVersion,
|
|
|
|
pub regions: Vec<ConstrainedLayoutRegion>,
|
|
|
|
/// Horizontal spring slots across all regions.
|
|
pub horizontal_slots: Vec<SpringSlot>,
|
|
|
|
/// Vertical bands.
|
|
pub vertical_bands: Vec<VerticalBand>,
|
|
|
|
/// Glyph-level layout objects.
|
|
pub glyphs: Vec<GlyphObject>,
|
|
|
|
/// Non-glyph line primitives (staff lines, stems, barlines,
|
|
/// ledger lines, brackets) presented to the solver alongside
|
|
/// the glyphs.
|
|
pub strokes: Vec<Stroke>,
|
|
|
|
/// Cubic-Bezier curve primitives (slurs), likewise.
|
|
pub curves: Vec<Curve>,
|
|
|
|
/// Additional constraints not captured by spring parameters.
|
|
pub constraints: Vec<LayoutConstraint>,
|
|
|
|
/// Which user break override each projected break constraint came
|
|
/// from, so a solver can cite it in the decision it records.
|
|
pub break_origins: Vec<BreakOrigin>,
|
|
|
|
/// Engraving decisions, carried forward from the logical stage.
|
|
pub engraving_decisions: Vec<EngravingDecision>,
|
|
|
|
/// Engraving-coverage gaps, surfaced rather than hidden.
|
|
pub diagnostics: Vec<LayoutDiagnostic>,
|
|
|
|
/// The glyph catalog this IR's metrics were drawn from.
|
|
pub catalog: GlyphCatalogIdentity,
|
|
}
|
|
|
|
/// A projected break constraint's user-override attribution.
|
|
pub struct BreakOrigin {
|
|
pub slot: SpringSlotId,
|
|
pub class: BreakClass,
|
|
pub override_id: EngravingOverrideId,
|
|
}
|
|
|
|
/// An object the projection could not engrave faithfully.
|
|
pub struct LayoutDiagnostic {
|
|
pub source: TypedObjectId,
|
|
pub kind: LayoutDiagnosticKind,
|
|
}
|
|
|
|
pub enum LayoutDiagnosticKind {
|
|
/// A pitch reached the constrained pass with no resolved (or
|
|
/// non-CMN) spelling; its notehead is placed on the clef
|
|
/// reference line, but its true staff position is unknown.
|
|
MissingSpelling,
|
|
|
|
/// A glyph the bound catalog does not carry; the object is
|
|
/// carried as a traced anchor rather than drawn at a guessed
|
|
/// shape.
|
|
UnbundledGlyph(GlyphReference),
|
|
}
|
|
|
|
pub enum LayoutConstraint {
|
|
/// Two glyphs must not overlap in 2D space.
|
|
NoCollision { a: GlyphObjectId, b: GlyphObjectId },
|
|
|
|
/// A glyph must align with another along an axis.
|
|
Align {
|
|
a: GlyphObjectId,
|
|
b: GlyphObjectId,
|
|
axis: Axis,
|
|
},
|
|
|
|
/// A glyph must be positioned within a region of space.
|
|
PositionWithin {
|
|
glyph: GlyphObjectId,
|
|
region: Rect,
|
|
},
|
|
|
|
/// A range of slots must end at a system boundary.
|
|
SystemBreakAt { slot: SpringSlotId, kind: BreakKind },
|
|
|
|
/// A range of slots must end at a page boundary.
|
|
PageBreakAt { slot: SpringSlotId, kind: BreakKind },
|
|
|
|
/// Custom constraint declared by a layout extension.
|
|
Registered(ConstraintRegistryId, ConstraintParameters),
|
|
}
|
|
|
|
pub enum BreakKind {
|
|
Hard,
|
|
Soft,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\begin{requirement}
|
|
\label{req:layoutir:break-satisfaction}
|
|
\textbf{Break-constraint satisfaction.} A \texttt{SystemBreakAt} at
|
|
\texttt{slot} is \emph{satisfied} iff the final resolved layout starts a
|
|
system at that slot; a \texttt{PageBreakAt} iff the final layout starts a page
|
|
there. A slot that already begins a system (respectively page) --- in
|
|
particular a region's first slot --- is trivially satisfied. Satisfaction is a
|
|
predicate on the output \texttt{ResolvedLayoutIR}, evaluable by any consumer,
|
|
not on the solver's internal spring state. A solver's tier claim
|
|
(Section~\ref{sec:layoutir:resolved}) reports which \texttt{Hard} break
|
|
constraints its layout satisfies; a \texttt{Soft} break is advisory and MAY be
|
|
left unsatisfied.
|
|
\end{requirement}
|
|
|
|
\section{ResolvedLayoutIR}
|
|
\label{sec:layoutir:resolved}
|
|
|
|
\texttt{ResolvedLayoutIR} is the output of the constraint solver:
|
|
every glyph, stroke, and curve has a definitive position. This is the
|
|
IR consumed by the renderer.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct ResolvedLayoutIR {
|
|
pub source: ScoreVersion,
|
|
|
|
/// Pages of the resolved score.
|
|
pub pages: Vec<ResolvedPage>,
|
|
|
|
/// Glyph-level objects with resolved positions.
|
|
pub glyphs: Vec<ResolvedGlyph>,
|
|
|
|
/// Non-glyph line primitives (staff lines, stems, barlines,
|
|
/// volta brackets, ...), positioned alongside the glyphs.
|
|
pub strokes: Vec<Stroke>,
|
|
|
|
/// Cubic-Bezier curve primitives (slurs, ...), positioned
|
|
/// alongside the glyphs.
|
|
pub curves: Vec<Curve>,
|
|
|
|
/// Engraving decisions, including any made by the constraint
|
|
/// solver (e.g., soft system breaks placed by casting-off).
|
|
pub engraving_decisions: Vec<EngravingDecision>,
|
|
}
|
|
|
|
pub struct ResolvedPage {
|
|
pub provenance: Provenance,
|
|
pub number: u32,
|
|
pub size: Size2D,
|
|
pub margins: Margins,
|
|
pub systems: Vec<ResolvedSystem>,
|
|
pub free_objects: Vec<GlyphObjectId>,
|
|
}
|
|
|
|
pub struct ResolvedSystem {
|
|
pub provenance: Provenance,
|
|
pub bounding_box: Rect,
|
|
pub staves: Vec<ResolvedStaff>,
|
|
pub measures: Vec<ResolvedMeasure>,
|
|
}
|
|
|
|
pub struct ResolvedGlyph {
|
|
pub provenance: Provenance,
|
|
pub glyph: GlyphReference,
|
|
pub position: Point2D,
|
|
pub transform: Option<Transform2D>,
|
|
pub bounding_box: BoundingBox,
|
|
pub style: GlyphStyle,
|
|
pub layer: i32,
|
|
}
|
|
|
|
/// A line primitive the solver positions (a staff line, stem,
|
|
/// barline, or bracket).
|
|
pub struct Stroke {
|
|
pub provenance: Provenance,
|
|
pub from: Point2D,
|
|
pub to: Point2D,
|
|
pub thickness: StaffSpace,
|
|
pub layer: i32,
|
|
pub style: GlyphStyle,
|
|
|
|
/// The vertical band this stroke belongs to: its owning staff,
|
|
/// declared by the projection that emitted it. A vertical solver
|
|
/// reads ownership from here rather than inferring it from
|
|
/// geometry. Unlike a glyph, a stroke is not listed in
|
|
/// `VerticalBand::members`; this is a one-way reference.
|
|
pub vertical_band: VerticalBandId,
|
|
}
|
|
|
|
/// A cubic-Bezier curve primitive (a slur), its four control
|
|
/// points in `p0`..`p3` drawing order.
|
|
pub struct Curve {
|
|
pub provenance: Provenance,
|
|
pub p0: Point2D,
|
|
pub p1: Point2D,
|
|
pub p2: Point2D,
|
|
pub p3: Point2D,
|
|
pub thickness: StaffSpace,
|
|
pub layer: i32,
|
|
pub style: GlyphStyle,
|
|
|
|
/// The line pattern the renderer strokes the path with
|
|
/// (a slur's authored `SpanStyle.line`): solid, dashed, or dotted.
|
|
pub line: LineStyle,
|
|
|
|
/// The vertical band this curve belongs to (see `Stroke`). A
|
|
/// slur's endpoints are lifted clear of its own staff, so its
|
|
/// owner is not recoverable from its geometry.
|
|
pub vertical_band: VerticalBandId,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\begin{requirement}
|
|
\label{req:layoutir:resolved-primitives}
|
|
\textbf{Non-glyph resolved primitives (ratified schema-major-2,
|
|
Phase~F).} Beyond glyphs, \texttt{ResolvedLayoutIR} carries
|
|
non-glyph \emph{line-stroke} primitives (staff lines, stems,
|
|
barlines, brackets) and \emph{cubic-B\'ezier curve} primitives
|
|
(slurs). Each \MUST{} carry provenance --- the basis of hit-testing
|
|
(\ref{sec:layoutir:provenance}) --- and is positioned by the solver
|
|
like a glyph: a spanning stroke or curve is re-spaced by the same
|
|
horizontal coordinate map its endpoint columns move under. These
|
|
primitives are \emph{non-canonical}: like layout identifiers and
|
|
glyph positions, they are not part of document state and enter no
|
|
content hash (\ref{req:layoutir:object-id-derivation}). A conformant
|
|
renderer draws them alongside the glyphs; the renderer encoding
|
|
itself is out of scope (\ref{sec:layoutir:render}).
|
|
\end{requirement}
|
|
|
|
\begin{requirement}
|
|
\label{req:layoutir:resolved-band-ownership}
|
|
\textbf{Band ownership through the resolved stage.} A resolved
|
|
\texttt{Stroke} and \texttt{Curve} \MUST{} retain the
|
|
\texttt{vertical\_band} they were given in
|
|
\texttt{ConstrainedLayoutIR}
|
|
(\ref{req:layoutir:primitive-band-ownership}), so a solver that
|
|
re-spaces or relocates them --- casting off into systems, or
|
|
renegotiating the gaps between a system's staves --- can attribute
|
|
each one to its owning staff. A \texttt{ResolvedGlyph} carries no
|
|
band: a glyph's ownership is consumed during the solve and its
|
|
outcome is already baked into the glyph's resolved position, so the
|
|
field would be dead weight in a rendering fingerprint.
|
|
|
|
Band ownership is \emph{non-canonical layout-attribution metadata}.
|
|
It enters no content hash and no canonical encoding
|
|
(\ref{req:layoutir:object-id-derivation}): it draws nothing, so two
|
|
layouts differing only in it are the same rendered layout. A
|
|
conformant canonical encoding of \texttt{ResolvedLayoutIR} therefore
|
|
omits \texttt{vertical\_band} even from the strokes and curves that
|
|
carry it.
|
|
\end{requirement}
|
|
|
|
\begin{requirement}
|
|
\label{req:layoutir:repeat-render}
|
|
\textbf{Minimal-tier repeat and volta rendering (ratified
|
|
schema-major-2, Phase~F).} A \texttt{RepeatStructure}
|
|
(\ref{sec:graph:repeats}) whose kind draws barlines
|
|
(\texttt{SimpleRepeat}, \texttt{Volta}) \MUST{} render a repeat
|
|
barline at each of its resolved boundaries --- the precomposed
|
|
repeat sign, either replacing a coinciding measure barline or
|
|
standing at the boundary column when none coincides, and the
|
|
repeat-dot pair beside a region-closing final barline (which is
|
|
never replaced). Each \texttt{Volta} \MUST{} render a bracket
|
|
spanning its resolved extent, with its ending numbers. A boundary
|
|
that does not resolve to a spacing column draws \emph{no} ink: a
|
|
traced anchor preserves the structure's provenance rather than
|
|
placing a mark at a false position. The jump kinds
|
|
(\texttt{DaCapo}, \texttt{DalSegno}) draw no Minimal-tier marks ---
|
|
their segno / coda / instruction text awaits a text primitive ---
|
|
and a repeat spanning more than one region renders in no single
|
|
region at this tier. The precise glyph vocabulary and spacing are
|
|
engraving-algorithm concerns (\ref{sec:layoutir:forward}).
|
|
\end{requirement}
|
|
|
|
\begin{requirement}
|
|
\label{req:layoutir:slur-curve}
|
|
\textbf{Slur rendering (ratified schema-major-2, Phase~F; extended
|
|
Push~3).} A \texttt{Slur} (\ref{sec:graph:slurs}) \MUST{} render
|
|
as a cubic-B\'ezier \texttt{Curve} arcing between its two endpoint
|
|
event columns, honoring an authored \texttt{CurvatureOverride}
|
|
(direction and apex height) when present and choosing them
|
|
otherwise. A slur whose endpoint does not resolve to a column on a
|
|
single staff of one region draws \emph{no} curve: a traced anchor
|
|
preserves its provenance rather than a floating arc. An authored
|
|
non-\texttt{Solid} \texttt{SpanStyle} line (dashed, dotted) is
|
|
rendered faithfully as that pattern (its \texttt{LineStyle} rides
|
|
the \texttt{Curve}); an implementation that instead defers the
|
|
pattern \MUST{} surface the deferral (a layout diagnostic), never
|
|
silently rendering solid. A slur whose span crosses a system break
|
|
\MUST{} split into per-system sub-curves --- the first carrying the
|
|
slur's provenance, the rest engraver-synthesized continuations
|
|
(\ref{req:layoutir:continuation-synthesis}) --- rather than drawing
|
|
whole in one system. The curvature-computing algorithm remains an
|
|
engraving-specification concern (\ref{sec:layoutir:forward}).
|
|
\end{requirement}
|
|
|
|
\section{RenderIR (Interface Only)}
|
|
\label{sec:layoutir:render}
|
|
|
|
The \texttt{RenderIR} stage is the renderer's input. Its full
|
|
specification is delivered separately; this section defines only the
|
|
interface contract.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub trait RenderIRProducer {
|
|
/// Convert resolved IR to renderer-bound primitives.
|
|
fn produce(
|
|
&self,
|
|
resolved: &ResolvedLayoutIR,
|
|
scale: ScaleContext,
|
|
config: RenderConfiguration,
|
|
) -> RenderIR;
|
|
}
|
|
|
|
pub struct RenderConfiguration {
|
|
/// Target output: PDF, SVG, on-screen, print.
|
|
pub target: RenderTarget,
|
|
|
|
/// Color space and color management options.
|
|
pub color: ColorConfiguration,
|
|
|
|
/// Anti-aliasing and rasterization options.
|
|
pub rasterization: RasterizationConfiguration,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\begin{requirement}
|
|
\label{req:layoutir:render-provenance}
|
|
Implementations producing \texttt{RenderIR} \MUST{} preserve
|
|
provenance from \texttt{ResolvedLayoutIR}: every renderer primitive
|
|
\MUST{} be traceable to its originating \texttt{ResolvedLayoutIR}
|
|
primitive --- a \texttt{ResolvedGlyph}, \texttt{Stroke}, or
|
|
\texttt{Curve} --- and therefore to its score graph source. This is
|
|
the basis of hit-testing, selection, and back-reference navigation
|
|
in the UI.
|
|
|
|
The full RenderIR type and its production rules are specified in
|
|
the renderer specification, outside the scope of this document.
|
|
\end{requirement}
|
|
|
|
\section{Incremental Layout and Caching}
|
|
\label{sec:layoutir:incremental}
|
|
|
|
The pipeline supports incremental layout via per-object dependency
|
|
tracking. An edit to a score graph object invalidates only the IR
|
|
objects whose provenance lists it as a dependency.
|
|
|
|
\subsection{The Layout Cache}
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct LayoutCache {
|
|
/// Reverse index: maps score graph object ids to the layout
|
|
/// objects depending on them.
|
|
pub dependencies: DependencyIndex,
|
|
|
|
/// Cached IR stages, partitioned for granular invalidation.
|
|
pub logical: HashMap<RegionId, LogicalRegionCache>,
|
|
pub constrained: HashMap<RegionId, ConstrainedRegionCache>,
|
|
pub resolved: HashMap<SystemId, ResolvedSystemCache>,
|
|
|
|
/// Cached layout-stage outputs at finer granularity for
|
|
/// performance-critical paths.
|
|
pub fine_cache: FineLayoutCache,
|
|
}
|
|
|
|
pub struct DependencyIndex {
|
|
/// For each score graph object, the layout objects depending
|
|
/// on it. Reverse-indexed to make invalidation O(k) where k is
|
|
/// the number of dependents.
|
|
pub forward: HashMap<TypedObjectId, Vec<LayoutObjectId>>,
|
|
|
|
/// For each layout object, its dependencies. Used to verify and
|
|
/// rebuild the reverse index on cache writes.
|
|
pub reverse: HashMap<LayoutObjectId, Vec<TypedObjectId>>,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsection{Invalidation Rules}
|
|
|
|
\begin{requirement}
|
|
\label{req:layoutir:dependency-invalidation}
|
|
When a semantic operation (Chapter~\ref{ch:semops}) is applied to
|
|
the score graph, the following invalidation \MUST{} occur:
|
|
|
|
\begin{enumerate}
|
|
\item Every layout object whose \texttt{Provenance} lists a
|
|
mutated score graph object in its \texttt{source} or
|
|
\texttt{dependencies} is invalidated.
|
|
\item Invalidation propagates: layout objects that depend on
|
|
invalidated layout objects (via cross-region references,
|
|
spanning structures, etc.) are also invalidated.
|
|
\item Invalidated objects are cleared from the cache at their
|
|
respective stages.
|
|
\item Invalidated systems trigger casting-off re-evaluation for
|
|
themselves and any downstream systems whose horizontal extent
|
|
may shift.
|
|
\end{enumerate}
|
|
|
|
Re-engraving runs from the first invalidated stage forward, using
|
|
cached values for non-invalidated objects.
|
|
\end{requirement}
|
|
|
|
\subsection{Frame-Budget Requirements}
|
|
|
|
\begin{requirement}
|
|
\label{req:layoutir:incremental-frame-budget}
|
|
The incremental layout system \MUST{} support the frame-budget
|
|
requirements established in Chapter~\ref{ch:perf}: an edit
|
|
affecting a single system on a 100-page orchestral score \MUST{}
|
|
re-layout in under 16.7\,ms on the reference hardware profile.
|
|
|
|
Achieving this requires (a) fine-grained dependency tracking as
|
|
specified above; (b) cached glyph metrics in the font catalog;
|
|
(c) parallel re-engraving where the affected systems are
|
|
independent; (d) the constraint solver supporting partial
|
|
re-resolution of affected slots only (see Chapter~\ref{ch:solver}).
|
|
\end{requirement}
|
|
|
|
\section{Region Uniformity}
|
|
\label{sec:layoutir:region-uniformity}
|
|
|
|
Staff-based, free-graphic, and hybrid regions produce structurally
|
|
identical \texttt{LayoutRegion} containers. The differences are
|
|
expressed in:
|
|
|
|
\begin{itemize}
|
|
\item The \texttt{time\_axis}: metric, proportional, or aleatoric.
|
|
\item The \texttt{objects} mix: staff-based regions contain
|
|
primarily notation objects; free-graphic regions contain primarily
|
|
\texttt{Graphic} objects; hybrid regions contain both.
|
|
\end{itemize}
|
|
|
|
\begin{requirement}
|
|
\label{req:layoutir:uniform-region-solving}
|
|
All three region kinds \MUST{} use the same \texttt{LayoutRegion}
|
|
container type. The constraint solver \MUST{} treat regions
|
|
uniformly: it consumes spring slots and constraints without
|
|
branching on region kind.
|
|
\end{requirement}
|
|
|
|
\begin{rationale}
|
|
Uniform containers permit the constraint solver to be agnostic to
|
|
region kind, simplifying its implementation. The differences
|
|
between region kinds are encoded in the \texttt{TimeAxis}
|
|
implementation and the object mix, not in container structure.
|
|
This is the same insight that makes the file format and semantic
|
|
operations layers tractable: structural uniformity at the
|
|
containers, polymorphism in the contents.
|
|
\end{rationale}
|
|
|
|
\section{Glyph Catalog Interface}
|
|
\label{sec:layoutir:catalog}
|
|
|
|
The IR references glyphs by identifier; glyph metrics are queried
|
|
from a font catalog at IR construction. This section defines the
|
|
interface; the catalog implementation is outside the core
|
|
specification.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub trait GlyphCatalog: Send + Sync {
|
|
/// Resolve a glyph reference to its metrics.
|
|
fn metrics(&self, glyph: &GlyphReference) -> Option<GlyphMetrics>;
|
|
|
|
/// Resolve a glyph reference to its rendering data.
|
|
fn render_data(&self, glyph: &GlyphReference) -> Option<GlyphRenderData>;
|
|
|
|
/// SMuFL version supported.
|
|
fn smufl_version(&self) -> SmuflVersion;
|
|
}
|
|
|
|
pub struct GlyphMetrics {
|
|
pub bounding_box: BoundingBox,
|
|
pub advance_width: StaffSpace,
|
|
pub anchors: HashMap<AnchorName, Point2D>,
|
|
}
|
|
|
|
pub struct GlyphRenderData {
|
|
/// Outline data: path commands suitable for vector rendering.
|
|
pub outline: Vec<PathCommand>,
|
|
|
|
/// Optional pre-rendered bitmap for raster targets.
|
|
pub bitmap: Option<GlyphBitmap>,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\begin{requirement}
|
|
\label{req:layoutir:glyph-catalog-lookups}
|
|
Implementations \MUST{} use a glyph catalog for metric and render-data
|
|
lookups during IR construction. Metrics \MUSTNOT{} be duplicated in
|
|
the IR; the IR carries glyph identifiers and queries the catalog.
|
|
Implementations \MAY{} cache metrics within IR objects as a
|
|
performance optimization provided cached values are invalidated when
|
|
the underlying catalog changes.
|
|
\end{requirement}
|
|
|
|
\subsection{Glyph Catalog Identity for Layout Conformance}
|
|
\label{sec:layoutir:catalog-identity}
|
|
|
|
Layout output depends on glyph metrics. For reproducible layout,
|
|
the exact glyph catalog used \MUST{} be identifiable.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
/// A reproducibility-quality identifier for the glyph catalog
|
|
/// used to produce a layout. Required for any layout conformance
|
|
/// claim that depends on byte-equal output across runs.
|
|
pub struct GlyphCatalogIdentity {
|
|
/// SMuFL version targeted.
|
|
pub smufl_version: SmuflVersion,
|
|
|
|
/// Identifier of the specific font in use (e.g., Bravura,
|
|
/// Petaluma, Leland).
|
|
pub font_id: FontId,
|
|
|
|
/// Optional semantic version of the font, if the font
|
|
/// publisher uses versioned releases.
|
|
pub font_version: Option<SemVer>,
|
|
|
|
/// Content hash of the metric data made available to the
|
|
/// layout solve. The hash is over a canonical serialization
|
|
/// of every glyph's metrics (bounding box, advance width,
|
|
/// named anchors) for every glyph referenced by the
|
|
/// ConstrainedLayoutIR delivered to the solver, including
|
|
/// glyphs that are candidates but do not appear in the final
|
|
/// ResolvedLayoutIR. Computed by BLAKE3 with the domain
|
|
/// tag "MUSCFNTM".
|
|
pub metrics_hash: ContentHash,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\begin{requirement}
|
|
\label{req:layoutir:glyph-catalog-identity}
|
|
Any layout conformance claim subject to byte-equal output
|
|
(within-implementation determinism per
|
|
Chapter~\ref{ch:solver}) \MUST{} declare the
|
|
\texttt{GlyphCatalogIdentity} under which it was produced.
|
|
Two layouts produced with different \texttt{metrics\_hash}
|
|
values are layouts under different inputs; byte equality is
|
|
not expected.
|
|
|
|
Implementations that cannot identify their glyph catalog at
|
|
this granularity \MUST{} mark their layout output as
|
|
\emph{implementation-specific} or \emph{non-canonical};
|
|
conformance suites \MUSTNOT{} accept such output as
|
|
byte-equivalent to a reference solver's output.
|
|
|
|
The \texttt{metrics\_hash} is computed over the glyphs that
|
|
are \emph{inputs} to the layout solve (those referenced by the
|
|
\texttt{ConstrainedLayoutIR}), not only over glyphs in the
|
|
final layout. Two solver invocations with different available
|
|
candidate-glyph metrics are operating on different inputs even
|
|
if they happen to converge on the same final glyph set; this
|
|
rule makes that difference visible in the
|
|
\texttt{metrics\_hash}.
|
|
|
|
The hash is computed only over glyphs the layout solve
|
|
actually consulted, not over the entire font catalog, so that
|
|
font additions (new glyphs unused by the solve) do not
|
|
invalidate prior layouts.
|
|
\end{requirement}
|
|
|
|
\section{Forward References}
|
|
\label{sec:layoutir:forward}
|
|
|
|
\begin{itemize}
|
|
\item The constraint solver consuming \texttt{ConstrainedLayoutIR}
|
|
and producing \texttt{ResolvedLayoutIR} is specified in
|
|
Chapter~\ref{ch:solver}.
|
|
\item The serialization of layout cache data for cross-session
|
|
persistence is specified in Chapter~\ref{ch:format}.
|
|
\item The complete RenderIR specification, the engraving algorithm
|
|
specifications (beam grouping, slur curvature, casting-off,
|
|
accidental ordering), the font catalog implementation, and the
|
|
renderer are delivered as separate specifications layered on top
|
|
of this core document.
|
|
\end{itemize}
|
|
|
|
% ===========================================================================
|
|
\chapter{File Format}
|
|
\label{ch:format}
|
|
|
|
This chapter specifies the on-disk file format. Chapter~\ref{ch:semops}
|
|
established that the canonical document state is the deterministic
|
|
reduction of an operation-envelope set. This chapter specifies how
|
|
that operation set, along with acceleration structures, profile
|
|
declarations, and extension declarations, is laid out on disk in a
|
|
way that is crash-recoverable, atomically updatable, and forward-
|
|
compatible.
|
|
|
|
The byte-level encoding details (record layouts, varint conventions,
|
|
exact field bit widths) are deferred to a companion document, the
|
|
Binary Format specification. This chapter specifies \emph{what} is
|
|
stored, how the parts relate, and how the format behaves under
|
|
concurrent edits, crashes, and forward-compatibility scenarios; the
|
|
Binary Format specifies \emph{how the bits are laid out}.
|
|
|
|
The file format's name is \texttt{musc} and its file extension is
|
|
\texttt{.musc}.
|
|
|
|
\section{Design Principles}
|
|
\label{sec:format:principles}
|
|
|
|
\begin{description}
|
|
\item[Canonical state is the operation set.] The canonical
|
|
document is the operation-envelope set
|
|
(Chapter~\ref{ch:semops}). After pruning, the canonical document
|
|
is a retained base snapshot plus the operation envelopes after
|
|
that snapshot's causal frontier. Everything else stored in the
|
|
bundle (snapshots, layout caches, indexes, rendered artifacts)
|
|
is an acceleration structure and \MUST{} be discardable.
|
|
\item[Fixed prelude, atomic superblock commit.] The file begins
|
|
with a fixed-size header followed by two fixed-size superblock
|
|
slots. The superblocks are the only mutable on-disk objects in
|
|
the bundle. Commits append new chunks, write a new manifest
|
|
chunk, then flip the active superblock by writing to the
|
|
currently-inactive slot. This makes commits atomic with respect
|
|
to crashes.
|
|
\item[Content-addressed immutable chunks.] Every other on-disk
|
|
object is content-addressed by BLAKE3 of its uncompressed
|
|
payload (with domain-separated preimage). Chunks are immutable
|
|
once written. Duplicate content shares storage automatically.
|
|
\item[Manifest as a table of roots.] The manifest does not carry
|
|
user-facing data. It carries the operation-set roots, the
|
|
retained snapshot references, blob references, profile
|
|
declarations, extension declarations, and the text-projection
|
|
root. User-facing metadata (title, composer, etc.) lives in
|
|
operation envelopes or in materialized state, not in the
|
|
physical header.
|
|
\item[Crash recovery is by superblock selection.] Recovery is
|
|
simply: read both superblocks, validate, and select the highest
|
|
valid generation. Anything not reachable from the selected
|
|
superblock is unreachable garbage and can be reclaimed by a
|
|
later conservative GC pass.
|
|
\item[Forward compatibility through opaque preservation and edit
|
|
barriers.] Unknown extension chunks are preserved across reads
|
|
and writes. Unknown required extensions either prevent open or
|
|
force read-only mode. Unknown optional extensions are preserved
|
|
unless an edit crosses a declared edit barrier.
|
|
\item[Streaming reads.] The bundle is designed so that opening a
|
|
large score touches the prelude, the active superblock, the
|
|
manifest, and a small set of bootstrap chunks (the most recent
|
|
canonical-base snapshot plus the envelope index). Per-region
|
|
content loads on demand.
|
|
\item[Deterministic text projection of semantic content.] The
|
|
format admits a deterministic projection to a canonical
|
|
s-expression form for diff tooling and version control. The
|
|
text projection preserves canonical document semantics
|
|
(operation envelopes, profile declarations, extension
|
|
declarations, reduced state). It does \emph{not} preserve
|
|
chunk offsets, compression choices, cache chunks, garbage
|
|
regions, or superblock generation numbers.
|
|
\item[No encryption, no DRM.] The format \MUSTNOT{} include
|
|
encryption, access control, or rights-management features.
|
|
Privacy at rest is the responsibility of the filesystem and
|
|
operating system. The format is open and offers no mechanism by
|
|
which user content can be locked away from its author or from
|
|
other implementations.
|
|
\end{description}
|
|
|
|
\begin{nongoal}
|
|
Digital rights management (DRM), content encryption,
|
|
hardware-binding, license enforcement, and any mechanism that
|
|
could lock user content to a particular vendor or platform are
|
|
explicitly out of scope. The format is designed for openness,
|
|
durability, and user ownership of data. Implementations \MUSTNOT{}
|
|
add proprietary extensions intended to restrict access to or use
|
|
of user content.
|
|
\end{nongoal}
|
|
|
|
\section{Durable Writes}
|
|
\label{sec:format:durable}
|
|
|
|
This chapter refers throughout to ``durable flushes'' of file
|
|
contents. The definition is platform-abstracted.
|
|
|
|
\begin{description}
|
|
\item[Durable flush.] The platform operation that makes
|
|
previously-written bytes durable according to the host
|
|
filesystem contract: \texttt{fsync} on POSIX-like systems,
|
|
\texttt{FlushFileBuffers} or \texttt{NtFlushBuffersFileEx} on
|
|
Windows, equivalent calls on other platforms. A successful
|
|
durable flush guarantees that bytes written before the call are
|
|
on the persistent storage medium when the call returns.
|
|
\end{description}
|
|
|
|
\begin{requirement}
|
|
\label{req:format:durable-flush}
|
|
Implementations \MUST{} use the platform's durable flush
|
|
primitive at the points required by the atomic write protocol
|
|
(Section~\ref{sec:format:commit}). Implementations \MAY NOT{}
|
|
rely on filesystem ordering guarantees or write-back caching
|
|
alone; durability \MUST{} be requested explicitly at the commit
|
|
point.
|
|
\end{requirement}
|
|
|
|
\section{The Bundle Layout}
|
|
\label{sec:format:bundle}
|
|
|
|
A \texttt{.musc} bundle is a single file whose first 576 bytes are a
|
|
fixed prelude: a 64-byte header followed by two 256-byte superblock
|
|
slots. The remainder of the file holds variable-length content:
|
|
chunk payloads, the manifest chunk, blob payloads, the text
|
|
projection (if present), and unreachable garbage from prior commits
|
|
that has not yet been collected.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct BundleLayout {
|
|
/// Fixed 64-byte header at offset 0.
|
|
pub header: FixedHeader,
|
|
|
|
/// Two fixed-size superblock slots at offsets 64 and 320.
|
|
pub superblock_a: Superblock, // at offset 64
|
|
pub superblock_b: Superblock, // at offset 320
|
|
|
|
/// Variable-length content begins at offset 576.
|
|
/// All chunks, the manifest, and blobs live here.
|
|
pub body: ContentRegion,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsection{The Fixed Header}
|
|
|
|
The header is exactly 64 bytes at file offset zero. It identifies
|
|
the format and locates the superblock slots. The header never
|
|
changes after the file is created.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct FixedHeader {
|
|
/// Magic bytes: ASCII "MUSCBND\0" (8 bytes).
|
|
pub magic: [u8; 8],
|
|
|
|
/// Major format version. Major version changes are
|
|
/// non-backward-compatible.
|
|
pub format_major: u16,
|
|
|
|
/// Minor format version. Minor version changes are
|
|
/// backward-compatible.
|
|
pub format_minor: u16,
|
|
|
|
/// Length of this header in bytes (currently always 64).
|
|
/// Reserved for future expansion via header_length growth.
|
|
pub header_length: u32,
|
|
|
|
/// Offset of superblock slot A. Currently always 64.
|
|
pub superblock_a_offset: u64,
|
|
|
|
/// Offset of superblock slot B. Currently always 320.
|
|
pub superblock_b_offset: u64,
|
|
|
|
/// 128-bit file UUID, set at creation of the physical bundle.
|
|
/// Changes on Save As (the physical bundle is a new file).
|
|
/// Distinct from document_id, which identifies the logical work.
|
|
pub file_uuid: FileUuid,
|
|
|
|
/// Reserved bytes at offsets 48..60. MUST be zero on write;
|
|
/// MUST be ignored on read. Future minor versions MAY use
|
|
/// these bytes for additional fields whose absence is
|
|
/// interpreted as zero.
|
|
pub reserved: [u8; 12],
|
|
|
|
/// CRC-32C of bytes 0..60 of this header (i.e., all preceding
|
|
/// fields plus the reserved bytes). At fixed offset 60..64.
|
|
pub header_crc: u32,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\begin{requirement}
|
|
\label{req:format:fixed-header-integrity}
|
|
The header \MUST{} be exactly 64 bytes at file offset zero. Its
|
|
contents \MUSTNOT{} change after file creation, except for the
|
|
case where a major-version upgrade rewrites the entire file.
|
|
Readers \MUST{} verify the magic bytes and the header CRC before
|
|
consulting any other part of the file.
|
|
|
|
Reserved bytes \MUST{} be zero when written by current
|
|
implementations. Readers \MUST{} ignore the values of reserved
|
|
bytes (future versions may use them).
|
|
|
|
The \texttt{file\_uuid} is fixed for the lifetime of a physical
|
|
bundle and persists across commits to that bundle. A
|
|
filesystem-level byte copy preserves the \texttt{file\_uuid}
|
|
(the copy is the same physical bundle duplicated). Save As,
|
|
export-as-new-bundle, and derivative-work creation \MUST{}
|
|
assign a fresh \texttt{file\_uuid}; the resulting bundle is a
|
|
new physical bundle, distinct from its source.
|
|
|
|
The \texttt{file\_uuid} identifies the physical bundle, not the
|
|
logical work. Two bundles with the same \texttt{file\_uuid} are
|
|
byte-level copies of the same physical bundle; two bundles
|
|
with the same \texttt{document\_id} (defined in the manifest;
|
|
see Section~\ref{sec:format:identity}) are versions of the
|
|
same logical work, which may or may not share their
|
|
\texttt{file\_uuid}.
|
|
\end{requirement}
|
|
|
|
\subsection{The Superblock Slots}
|
|
|
|
Each superblock slot is exactly 256 bytes. The bundle has two slots
|
|
(A at offset 64, B at offset 320). One is active; the other is the
|
|
write target for the next commit. Atomic commit is the act of
|
|
writing the inactive slot and durably flushing it.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct Superblock {
|
|
/// Magic bytes: ASCII "MUSCSUPR" (8 bytes).
|
|
pub magic: [u8; 8],
|
|
|
|
/// Generation counter. The active superblock is the one with
|
|
/// the highest valid generation.
|
|
pub generation: u64,
|
|
|
|
/// Offset of the manifest chunk for this generation.
|
|
pub manifest_offset: u64,
|
|
|
|
/// Length of the manifest chunk in bytes.
|
|
pub manifest_length: u64,
|
|
|
|
/// BLAKE3 content hash of the manifest chunk's uncompressed
|
|
/// payload, with domain-separated preimage
|
|
/// (Section~\ref{sec:format:hashing}).
|
|
pub manifest_hash: ContentHash,
|
|
|
|
/// Schema version of the manifest at this generation.
|
|
pub manifest_schema_version: SchemaVersion,
|
|
|
|
/// Reduction algorithm version that produced any
|
|
/// canonical-base snapshots in this manifest.
|
|
pub reduction_algorithm_version: ReductionAlgorithmVersion,
|
|
|
|
/// Profile under which this superblock is valid.
|
|
pub profile_id: ProfileId,
|
|
|
|
/// Commit state. Distinguishes a fully-committed superblock
|
|
/// from one that was written but whose commit had not yet
|
|
/// completed when the writer crashed (rare; the protocol does
|
|
/// not normally leave such a state visible).
|
|
pub commit_state: CommitState,
|
|
|
|
/// Wall-clock timestamp at commit, in canonical units.
|
|
/// Advisory only; superblock selection is by generation, not
|
|
/// by timestamp.
|
|
pub commit_timestamp: WallClockTime,
|
|
|
|
/// CRC-32C of the bytes of this superblock up to (but not
|
|
/// including) the CRC field. Lets readers reject torn writes.
|
|
pub superblock_crc: u32,
|
|
|
|
/// Reserved padding to 256 bytes. MUST be zero on write;
|
|
/// MUST be ignored on read.
|
|
pub reserved: [u8; /* computed */ 256 - HEADER_FIELDS_SIZE],
|
|
}
|
|
|
|
pub enum CommitState {
|
|
/// Fully committed. The manifest at manifest_offset is the
|
|
/// canonical state for this generation.
|
|
Committed,
|
|
|
|
/// Reserved for future use; writers MUST NOT produce
|
|
/// non-Committed values in this version of the format.
|
|
Reserved(u32),
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsection{Superblock Selection}
|
|
|
|
\begin{requirement}
|
|
\label{req:format:superblock-selection}
|
|
On open, readers \MUST{} perform the following selection:
|
|
|
|
\begin{enumerate}
|
|
\item Read both superblock slots.
|
|
\item For each slot, validate: magic bytes, CRC, that the
|
|
referenced manifest chunk's actual BLAKE3 hash matches the
|
|
declared \texttt{manifest\_hash}, and that
|
|
\texttt{commit\_state} is \texttt{Committed}. A slot
|
|
failing any of these checks is \emph{not valid} for
|
|
ordinary selection.
|
|
\item A slot whose \texttt{commit\_state} is not
|
|
\texttt{Committed} is invalid for ordinary selection but
|
|
\MAY{} be inspected in diagnostic recovery mode. Writers
|
|
in this format version \MUSTNOT{} produce such states
|
|
under normal commit; observing one indicates either a
|
|
crashed writer (recovery falls back to the other slot per
|
|
the atomic-write protocol) or a non-conforming writer.
|
|
\item If both slots are valid and their generations differ by
|
|
more than one, the reader \MUST{} report an integrity anomaly
|
|
(the file may have been tampered with, accidentally merged,
|
|
or written by a non-conforming implementation). The reader
|
|
\MAY{} continue in read-only recovery mode using the
|
|
highest-generation valid slot; it \MUSTNOT{} silently treat
|
|
the file as normal.
|
|
\item If both slots are valid and their generations differ by
|
|
exactly one, the slot with the higher generation is active.
|
|
\item If both slots are valid and their generations are
|
|
\emph{equal}, the reader \MUST{} compare the slots'
|
|
\emph{load-bearing} selection fields:
|
|
\texttt{manifest\_hash}, \texttt{manifest\_schema\_version},
|
|
\texttt{reduction\_algorithm\_version}, and
|
|
\texttt{profile\_id}. The advisory fields
|
|
\texttt{commit\_timestamp} and the physical chunk
|
|
offset/length are \emph{excluded} from this comparison.
|
|
\begin{itemize}
|
|
\item If every load-bearing field is equal, the two slots
|
|
select equivalent canonical state; the reader \MUST{}
|
|
deterministically choose slot~A and open normally.
|
|
\item If any load-bearing field differs, the reader \MUST{}
|
|
report a format-layer \texttt{IntegrityAnomaly} of kind
|
|
\texttt{DivergentSameGeneration} and open the bundle
|
|
read-only. Two genuinely different committed states cannot
|
|
share a generation under a conforming writer (the
|
|
atomic-write protocol increments the generation on every
|
|
commit), so the divergence is a structural failure, not a
|
|
state the reader may silently resolve by picking one.
|
|
\end{itemize}
|
|
\item If exactly one slot is valid, that slot is active.
|
|
\item If neither slot is valid, the file is corrupt; readers
|
|
\MUST{} surface this as a hard error and \MUSTNOT{}
|
|
synthesize state.
|
|
\end{enumerate}
|
|
\end{requirement}
|
|
|
|
\begin{rationale}
|
|
The advisory fields are excluded from the equal-generation
|
|
comparison because they do not determine which canonical state a
|
|
slot selects: \texttt{commit\_timestamp} is wall-clock metadata,
|
|
and the physical offset/length merely locate the manifest chunk in
|
|
the file. Two slots that agree on the four load-bearing fields name
|
|
the same canonical document even if they were written at different
|
|
times or to different offsets, so treating an advisory-only
|
|
difference as divergence would raise false anomalies on benign
|
|
repacks. Conversely, a difference in any load-bearing field means
|
|
the two slots disagree about canonical state itself, which is the
|
|
precise condition that must not be silently resolved.
|
|
\end{rationale}
|
|
|
|
\section{The Manifest}
|
|
\label{sec:format:manifest}
|
|
|
|
The manifest is a table of roots and declarations. It is itself a
|
|
chunk (content-addressed, immutable). Each commit writes a new
|
|
manifest chunk and points the inactive superblock slot at it.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct Manifest {
|
|
/// Identifier of the logical work. Stable across Save As
|
|
/// operations and across copies that are intended to remain
|
|
/// versions of the same work. May persist across forks if
|
|
/// the user explicitly preserves it.
|
|
pub document_id: DocumentId,
|
|
|
|
/// Optional identifier of a shared ancestor document. Used by
|
|
/// version-control workflows and fork-tracking to express
|
|
/// genealogy. Absent for documents with no declared ancestry.
|
|
pub lineage_id: Option<LineageId>,
|
|
|
|
/// Identifier of this manifest. Each commit produces a new
|
|
/// ManifestId; old manifests are retained in the chunk store
|
|
/// until garbage collection removes them subject to the
|
|
/// retention policy.
|
|
pub manifest_id: ManifestId,
|
|
|
|
/// Generation counter matching the superblock that references
|
|
/// this manifest.
|
|
pub generation: u64,
|
|
|
|
/// Operation-envelope blocks defining the canonical document.
|
|
/// Block ordering is irrelevant for canonical state (envelopes
|
|
/// are a set), and the canonical manifest encoding sorts chunk
|
|
/// references ascending by their encoded form (Appendix D), so
|
|
/// a writer cannot order this list by stamp; stamp-ordered
|
|
/// scanning goes through operation_block_summaries below.
|
|
pub operation_roots: Vec<ChunkRef>,
|
|
|
|
/// Per-block summary metadata (Section on operation-envelope
|
|
/// blocks below), keyed by the block's ChunkId in canonical
|
|
/// ascending order. Supplied by the operation layer and carried
|
|
/// opaquely by the storage layer; non-canonical and optional (a
|
|
/// block need not have an entry). Lets a reader select or skip
|
|
/// a block by causal frontier or stamp range without decoding
|
|
/// its envelopes.
|
|
pub operation_block_summaries:
|
|
BTreeMap<ChunkId, OperationBlockSummary>,
|
|
|
|
/// Optional operation index chunk: maps OperationId to
|
|
/// (block, offset) for fast random access. If absent,
|
|
/// implementations rebuild on demand by scanning blocks.
|
|
pub operation_index_root: Option<ChunkRef>,
|
|
|
|
/// The active canonical-base snapshot, if pruning has occurred.
|
|
/// At most one canonical base is active at a time. When
|
|
/// present, the canonical document is the base snapshot's
|
|
/// materialized state plus the deterministic reduction of
|
|
/// envelopes whose OperationIds are not covered by the base's
|
|
/// causal frontier.
|
|
pub canonical_base: Option<SnapshotRef>,
|
|
|
|
/// Acceleration snapshots: caches that materialize state at
|
|
/// various causal frontiers for fast incremental loading.
|
|
/// These MAY be discarded freely without affecting canonical
|
|
/// state.
|
|
pub acceleration_snapshots: Vec<SnapshotRef>,
|
|
|
|
/// Blob references for large opaque content (audio, images,
|
|
/// custom fonts).
|
|
pub blob_roots: Vec<BlobRef>,
|
|
|
|
/// Profile declarations: which conformance profiles this
|
|
/// manifest claims to satisfy.
|
|
pub profile_declarations: Vec<ProfileDeclaration>,
|
|
|
|
/// Extension declarations: which extensions contributed to
|
|
/// this manifest, with their required/optional flag and
|
|
/// edit barriers.
|
|
pub extension_declarations: Vec<ExtensionDeclaration>,
|
|
|
|
/// Text projection root chunk, if a text projection is
|
|
/// maintained in this bundle. Non-canonical accelerator.
|
|
pub text_projection_root: Option<ChunkRef>,
|
|
|
|
/// Optional integrity index root chunk: cross-references chunk
|
|
/// hashes for faster integrity verification of large bundles.
|
|
/// Non-canonical accelerator; absence is permitted.
|
|
pub integrity_root: Option<ChunkRef>,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsection{Canonical and Non-Canonical Manifest Roots}
|
|
\label{sec:format:roots}
|
|
|
|
The manifest's fields partition cleanly into two groups by their
|
|
relation to canonical state.
|
|
|
|
\textbf{Canonical roots} (define the document):
|
|
|
|
\begin{itemize}
|
|
\item \texttt{operation\_roots}: operation-envelope blocks
|
|
containing the canonical operation set.
|
|
\item \texttt{canonical\_base}: the active canonical base
|
|
snapshot, if pruning has occurred.
|
|
\item \texttt{blob\_roots}: blobs referenced by canonical
|
|
operations or by canonical reduced state are themselves
|
|
canonical; blobs referenced only by acceleration structures
|
|
are non-canonical.
|
|
\item \texttt{document\_id}, \texttt{lineage\_id},
|
|
\texttt{profile\_declarations},
|
|
\texttt{extension\_declarations}: structural metadata
|
|
governing how the canonical document is interpreted.
|
|
\end{itemize}
|
|
|
|
\textbf{Non-canonical reachable roots} (accelerators only):
|
|
|
|
\begin{itemize}
|
|
\item \texttt{operation\_index\_root}: rebuildable from
|
|
operation envelope blocks.
|
|
\item \texttt{acceleration\_snapshots}: caches at causal
|
|
frontiers other than the canonical base.
|
|
\item \texttt{text\_projection\_root}: derivable from canonical
|
|
state.
|
|
\item \texttt{integrity\_root}: rebuildable from chunk hashes.
|
|
\end{itemize}
|
|
|
|
\begin{requirement}
|
|
\label{req:format:manifest-reachability}
|
|
The manifest \MUST{} list every chunk that contributes to the
|
|
current canonical state, transitively through its canonical
|
|
roots (operation roots, canonical base, canonical blobs and
|
|
extension chunks). It \MAY{} additionally list non-canonical
|
|
reachable roots (acceleration snapshots, operation index, text
|
|
projection, integrity index, layout caches).
|
|
|
|
Chunks not reachable from any manifest root, canonical or
|
|
non-canonical, are unreachable; readers \MUSTNOT{} rely on
|
|
them and conservative garbage collection \MAY{} reclaim them
|
|
(Section~\ref{sec:format:gc}).
|
|
|
|
Non-canonical reachable chunks \MAY{} be rebuilt or replaced
|
|
without altering canonical state. If a non-canonical chunk's
|
|
hash fails verification, it \MUST{} be discarded and (if
|
|
needed) rebuilt; failed verification of a non-canonical chunk
|
|
is \emph{not} a corruption of the bundle.
|
|
|
|
Failed verification of a canonical chunk (operation envelope
|
|
block, canonical base snapshot, canonical blob, manifest)
|
|
\emph{is} corruption and \MUST{} be surfaced as a hard error.
|
|
|
|
User-facing metadata (title, composer, lyricist, copyright, and
|
|
similar fields) is \emph{not} part of the manifest. Such metadata
|
|
lives in the operation set and the materialized state. The
|
|
manifest carries only the structural roots and declarations that
|
|
the format needs to locate and validate content.
|
|
\end{requirement}
|
|
|
|
\subsection{File, Document, and Lineage Identity}
|
|
\label{sec:format:identity}
|
|
|
|
The format distinguishes three identifiers, each serving a
|
|
different identity question:
|
|
|
|
\begin{description}
|
|
\item[\texttt{file\_uuid}.] Physical bundle identity. Set at
|
|
file creation; persists for the lifetime of the physical
|
|
file. \textbf{Changes on Save As}: a copy created by Save As
|
|
is a new physical bundle and \MUST{} be assigned a fresh
|
|
\texttt{file\_uuid}. Used by tools that need to identify
|
|
``this exact file on disk'' (caches, backup software, file
|
|
fingerprinting).
|
|
\item[\texttt{document\_id}.] Logical work identity. Stable
|
|
across Save As copies that are intended to remain the same
|
|
work (the canonical case: ``I'm saving my piece to a backup
|
|
folder, and it's still the same piece''). \textbf{Persists
|
|
across forks at the user's discretion}: a fork that
|
|
represents a derivative work \SHOULD{} have a new
|
|
\texttt{document\_id}, while a fork that is a working copy
|
|
of the original \SHOULD{} preserve it. The format does not
|
|
enforce a policy; the editor surfaces the choice.
|
|
\item[\texttt{lineage\_id}.] Optional shared ancestor identity.
|
|
Records that two documents share a common ancestor for
|
|
version-control or genealogy purposes. Multiple documents
|
|
\MAY{} share a \texttt{lineage\_id}; the field is set when a
|
|
fork explicitly preserves ancestry information.
|
|
\end{description}
|
|
|
|
\begin{requirement}
|
|
\label{req:format:file-document-lineage-identity}
|
|
Implementations \MUST{} assign a fresh \texttt{file\_uuid} on
|
|
any Save As operation. They \MUSTNOT{} carry the source file's
|
|
UUID into the new file: doing so would conflate physical and
|
|
logical identity and break tools that rely on
|
|
\texttt{file\_uuid} for content fingerprinting.
|
|
|
|
The choice of whether to preserve \texttt{document\_id} across
|
|
a Save As is exposed to the user: the editor \SHOULD{} default
|
|
to preserving \texttt{document\_id} (the common case is
|
|
``save a copy of the same work to a new file'') and \SHOULD{}
|
|
offer a clear ``Save As Derivative Work'' or equivalent action
|
|
that mints a new \texttt{document\_id}.
|
|
|
|
The format \MUSTNOT{} treat documents with the same
|
|
\texttt{document\_id} as automatically synchronizable: that
|
|
decision belongs to the collaboration transport
|
|
(Appendix~\ref{app:deferred}).
|
|
\end{requirement}
|
|
|
|
\subsection{Manifest Encoding}
|
|
\label{sec:format:manifest-encoding}
|
|
|
|
The manifest chunk is special: it is the bootstrap entry from the
|
|
superblock into the chunk graph. A cold reader needs to decode the
|
|
manifest before it has any information from the chunk store.
|
|
|
|
\begin{requirement}
|
|
\label{req:format:uncompressed-manifest}
|
|
In this format version, the manifest chunk \MUST{} be stored
|
|
\emph{uncompressed}. Its \texttt{CompressionAlgorithm} on disk
|
|
is implicitly \texttt{None}; the superblock's
|
|
\texttt{manifest\_length} field is therefore both the on-disk
|
|
length and the uncompressed payload length. Implementations
|
|
\MUST{} reject as malformed any bundle whose manifest payload
|
|
is not directly parseable as a canonical manifest chunk's
|
|
uncompressed bytes.
|
|
|
|
This rule eliminates a bootstrap chicken-and-egg problem: a
|
|
reader can decode the manifest using only information present
|
|
in the fixed header and the active superblock, without first
|
|
consulting the manifest to learn how the manifest is compressed.
|
|
|
|
All other chunks (operation envelope blocks, snapshots,
|
|
extension data, layout caches, blobs, integrity indexes,
|
|
operation indexes, text projection chunks) \MAY{} be compressed.
|
|
Future format major versions \MAY{} permit manifest compression,
|
|
but doing so requires explicit superblock fields
|
|
(\texttt{manifest\_compression},
|
|
\texttt{manifest\_uncompressed\_length}) and is a
|
|
non-backward-compatible change.
|
|
\end{requirement}
|
|
|
|
\begin{rationale}
|
|
The manifest is small (a few kilobytes at most for typical
|
|
scores), so compression yields negligible space savings.
|
|
Eliminating the bootstrap dependency is far more valuable than
|
|
the few hundred bytes of compression headroom.
|
|
\end{rationale}
|
|
|
|
\begin{requirement}
|
|
\label{req:format:manifest-id}
|
|
The \texttt{manifest\_id} field \MUST{} be derived as
|
|
\[
|
|
\texttt{ManifestId} = \mathrm{trunc128}\!\left(
|
|
\mathrm{BLAKE3}(\texttt{"MUSCMNIF"} \Vert \texttt{document\_id}
|
|
\Vert \texttt{generation} \Vert \texttt{manifest\_body})\right),
|
|
\]
|
|
where \texttt{document\_id} is its 16-byte canonical form,
|
|
\texttt{generation} is the 8-byte little-endian \texttt{u64}, and
|
|
\texttt{manifest\_body} is the canonical manifest encoding with the
|
|
\texttt{manifest\_id} field itself \emph{excluded}. The exclusion is
|
|
normative: a conforming writer \MUST{} omit (equivalently, zero) the
|
|
\texttt{manifest\_id} field when computing the preimage, so that the
|
|
identifier does not reference itself. $\mathrm{trunc128}$ takes the
|
|
leading 16 bytes of the 32-byte BLAKE3 output. Two conforming
|
|
writers committing the same manifest body at the same generation of
|
|
the same document therefore derive identical \texttt{ManifestId}s.
|
|
\end{requirement}
|
|
|
|
\section{Content Hashing}
|
|
\label{sec:format:hashing}
|
|
|
|
Epiphany uses BLAKE3 as its single content-hashing algorithm.
|
|
|
|
\begin{requirement}
|
|
\label{req:format:blake3-content-hashes}
|
|
All content hashes in the bundle \MUST{} be BLAKE3-256 outputs
|
|
(32 bytes). Implementations \MUSTNOT{} use any other algorithm
|
|
for content addressing; SHA-256, SHA-3, and other algorithms
|
|
\MUSTNOT{} appear in this version of the format. Future major
|
|
versions \MAY{} introduce additional algorithms; such changes
|
|
require a new format major version.
|
|
\end{requirement}
|
|
|
|
\subsection{Domain-Separated Preimages}
|
|
|
|
A naive content-addressing scheme that hashes only payload bytes is
|
|
vulnerable to collisions across chunks of different semantic kinds.
|
|
Epiphany uses domain-separated preimages.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
/// Computes the canonical hash preimage for a chunk's content.
|
|
/// The chunk's compression algorithm is metadata on the ChunkRef,
|
|
/// not part of the content identity: identical uncompressed
|
|
/// payloads produce identical hashes regardless of how they are
|
|
/// physically compressed in storage. This preserves deduplication
|
|
/// across compression choices.
|
|
fn hash_preimage(
|
|
domain_tag: &[u8; 8],
|
|
chunk_kind: ChunkKind,
|
|
schema_version: SchemaVersion,
|
|
uncompressed_length: u64,
|
|
uncompressed_payload: &[u8],
|
|
) -> Vec<u8> {
|
|
let mut preimage = Vec::new();
|
|
preimage.extend_from_slice(domain_tag);
|
|
preimage.extend_from_slice(&chunk_kind.canonical_bytes());
|
|
preimage.extend_from_slice(&schema_version.canonical_bytes());
|
|
preimage.extend_from_slice(&uncompressed_length.to_le_bytes());
|
|
preimage.extend_from_slice(uncompressed_payload);
|
|
preimage
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\begin{requirement}
|
|
\label{req:format:domain-separated-chunk-hashes}
|
|
The content hash of a chunk \MUST{} be BLAKE3 of the canonical
|
|
preimage above. The domain tag, chunk kind, schema version, and
|
|
uncompressed length are part of the preimage; the compression
|
|
algorithm is \emph{not}. Two chunks with identical uncompressed
|
|
payloads, identical kinds, and identical schema versions \MUST{}
|
|
produce identical hashes regardless of their compression choices.
|
|
\end{requirement}
|
|
|
|
\begin{rationale}
|
|
Including compression algorithm in the hash would cause
|
|
identical content stored with different compression to have
|
|
different addresses, defeating deduplication and forcing churn
|
|
during recompression or repack. Including domain tag, kind, and
|
|
schema version in the preimage closes a real collision: two
|
|
semantically different chunks with identical raw bytes are no
|
|
longer interchangeable.
|
|
|
|
The domain tag is a fixed 8-byte string identifying the bundle
|
|
format and protecting against cross-format collisions if the
|
|
same BLAKE3 hash were reused in unrelated contexts. The canonical
|
|
domain tag for \texttt{.musc} chunks is \texttt{"MUSCCHNK"};
|
|
manifest chunks use \texttt{"MUSCMANI"}; blob payloads use
|
|
\texttt{"MUSCBLOB"}.
|
|
\end{rationale}
|
|
|
|
\section{Chunks}
|
|
\label{sec:format:chunks}
|
|
|
|
A chunk is an immutable, content-addressed unit of storage. Chunks
|
|
are referenced from the manifest by \texttt{ChunkRef}.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct ChunkRef {
|
|
/// Content identifier: BLAKE3 of the canonical preimage.
|
|
pub id: ChunkId,
|
|
|
|
/// Kind of chunk. The reader uses this to dispatch parsing.
|
|
pub kind: ChunkKind,
|
|
|
|
/// Schema version targeted by this chunk's payload encoding.
|
|
pub schema_version: SchemaVersion,
|
|
|
|
/// Offset of the chunk's compressed payload in the bundle file.
|
|
pub offset: u64,
|
|
|
|
/// Length of the on-disk compressed payload.
|
|
pub compressed_length: u64,
|
|
|
|
/// Length of the uncompressed payload (used to size buffers
|
|
/// before decompression).
|
|
pub uncompressed_length: u64,
|
|
|
|
/// Compression algorithm. Part of the ChunkRef, not part of
|
|
/// the chunk's identity.
|
|
pub compression: CompressionAlgorithm,
|
|
|
|
/// Restated content hash, redundant with the id field but
|
|
/// included in the ChunkRef for fast verification without
|
|
/// fetching the chunk.
|
|
pub hash: ContentHash,
|
|
}
|
|
|
|
pub enum ChunkKind {
|
|
/// A block of operation envelopes.
|
|
OperationEnvelopeBlock,
|
|
|
|
/// An operation-id-to-block index accelerator.
|
|
OperationIndex,
|
|
|
|
/// A materialized snapshot of canonical state (full or
|
|
/// partial). Canonical if referenced as the manifest's
|
|
/// canonical_base; cache (acceleration) otherwise.
|
|
Snapshot,
|
|
|
|
/// A large opaque blob (audio, image, font, ML model).
|
|
Blob,
|
|
|
|
/// Extension-defined data, preserved opaquely by core readers.
|
|
ExtensionData,
|
|
|
|
/// The text projection's root document.
|
|
TextProjection,
|
|
|
|
/// Layout cache: derivative from canonical state, always
|
|
/// discardable.
|
|
LayoutCache,
|
|
|
|
/// Integrity index: cross-references chunk hashes.
|
|
IntegrityIndex,
|
|
|
|
/// Manifest chunk. Special: referenced from the superblock,
|
|
/// not from another chunk.
|
|
Manifest,
|
|
}
|
|
|
|
pub enum CompressionAlgorithm {
|
|
/// No compression. Stored payload bytes equal uncompressed.
|
|
None,
|
|
|
|
/// Zstandard compression. Implementations MUST support reading.
|
|
Zstd { level: u8 },
|
|
|
|
/// Other algorithms reserved for future versions.
|
|
Reserved(u8),
|
|
}
|
|
|
|
pub struct ContentHash(pub [u8; 32]);
|
|
|
|
/// Chunk identifier. A newtype around ContentHash: the chunk's
|
|
/// identifier is identical to its content hash, but the type
|
|
/// distinction is preserved to make role visible at use sites
|
|
/// (a ChunkId is what you store in a ChunkRef and what you look
|
|
/// up; a ContentHash is what you compute by hashing). Both occupy
|
|
/// the same 32 bytes.
|
|
pub struct ChunkId(pub ContentHash);
|
|
\end{lstlisting}
|
|
|
|
\begin{requirement}
|
|
\label{req:format:chunkkind-discriminants}
|
|
\texttt{ChunkKind::canonical\_bytes()} is a single byte: the
|
|
variant's declaration-order discriminant. Because that byte is part
|
|
of the chunk hash preimage (Section~\ref{sec:format:chunks},
|
|
``Domain-Separated Preimages''), this assignment is normative and
|
|
stable and \MUSTNOT{} be reordered:
|
|
|
|
\begin{center}
|
|
\begin{tabular}{r l @{\qquad} r l}
|
|
\toprule
|
|
\textbf{Disc.} & \textbf{Variant} &
|
|
\textbf{Disc.} & \textbf{Variant} \\
|
|
\midrule
|
|
0 & \texttt{OperationEnvelopeBlock} & 5 & \texttt{TextProjection} \\
|
|
1 & \texttt{OperationIndex} & 6 & \texttt{LayoutCache} \\
|
|
2 & \texttt{Snapshot} & 7 & \texttt{IntegrityIndex} \\
|
|
3 & \texttt{Blob} & 8 & \texttt{Manifest} \\
|
|
4 & \texttt{ExtensionData} & & \\
|
|
\bottomrule
|
|
\end{tabular}
|
|
\end{center}
|
|
|
|
\texttt{CompressionAlgorithm} encodes as a fixed \emph{two} bytes: a
|
|
declaration-order discriminant byte followed by a single parameter
|
|
byte that is always present. \texttt{None} $= 0$, and its parameter
|
|
byte \MUST{} be zero --- a reader \MUST{} reject a non-zero one rather
|
|
than ignore it (Section~\ref{sec:format:binary}, and the Binary Format
|
|
companion's \sectionsc{Chunk References}). \texttt{Zstd\{level\}}
|
|
$= 1$ with the \texttt{level} in the parameter byte;
|
|
\texttt{Reserved(u8)} $= 2$ with the reserved value in the parameter
|
|
byte. The parameter byte is written even for \texttt{None}, so the
|
|
encoding is fixed-width and a reader always consumes two bytes.
|
|
Compression is \emph{not} part of a
|
|
chunk's content identity (it is metadata on the \texttt{ChunkRef}),
|
|
so these discriminants are stable but do not affect chunk hashes.
|
|
|
|
\texttt{ProfileId}'s discriminants are pinned in
|
|
Section~\ref{sec:format:profiles}
|
|
(Requirement~\ref{req:format:profileid-discriminants}).
|
|
\end{requirement}
|
|
|
|
\begin{requirement}
|
|
\label{req:format:immutable-verified-chunks}
|
|
Chunks \MUST{} be immutable. Once a chunk is written and its bytes
|
|
durably flushed, those bytes \MUSTNOT{} be modified. New state
|
|
is encoded as new chunks with new identifiers; old chunks become
|
|
unreachable garbage when their referencing manifests are no
|
|
longer reachable.
|
|
|
|
Implementations \MUST{} verify chunk hashes on first read of each
|
|
chunk in a session: decompress the payload, compute the canonical
|
|
preimage's BLAKE3, and confirm the result matches the declared
|
|
\texttt{hash}. Subsequent reads \MAY{} skip verification if the
|
|
chunk has been validated within the current session. A chunk
|
|
whose hash does not match \MUST{} be treated as corrupt: if it is
|
|
a cache chunk, discard and rebuild; if it is a canonical chunk
|
|
(operation block, retained snapshot, or blob referenced from the
|
|
manifest), surface as hard corruption.
|
|
|
|
Readers \MUST{} verify that decompression of a chunk's payload
|
|
produces exactly \texttt{uncompressed\_length} bytes. Length
|
|
mismatch is treated as corruption.
|
|
\end{requirement}
|
|
|
|
\section{Operation Envelope Blocks}
|
|
\label{sec:format:opblocks}
|
|
|
|
The canonical document is stored as operation-envelope blocks. Each
|
|
block is a chunk of kind \texttt{OperationEnvelopeBlock} whose
|
|
payload is a vector of envelopes. A block is identified by its
|
|
\texttt{ChunkId}; because chunks are content-addressed, that
|
|
identifier cannot appear inside the block payload itself. Summary
|
|
metadata for a block travels in the \emph{manifest}
|
|
(\texttt{operation\_block\_summaries}), keyed by the block's chunk
|
|
id: the summary is computed by the operation layer from the block's
|
|
envelopes, and the storage layer carries it as opaque bytes without
|
|
interpreting it.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct OperationEnvelopeBlock {
|
|
/// Operation envelopes in this block. Order within a block is
|
|
/// not normative for canonical reduction (envelopes are a set)
|
|
/// but writers SHOULD preserve authoring order within a block
|
|
/// for diagnostic purposes.
|
|
pub envelopes: Vec<OperationEnvelope>,
|
|
}
|
|
|
|
/// Manifest-side summary of one block, keyed by the block's
|
|
/// ChunkId. Non-canonical and optional: a reader MUST NOT rely
|
|
/// on a summary being present, and canonical state never depends
|
|
/// on one.
|
|
pub struct OperationBlockSummary {
|
|
/// DVV summary covering the envelopes in this block. Enables
|
|
/// readers to determine causal coverage without parsing every
|
|
/// envelope.
|
|
pub dvv_summary: CausalContext,
|
|
|
|
/// Earliest stamp in the block.
|
|
pub min_stamp: OperationStamp,
|
|
|
|
/// Latest stamp in the block.
|
|
pub max_stamp: OperationStamp,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\begin{requirement}
|
|
\label{req:format:operation-block-bounds}
|
|
Writers \SHOULD{} begin a new operation-envelope block when
|
|
adding another envelope would cause the uncompressed block
|
|
payload to exceed 1 \,MiB, except when an individual envelope's
|
|
encoded size exceeds 1\,MiB, in which case the envelope occupies
|
|
its own block.
|
|
|
|
Readers \MUST{} accept valid blocks of any size up to a profile-
|
|
declared maximum (default: 64\,MiB uncompressed). Profile
|
|
declarations \MAY{} raise or lower this bound. Blocks exceeding
|
|
the active profile's bound \MUST{} be treated as malformed.
|
|
|
|
The \texttt{operation\_roots} field of the manifest \MUST{} list
|
|
every operation-envelope block that contributes to the canonical
|
|
document. The set of envelopes \emph{is} the union of all
|
|
envelopes across all referenced blocks; block boundaries are
|
|
storage artifacts, not semantic structure.
|
|
\end{requirement}
|
|
|
|
\subsection{The Operation Index}
|
|
|
|
The optional \texttt{operation\_index\_root} chunk maps each
|
|
\texttt{OperationId} to the \texttt{ChunkRef} of its enclosing
|
|
block plus an offset within the block. It enables $O(\log n)$
|
|
lookup of an operation by id.
|
|
|
|
\begin{requirement}
|
|
\label{req:format:rebuildable-operation-index}
|
|
The operation index is an acceleration structure, not canonical.
|
|
If absent, readers rebuild it by scanning all blocks. If present
|
|
but corrupt or stale, readers \MUST{} reject the index and
|
|
rebuild from blocks.
|
|
|
|
Writers \SHOULD{} rebuild or incrementally update the operation
|
|
index at commit time when the operation set has grown
|
|
significantly since the last commit.
|
|
\end{requirement}
|
|
|
|
\section{Snapshots and Canonical Bases}
|
|
\label{sec:format:snapshots}
|
|
|
|
A snapshot is a materialized score state at a particular causal
|
|
frontier. Snapshots have two distinct roles:
|
|
|
|
\begin{description}
|
|
\item[Acceleration snapshot.] A cache of the materialized state at
|
|
some point, used to skip reduction work. Listed in the manifest
|
|
(or not), but \emph{not} declared as a canonical base.
|
|
Discardable.
|
|
\item[Canonical base.] A materialized state at a causal frontier
|
|
that the manifest declares as the base after pruning. Together
|
|
with the operation envelopes after the frontier, this
|
|
constitutes the canonical document. Not discardable while it
|
|
remains declared.
|
|
\end{description}
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct SnapshotRef {
|
|
pub snapshot_id: SnapshotId,
|
|
|
|
/// The causal frontier this snapshot materializes: the
|
|
/// envelopes whose effects are baked into the snapshot.
|
|
pub covers_causal_frontier: CausalContext,
|
|
|
|
/// Reduction algorithm version under which the snapshot was
|
|
/// produced. Snapshots produced under an earlier algorithm
|
|
/// version cannot be used as canonical bases under a later
|
|
/// algorithm without rebuilding.
|
|
pub reduction_algorithm_version: ReductionAlgorithmVersion,
|
|
|
|
/// Profile under which the snapshot was produced.
|
|
pub profile_id: ProfileId,
|
|
|
|
/// Root chunk of the snapshot's materialized state.
|
|
pub root: ChunkRef,
|
|
|
|
/// Hash of the snapshot's root chunk for fast verification.
|
|
pub hash: ContentHash,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsection{The Canonical Document Identity}
|
|
|
|
\begin{requirement}
|
|
\label{req:format:canonical-document-reduction}
|
|
The canonical document is defined as follows:
|
|
|
|
\begin{itemize}
|
|
\item If \texttt{canonical\_base} is \texttt{None}, the
|
|
canonical document is the deterministic reduction
|
|
(Section~\ref{sec:semops:reduction}) of the union of
|
|
envelopes in all \texttt{operation\_roots} blocks.
|
|
\item If \texttt{canonical\_base} is \texttt{Some(base)}, the
|
|
canonical document is the base snapshot's materialized
|
|
state plus the deterministic reduction of all envelopes
|
|
whose \texttt{OperationId} is \emph{not covered} by the
|
|
base's \texttt{covers\_causal\_frontier}. An
|
|
\texttt{OperationId} is covered by a \texttt{CausalContext}
|
|
if and only if it appears in the dotted version vector's
|
|
per-replica contiguous range or among its dots (i.e.,
|
|
causal-precedes-or-equals under the DVV's induced partial
|
|
order). HLC stamps \MUSTNOT{} be used to determine
|
|
coverage; only the DVV frontier is canonical.
|
|
\item A snapshot may serve as the canonical base only if its
|
|
\texttt{reduction\_algorithm\_version} equals the active
|
|
superblock's \texttt{reduction\_algorithm\_version} and its
|
|
\texttt{profile\_id} matches the manifest's profile
|
|
declarations.
|
|
\end{itemize}
|
|
|
|
At most one \texttt{canonical\_base} is active at any time.
|
|
Pruning replaces the current canonical base with a new one
|
|
whose frontier strictly dominates the previous frontier under
|
|
the DVV partial order; the old base becomes unreachable and
|
|
is subject to garbage collection.
|
|
|
|
Acceleration snapshots (\texttt{acceleration\_snapshots}) \MUST{}
|
|
be ignored for the purposes of canonical state. They exist to
|
|
accelerate cold open and incremental reduction; if their content
|
|
disagrees with what the canonical reduction would produce, they
|
|
\MUST{} be rebuilt or discarded.
|
|
|
|
Caches, indexes, layout chunks, and other materialized artifacts
|
|
in the bundle are acceleration structures only and \MUST{} be
|
|
ignored or rebuilt if they disagree with the canonical document.
|
|
\end{requirement}
|
|
|
|
\subsection{Pruning}
|
|
|
|
\begin{requirement}
|
|
\label{req:format:pruning-state-preservation}
|
|
Pruning is the act of removing operation envelopes that are
|
|
causally covered by a new canonical base snapshot. Pruning:
|
|
|
|
\begin{enumerate}
|
|
\item Materializes a snapshot covering the chosen causal
|
|
frontier (a DVV).
|
|
\item Replaces the current \texttt{canonical\_base} field
|
|
with a \texttt{SnapshotRef} naming the new snapshot.
|
|
\item Removes operation-envelope blocks whose envelopes are
|
|
\emph{entirely} covered by the new frontier. Blocks with
|
|
mixed coverage \MUST{} be split or retained whole;
|
|
uncovered envelopes \MUST{} remain reachable.
|
|
\item Commits the new manifest via the atomic write protocol
|
|
(Section~\ref{sec:format:commit}).
|
|
\end{enumerate}
|
|
|
|
Pruning \MUSTNOT{} alter the canonical document state: the
|
|
reduction of (base snapshot $+$ post-frontier envelopes) under
|
|
the active algorithm \MUST{} equal the reduction of the
|
|
pre-pruning envelope set.
|
|
|
|
Implementations \MUSTNOT{} prune envelopes outside the explicit
|
|
pruning protocol. Operation envelopes are not silently garbage-
|
|
collected based on age or other heuristics.
|
|
\end{requirement}
|
|
|
|
\section{Blobs}
|
|
\label{sec:format:blobs}
|
|
|
|
The blob store holds large opaque content: audio reference tracks,
|
|
embedded images, custom fonts, ML model checkpoints.
|
|
|
|
\begin{requirement}
|
|
\label{req:format:blob-hash-shape}
|
|
A \texttt{BlobId} \MUST{} be BLAKE3 over the \emph{bare} preimage
|
|
\texttt{"MUSCBLOB"} $\Vert$ \texttt{uncompressed\_payload}: the
|
|
8-byte domain tag immediately followed by the uncompressed payload
|
|
bytes, with \emph{no} chunk-kind, schema-version, or length fields.
|
|
This is deliberately \emph{unlike} the structured chunk preimage of
|
|
Section~\ref{sec:format:chunks} (which commits to kind, schema
|
|
version, and length): a blob is opaque payload with no schema, so
|
|
its identity commits only to the domain tag and the bytes. As with
|
|
chunks, the compression algorithm is metadata on the
|
|
\texttt{BlobRef} and is \emph{not} part of the blob's identity, so
|
|
identical payloads stored under different compression share a
|
|
\texttt{BlobId}.
|
|
\end{requirement}
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct BlobRef {
|
|
pub blob_id: BlobId,
|
|
pub media_type: String, // RFC 6838 media type
|
|
pub offset: u64,
|
|
pub compressed_length: u64,
|
|
pub uncompressed_length: u64,
|
|
pub compression: CompressionAlgorithm,
|
|
pub hash: ContentHash,
|
|
|
|
/// Optional declared maximum size that readers MAY use to
|
|
/// reject blobs that exceed reader resource limits.
|
|
pub declared_max_uncompressed_length: Option<u64>,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\begin{requirement}
|
|
\label{req:format:lazy-bounded-blobs}
|
|
Readers \MUST{} apply resource limits to blob loading:
|
|
uncompressed length \MUST{} be checked against the reader's
|
|
policy before decompression begins; \texttt{media\_type} \MUST{}
|
|
be validated against the reader's accepted list before any
|
|
type-specific parsing.
|
|
|
|
Implementations \MUSTNOT{} eagerly load all blobs at open;
|
|
blobs \MUST{} be streamed on demand. Operations requiring blob
|
|
content \MUST{} be designed for lazy access.
|
|
|
|
Blob content is opaque to the core. The core stores, transmits,
|
|
and integrity-checks blobs; it does not interpret their bytes.
|
|
\end{requirement}
|
|
|
|
\section{The Atomic Write Protocol}
|
|
\label{sec:format:commit}
|
|
|
|
A commit transforms the bundle from one canonical state to another.
|
|
The protocol uses two superblock slots so that a crash at any point
|
|
during commit leaves the bundle in either the pre-commit state
|
|
(active superblock unchanged) or the post-commit state (active
|
|
superblock now points at the new manifest); no intermediate state
|
|
is visible.
|
|
|
|
\begin{requirement}
|
|
\label{req:format:atomic-commit-sequence}
|
|
Commits \MUST{} follow this sequence:
|
|
|
|
\begin{enumerate}
|
|
\item Write all new chunks (envelope blocks, possibly a new
|
|
snapshot, possibly a new operation index, etc.) to file
|
|
regions outside the prelude and outside currently-reachable
|
|
chunks. Writers \MAY{} append at end-of-file or fill known-
|
|
unreachable regions, but \MUSTNOT{} overwrite any
|
|
currently-reachable chunk.
|
|
\item Durably flush the file.
|
|
\item Compute the new manifest chunk's payload and write it.
|
|
\item Durably flush the file.
|
|
\item Compute the new \texttt{Superblock} with generation
|
|
$= \text{active\_generation} + 1$ and \texttt{manifest\_offset}
|
|
pointing at the new manifest chunk.
|
|
\item Write the new superblock to the currently-inactive
|
|
superblock slot (a 256-byte write at a fixed offset).
|
|
\item Durably flush the file. \emph{This is the commit point.}
|
|
Before this flush returns successfully, the active superblock
|
|
is the old one; after it returns successfully, the active
|
|
superblock is the new one.
|
|
\end{enumerate}
|
|
|
|
After step 7, the previously-active superblock slot becomes the
|
|
inactive slot for the next commit. Garbage collection is a
|
|
separate, optional, deferred operation (Section~\ref{sec:format:gc}).
|
|
\end{requirement}
|
|
|
|
\subsection{Crash Recovery}
|
|
|
|
\begin{requirement}
|
|
\label{req:format:crash-recovery}
|
|
On open after any crash, recovery follows the superblock
|
|
selection rule (Section~\ref{sec:format:bundle}). The bundle is
|
|
always in a recoverable state:
|
|
|
|
\begin{itemize}
|
|
\item Crash between steps 1 and 7: the active superblock is
|
|
unchanged; the new chunks and the new manifest are
|
|
unreachable garbage; the document opens at the pre-commit
|
|
state. A later GC reclaims the orphaned bytes.
|
|
\item Crash during step 7: the superblock slot write may be
|
|
torn. The CRC check rejects torn writes. Recovery falls back
|
|
to the other slot. The document opens at the pre-commit
|
|
state. The partially-written slot will be overwritten by the
|
|
next successful commit.
|
|
\item Crash after step 7: the new superblock is active. The
|
|
document opens at the post-commit state. No special
|
|
recovery is needed.
|
|
\end{itemize}
|
|
|
|
Recovery \MUSTNOT{} synthesize or guess: if neither superblock
|
|
validates, the file is corrupt and \MUST{} be reported as such.
|
|
\end{requirement}
|
|
|
|
\section{Garbage Collection and Retention}
|
|
\label{sec:format:gc}
|
|
|
|
Garbage collection reclaims bytes occupied by chunks that are no
|
|
longer reachable from any retained manifest. Retention policy
|
|
governs which old manifests are preserved for rollback.
|
|
|
|
\subsection{Retention Policy}
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct RetentionPolicy {
|
|
/// Maximum number of previous manifests to retain beyond the
|
|
/// active one. 0 means "no rollback retention"; the active
|
|
/// manifest is the only one preserved. The default declared by
|
|
/// the Full profile is profile-defined.
|
|
pub retain_previous_manifests: u32,
|
|
|
|
/// Optional wall-clock duration beyond which old retained
|
|
/// manifests may be reclaimed regardless of count. None means
|
|
/// no time-based eviction.
|
|
pub retain_duration: Option<WallClockDuration>,
|
|
|
|
/// Whether to preserve manifests explicitly marked as named
|
|
/// checkpoints (user-invoked "save a version" action) beyond
|
|
/// the count and duration limits.
|
|
pub retain_named_checkpoints: bool,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\begin{requirement}
|
|
\label{req:format:manifest-retention}
|
|
The active conformance profile \MUST{} declare a
|
|
\texttt{RetentionPolicy}. Implementations \MUST{} honor the
|
|
declared policy: chunks reachable from any retained manifest
|
|
\MUSTNOT{} be garbage-collected, and retention selection
|
|
(which manifests are retained) \MUST{} be deterministic given
|
|
the manifest history and the policy.
|
|
|
|
Rollback is the act of writing a new superblock that points at
|
|
a previously-active manifest still reachable in the chunk
|
|
store. The fixed layout has exactly two superblock slots;
|
|
rollback does not require additional on-disk superblocks. It
|
|
requires only that the target manifest's reachable chunks
|
|
remain in the bundle, which the retention policy guarantees.
|
|
\end{requirement}
|
|
|
|
\subsection{Garbage Collection Rules}
|
|
|
|
\begin{requirement}
|
|
\label{req:format:conservative-garbage-collection}
|
|
Garbage collection is a conservative, optional, deferred
|
|
operation. It \MUST{} preserve every byte reachable transitively
|
|
from:
|
|
|
|
\begin{enumerate}
|
|
\item The active superblock's manifest.
|
|
\item Every retained manifest selected by the active
|
|
\texttt{RetentionPolicy}.
|
|
\item Any pending edit's working set (chunks written but not
|
|
yet committed).
|
|
\end{enumerate}
|
|
|
|
Garbage collection \MUSTNOT{} run as part of a commit's critical
|
|
path. It \MAY{} run as a background operation or as an explicit
|
|
user-invoked compaction. Pre-GC and post-GC bundles \MUST{}
|
|
contain identical canonical state and identical retained-rollback
|
|
reachability.
|
|
\end{requirement}
|
|
|
|
\section{Forward Compatibility and Edit Barriers}
|
|
\label{sec:format:fwdcompat}
|
|
|
|
The bundle must round-trip cleanly across implementations that may
|
|
not all understand every extension referenced in it.
|
|
|
|
\subsection{Extension Declarations}
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct ExtensionDeclaration {
|
|
pub extension_id: ExtensionId,
|
|
pub version: SemVer,
|
|
|
|
/// If true, an implementation that does not understand this
|
|
/// extension MUST refuse to open the bundle for editing, but
|
|
/// MAY open read-only.
|
|
pub required: bool,
|
|
|
|
/// Root chunks owned by this extension. Opaque to core
|
|
/// readers; preserved across reads and writes.
|
|
pub preserved_chunk_roots: Vec<ChunkRef>,
|
|
|
|
/// Object kinds this extension contributes to or depends on,
|
|
/// for edit-barrier evaluation.
|
|
pub affected_object_kinds: Vec<ObjectKind>,
|
|
|
|
/// Edit barriers declared by this extension.
|
|
pub edit_barriers: Vec<EditBarrier>,
|
|
}
|
|
|
|
pub struct EditBarrier {
|
|
/// Scope of the barrier: whole score, region, staff instance,
|
|
/// analysis layer, object set, pitch space, tuning context, etc.
|
|
pub scope: BarrierScope,
|
|
|
|
/// Object kinds protected by this barrier.
|
|
pub affected_object_kinds: Vec<ObjectKind>,
|
|
|
|
/// Operation kind tags prohibited by this barrier. A barrier
|
|
/// prohibits an operation class, not one exact payload, so it
|
|
/// references OperationKindTag rather than OperationKind.
|
|
pub prohibited_operation_kinds: Vec<OperationKindTag>,
|
|
|
|
/// Additional condition narrowing the barrier (e.g., applies
|
|
/// only when a specific attachment is present).
|
|
pub condition: BarrierCondition,
|
|
}
|
|
|
|
/// A discriminator-only view of OperationKind (Chapter ch:semops),
|
|
/// without payload. Used by edit barriers, conformance reports,
|
|
/// and diagnostic surfaces where the operation's class matters
|
|
/// but its payload does not. There is a canonical mapping from
|
|
/// OperationKind to OperationKindTag.
|
|
pub enum OperationKindTag {
|
|
InsertEvent,
|
|
DeleteEvent,
|
|
ModifyEvent,
|
|
RespellPitch,
|
|
Transpose,
|
|
CreateCrossCutting,
|
|
DeleteCrossCutting,
|
|
ModifyCrossCutting,
|
|
ChangeRegionTimeModel,
|
|
InsertRegion,
|
|
DeleteRegion,
|
|
InsertStaffInstance,
|
|
DeleteStaffInstance,
|
|
SetUserSystemBreak,
|
|
SetUserPageBreak,
|
|
DeclareTransaction,
|
|
Registered(OperationKindRegistryId),
|
|
// Appended after Registered (append-only vocabulary; the
|
|
// tag's wire discriminants never reorder). Phase-2/Phase-3
|
|
// operation-catalog growth:
|
|
InsertIdentifiedPitch,
|
|
DeleteIdentifiedPitch,
|
|
ModifyIdentifiedPitch,
|
|
CreateVoice,
|
|
DeleteVoice,
|
|
SetMetadata,
|
|
SetMetricGrid,
|
|
InsertStaff,
|
|
SetTimeSignature,
|
|
SetTempoSegment,
|
|
SetStaffLayout,
|
|
// Schema-major-2 revision (repeat authoring). The pair keeps
|
|
// its kind names verbatim, as the cross-cutting tags do.
|
|
CreateRepeatStructure,
|
|
DeleteRepeatStructure,
|
|
}
|
|
|
|
pub enum BarrierScope {
|
|
WholeScore,
|
|
Region(RegionId),
|
|
StaffInstance(StaffInstanceId),
|
|
AnalysisLayer(AnalysisLayerId),
|
|
ObjectSet(Vec<TypedObjectId>),
|
|
PitchSpace(PitchSpaceId),
|
|
TuningContext,
|
|
/// Registered scope kind for grammar-specific scoping.
|
|
Registered(BarrierScopeRegistryId),
|
|
}
|
|
|
|
/// Additional narrowing condition for an edit barrier. The barrier
|
|
/// applies only when its scope, affected object kinds, prohibited
|
|
/// operation kinds, and this condition all match the candidate edit.
|
|
pub enum BarrierCondition {
|
|
/// The barrier applies unconditionally within its scope.
|
|
Always,
|
|
|
|
/// The barrier applies only while the named object exists
|
|
/// (not tombstoned). Used when an extension's invariant
|
|
/// depends on a specific object remaining live.
|
|
ObjectExists(TypedObjectId),
|
|
|
|
/// The barrier applies only when the named object carries
|
|
/// data declared by the named extension. Used when the
|
|
/// invariant protected by the barrier exists only for
|
|
/// objects participating in the extension.
|
|
ObjectHasExtensionData {
|
|
object: TypedObjectId,
|
|
extension: ExtensionId,
|
|
},
|
|
|
|
/// Conjunction: all conditions must match.
|
|
All(Vec<BarrierCondition>),
|
|
|
|
/// Disjunction: any condition matching is sufficient.
|
|
Any(Vec<BarrierCondition>),
|
|
|
|
/// Negation: the inner condition must not match.
|
|
Not(Box<BarrierCondition>),
|
|
|
|
/// Registered condition kind, evaluated by an extension-aware
|
|
/// implementation. Core implementations evaluate Registered
|
|
/// conditions as Always (conservative): they treat the barrier
|
|
/// as active whenever an unknown extension's condition would
|
|
/// otherwise narrow it.
|
|
Registered(BarrierConditionRegistryId),
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsection{Behavior Under Unknown Extensions}
|
|
|
|
\begin{requirement}
|
|
\label{req:format:unknown-extension-preservation}
|
|
When an implementation opens a bundle declaring an extension it
|
|
does not understand:
|
|
|
|
\begin{itemize}
|
|
\item If the extension is \texttt{required = true}, the
|
|
implementation \MUST{} either refuse to open the bundle or
|
|
open it strictly read-only. It \MUSTNOT{} edit.
|
|
\item If the extension is \texttt{required = false}, the
|
|
implementation \MAY{} open the bundle for editing, but \MUST{}
|
|
preserve the extension's \texttt{preserved\_chunk\_roots}
|
|
across reads and writes. The chunks are treated as opaque
|
|
bytes.
|
|
\item Edits \MUST{} be checked against every active edit
|
|
barrier. An edit matching a barrier's scope, affected object
|
|
kinds, and operation kinds is prohibited unless the user
|
|
explicitly performs an \emph{unsafe edit}.
|
|
\item An unsafe edit is an explicit user action that
|
|
acknowledges loss of extension data. The unsafe-edit
|
|
operation \MUST{} tombstone the relevant extension chunks
|
|
(so they are no longer preserved) rather than silently
|
|
breaking extension invariants.
|
|
\end{itemize}
|
|
|
|
The unsafe-edit mechanism prevents unknown extensions from
|
|
rendering a document permanently unedittable while still
|
|
preserving extension data by default.
|
|
\end{requirement}
|
|
|
|
\begin{requirement}
|
|
\label{req:format:barrier-matching}
|
|
\textbf{Barrier matching for target-free and opaque operations
|
|
(ratified Pass 12).}
|
|
An operation with no graph target (\texttt{SetMetadata},
|
|
\texttt{DeclareTransaction}) is matched by \emph{score-wide}
|
|
barriers only (\texttt{BarrierScope::WholeScore}); a barrier with
|
|
any narrower scope cannot match it, because there is no target to
|
|
test the scope against. An opaque \texttt{Registered} operation is
|
|
matched \emph{fully conservatively}: since its targets and effects
|
|
are unknown to a core implementation, it matches every active
|
|
barrier whose remaining predicates do not exclude it.
|
|
\end{requirement}
|
|
|
|
\begin{requirement}
|
|
\label{req:format:unsafe-tombstone}
|
|
\textbf{Unsafe-edit tombstone semantics (ratified Pass 12).}
|
|
Crossing a barrier by an unsafe edit immediately deactivates the
|
|
owning extension's \emph{remaining} barriers for the editing
|
|
session --- the extension's invariants are already forfeit, so its
|
|
other barriers no longer protect anything. The crossing \MUST{} be
|
|
durably recorded at the next bundle commit: the pending tombstone
|
|
set survives the session that performed the unsafe edit. A
|
|
tombstoned extension that was declared \texttt{required = true}
|
|
leaves the bundle openable read-only by implementations that
|
|
depended on that extension (its invariants can no longer be
|
|
trusted); implementations that never understood it proceed under
|
|
the unknown-extension rules above. The manifest-side \emph{byte
|
|
encoding} of the tombstone record is deferred to the Binary Format
|
|
companion (the manifest schema is frozen at major~0, so the record
|
|
must ride an append-safe channel); see the companion's open
|
|
questions.
|
|
\end{requirement}
|
|
|
|
\section{Text Projection}
|
|
\label{sec:format:textproj}
|
|
|
|
The format admits a deterministic projection to a canonical
|
|
s-expression text form. The text projection is normative.
|
|
|
|
The form itself is supplied by the \emph{Text Projection} companion
|
|
(version~0.1.0), which this section delegates to: the syntax, the canonical
|
|
layout, the parse rules, and the round-trip conformance requirement are its.
|
|
This section states \emph{what} is projected; the companion states \emph{how}.
|
|
|
|
\begin{requirement}
|
|
\label{req:format:textproj}
|
|
The text projection \MUST{} preserve, deterministically and
|
|
bidirectionally with the binary form:
|
|
|
|
\begin{itemize}
|
|
\item All operation envelopes (with their identities, stamps,
|
|
causal contexts, transaction groupings, and payloads).
|
|
\item All profile declarations.
|
|
\item All extension declarations \emph{in full} --- identity, semantic
|
|
version, required flag, affected object kinds, edit barriers, and
|
|
preserved chunk roots --- with opaque payloads in a canonical text
|
|
encoding.
|
|
\item Every \textbf{canonical blob}: a blob referenced by a canonical
|
|
operation or by canonical reduced state is a canonical root of this
|
|
document (Section~\ref{sec:format:manifest}), so the projection carries
|
|
its media type and its uncompressed payload. Blobs referenced only by
|
|
acceleration structures are non-canonical and are not projected.
|
|
\item The active canonical base snapshot's identity, frontier, and
|
|
reduction-algorithm version, \emph{with its payload inline}. The core
|
|
specification once permitted the payload to be ``referenced externally'';
|
|
that is withdrawn, because a compacted document's base is derivable from
|
|
nothing else and an external reference would make the projection lossy
|
|
(Text Projection companion,
|
|
\texttt{req:textproj:base-snapshot-inline}).
|
|
\item All canonical reduced state --- preserved by \emph{determining} it,
|
|
never by carrying a second literal copy. Reduced state is a deterministic
|
|
function of the operation set and the canonical base, so a projection
|
|
carrying both would hold two sources of truth for one fact. See the Text
|
|
Projection companion, requirement
|
|
\texttt{req:textproj:reduced-state-derived}.
|
|
\end{itemize}
|
|
|
|
The text projection \MUSTNOT{} be required to preserve:
|
|
|
|
\begin{itemize}
|
|
\item Chunk offsets within the file.
|
|
\item Compression algorithm choices.
|
|
\item Cache chunks (operation indexes, layout caches, integrity
|
|
indexes).
|
|
\item Garbage bytes from prior commits.
|
|
\item Superblock generation numbers, slot assignments, or CRCs.
|
|
\end{itemize}
|
|
|
|
Two binary bundles whose canonical document semantics are
|
|
identical \MUST{} project to identical text. Two text projections
|
|
that parse to identical canonical document semantics \MUST{}
|
|
re-serialize to binary bundles whose canonical document
|
|
semantics are identical (though their physical layout, chunking,
|
|
and compression \MAY{} differ).
|
|
\end{requirement}
|
|
|
|
\subsection{Use Cases}
|
|
|
|
The text projection serves three primary use cases:
|
|
|
|
\begin{description}
|
|
\item[Version control.] Text projections diff and merge in
|
|
line-based tools. Merge conflicts surface at the operation-
|
|
envelope level, which is the meaningful level for collaborative
|
|
editing.
|
|
\item[Format inspection and debugging.] A canonical text form
|
|
enables direct inspection of bundle contents without binary
|
|
tooling.
|
|
\item[Long-term archival.] Text formats survive transitions
|
|
between binary specifications. The text projection's structure
|
|
is stable across binary format major versions where the
|
|
semantic content is preserved.
|
|
\end{description}
|
|
|
|
For archival to hold, the text must be self-contained. Where a document has been
|
|
compacted onto a canonical base and its prior operations pruned, that base is
|
|
derivable from nothing else in the document, so the companion carries the
|
|
snapshot payload \emph{inline} rather than by reference
|
|
(\texttt{req:textproj:base-snapshot-inline}). A reference-only projection of a
|
|
compacted document would not determine the document it claims to project.
|
|
|
|
\section{Schema Versioning}
|
|
\label{sec:format:schema}
|
|
|
|
Chunk payloads are encoded against a schema declared by each
|
|
chunk's \texttt{schema\_version} field. Schemas evolve under a
|
|
semantic versioning discipline.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct SchemaVersion {
|
|
pub major: u16,
|
|
pub minor: u16,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\begin{requirement}
|
|
\label{req:format:schema-version-compatibility}
|
|
Schema versioning rules:
|
|
|
|
\begin{itemize}
|
|
\item Major version changes are non-backward-compatible.
|
|
Readers that do not support the chunk's major schema
|
|
version \MUST{} handle it according to the chunk's role:
|
|
\begin{itemize}
|
|
\item \emph{Canonical chunks} (operation envelope blocks,
|
|
the canonical base snapshot, the manifest, and blobs
|
|
referenced by canonical operations or reduced state)
|
|
\MUST{} be either parseable or the bundle \MUST{} be
|
|
opened in read-only preservation mode (or refused). A
|
|
reader that cannot parse a canonical chunk cannot know
|
|
the canonical document.
|
|
\item \emph{Non-canonical chunks} (acceleration
|
|
snapshots, operation index, layout caches, text
|
|
projection, integrity index, extension-defined chunks
|
|
declared as preservable opaque content)
|
|
\MAY{} be treated as opaque preserved content; the
|
|
reader does not need to understand them to open the
|
|
bundle, and writes preserve them verbatim.
|
|
\end{itemize}
|
|
\item Unknown canonical operation payloads (operation kinds
|
|
not in the reader's understood catalog) require the
|
|
relevant extension support or the reader \MUST{} open the
|
|
bundle read-only without materializing the unknown
|
|
operations. Materializing an unknown operation \MUSTNOT{}
|
|
proceed silently: doing so would fork canonical state
|
|
against any reader that does understand the operation.
|
|
\item Minor version changes are backward-compatible. A reader
|
|
supporting major version $N$ \MUST{} accept any minor version
|
|
$N.\textit{m}$ for $m \leq$ the reader's supported minor.
|
|
Under the positional, frozen-layout wire form, a minor change
|
|
\MUST{} only \emph{append discriminants to the append-safe
|
|
vocabularies the Binary Format companion defines} (its open
|
|
\texttt{Registered} vocabularies, plus ratified appends to
|
|
\texttt{OperationKind}, \texttt{OperationKindTag},
|
|
\texttt{OperationPayload}, and the closed value-layer unions), which
|
|
leaves the bytes of every prior value untouched; it \MUSTNOT{} add or
|
|
reorder a struct field --- adding a field, even an \texttt{Option},
|
|
occupies a new positional slot and shifts subsequent bytes, so it is a
|
|
\emph{major} change (Binary Format companion,
|
|
\sectionsc{What ``Additive'' Means Here}).
|
|
\item The manifest's \texttt{manifest\_schema\_version} declares
|
|
the schema of the manifest itself. Each chunk's
|
|
\texttt{schema\_version} field declares the schema of that
|
|
chunk's payload.
|
|
\item Schema evolution is governed by the Binary Format
|
|
companion specification, which defines the wire encoding
|
|
for each schema version.
|
|
\end{itemize}
|
|
\end{requirement}
|
|
|
|
Schema major~1 is the first data-model expansion major: it adds
|
|
\texttt{Canvas.layout\_defaults}, \texttt{Instrument.range}, and
|
|
\texttt{Region.permits\_spanning\_slurs} to the graph, and unifies the
|
|
non-canonical resolved-layout length-prefix width. Its wire form and the
|
|
byte-for-byte migration from major~0 are defined in the Binary Format
|
|
companion. The canonical-base \texttt{MaterializedState} embeds none of these
|
|
values, so it is byte-identical across the bump and stays major~0.
|
|
\texttt{Canvas.layout\_defaults} and \texttt{Instrument.range} reach only the
|
|
non-canonical acceleration (full-\texttt{Score}) snapshot; but
|
|
\texttt{Region.permits\_spanning\_slurs} also reaches the \emph{canonical}
|
|
operation layer, because \texttt{CreateRegion} embeds a full \texttt{Region},
|
|
so an operation-envelope block bearing a v1 \texttt{CreateRegion} is major~1.
|
|
Consequently a major-0-only reader opens a major-1 bundle \emph{fully} only
|
|
when the bundle carries no v1 \texttt{CreateRegion} operation; otherwise it
|
|
reads the major-0 canonical base and manifest but opens read-only (it cannot
|
|
replay the v1 canonical operations), per the canonical/non-canonical rules
|
|
above. It always discards the higher-major non-canonical caches. A major-1
|
|
reader migrates a major-0 acceleration snapshot and a major-0
|
|
\texttt{CreateRegion} payload on read (default-filling the new fields).
|
|
|
|
Schema major~2 is the second data-model expansion major: it fills the
|
|
truncated bodies of the cross-cutting structures (\texttt{Slur},
|
|
\texttt{Tie}, \texttt{Beam}, \texttt{Spanner}), \texttt{RepeatStructure}
|
|
(kind and voltas), \texttt{Staff} and \texttt{StaffLineConfiguration},
|
|
\texttt{Instrument}, and \texttt{ScoreMetadata}, per this revision's
|
|
Chapter~5 definitions. The same per-payload-type rules apply: the
|
|
canonical-base \texttt{MaterializedState} embeds none of these values and
|
|
stays major~0; the filled values reach the acceleration snapshot
|
|
\emph{and}, through the eight operation payloads that embed them
|
|
(\texttt{CreateCrossCutting}, \texttt{ModifyCrossCutting},
|
|
\texttt{InsertStaff} and \texttt{InsertStaffInstance} (the Operation
|
|
Catalog's \texttt{CreateStaff} / \texttt{CreateStaffInstance}),
|
|
\texttt{SetStaffLayout}, \texttt{SetMetadata},
|
|
\texttt{CreateRepeatStructure} (the repeat-authoring revision --- it
|
|
embeds the filled \texttt{RepeatStructure} unconditionally, so it is
|
|
\emph{born at v2} and every block carrying one stamps major~2; its
|
|
\texttt{DeleteRepeatStructure} sibling carries a bare identifier, a
|
|
major-0 layout, and stamps major~0), and --- transitively,
|
|
because an embedded \texttt{Region} may carry a \texttt{StaffInstance}
|
|
whose \texttt{staff\_lines\_override} embeds the filled
|
|
\texttt{StaffLineConfiguration} --- \texttt{InsertRegion}
|
|
(\texttt{CreateRegion})), the canonical operation layer. A block is
|
|
stamped with the \emph{lowest} schema major whose layouts decode its
|
|
bytes (the companion's minimal-stamping rule --- deterministic, so
|
|
identical content hashes identically): any v2-dependent bytes make it
|
|
major~2, and a lower-major-only reader opens such a bundle read-only. The wire form
|
|
and the total default-filling v1${\to}$v2 migration are defined in the
|
|
Binary Format companion.
|
|
|
|
\section{Format Profiles}
|
|
\label{sec:format:profiles}
|
|
|
|
A format profile defines a conformance subset that implementations
|
|
\MAY{} promise to support. Profiles enable lightweight readers to
|
|
declare their capabilities precisely.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct ProfileDeclaration {
|
|
pub profile_id: ProfileId,
|
|
pub version: SemVer,
|
|
|
|
/// Constraints imposed by this profile (maximum block size,
|
|
/// permitted compression algorithms, required extensions, etc.)
|
|
pub constraints: ProfileConstraints,
|
|
}
|
|
|
|
pub enum ProfileId {
|
|
/// Full profile: all features supported, no restrictions.
|
|
Full,
|
|
|
|
/// Read-only profile: bundles produced under this profile MAY
|
|
/// be opened by readers that cannot edit. Editing implementations
|
|
/// SHOULD upgrade the profile on first edit.
|
|
ReadOnly,
|
|
|
|
/// Lite profile: a reduced feature set for embedded or mobile
|
|
/// readers. Restricts maximum block sizes, snapshot complexity,
|
|
/// and permitted extensions.
|
|
Lite,
|
|
|
|
/// Custom profile defined by a registered profile declaration.
|
|
Custom(ProfileRegistryId),
|
|
}
|
|
\end{lstlisting}
|
|
|
|
The constraints a profile imposes are carried in
|
|
\texttt{ProfileConstraints}, which the GC and retention rules
|
|
(Section~\ref{sec:format:gc}) reference for the mandatory
|
|
\texttt{RetentionPolicy}:
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct ProfileConstraints {
|
|
/// Maximum uncompressed operation-envelope block size a reader
|
|
/// must accept under this profile (default 64 MiB).
|
|
pub max_uncompressed_block_size: u64,
|
|
|
|
/// The RetentionPolicy this profile declares. Required: the GC
|
|
/// and retention rules read it from the active profile.
|
|
pub retention_policy: RetentionPolicy,
|
|
// Additional constraint fields (permitted compression sets,
|
|
// required-extension lists) are reserved for a later revision;
|
|
// a reader MUST tolerate their future addition under the
|
|
// minor-version compatibility rule.
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\begin{requirement}
|
|
\label{req:format:profile-constraints}
|
|
Every \texttt{ProfileDeclaration} \MUST{} carry a
|
|
\texttt{ProfileConstraints}, and every \texttt{ProfileConstraints}
|
|
\MUST{} include a \texttt{retention\_policy} field. This satisfies
|
|
the requirement (Section~\ref{sec:format:gc}) that the active
|
|
conformance profile declare a \texttt{RetentionPolicy}: the policy
|
|
lives inside the active profile's constraints, not as a free-standing
|
|
manifest field.
|
|
|
|
When a bundle declares more than one profile, the
|
|
\texttt{RetentionPolicy} in force is the one declared by the
|
|
\emph{first} profile in the manifest's \texttt{profile\_declarations}
|
|
list (declaration order is canonical). A bundle that declares no
|
|
profile constraints carrying a policy uses the Full profile's
|
|
default retention policy.
|
|
\end{requirement}
|
|
|
|
\begin{requirement}
|
|
\label{req:format:profile-support}
|
|
Every bundle \MUST{} declare at least one profile in its manifest's
|
|
\texttt{profile\_declarations}. An implementation \MAY{} open a
|
|
bundle if it supports any of the declared profiles, subject to
|
|
the required-extension rules
|
|
(Section~\ref{sec:format:fwdcompat}).
|
|
\end{requirement}
|
|
|
|
\begin{requirement}
|
|
\label{req:format:profileid-discriminants}
|
|
\texttt{ProfileId} encodes as a fixed \emph{20} bytes: a
|
|
little-endian \texttt{u32} declaration-order discriminant
|
|
(\texttt{Full} $= 0$, \texttt{ReadOnly} $= 1$, \texttt{Lite} $= 2$,
|
|
\texttt{Custom} $= 3$) followed by a 16-byte
|
|
\texttt{ProfileRegistryId}. The trailing 16 bytes carry the registry
|
|
id for \texttt{Custom} and are zero for the other three variants; the
|
|
field is always present, so the encoding is fixed-width (the
|
|
superblock reserves a fixed slot for it). This assignment is
|
|
normative and stable.
|
|
\texttt{ProfileId} participates in superblock selection
|
|
(Section~\ref{sec:format:bundle}, the load-bearing field set), so
|
|
its discriminants are part of that comparison.
|
|
\end{requirement}
|
|
|
|
\section{Compression}
|
|
\label{sec:format:compression}
|
|
|
|
Each chunk \MAY{} be stored compressed. Compression is per-chunk
|
|
metadata, not part of content identity.
|
|
|
|
\begin{requirement}
|
|
\label{req:format:zstandard-support}
|
|
Conforming implementations \MUST{} support reading chunks
|
|
compressed with Zstandard at any level zstd defines. Writers
|
|
\MAY{} choose to compress or not on a per-chunk basis;
|
|
uncompressed chunks (\texttt{CompressionAlgorithm::None}) are
|
|
always permitted.
|
|
|
|
Implementations \MUSTNOT{} require a specific compression
|
|
algorithm for canonical conformance. Future major format
|
|
versions \MAY{} introduce additional algorithms via the
|
|
\texttt{Reserved} variant; such changes require a new format
|
|
major version.
|
|
|
|
Decompression \MUST{} verify the output length against the
|
|
declared \texttt{uncompressed\_length} and reject chunks whose
|
|
decompressed size disagrees.
|
|
\end{requirement}
|
|
|
|
\section{Streaming Reads}
|
|
\label{sec:format:streaming}
|
|
|
|
Cold open of a large score \MUST{} be fast: under one second to
|
|
first interactive frame on a 100-page orchestral score, per the
|
|
performance requirements (Chapter~\ref{ch:perf}). The format's
|
|
prelude and chunk-graph design support this.
|
|
|
|
\subsection{Required Locatability}
|
|
|
|
\begin{requirement}
|
|
\label{req:format:chunk-locatability}
|
|
The bundle \MUST{} guarantee that:
|
|
|
|
\begin{itemize}
|
|
\item The fixed header is at offset zero of the file.
|
|
\item Both superblock slots are at fixed offsets within the
|
|
first 576 bytes.
|
|
\item The active manifest is locatable from the active
|
|
superblock without scanning.
|
|
\item Operation-envelope blocks, the canonical base snapshot
|
|
(if any), acceleration snapshots, blobs, and extension
|
|
chunks are locatable from the manifest's roots without
|
|
scanning.
|
|
\item The operation index (if present) provides $O(\log n)$
|
|
operation-id lookup; if absent, scanning a small number of
|
|
blocks suffices.
|
|
\end{itemize}
|
|
|
|
Locatable means: a reader can determine the offset and length of
|
|
the chunk in the bundle using only previously read content.
|
|
\end{requirement}
|
|
|
|
\subsection{Cold Open Procedure}
|
|
|
|
The recommended cold open sequence:
|
|
|
|
\begin{enumerate}
|
|
\item Read the 64-byte fixed header. Verify magic and CRC.
|
|
\item Read both 256-byte superblock slots. Verify CRC on each.
|
|
Select the active slot per the superblock selection rule
|
|
(Section~\ref{sec:format:bundle}).
|
|
\item Read and verify the manifest chunk referenced by the
|
|
active superblock. The manifest is stored uncompressed
|
|
(Section~\ref{sec:format:manifest-encoding}), so this is a
|
|
single read-and-parse.
|
|
\item If the manifest's \texttt{canonical\_base} is
|
|
\texttt{Some}, read and verify the canonical base snapshot.
|
|
The snapshot provides the materialized state at its declared
|
|
causal frontier.
|
|
\item Stream the operation-envelope blocks listed in
|
|
\texttt{operation\_roots}. Apply the canonical reduction
|
|
(Chapter~\ref{ch:semops}) over the envelopes whose
|
|
\texttt{OperationId} is not covered by the canonical base's
|
|
\texttt{covers\_causal\_frontier} (if a base is present), or
|
|
over every envelope in the operation set (if no base is
|
|
present). The result is the materialized score graph.
|
|
\item Invoke the layout pipeline (Chapter~\ref{ch:layout-ir})
|
|
over the materialized graph, producing the first system's
|
|
layout. Layout caches (if reachable from the manifest as
|
|
accelerators) \MAY{} be consulted to skip work; cache misses
|
|
fall back to fresh layout computation.
|
|
\item Load remaining regions, envelope blocks, snapshot ranges,
|
|
and layout caches lazily as the user navigates.
|
|
\end{enumerate}
|
|
|
|
For unsynchronized first-frame display, implementations \MAY{}
|
|
present the canonical base's materialized state immediately while
|
|
the post-frontier reduction completes in the background; user
|
|
edits \MUST{} be deferred until reduction completes, since the
|
|
visible state is provisional until then.
|
|
|
|
\subsection{Memory Mapping}
|
|
|
|
\begin{requirement}
|
|
\label{req:format:safe-memory-mapping}
|
|
Implementations \MAY{} memory-map the bundle file for efficient
|
|
random access to chunks and blobs. The format's content-addressing
|
|
and chunk immutability guarantees make memory mapping safe:
|
|
chunks cannot change underneath a reader holding a pointer into
|
|
them. Superblock slots are 256-byte aligned and never overlap
|
|
reachable chunk content, so writers updating an inactive
|
|
superblock cannot corrupt readers accessing the active state.
|
|
\end{requirement}
|
|
|
|
\section{Binary Format Companion}
|
|
\label{sec:format:binary}
|
|
|
|
The byte-level encoding (varint conventions, exact field bit widths,
|
|
record alignment, endianness, string encoding details, struct
|
|
layouts) is delivered as a companion specification, the Binary
|
|
Format document. The Binary Format document is normative; an
|
|
implementation cannot conform to the file format specification
|
|
without conforming to the Binary Format specification.
|
|
|
|
\subsection{Ratified Convention Baseline}
|
|
\label{sec:format:codec-baseline}
|
|
|
|
Although the full Binary Format companion is still to be written, the
|
|
canonical encoding conventions shared by the core, operations, and
|
|
bundle layers are ratified now, so those three layers do not drift
|
|
apart before the companion formalizes them. The companion
|
|
\emph{inherits} this baseline rather than re-deriving it. Every
|
|
ratified discriminant table, derivation preimage, and primitive
|
|
encoding is consolidated in
|
|
Appendix~\ref{app:bytes} (the Canonical Byte-Layout Reference); the
|
|
companion imports that appendix as its starting point rather than
|
|
recovering the layouts from the three crates.
|
|
|
|
\begin{requirement}
|
|
\label{req:format:codec-conventions}
|
|
The canonical encoding of every composite structure (whole
|
|
\texttt{Score}, operation envelopes, manifest) \MUST{} follow these
|
|
conventions:
|
|
\begin{itemize}
|
|
\item Integers are little-endian. A boolean is a single
|
|
\texttt{0}/\texttt{1} byte.
|
|
\item Counts and the length prefix of every variable-width
|
|
\emph{leaf} (an identifier, a \texttt{RationalTime}, a string)
|
|
are \texttt{u32} little-endian. Length-prefixing every leaf
|
|
makes the decoder width-agnostic: it never infers a boundary
|
|
from context.
|
|
\item A tagged union is encoded as a single discriminant byte
|
|
followed by the variant payload.
|
|
\item Free-text string fields are length-prefixed UTF-8 and are
|
|
\emph{not} NFC-folded by the codec: the in-memory value is
|
|
preserved byte-exact, so \texttt{decode(encode(x)) == x}.
|
|
Catalog identifiers are NFC-normalized at construction, not in
|
|
the codec.
|
|
\end{itemize}
|
|
|
|
Certain fixed hashing preimages deliberately depart from the
|
|
single-discriminant-byte convention where a wider or order-bearing
|
|
tag is required --- notably \texttt{TypedObjectId}'s 16-bit
|
|
big-endian discriminant
|
|
(Requirement~\ref{req:graph:typed-object-id-discriminants}). Such
|
|
departures are pinned at their definitions and are not overridden by
|
|
this baseline.
|
|
\end{requirement}
|
|
|
|
\begin{requirement}
|
|
\label{req:format:rationaltime-encoding}
|
|
The primitive scalar layouts referenced throughout the canonical
|
|
encodings are ratified as follows:
|
|
\begin{itemize}
|
|
\item \texttt{RationalTime} encodes as a sign byte
|
|
(\texttt{0} = zero, \texttt{1} = positive, \texttt{2} =
|
|
negative), then the \texttt{u32}-little-endian-length-prefixed
|
|
big-endian magnitude of the numerator, then the
|
|
\texttt{u32}-little-endian-length-prefixed big-endian magnitude
|
|
of the denominator. The value \MUST{} always be stored reduced
|
|
(lowest terms, positive denominator), so the encoding is
|
|
canonical: equal rationals encode to equal bytes.
|
|
\item Wall-clock integers (\texttt{WallClockTime},
|
|
\texttt{WallClockDuration}) are little-endian fixed-width
|
|
integers, matching \texttt{QuantizedCoord}.
|
|
\end{itemize}
|
|
\end{requirement}
|
|
|
|
\begin{openquestion}
|
|
The remainder of the Binary Format companion --- the full composite
|
|
struct layouts, schema-version wire evolution, and varint details
|
|
beyond the baseline above --- is under development as a separate
|
|
specification. Its development follows this chapter and inherits the
|
|
ratified baseline (Sections~\ref{req:format:codec-conventions}
|
|
and~\ref{req:format:rationaltime-encoding}); the remaining byte-level
|
|
decisions depend on the structural decisions specified here.
|
|
\end{openquestion}
|
|
|
|
\section{Forward References}
|
|
|
|
\begin{itemize}
|
|
\item The constraint-solver interface (Chapter~\ref{ch:solver})
|
|
operates on the in-memory \texttt{ConstrainedLayoutIR}; layout
|
|
caches stored in the bundle are derivative and not part of the
|
|
canonical state.
|
|
\item The full performance requirements that this format must
|
|
satisfy are in Chapter~\ref{ch:perf}.
|
|
\item The Binary Format companion specifies byte-level encoding.
|
|
\item The Text Projection companion specifies the canonical
|
|
s-expression form.
|
|
\item The Profile Conformance companion specifies the feature
|
|
lists per profile.
|
|
\end{itemize}
|
|
|
|
% ===========================================================================
|
|
\chapter{Constraint-Solver Interface}
|
|
\label{ch:solver}
|
|
|
|
This chapter specifies the constraint-solver interface: the
|
|
\texttt{ConstrainedLayoutIR} the solver consumes, the
|
|
\texttt{ResolvedLayoutIR} it produces, the constraints it must
|
|
respect, the determinism contract it must satisfy, the diagnostic
|
|
information it must emit, and the conformance discipline by which
|
|
implementations are judged.
|
|
|
|
The chapter deliberately specifies the \emph{interface} and its
|
|
contracts rather than a particular algorithm. Implementations are
|
|
free to choose among approaches (Cassowary, linear programming
|
|
relaxations, custom heuristics, differentiable layout, learned
|
|
methods) and to evolve their choices over time. Conformance is
|
|
established by hard-constraint validity, deterministic behavior,
|
|
diagnostic completeness, and performance and quality on a reference
|
|
suite of test scores.
|
|
|
|
\section{Design Principles}
|
|
\label{sec:solver:principles}
|
|
|
|
\begin{description}
|
|
\item[The interface is normative; global optimality is not.] The
|
|
solver's input/output contract, its constraint families, its
|
|
diagnostic surface, and the discipline by which it is evaluated
|
|
are normative. Global aesthetic optimality (proving no better
|
|
layout exists) is neither decidable nor practical to verify for
|
|
arbitrary scores; it is therefore replaced by reference-suite
|
|
thresholds.
|
|
\item[Hard constraints are validity; quality metrics are evaluation
|
|
axes.] Hard constraints are the conditions under which a layout
|
|
is valid at all. Quality metrics are the axes along which valid
|
|
layouts are evaluated and compared. The solver \MUSTNOT{}
|
|
trade off a hard-constraint violation for a better quality
|
|
score: a layout that violates a hard constraint is not a layout,
|
|
regardless of its quality vector.
|
|
\item[Determinism within an implementation; equivalence across
|
|
implementations.] A given implementation at a fixed version
|
|
\MUST{} produce identical output for identical input. Different
|
|
conforming implementations \MAY{} produce different layouts;
|
|
they conform by satisfying hard constraints, meeting diagnostic
|
|
contracts, behaving deterministically internally, and passing
|
|
reference-suite thresholds. Cross-implementation byte-identity
|
|
is explicitly not required.
|
|
\item[Pareto frontiers as design target, not conformance
|
|
obligation.] Solvers \SHOULD{} seek layouts on or near the
|
|
Pareto frontier of the normative quality metrics. They are
|
|
\emph{not} required to prove they have done so universally;
|
|
conformance is reference-suite-based.
|
|
\item[Deterministic budgets, not wall-clock budgets.] Wall-clock
|
|
time \MAY{} stop interactive work, but the returned layout's
|
|
canonical form depends on deterministic counters
|
|
(iterations, constraint evaluations, search nodes), not on
|
|
scheduler luck.
|
|
\item[Incremental solving as observational equivalence.]
|
|
Incremental re-solving \MUST{} be observationally equivalent
|
|
to full solving of the declared invalidation scope under the
|
|
same profile and budget. Implementations are free to widen
|
|
scopes conservatively but \MAY NOT{} return layouts that
|
|
diverge from the canonical scoped result.
|
|
\item[Structured diagnostics, never silent corruption.] Solver
|
|
failures are first-class outputs with diagnostic information
|
|
sufficient for the editor to surface meaningful feedback. The
|
|
score graph is never invalidated by a solver failure; a solver
|
|
error means ``we could not produce a layout,'' not ``the
|
|
score is broken.''
|
|
\end{description}
|
|
|
|
\section{The Solver Interface}
|
|
\label{sec:solver:interface}
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub trait ConstraintSolver: Send + Sync {
|
|
/// The solver's identifying tier. Used by readers/editors to
|
|
/// decide whether this solver is sufficient for the active
|
|
/// score's declared solver tier requirement.
|
|
fn tier(&self) -> SolverTier;
|
|
|
|
/// The solver's implementation version. Identical input plus
|
|
/// identical version produces identical output (within-
|
|
/// implementation determinism).
|
|
fn version(&self) -> SolverVersion;
|
|
|
|
/// Solve from scratch.
|
|
fn solve(
|
|
&self,
|
|
input: &ConstrainedLayoutIR,
|
|
config: &SolverConfig,
|
|
) -> SolveReport;
|
|
|
|
/// Solve incrementally over the declared invalidation scope.
|
|
fn solve_incremental(
|
|
&self,
|
|
input: &ConstrainedLayoutIR,
|
|
prior: &SolverState,
|
|
invalidations: &InvalidationSet,
|
|
config: &SolverConfig,
|
|
) -> SolveReport;
|
|
}
|
|
|
|
pub struct SolverConfig {
|
|
/// Conformance profile under which to solve. Selects metric
|
|
/// thresholds, profile-specific constraints, and the active
|
|
/// extension set.
|
|
pub profile: SolverProfile,
|
|
|
|
/// Deterministic budget. Wall-clock time is advisory only.
|
|
pub budget: SolverBudget,
|
|
|
|
/// Tie-breaking weights for selecting among layouts of
|
|
/// equivalent quality.
|
|
pub tie_breaking: TieBreakingWeights,
|
|
}
|
|
|
|
pub struct SolverBudget {
|
|
/// Maximum solver iterations (algorithm-defined unit; the
|
|
/// reference algorithm counts Cassowary pivots).
|
|
pub max_iterations: u64,
|
|
|
|
/// Maximum search-tree nodes explored, for algorithms that
|
|
/// perform discrete search.
|
|
pub max_nodes: u64,
|
|
|
|
/// Maximum constraint-evaluation count.
|
|
pub max_constraint_evaluations: u64,
|
|
|
|
/// Advisory wall-clock cap, in milliseconds. The solver MAY
|
|
/// stop early when this is exceeded, but the layout it
|
|
/// returns under stoppage MUST be the same layout that the
|
|
/// deterministic budget would have produced at the
|
|
/// corresponding deterministic counter state. Wall-clock time
|
|
/// MUST NOT change the canonical layout.
|
|
pub advisory_wall_time_ms: Option<u64>,
|
|
}
|
|
|
|
pub struct SolverBudgetUsed {
|
|
pub iterations: u64,
|
|
pub nodes: u64,
|
|
pub constraint_evaluations: u64,
|
|
pub wall_time_ms: u64,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\begin{requirement}
|
|
\label{req:solver:solver-purity}
|
|
\texttt{solve} and \texttt{solve\_incremental} \MUST{} be pure
|
|
functions of their inputs within the determinism contract
|
|
(Section~\ref{sec:solver:determinism}). They \MUSTNOT{} consult
|
|
external state (system clock, environment, thread scheduling,
|
|
random sources) in ways that influence the returned layout.
|
|
|
|
Implementations \MAY{} use multi-threaded execution internally,
|
|
but the result \MUST{} be identical to a single-threaded
|
|
execution under the same deterministic budget.
|
|
\end{requirement}
|
|
|
|
\section{The Solver Report}
|
|
\label{sec:solver:report}
|
|
|
|
Every solver invocation returns a \texttt{SolveReport}. There is no
|
|
separate ``error'' return; failures and partial successes appear
|
|
as report variants distinguished by \texttt{SolveStatus}.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct SolveReport {
|
|
pub status: SolveStatus,
|
|
|
|
/// Whether every hard constraint is satisfied.
|
|
pub satisfied_hard_constraints: bool,
|
|
|
|
/// The layout produced. Always present; under failure
|
|
/// statuses, the layout is diagnostic-only and MUST NOT be
|
|
/// used as if it were valid.
|
|
pub layout: ResolvedLayoutIR,
|
|
|
|
/// Updated solver state for subsequent incremental calls.
|
|
pub state: SolverState,
|
|
|
|
/// Unsatisfied hard constraints, if any. Empty under
|
|
/// SolveStatus::Solved.
|
|
pub unsatisfied_constraints: Vec<ConstraintId>,
|
|
|
|
/// Non-fatal warnings about the solution.
|
|
pub warnings: Vec<SolverWarning>,
|
|
|
|
/// Quality metric vector for the returned layout.
|
|
pub metric_vector: QualityMetricVector,
|
|
|
|
/// Budget consumed during this solve.
|
|
pub budget_used: SolverBudgetUsed,
|
|
}
|
|
|
|
pub enum SolveStatus {
|
|
/// All hard constraints satisfied, target quality reached.
|
|
Solved,
|
|
|
|
/// All hard constraints satisfied, but warnings were
|
|
/// generated (e.g., a soft constraint had a large violation,
|
|
/// or a layout decision was unusual and worth surfacing).
|
|
SolvedWithWarnings,
|
|
|
|
/// Deterministic budget exhausted before reaching target
|
|
/// quality. The returned layout MUST still satisfy all hard
|
|
/// constraints; it simply may not be optimal. If hard
|
|
/// constraints could not be satisfied within budget, the
|
|
/// status is Unsatisfiable, not PartialBudgetExhausted.
|
|
PartialBudgetExhausted,
|
|
|
|
/// Hard constraints cannot be simultaneously satisfied. The
|
|
/// returned layout is diagnostic-only and MUST NOT be
|
|
/// presented as a valid layout.
|
|
Unsatisfiable,
|
|
|
|
/// Solver bug or unexpected error. The returned layout is
|
|
/// diagnostic-only. Editors SHOULD surface this as a defect
|
|
/// to report.
|
|
InternalError,
|
|
}
|
|
|
|
pub struct SolverWarning {
|
|
pub kind: SolverWarningKind,
|
|
pub affected_objects: Vec<TypedObjectId>,
|
|
pub message: String,
|
|
}
|
|
|
|
pub enum SolverWarningKind {
|
|
LargeSoftConstraintViolation { constraint: ConstraintId, magnitude: f64 },
|
|
UnusualLayoutDecision(String),
|
|
QualityFloorApproached { metric: QualityMetricKind },
|
|
ExtensionWarning(ExtensionWarningId),
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\begin{requirement}
|
|
\label{req:solver:report-authority}
|
|
The \texttt{SolveReport.layout} field is always populated, but
|
|
its authority depends on \texttt{status}:
|
|
|
|
\begin{itemize}
|
|
\item \texttt{Solved} or \texttt{SolvedWithWarnings}: the
|
|
layout is a valid layout that the editor may render.
|
|
\item \texttt{PartialBudgetExhausted}: the layout is a valid
|
|
layout (\texttt{satisfied\_hard\_constraints} is \texttt{true})
|
|
and the editor may render it, while noting that quality
|
|
is below the profile's target.
|
|
\item \texttt{Unsatisfiable} or \texttt{InternalError}: the
|
|
layout is diagnostic-only. Editors \MAY{} render it for the
|
|
user to see what failed, but \MUST{} clearly indicate the
|
|
layout is not authoritative.
|
|
\end{itemize}
|
|
|
|
A solver \MUSTNOT{} return \texttt{PartialBudgetExhausted} with
|
|
\texttt{satisfied\_hard\_constraints == false}. If hard constraints
|
|
cannot be satisfied within the budget, the correct status is
|
|
\texttt{Unsatisfiable}, with the partial layout marked as such.
|
|
\end{requirement}
|
|
|
|
\begin{requirement}
|
|
\label{req:solver:subconformant-report}
|
|
\textbf{Sub-conformant report shape (ratified Pass 12).}
|
|
A below-conformance passthrough solver (\texttt{SolverTier::Stub})
|
|
presented with declared constraints it does not evaluate reports
|
|
\texttt{SolvedWithWarnings} with
|
|
\texttt{satisfied\_hard\_constraints == false} and a warning
|
|
naming the unevaluated-constraint condition. The layout stays
|
|
renderable; the report makes no conformance claim and satisfies
|
|
no minimum-tier requirement. This is the one sanctioned case in
|
|
which a renderable status carries
|
|
\texttt{satisfied\_hard\_constraints == false}: the field reports
|
|
\emph{evaluated} satisfaction, and a non-evaluating solver has
|
|
nothing to claim.
|
|
\end{requirement}
|
|
|
|
\section{Constraint Families}
|
|
\label{sec:solver:families}
|
|
|
|
The solver consumes constraints in normalized form. The constraint
|
|
families enumerated here are the normative core; implementations
|
|
\MAY{} support additional families via
|
|
\texttt{LayoutConstraint::Registered}.
|
|
|
|
\subsection{Strength Levels}
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub enum ConstraintStrength {
|
|
/// Hard constraint. The solver MUST satisfy this constraint,
|
|
/// or return Unsatisfiable.
|
|
Required,
|
|
|
|
/// Soft constraint with an associated weight. The solver
|
|
/// minimizes the weighted violation when optimizing.
|
|
Preferred { weight: f64 },
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\begin{requirement}
|
|
\label{req:solver:required-constraint-strength}
|
|
A solver \MUSTNOT{} treat a \texttt{Required} constraint as
|
|
if it were \texttt{Preferred} for any reason, including for
|
|
quality optimization. A layout that violates a \texttt{Required}
|
|
constraint is not a valid layout, regardless of its quality
|
|
vector.
|
|
\end{requirement}
|
|
|
|
\begin{requirement}
|
|
\label{req:solver:kind-strength}
|
|
\textbf{Strength is kind-determined (ratified Pass 12).}
|
|
A constraint instance carries no strength field; its strength is
|
|
determined by its kind. Break constraints take their strength
|
|
from \texttt{BreakKind}: \texttt{Hard} $\to$ \texttt{Required},
|
|
\texttt{Soft} $\to$ \texttt{Preferred} with weight $1.0$. Every
|
|
other core constraint family normalizes to \texttt{Required}.
|
|
\texttt{Registered} constraint kinds normalize conservatively to
|
|
\texttt{Required}. A future constraint family declares its
|
|
strength (and weight, if \texttt{Preferred}) in its normative
|
|
definition --- the strength channel is the \emph{kind}, not the
|
|
instance.
|
|
\end{requirement}
|
|
|
|
\subsection{Normative Constraint Families}
|
|
|
|
The constraint catalog (spring, collision, alignment, containment,
|
|
break, aesthetic, extension) is specified in
|
|
Chapter~\ref{ch:layout-ir}. Implementations \MUST{} support every
|
|
family normatively required by the active solver tier
|
|
(Section~\ref{sec:solver:tiers}).
|
|
|
|
\section{Quality Metrics}
|
|
\label{sec:solver:quality}
|
|
|
|
The solver's output is evaluated against a normative set of quality
|
|
metrics. Each metric is normalized to the closed interval
|
|
$[0.0, 1.0]$, with $0.0$ representing the best achievable value and
|
|
$1.0$ representing the worst tolerable value before a profile
|
|
threshold flags the metric as failing.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
/// A quality metric value normalized to [0.0, 1.0].
|
|
/// Lower is always better.
|
|
pub struct NormalizedMetric(pub f64);
|
|
|
|
impl NormalizedMetric {
|
|
/// Construct, panicking if the value is not finite or is out
|
|
/// of range. Conforming implementations MUST construct only
|
|
/// valid NormalizedMetric values.
|
|
pub fn new(value: f64) -> Self {
|
|
assert!(value.is_finite());
|
|
assert!(0.0 <= value && value <= 1.0);
|
|
NormalizedMetric(value)
|
|
}
|
|
}
|
|
|
|
pub struct QualityMetricVector {
|
|
pub collision_penalty: NormalizedMetric,
|
|
pub spacing_distortion: NormalizedMetric,
|
|
pub slur_shape_penalty: NormalizedMetric,
|
|
pub beam_slope_penalty: NormalizedMetric,
|
|
pub vertical_density_penalty: NormalizedMetric,
|
|
pub system_break_penalty: NormalizedMetric,
|
|
pub page_fill_efficiency: NormalizedMetric,
|
|
pub casting_off_quality: NormalizedMetric,
|
|
pub symbol_density_uniformity: NormalizedMetric,
|
|
|
|
/// Additional metrics contributed by extensions. Each MUST be
|
|
/// a valid NormalizedMetric (finite, in [0.0, 1.0], lower is
|
|
/// better).
|
|
pub extension_metrics: Vec<ExtensionMetric>,
|
|
}
|
|
|
|
pub struct ExtensionMetric {
|
|
pub metric_id: ExtensionMetricId,
|
|
pub value: NormalizedMetric,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\begin{requirement}
|
|
\label{req:solver:normalized-metric-bounds}
|
|
Every \texttt{NormalizedMetric} value \MUST{} satisfy:
|
|
\begin{itemize}
|
|
\item It is finite (not NaN, not infinity).
|
|
\item It lies in the closed interval $[0.0, 1.0]$.
|
|
\item Lower values denote better layouts; higher values denote
|
|
worse layouts.
|
|
\end{itemize}
|
|
|
|
Per-metric normalization functions (mapping raw measurements to
|
|
$[0.0, 1.0]$) are specified in the Quality Metric Catalog
|
|
companion document. Implementations \MUST{} use the catalog's
|
|
normalization; arbitrary normalization is non-conforming.
|
|
|
|
Extension metrics \MAY{} define their own normalization function
|
|
but \MUSTNOT{} change orientation or range: $0.0$ remains best,
|
|
$1.0$ remains worst, and values must remain finite within range.
|
|
\end{requirement}
|
|
|
|
\subsection{Pareto Frontiers as Design Target}
|
|
|
|
Solvers should seek layouts on or near the Pareto frontier of the
|
|
normative metrics. This is the design intent. It is not the
|
|
conformance requirement.
|
|
|
|
\begin{requirement}
|
|
\label{req:solver:pareto-efficiency}
|
|
The normative metric set defines the axes along which layout
|
|
quality is evaluated. Solvers \SHOULD{} produce Pareto-efficient
|
|
layouts with respect to these metrics: layouts for which no
|
|
metric can be improved without worsening another. Because global
|
|
Pareto optimality is not practically verifiable for arbitrary
|
|
scores, conformance is determined by reference-suite thresholds
|
|
(Section~\ref{sec:solver:conformance}) rather than by proof of
|
|
Pareto-optimality.
|
|
|
|
When multiple Pareto-equivalent layouts exist, the configured
|
|
\texttt{TieBreakingWeights} select among them. Tie-breaking is
|
|
deterministic.
|
|
\end{requirement}
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub struct TieBreakingWeights {
|
|
pub collision: f64,
|
|
pub spacing: f64,
|
|
pub slur_shape: f64,
|
|
pub beam_slope: f64,
|
|
pub vertical_density: f64,
|
|
pub system_break: f64,
|
|
pub page_fill: f64,
|
|
pub casting_off: f64,
|
|
pub symbol_density: f64,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\begin{requirement}
|
|
\label{req:solver:tie-breaking-weight-defaults}
|
|
Tie-breaking weights \MUST{} have normative defaults specified
|
|
in the Quality Metric Catalog. Implementations \MAY{} permit
|
|
users to customize these weights; the defaults represent the
|
|
reference engraving aesthetic and are the basis for
|
|
reference-suite conformance.
|
|
\end{requirement}
|
|
|
|
\section{Conformance Tiers}
|
|
\label{sec:solver:tiers}
|
|
|
|
Solver conformance is graded into tiers, each declaring a subset
|
|
of features the implementation supports. Tiers are \emph{orthogonal}
|
|
to file-format profiles (Chapter~\ref{ch:format}); a document may
|
|
declare both an authoring file profile and a minimum solver tier,
|
|
and the two axes are independent.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub enum SolverTier {
|
|
/// Not a conformance tier: an interface-only / passthrough
|
|
/// solver that evaluates no constraints and computes no
|
|
/// quality metrics. Ordered below every conformant tier.
|
|
Stub,
|
|
|
|
/// Minimal Layout Solver.
|
|
Minimal,
|
|
|
|
/// Standard Engraving Solver.
|
|
Standard,
|
|
|
|
/// Advanced / Extension-Aware Solver.
|
|
Advanced,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
The \texttt{Stub} variant exists so scaffolding, harnesses, and
|
|
not-yet-conformant pipelines can label themselves honestly: it makes
|
|
no conformance claim, and a document requiring any conformant tier
|
|
is never satisfied by a solver declaring \texttt{Stub}.
|
|
|
|
\subsection{Minimal Layout Solver}
|
|
|
|
\begin{requirement}
|
|
\label{req:solver:minimal-tier}
|
|
A solver claiming the Minimal tier \MUST{}:
|
|
|
|
\begin{itemize}
|
|
\item Satisfy every hard constraint or return
|
|
\texttt{Unsatisfiable} (validity conformance).
|
|
\item Be deterministic within its declared implementation
|
|
version (Section~\ref{sec:solver:determinism}).
|
|
\item Produce well-formed \texttt{SolveReport} values with
|
|
accurate \texttt{satisfied\_hard\_constraints},
|
|
\texttt{unsatisfied\_constraints}, and metric vectors
|
|
(diagnostic conformance).
|
|
\item Support common-music-notation regions, metric time, and
|
|
the standard constraint families.
|
|
\item Pass the Minimal subset of the reference suite.
|
|
\end{itemize}
|
|
|
|
A Minimal solver \MAY{} produce mediocre aesthetic quality;
|
|
metric thresholds for the Minimal tier are relaxed relative to
|
|
the Standard tier.
|
|
\end{requirement}
|
|
|
|
\subsection{Standard Engraving Solver}
|
|
|
|
\begin{requirement}
|
|
\label{req:solver:standard-tier}
|
|
A solver claiming the Standard tier \MUST{}:
|
|
|
|
\begin{itemize}
|
|
\item Satisfy every Minimal-tier obligation.
|
|
\item Meet the Standard-tier metric thresholds on the
|
|
reference suite for every quality metric.
|
|
\item Support the incremental solving contract with at least
|
|
Measure-local invalidation scope
|
|
(Section~\ref{sec:solver:incremental}).
|
|
\item Support every Standard-tier constraint family declared
|
|
in the Quality Metric Catalog.
|
|
\end{itemize}
|
|
|
|
The Standard tier corresponds to ``professional engraving
|
|
quality'' for common-practice notation.
|
|
\end{requirement}
|
|
|
|
\subsection{Advanced / Extension-Aware Solver}
|
|
|
|
\begin{requirement}
|
|
\label{req:solver:advanced-tier}
|
|
A solver claiming the Advanced tier \MUST{}:
|
|
|
|
\begin{itemize}
|
|
\item Satisfy every Standard-tier obligation.
|
|
\item Support proportional and aleatoric region layout, not
|
|
only metric regions.
|
|
\item Support microtonal accidentals and their interactions
|
|
with adjacent glyphs.
|
|
\item Support extension-contributed constraint families and
|
|
extension metric thresholds for each registered extension
|
|
whose layout requirements are part of the Advanced
|
|
reference suite.
|
|
\item Support at least System-local invalidation scope
|
|
under incremental solving.
|
|
\end{itemize}
|
|
\end{requirement}
|
|
|
|
\subsection{Tier Declaration and Document Compatibility}
|
|
|
|
\begin{requirement}
|
|
\label{req:solver:solver-tier-compatibility}
|
|
Documents \MAY{} declare a minimum solver tier required for
|
|
faithful engraving in their profile declarations
|
|
(Chapter~\ref{ch:format}). A solver with tier lower than the
|
|
document's declared minimum \MAY{} open the document but
|
|
\SHOULD{} surface a clear warning that engraving quality may
|
|
not match the document's intent.
|
|
|
|
Solver tier is independent of file-format profile. A document
|
|
authored under the \texttt{Lite} file profile \MAY{} declare a
|
|
minimum solver tier of \texttt{Standard}; a document authored
|
|
under the \texttt{Full} file profile \MAY{} declare no minimum
|
|
tier and be displayable by a \texttt{Minimal} solver.
|
|
\end{requirement}
|
|
|
|
\section{Determinism}
|
|
\label{sec:solver:determinism}
|
|
|
|
The solver's determinism contract has two parts. \emph{Within-
|
|
implementation determinism} is a strict byte-equality obligation;
|
|
\emph{cross-implementation conformance} is reference-suite-based.
|
|
|
|
\subsection{Within-Implementation Determinism}
|
|
|
|
\begin{requirement}
|
|
\label{req:solver:within-implementation-byte-stability}
|
|
A solver implementation at a fixed version \MUST{} produce
|
|
byte-for-byte identical \texttt{SolveReport} output for
|
|
identical inputs, where ``identical inputs'' means:
|
|
|
|
\begin{itemize}
|
|
\item Identical \texttt{ConstrainedLayoutIR}.
|
|
\item Identical \texttt{SolverConfig} (profile, budget,
|
|
tie-breaking weights).
|
|
\item Identical font and glyph metrics (referenced by version
|
|
and content hash).
|
|
\item Identical reduction algorithm version
|
|
(Chapter~\ref{ch:semops}).
|
|
\item For incremental calls: identical prior
|
|
\texttt{SolverState} and identical invalidation set.
|
|
\item Identical solver implementation version (i.e., the same
|
|
binary).
|
|
\end{itemize}
|
|
|
|
Determinism \MUST{} hold across runs on the same machine, across
|
|
cold and warm starts, and across hardware threads.
|
|
Implementations \MUSTNOT{} use fast-math flags, processor-
|
|
dependent SIMD without canonical-output guarantees, or any
|
|
compiler options that vary floating-point results across builds
|
|
on the same platform.
|
|
|
|
Where parallelism is used internally, the solver \MUST{}
|
|
serialize the merge step so that thread scheduling does not
|
|
affect output. Deterministic budgets \MUST{} be enforced from
|
|
shared counters that capture all parallel contributions.
|
|
\end{requirement}
|
|
|
|
\subsection{Cross-Implementation Conformance}
|
|
|
|
\begin{requirement}
|
|
\label{req:solver:cross-implementation-conformance}
|
|
Cross-implementation determinism (byte-equality of outputs
|
|
across different conforming solvers) is \emph{not} required.
|
|
Different conforming solvers \MAY{} produce different layouts
|
|
for the same input. Conformance across implementations is
|
|
established by:
|
|
|
|
\begin{enumerate}
|
|
\item All hard constraints satisfied on every reference suite
|
|
score.
|
|
\item The implementation is internally deterministic per the
|
|
within-implementation rule above.
|
|
\item \texttt{SolveReport} values are well-formed and
|
|
diagnostically accurate.
|
|
\item For each reference-suite score, every quality metric is
|
|
within the tier's declared threshold.
|
|
\end{enumerate}
|
|
\end{requirement}
|
|
|
|
\begin{rationale}
|
|
Cross-implementation byte equality would freeze the ecosystem
|
|
to whatever single algorithm first claimed conformance, and
|
|
would preclude algorithmic innovation. Reference-suite
|
|
thresholds give users a meaningful quality floor while
|
|
permitting genuine implementation diversity. Within-
|
|
implementation determinism remains essential for reproducible
|
|
builds, regression testing, and predictable behavior under
|
|
collaborative editing.
|
|
\end{rationale}
|
|
|
|
\section{Incremental Solving}
|
|
\label{sec:solver:incremental}
|
|
|
|
\subsection{Invalidation Scopes}
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub enum InvalidationScope {
|
|
/// A single object's layout depends only on local context.
|
|
ObjectLocal,
|
|
/// Effects bounded within a single measure.
|
|
MeasureLocal,
|
|
/// Effects bounded within a single system.
|
|
SystemLocal,
|
|
/// Effects bounded within a single page.
|
|
PageLocal,
|
|
/// Effects bounded within a single region.
|
|
RegionLocal,
|
|
/// Effects may propagate across the entire score.
|
|
WholeScore,
|
|
}
|
|
|
|
pub struct InvalidationSet {
|
|
/// Declared scope. The solver MAY widen this scope
|
|
/// conservatively; it MUST NOT narrow it.
|
|
pub scope: InvalidationScope,
|
|
|
|
/// Specific invalidated entries within the declared scope.
|
|
pub slots: Vec<SpringSlotId>,
|
|
pub bands: Vec<VerticalBandId>,
|
|
pub constraints: Vec<ConstraintId>,
|
|
pub glyphs: Vec<GlyphObjectId>,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsection{Observational Equivalence}
|
|
|
|
\begin{requirement}
|
|
\label{req:solver:incremental-observational-equivalence}
|
|
For any invalidation set with declared scope $S$,
|
|
\texttt{solve\_incremental} \MUST{} produce a layout
|
|
observationally equivalent to \texttt{solve} called on the
|
|
same input restricted to $S$, under the same profile, the same
|
|
configuration, and the same deterministic budget. ``Observational
|
|
equivalence'' means byte-identical \texttt{ResolvedLayoutIR}
|
|
output for objects within $S$, and unchanged output for objects
|
|
outside $S$.
|
|
|
|
The solver \MAY{} widen the scope conservatively: an
|
|
\texttt{ObjectLocal} invalidation may be solved as
|
|
\texttt{MeasureLocal} if that simplifies the implementation,
|
|
provided the widened result is also observationally equivalent
|
|
to full solving at the widened scope.
|
|
|
|
The solver \MAY NOT{} narrow the scope. If
|
|
\texttt{SystemLocal} effects might propagate (e.g., an edit at
|
|
the system's end shifts subsequent systems' contents), the
|
|
invalidation \MUST{} be declared at least
|
|
\texttt{SystemLocal} or higher.
|
|
\end{requirement}
|
|
|
|
\subsection{Propagation Documentation}
|
|
|
|
\begin{requirement}
|
|
\label{req:solver:propagation-scope-contract}
|
|
Implementations \MUST{} document, per their published
|
|
conformance contract, the maximum invalidation scope they can
|
|
service for each constraint family. The documented bound is the
|
|
implementation's contract for how far edit effects propagate
|
|
before requiring whole-score re-solving.
|
|
\end{requirement}
|
|
|
|
\section{Conformance: The Reference Suite}
|
|
\label{sec:solver:conformance}
|
|
|
|
Solver conformance is established by performance on a normative
|
|
reference suite of test scores. Each tier has its own subset of
|
|
the suite and its own metric thresholds.
|
|
|
|
\begin{requirement}
|
|
\label{req:solver:reference-suite-passage}
|
|
The Reference Suite is delivered as a companion specification.
|
|
Each suite entry consists of:
|
|
|
|
\begin{itemize}
|
|
\item A test score in canonical \texttt{.musc} form.
|
|
\item Per-tier inclusion: whether the entry is required for
|
|
Minimal, Standard, or Advanced conformance.
|
|
\item Per-tier metric thresholds: the maximum permitted
|
|
\texttt{NormalizedMetric} value for each quality metric.
|
|
\item Optional fixed-expectation tests: specific layout
|
|
properties (a particular bar's width, a specific system
|
|
break location) that all conforming solvers \MUST{}
|
|
reproduce. Used sparingly, only where the suite intends to
|
|
pin an unambiguous expectation.
|
|
\end{itemize}
|
|
|
|
A solver claiming a given tier \MUST{} pass every entry
|
|
required at that tier. Failure on any single entry is
|
|
conformance failure at the claimed tier.
|
|
|
|
Suite versions are tied to specification versions; conforming
|
|
implementations \MUST{} declare which suite version they pass.
|
|
\end{requirement}
|
|
|
|
\begin{rationale}
|
|
The reference suite makes conformance checkable. The earlier
|
|
universal-Pareto formulation quantified over all possible
|
|
layouts of every possible score, which is undecidable.
|
|
Reference-suite thresholds replace ``no better layout exists''
|
|
with ``this layout is within tolerance on these specific
|
|
scores,'' which is testable, falsifiable, and operationally
|
|
useful. The cost is that conformance is only as good as the
|
|
suite's coverage; that is acceptable, because suite coverage is
|
|
visible, evolvable, and can be expanded as the spec matures.
|
|
\end{rationale}
|
|
|
|
\section{Reference Algorithm}
|
|
\label{sec:solver:reference-algo}
|
|
|
|
The reference algorithm is explanatory and diagnostic. Its purpose
|
|
is to disambiguate constraint interactions where prose alone might
|
|
leave a choice open, and to generate the expected outputs published
|
|
with the reference suite. It is \emph{not} the normative algorithm
|
|
and \emph{not} the aesthetic floor.
|
|
|
|
\begin{requirement}
|
|
\label{req:solver:algorithm-independence}
|
|
Conforming implementations are \emph{not} required to use the
|
|
reference algorithm or to reproduce its layouts, except where
|
|
a reference-suite entry explicitly fixes an expected behavior
|
|
via a fixed-expectation test. Implementations \MAY{} choose
|
|
any algorithm satisfying the contract: hard constraints,
|
|
determinism, diagnostics, reference-suite thresholds.
|
|
\end{requirement}
|
|
|
|
The reference algorithm is described in a non-normative companion
|
|
document. A natural starting point uses the Cassowary linear-
|
|
arithmetic constraint solver for the linear layer (positions,
|
|
alignments, containment) together with a separate numerical
|
|
optimization layer for aesthetic constraints (beam slopes, slur
|
|
curvatures) that are nonlinear in nature. Hybrid approaches and
|
|
alternative architectures (differentiable layout, constraint
|
|
programming, learned methods) are all conforming choices.
|
|
|
|
\section{Forward References}
|
|
|
|
\begin{itemize}
|
|
\item End-to-end performance requirements, including frame budgets
|
|
that the solver participates in, are in Chapter~\ref{ch:perf}.
|
|
Solver performance is measured in the context of the full
|
|
layout pipeline.
|
|
\item The Quality Metric Catalog companion document specifies
|
|
the exact normalization functions for each metric, the default
|
|
tie-breaking weights, and the per-tier thresholds.
|
|
\item The Reference Suite companion document specifies the
|
|
test scores, per-tier inclusion, and per-tier metric
|
|
thresholds.
|
|
\item The reference-algorithm companion document is non-normative
|
|
and describes one starting-point implementation.
|
|
\end{itemize}
|
|
|
|
% ===========================================================================
|
|
\chapter{Performance Requirements}
|
|
\label{ch:perf}
|
|
|
|
This chapter specifies performance contracts for the \emph{core
|
|
layer}. Targets are normative; they bound what conforming
|
|
implementations promise and what tests measure for conformance.
|
|
The chapter restates targets introduced informally in earlier
|
|
chapters and adds the measurement methodology.
|
|
|
|
\section{Core/Product Boundary}
|
|
\label{sec:perf:boundary}
|
|
|
|
Chapter~\ref{ch:intro} excluded the editor UI, the audio engine,
|
|
the renderer beyond the layout IR, and platform integrations from
|
|
the core's scope. Many performance properties of a working
|
|
notation system depend on those out-of-scope components: first
|
|
interactive frame after launch involves UI bootstrapping, edit
|
|
response involves UI event handling, scrolling and selection
|
|
involve a real renderer, audio acquisition involves a working
|
|
audio engine. The core cannot promise end-to-end timings for
|
|
behavior it does not own.
|
|
|
|
This chapter therefore distinguishes three obligation classes:
|
|
|
|
\begin{description}
|
|
\item[Core obligations.] The core data structures, layout IR,
|
|
solver, and file format \MUST{} admit the stated bounds. The
|
|
core's own work (reduction, layout, file writes, snapshot
|
|
acquisition) \MUST{} fit within the budgets allocated to it
|
|
in the end-to-end frame budget. The core \MUST{} expose APIs
|
|
suitable for each downstream component to do its own work
|
|
inside its remaining budget (e.g., a lock-free immutable
|
|
snapshot API for audio engines, an incremental invalidation
|
|
interface for renderers, a cancellable solve API for UIs).
|
|
\item[Product obligations.] End-to-end UI, audio, and rendering
|
|
targets (first interactive frame after launch, end-to-end
|
|
edit-to-pixel latency, end-to-end scroll smoothness,
|
|
audio-thread snapshot acquisition latency under load) are the
|
|
obligation of the product layers built atop the core. Those
|
|
targets are specified in product-specific companion documents,
|
|
not here.
|
|
\item[Performance Reference Suite obligations.] The Performance
|
|
Reference Suite (Appendix~\ref{app:deferred}) measures core
|
|
behavior only: layout time per system, file write/read time,
|
|
reduction throughput, solver budget consumption, snapshot
|
|
acquisition latency. End-to-end UI traces are out of scope
|
|
for the core's reference suite.
|
|
\end{description}
|
|
|
|
\begin{requirement}
|
|
\label{req:perf:core-product-budget-boundary}
|
|
Where this chapter states a performance target, the target is a
|
|
\emph{core obligation} unless explicitly labeled product
|
|
obligation. Core obligations \MUST{} be satisfiable by a
|
|
conforming core implementation independent of the product
|
|
layers built atop it; product-layer code is permitted to
|
|
consume budget on top of the core's portion, but the core's
|
|
own portion \MUST{} fit within the stated bound.
|
|
\end{requirement}
|
|
|
|
\section{Design Principles}
|
|
\label{sec:perf:principles}
|
|
|
|
\begin{description}
|
|
\item[Targets are part of the contract.] Core performance
|
|
requirements are not aspirations. A conforming core
|
|
implementation \MUST{} meet the stated targets on the
|
|
reference hardware profile under the stated measurement
|
|
conditions.
|
|
\item[Frame budgets dominate.] User experience is ultimately
|
|
governed by frame timing. The core's portion of each frame
|
|
budget is stated explicitly; the product layer's portion is
|
|
specified separately.
|
|
\item[Reference hardware profile.] All targets are stated
|
|
against a specified hardware profile. Implementations \MAY{}
|
|
document additional profiles (lower-end mobile, higher-end
|
|
workstation); the reference profile is the basis of
|
|
conformance.
|
|
\item[Measurement methodology is normative.] Without specified
|
|
measurement methodology, performance targets are
|
|
unenforceable. This chapter specifies how each target is
|
|
measured.
|
|
\item[Tail latency matters.] Average performance is
|
|
insufficient; targets are stated as percentiles (typically
|
|
p99 or worst-case) to reflect what users actually experience.
|
|
\end{description}
|
|
|
|
\section{Reference Hardware Profile}
|
|
\label{sec:perf:hardware}
|
|
|
|
The reference hardware profile defines the platform against which
|
|
performance conformance is measured. Implementations \MAY{} target
|
|
additional profiles; this profile is the basis of conformance.
|
|
|
|
\begin{lstlisting}
|
|
Reference Hardware Profile (2026 baseline):
|
|
|
|
Desktop reference:
|
|
- Apple silicon (M-series) or x86-64 equivalent
|
|
- 8 performance cores (or equivalent throughput)
|
|
- 16 GB RAM
|
|
- NVMe SSD storage
|
|
- Discrete or integrated GPU with Metal/Vulkan support
|
|
- Display: 1440p at 60 Hz minimum
|
|
|
|
Tablet reference:
|
|
- iPad Pro M-series or equivalent
|
|
- 8 GB RAM minimum
|
|
- SSD storage
|
|
- Display: 120 Hz Pro Motion or equivalent
|
|
\end{lstlisting}
|
|
|
|
\begin{requirement}
|
|
\label{req:perf:reference-hardware}
|
|
Conforming implementations \MUST{} meet the targets in this chapter
|
|
on the reference hardware profile. Implementations \MAY{} miss
|
|
targets on hardware below the reference profile (e.g., older
|
|
devices, low-end embedded targets); such gaps \MUST{} be documented
|
|
in the implementation's published performance contract.
|
|
|
|
The reference profile is updated periodically as commodity hardware
|
|
evolves. The version of the reference profile against which an
|
|
implementation conforms \MUST{} be declared.
|
|
\end{requirement}
|
|
|
|
\section{Frame Budgets}
|
|
\label{sec:perf:frame}
|
|
|
|
\subsection{Interactive Edit Latency}
|
|
|
|
\begin{requirement}
|
|
\label{req:perf:single-system-edit-latency}
|
|
The \emph{core's portion} of a single-system edit (operation
|
|
envelope construction, reduction, incremental layout through
|
|
\texttt{ResolvedLayoutIR}) \MUST{} complete within one frame
|
|
(16.7\,ms at 60\,Hz) at the p99 percentile, measured on the
|
|
reference hardware profile. This includes the case where the
|
|
affected system is on a 100-page orchestral score: the core's
|
|
incremental layout machinery (Chapter~\ref{ch:layout-ir}) and
|
|
the constraint solver's incremental contract
|
|
(Chapter~\ref{ch:solver}) jointly satisfy this requirement.
|
|
|
|
On 120\,Hz displays (tablet reference), the core's portion
|
|
\SHOULD{} complete within 8.3\,ms at p99. This is not required
|
|
for conformance but is the target for first-class tablet
|
|
performance.
|
|
|
|
End-to-end edit-to-pixel latency (input handling, hit testing,
|
|
render submission, display flip) is a product-layer obligation.
|
|
The core's contribution is bounded by this target so that the
|
|
product layer has the remaining frame budget to do its work.
|
|
\end{requirement}
|
|
|
|
\subsection{Multi-System Edits}
|
|
|
|
\begin{requirement}
|
|
\label{req:perf:multi-system-edit-latency}
|
|
Edits affecting up to ten systems (e.g., a transposition over
|
|
a substantial passage) \MUST{} complete within four frames
|
|
(66.7\,ms at 60\,Hz) at p99. The four-frame budget gives the
|
|
editor headroom to display a transient ``thinking'' indication
|
|
without violating perception of responsiveness.
|
|
\end{requirement}
|
|
|
|
\subsection{Full Re-Layout}
|
|
|
|
\begin{requirement}
|
|
\label{req:perf:full-relayout-latency}
|
|
Full re-layout of a 100-page orchestral score (triggered, for
|
|
example, by a tuning system change applied score-wide) \SHOULD{}
|
|
complete within two seconds at p99. This is a soft target; the
|
|
hard target on full re-layout is that the operation be
|
|
cancellable: a user beginning a different edit \MUST{} cause the
|
|
full re-layout to abort and the new edit to proceed.
|
|
\end{requirement}
|
|
|
|
\section{Cold Open}
|
|
\label{sec:perf:open}
|
|
|
|
\begin{requirement}
|
|
\label{req:perf:cold-open-latency}
|
|
The \emph{core's portion} of cold open of a 100-page orchestral
|
|
score from a \texttt{.musc} bundle \MUST{} reach
|
|
\emph{first-system-ready} state within one second on the
|
|
reference hardware profile.
|
|
|
|
First-system-ready is defined as: the prelude has been read,
|
|
the active superblock and manifest validated, the canonical
|
|
base snapshot (if any) acquired, post-frontier envelopes
|
|
reduced for at least the first system's coverage, and the
|
|
first system's \texttt{ResolvedLayoutIR} produced. Rendering,
|
|
hit-testing, and UI bootstrapping that build atop this state
|
|
are product-layer obligations.
|
|
|
|
Background loading of remaining systems \MAY{} continue after
|
|
first-system-ready and \MUSTNOT{} block user interaction
|
|
served from already-loaded state.
|
|
not-yet-visible systems is permitted; full-score loading need not
|
|
complete in one second.
|
|
\end{requirement}
|
|
|
|
\subsection{Cold Open Measurement}
|
|
|
|
Cold open is measured from process start (or, for warm processes
|
|
opening a new score, from the open API invocation) to the first
|
|
frame containing fully rendered content for the user's initial
|
|
viewport.
|
|
|
|
\subsection{Streaming Read Discipline}
|
|
|
|
\begin{requirement}
|
|
\label{req:perf:streaming-cold-open}
|
|
The cold open target \MUST{} be achieved through streaming reads
|
|
per Chapter~\ref{ch:format}: the implementation \MUSTNOT{} load
|
|
the full score into memory before displaying the first system.
|
|
Implementations failing to meet the target by eager loading are
|
|
non-conforming.
|
|
\end{requirement}
|
|
|
|
\section{Memory Budgets}
|
|
\label{sec:perf:memory}
|
|
|
|
\begin{requirement}
|
|
\label{req:perf:core-memory-budget}
|
|
Steady-state resident memory for a 100-page orchestral score
|
|
\SHOULD{} remain below 500\,MB on the reference hardware profile,
|
|
exclusive of evictable caches. Evictable caches \MAY{} push total
|
|
resident memory higher; they \MUST{} be evicted under memory
|
|
pressure without compromising correctness.
|
|
\end{requirement}
|
|
|
|
\subsection{Memory Categories}
|
|
|
|
For measurement, memory is partitioned into:
|
|
|
|
\begin{description}
|
|
\item[Core data.] The score graph, attachments, indexes, and
|
|
operation envelope set. Cannot be evicted without losing
|
|
state.
|
|
\item[Layout cache.] The cached IR stages. Evictable; rebuilds on
|
|
demand but at performance cost.
|
|
\item[Glyph and font caches.] Glyph render data and metric caches.
|
|
Evictable; small-to-moderate size.
|
|
\item[Audio buffers and reference tracks.] Audio data loaded for
|
|
playback or reference. Evictable.
|
|
\item[Implementation overhead.] Runtime, allocator metadata,
|
|
OS-level mappings.
|
|
\end{description}
|
|
|
|
\begin{requirement}
|
|
\label{req:perf:memory-category-reporting}
|
|
Implementations \MUST{} expose the core-data memory consumption
|
|
separately from evictable caches. Reporting tools \MUST{} be able
|
|
to distinguish them. The 500\,MB target applies to core data only.
|
|
\end{requirement}
|
|
|
|
\subsection{Per-Object Memory Discipline}
|
|
|
|
\begin{requirement}
|
|
\label{req:perf:per-object-memory-layout}
|
|
Implementations \MUSTNOT{} embed glyph metrics in IR objects
|
|
(Chapter~\ref{ch:layout-ir}). Implementations \MUSTNOT{} embed
|
|
spelling-attachment payloads on pitch objects
|
|
(Chapter~\ref{ch:pitch}); attachments are externally indexed.
|
|
These discipline requirements bound per-object memory at known
|
|
upper limits.
|
|
\end{requirement}
|
|
|
|
\section{Core Data Structures for Audio Thread Use}
|
|
\label{sec:perf:audio}
|
|
|
|
The audio engine is outside the core specification. The core's
|
|
obligation is to expose data structures that permit real-time
|
|
audio safety; audio-engine performance is the obligation of the
|
|
product audio layer (Section~\ref{sec:perf:boundary}).
|
|
|
|
\begin{requirement}
|
|
\label{req:perf:audio-thread-data-access}
|
|
The core's read-only data structures consumed by an audio
|
|
thread (pitch resolution, tempo map lookup, event arena
|
|
queries) \MUST{} be designed for allocation-free, lock-free
|
|
access. Specifically:
|
|
|
|
\begin{itemize}
|
|
\item Pitch frequency resolution given a tuning system \MUST{}
|
|
be performable without allocation.
|
|
\item Tempo-map conversion (musical to wall-clock and vice
|
|
versa) \MUST{} be performable without allocation.
|
|
\item Time-indexed event lookup for playback \MUST{} be
|
|
performable without allocation.
|
|
\end{itemize}
|
|
|
|
These are properties of the core's data layouts, not of any
|
|
particular audio engine. A product-layer audio engine that
|
|
consumes them is responsible for its own real-time discipline
|
|
(no syscalls on the audio thread, no priority inversion, no
|
|
unbounded loops); the core only promises that its data
|
|
structures do not preclude that discipline.
|
|
\end{requirement}
|
|
|
|
\subsection{Snapshot API}
|
|
|
|
\begin{requirement}
|
|
\label{req:perf:lock-free-snapshots}
|
|
The core \MUST{} provide an immutable-snapshot API: the
|
|
ability for any thread to acquire a read-only view of the
|
|
score graph at a consistent point in time, suitable for use
|
|
by an audio engine. Snapshot acquisition by the snapshot
|
|
reader \MUST{} be lock-free in the sense that it never blocks
|
|
on edit-thread progress: an edit in flight \MUSTNOT{} prevent
|
|
the snapshot reader from obtaining a valid (possibly older)
|
|
snapshot.
|
|
|
|
Snapshot \emph{release} \MAY{} be deferred to a non-audio
|
|
thread. The snapshot mechanism is implementation-defined
|
|
(double-buffered immutable copies, persistent data structures,
|
|
reference-counted immutable trees, hazard pointers); only the
|
|
externally observable lock-free acquisition property is
|
|
normative here.
|
|
|
|
End-to-end audio-thread acquisition latency under load is a
|
|
product-layer obligation; the core's contribution is bounded
|
|
by the lock-free guarantee.
|
|
\end{requirement}
|
|
|
|
\section{Constraint-Solver Performance Targets}
|
|
\label{sec:perf:solver}
|
|
|
|
\begin{requirement}
|
|
\label{req:perf:solver-latency}
|
|
The constraint solver (Chapter~\ref{ch:solver}) \MUST{} satisfy:
|
|
|
|
\begin{itemize}
|
|
\item Incremental solving of a single-system edit completes
|
|
within 10\,ms at p99 on the reference hardware profile.
|
|
(This is a tighter target than the overall frame budget; the
|
|
remaining 6.7\,ms accommodates the rest of the pipeline.)
|
|
\item Cold solving of a 100-page orchestral score completes
|
|
within two seconds at p99.
|
|
\item Incremental solving propagation is bounded as documented
|
|
in the implementation's published contract.
|
|
\end{itemize}
|
|
\end{requirement}
|
|
|
|
\section{File Format Performance}
|
|
\label{sec:perf:format}
|
|
|
|
\begin{requirement}
|
|
\label{req:perf:file-operation-latency}
|
|
File format operations \MUST{} satisfy:
|
|
|
|
\begin{itemize}
|
|
\item Bundle write of a typical edit (one or more operation
|
|
envelopes appended to the operation-envelope block stream,
|
|
manifest rewrite, superblock flip) completes within
|
|
50\,ms at p99 on the reference hardware profile.
|
|
\item Bundle read of the manifest and bootstrap chunks
|
|
(sufficient for first interactive frame) completes within
|
|
200\,ms at p99 for a 100-page orchestral score.
|
|
\item Operation-envelope reduction rate \MUST{} exceed
|
|
10{,}000 envelopes per second on the reference hardware
|
|
profile, measured during cold reduction from a fresh
|
|
canonical base.
|
|
\end{itemize}
|
|
\end{requirement}
|
|
|
|
\section{Measurement Methodology}
|
|
\label{sec:perf:methodology}
|
|
|
|
\begin{requirement}
|
|
\label{req:perf:measurement-methodology}
|
|
Performance conformance \MUST{} be measured using:
|
|
|
|
\begin{itemize}
|
|
\item A reference test corpus including the conformance suite
|
|
from Chapter~\ref{ch:solver}, augmented with edit traces
|
|
representative of typical authoring sessions.
|
|
\item Measurements at the p99 percentile across at least 1000
|
|
iterations per scenario.
|
|
\item Warm runs after a cold warm-up phase, unless the target
|
|
is explicitly cold (cold open, cold load).
|
|
\item Release builds with full optimization enabled, but without
|
|
fast-math or precision-compromising flags.
|
|
\item The reference hardware profile, with no other significant
|
|
load on the system during measurement.
|
|
\end{itemize}
|
|
|
|
Implementations \MUST{} publish their measurement results against
|
|
this methodology for conformance claims.
|
|
\end{requirement}
|
|
|
|
\section{Regression Testing}
|
|
\label{sec:perf:regression}
|
|
|
|
\begin{requirement}
|
|
\label{req:perf:performance-regression-gate}
|
|
Conforming implementations \SHOULD{} maintain continuous
|
|
performance regression testing against the reference corpus, with
|
|
alerts for regressions exceeding 10\% on any target. The
|
|
regression suite \SHOULD{} be a precondition for releases.
|
|
\end{requirement}
|
|
|
|
\section{Forward References}
|
|
|
|
\begin{itemize}
|
|
\item The full performance reference corpus is delivered as a
|
|
companion specification, the Performance Reference Suite.
|
|
\item Audio engine performance targets (latency, jitter, stream
|
|
discipline) are specified in the audio engine specification,
|
|
outside this document.
|
|
\item Network and synchronization performance for the collaboration
|
|
layer is specified in the collaboration specification, outside
|
|
this document.
|
|
\end{itemize}
|
|
|
|
% ===========================================================================
|
|
\chapter{Extension Points}
|
|
\label{ch:extension}
|
|
|
|
This chapter consolidates the extension points specified across
|
|
prior chapters into a single reference. Each extension point admits
|
|
implementation-defined or plugin-contributed content that the core
|
|
treats opaquely, subject to the contracts stated here.
|
|
|
|
The chapter is a navigation aid rather than a source of new
|
|
requirements: every extension point referenced here is defined in
|
|
its originating chapter, and the originating definition is
|
|
authoritative. This chapter pulls the points together for plugin
|
|
authors and future specification editors.
|
|
|
|
\section{Design Principles}
|
|
\label{sec:ext:principles}
|
|
|
|
\begin{description}
|
|
\item[Extensibility without forking.] Every extension point
|
|
permits content to be added without modifying the core
|
|
specification or its implementations. Plugins extend; they do
|
|
not fork.
|
|
\item[Identifiers, not strings.] Extension points are identified
|
|
by typed registry identifiers, not free-form strings. Registries
|
|
are explicit, versioned, and discoverable.
|
|
\item[Contracts, not contents.] Extension points specify what an
|
|
extension \emph{must satisfy} (interfaces, invariants, reduction
|
|
rules, performance bounds) rather than what an extension
|
|
\emph{must contain}.
|
|
\item[Capability-bound at the runtime.] Plugin runtime
|
|
permissions (read score, write score, network, filesystem) are
|
|
declared per extension and granted per user. This is enforced
|
|
by the plugin runtime, which is outside this specification; the
|
|
extension points are designed to admit such enforcement.
|
|
\item[Forward compatibility.] Extensions referenced in a stored
|
|
score \MUST{} be preserved across reads and writes even when the
|
|
reading implementation does not understand them (per
|
|
Chapter~\ref{ch:format}). Extensions thus survive distribution
|
|
even where they cannot be exercised.
|
|
\end{description}
|
|
|
|
\section{Notation Grammar Extensions}
|
|
\label{sec:ext:grammar}
|
|
|
|
The notation grammar layer supports extension at several points:
|
|
|
|
\begin{description}
|
|
\item[\texttt{PositionStructure::Registered}] (Chapter~\ref{ch:tuning}).
|
|
Custom position structures for pitch spaces whose organization
|
|
does not fit the chromatic, diatonic-over-chromatic, or JI
|
|
lattice families. Used for maqam, gamelan, raga, and other
|
|
non-Western grammars.
|
|
\item[\texttt{IntervalAlgebra::Registered}] (Chapter~\ref{ch:tuning}).
|
|
Custom interval algebras for grammar-specific arithmetic.
|
|
\item[\texttt{TranspositionBehavior::Registered}]
|
|
(Chapter~\ref{ch:tuning}). Custom transposition rules.
|
|
\item[\texttt{SpellingAlgorithmId}] (Chapter~\ref{ch:pitch}).
|
|
Registered spelling-inference algorithms for the spelling
|
|
pre-pass.
|
|
\item[\texttt{NominalRegistry}] (Chapter~\ref{ch:tuning}).
|
|
Score-level or grammar-level extensions to the catalog of
|
|
named position-letters.
|
|
\item[\texttt{AccidentalRegistry}] (Chapter~\ref{ch:tuning}).
|
|
Score-level extensions to the accidental catalog, subject to
|
|
the registry's \texttt{extensible} flag.
|
|
\end{description}
|
|
|
|
\section{Tuning System Extensions}
|
|
\label{sec:ext:tuning}
|
|
|
|
\begin{description}
|
|
\item[\texttt{TuningFunctionId}] (Chapter~\ref{ch:tuning}).
|
|
Registered procedural tuning functions (historical
|
|
temperaments, custom meantone variants, generative tunings).
|
|
\item[\texttt{AdaptiveTuningFunctionId}] (Chapter~\ref{ch:tuning}).
|
|
Registered adaptive tuning functions taking harmonic context as
|
|
input.
|
|
\item[\texttt{TuningFileFormat}] (Chapter~\ref{ch:tuning}).
|
|
Recognized external tuning file formats (Scala, MIDI Tuning
|
|
Standard, additional formats via registry).
|
|
\item[\texttt{CompatibilityMapping}] (Chapter~\ref{ch:tuning}).
|
|
Declared compatibility between pitch spaces and tuning systems
|
|
whose underlying spaces differ.
|
|
\end{description}
|
|
|
|
\section{Glyph and Font Extensions}
|
|
\label{sec:ext:glyphs}
|
|
|
|
\begin{description}
|
|
\item[\texttt{CustomGlyphId}] (Chapter~\ref{ch:tuning}).
|
|
Custom glyphs not in SMuFL, defined per score or per plugin.
|
|
\item[\texttt{Composite} glyph references] (Chapter~\ref{ch:tuning}).
|
|
Composite glyphs constructed from multiple primary glyph
|
|
references.
|
|
\item[\texttt{ModificationRegistryId}] (Chapter~\ref{ch:tuning}).
|
|
Registered pitch-space modifications for grammar-defined
|
|
accidentals.
|
|
\end{description}
|
|
|
|
\section{Score Graph Extensions}
|
|
\label{sec:ext:graph}
|
|
|
|
\begin{description}
|
|
\item[\texttt{ExtensionOp}] (Chapter~\ref{ch:semops}).
|
|
Implementation-defined operation kinds extending the core's
|
|
semantic operations. Extension operations \MUST{} satisfy the
|
|
operation framework's requirements (preconditions, effects,
|
|
inverse, reduction rule, re-anchoring).
|
|
\item[\texttt{CustomMarkerId}] (Chapter~\ref{ch:graph}).
|
|
Custom marker kinds beyond the core's standard catalog.
|
|
\item[\texttt{CustomAnalyticalKindId}] (Chapter~\ref{ch:graph}).
|
|
Custom analytical annotation kinds.
|
|
\item[\texttt{Custom event variants}] (Chapter~\ref{ch:graph}).
|
|
The event taxonomy is closed in the core, but the \texttt{Group}
|
|
layout object and the \texttt{Registered} fields throughout
|
|
permit composition of core types into extended structures.
|
|
\end{description}
|
|
|
|
\section{Layout IR Extensions}
|
|
\label{sec:ext:layout}
|
|
|
|
\begin{description}
|
|
\item[\texttt{SynthesisRegistryId}] (Chapter~\ref{ch:layout-ir}).
|
|
Custom synthesis kinds for engraver-generated objects.
|
|
\item[\texttt{ConstraintRegistryId}] (Chapter~\ref{ch:layout-ir}).
|
|
Registered layout constraint kinds extending the standard
|
|
constraint families.
|
|
\item[\texttt{MappingRegistryId}] (Chapter~\ref{ch:graph}).
|
|
Custom parameter mappings for graphic-object time bindings.
|
|
\item[\texttt{Custom view kinds}] (Chapter~\ref{ch:graph}).
|
|
The \texttt{ViewKind::Custom} variant admits user-defined
|
|
view kinds.
|
|
\end{description}
|
|
|
|
\section{Constraint-Solver Extensions}
|
|
\label{sec:ext:solver}
|
|
|
|
\begin{description}
|
|
\item[\texttt{LayoutConstraint::Registered}]
|
|
(Chapter~\ref{ch:layout-ir}). Custom constraint families
|
|
consumed by the solver. Extension constraints \MUST{} declare
|
|
their strength. When soft, extension constraints contribute
|
|
to the quality vector through an extension metric subject to
|
|
the \texttt{NormalizedMetric} discipline
|
|
(Chapter~\ref{ch:solver}) and to the tier-specific reference-
|
|
suite thresholds.
|
|
\end{description}
|
|
|
|
\section{File Format Extensions}
|
|
\label{sec:ext:format}
|
|
|
|
\begin{description}
|
|
\item[\texttt{FormatProfile::Custom}] (Chapter~\ref{ch:format}).
|
|
Profile definitions beyond Full, ReadOnly, and Lite.
|
|
\item[\texttt{Self-describing extension envelope}]
|
|
(Chapter~\ref{ch:format}). Plugin-contributed and
|
|
forward-compatible content is carried in self-describing
|
|
extension records that the core preserves opaquely.
|
|
\end{description}
|
|
|
|
\section{Extension Registry Contract}
|
|
\label{sec:ext:registry}
|
|
|
|
Every extension registry shares a common structural contract.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub trait ExtensionRegistry {
|
|
type Id;
|
|
type Definition;
|
|
|
|
/// Resolve an identifier to its definition.
|
|
fn resolve(&self, id: Self::Id) -> Option<&Self::Definition>;
|
|
|
|
/// Enumerate all registered definitions.
|
|
fn enumerate(&self) -> Vec<&Self::Definition>;
|
|
|
|
/// Version of the registry. Score files declare which version
|
|
/// their references target.
|
|
fn version(&self) -> RegistryVersion;
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\begin{requirement}
|
|
\label{req:ext:registry-versioning}
|
|
Every extension registry \MUST{} be versioned. Stored references
|
|
to registered definitions \MUST{} include the registry version
|
|
they target. Loading a score with references to registry versions
|
|
newer than the reader understands \MUST{} preserve those references
|
|
per the forward-compatibility rules of Chapter~\ref{ch:format}.
|
|
\end{requirement}
|
|
|
|
\subsection{Identifier Stability}
|
|
|
|
\begin{requirement}
|
|
\label{req:ext:registry-id-stability}
|
|
Registry identifiers \MUST{} be stable across registry versions
|
|
within a major version. Renumbering, reassignment, or repurposing
|
|
of identifiers is forbidden within a major version. Removal of a
|
|
definition \MUST{} reserve its identifier; the identifier \MUSTNOT{}
|
|
be reassigned to a different definition.
|
|
\end{requirement}
|
|
|
|
\subsection{Discoverability}
|
|
|
|
Registries \MUST{} support enumeration: an implementation can list
|
|
all known definitions and their identifiers. This enables tooling
|
|
(plugin browsers, score migration assistants, conformance testers)
|
|
to operate against unknown registries by inspection.
|
|
|
|
\section{Plugin Runtime Considerations}
|
|
\label{sec:ext:runtime}
|
|
|
|
The plugin runtime is outside this specification, but extension
|
|
points are designed to admit a capability-bound runtime as described
|
|
in the feature compendium:
|
|
|
|
\begin{itemize}
|
|
\item Extensions are isolatable: an extension's failure does not
|
|
crash the host.
|
|
\item Extensions are introspectable: their declared capabilities
|
|
and inputs are inspectable before execution.
|
|
\item Extensions are revocable: an extension can be disabled
|
|
without invalidating the scores that reference it.
|
|
\end{itemize}
|
|
|
|
\begin{requirement}
|
|
\label{req:ext:disabled-extension-preservation}
|
|
Extension points \MUST{} be designed such that disabling an
|
|
extension does not invalidate stored content referencing it. The
|
|
content remains in the score, opaque to the host, until the
|
|
extension is re-enabled or the references are explicitly removed
|
|
by the user.
|
|
\end{requirement}
|
|
|
|
\section{Conformance and Extensions}
|
|
\label{sec:ext:conformance}
|
|
|
|
Extensions do not affect core conformance directly, but they
|
|
interact with it:
|
|
|
|
\begin{requirement}
|
|
\label{req:ext:extension-conformance}
|
|
A conforming implementation:
|
|
|
|
\begin{itemize}
|
|
\item \MUST{} support every extension point as a structural
|
|
slot (i.e., scores containing extension references are
|
|
readable and writable).
|
|
\item \MUSTNOT{} require any specific extension to be installed
|
|
for core conformance.
|
|
\item \MAY{} require specific extensions for specific features
|
|
(e.g., a particular plugin for a particular grammar).
|
|
\item \MUST{} clearly distinguish between core conformance and
|
|
extension-dependent features in its documentation.
|
|
\end{itemize}
|
|
\end{requirement}
|
|
|
|
\section{Forward References}
|
|
|
|
\begin{itemize}
|
|
\item The plugin runtime (WebAssembly-based, capability-bound)
|
|
is specified in the Plugin Runtime specification, outside this
|
|
document.
|
|
\item The complete extension registry catalog is delivered as a
|
|
companion document.
|
|
\item Implementation-defined extension contributions are governed
|
|
by the implementation's published extension specification.
|
|
\end{itemize}
|
|
|
|
% ===========================================================================
|
|
\appendix
|
|
|
|
\chapter{Intentionally Deferred Types and Specifications}
|
|
\label{app:deferred}
|
|
|
|
This appendix catalogs types, concepts, and specifications that
|
|
the core specification refers to but does not fully define. Each
|
|
entry is \emph{deliberately external}: it is the responsibility
|
|
of a named companion specification or a future revision of this
|
|
document. The purpose of this appendix is to distinguish
|
|
deliberately external content from accidentally missing content,
|
|
so that reviewers and implementers can quickly tell which
|
|
unresolved references represent open work and which represent
|
|
boundaries to other documents.
|
|
|
|
\section{Companion Specifications}
|
|
\label{sec:deferred:companions}
|
|
|
|
The following are companion specifications referenced throughout
|
|
the core. None has been delivered as of this revision; each is
|
|
named here with the chapter(s) that depend on it.
|
|
|
|
\begin{description}
|
|
\item[Binary Format companion.] Byte-level encoding of all
|
|
canonical chunks: varint conventions, exact field widths,
|
|
record layouts, endianness rules, string encoding details.
|
|
Referenced from Chapter~\ref{ch:format}. The on-disk
|
|
representations specified in that chapter are structural;
|
|
the Binary Format specifies how the bits are laid out.
|
|
\item[Text Projection companion.] The canonical s-expression
|
|
text projection of the bundle: grammar, deterministic
|
|
serialization rules, base64 encoding for opaque payloads,
|
|
handling of extension data, round-trip semantics with
|
|
binary form. Referenced from Chapter~\ref{ch:format}
|
|
Section~\ref{sec:format:textproj}.
|
|
\item[Profile Conformance specification.] The complete
|
|
catalog of file-format profiles (Full, ReadOnly, Lite, plus
|
|
any normative custom profiles), with their constraints,
|
|
permitted compression algorithms, maximum block sizes,
|
|
required extensions, and conformance test discipline.
|
|
Referenced from Chapter~\ref{ch:format}
|
|
Section~\ref{sec:format:profiles}.
|
|
\item[Quality Metric Catalog.] Per-metric normalization
|
|
functions mapping raw measurements to \texttt{NormalizedMetric}
|
|
values, default tie-breaking weights, per-tier metric
|
|
thresholds, and the formal definition of each quality
|
|
metric in the normative metric set. Referenced from
|
|
Chapter~\ref{ch:solver}.
|
|
\item[Reference Suite.] The collection of test scores against
|
|
which solver conformance is established, with per-tier
|
|
inclusion, per-tier metric thresholds, and any
|
|
fixed-expectation tests. Versioned with this specification.
|
|
Referenced from Chapter~\ref{ch:solver}
|
|
Section~\ref{sec:solver:conformance}.
|
|
\item[Performance Reference Suite.] The collection of edit
|
|
traces and reference scores against which performance
|
|
conformance is established. Referenced from
|
|
Chapter~\ref{ch:perf}.
|
|
\item[Operation Catalog companion.] The complete list of
|
|
primitive operations and compound operations supported by
|
|
the core, each with full preconditions, effects, inverse,
|
|
and reduction rule. The representative operations in
|
|
Chapter~\ref{ch:semops} illustrate the contract; the full
|
|
catalog is delivered separately.
|
|
\item[Reference Algorithm companion.] A non-normative
|
|
description of one starting-point implementation of the
|
|
constraint solver. Explanatory and diagnostic only;
|
|
conforming implementations are not required to use it.
|
|
Referenced from Chapter~\ref{ch:solver}
|
|
Section~\ref{sec:solver:reference-algo}.
|
|
\end{description}
|
|
|
|
\section{Externally Provided Components}
|
|
\label{sec:deferred:external}
|
|
|
|
The following are real components on which the core depends but
|
|
which are owned by other specifications outside the
|
|
Epiphany core scope.
|
|
|
|
\begin{description}
|
|
\item[SMuFL glyph catalog.] The Standard Music Font Layout
|
|
provides the normative glyph repertoire. Referenced from
|
|
Chapter~\ref{ch:tuning} and the layout chapters. SMuFL is
|
|
maintained by the W3C Music Notation Community Group; the
|
|
core spec declares its dependency on the SMuFL version
|
|
targeted but does not redefine SMuFL.
|
|
\item[Font metric provider.] Implementations resolve glyph
|
|
geometric data (bounding boxes, anchor points, advance
|
|
widths) through a font metric provider. The provider's
|
|
interface is part of the implementation contract; specific
|
|
font metrics are external content.
|
|
\item[Audio engine specification.] Out of scope here per
|
|
Chapter~\ref{ch:intro}. The core exposes data structures
|
|
designed for allocation-free, lock-free audio-thread access
|
|
(Chapter~\ref{ch:perf}); the audio engine's internals,
|
|
latency obligations, and stream discipline are specified
|
|
separately.
|
|
\item[Plugin runtime specification.] A capability-bound,
|
|
sandboxed runtime hosts extension code. The runtime is
|
|
out of scope here; the extension points
|
|
(Chapter~\ref{ch:extension}) are designed to admit such a
|
|
runtime.
|
|
\item[Collaboration transport.] The wire protocol by which
|
|
operation envelopes are exchanged between replicas is
|
|
out of scope here. Chapter~\ref{ch:semops} specifies what
|
|
must be exchanged and how it is reduced; the transport
|
|
layer is separate.
|
|
\item[User interface and editor.] Editor commands, selection
|
|
semantics, keyboard shortcuts, gesture grammar, and
|
|
UI-level interaction patterns are out of scope. The core
|
|
exposes a programmatic operation API; UI mapping is
|
|
layered above.
|
|
\end{description}
|
|
|
|
\section{Open Canonical Algorithms}
|
|
\label{sec:deferred:open-algos}
|
|
|
|
The following algorithms affect canonical state but are not yet
|
|
specified normatively, profile-declared, or marked
|
|
non-canonical. Per Appendix~\ref{app:determinism}
|
|
Section~\ref{sec:det:open}, each \MUST{} receive one of those
|
|
dispositions before the document can claim full canonical
|
|
score determinism across implementations.
|
|
|
|
\begin{description}
|
|
\item[Spelling pre-pass algorithm.] The algorithm that
|
|
computes default spellings from key signature, harmonic
|
|
context, and notation grammar. Referenced from
|
|
Chapter~\ref{ch:pitch}. Several established algorithms
|
|
exist (Longuet-Higgins, Temperley pitch-spelling preference
|
|
rules, and others); the choice of normative algorithm or
|
|
profile-declared algorithm set is open.
|
|
\item[Notational decomposition algorithm.] The algorithm that
|
|
breaks a sounding duration into notehead values, augmentation
|
|
dots, and ties under metric context. Referenced from
|
|
Chapter~\ref{ch:time}. Conservatoire engraving manuals
|
|
describe the conventions verbosely; the formalization is
|
|
open.
|
|
\item[Tempo curve integration.] The numerical integration
|
|
algorithm for converting musical position to wall-clock
|
|
position under \texttt{TempoShape::Curve}. Referenced from
|
|
Chapter~\ref{ch:time}. Possible candidates include Romberg
|
|
integration and adaptive Gauss-Kronrod; the normative
|
|
choice is open.
|
|
\item[Wallclock-to-musical root-finding.] The root-finding
|
|
algorithm for the inverse of tempo curve integration.
|
|
Referenced from Chapter~\ref{ch:time}. Possible candidates
|
|
include bisection with monotonicity guarantees and
|
|
Brent's method; the normative choice is open.
|
|
\end{description}
|
|
|
|
\section{Extension Registry Catalogs}
|
|
\label{sec:deferred:registries}
|
|
|
|
The extension registries enumerated in
|
|
Chapter~\ref{ch:extension} (notation grammar extensions,
|
|
tuning system extensions, glyph and font extensions, score
|
|
graph extensions, layout IR extensions, solver extensions,
|
|
file format extensions) are referenced by typed registry
|
|
identifiers in the core. The concrete catalogs of registered
|
|
extensions (the contents of each registry) are delivered as
|
|
separate, versioned registry documents. Each registry document
|
|
specifies its identifier numbering, the version under which
|
|
each registered identifier was added, and the registered
|
|
content's contract.
|
|
|
|
\section{Support-Type Identity Semantics}
|
|
\label{sec:deferred:support-types}
|
|
|
|
Several identifier-like support types appear throughout the
|
|
specification by name and shape, but their detailed encoding is
|
|
delivered by companion specifications. Their semantic identity
|
|
rules are normative here.
|
|
|
|
\begin{longtable}{p{3.4cm} p{3.6cm} p{6.6cm}}
|
|
\toprule
|
|
\textbf{Type} & \textbf{Source} & \textbf{Semantic rule} \\
|
|
\midrule
|
|
\endhead
|
|
|
|
\texttt{FileUuid} &
|
|
128-bit random at bundle creation &
|
|
Physical bundle identity. Fixed for the lifetime of the
|
|
physical bundle; preserved by filesystem byte-copy; freshly
|
|
minted on Save As, export-as-new-bundle, and derivative-work
|
|
creation. \\
|
|
|
|
\texttt{DocumentId} &
|
|
128-bit random at logical-work creation &
|
|
Logical work identity. Stable across Save As copies intended
|
|
to remain versions of the same work. May persist across forks
|
|
at user discretion. \\
|
|
|
|
\texttt{LineageId} &
|
|
128-bit random at fork-with-ancestry &
|
|
Shared-ancestor identity. Optional. Multiple documents may
|
|
share a \texttt{LineageId} declaring common ancestry for
|
|
version-control or genealogy purposes. \\
|
|
|
|
\texttt{ManifestId} &
|
|
Content-derived from manifest payload &
|
|
Unique per manifest version. Derived as
|
|
\texttt{trunc128(BLAKE3("MUSCMNIF" || document\_id ||
|
|
generation || manifest\_body))} with the \texttt{manifest\_id}
|
|
field excluded from \texttt{manifest\_body}; the derivation is
|
|
ratified normatively in
|
|
Section~\ref{req:format:manifest-id}. \\
|
|
|
|
\texttt{SnapshotId} &
|
|
Content-derived from snapshot payload and frontier &
|
|
Stable for a snapshot's root and causal frontier. Two snapshots
|
|
with identical materialized state at identical frontiers have
|
|
identical ids. The Binary Format companion defines the exact
|
|
derivation. \\
|
|
|
|
\texttt{BlobId} &
|
|
BLAKE3 of blob content with domain tag \texttt{"MUSCBLOB"} &
|
|
Content hash of the blob's uncompressed payload, identical to
|
|
the blob's \texttt{ContentHash}. \\
|
|
|
|
\texttt{AuthorId} &
|
|
Implementation-defined; persists across replica reseatings &
|
|
Identifies the human author of operations, distinct from
|
|
\texttt{ReplicaId} (which identifies the device or session).
|
|
One author may operate multiple replicas; one replica may host
|
|
one author at a time. The collaboration transport companion
|
|
defines authentication. \\
|
|
|
|
\texttt{ReductionAlgorithmVersion} &
|
|
Semantic version &
|
|
Selects canonical reduction semantics. Two replicas with
|
|
different \texttt{ReductionAlgorithmVersion} may produce
|
|
different canonical states from the same operation set; the
|
|
active superblock declares the version under which the
|
|
bundle's canonical base was materialized
|
|
(Section~\ref{sec:format:bundle}). \\
|
|
|
|
\texttt{SolverProfile} &
|
|
Registered profile identifier &
|
|
Selects the solver's hard-constraint set, normalized-metric
|
|
thresholds, tie-breaking weights, and active extension
|
|
catalog (Chapter~\ref{ch:solver}). The Quality Metric Catalog
|
|
companion defines the registered profile catalog. \\
|
|
|
|
\texttt{ProfileId} (file format) &
|
|
Registered profile identifier &
|
|
Selects the file-format conformance subset (Full, ReadOnly,
|
|
Lite, or registered Custom). The Profile Conformance companion
|
|
defines the registered profile catalog. \\
|
|
|
|
\texttt{ContentHash} &
|
|
BLAKE3-256 output (32 bytes) &
|
|
Cryptographic content hash. The canonical hash for all
|
|
content-addressed objects in the bundle. \\
|
|
|
|
\texttt{ChunkId} &
|
|
Newtype around \texttt{ContentHash} &
|
|
Same 32 bytes as the underlying \texttt{ContentHash}; the
|
|
type distinction makes role visible at use sites. \\
|
|
|
|
\texttt{OperationId} &
|
|
64-bit \texttt{ReplicaId} + 64-bit counter, both
|
|
randomly initialized; counter monotonic &
|
|
Stable identity of an operation, set by the authoring replica
|
|
at the moment of authoring. Never reassigned. \\
|
|
|
|
\texttt{TransactionId} &
|
|
128-bit, author-minted &
|
|
Identifies a transaction declared by a
|
|
\texttt{DeclareTransaction} envelope. Member envelopes
|
|
reference this id in their envelope \texttt{transaction}
|
|
field. \\
|
|
|
|
\texttt{ConflictId} &
|
|
Content-derived via BLAKE3 truncation with domain tag
|
|
\texttt{"MUSCCONF"} &
|
|
Identifier of a conflict record. Derived from canonical kind,
|
|
sorted causing operations, sorted affected objects.
|
|
Section~\ref{sec:semops:conflict-id}. \\
|
|
|
|
\texttt{Typed identifiers} (EventId, PitchId, VoiceId,
|
|
StaffId, etc.) &
|
|
64-bit \texttt{ReplicaId} + 64-bit counter; or
|
|
\texttt{ReplicaId::SYSTEM\_DERIVED} + BLAKE3-derived counter
|
|
for system-derived identifiers &
|
|
Stable identity within their typed namespace.
|
|
Section~\ref{sec:graph:system-derived}. \\
|
|
|
|
\bottomrule
|
|
\end{longtable}
|
|
|
|
\begin{requirement}
|
|
\label{req:deferred:identity-semantics}
|
|
Implementations \MUST{} respect the semantic identity rules
|
|
declared above. They \MAY{} freely choose the bit-level
|
|
encoding of each type (the Binary Format companion specifies
|
|
the canonical wire encoding); they \MAY NOT{} reinterpret the
|
|
identity semantics. In particular, two distinct works \MUSTNOT{}
|
|
share a \texttt{DocumentId}; two byte-distinct chunks \MUSTNOT{}
|
|
share a \texttt{ContentHash}; two distinct operations \MUSTNOT{}
|
|
share an \texttt{OperationId}.
|
|
\end{requirement}
|
|
|
|
\section{Distinction from Open Questions}
|
|
\label{sec:deferred:vs-open}
|
|
|
|
This appendix lists content that is \emph{deliberately external}
|
|
to the core specification: it is owned by a companion document,
|
|
a future revision, or a third-party specification.
|
|
|
|
It does \emph{not} list ``open questions'' in the core's own
|
|
design space. Those are marked with the
|
|
\textsf{\textit{Open Question}} callout where they appear in
|
|
the body of the text, and they represent unresolved decisions
|
|
\emph{within} the core specification's scope. The two
|
|
categories overlap only at the four open canonical algorithms
|
|
in Section~\ref{sec:deferred:open-algos}, which are unresolved
|
|
in the core text \emph{and} whose resolution will likely take
|
|
the form of a companion specification or a profile declaration.
|
|
|
|
\chapter{Glossary}
|
|
\label{app:glossary}
|
|
|
|
This glossary defines key terms used throughout the specification.
|
|
Definitions are drawn from their introducing chapters; the chapter
|
|
reference points to the authoritative definition.
|
|
|
|
\begin{description}
|
|
\item[Acoustic pitch] (Chapter~\ref{ch:pitch}). The frequency
|
|
realization of a pitch under an active tuning system. One of the
|
|
two intrinsic layers of a \texttt{Pitch}.
|
|
\item[Adaptive tuning] (Chapter~\ref{ch:tuning}). A tuning system
|
|
whose frequency resolution depends on harmonic context, not on
|
|
pitch-space position alone.
|
|
\item[Aleatoric time] (Chapter~\ref{ch:time}). A region time model
|
|
in which events have partial or undefined ordering, expressed
|
|
as a directed acyclic graph with optional interval bounds.
|
|
\item[Anchor] (Chapter~\ref{ch:time}). A reference to a point in
|
|
time, expressed relative to an identified object (event,
|
|
measure, region, wall-clock time) plus an offset. Anchors
|
|
survive edits that do not delete their target.
|
|
\item[Arena (event arena)] (Chapter~\ref{ch:graph}). The flat
|
|
storage owned by a score holding all events of all kinds, with
|
|
$O(1)$ lookup by \texttt{EventId}.
|
|
\item[Bundle] (Chapter~\ref{ch:format}). The on-disk container
|
|
holding a score: header, manifest, chunk store, and blob store
|
|
in a single \texttt{.musc} file.
|
|
\item[Canonical reduction] (Chapter~\ref{ch:semops}). The
|
|
deterministic procedure that materializes a score graph from
|
|
the operation set: sort by canonical order, group transactions,
|
|
apply each operation against working state, record effects.
|
|
\item[Canvas] (Chapter~\ref{ch:graph}). The spatial root of a
|
|
score: a 2D space partitioned into regions of staff-based,
|
|
free-graphic, or hybrid content.
|
|
\item[Causal predecessors] (Chapter~\ref{ch:semops}). The set of
|
|
operations on which a given operation causally depends. Used by
|
|
the CRDT layer to determine concurrency. Expressed in canonical
|
|
form as a dotted version vector
|
|
(\texttt{CausalContext}); see ``Dotted version vector.''
|
|
\item[Chunk] (Chapter~\ref{ch:format}). An immutable
|
|
content-addressed unit of storage within a bundle. Identified
|
|
by the cryptographic hash of its uncompressed payload, with a
|
|
domain-separated preimage.
|
|
\item[Compound operation] (Chapter~\ref{ch:semops}). A user-facing
|
|
operation that expands into a sequence of primitive operations
|
|
applied as a transaction.
|
|
\item[Conflict record] (Chapter~\ref{ch:semops}). A first-class
|
|
graph object recording an operation that could not apply
|
|
cleanly under canonical reduction. Stable, addressable,
|
|
user-visible, and resolvable by subsequent
|
|
\texttt{ResolveConflict} operations.
|
|
\item[CRDT] (Chapters~\ref{ch:semops},~\ref{ch:format}).
|
|
Conflict-free replicated data type. In Epiphany, the
|
|
\emph{operation set} is a true CRDT (a grow-only set of
|
|
operation envelopes); the materialized score graph is
|
|
\emph{not} itself a CRDT but is the deterministic reduction
|
|
of that set.
|
|
\item[Cross-cutting structure] (Chapter~\ref{ch:graph}). A
|
|
first-class object spanning multiple tree-nodes via references:
|
|
slurs, ties, beams, spanners, markers, repeats, analytical
|
|
annotations, comments, graphic gestures, lyrics, chord symbols.
|
|
\item[Decomposition] (Chapter~\ref{ch:time}). The notational
|
|
breakdown of an event's sounding duration into notehead values,
|
|
augmentation dots, and ties. Attached externally by the
|
|
decomposition pre-pass.
|
|
\item[Delta] (Chapter~\ref{ch:semops}). The structural record of
|
|
an operation's effect, sufficient to reconstruct the pre-state
|
|
of every object the operation modifies.
|
|
\item[Document identifier] (Chapter~\ref{ch:format}). The
|
|
logical work identity (\texttt{DocumentId}), stable across
|
|
Save As copies that are intended to remain versions of the
|
|
same work. May persist across forks at the user's
|
|
discretion. Distinct from \texttt{FileUuid}
|
|
(physical bundle identity, changes on Save As) and
|
|
\texttt{LineageId} (optional shared-ancestor identity).
|
|
\item[Dotted version vector] (Chapters~\ref{ch:semops},
|
|
\ref{ch:format}). The compact causal-context representation
|
|
of operation history. Operational semantics (causal
|
|
precedence, frontier coverage, monotonic growth) are
|
|
specified in Chapter~\ref{ch:semops}; the on-disk
|
|
serialization is specified in Chapter~\ref{ch:format}.
|
|
\item[Edit barrier] (Chapter~\ref{ch:format}). A structured
|
|
declaration by an extension that certain edits would
|
|
invalidate the extension's data. Implementations not
|
|
understanding the extension refuse matching edits unless the
|
|
user performs an explicit unsafe edit.
|
|
\item[Engraving decision] (Chapter~\ref{ch:layout-ir}). A choice
|
|
made by the engraver (stem direction, beam consolidation, etc.)
|
|
recorded explicitly in the layout IR for inspection and
|
|
override.
|
|
\item[Engraving override] (Chapter~\ref{ch:layout-ir}). A user
|
|
assertion that the engraver's default decision should be
|
|
replaced. Authoritative in the score graph; projected into the
|
|
layout IR.
|
|
\item[Event] (Chapter~\ref{ch:graph}). A rhythmic atom of the
|
|
score: pitched, unpitched, rest, indeterminate, trajectory,
|
|
graphic, or cue. Owned by exactly one voice.
|
|
\item[Format profile] (Chapter~\ref{ch:format}). A named subset
|
|
of the file format that conforming implementations promise to
|
|
support. Profiles enumerated: Full, ReadOnly, Lite, Custom.
|
|
\item[Frame budget] (Chapter~\ref{ch:perf}). The time allowed for
|
|
a layout-and-render operation to maintain 60\,Hz (16.7\,ms) or
|
|
120\,Hz (8.3\,ms) interactivity.
|
|
\item[Glyph catalog identity] (Chapter~\ref{ch:layout-ir}). The
|
|
identifier (SMuFL version, font id, optional font version,
|
|
metrics hash) under which layout output was produced.
|
|
Required for any byte-equal-output layout conformance claim.
|
|
\item[Graphic content] (Chapter~\ref{ch:graph}). Free-form vector
|
|
graphics, drawn strokes, text, and images within free-graphic
|
|
or hybrid regions.
|
|
\item[Hard constraint] (Chapter~\ref{ch:solver}). A constraint
|
|
that must be satisfied; violation produces a solver error.
|
|
\item[Hybrid logical clock] (Chapters~\ref{ch:semops},
|
|
\ref{ch:format}). A clock combining physical wall-clock
|
|
time with a logical counter, used as a tie-breaker in
|
|
canonical reduction order. Operational semantics
|
|
(per-replica monotonicity, role in canonical reduction
|
|
ordering, malformed-envelope behavior) are specified in
|
|
Chapter~\ref{ch:semops}; the on-disk serialization is
|
|
specified in Chapter~\ref{ch:format}.
|
|
\item[Identifier (typed)] (Chapter~\ref{ch:graph}). A 128-bit
|
|
stable identifier for a named object: \texttt{EventId},
|
|
\texttt{VoiceId}, \texttt{StaffId}, etc. Generated by
|
|
replica-plus-counter scheme.
|
|
\item[Incremental layout] (Chapter~\ref{ch:layout-ir}). Re-layout
|
|
of only the IR objects whose dependencies have changed, with
|
|
cached values reused elsewhere.
|
|
\item[Instrument] (Chapter~\ref{ch:graph}). An abstract definition
|
|
of a sounding entity: name, transposition, range, default
|
|
sound, default clef, default staff count.
|
|
\item[Layout IR] (Chapter~\ref{ch:layout-ir}). The pipeline of
|
|
intermediate representations between score graph and renderer:
|
|
\texttt{LogicalLayoutIR}, \texttt{ConstrainedLayoutIR},
|
|
\texttt{ResolvedLayoutIR}, \texttt{RenderIR}.
|
|
\item[Manifest] (Chapter~\ref{ch:format}). The mutable index of a
|
|
bundle: a table of operation roots, optional canonical base
|
|
snapshot, acceleration snapshots, blob roots, profile
|
|
declarations, and extension declarations. Itself a
|
|
content-addressed chunk; the active manifest is selected
|
|
via the active superblock slot
|
|
(Chapter~\ref{ch:format}).
|
|
\item[Metric time] (Chapter~\ref{ch:time}). A region time model
|
|
with measures, beats, and exact rational positions. The
|
|
dominant model for Western notated music.
|
|
\item[Notation grammar] (Chapter~\ref{ch:tuning}). The declarative
|
|
definition of a notation system's pitch space, intervals,
|
|
accidentals, and spelling rules. CMN is the default; others
|
|
extend.
|
|
\item[Operation envelope] (Chapter~\ref{ch:semops}). A
|
|
transmitted, stored, and reducible record carrying an
|
|
operation's payload together with its identity, ordering
|
|
stamp, causal context, and optional transaction grouping.
|
|
The unit of replication.
|
|
\item[Operation kind] (Chapter~\ref{ch:semops}). The tagged
|
|
enumeration of primitive operation variants: InsertEvent,
|
|
DeleteEvent, RespellPitch, Transpose, ChangeRegionTimeModel,
|
|
and so on, plus \texttt{Registered} for extension-defined
|
|
primitives. The full catalog is delivered separately.
|
|
\item[Operation log] (Chapter~\ref{ch:format}). The physical
|
|
storage of the canonical operation-envelope set on disk, as
|
|
operation-envelope blocks. The canonical document is the
|
|
deterministic reduction of this envelope set, or, after
|
|
pruning, of the manifest's \texttt{canonical\_base}
|
|
snapshot plus envelopes whose \texttt{OperationId}s are not
|
|
covered by the base's causal frontier.
|
|
\item[Operation set] (Chapter~\ref{ch:semops}). The replicated
|
|
grow-only CRDT of operation envelopes. The canonical
|
|
document is its deterministic reduction.
|
|
\item[Pareto frontier (design target)]
|
|
(Chapter~\ref{ch:solver}). The set of layouts for which no
|
|
quality metric can be improved without worsening another.
|
|
Solvers \SHOULD{} seek layouts near the frontier; conformance
|
|
is reference-suite-based, not proof of Pareto optimality.
|
|
\item[Part] (Chapter~\ref{ch:graph}). A per-instrument or
|
|
per-section view onto the score for printed extraction. Parts
|
|
are projections, not storage.
|
|
\item[Pitch space] (Chapter~\ref{ch:tuning}). The analytical
|
|
universe of a pitch system: what positions exist, how they
|
|
relate, what accidentals are valid. Distinct from tuning system.
|
|
\item[Pitch spelling] (Chapter~\ref{ch:pitch}). The notational
|
|
appearance of a pitch: nominal, accidental, octave. Attached
|
|
externally by the spelling pre-pass.
|
|
\item[Primitive operation] (Chapter~\ref{ch:semops}). The atomic
|
|
unit of editing. Primitive operations compose into compound
|
|
operations and transactions.
|
|
\item[Provenance] (Chapter~\ref{ch:layout-ir}). The record on
|
|
each layout IR object tracing back to its score graph source,
|
|
enabling selection, editing back-references, and incremental
|
|
layout invalidation.
|
|
\item[Proportional time] (Chapter~\ref{ch:time}). A region time
|
|
model in which horizontal position is wall-clock time, with no
|
|
measures or beats.
|
|
\item[Re-anchoring] (Chapter~\ref{ch:semops}). The normative
|
|
response of a cross-cutting structure when its referent is
|
|
deleted: cascade delete, truncate, re-anchor, mark orphaned,
|
|
or refuse.
|
|
\item[Reference pitch] (Chapter~\ref{ch:tuning}). The anchor
|
|
converting tuning system structure to absolute frequencies:
|
|
a position and a frequency in Hertz. A property of the score,
|
|
not the tuning system.
|
|
\item[Region] (Chapter~\ref{ch:graph}). A spatial-temporal
|
|
container in the canvas declaring a time model (metric,
|
|
proportional, aleatoric) and a content model (staff-based,
|
|
free-graphic, hybrid).
|
|
\item[Repair record] (Chapter~\ref{ch:semops}). A deterministic
|
|
compensating change made during reduction to preserve graph
|
|
invariants (re-anchoring, spanner truncation, attachment
|
|
tombstoning, voice promotion, tuplet compensation). Recorded
|
|
as part of canonical state via
|
|
\texttt{OperationEffect::AppliedWithRepair}.
|
|
\item[Replica] (Chapter~\ref{ch:graph},~\ref{ch:format}). An
|
|
instance of a score on a device. Each replica has a unique
|
|
identifier and authors operations with monotonic local
|
|
sequence numbers. The replica identifier
|
|
\texttt{ReplicaId::SYSTEM\_DERIVED} is reserved for
|
|
deterministically-derived system identifiers.
|
|
\item[Retention policy] (Chapter~\ref{ch:format}). The
|
|
declared rule governing how many old manifests (and their
|
|
reachable chunks) are preserved in the bundle for rollback
|
|
purposes. Rollback is achieved through retained manifests in
|
|
the chunk store, not by retaining multiple on-disk
|
|
superblock generations.
|
|
\item[Scale position] (Chapter~\ref{ch:pitch}). The analytical
|
|
identity of a pitch within a pitch space. One of the two
|
|
intrinsic layers of a \texttt{Pitch}.
|
|
\item[Score graph] (Chapter~\ref{ch:graph}). The in-memory
|
|
representation of all musical content in a score: canonical
|
|
truth from which layout, serialization, and editing operations
|
|
derive.
|
|
\item[Semantic operation] (Chapter~\ref{ch:semops}). A mutation
|
|
of the score graph: insert event, delete measure, transpose,
|
|
respell pitch, etc.
|
|
\item[SMuFL] (Chapter~\ref{ch:tuning}). Standard Music Font
|
|
Layout. The normative source for glyph references in the core.
|
|
\item[Snapshot] (Chapter~\ref{ch:format}). A materialized score
|
|
state. As an acceleration snapshot (listed in
|
|
\texttt{acceleration\_snapshots}), derivative and discardable.
|
|
As a canonical-base snapshot (named in the manifest's
|
|
\texttt{canonical\_base} field with declared causal frontier
|
|
and reduction algorithm version), the canonical base for
|
|
pruned documents, in which case the canonical state is the
|
|
base snapshot plus envelopes whose \texttt{OperationId}s are
|
|
not covered by the base's causal frontier.
|
|
\item[Soft constraint] (Chapter~\ref{ch:solver}). A constraint
|
|
with a weight; violation is permitted but penalized in
|
|
quality metrics.
|
|
\item[Sounding duration] (Chapter~\ref{ch:time}). The exact
|
|
rational duration an event occupies in time. Intrinsic;
|
|
notational decomposition is attached separately.
|
|
\item[Spring slot] (Chapter~\ref{ch:layout-ir}). A time slot's
|
|
horizontal spacing parameters (min, preferred, max widths;
|
|
stretch, compress factors) consumed by the constraint solver.
|
|
\item[Staff] (Chapter~\ref{ch:graph}). The global, abstract
|
|
staff identity persisting across the entire score. A staff is
|
|
declared once at the score level and referenced by staff
|
|
instances in any region where it appears.
|
|
\item[Staff instance] (Chapter~\ref{ch:graph}). The
|
|
region-local manifestation of a staff: the voices, measures,
|
|
clef changes, key changes, and metric grid that belong to
|
|
that staff for one region. A single \texttt{Staff} may have
|
|
multiple \texttt{StaffInstance}s across the score.
|
|
\item[Tempo map] (Chapter~\ref{ch:time}). The function mapping
|
|
musical positions to wall-clock positions, piecewise over
|
|
musical time with constant, linear, exponential, or curve
|
|
segments.
|
|
\item[Time anchor] (Chapter~\ref{ch:time}). See ``Anchor.''
|
|
\item[Time axis] (Chapter~\ref{ch:layout-ir}). The polymorphic
|
|
layout-IR component projecting time positions to horizontal
|
|
positions. Three implementations: metric, proportional,
|
|
aleatoric.
|
|
\item[Transaction] (Chapter~\ref{ch:semops}). A replicated
|
|
operation group that reduces atomically: either every member
|
|
applies and the transaction reduces to \texttt{Applied}, or
|
|
the entire transaction reduces to \texttt{Conflicted} with a
|
|
recorded conflict.
|
|
\item[Trajectory] (Chapter~\ref{ch:graph}). A continuous
|
|
pitch-motion event: glissando, portamento, pitch bend. A
|
|
sibling to pitched events under the event taxonomy.
|
|
\item[Tuning system] (Chapter~\ref{ch:tuning}). The mechanism
|
|
mapping pitch-space positions to frequencies, given a
|
|
reference pitch. Distinct from pitch space.
|
|
\item[Tuplet] (Chapter~\ref{ch:time}). A grouping object
|
|
annotating irregular rhythmic groupings. Does not modify
|
|
sounding durations; purely notational and analytical.
|
|
\item[Typed object identifier]
|
|
(Chapter~\ref{ch:graph}). A tagged union
|
|
(\texttt{TypedObjectId}) over every identifier kind in the
|
|
score graph. Used wherever an object is referenced
|
|
generically (cross-cutting endpoints, conflict
|
|
\texttt{affected\_objects}, repair records, edit barriers).
|
|
\item[Validation mode] (Chapter~\ref{ch:semops}). The strictness
|
|
setting for operation precondition checking: strict authoring
|
|
or lenient replay.
|
|
\item[Vertical band] (Chapter~\ref{ch:layout-ir}). A horizontal
|
|
slice of the canvas with spring parameters for vertical
|
|
spacing: staff bands, inter-staff gaps, inter-system gaps,
|
|
margin bands.
|
|
\item[View] (Chapter~\ref{ch:graph}). A recipe for displaying a
|
|
configuration of the score: which layers are active, which
|
|
parts are rendered, what overrides apply.
|
|
\item[Voice] (Chapter~\ref{ch:graph}). A polyphonic line within
|
|
a staff. Owns a sequence of events.
|
|
\item[Wall-clock time] (Chapter~\ref{ch:time}). Time measured by
|
|
the playback engine or external sync source, in fixed-point
|
|
nanoseconds. Distinct from musical time.
|
|
\end{description}
|
|
|
|
\chapter{Bibliography and References}
|
|
\label{app:refs}
|
|
|
|
\section*{Engraving Treatises}
|
|
|
|
\begin{description}
|
|
\item[Behind Bars.] Elaine Gould. \emph{Behind Bars: The Definitive
|
|
Guide to Music Notation.} Faber Music, 2011. The principal
|
|
modern reference for music engraving conventions.
|
|
\item[Music Notation in the Twentieth Century.] Kurt Stone.
|
|
\emph{Music Notation in the Twentieth Century: A Practical
|
|
Guidebook.} W. W. Norton, 1980. Authoritative reference for
|
|
contemporary notation.
|
|
\item[Essentials of Music Notation.] Tom Ross. \emph{The Art of
|
|
Music Engraving and Processing.} Hansen Books, 1970. Classical
|
|
treatise on engraving conventions, still influential.
|
|
\item[Music Notation.] Gardner Read. \emph{Music Notation: A
|
|
Manual of Modern Practice.} Crescendo Publishers, 1969.
|
|
Reference for engraving practices and history.
|
|
\end{description}
|
|
|
|
\section*{Music Theory and Analysis}
|
|
|
|
\begin{description}
|
|
\item[The Cognition of Basic Musical Structures.]
|
|
David Temperley. MIT Press, 2001. Source of preference-rule
|
|
approaches to spelling and meter analysis.
|
|
\item[Pitch Spelling and the Line of Fifths.]
|
|
H. Christopher Longuet-Higgins. Foundational work on
|
|
pitch-spelling algorithms.
|
|
\item[Tonal Pitch Space.] Fred Lerdahl. Oxford University Press,
|
|
2001. Pitch-space theory as analytical framework.
|
|
\item[A Generative Theory of Tonal Music.]
|
|
Fred Lerdahl and Ray Jackendoff. MIT Press, 1983. Foundational
|
|
work in formal music theory and the source of preference-rule
|
|
methodology.
|
|
\end{description}
|
|
|
|
\section*{Tuning, Microtonality, and Just Intonation}
|
|
|
|
\begin{description}
|
|
\item[Genesis of a Music.] Harry Partch. University of Wisconsin
|
|
Press, 2nd ed. 1974. Foundational text in extended just
|
|
intonation and microtonal practice.
|
|
\item[Tuning, Timbre, Spectrum, Scale.] William Sethares.
|
|
Springer, 2nd ed. 2004. Modern treatment of tuning systems
|
|
and their psychoacoustic foundations.
|
|
\item[Helmholtz-Ellis JI Pitch Notation.] Marc Sabat and
|
|
Wolfgang von Schweinitz. The HEJI accidental system referenced
|
|
by the core's built-in JI pitch spaces.
|
|
\item[Sagittal: A Microtonal Notation System.] George D. Secor
|
|
and David C. Keenan. The Sagittal microtonal accidental system.
|
|
\end{description}
|
|
|
|
\section*{Constraint-Based Layout}
|
|
|
|
\begin{description}
|
|
\item[Cassowary Linear-Arithmetic Constraint Solver.]
|
|
Greg J. Badros, Alan Borning, and Peter J. Stuckey.
|
|
\emph{The Cassowary Linear Arithmetic Constraint Solving
|
|
Algorithm.} ACM Transactions on Computer-Human Interaction,
|
|
8(4):267-306, 2001. Source of the constraint-solver model
|
|
referenced in Chapter~\ref{ch:solver}.
|
|
\item[Music Engraving with Constraint Programming.]
|
|
Multiple authors. Survey of constraint-based approaches to
|
|
music engraving.
|
|
\item[The Gourlay Algorithm.] J. S. Gourlay. \emph{A Language
|
|
for Music Printing.} Communications of the ACM, 1986. Source
|
|
of the proportional spacing model referenced in
|
|
Chapter~\ref{ch:layout-ir}.
|
|
\end{description}
|
|
|
|
\section*{CRDT and Distributed Systems}
|
|
|
|
\begin{description}
|
|
\item[A Comprehensive Study of Convergent and Commutative
|
|
Replicated Data Types.] Marc Shapiro, Nuno Pregui\c{c}a,
|
|
Carlos Baquero, and Marek Zawirski. INRIA Technical Report
|
|
RR-7506, 2011. Foundational survey of CRDTs.
|
|
\item[Dotted Version Vectors.] Nuno Pregui\c{c}a et al.
|
|
Refinement of vector clocks used in the file format
|
|
(Chapter~\ref{ch:format}).
|
|
\item[Hybrid Logical Clocks.] Sandeep S. Kulkarni et al.
|
|
Clocks combining physical and logical time, used for total
|
|
ordering of operation envelopes
|
|
(Chapter~\ref{ch:semops}).
|
|
\end{description}
|
|
|
|
\section*{Prior-Art Notation Formats}
|
|
|
|
\begin{description}
|
|
\item[MusicXML.] Recordare LLC, later W3C Music Notation Community
|
|
Group. The dominant interchange format; surveyed for the
|
|
interchange profile work referenced in
|
|
Chapter~\ref{ch:graph}.
|
|
\item[MEI (Music Encoding Initiative).] An XML-based encoding
|
|
designed for scholarly editions and musicology. Influenced the
|
|
spec's handling of editorial apparatus and analytical layers.
|
|
\item[MNX.] W3C Music Notation Community Group draft. The
|
|
successor effort to MusicXML; informed several decisions on
|
|
layered data models.
|
|
\item[LilyPond.] David Bauer, Han-Wen Nienhuys, Jan Nieuwenhuizen.
|
|
Open-source music engraving software using a text-based input
|
|
format and a sophisticated rule-based engraver. Significant
|
|
influence on the spec's engraving philosophy.
|
|
\item[SMuFL.] W3C Music Notation Community Group. The Standard
|
|
Music Font Layout, referenced as the normative glyph catalog
|
|
in Chapter~\ref{ch:tuning}.
|
|
\end{description}
|
|
|
|
\section*{Additional References}
|
|
|
|
\begin{description}
|
|
\item[The Anatomy of a Note.] Multiple papers on the data
|
|
representation of musical notation in computer systems.
|
|
\item[Notation in Computer Music.] Various proceedings of the
|
|
International Computer Music Conference (ICMC) and the New
|
|
Interfaces for Musical Expression (NIME) workshops.
|
|
\end{description}
|
|
|
|
\chapter{Determinism Contract}
|
|
\label{app:determinism}
|
|
|
|
This appendix consolidates the reproducibility obligations that
|
|
appear throughout the specification. It defines exactly which
|
|
values are admissible in canonical state, how they are serialized,
|
|
how comparisons work, and where implementation freedom begins.
|
|
|
|
The appendix is the document's legal code for reproducibility:
|
|
where the body of the specification says ``deterministic,'' this
|
|
appendix says what that means.
|
|
|
|
\section{Thesis}
|
|
\label{sec:det:thesis}
|
|
|
|
\begin{requirement}
|
|
\label{req:determinism:platform-independent-state}
|
|
Canonical document state \MUST{} be independent of platform,
|
|
CPU, locale, thread scheduling, hash-map iteration order,
|
|
floating-point environment, compression settings, and
|
|
wall-clock timing.
|
|
|
|
Where exact equality is required, this specification defines
|
|
canonical representations. Where numerical approximation is
|
|
unavoidable, this specification defines deterministic
|
|
tolerances, quantization, and reporting rules.
|
|
|
|
Implementations \MAY{} freely choose internal data structures,
|
|
algorithms, and parallelism strategies. The canonical externally
|
|
observable result \MUSTNOT{} depend on those choices.
|
|
\end{requirement}
|
|
|
|
\section{Layers of Determinism}
|
|
\label{sec:det:layers}
|
|
|
|
The specification distinguishes five layers. Each layer has its
|
|
own determinism contract; conflating them is the most common
|
|
source of impossible requirements.
|
|
|
|
\begin{description}
|
|
\item[Canonical score determinism] (Chapter~\ref{ch:semops}).
|
|
The reduction of an operation-envelope set to a materialized
|
|
score graph is fully deterministic \emph{when every algorithm
|
|
contributing to that reduction has received one of the
|
|
dispositions in Section~\ref{sec:det:open}} (normatively
|
|
specified, profile-declared by versioned identifier, or
|
|
explicitly non-canonical). Under that condition, identical
|
|
operation sets produce identical materialized score states
|
|
across all conforming implementations, with identical object
|
|
identifiers, tombstones, conflicts, and effects. For
|
|
algorithms still in open-question state
|
|
(Section~\ref{sec:det:open}), this byte-equal cross-
|
|
implementation guarantee \MUSTNOT{} be claimed; the output
|
|
of such algorithms is either non-canonical or governed by the
|
|
profile's declared algorithm version.
|
|
\item[Canonical serialization determinism]
|
|
(Chapter~\ref{ch:format}). The bundle format's chunk hashes,
|
|
manifest encoding, text projection, and the ordering of
|
|
canonical maps and sets are deterministic. Two implementations
|
|
producing the same canonical state \MUST{} produce identical
|
|
chunk content hashes and identical text-projection bytes.
|
|
\item[Layout determinism] (Chapter~\ref{ch:solver}). Within a
|
|
single solver implementation at a fixed version, with fixed
|
|
profile, fixed budget, and fixed font/glyph metrics, layout
|
|
output is byte-identical across runs. Across different solver
|
|
implementations, layout output \MAY{} differ aesthetically
|
|
within reference-suite thresholds.
|
|
\item[Conformance determinism]. Reference-suite-based:
|
|
conforming implementations need not produce byte-identical
|
|
output to each other; they need to fall within declared
|
|
metric thresholds on every reference-suite entry. See
|
|
Chapter~\ref{ch:solver} Section~\ref{sec:solver:conformance}.
|
|
\item[Non-canonical cache determinism]. Implementations
|
|
maintain caches (operation index, layout cache, glyph metric
|
|
cache, render artifact cache). These \MAY{} vary across
|
|
runs, platforms, and implementations. They \MUSTNOT{} affect
|
|
canonical state and \MUST{} be discardable
|
|
(Chapter~\ref{ch:semops} Section~\ref{sec:semops:caches}).
|
|
\end{description}
|
|
|
|
\begin{requirement}
|
|
\label{req:determinism:determinism-layer-scope}
|
|
Byte-equality obligations \MUST{} be applied only to the layer
|
|
for which they are stated. Implementations \MUSTNOT{} interpret
|
|
``deterministic'' more strongly than its layer specifies, and
|
|
\MUSTNOT{} interpret it more weakly either.
|
|
\end{requirement}
|
|
|
|
\begin{rationale}
|
|
The conditional in canonical score determinism is essential.
|
|
Several algorithms affecting canonical state remain open
|
|
questions in this draft: the spelling pre-pass
|
|
(Chapter~\ref{ch:pitch}), the notational decomposition
|
|
algorithm (Chapter~\ref{ch:time}), and the
|
|
\texttt{wallclock\_to\_musical} root-finding algorithm
|
|
(Chapter~\ref{ch:time}). Until those algorithms receive
|
|
Section~\ref{sec:det:open} dispositions, cross-implementation
|
|
byte equality cannot be promised for outputs derived from them.
|
|
|
|
This is not a weakening of the determinism contract; it is a
|
|
precise statement of what the contract currently covers. Once
|
|
the open algorithms are specified or profile-declared, the
|
|
conditional resolves and full cross-implementation byte
|
|
equality applies to all of canonical state.
|
|
\end{rationale}
|
|
|
|
\section{Floating-Point Values in Canonical State}
|
|
\label{sec:det:fp}
|
|
|
|
\subsection{Permitted Forms}
|
|
|
|
\begin{requirement}
|
|
\label{req:determinism:canonical-floating-point}
|
|
Canonical stored floating-point values \MUST{} be finite
|
|
IEEE 754 binary64 values. NaN and infinity \MUSTNOT{} appear
|
|
in canonical chunks: implementations \MUST{} reject them at
|
|
serialization time and \MUST{} treat their presence on read as
|
|
data corruption.
|
|
|
|
The value \texttt{-0.0} (negative zero) \MUST{} be canonicalized
|
|
to \texttt{+0.0} before storage. Two floating-point values that
|
|
differ only in zero sign are equal under canonical comparison.
|
|
|
|
Floating-point values appear in advisory, acoustic, tuning,
|
|
tempo, layout, and quality-metric contexts. Where exact
|
|
identity is needed (object identifiers, operation ordering,
|
|
graph membership, hash identity), floating point \MUSTNOT{}
|
|
be used.
|
|
\end{requirement}
|
|
|
|
\subsection{Serialization}
|
|
|
|
\begin{requirement}
|
|
\label{req:determinism:floating-point-serialization}
|
|
Canonical \texttt{f64} values are serialized as little-endian
|
|
IEEE 754 binary64 octets after applying the
|
|
\texttt{-0.0 $\to$ +0.0} canonicalization. The eight serialized
|
|
bytes are the canonical representation; two values whose
|
|
canonical bytes are equal are canonically equal.
|
|
|
|
Hash preimages incorporating floating-point values
|
|
(Section~\ref{sec:format:hashing}) \MUST{} use the canonical
|
|
serialized bytes, not platform-native byte orders or
|
|
representations.
|
|
\end{requirement}
|
|
|
|
\subsection{Equality}
|
|
|
|
\begin{requirement}
|
|
\label{req:determinism:floating-point-equality}
|
|
Canonical equality of two floating-point values is byte equality
|
|
of their canonical serialized representations. The IEEE 754
|
|
notion that \texttt{NaN $\ne$ NaN} is irrelevant in canonical
|
|
state, because canonical state cannot contain NaN.
|
|
|
|
For \texttt{NormalizedMetric} values
|
|
(Chapter~\ref{ch:solver}), additional invariants apply: the
|
|
value is finite, in $[0.0, 1.0]$, and lower is better.
|
|
Comparisons of \texttt{NormalizedMetric} use IEEE 754 ordered
|
|
comparison; the \texttt{-0.0 $\to$ +0.0} canonicalization
|
|
applies before comparison.
|
|
\end{requirement}
|
|
|
|
\section{Rounding and CPU Behavior}
|
|
\label{sec:det:rounding}
|
|
|
|
\begin{requirement}
|
|
\label{req:determinism:floating-point-execution}
|
|
Numerical operations whose results enter canonical state
|
|
\MUST{} use IEEE 754 round-to-nearest, ties-to-even. The
|
|
ambient floating-point rounding mode (which on most platforms
|
|
defaults to round-to-nearest-ties-to-even but can be changed)
|
|
\MUSTNOT{} affect canonical results: implementations \MUST{}
|
|
either save/restore the rounding mode or use operations
|
|
insensitive to it.
|
|
|
|
Implementations \MUSTNOT{} compile canonical numerical
|
|
algorithms with \texttt{-ffast-math}, \texttt{-funsafe-math-
|
|
optimizations}, or equivalent flags that permit
|
|
non-IEEE-conformant transformations. Implementations \MUSTNOT{}
|
|
reorder, reassociate, or distribute floating-point expressions
|
|
in ways that can change the canonical result.
|
|
|
|
Implementations \MUSTNOT{} silently fuse multiplications and
|
|
additions into fused-multiply-add operations unless the
|
|
algorithm explicitly requests FMA. Implementations \MUSTNOT{}
|
|
depend on, or be sensitive to, ambient floating-point exception
|
|
flags or host rounding mode.
|
|
|
|
Where implementations use SIMD, GPU acceleration, or platform
|
|
math libraries (\texttt{libm}, vendor math libraries) in
|
|
canonical algorithms, they \MUST{} produce results identical
|
|
to a strict-IEEE single-threaded baseline. Differences in
|
|
transcendental function implementations (\texttt{sin},
|
|
\texttt{cos}, \texttt{exp}, \texttt{log}) across libm versions
|
|
are a real source of nondeterminism; implementations using
|
|
transcendentals in canonical algorithms \MUST{} use a
|
|
documented portable implementation (e.g., a vendored math
|
|
library or a software fallback path).
|
|
\end{requirement}
|
|
|
|
\section{Exact and Quantized Representations}
|
|
\label{sec:det:exact}
|
|
|
|
The specification prefers exact representations over
|
|
floating-point wherever possible. The following are exact by
|
|
design:
|
|
|
|
\begin{description}
|
|
\item[Time and duration.] \texttt{MusicalPosition} and
|
|
\texttt{MusicalDuration} are rational
|
|
(Chapter~\ref{ch:time}). Inline-or-promoted representation;
|
|
arithmetic is exact.
|
|
\item[Operation ordering.] Causal order from DVV; total order
|
|
by HLC and identifier (Chapter~\ref{ch:semops}). All integer-
|
|
valued.
|
|
\item[Identifiers.] All graph identifiers are 128-bit values
|
|
composed of replica plus counter (Chapter~\ref{ch:graph}).
|
|
\item[Hashes.] All canonical hashes are BLAKE3-256
|
|
(Chapter~\ref{ch:format}).
|
|
\end{description}
|
|
|
|
\subsection{Quantized Layout Coordinates}
|
|
|
|
Spatial layout coordinates require numeric computation, but
|
|
canonical output must be stable. The specification quantizes
|
|
canonical coordinates to a fixed grid.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
/// A canonical spatial coordinate, quantized to 1/1024 of a
|
|
/// staff space. Used for canonical serialized output of
|
|
/// ResolvedLayoutIR positions. Internal solvers MAY use floating
|
|
/// point during computation; canonical serialization rounds to
|
|
/// QuantizedCoord.
|
|
pub struct QuantizedCoord {
|
|
/// Coordinate value in units of 1/1024 staff space.
|
|
pub units: i64,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\begin{requirement}
|
|
\label{req:determinism:layout-coordinate-quantization}
|
|
The canonical spatial coordinate grid is $1/1024$ staff space
|
|
per unit. Layout coordinates emitted into canonical
|
|
\texttt{ResolvedLayoutIR} \MUST{} be quantized to this grid
|
|
before serialization. The quantization rule is round-to-nearest,
|
|
ties-to-even on the integer unit value.
|
|
|
|
Implementations \MAY{} use floating-point coordinates internally
|
|
during layout computation. The act of quantization at
|
|
serialization time absorbs all floating-point variation from
|
|
the canonical output. Two implementations whose internal
|
|
computations agree to better than $1/2048$ staff space at
|
|
every coordinate produce identical canonical output after
|
|
quantization.
|
|
|
|
The grid spacing is fixed at $1/1024$ staff space for this
|
|
format version. Future major versions \MAY{} adjust the
|
|
spacing; doing so is a non-backward-compatible change.
|
|
\end{requirement}
|
|
|
|
\begin{rationale}
|
|
$1/1024$ staff space is finer than visual resolution at any
|
|
practical printing density (a staff space is typically 1.5--2\,mm,
|
|
so $1/1024$ is roughly 1.5--2\,$\mu$m, well below print
|
|
resolution). The grid is fine enough that quantization is
|
|
inaudible musically and invisible visually, but coarse enough
|
|
that floating-point noise from different solver implementations
|
|
routinely rounds away.
|
|
\end{rationale}
|
|
|
|
\section{Tolerance Classes}
|
|
\label{sec:det:tolerances}
|
|
|
|
The specification refers to numerical tolerances in several
|
|
places (acoustic comparison, layout quality thresholds, tempo
|
|
integration, solver residual). All such tolerances belong to one
|
|
of a small set of named classes, each with declared semantics.
|
|
|
|
\begin{lstlisting}[language=Rust]
|
|
pub enum ToleranceClass {
|
|
/// Acoustic pitch comparison, in cents.
|
|
AcousticCents,
|
|
|
|
/// Layout coordinate comparison, in staff spaces.
|
|
LayoutCoordinate,
|
|
|
|
/// Quality metric comparison; absolute on the [0.0, 1.0]
|
|
/// NormalizedMetric scale.
|
|
QualityMetric,
|
|
|
|
/// Tempo integration residual: maximum permitted error in
|
|
/// musical_to_wallclock or wallclock_to_musical conversion.
|
|
TempoIntegration,
|
|
|
|
/// Solver residual: maximum permitted constraint violation
|
|
/// for a soft constraint to be considered satisfied.
|
|
SolverResidual,
|
|
}
|
|
|
|
pub struct Tolerance {
|
|
pub class: ToleranceClass,
|
|
|
|
/// Absolute tolerance. The unit is implied by the class.
|
|
pub absolute: f64,
|
|
|
|
/// Optional relative tolerance, applied to nonzero values.
|
|
pub relative: Option<f64>,
|
|
|
|
/// What the tolerance governs.
|
|
pub governance: ToleranceGovernance,
|
|
}
|
|
|
|
pub enum ToleranceGovernance {
|
|
/// Tolerance affects canonical equality (rare; usually only
|
|
/// for diagnostic comparison).
|
|
Equality,
|
|
|
|
/// Tolerance is a validation threshold (constraint considered
|
|
/// satisfied if violation is below tolerance).
|
|
Validation,
|
|
|
|
/// Tolerance affects only diagnostic output; canonical state
|
|
/// is unaffected.
|
|
Diagnostic,
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\begin{requirement}
|
|
\label{req:determinism:named-tolerances}
|
|
Specifications and conformance documents \MUSTNOT{} introduce
|
|
ad-hoc epsilon constants. Every numerical tolerance that
|
|
affects normative behavior \MUST{} be declared as a named
|
|
\texttt{Tolerance} value belonging to a defined
|
|
\texttt{ToleranceClass}, with an explicit unit, absolute and
|
|
optional relative bounds, and governance category.
|
|
|
|
Tolerances \MUSTNOT{} apply to identity: \texttt{PitchId},
|
|
\texttt{EventId}, \texttt{OperationId}, graph membership,
|
|
hash identity, and operation ordering are exact, never within
|
|
tolerance.
|
|
|
|
The Quality Metric Catalog, the Reference Suite, and the
|
|
Performance Reference Suite enumerate the specific tolerance
|
|
values for each profile and tier. Their values are normative
|
|
per the companion specifications.
|
|
\end{requirement}
|
|
|
|
\section{Ordered Iteration over Sets and Maps}
|
|
\label{sec:det:ordering}
|
|
|
|
Canonical output frequently depends on iterating a collection.
|
|
Hash-map and B-tree implementation order leaks platform-specific
|
|
behavior into canonical output if not controlled.
|
|
|
|
\begin{requirement}
|
|
\label{req:determinism:canonical-collection-order}
|
|
Whenever canonical output, canonical serialization, or canonical
|
|
hashing depends on iterating a set, map, or other collection,
|
|
iteration \MUST{} occur in a specified total order. The
|
|
following total orders are normative:
|
|
|
|
\begin{itemize}
|
|
\item Typed identifiers (\texttt{EventId},
|
|
\texttt{PitchId}, \texttt{VoiceId}, etc.): lexicographic
|
|
ascending on the identifier's canonical byte form
|
|
(16 bytes: 8-byte replica, 8-byte counter, big-endian).
|
|
\item Operation envelopes: causal order first (from DVV
|
|
closure), then HLC physical time, then HLC logical
|
|
counter, then \texttt{OperationId} as final tie-break.
|
|
\item Chunk references: ascending by \texttt{ChunkKind}
|
|
discriminant, then by content hash lexicographic, then by
|
|
file offset.
|
|
\item Conflict records: ascending by \texttt{ConflictId}.
|
|
\item Extension declarations: ascending by
|
|
\texttt{ExtensionId}, then by semantic version
|
|
lexicographic.
|
|
\item Strings (text fields): byte-lexicographic on the UTF-8
|
|
NFC representation (Section~\ref{sec:det:text}).
|
|
\item Rationals (\texttt{RationalTime} sequences): by
|
|
numeric value ascending.
|
|
\item Composite keys: lexicographic on the canonical
|
|
representation of the key tuple's component fields, in
|
|
declaration order.
|
|
\end{itemize}
|
|
|
|
Hash-map iteration order, B-tree implementation differences,
|
|
and similar implementation artifacts \MUSTNOT{} leak into
|
|
canonical output.
|
|
\end{requirement}
|
|
|
|
\section{Text and Unicode}
|
|
\label{sec:det:text}
|
|
|
|
Locale-dependent text behavior is a routine source of canonical
|
|
divergence. The specification fixes this at the encoding layer.
|
|
|
|
\begin{requirement}
|
|
\label{req:determinism:unicode-canonicalization}
|
|
Canonical text fields \MUST{} be encoded as UTF-8 with Unicode
|
|
NFC (Normalization Form Canonical Composition) applied. Two
|
|
texts whose NFC byte representations are equal are canonically
|
|
equal regardless of decomposition state.
|
|
|
|
Comparisons of canonical text fields for identity \MUST{} be
|
|
byte comparisons of NFC-encoded UTF-8. User-facing display
|
|
collation (sorting by locale rules, case-insensitive matching,
|
|
diacritic folding) is non-canonical and \MUSTNOT{} affect
|
|
canonical state.
|
|
|
|
Locale settings \MUSTNOT{} affect:
|
|
\begin{itemize}
|
|
\item Parsing of canonical text projections.
|
|
\item Serialization of canonical text fields.
|
|
\item Sorting of canonical map keys.
|
|
\item Decimal formatting in canonical text projections (the
|
|
text projection uses a fixed decimal-point convention;
|
|
locale comma-vs-period preferences are irrelevant).
|
|
\item Date and time formatting in canonical data (canonical
|
|
timestamps use a fixed format; locale formats are for UI
|
|
only).
|
|
\end{itemize}
|
|
|
|
Two implementations operating under different locale settings
|
|
on the same canonical document \MUST{} produce identical
|
|
canonical output, including identical chunk hashes and
|
|
identical text projections.
|
|
\end{requirement}
|
|
|
|
\section{Randomness and Parallelism}
|
|
\label{sec:det:rng}
|
|
|
|
\subsection{Randomness}
|
|
|
|
\begin{requirement}
|
|
\label{req:determinism:canonical-random-seeds}
|
|
Randomness \MUSTNOT{} affect canonical output unless the
|
|
random seed is an explicit canonical input.
|
|
|
|
Algorithms whose results enter canonical state and that use
|
|
pseudorandom generation (e.g., probabilistic constraint solvers,
|
|
randomized rounding) \MUST{} consume their seed as a declared
|
|
algorithm parameter that becomes part of the algorithm's
|
|
conformance configuration. Identical seed plus identical input
|
|
yields identical output.
|
|
|
|
Identifier minting (\texttt{ReplicaId} initialization) is the
|
|
only conforming use of platform-level randomness in this
|
|
specification, and even there the random output enters
|
|
canonical state only via the resulting identifiers.
|
|
\end{requirement}
|
|
|
|
\subsection{Parallelism}
|
|
|
|
\begin{requirement}
|
|
\label{req:determinism:parallel-baseline-equivalence}
|
|
Parallel implementations of canonical algorithms \MUST{}
|
|
produce results identical to a strict single-threaded baseline.
|
|
Race timing, work-stealing order, and thread count \MUSTNOT{}
|
|
affect canonical state.
|
|
|
|
Where parallel reductions are used (summation over many
|
|
contributions, parallel constraint evaluation), the merge step
|
|
\MUST{} be deterministic: ordered reduction over a canonical
|
|
ordering of contributions, not arbitrary tree-merge order.
|
|
|
|
Implementations \MAY{} use parallel execution as a performance
|
|
optimization. They \MAY NOT{} use it as a license for
|
|
nondeterminism.
|
|
\end{requirement}
|
|
|
|
\section{Compression and File Bytes}
|
|
\label{sec:det:compression}
|
|
|
|
\begin{requirement}
|
|
\label{req:determinism:compression-independent-identity}
|
|
Canonical content identity is the uncompressed payload bytes
|
|
with the domain-separated preimage
|
|
(Chapter~\ref{ch:format}). Compression algorithm choice and
|
|
compression level \MUSTNOT{} affect canonical identity.
|
|
Identical uncompressed content has identical
|
|
\texttt{ContentHash} regardless of how it was physically
|
|
compressed.
|
|
|
|
Canonical chunk payload encoding \MUST{} be deterministic: the
|
|
same in-memory canonical structure \MUST{} produce identical
|
|
uncompressed bytes across runs and across implementations,
|
|
within the same schema version. The Binary Format companion
|
|
specification defines the canonical encoding.
|
|
|
|
Compression bytes (the physical bytes stored on disk after
|
|
zstd or another algorithm is applied) are \emph{not} canonical
|
|
unless a profile explicitly requires bit-identical bundle
|
|
reproduction. Implementations \MAY{} recompress chunks (e.g.,
|
|
during compaction or repack) without altering canonical state.
|
|
|
|
Readers \MUST{} verify decompressed payload against the
|
|
declared content hash before treating the payload as
|
|
authoritative.
|
|
\end{requirement}
|
|
|
|
\section{Open Algorithm Hooks}
|
|
\label{sec:det:open}
|
|
|
|
Two algorithms remain open questions in the body of the
|
|
specification: tempo integration for arbitrary curves
|
|
and root-finding for \texttt{wallclock\_to\_musical}
|
|
(Chapter~\ref{ch:time}). These algorithms affect canonical
|
|
state. The spelling pre-pass (Chapter~\ref{ch:pitch}) and the
|
|
notational decomposition algorithm (Chapter~\ref{ch:time})
|
|
were resolved in Pass~12 to the profile-declared disposition
|
|
below, each with a ratified default identifier
|
|
(Requirements~\ref{req:pitch:spelling-algorithm}
|
|
and~\ref{req:time:decomposition-algorithm}).
|
|
|
|
\begin{requirement}
|
|
\label{req:determinism:canonical-algorithm-registration}
|
|
Any algorithm that affects canonical state \MUST{} satisfy one
|
|
of:
|
|
|
|
\begin{itemize}
|
|
\item It is specified normatively in this document or a
|
|
named companion specification. Conforming implementations
|
|
\MUST{} implement the specified algorithm.
|
|
\item It is profile-declared: the active conformance profile
|
|
names a specific algorithm by versioned identifier. Two
|
|
conforming implementations on the same profile use the
|
|
same algorithm. Profiles may differ in their algorithm
|
|
choices.
|
|
\item It is explicitly marked non-canonical or advisory: its
|
|
output appears in the score only in non-canonical layers
|
|
(caches, diagnostics, user-facing rendering hints) and
|
|
\MUSTNOT{} appear in canonical chunks.
|
|
\end{itemize}
|
|
|
|
Implementations \MUSTNOT{} introduce algorithms affecting
|
|
canonical state outside of these three categories. ``Vendor's
|
|
spelling preference'' or ``proprietary decomposition heuristic''
|
|
\MUSTNOT{} silently determine canonical content.
|
|
\end{requirement}
|
|
|
|
\section{Conformance Statement}
|
|
\label{sec:det:conformance}
|
|
|
|
\begin{requirement}
|
|
\label{req:determinism:conformance-disclosure}
|
|
Conforming implementations \MUST{} declare, as part of their
|
|
published conformance statement:
|
|
|
|
\begin{itemize}
|
|
\item Their platform and floating-point library, with the
|
|
libm version or vendored math library reference.
|
|
\item Their parallel execution strategy and the demonstrated
|
|
equivalence to single-threaded baseline.
|
|
\item Their handling of the \texttt{-0.0} canonicalization
|
|
and NaN/infinity rejection.
|
|
\item Their adherence to round-to-nearest-ties-to-even.
|
|
\item Their compliance with the canonical iteration orders
|
|
in Section~\ref{sec:det:ordering}.
|
|
\item Their NFC normalization implementation reference.
|
|
\item Their declared algorithms for any open-question area
|
|
(spelling, decomposition, tempo curves), with versioned
|
|
identifiers.
|
|
\end{itemize}
|
|
\end{requirement}
|
|
|
|
% ===========================================================================
|
|
\chapter{Canonical Byte-Layout Reference}
|
|
\label{app:bytes}
|
|
|
|
\providecommand{\bul}{\_\discretionary{}{}{}}% breakable underscore for long identifiers
|
|
\providecommand{\cwb}{\discretionary{}{}{}}% zero-width break for long camelCase identifiers
|
|
|
|
\section{Status of this reference}
|
|
|
|
This appendix consolidates, in one place, every byte layout pinned by
|
|
the Pass 11 ratification: the discriminant tables, derivation
|
|
preimages, and primitive encodings that the core, operations, and
|
|
bundle layers already implement and lock with golden-bytes tests. Its
|
|
purpose is to give the Binary Format companion
|
|
(Section~\ref{sec:format:binary}) a single point to import, rather than
|
|
re-deriving these layouts from three separate crates.
|
|
|
|
This reference is a \emph{consolidation, not a second source of truth.}
|
|
Every entry cites the requirement that defines it normatively; where
|
|
this appendix and a cited requirement appear to disagree, the cited
|
|
requirement governs. The appendix introduces no obligation beyond what
|
|
its citations already impose. The layouts that Pass 11 deliberately
|
|
left to the interchange companions are listed in
|
|
Section~\ref{sec:bytes:deferred} so the boundary is explicit.
|
|
|
|
\section{Shared encoding conventions}
|
|
\label{sec:bytes:conventions}
|
|
|
|
All composite canonical encodings follow the convention baseline that
|
|
the Binary Format companion inherits
|
|
(Requirement~\ref{req:format:codec-conventions}): little-endian
|
|
integers; a boolean as a single \texttt{0}/\texttt{1} byte; \texttt{u32}
|
|
little-endian counts and a length prefix on every variable-width leaf;
|
|
a tagged union as a single discriminant byte followed by its variant
|
|
payload; and free-text strings as length-prefixed UTF-8 that the codec
|
|
does \emph{not} NFC-fold (catalog identifiers are NFC-normalized at
|
|
construction, not in the codec). Certain hashing preimages deliberately
|
|
depart from the single-discriminant-byte convention where a wider or
|
|
order-bearing tag is required --- notably the \texttt{TypedObjectId}
|
|
16-bit big-endian discriminant; such departures are pinned at their
|
|
definitions and noted in the tables below.
|
|
|
|
\section{System-derived identifier function}
|
|
\label{sec:bytes:system-derived}
|
|
|
|
Several identifiers below are content-addressed rather than
|
|
counter-allocated. They share one function
|
|
(Section~\ref{sec:graph:system-derived}): the 64-bit counter of a
|
|
\texttt{ReplicaId::SYSTEM\_DERIVED}
|
|
(\texttt{0xffff\_ffff\_ffff\_ffff}) identifier is
|
|
$\mathrm{BE\_u64}(\mathrm{BLAKE3}(\texttt{domain\_tag} \Vert
|
|
\texttt{canonical\_inputs})[0..8])$. The domain tag is exactly eight
|
|
bytes. The reserved built-in tags \emph{for this derivation} are the
|
|
closed set \{\texttt{MUSCSVCE}, \texttt{MUSCSPCH}, \texttt{MUSCSANM}\}
|
|
--- the three system-derived-identifier tags only; the other reserved
|
|
domain tags catalogued in Section~\ref{sec:bytes:tags} feed plain
|
|
hashing preimages, not this counter. Tags introduced by registered
|
|
extensions \MUST{} begin with \texttt{MUSCS}, be exactly eight bytes,
|
|
and not collide with the reserved three.
|
|
|
|
\section{Discriminant tables, derivations, and encodings}
|
|
\label{sec:bytes:tables}
|
|
|
|
\subsection{Object-graph identity (canonical document state)}
|
|
|
|
\begin{longtable}{>{\raggedright\arraybackslash}p{3.5cm} >{\raggedright\arraybackslash}p{6.8cm} p{2.2cm}}
|
|
\toprule
|
|
\textbf{Layout} & \textbf{Canonical bytes / preimage} &
|
|
\textbf{Requirement} \\
|
|
\midrule
|
|
\endhead
|
|
\texttt{TypedObjectId} tag &
|
|
16-bit big-endian discriminant (\texttt{0}--\texttt{27}) $\Vert$
|
|
variant payload. \texttt{Registered} $= 27$ encodes
|
|
\texttt{reg}~(16 BE) $\Vert$ \texttt{raw}~(16 BE), 34 bytes total.
|
|
\texttt{ObjectKindRegistryId} is 128-bit &
|
|
\ref{req:graph:typed-object-id-discriminants} \\[2pt]
|
|
|
|
Promoted \texttt{VoiceId} &
|
|
System-derived under \texttt{MUSCSVCE} over a 64-byte preimage:
|
|
\texttt{staff\_instance} $\Vert$ \texttt{original\_voice} $\Vert$
|
|
\texttt{winning\_op} $\Vert$ \texttt{losing\_op}, each 16-byte
|
|
big-endian &
|
|
\S\ref{sec:graph:promoted-voices} \\[2pt]
|
|
|
|
System-derived \texttt{PitchId} &
|
|
System-derived under \texttt{MUSCSPCH} over the intrinsic identity
|
|
(scale position, acoustic realization); variable-width strings
|
|
length-prefixed and NFC-normalized at the boundary; the tuning
|
|
reference is always present (\texttt{Inherit} as a distinct presence
|
|
marker) &
|
|
\ref{req:graph:system-derived-pitch-id} \\[2pt]
|
|
|
|
\texttt{Integrity\cwb AnomalyId} &
|
|
System-derived under \texttt{MUSCSANM} over
|
|
\texttt{Integrity\cwb AnomalyKind::\cwb to\bul canonical\bul bytes()} &
|
|
\ref{req:graph:integrity-anomaly-id} \\[2pt]
|
|
|
|
\texttt{ObjectKind} discriminant &
|
|
Single byte: \texttt{Voice} $= 0$, \texttt{Pitch} $= 1$,
|
|
\texttt{Registered} $= 2$. Enters the \texttt{IntegrityAnomalyId}
|
|
preimage &
|
|
\ref{req:graph:object-kind-vocab} \\
|
|
\bottomrule
|
|
\end{longtable}
|
|
|
|
\subsection{Bundle layer (chunk store, manifest, blobs)}
|
|
|
|
\begin{longtable}{>{\raggedright\arraybackslash}p{3.5cm} >{\raggedright\arraybackslash}p{6.8cm} p{2.2cm}}
|
|
\toprule
|
|
\textbf{Layout} & \textbf{Canonical bytes / preimage} &
|
|
\textbf{Requirement} \\
|
|
\midrule
|
|
\endhead
|
|
Chunk content hash &
|
|
BLAKE3 of \texttt{domain\bul tag} $\Vert$ \texttt{ChunkKind} byte
|
|
$\Vert$ \texttt{SchemaVersion} (4) $\Vert$
|
|
\texttt{uncompressed\bul length} (u64 LE) $\Vert$ \texttt{payload};
|
|
tag \texttt{MUSCCHNK} for chunks, \texttt{MUSCMANI} for the manifest.
|
|
Compression is \emph{not} in the preimage &
|
|
\S\ref{sec:format:hashing} \\[2pt]
|
|
|
|
\texttt{ChunkKind} &
|
|
Single declaration-order byte, \texttt{0}--\texttt{8} (in the chunk
|
|
hash preimage, so stable) &
|
|
\ref{req:format:chunkkind-discriminants} \\[2pt]
|
|
|
|
\texttt{Compression\cwb Algorithm} &
|
|
Fixed two bytes: discriminant $\Vert$ parameter. \texttt{None} $=$
|
|
\texttt{[0,0]}, \texttt{Zstd\{level\}} $=$ \texttt{[1,level]},
|
|
\texttt{Reserved(v)} $=$ \texttt{[2,v]}. Not part of chunk identity &
|
|
\ref{req:format:chunkkind-discriminants} \\[2pt]
|
|
|
|
\texttt{SchemaVersion} &
|
|
\texttt{major}~(u16 LE) $\Vert$ \texttt{minor}~(u16 LE), four bytes &
|
|
\S\ref{sec:format:hashing} \\[2pt]
|
|
|
|
\texttt{BlobId} &
|
|
Bare BLAKE3 of \texttt{MUSCBLOB} $\Vert$
|
|
\texttt{uncompressed\bul payload}: the domain tag immediately
|
|
followed by payload, with no kind, schema, or length fields &
|
|
\ref{req:format:blob-hash-shape} \\[2pt]
|
|
|
|
\texttt{ManifestId} &
|
|
\texttt{trunc128} (leading 16 bytes) of BLAKE3 of \texttt{MUSCMNIF}
|
|
$\Vert$ \texttt{document\bul id} (16) $\Vert$ \texttt{generation}
|
|
(u64 LE) $\Vert$ \texttt{manifest\bul body}; the
|
|
\texttt{manifest\bul id} field is excluded from the body &
|
|
\ref{req:format:manifest-id} \\[2pt]
|
|
|
|
\texttt{ProfileId} &
|
|
Fixed 20 bytes: discriminant (u32 LE; \texttt{Full} $= 0$,
|
|
\texttt{ReadOnly} $= 1$, \texttt{Lite} $= 2$, \texttt{Custom} $= 3$)
|
|
$\Vert$ \texttt{ProfileRegistryId}~(16, zero unless \texttt{Custom}).
|
|
Load-bearing in superblock selection &
|
|
\ref{req:format:profileid-discriminants} \\
|
|
\bottomrule
|
|
\end{longtable}
|
|
|
|
\subsection{Operation layer (transaction metadata)}
|
|
|
|
\begin{longtable}{>{\raggedright\arraybackslash}p{3.5cm} >{\raggedright\arraybackslash}p{6.8cm} p{2.2cm}}
|
|
\toprule
|
|
\textbf{Layout} & \textbf{Canonical bytes} & \textbf{Requirement} \\
|
|
\midrule
|
|
\endhead
|
|
\texttt{Transaction\cwb Category} &
|
|
Single byte: \texttt{NoteEntry} $= 0$, \texttt{Structural} $= 1$,
|
|
\texttt{Layout} $= 2$, \texttt{Import} $= 3$, \texttt{Registered}
|
|
$= 4$ &
|
|
\ref{req:semops:transaction-category} \\[2pt]
|
|
|
|
\texttt{ResolutionAction} &
|
|
Single byte: \texttt{AcceptLoser} $= 0$, \texttt{KeepWinner} $= 1$,
|
|
\texttt{Override} $= 2$, \texttt{Reanchor} $= 3$, \texttt{Dismiss}
|
|
$= 4$, \texttt{Registered} $= 5$. Encoded into the operation content
|
|
hash &
|
|
\ref{req:semops:resolution-action-discriminants} \\
|
|
\bottomrule
|
|
\end{longtable}
|
|
|
|
\subsection{Primitive scalar encodings}
|
|
|
|
\begin{longtable}{>{\raggedright\arraybackslash}p{3.5cm} >{\raggedright\arraybackslash}p{6.8cm} p{2.2cm}}
|
|
\toprule
|
|
\textbf{Layout} & \textbf{Canonical bytes} & \textbf{Requirement} \\
|
|
\midrule
|
|
\endhead
|
|
\texttt{RationalTime} &
|
|
Sign byte (\texttt{0} zero, \texttt{1} positive, \texttt{2} negative)
|
|
$\Vert$ numerator magnitude (u32-LE length-prefixed, big-endian)
|
|
$\Vert$ denominator magnitude (u32-LE length-prefixed, big-endian).
|
|
Always stored reduced (lowest terms, positive denominator), so equal
|
|
rationals encode to equal bytes &
|
|
\ref{req:format:rationaltime-encoding} \\[2pt]
|
|
|
|
Wall-clock integers &
|
|
Fixed-width little-endian (matching \texttt{QuantizedCoord}) &
|
|
\ref{req:format:rationaltime-encoding} \\
|
|
\bottomrule
|
|
\end{longtable}
|
|
|
|
\section{Domain-tag registry}
|
|
\label{sec:bytes:tags}
|
|
|
|
Every reserved built-in eight-byte domain tag, in one place. The nine
|
|
\emph{canonical} tags ---
|
|
whose preimages produce identifiers and content hashes that are part of
|
|
the interoperable, durably persisted form every conforming
|
|
implementation must reproduce bit-identically --- are
|
|
listed first; \texttt{MUSCFNTM} and \texttt{MUSCLOID} are
|
|
\emph{non-canonical} (layout-only) and listed last, for completeness
|
|
(see Section~\ref{sec:bytes:deferred}). Registered extensions may
|
|
additionally mint \emph{system-derived} tags, which \MUST{} begin with
|
|
\texttt{MUSCS}, be exactly eight bytes, and not collide with a reserved
|
|
tag (Section~\ref{sec:bytes:system-derived}).
|
|
|
|
\begin{longtable}{>{\raggedright\arraybackslash}p{2.6cm} >{\raggedright\arraybackslash}p{10.4cm}}
|
|
\toprule
|
|
\textbf{Tag} & \textbf{Use} \\
|
|
\midrule
|
|
\endhead
|
|
\texttt{MUSCCHNK} & Chunk content-hash preimage
|
|
(\S\ref{sec:format:hashing}) \\
|
|
\texttt{MUSCMANI} & Manifest chunk content-hash preimage
|
|
(\S\ref{sec:format:hashing}) \\
|
|
\texttt{MUSCBLOB} & Blob identity, bare preimage
|
|
(Requirement~\ref{req:format:blob-hash-shape}) \\
|
|
\texttt{MUSCMNIF} & Manifest-id derivation
|
|
(Requirement~\ref{req:format:manifest-id}) \\
|
|
\texttt{MUSCCONF} & \texttt{ConflictId} derivation
|
|
(\S\ref{sec:semops:conflict-id}) \\
|
|
\texttt{MUSCENVH} & Operation-envelope hash (\texttt{EnvelopeHash})
|
|
(\S\ref{sec:semops:equivocation}) \\
|
|
\texttt{MUSCSVCE} & System-promoted voice id
|
|
(\S\ref{sec:graph:promoted-voices}) \\
|
|
\texttt{MUSCSPCH} & System-derived pitch id
|
|
(Requirement~\ref{req:graph:system-derived-pitch-id}) \\
|
|
\texttt{MUSCSANM} & Integrity-anomaly id
|
|
(Requirement~\ref{req:graph:integrity-anomaly-id}) \\
|
|
\texttt{MUSCFNTM} & Font-metrics hash for layout conformance ---
|
|
\emph{non-canonical} (\S\ref{sec:layoutir:catalog-identity}) \\
|
|
\texttt{MUSCLOID} & Layout-object id --- \emph{non-canonical},
|
|
wired by the reference implementation as of P12-I2
|
|
(Requirement~\ref{req:layoutir:object-id-derivation}) \\
|
|
\bottomrule
|
|
\end{longtable}
|
|
|
|
\section{Reference-implementation locks}
|
|
\label{sec:bytes:goldens}
|
|
|
|
Each ratified layout above is anchored by a test in the reference
|
|
implementation that fails deliberately if the layout drifts. Most are
|
|
\emph{golden-bytes} tests pinning the literal encoding; the two noted
|
|
exceptions (\texttt{BlobId} and \texttt{RationalTime}) are anchored by
|
|
round-trip and canonicalization tests, which catch a drift in decode or
|
|
normalized form but not every literal-byte change. These anchors make
|
|
the spec-to-code correspondence checkable.
|
|
|
|
\begin{longtable}{>{\raggedright\arraybackslash}p{3.7cm} >{\raggedright\arraybackslash}p{9.3cm}}
|
|
\toprule
|
|
\textbf{Layout} & \textbf{Anchoring test} \\
|
|
\midrule
|
|
\endhead
|
|
\texttt{TypedObjectId} &
|
|
{\footnotesize\texttt{epiphany-core/src/ids.rs}:
|
|
\texttt{typed\bul object\bul id\bul byte\bul form\bul is\bul locked}} \\
|
|
Promoted \texttt{VoiceId} &
|
|
{\footnotesize\texttt{epiphany-core/src/graph.rs}:
|
|
\texttt{promoted\bul voice\bul id\bul byte\bul form\bul is\bul locked}} \\
|
|
System \texttt{PitchId} &
|
|
{\footnotesize\texttt{epiphany-core/src/pitch.rs}:
|
|
\texttt{system\bul pitch\bul id\bul byte\bul form\bul is\bul locked}} \\
|
|
\texttt{Integrity\cwb AnomalyId} &
|
|
{\footnotesize\texttt{epiphany-ops/src/anomaly.rs}:
|
|
\texttt{integrity\bul anomaly\bul id\bul byte\bul form\bul is\bul locked}} \\
|
|
\texttt{ObjectKind} &
|
|
{\footnotesize\texttt{epiphany-ops/src/support.rs}:
|
|
\texttt{object\bul kind\bul discriminants\bul are\bul golden}} \\
|
|
\texttt{ChunkKind} &
|
|
{\footnotesize\texttt{epiphany-bundle/src/chunk.rs}:
|
|
\texttt{chunk\bul kind\bul discriminants\bul are\bul golden}} \\
|
|
\texttt{Compression\cwb Algorithm} &
|
|
{\footnotesize\texttt{epiphany-bundle/src/chunk.rs}:
|
|
\texttt{compression\bul algorithm\bul encoding\bul is\bul golden}} \\
|
|
\texttt{ProfileId} &
|
|
{\footnotesize\texttt{epiphany-bundle/src/superblock.rs}:
|
|
\texttt{profile\bul id\bul discriminants\bul are\bul golden}} \\
|
|
\texttt{ManifestId} &
|
|
{\footnotesize\texttt{epiphany-bundle/src/ids.rs}:
|
|
\texttt{manifest\bul id\bul is\bul content\bul derived\bul and\bul deterministic}} \\
|
|
\texttt{Transaction\cwb Category} &
|
|
{\footnotesize\texttt{epiphany-ops/src/payload.rs}:
|
|
\texttt{transaction\bul category\bul discriminants\bul are\bul golden}} \\
|
|
\texttt{ResolutionAction} &
|
|
{\footnotesize\texttt{epiphany-ops/src/conflict.rs}:
|
|
\texttt{resolution\bul action\bul discriminants\bul are\bul golden}} \\
|
|
\texttt{BlobId} &
|
|
{\footnotesize\texttt{epiphany-determinism/src/hash.rs}:
|
|
\texttt{ContentHash::of\bul blob} and its round-trip locks
|
|
\emph{(round-trip, not a literal-byte golden)}} \\
|
|
\texttt{RationalTime} &
|
|
{\footnotesize\texttt{epiphany-core/src/time.rs}:
|
|
\texttt{equal\bul rationals\bul encode\bul identically}
|
|
\emph{(canonical-equality lock, not a literal-byte golden)}} \\
|
|
\bottomrule
|
|
\end{longtable}
|
|
|
|
The \texttt{MUSCLOID} layout-object-id derivation
|
|
(Requirement~\ref{req:layoutir:object-id-derivation}), wired in P12-I2,
|
|
is anchored differently because it is \emph{non-canonical}: its ids
|
|
never enter document state or a content hash, so there is no durable
|
|
byte layout to golden-lock. It is instead pinned by a derivation
|
|
reference-lock test (\texttt{epiphany-layout-ir/src/provenance.rs}:
|
|
\texttt{stable\bul id\bul uses\bul the\bul ratified\bul muscloid\bul derivation}),
|
|
which fixes the tagged preimage; by the tag-spelling lock
|
|
(\texttt{epiphany-determinism/src/domain.rs}:
|
|
\texttt{exact\bul tag\bul spellings\bul match\bul spec}); and by the
|
|
renderer's provenance goldens, whose \texttt{data-prov} hex changes if
|
|
the derivation drifts.
|
|
|
|
\section{Layouts deferred to the companions}
|
|
\label{sec:bytes:deferred}
|
|
|
|
The following are intentionally \emph{not} ratified here; they remain
|
|
provisional pending the Binary Format companion and the Operation
|
|
Catalog, and are recorded so the companion authors know precisely which
|
|
layouts they own versus inherit:
|
|
|
|
\begin{itemize}
|
|
\item \texttt{OperationKindTag} / \texttt{OperationKind} discriminants
|
|
and the full per-operation payload encodings. The reference
|
|
implementation assigns provisional declaration-order discriminants
|
|
(\texttt{0}--\texttt{16}) locked only for mutual distinctness, not
|
|
to literal values; pinning the literal wire form is the
|
|
Operation Catalog's and Binary Format companion's job.
|
|
\item The full composite struct layouts --- whole-\texttt{Score},
|
|
operation envelopes, the manifest body's field order --- together
|
|
with schema-version wire evolution and varint details beyond the
|
|
convention baseline
|
|
(Requirement~\ref{req:format:codec-conventions}). The companion
|
|
inherits the baseline and formalizes these.
|
|
\end{itemize}
|
|
|
|
\chapter{Revision History}
|
|
\label{app:history}
|
|
|
|
\begin{longtable}{p{2cm} p{2.5cm} p{9cm}}
|
|
\toprule
|
|
\textbf{Date} & \textbf{Section} & \textbf{Change} \\
|
|
\midrule
|
|
\endhead
|
|
\today & All & Initial scaffold; chapter structure and front matter
|
|
established. \\
|
|
\today & Ch.~\ref{ch:pitch} & Pitch chapter written: layered pitch type,
|
|
scale position, acoustic realization, spelling subsystem with attachments,
|
|
precedence, pre-pass, and analytical layers. Chapter structure expanded:
|
|
pitch separated from time/tuning; semantic operations chapter added. \\
|
|
\today & Ch.~\ref{ch:time} & Time and duration chapter written: exact
|
|
rational time with inline-or-promoted representation, distinct position
|
|
and duration newtypes, wall-clock time as fixed-point nanoseconds, time
|
|
anchors, time signatures with explicit beat groups including irrational
|
|
meters, tempo maps with deterministic conversion, notational
|
|
decomposition pre-pass, tuplets as grouping objects, and three region
|
|
time models (metric, proportional, aleatoric). \\
|
|
\today & Ch.~\ref{ch:tuning} & Tuning systems and pitch spaces chapter
|
|
written: pitch spaces as analytical universes distinct from tuning
|
|
systems, position structures (chromatic, diatonic-over-chromatic, JI
|
|
lattice, registered), interval algebras, nominal and accidental
|
|
registries, SMuFL glyph references with versioning, tuning resolution
|
|
variants including adaptive tuning, score-level reference pitch,
|
|
hierarchical resolution rules, and the normative built-in catalog of
|
|
pitch spaces and tuning systems. \\
|
|
\today & Ch.~\ref{ch:graph} & Score graph chapter written: hybrid
|
|
tree-plus-cross-cutting topology, canvas-region-staff-voice-event
|
|
hierarchy with regions carrying time and content models, typed 128-bit
|
|
identifiers with replica-plus-counter generation, flat event arena,
|
|
complete event taxonomy (pitched, unpitched, rest, indeterminate,
|
|
trajectory, graphic, cue), cross-cutting registry partitioned by type,
|
|
graphic content with region-local coordinates and time bindings, parts
|
|
as projections separate from the canvas, analysis layers and views,
|
|
graph invariants, and required indexes. \\
|
|
\today & Ch.~\ref{ch:semops} & Semantic operations chapter written:
|
|
operation framework with dual command/delta layering, primitive and
|
|
compound operation distinction, transactions as atomic user units,
|
|
precondition classification (invariant vs. advisory) with strict
|
|
authoring and lenient replay modes, the normative re-anchoring rule
|
|
table, inverse-based undo with single-replica scope, semantic merge
|
|
semantics with five merge families, and representative operations
|
|
illustrating each major category. The full operation catalog is
|
|
deferred to a separate conformance specification. \\
|
|
\today & Ch.~\ref{ch:layout-ir} & Layout IR chapter written: pipeline
|
|
of four stages (LogicalLayoutIR, ConstrainedLayoutIR,
|
|
ResolvedLayoutIR, RenderIR-at-interface), shared provenance discipline,
|
|
spatial primitives in staff spaces with f32 precision, polymorphic
|
|
time axis trait with metric/proportional/aleatoric implementations,
|
|
composite logical objects flattened to glyph-level constrained
|
|
objects, spring-based horizontal and vertical layout, explicit
|
|
engraving decision recording, hard and soft override mechanism with
|
|
preservation rules, fine-grained dependency tracking with invalidation
|
|
rules, glyph catalog interface, and uniform region containers across
|
|
kinds. \\
|
|
\today & Ch.~\ref{ch:format} & File format chapter written: bundle
|
|
container structure with header, manifest, chunk store, and blob
|
|
store; custom schema-defined binary format with self-describing
|
|
extension envelope; content-addressed immutable chunks with manifest
|
|
as the only mutable index; multi-level chunking (score-level,
|
|
staff-region, locality-grouped cross-cutting, attachment chunks);
|
|
operation log as canonical source of truth with dotted version
|
|
vectors and hybrid logical clocks; snapshots as derivative caches;
|
|
schema versioning with lazy migration and forward compatibility;
|
|
format profiles (Full, ReadOnly, Lite); per-chunk zstd compression;
|
|
atomic write protocol; deterministic s-expression text projection;
|
|
streaming read guarantees. DRM and encryption explicitly excluded. \\
|
|
\today & Ch.~\ref{ch:solver} & Constraint-solver interface chapter
|
|
written: interface-and-contract specification rather than algorithm;
|
|
the solver trait with from-scratch and incremental modes; Cassowary-
|
|
style Required and Preferred constraint strengths; the constraint
|
|
family catalog (spring, collision, alignment, containment, break,
|
|
aesthetic, extension); the normative quality metric set (optical
|
|
spacing, collisions, page fill, casting-off, beam angle, slur
|
|
curvature, vertical balance, symbol density uniformity); Pareto-
|
|
bounded quality target with configurable slack and tie-breaking
|
|
weights for ambiguous cases; within-implementation byte-determinism
|
|
with cross-implementation Pareto equivalence; incremental solving
|
|
contract with propagation bounds; structured error reporting with
|
|
partial solutions on budget exhaustion; reference suite for
|
|
conformance; non-normative recommendation of Cassowary as a
|
|
starting algorithm. \\
|
|
\today & Chs.~\ref{ch:perf},~\ref{ch:extension} &
|
|
Performance Requirements chapter expanded from skeleton: reference
|
|
hardware profile (2026 desktop and tablet baselines), frame budgets
|
|
for interactive edits at p99, cold-open targets, memory budgets
|
|
separating core data from evictable caches, audio thread discipline
|
|
requirements crossing the core/audio boundary, constraint-solver
|
|
performance targets, file format performance targets, measurement
|
|
methodology, regression testing recommendations. Extension Points
|
|
chapter written from skeleton: consolidated catalog of every
|
|
extension point in the spec organized by chapter of origin
|
|
(notation grammar, tuning system, glyph, score graph, layout IR,
|
|
constraint solver, file format extensions), the extension registry
|
|
contract requiring versioning and identifier stability, plugin
|
|
runtime considerations, conformance interactions. \\
|
|
\today & Appendices & Glossary expanded with 50+ defined terms
|
|
cross-referenced to their originating chapters. Bibliography
|
|
populated with engraving treatises, music theory references,
|
|
tuning and microtonality sources, constraint-based layout
|
|
literature, CRDT references, and prior-art notation format
|
|
citations. \\
|
|
\today & All & Platform named \textit{Epiphany}. Typography refreshed
|
|
to TeX Gyre Pagella (body), Heros (sans), and Cursor (mono).
|
|
Color palette refined to deep teal, antique gold, warm parchment
|
|
neutrals, and deep crimson accents. Title page redesigned with
|
|
ornamental rules and an oldstyle classical feel. Status section,
|
|
introduction, and PDF metadata updated to reflect the platform's
|
|
name. \\
|
|
\today & Pass 1 revision (Chs.~\ref{ch:pitch},~\ref{ch:time},~\ref{ch:tuning},~\ref{ch:graph},~\ref{ch:semops}) &
|
|
Type-seam corrections in response to external review.
|
|
Introduced \texttt{IdentifiedPitch} wrapping every embedded pitch
|
|
with a stable \texttt{PitchId}; updated \texttt{PitchedEvent},
|
|
\texttt{TrajectoryEndpoint}, and \texttt{TrajectoryShape::Stepwise}
|
|
accordingly. Introduced \texttt{EventDuration} union covering
|
|
musical, wall-clock, and indeterminate cases, with concrete-only
|
|
\texttt{DurationBounds} avoiding recursive indeterminacy.
|
|
Aleatoric regions now declare an explicit
|
|
\texttt{AleatoricAnchoringDiscipline}. \texttt{PitchSpelling}
|
|
carries a stack of accidentals (\texttt{Vec<AccidentalId>}) with
|
|
normative additive semantics and duplicate-detection invariants,
|
|
replacing the prior \texttt{Option<AccidentalId>}.
|
|
\texttt{PitchSpacePosition} gains a \texttt{JiVector} variant for
|
|
first-class just-intonation lattice positions. The default CMN
|
|
pitch space is now correctly described as
|
|
\texttt{cmn-12} (diatonic-over-chromatic), not pure chromatic.
|
|
Chapter~\ref{ch:pitch}'s reference-pitch text rewritten to defer
|
|
to Chapter~\ref{ch:tuning}, resolving the prior ownership
|
|
contradiction. \texttt{MusicalPosition} and \texttt{MusicalDuration}
|
|
now derive \texttt{Clone}, not \texttt{Copy}, with a permitted
|
|
inline-or-pointer-tagged implementation noted as conforming.
|
|
\texttt{Tie::pitch\_pairing} now uses \texttt{Vec<(PitchId, PitchId)>}
|
|
instead of positional indices. The duplicate \texttt{AnalysisLayer}
|
|
definition in Chapter~\ref{ch:pitch} was removed in favor of the
|
|
authoritative definition in Chapter~\ref{ch:graph}. Graph
|
|
invariants expanded to cover identified pitches, tombstoning,
|
|
time-model agreement, and \texttt{PitchId}-pairing tie targets.
|
|
Representative operations (\texttt{InsertEvent},
|
|
\texttt{DeleteEvent}, \texttt{RespellPitch}, \texttt{Transpose})
|
|
updated for atomic ID minting, pitch tombstoning,
|
|
voice-promotion on concurrent insertion, and \texttt{PitchId}
|
|
preservation through transposition. Status downgraded to
|
|
``structural working draft.'' \\
|
|
\today & Pass 2 revision (Chs.~\ref{ch:time},~\ref{ch:graph},~\ref{ch:semops}) &
|
|
Graph and time invariants strengthened in response to external
|
|
review. \texttt{TimeAnchor} offsets generalized via the
|
|
\texttt{AnchorOffset} union (Musical, WallClock, Zero), with
|
|
normative agreement between offset variant and target region's
|
|
time model. \texttt{TempoSegment} restructured to carry explicit
|
|
\texttt{end: Option<TimeAnchor>}, \texttt{start\_tempo},
|
|
\texttt{end\_tempo: Option<Tempo>}, and \texttt{shape}; tempo
|
|
segments are required to be monotonic in musical time so that
|
|
wall-clock-to-musical conversion is single-valued. Polymeter
|
|
admitted natively: \texttt{StaffBasedContent} now carries
|
|
\texttt{Vec<StaffInstance>} (region-local), plus
|
|
\texttt{default\_metric\_grid: Option<MetricGrid>} and
|
|
\texttt{barline\_alignment\_groups: Vec<BarlineAlignmentGroup>}.
|
|
Measures and metric grids are per-staff-instance, not per-region.
|
|
Staff identity split into the global \texttt{Staff} (declared once
|
|
at score level) and the region-local \texttt{StaffInstance};
|
|
\texttt{Score} top-level structure expanded with
|
|
\texttt{staves: Vec<Staff>} and
|
|
\texttt{staff\_groups: Vec<StaffGroup>}.
|
|
\texttt{DeleteEvent} preconditions strengthened: target events
|
|
belonging to tuplets require explicit
|
|
\texttt{TupletCompensation} (\texttt{NotInTuplet},
|
|
\texttt{ReplaceWithRest}, \texttt{RewriteTuplets},
|
|
\texttt{CascadeDeleteTuplets}). Tie validation reframed from
|
|
universal immediate-adjacency to class-specific profiles:
|
|
\texttt{TieClass} (Standard, Editorial, CrossVoice,
|
|
LaissezVibrer, Registered) admits editorial, cross-voice, and
|
|
laissez-vibrer ties without relaxing standard-tie invariants.
|
|
System-promoted voices formalized: \texttt{VoiceOrigin} enum
|
|
(UserDeclared, Imported, SystemPromoted); promoted voices have
|
|
deterministically-derived \texttt{VoiceId}s, are first-class
|
|
graph objects, are visible to users, and are normalized only by
|
|
explicit user operation. Graph invariants expanded from 14 to 18
|
|
normative invariants reflecting staff-instance ontology,
|
|
per-staff measures, anchor-offset agreement, system-promoted
|
|
voice identity, and barline-alignment-group containment. New
|
|
identifier types: \texttt{StaffInstanceId},
|
|
\texttt{StaffGroupId}, \texttt{BarlineAlignmentGroupId},
|
|
\texttt{TimeSignatureId}. \\
|
|
\today & Pass 3 revision (Ch.~\ref{ch:semops},~\ref{ch:format}) &
|
|
Concurrent semantics rewritten from pairwise operation merge to
|
|
operation-set deterministic reduction. The replicated operation
|
|
set is now framed as a true CRDT (a grow-only set of envelopes);
|
|
the materialized score graph is its deterministic reduction, not
|
|
itself a CRDT. \texttt{OperationId} (replica + counter) separated
|
|
from \texttt{OperationStamp} (HLC + id); canonical reduction order
|
|
is causal-then-HLC-then-lexicographic. Causal contexts expressed as
|
|
dotted version vectors (\texttt{CausalContext}), not exhaustive
|
|
predecessor lists; exhaustive lists are explicitly forbidden as
|
|
canonical representation. \texttt{OperationEnvelope} introduced
|
|
with id, author, stamp, causal context, optional transaction,
|
|
payload. Four-phase operation lifecycle specified: Prepare,
|
|
Commit, Reduce, Report. Operation effects formalized:
|
|
\texttt{Applied}, \texttt{AppliedWithRepair}, \texttt{Conflicted},
|
|
\texttt{TombstonedTarget}, \texttt{NoOp\{reason\}}; every replica
|
|
reducing the same set records the same effects with the same
|
|
reasons. Conflict records made first-class graph objects in a
|
|
score-level \texttt{ConflictRegistry}: stable, addressable,
|
|
user-visible, resolvable by subsequent
|
|
\texttt{ResolveConflict} operations. Tombstones formalized with
|
|
explicit retention rules (pending replication, undo, conflict
|
|
resolution, attachment resolution). Re-anchoring rewritten as a
|
|
total deterministic function: ``nearest'' is now a strict
|
|
lexicographic minimum over (containment proximity, time distance,
|
|
direction preference, object id), with no implementation
|
|
discretion. Re-anchoring rule table updated for the new
|
|
staff-instance ontology and the deterministic ``nearest''
|
|
function. Transactions formalized as atomic replicated groups:
|
|
primitive members \MUSTNOT{} materialize as independently
|
|
visible intermediate states; failures conflict the whole
|
|
transaction. Undo redefined as a forward compensating operation
|
|
(\texttt{UndoTransaction}) with three policies
|
|
(\texttt{StrictInverse}, \texttt{BestEffort}, \texttt{Cascade});
|
|
bit-identical undo demoted to content equivalence. LWW reduction
|
|
restricted to explicitly-marked \texttt{LwwAdvisory} scalar
|
|
fields (metadata, view preferences, layout hints, system/page
|
|
break preferences, optional cross-cutting display hints);
|
|
structural musical fields \MUSTNOT{} default to LWW. Cache
|
|
invalidation rule: caches are acceleration only, never canonical;
|
|
invalidated on operation-set, causal-order, reduction-algorithm,
|
|
or profile change. Per-operation merge families renamed to
|
|
reduction rules and rewritten in the new model;
|
|
\texttt{ChangeRegionTimeModel} now correctly conflicts rather
|
|
than silently coercing incompatible contained events;
|
|
\texttt{SetUserSystemBreak} explicitly marked LwwAdvisory;
|
|
\texttt{RespellPitch} concurrent collisions preserve the losing
|
|
spelling in a structural-field-collision conflict record. New
|
|
identifier types: \texttt{ConflictId}, \texttt{TransactionId}.
|
|
Operation log restructured to store envelopes rather than
|
|
primitive operations with separate stored-sequence metadata. \\
|
|
\today & Pass 4 revision (Ch.~\ref{ch:format}) &
|
|
File-format restructured around Pass 3's operation-set
|
|
canonical-document model. The earlier ``single mutable manifest''
|
|
story replaced with a fixed 64-byte header plus two fixed-size
|
|
256-byte superblock slots; the superblocks are the only mutable
|
|
on-disk objects, and atomic commit is achieved by writing the
|
|
inactive superblock slot with generation $+ 1$. Active superblock
|
|
selected by highest valid generation. Generation gap greater than
|
|
one detected as integrity anomaly with read-only recovery mode.
|
|
Manifest reframed as a table of roots and declarations
|
|
(operation\_roots, retained\_snapshots, blob\_roots,
|
|
profile\_declarations, extension\_declarations, text\_projection
|
|
root, integrity\_root); user-facing metadata explicitly removed
|
|
from the manifest in favor of operation envelopes and reduced
|
|
state. BLAKE3 fixed as the single content-hashing algorithm,
|
|
replacing the prior ``BLAKE3 or SHA-256'' uncertainty. Hash
|
|
preimage domain-separated and explicitly excludes
|
|
\texttt{compression\_algorithm} to preserve deduplication across
|
|
compression choices. Operation-envelope blocks specified as the
|
|
canonical storage unit with a soft 1\,MiB uncompressed target
|
|
size and a default 64\,MiB upper bound per profile. Snapshots
|
|
separated into acceleration snapshots (caches, discardable) and
|
|
canonical-base snapshots (declared in
|
|
\texttt{retained\_snapshots}, required for pruning). Pruning
|
|
protocol specified: canonical state preserved as (base snapshot
|
|
$+$ post-frontier envelopes). Atomic write protocol specified
|
|
step-by-step with explicit durable-flush points; the durable
|
|
flush is platform-abstracted to cover \texttt{fsync},
|
|
\texttt{FlushFileBuffers}, and equivalents. Crash recovery rules
|
|
enumerated by failure point. Garbage collection specified as
|
|
conservative, optional, deferred; never on the commit critical
|
|
path. Edit barriers formalized as a structured first-class
|
|
concept (\texttt{EditBarrier} with scope, affected object kinds,
|
|
prohibited operation kinds, and condition), with an
|
|
unsafe-edit escape hatch that tombstones extension data rather
|
|
than silently corrupting it. Text projection reframed:
|
|
semantic round-trip required (envelopes, declarations, reduced
|
|
state); physical-layout fidelity (chunk offsets, compression
|
|
choices, cache chunks, garbage regions, superblock generation)
|
|
explicitly not required. Schema versioning, format profiles
|
|
(Full, ReadOnly, Lite, Custom), and compression sections updated
|
|
for the new architecture. Streaming-read cold-open procedure
|
|
rewritten to use snapshot $+$ envelope-stream model. \\
|
|
\today & Pass 5 revision (Ch.~\ref{ch:solver}) &
|
|
Solver conformance reframed from universal Pareto optimality to
|
|
reference-suite-based conformance with profile-specific
|
|
thresholds. The earlier ``no better Pareto layout exists''
|
|
obligation, which quantified over an undecidable space of
|
|
possible layouts, has been replaced with checkable, falsifiable
|
|
thresholds on a reference suite. Solver obligations split into
|
|
four classes: validity conformance (hard constraints, provenance,
|
|
unit system, invariants), determinism conformance, diagnostic
|
|
conformance (\texttt{SolveStatus}, \texttt{SolveReport}, warnings,
|
|
unsatisfied-constraint reporting, metric vectors), and quality
|
|
conformance (reference-suite thresholds). Three solver tiers
|
|
introduced (\texttt{Minimal}, \texttt{Standard}, \texttt{Advanced})
|
|
with declared obligations per tier; tiers are orthogonal to
|
|
file-format profiles. Deterministic budgets formalized
|
|
(\texttt{max\_iterations}, \texttt{max\_nodes},
|
|
\texttt{max\_constraint\_evaluations}); wall-clock time demoted
|
|
to advisory only. Wall-clock \MUSTNOT{} change the canonical
|
|
layout. Quality metrics normalized: \texttt{NormalizedMetric} is
|
|
a finite \texttt{f64} in $[0.0, 1.0]$ with $0.0$ best and $1.0$
|
|
worst; orientation and range fixed across all metrics including
|
|
extension metrics. Hard constraints explicitly separated from
|
|
quality metrics: a solver \MUSTNOT{} trade off a hard-constraint
|
|
violation for a better quality score. Within-implementation
|
|
determinism (byte-equal output for identical input + version)
|
|
separated from cross-implementation conformance (reference-suite
|
|
thresholds, not byte equality). \texttt{SolveReport} unified the
|
|
prior \texttt{Result<SolveOutput, SolverError>} return: every
|
|
invocation yields a report with a \texttt{SolveStatus}
|
|
(\texttt{Solved}, \texttt{SolvedWithWarnings},
|
|
\texttt{PartialBudgetExhausted}, \texttt{Unsatisfiable},
|
|
\texttt{InternalError}). \texttt{PartialBudgetExhausted}
|
|
preserves hard-constraint validity; budget exhaustion that
|
|
cannot satisfy hard constraints returns \texttt{Unsatisfiable}.
|
|
Six invalidation scopes specified (\texttt{ObjectLocal},
|
|
\texttt{MeasureLocal}, \texttt{SystemLocal}, \texttt{PageLocal},
|
|
\texttt{RegionLocal}, \texttt{WholeScore}); incremental solving
|
|
contract reframed as observational equivalence to scoped full
|
|
solving (solvers \MAY{} widen scopes conservatively but
|
|
\MAY NOT{} narrow them). Reference algorithm demoted from
|
|
``recommended starting point'' to explanatory and diagnostic
|
|
only; conforming implementations \MAY{} choose any algorithm.
|
|
Pass 4 rollback-language corrected: rollback is achieved by
|
|
retained manifests in the chunk store, not by retaining multiple
|
|
on-disk superblock generations (the fixed layout has only two
|
|
slots). \\
|
|
\today & Pass 6 revision (App.~\ref{app:determinism}) &
|
|
New Determinism Contract appendix consolidates reproducibility
|
|
obligations scattered across Chapters~\ref{ch:pitch}--\ref{ch:solver}
|
|
into a single normative reference. Five layers of determinism
|
|
distinguished: canonical score determinism (byte-equal
|
|
cross-implementation), canonical serialization determinism
|
|
(byte-equal chunk hashes and text projection), layout
|
|
determinism (byte-equal within implementation version),
|
|
conformance determinism (reference-suite-based, not byte-equal
|
|
cross-implementation), and non-canonical cache determinism
|
|
(caches may vary, MUST NOT affect canonical state). Canonical
|
|
floating-point values restricted to finite IEEE 754 binary64;
|
|
NaN and infinity prohibited from canonical chunks;
|
|
\texttt{-0.0} canonicalized to \texttt{+0.0} before storage and
|
|
hashing. Little-endian IEEE 754 binary64 serialization fixed.
|
|
IEEE 754 round-to-nearest, ties-to-even mandated for canonical
|
|
numerical algorithms; \texttt{-ffast-math} and equivalents
|
|
prohibited; reassociation, distribution, and implicit
|
|
fused-multiply-add forbidden where they could change canonical
|
|
output. Quantized layout coordinates introduced
|
|
(\texttt{QuantizedCoord} at $1/1024$ staff space) so canonical
|
|
\texttt{ResolvedLayoutIR} positions are stable across
|
|
implementations whose internal computations agree to within
|
|
half-quantum. Tolerance classes formalized
|
|
(\texttt{AcousticCents}, \texttt{LayoutCoordinate},
|
|
\texttt{QualityMetric}, \texttt{TempoIntegration},
|
|
\texttt{SolverResidual}) with governance category
|
|
(\texttt{Equality}, \texttt{Validation}, \texttt{Diagnostic});
|
|
ad-hoc epsilons prohibited; tolerances MUST NOT apply to
|
|
identity. Canonical iteration order specified for every
|
|
enumerable collection (identifiers lexicographic on canonical
|
|
byte form; operations causal-then-HLC-then-id; chunk references
|
|
by kind, hash, offset; conflicts by id; extensions by id then
|
|
version; strings byte-lexicographic on NFC UTF-8). Text
|
|
normalization fixed at UTF-8 NFC; locale prohibited from
|
|
affecting canonical parsing, serialization, sorting, decimal
|
|
formatting, or projection. Randomness MUST NOT enter canonical
|
|
state unless the seed is an explicit canonical input.
|
|
Parallelism permitted but MUST produce results identical to a
|
|
strict single-threaded baseline through deterministic ordered
|
|
reductions. Compression confirmed as non-canonical (identical
|
|
uncompressed content has identical hash regardless of
|
|
compression choice); recompression permitted without altering
|
|
canonical state. Open algorithm hooks (spelling pre-pass,
|
|
decomposition, tempo integration, root-finding) required to be
|
|
either normatively specified, profile-declared by versioned
|
|
identifier, or explicitly non-canonical/advisory; vendor
|
|
freedom over canonical algorithms prohibited. Conformance
|
|
statement enumerates the required disclosures implementations
|
|
must publish (libm, parallelism strategy, NFC implementation,
|
|
declared algorithms for open-question areas). Status section
|
|
updated to reference Appendix~\ref{app:determinism}. \\
|
|
\today & Pass 7 revision (editorial, all chapters) &
|
|
Editorial consistency pass: no architectural changes, only
|
|
cleanup. Resolved the contradiction between
|
|
Appendix~\ref{app:determinism}'s ``byte-equal canonical state
|
|
cross-implementation'' and the existence of open canonical
|
|
algorithms by adding an explicit conditional on the disposition
|
|
of Section~\ref{sec:det:open}. Stale section-number cross-
|
|
references replaced with symbolic labels (Section~2.4 $\to$
|
|
\texttt{sec:pitch:spelling}; Section~2.4.4 $\to$
|
|
\texttt{sec:pitch:precedence}; Section~5.9.3 $\to$
|
|
\texttt{sec:graph:timebinding}; Section~6.6 $\to$
|
|
\texttt{sec:semops:conflicts}; Section~6.8 $\to$
|
|
\texttt{sec:semops:undo}). Terminology normalized:
|
|
``merge family'' $\to$ ``reduction rule'' where the operation
|
|
framework was meant; ``Pareto-bounded quality model'' $\to$
|
|
reference-suite/normalized-metric language in extension points;
|
|
glossary entries for Operation log, Operation set, Operation
|
|
envelope, Canonical reduction, Pareto frontier (design target),
|
|
Staff, Staff instance, Conflict record, CRDT, Snapshot, and
|
|
Transaction rewritten or added. New Appendix~\ref{app:deferred}
|
|
(Intentionally Deferred Types and Specifications) catalogs
|
|
companion specifications (Binary Format, Text Projection,
|
|
Profile Conformance, Quality Metric Catalog, Reference Suite,
|
|
Performance Reference Suite, Operation Catalog, Reference
|
|
Algorithm), externally provided components (SMuFL, font metric
|
|
provider, audio engine, plugin runtime, collaboration
|
|
transport, user interface), open canonical algorithms, and
|
|
extension registry catalogs; distinguishes deliberately external
|
|
content from accidentally missing content. Status section
|
|
rewritten to reflect structural stabilization: substantive
|
|
review passes complete, architecture not under active redesign,
|
|
remaining blockers are companion specifications and open
|
|
canonical algorithms; the section enumerates what conformance
|
|
can be established now and what awaits companion delivery.
|
|
Revision history entries for Passes 1--7 read as the document's
|
|
architectural-stabilization log, consolidating the design
|
|
decisions taken in response to external review. \\
|
|
\today & Pass 8 revision (post-review correctness pass) &
|
|
Determinism correctness pass addressing review of the
|
|
Pass 7 output. \texttt{ConflictId} reframed as content-derived
|
|
via BLAKE3 truncation with domain tag \texttt{"MUSCCONF"},
|
|
fixing a determinism leak where each replica would have minted
|
|
its own conflict identifiers from local replica-counter
|
|
sequences; conflict records produced during reduction now have
|
|
identifiers that agree across replicas by construction.
|
|
\texttt{RepairRecord} defined (was referenced by
|
|
\texttt{OperationEffect::AppliedWithRepair} without
|
|
definition), with a \texttt{RepairKind} enumeration covering
|
|
\texttt{Reanchored}, \texttt{SpannerTruncated},
|
|
\texttt{Orphaned}, \texttt{CascadeDeleted},
|
|
\texttt{AttachmentTombstoned}, \texttt{VoicePromoted},
|
|
\texttt{TupletCompensated}, and \texttt{Registered}.
|
|
\texttt{TypedObjectId} defined as a tagged union over every
|
|
identifier kind in the score graph, with canonical-bytes
|
|
serialization for hashing/ordering/equality.
|
|
\texttt{OperationKind} defined as a tagged enumeration of
|
|
primitive operation variants, plus a \texttt{Registered}
|
|
variant for extension-defined primitives. System-derived
|
|
identifier namespace formalized:
|
|
\texttt{ReplicaId::SYSTEM\_DERIVED} reserved at
|
|
\texttt{0xffff\_ffff\_ffff\_ffff}, with the 64-bit counter
|
|
derived deterministically via BLAKE3 truncation; domain tags
|
|
for system identifiers (\texttt{"MUSCSVCE"} for
|
|
system-promoted voices, \texttt{"MUSCSPCH"} for system-derived
|
|
pitches) enumerated; user-authored replicas \MUSTNOT{} use
|
|
the reserved value. Envelope-acceptance rules added: a
|
|
well-formed envelope requires non-system replica id, finite
|
|
physical time, well-formed DVV, deserializable payload;
|
|
per-replica monotonicity enforced; clock-skew warnings allowed
|
|
but reordering for plausibility prohibited; authentication
|
|
deferred to collaboration transport. Canonical-base snapshot
|
|
semantics repaired: manifest split into single
|
|
\texttt{canonical\_base: Option<SnapshotRef>} plus
|
|
\texttt{acceleration\_snapshots: Vec<SnapshotRef>}; coverage
|
|
test specified as DVV-frontier membership, explicitly
|
|
forbidding HLC-stamp comparison for canonicality determination;
|
|
pruning replaces the canonical base atomically. Header CRC
|
|
moved to the last 4 bytes of the 64-byte fixed header,
|
|
resolving a circular-definition bug where the CRC field
|
|
appeared inside its own coverage range. Manifest chunks
|
|
\MUST{} be stored uncompressed in this format version,
|
|
resolving the bootstrap chicken-and-egg problem (a reader
|
|
needs to know how to decode the manifest before it has any
|
|
manifest information). \texttt{ChunkId} defined as a newtype
|
|
around \texttt{ContentHash}, clarifying the prior comment that
|
|
the hash and id were redundant. Three-layer identity model for
|
|
documents: \texttt{file\_uuid} (physical bundle, changes on
|
|
Save As), \texttt{document\_id} (logical work, persists across
|
|
Save As by default), optional \texttt{lineage\_id} (shared
|
|
ancestor for genealogy/version-control purposes); Save As
|
|
semantics specified normatively. \texttt{RetentionPolicy}
|
|
defined as a first-class concept with
|
|
\texttt{retain\_previous\_manifests},
|
|
\texttt{retain\_duration}, \texttt{retain\_named\_checkpoints};
|
|
rollback achieved via retained manifests in the chunk store,
|
|
not multiple on-disk superblock generations.
|
|
\texttt{BarrierCondition} defined (\texttt{Always},
|
|
\texttt{ObjectExists}, \texttt{ObjectHasExtensionData},
|
|
\texttt{All}, \texttt{Any}, \texttt{Not}, \texttt{Registered}),
|
|
making the edit-barrier model implementable for unknown
|
|
extensions. \texttt{GlyphCatalogIdentity} defined in
|
|
Chapter~\ref{ch:layout-ir}, required for any byte-equal-output
|
|
layout conformance claim; without it, layout output is
|
|
implementation-specific. \texttt{Box<dyn TimeAxis>} replaced
|
|
with \texttt{TimeAxisModel} enum (Metric, Proportional,
|
|
Aleatoric, Registered), giving the layout IR a canonical
|
|
representation for serialization, hashing, and conformance
|
|
comparison while still permitting internal trait-object
|
|
dispatch. Performance chapter restructured around an explicit
|
|
core/product/Performance-Reference-Suite obligation split:
|
|
core obligations bound the core's own work (reduction, layout,
|
|
file writes, snapshot acquisition), while end-to-end UI/audio
|
|
targets are product-layer obligations specified in companion
|
|
documents. Editorial sweeps: remaining ``merge semantics''
|
|
prose in Chapter~\ref{ch:intro}, the compound transpose
|
|
primitive, the \texttt{VoiceOrigin::SystemPromoted} comment,
|
|
and the extension-point design principles updated to
|
|
``reduction semantics'' or ``reduction rules''; tempo curve,
|
|
decomposition, and spelling pre-pass open questions updated
|
|
to reference Appendix~\ref{app:determinism}
|
|
Section~\ref{sec:det:open} dispositions. Glossary additions:
|
|
Edit barrier, Glyph catalog identity, Operation kind, Repair
|
|
record, Retention policy, Typed object identifier, Document
|
|
identifier (with cross-references to FileUuid and LineageId).
|
|
Reading Guide canonical-vs-non-canonical table updated to
|
|
reflect the canonical\_base / acceleration\_snapshots split. \\
|
|
\today & Pass 9 revision (correctness and consistency pass) &
|
|
Operation-envelope duplicate handling formalized into three
|
|
outcomes: \texttt{IdempotentDuplicate} (same id, byte-identical
|
|
envelope: silently dropped), \texttt{FirstAcceptance} (new id:
|
|
accepted after well-formedness check), and
|
|
\texttt{EnvelopeEquivocation} (same id, different canonical
|
|
bytes: divergent envelope rejected, integrity diagnostic
|
|
surfaced, bundle non-conforming if equivocation appears
|
|
on-disk). Well-formedness invariant
|
|
\texttt{envelope.stamp.id == envelope.id} made normative. HLC
|
|
monotonicity reframed as a property of the set of accepted
|
|
envelopes, not arrival order: out-of-order delivery is fine;
|
|
on detection of inconsistency, both envelopes are retained but
|
|
the offending replica's stream from the violating counter
|
|
onward is quarantined and the document is flagged with a
|
|
replica-equivocation anomaly. The free-form
|
|
\texttt{NoOpReason::PreconditionFailedUnderReduction { reason:
|
|
String }} replaced with a typed \texttt{PreconditionFailureReason}
|
|
enum, closing a determinism leak (free-form strings in
|
|
canonical state could otherwise differ across implementations).
|
|
Transaction descriptor ordering integrated into canonical
|
|
reduction via causal dependency: every primitive member of a
|
|
transaction \MUST{} causally depend on its
|
|
\texttt{DeclareTransaction} envelope; members lacking such a
|
|
dependency reduce to \texttt{NoOp\{TransactionConflict\}} with
|
|
a synthesized conflict record. The fixed-header
|
|
\texttt{file\_uuid} requirement reworded to match the Pass 8
|
|
identity model: fixed for the lifetime of a physical bundle,
|
|
preserved by filesystem byte-copy, freshly minted on Save As.
|
|
Manifest \texttt{integrity\_root} made \texttt{Option<ChunkRef>}
|
|
(it is an accelerator); new ``Canonical and Non-Canonical
|
|
Manifest Roots'' subsection partitions the manifest's fields
|
|
by their role in canonical state, with the rule that failed
|
|
verification of non-canonical chunks is rebuildable rather
|
|
than corruption. Cold-open procedure rewritten to reflect the
|
|
canonical-base / operation-envelope-block model (the prior
|
|
text retained pre-Pass-3 region-chunks language). Streaming-
|
|
read locatability list updated. System-derived counter
|
|
collisions: mandatory collision check during reduction;
|
|
collision becomes hard corruption via new
|
|
\texttt{ConflictKind::SystemIdentifierCollision} record naming
|
|
both colliding canonical-input sets. \texttt{GlyphCatalogIdentity}
|
|
\texttt{metrics\_hash} scope broadened: hash covers glyphs
|
|
referenced by the \texttt{ConstrainedLayoutIR} (solver inputs),
|
|
not just glyphs in the final \texttt{ResolvedLayoutIR}.
|
|
\texttt{CommitState} validation added to superblock selection:
|
|
non-\texttt{Committed} superblock is not valid for ordinary
|
|
selection. Schema-version fallback refined into three cases by
|
|
chunk role: canonical chunks with unsupported major schema
|
|
require read-only preservation mode (or refusal); non-canonical
|
|
chunks may be opaquely preserved; unknown canonical operation
|
|
payloads require extension support or open read-only without
|
|
materialization. New \texttt{OperationKindTag} (discriminator-
|
|
only enum) introduced for edit barriers, conformance reports,
|
|
and diagnostic surfaces; \texttt{EditBarrier} switched to
|
|
\texttt{prohibited\_operation\_kinds: Vec<OperationKindTag>}.
|
|
``Absolute musical positions \MUSTNOT{} appear in stored
|
|
data'' rule narrowed: stored \emph{references to external
|
|
time points} use \texttt{TimeAnchor}; events' \emph{own
|
|
positions} within their owning voice and region may be stored
|
|
as \texttt{EventPosition}. New Appendix~\ref{app:deferred}
|
|
Section~\ref{sec:deferred:support-types} catalogs the semantic
|
|
identity rules for \texttt{FileUuid}, \texttt{DocumentId},
|
|
\texttt{LineageId}, \texttt{ManifestId}, \texttt{SnapshotId},
|
|
\texttt{BlobId}, \texttt{AuthorId},
|
|
\texttt{ReductionAlgorithmVersion}, \texttt{SolverProfile},
|
|
\texttt{ProfileId}, \texttt{ContentHash}, \texttt{ChunkId},
|
|
\texttt{OperationId}, \texttt{TransactionId},
|
|
\texttt{ConflictId}, and the typed identifier family.
|
|
Editorial: \texttt{MUSTNOT} and \texttt{SHOULDNOT} macros
|
|
switched to \texttt{nobreakspace} so \texttt{pdftotext}
|
|
extracts ``MUST NOT'' as two tokens; Manifest, Operation log,
|
|
and Snapshot glossary entries updated to match the
|
|
\texttt{canonical\_base} / \texttt{acceleration\_snapshots}
|
|
manifest field names. \\
|
|
\today & Pass 10 revision (order-independence and integrity-
|
|
anomaly cleanup) &
|
|
\texttt{OperationId} equivocation reworked from a first-arrival
|
|
rule (which made canonical state arrival-order-dependent across
|
|
replicas observing the same envelopes in different orders) to an
|
|
order-independent \texttt{OperationSlot} model. The slot is
|
|
either \texttt{Single(OperationEnvelope)} or
|
|
\texttt{Equivocated{} \{ operation\_id, candidates:
|
|
BTreeSet<EnvelopeHash> \}}; observing a second distinct
|
|
canonical envelope under an \texttt{OperationId} transitions
|
|
the slot to \texttt{Equivocated} regardless of arrival order.
|
|
Equivocated slots produce no canonical reduction effect; both
|
|
candidates are retained for diagnostic recovery. Operations
|
|
causally depending on an equivocated id are held pending
|
|
resolution. Resolution is by explicit policy: transport-level
|
|
credential revocation, local user reconciliation, or a
|
|
profile-declared deterministic selection function. No
|
|
arrival-order default applies. HLC monotonicity-anomaly
|
|
handling sharpened: a new \texttt{AnomalousReplicaSegment}
|
|
type captures the offending replica, the first violating
|
|
counter, the reason, and the excluded envelope ids. Operations
|
|
from the violating counter onward are retained but
|
|
\emph{excluded from canonical reduction}; canonical state is
|
|
derived only from envelopes that satisfy the monotonicity
|
|
invariant. The earlier ``quarantined'' wording is now backed
|
|
by an explicit structural exclusion rule. System-derived ID
|
|
collisions moved out of \texttt{ConflictKind} and into a new
|
|
\texttt{IntegrityAnomaly} type with kinds
|
|
\texttt{SystemIdentifierCollision},
|
|
\texttt{OperationSlotEquivocated}, and
|
|
\texttt{ReplicaStreamQuarantined}. The distinction is that
|
|
conflict records are ordinary canonical-state facts (a
|
|
representable musical conflict between two valid edits) while
|
|
integrity anomalies indicate the structural assumptions
|
|
underlying canonical state have failed and require external
|
|
recovery; an integrity anomaly takes the document out of
|
|
ordinary canonical operation. Stale ``refined in a later
|
|
revision'' parenthetical removed from the \texttt{DeleteEvent}
|
|
reduction rule; the re-anchoring total order is part of the
|
|
current architecture and needs no further refinement. Typo
|
|
fixed in the superblock-selection requirement
|
|
(\texttt{MAY{}-be-valid} replaced with the prose ``invalid for
|
|
ordinary selection but may be inspected in diagnostic recovery
|
|
mode''). Glossary chapter references for ``Dotted version
|
|
vector'' and ``Hybrid logical clock'' updated to ``Chapters
|
|
6, 8'' with explicit ``defined operationally in
|
|
Chapter~\ref{ch:semops}; serialized in
|
|
Chapter~\ref{ch:format}'' wording, reflecting that the
|
|
Pass 3 architectural shift made causal context part of
|
|
operation semantics rather than only file storage. \\
|
|
\today & Pass 11 revision (spec ratification) &
|
|
Converted the v0 implementation's provisional, golden-locked byte
|
|
choices into ratified normative spec text; the architecture is
|
|
unchanged. \emph{Adopt-and-pin (bytes):} pinned the
|
|
\texttt{TypedObjectId} 16-bit big-endian discriminant table
|
|
(\texttt{0}--\texttt{27}, \texttt{Registered}~=~27; added the
|
|
\texttt{Tuplet}/\texttt{RepeatStructure}/\texttt{LyricLine}/%
|
|
\texttt{ChordSymbol}/\texttt{View} variants the implementation
|
|
carried); the promoted-voice (\texttt{MUSCSVCE}), system-pitch
|
|
(\texttt{MUSCSPCH}, tuning always part of identity), and
|
|
integrity-anomaly (\texttt{MUSCSANM}, promoted to a reserved
|
|
built-in tag) derivations; the \texttt{ChunkKind} /
|
|
\texttt{ProfileId} / \texttt{CompressionAlgorithm} discriminants;
|
|
the \texttt{ManifestId} preimage (with \texttt{manifest\_id}
|
|
excluded); and the \texttt{RationalTime}/scalar primitive layouts
|
|
plus the shared codec convention baseline the Binary Format
|
|
companion inherits. \emph{Decide-and-pin:} \texttt{TempoShape::Linear}
|
|
interpolates \emph{speed} (whole notes/second), not bpm; a
|
|
\texttt{StructuralFieldCollision} tags the \emph{winner}
|
|
\texttt{Conflicted}; lifted the $>$2-way voice-promotion
|
|
generalization (partial-overlap, lowest-id-survivor) to normative;
|
|
pinned the \texttt{TransactionCategory} and \texttt{ObjectKind} core
|
|
vocabularies; added \texttt{ResolutionAction::Dismiss} so the
|
|
\texttt{Dismissed} resolution state is reachable by an authored
|
|
operation; and specified the (non-canonical) \texttt{LayoutObjectId}
|
|
derivation with a \texttt{MUSCLOID} tag as the Track~A target (then
|
|
deferred to Track~A; subsequently wired by the reference
|
|
implementation in P12-I2). \emph{Fixes:} blob hashing
|
|
is the bare \texttt{"MUSCBLOB" || payload} (deleted the
|
|
contradictory ``identically to chunks'' phrasing); added the
|
|
equal-generation superblock rule (\texttt{DivergentSameGeneration}
|
|
on load-bearing-field divergence); defined \texttt{ProfileConstraints}
|
|
and placed the required \texttt{RetentionPolicy} in it with
|
|
first-declared multi-profile precedence; made the DVV zero-based
|
|
counter floor normative; reconciled the graph-invariant count to 19
|
|
(was 18 in QUICKSTART) and named the three construction-time MUSTs
|
|
(time-signature beat-group sum, ordering-DAG acyclicity, and
|
|
non-degenerate \texttt{TupletRatio}, now enforced at construction).
|
|
\emph{Consolidation:} added the Canonical Byte-Layout Reference
|
|
(Appendix~\ref{app:bytes}) gathering every ratified discriminant,
|
|
derivation preimage, and primitive encoding into one place for the
|
|
Binary Format companion to import, with a domain-tag registry and
|
|
golden-lock anchors; and closed two follow-through gaps where the
|
|
discriminant \emph{bytes} of \texttt{ObjectKind} (\texttt{Voice}~=~0,
|
|
\texttt{Pitch}~=~1, \texttt{Registered}~=~2; feeds the
|
|
\texttt{IntegrityAnomalyId} preimage) and \texttt{ResolutionAction}
|
|
(\texttt{AcceptLoser}~=~0~$\dots$~\texttt{Registered}~=~5; feeds the
|
|
operation content hash) were golden-locked in code but unpinned in
|
|
spec text.
|
|
\\
|
|
\today & Pass 12 tranche 1 (audit spec-alignment) &
|
|
Aligned spec text with dispositions the project had already made
|
|
and the reference implementation exercises; no byte-layout or
|
|
architecture change. The re-anchoring rule table's Slur and
|
|
Spanner rows now state the Operation Catalog's surviving-endpoint
|
|
semantics (re-anchor while $\geq 1$ endpoint survives,
|
|
cascade-delete only when none does; proximity-aware re-targeting
|
|
deferred). \texttt{TupletCompensation::RewriteTuplets} adopts the
|
|
catalog's id-only v1 payload (\texttt{TupletRewrite} removed):
|
|
graph-aware reduction refuses the variant rather than fabricate
|
|
rewritten values, and a value-carrying payload revision reopens
|
|
it. Operation-envelope blocks are pure envelope vectors (a
|
|
content-addressed chunk cannot embed its own id); the per-block
|
|
summary metadata
|
|
(\texttt{dvv\_summary}/\texttt{min\_stamp}/\texttt{max\_stamp})
|
|
relocated to the manifest's \texttt{operation\_block\_summaries}
|
|
map (opaque ops-supplied bytes, non-canonical, ChunkId-keyed),
|
|
and the order-blocks-by-\texttt{min\_stamp} \SHOULD{} was dropped
|
|
because the canonical manifest encoding sorts chunk references by
|
|
encoded form. The spelling and decomposition pre-passes are now
|
|
specified as producing \emph{canonical derived annotations} ---
|
|
deterministic functions of (materialized graph, profile,
|
|
versioned algorithm id), recomputed on materialization and never
|
|
stored --- replacing the stored-\texttt{Inferred}-attachment
|
|
model; the incremental re-run requirement is demoted from
|
|
\MUST{} to \MAY{} with caching and incrementality required to be
|
|
unobservable. Spelling-precedence ties now break by canonical
|
|
attachment order (attachments carry no creation timestamp). The
|
|
\texttt{ConflictRegistry} moved off the \texttt{Score} root into
|
|
canonical materialized state, matching Chapter~6.
|
|
\texttt{SolverTier} gains the non-conformance \texttt{Stub}
|
|
variant. Companion: Operation Catalog 0.2.0 $\rightarrow$ 0.3.0
|
|
(ModifyEvent materializes metric placement changes behind a
|
|
voice-occupancy placement precondition). Dispositions logged in
|
|
\texttt{spec/PASS12\_RATIFICATION\_LOG.md}; the Pass 12 batch
|
|
itself remains open.
|
|
\\
|
|
\today & Pass 12 tranche 1 addendum (Push-3 enablers) &
|
|
Two enabler ratifications for the machinery-wiring work.
|
|
\texttt{ResolveEquivocation}, previously a dangling prose
|
|
reference in the equivocation section, is now a defined
|
|
meta-operation: \texttt{OperationPayload} gains the variant here,
|
|
and the Operation Catalog (0.3.0 $\rightarrow$ 0.4.0) pins its
|
|
payload schema (\texttt{\{ target: OperationId, chosen:
|
|
EnvelopeHash \}}), the order-independent
|
|
earliest-resolve-governs promotion, idempotence, differing-resolve
|
|
meta-conflict, and precondition no-ops; the profile-declared
|
|
selection-policy path remains open (P12-K5). Engraving break
|
|
overrides became position-addressable:
|
|
\texttt{OverrideKind::SystemBreak}/\texttt{PageBreak} carry the
|
|
break's \texttt{TimeAnchor} (the \texttt{ScoreGraph} target names
|
|
the owning region; projected break overrides carry
|
|
\texttt{Internal} origin pending P11-C8 authorship surfacing),
|
|
closing the representational gap that blocked the logical-stage
|
|
override projection §Engraving Overrides requires.
|
|
\\
|
|
\today & Phase-3 first tranche (casting-off + catalog) &
|
|
Companion movements, no core-spec byte or architecture change. The
|
|
Operation Catalog (0.4.0 $\rightarrow$ 0.5.0) gains the Phase-3
|
|
schema-fill tranche --- \texttt{CreateStaff} (set-union global-staff
|
|
mint, plus a staff-liveness precondition on
|
|
\texttt{CreateStaffInstance}), \texttt{SetTimeSignature} and
|
|
\texttt{SetTempoSegment} (LWW structural overwrites beneath the
|
|
whole-grid \texttt{SetMetricGrid}), \texttt{SetStaffLayout} (LWW
|
|
advisory) --- and a rewritten \sectionsc{UndoTransaction}:
|
|
\texttt{StrictInverse}/\texttt{BestEffort} now perform
|
|
\emph{value restoration} for overwrite primitives via
|
|
canonical-order write chains (delete resurrection, Transpose
|
|
inversion, and Cascade closure stay deferred, P11-C8 narrowed).
|
|
The Binary Format companion (0.1.0 $\rightarrow$ 0.2.0) appends
|
|
the corresponding wire discriminants. This document's
|
|
\texttt{OperationKind} listing gains the four kinds. The create
|
|
score/canvas slots remain deliberately unavailable pending an
|
|
addressable root model (P12-K8).
|
|
\\
|
|
\today & Schema major 1 (data-model expansion) &
|
|
Defines the two referenced-but-undefined types
|
|
\texttt{CanvasLayoutDefaults} (with \texttt{CanvasSize} /
|
|
\texttt{CanvasMargins}, staff-space page geometry, A4/8\,mm default;
|
|
closes P12-I7) and \texttt{PitchRange} (advisory pitch compass, used by
|
|
\texttt{Instrument.range} and \texttt{IndeterminacyHints}); adds
|
|
\texttt{Region.permits\_spanning\_slurs} (default false, P12-K7). Records
|
|
that schema major~1 is the first data-model expansion major: the
|
|
canonical-base \texttt{MaterializedState} embeds none of these values and
|
|
stays major~0, byte-identical, but \texttt{Region.permits\_spanning\_slurs}
|
|
reaches the \emph{canonical} \texttt{CreateRegion} operation payload, so a
|
|
major-0 reader opens a bundle carrying v1 \texttt{CreateRegion} ops
|
|
read-only. The wire form, the resolved-layout length-prefix unification
|
|
(the manifest-embedded barrier blobs stay \texttt{u64}), and the
|
|
byte-for-byte v0${\to}$v1 migration are ratified in the Binary Format
|
|
companion (0.2.0 $\rightarrow$ 0.3.0).
|
|
\\
|
|
\today & Schema major 1 Phase F (engrave ratify-only) &
|
|
Ratifies three engrave/layout-ir dispositions the implementation already made
|
|
(Pass 12 batch I8/I9/I10, now struck), with no byte-layout or architecture
|
|
change: the \emph{break-constraint satisfaction} predicate
|
|
(Requirement~\ref{req:layoutir:break-satisfaction} --- a \texttt{SystemBreakAt}
|
|
/ \texttt{PageBreakAt} is satisfied iff the final \texttt{ResolvedLayoutIR}
|
|
starts a system/page at that slot, a region-first slot trivially); \emph{break-override
|
|
attribution} threaded through a \texttt{ConstrainedLayoutIR.break\_origins}
|
|
sidecar, declining to widen the normalized constraint record
|
|
(Requirement~\ref{req:layoutir:break-origin-attribution}); and the
|
|
\emph{system-continuation} synthesis kind
|
|
\texttt{Registered(SYSTEM\_CONTINUATION\_SYNTHESIS)} with an
|
|
\texttt{(original,\,ordinal)} instance key
|
|
(Requirement~\ref{req:layoutir:continuation-synthesis}). Flags one open item:
|
|
which region governs a cross-region slur's spanning permission (implemented as
|
|
conservative AND, P12-K12).
|
|
\\
|
|
\today & Pass 12 G-ratification (the batch pass) &
|
|
Retires the open Pass-12 batch (28 rows; dispositions in
|
|
\texttt{PASS12\_RATIFICATION\_LOG.md}). Algorithm dispositions: the spelling
|
|
pre-pass and notational decomposition move to profile-declared-by-versioned-
|
|
identifier with ratified v1 defaults
|
|
(Requirements~\ref{req:pitch:spelling-algorithm}
|
|
and~\ref{req:time:decomposition-algorithm}; closes P12-H1/H3/H4/H5/C5);
|
|
decomposition precedence pinned fixed (P12-H6); authored annotations for
|
|
inference-ineligible targets surface
|
|
(Requirement~\ref{req:pitch:authored-uninferred}, P12-H7). Identity:
|
|
system-derived intrinsic content immutable under reduction (P12-K3). Genesis
|
|
ratified outside the operation set (P12-K8). Cross-region slur permission =
|
|
AND (P12-K12). Re-anchoring: cue cascade-on-any-source, Range truncate
|
|
defined, annotation orphaning sanctioned, \texttt{ReanchorReason::
|
|
SameCanvasNearer} appended (P12-C1/C2/C3/C4). Barriers: target-free and
|
|
opaque-operation matching (Requirement~\ref{req:format:barrier-matching},
|
|
P12-E4); unsafe-edit tombstone semantics
|
|
(Requirement~\ref{req:format:unsafe-tombstone}, P12-E5; encoding deferred to
|
|
the Binary Format companion). Solver: kind-determined strength
|
|
(Requirement~\ref{req:solver:kind-strength}, P12-I4), sub-conformant report
|
|
shape (Requirement~\ref{req:solver:subconformant-report}, P12-I5), the
|
|
Minimal-tier constraint floor
|
|
(Requirement~\ref{req:layoutir:constraint-floor}, P12-I6). The stale
|
|
\texttt{OperationKindTag} listing gains the eleven appended tags. Companion
|
|
movements: Operation Catalog 0.5.0~$\rightarrow$~0.6.0, Binary Format
|
|
0.3.0~$\rightarrow$~0.4.0.
|
|
\\
|
|
\today & Schema major 2 (data-model expansion, Phase A) &
|
|
Defines the leaf types Chapter~5's ratified shapes referenced but never
|
|
pinned: \texttt{SlurKind}, \texttt{CurveDirection},
|
|
\texttt{CurvatureOverride}, the shared \texttt{SpanStyle} record
|
|
(consolidating the formerly-sketched identical
|
|
\texttt{SlurStyle}/\texttt{TieStyle}/\texttt{SpannerStyle} triplet),
|
|
\texttt{SubBeam}, \texttt{BeamGeometryOverride}, the
|
|
\texttt{SpannerKind} payload types (\texttt{HairpinDirection},
|
|
\texttt{OctaveOffset}, \texttt{PedalKind}, \texttt{TextLineDefinition},
|
|
\texttt{BracketKind}) --- \texttt{SpannerKind}
|
|
gains a leading \texttt{Generic} variant as the honest migration default
|
|
--- \texttt{Volta}, \texttt{MetadataValue}, \texttt{Timestamp},
|
|
\texttt{SoundConfiguration}, \texttt{TranspositionInterval} (structural,
|
|
advisory until the Chapter~4 tuning catalog per the P12-K2 discipline),
|
|
\texttt{UnpitchedMember}, \texttt{SpaceUnit}, \texttt{LineStyle}, and
|
|
\texttt{StaffBracketKind}. Ratifies the clef/key content model into
|
|
Chapter~5 (\texttt{Clef}, \texttt{ClefShape}, \texttt{KeySignature},
|
|
\texttt{ClefChange}, \texttt{KeySignatureChange} --- the visible-slice
|
|
value types) and retires the \texttt{ClefId} identifier sketch:
|
|
\texttt{Staff.default\_clef} and \texttt{Instrument.default\_clef} embed
|
|
\texttt{Clef} values, and \texttt{Instrument.default\_staff\_lines} is
|
|
reconciled from \texttt{u8} to \texttt{StaffLineConfiguration}. Metadata
|
|
timestamps are pinned strictly authored
|
|
(Requirement~\ref{req:graph:metadata-timestamps}: nothing writes them
|
|
implicitly). Chapter~8 gains the schema-major-2 paragraph (canonical base
|
|
stays major~0; the filled values reach the snapshot and the seven embedding
|
|
operation payloads, \texttt{InsertRegion} transitively). Wire forms and the
|
|
total default-filling migration: Binary Format companion
|
|
0.4.0~$\rightarrow$~0.5.0.
|
|
\\
|
|
\today & Schema major 2 (repeat authoring, Phase D) &
|
|
The dedicated \texttt{CreateRepeatStructure}/%
|
|
\texttt{DeleteRepeatStructure} pair enters the operation set:
|
|
Chapter~6's \texttt{OperationKind} enum and Chapter~8's
|
|
\texttt{OperationKindTag} listing gain the two variants (repeats are not
|
|
\texttt{CrossCuttingValue} kinds on the wire --- the reconciliation
|
|
note follows the \texttt{CrossCuttingStructure} listing), and the
|
|
re-anchoring rule table gains the Repeat-structure~/ Anchor row
|
|
(re-anchor to the nearest surviving anchor across \emph{every}
|
|
event-referencing anchor site --- \texttt{start}/\texttt{end}, jump
|
|
targets, volta spans --- cascade-delete only when none survives).
|
|
Six-part schemas: Operation Catalog \sectionsc{Repeat Structures},
|
|
0.6.0~$\rightarrow$~0.7.0. Wire discriminants 28/29 and the
|
|
per-payload stamping (create born at v2; delete a minor kind append
|
|
at major~0): Binary Format companion 0.5.0~$\rightarrow$~0.6.0.
|
|
\\
|
|
\today & Schema major 2 (rendering, Phase F) &
|
|
The rendering consumers of the schema-major-2 data model are
|
|
ratified into Chapter~7. \texttt{ResolvedLayoutIR} gains the
|
|
non-glyph primitive vocabulary it has always carried in
|
|
implementation --- line \texttt{Stroke}s (staff lines, stems,
|
|
barlines, volta brackets) and cubic-B\'ezier \texttt{Curve}s
|
|
(slurs) --- as \ref{req:layoutir:resolved-primitives}
|
|
(provenance-traced, non-canonical). Two Minimal-tier rendering
|
|
floors join the constraint floor: \ref{req:layoutir:repeat-render}
|
|
(E1: repeat barlines at resolved boundaries, volta brackets with
|
|
ending numbers, honest non-drawing of unresolvable boundaries,
|
|
jump-kind marks and cross-region repeats deferred) and
|
|
\ref{req:layoutir:slur-curve} (E2: a slur renders as a cubic
|
|
B\'ezier honoring \texttt{CurvatureOverride}, honest non-drawing of
|
|
unresolvable or cross-staff spans, a non-\texttt{Solid} line style
|
|
surfaced not silently rendered solid). The curvature-computing
|
|
algorithm and the \texttt{RenderIR} encoding stay forward-referenced
|
|
out (\ref{sec:layoutir:forward}, \ref{sec:layoutir:render}). Layout
|
|
geometry is non-canonical, so no wire form and no companion version
|
|
moves --- an engrave-side ratify-as-implemented, mirroring the
|
|
schema-major-1 Phase~F precedent.
|
|
\\
|
|
\today & Layout IR (slur rendering completion, Push 3) &
|
|
The three slur refinements \ref{req:layoutir:slur-curve} deferred are
|
|
now landed and its requirement extended: an authored non-\texttt{Solid}
|
|
\texttt{SpanStyle} line renders faithfully (dashed / dotted --- the
|
|
\texttt{LineStyle} rides the \texttt{Curve}, whose listing gains the
|
|
field), a slur spanning a system break splits into per-system
|
|
sub-curves (de~Casteljau; first segment keeps the slur's provenance,
|
|
the rest synthesized continuations), and \texttt{slur\_shape\_penalty}
|
|
moves off its pinned $0.0$ to a real measurement (Quality Metric
|
|
Catalog \sectionsc{slur\_shape\_penalty} rationale refreshed; formula
|
|
unchanged, no version move). Layout geometry stays non-canonical --- no
|
|
wire form, no companion version moves.
|
|
\\
|
|
\midrule
|
|
\today & Layout IR (primitive band ownership, Push 3) &
|
|
Chapter 7 gains \ref{req:layoutir:primitive-band-ownership}: every
|
|
primitive the projection presents to the solver ---
|
|
\texttt{GlyphObject}, \texttt{Stroke}, \texttt{Curve} --- declares the
|
|
\texttt{VerticalBandId} that owns it, naming a band that exists, and a
|
|
vertical solver takes ownership from that declaration rather than
|
|
inferring it from geometry. Ratifies-as-implemented the contract a
|
|
vertical solver needs and an inference cannot supply: a slur's
|
|
endpoints are lifted clear of its own staff, so its owner is not
|
|
recoverable from where it is drawn. The \texttt{ConstrainedLayoutIR}
|
|
listing gains \texttt{strokes} / \texttt{curves} (present since staff
|
|
lines, never listed) and the \texttt{Stroke} / \texttt{Curve} listings
|
|
gain \texttt{vertical\_band}; a band now exists for every staff of a
|
|
region and for its margin, members or not.
|
|
\ref{req:layoutir:resolved-band-ownership} carries the declaration
|
|
through the resolved stage on strokes and curves (a
|
|
\texttt{ResolvedGlyph} keeps none --- its ownership is already baked
|
|
into its position). Band ownership is non-canonical attribution
|
|
metadata: it enters no content hash and no canonical encoding, so no
|
|
wire form and no companion version moves.
|
|
\\
|
|
\bottomrule
|
|
\end{longtable}
|
|
|
|
\end{document}
|