Invariants shared by every MCC music-theory module.
Invariants shared by every MCC music-theory module.
Status: approved (Phase 2). Every rule below is normative; later phases implement and test against it. Identifiers such as SPEC-OCT-1 are referenced from unit tests.
Terminology revision (after Phase 4): the spelled letter + accidental type is NoteName and PitchClass is the integer class 0-11, following the standard music-theory meaning of "pitch class". The rules are unchanged.
Terminology
MCC builds absolute pitch in two steps:
Letter + Accidental -> NoteName (written spelling, no octave)
NoteName + Octave -> Pitch (written, absolute)
MCC::Note is not an alternate absolute-pitch type: MCC::Pitch represents height and spelling; MCC::Note adds a rhythmic value.
Octave convention
- SPEC-OCT-1: MCC uses scientific pitch notation. Middle C is C4.
- SPEC-OCT-2: Under the standard tuning A4 is 440 Hz.
- SPEC-OCT-3: The octave number belongs to the letter, not to the sounding pitch. Octaves change between B and C. B#3 is written in octave 3 and sounds as C4; Cb4 is written in octave 4 and sounds as B3.
- SPEC-OCT-4: Octaves are signed 8-bit values in [-128, 127]. Operations that would leave this range produce an invalid result (see errors).
Written equality versus enharmonic equivalence
- SPEC-EQ-1: operator== compares written spelling: letter, accidental and, for pitches, octave. C# != Db and C#4 != Db4.
- SPEC-EQ-2: Enharmonic equivalence is always explicit: MCC::IsEnharmonic(a, b). For note names it compares pitch classes; for pitches it compares chromatic indices. B#3 and C4 are enharmonic; B#3 and C3 are not.
- SPEC-EQ-3: No implicit conversion exists between NoteName, Pitch, PitchClass and ChromaticIndex.
Ordering
- SPEC-ORD-1: Letters are ordered C D E F G A B with diatonic indices 0-6 and natural semitone offsets 0 2 4 5 7 9 11.
- SPEC-ORD-2: Diatonic index of a pitch = octave * 7 + letterIndex.
- SPEC-ORD-3: Chromatic index of a pitch = (octave + 1) * 12 + letterSemitone + accidental.
- SPEC-ORD-4: Pitch class of a note name = (letterSemitone + accidental) reduced modulo 12 into [0, 11].
- SPEC-ORD-5: operator< is written order, used for sorting and containers: octave, then letter, then accidental. It is not pitch height: B#3 < Cb4 in written order although B#3 sounds higher.
- SPEC-ORD-6: Pitch height is compared through chromatic indices, with ties broken by written order to keep sorting deterministic.
Accidental range
- SPEC-ACC-1: Accidentals range from -4 (quadruple flat) to +4 (quadruple sharp), matching the historical MCC token model (9 values per letter).
- SPEC-ACC-2: Natural (0) is a distinct written accidental; the formatting layer decides whether it is printed.
- SPEC-ACC-3: Any operation whose result needs an accidental outside [-4, +4] produces an invalid result; it never respells silently.
Absolute chromatic coordinate
- SPEC-CHR-1: MCC::ChromaticIndex is a signed 16-bit semitone count with origin C-1 = 0, so C0 = 12, C4 = 60 and A4 = 69. The numbering coincides with MIDI note numbers so values are familiar when debugging.
- SPEC-CHR-2: The full octave and accidental ranges fit in the type (-127 * 12 - 4 = -1528 to 128 * 12 + 11 + 4 = 1551).
- SPEC-CHR-3: A chromatic index is not restricted to the MIDI range and MCC exposes no MIDI types. MIDILAR converts a pitch to a MIDI note number by taking its chromatic index and checking that it lies in [0, 127].
Intervals
- SPEC-INT-1: An interval stores a diatonic step count and a chromatic semitone count; quality and number are derived from both.
- SPEC-INT-2: Intervals are directed: ascending, descending or unison. Number is 1-based (1 = unison, 8 = octave) and always positive; the direction is stored separately.
- SPEC-INT-3: Simple intervals have numbers 1-8; compound intervals have numbers above 8 and reduce to simple ones by removing octaves (10 -> 3).
- SPEC-INT-4: Qualities are perfect, major, minor, augmented and diminished, with augmented and diminished repeatable up to four times. Perfect applies to unisons, fourths, fifths and octaves (and compounds); major/minor to the rest.
- SPEC-INT-5: A diminished unison is not produced: the interval from C to Cb is a descending augmented unison.
- SPEC-INT-6: Inversion of a simple interval satisfies number + inverted number = 9 and swaps major/minor and augmented/diminished. Because of SPEC-INT-5, an augmented octave inverts to an augmented unison in the opposite direction.
- SPEC-INT-7: Transposition preserves spelling: pitch + interval moves the letter by the diatonic count and sets the accidental required by the chromatic count.
Error handling without exceptions
MCC never throws and never has undefined behavior on invalid input.
- SPEC-ERR-1: Accidental, NoteName, Pitch, Interval, NoteValue, Note and Meter each have one canonical invalid value and an IsValid() query. Letter is a closed enumeration and is always valid.
- SPEC-ERR-2: Default construction produces the invalid value.
- SPEC-ERR-3: Construction from checked inputs (integers, text) that are out of range produces the invalid value.
- SPEC-ERR-4: Invalid values propagate: any operation with an invalid operand returns an invalid result.
- SPEC-ERR-5: Operations whose result is unrepresentable (octave or accidental overflow) return an invalid result.
- SPEC-ERR-6: All invalid values of a type compare equal to each other and unequal to every valid value. IsEnharmonic() with an invalid operand is false.
- SPEC-ERR-7: Numeric queries on an invalid value (for example ChromaticIndex()) return a documented sentinel and must not be relied on; debug builds may assert.
Embedded memory and object-size constraints
- SPEC-EMB-1: Fundamental value types are trivially copyable, have no virtual functions and never allocate.
- SPEC-EMB-2: Size budgets: Letter, Accidental and PitchClass 1 byte, NoteName at most 2 bytes, Pitch at most 4 bytes, Interval at most 4 bytes; scale and chord patterns 8 bytes, Scale and Chord at most 12 bytes, KeySignature 1 byte, Key at most 4 bytes, NoteValue at most 2 bytes, Note at most 6 bytes and Meter at most 3 bytes. Enforced with static_assert in the unit tests and in the library build for every target, AVR and Arm included (src/MCC.cpp).
- SPEC-EMB-3: Construction, comparison, transposition and interval arithmetic are constexpr.
- SPEC-EMB-4: Core code uses no RTTI, exceptions, iostreams or floating point; only tuning/frequency APIs use float.
- SPEC-EMB-5: Catalog data is constexpr static data placed in program memory on AVR through FOUNDATION_FLASH and read only through Foundation::Utils::Flash, so it occupies no RAM.
Scales
- SPEC-SCL-1: A scale pattern has 1 to 12 degrees with strictly ascending semitones inside one octave; the first degree is the perfect unison.
- SPEC-SCL-2: Each degree stores diatonic steps as well as semitones, so a scale spelled from any root keeps one letter per step: E major contains G#, never Ab. Repeated steps (b5 5 in the blues scale) are allowed.
- SPEC-SCL-3: A degree whose spelling needs an accidental outside [-4, +4] is the invalid note name; the scale itself stays valid.
- SPEC-SCL-4: Catalog identifiers are stable and never reused. Names and aliases are unique and matched exactly by Scales::Find().
Chords
- SPEC-CHD-1: A chord pattern has 1 to 7 tones with strictly ascending semitones below two octaves; the first tone is the perfect unison.
- SPEC-CHD-2: Each tone keeps its written number, so extensions are spelled as compound intervals: the ninth of C9 is D, written as a ninth.
- SPEC-CHD-3: Inversion k places tone k + 1 in the bass and raises each earlier tone by whole octaves until the voicing ascends (close position).
- SPEC-CHD-4: Catalog identifiers are stable and never reused. Names, symbols and aliases are unique; Chords::Find() matches names and aliases exactly.
- SPEC-CHD-5: Chords::FromScale() keeps a chord only when every tone is written in the scale; membership is by spelling, never enharmonic.
Keys
- SPEC-KEY-1: A key signature has zero to seven sharps (F C G D A E B) or flats (B E A D G C F); anything else is the invalid signature.
- SPEC-KEY-2: A key is a tonic and one of the seven diatonic modes, modelled apart from its scale. Its signature follows from the circle of fifths; a tonic that would need more than seven sharps or flats is invalid.
- SPEC-KEY-3: Spelling in a key uses the key's letters for diatonic pitch classes. A chromatic pitch class alters a diatonic note by one semitone, choosing the fewest accidentals and, on a tie, a raised note in sharp keys (and C) or a lowered note in flat keys.
Written rhythm
- SPEC-RHY-1: A NoteValue base is a power-of-two fraction of a whole note, from whole (1/1) through 256th (1/256). Other base denominators are invalid.
- SPEC-RHY-2: A note value has zero to four augmentation dots. Every dot adds half of the preceding addition, so base with d dots is exactly (2^(d+1) - 1) / (baseDenominator * 2^d) of a whole note. The result is represented as an exact reduced integer fraction, never as floating point.
- SPEC-RHY-3: Written identity includes both the base and the dot count; ordering compares the exact duration. Invalid values sort after valid values, following the other MCC value types.
- SPEC-RHY-4: NoteValue describes notation only. Tempo, wall-clock duration, scheduling, MIDI clock and PPQN conversion belong outside the type and, where protocol or execution is involved, outside MCC.
- SPEC-RHY-5: Note combines exactly one valid written Pitch and one valid NoteValue. Invalid input produces the canonical invalid note; constructors that omit the value use a quarter note.
- SPEC-RHY-6: Note construction from letter, accidental, note name or pitch preserves spelling. Copy-style pitch movement preserves the note value, and changing the note value preserves the pitch.
- SPEC-RHY-7: MCC never consumes a MIDI-note type, conditionally or otherwise. A MIDI number has no unique spelling. MIDILAR owns conversion to MCC::Note and must receive a key or another explicit spelling policy, plus the desired NoteValue.
- SPEC-RHY-8: A Meter preserves a numerator in [1, 255] and an undotted power-of-two denominator from whole through 256th. Written equality does not reduce the signature: 3/4 != 6/8; equal measure length is an explicit HasSameMeasureDuration() query.
- SPEC-RHY-9: Numerators one through four are simple. Numerators at least six and divisible by three are compound, with three denominator pulses per dotted beat. Other numerators are irregular: their grouping is not inferred and must later be supplied by a rhythm pattern.
- SPEC-RHY-10: A meter exposes its exact reduced measure length as a fraction of a whole note. It contains no tempo, clock, PPQN or playback state. A compound signature with denominator 1 is unsupported because its beat would exceed the largest current NoteValue.
- SPEC-RHY-11: A RhythmPattern is a cyclic grid of one to 65535 equal steps, each an onset or a rest, whose step count is chosen and changed at runtime. Like a vector, it keeps a step count and a capacity in whole bytes (multiples of eight steps); changing the step count within the capacity never reallocates. The steps are a Foundation::Containers::BitVector, so storage is either owned (heap, a cpstd::vector grown on demand) or a caller-provided buffer attached with a non-null pointer to Attach(), which the pattern never reallocates or frees; in-place operations (Parse, Rotate, Invert, Append) work inside it without allocating. A request the storage cannot hold, including an allocation failure under CPSTL's own implementation, yields the invalid pattern or a false result that leaves the pattern unchanged (in CPSTL's STL mode an allocation failure follows std::vector). A pattern without onsets is valid; only the zero-step pattern is invalid. Box notation (x onset, . rest) and interonset intervals (3-3-4-2-4, first onset on step 0) describe the same patterns; malformed text or more than 65535 steps is invalid.
- SPEC-RHY-12: Step queries reduce modulo the step count. Rotated(n) starts the cycle n steps later, so step i of the result is step i + n of the source; rotating by the step count is the identity.
- SPEC-RHY-13: Two patterns are rotations of each other (the same necklace) when their step counts match and some rotation makes them equal. Equality itself is exact: the 3-2 and 2-3 claves are rotations but not equal.
- SPEC-RHY-14: A RhythmPattern is structure only. It records no step value, meter, tempo, clock or PPQN; written context belongs to the catalog entry and playback to MIDILAR.
Text formatting and buffer policy
- SPEC-TXT-1: The core API never requires std::string. Formatting writes into a caller-provided char buffer.
- SPEC-TXT-2: Formatting follows snprintf semantics: it returns the length the full text needs, writes at most size - 1 characters and always null-terminates when size > 0.
- SPEC-TXT-3: Default output is ASCII: # sharp, b flat, repeated for multiples (C##4, Ebbb3); natural is omitted unless requested. Unicode symbols are opt-in through NotationOptions.
- SPEC-TXT-4: Parsing accepts every output form in every supported encoding and a few common variants: a letter in either case, x for the double sharp, n/♮ for the natural and a leading + on intervals. Sharps and flats never mix; malformed text, including malformed UTF-8 or UTF-16, returns the invalid value.
- SPEC-TXT-5: The output encoding follows the buffer's character type: char holds ASCII or UTF-8, char16_t UTF-16 and char32_t UTF-32. Lengths and capacities count code units, and truncation never splits a multi-unit character.
Invariant-to-test map
| Invariants | Planned test suite | Phase |
| SPEC-ORD-1, ORD-4, ACC-1..3 | NoteNameTests (exhaustive 7 x 9 spellings) | 3 |
| SPEC-EQ-1..3, ORD-5..6 | PitchComparisonTests | 3-4 |
| SPEC-OCT-1..4, ORD-2..3, CHR-1..2 | PitchTests (octave and B#/Cb boundaries) | 4 |
| SPEC-OCT-2 | TuningTests (A4 = 440 Hz, C4 ~ 261.63 Hz) | 4 |
| SPEC-INT-1..5 | IntervalTests | 5 |
| SPEC-INT-6..7 | IntervalPropertyTests (inversion, round-trip transposition) | 5 |
| SPEC-ERR-1..7 | InvalidStateTests per type | 3-5 |
| SPEC-EMB-1..3 | static_assert layout tests, AVR build | 3-5 |
| SPEC-SCL-1..4, EMB-5 | ScalePatternTests, ScaleTests, ScaleCatalogTests, manual AVR section inspection | 6 |
| SPEC-CHD-1..5, EMB-5 | ChordPatternTests, ChordTests, ChordCatalogTests, FromScaleTests, manual AVR section inspection | 7 |
| SPEC-KEY-1..3 | KeyTests | 8 |
| SPEC-TXT-1..5 | NotationTests | 8 |
| SPEC-RHY-1..4 | NoteValueTests (all bases and dot counts) | 9 |
| SPEC-RHY-5..7 | NoteTests (constructors, invalid state, spelling and movement) | 9 |
| SPEC-RHY-8..10 | MeterTests (all numerators and denominators, beat derivation) | 9 |
| SPEC-RHY-11..14 | RhythmPatternTests (both notations, capacity, rotation, necklaces, invalid state) | 9 |