Skip to content

Add support for implicit octaves - #335

Merged
mscuthbert merged 4 commits into
masterfrom
octave-is-implicit
Sep 2, 2026
Merged

mscuthbert merged 4 commits into
masterfrom
octave-is-implicit

Conversation

@mscuthbert

Copy link
Copy Markdown
Member

Music21p started off with a very broad concept of implicit octaves -- simply make octave = None and tada -- it was pitch without octave. But sometimes you need an octave, so it had .implicitOctave which gave 4 for octaveless pitches.

By the time I started on music21j I recognized that music21p's version was overly complicated. So music21j only had octave as a number (int). It never supported implicit octaves.

Now as of cuthbertLab/music21#2023 music21p also makes octave only a number. Here we add "octaveIsImplicit" for parity with music21p and leave our always-int .octave alone.

Other changes to sync w/ music21p but break older behavior here:

  • new Pitch('C').nameWithOctave is now 'C', not 'C4' — same for unicodeNameWithOctave, toString() and Note.nameWithOctave.
  • new Pitch('C').eq(new Pitch('C4')) is now false; eq compares the stored octave, not the reported one.
  • KeySignature.alteredPitches and Key.tonic are octaveless.
  • Pitch(3) (a pitchClass) is octaveless; Pitch(65) (a midi number) is not.

AI-assisted (Claude)

Ports the music21p `octave-int` branch (7a81a6bc0..8c65af725).  m21j was
already halfway there: `_octave` was seeded to 4 so `.octave` was in
practice always a number, but `implicitOctave`, `unicodeNameWithOctave`
and `_getEnharmonicHelper` still tested for `undefined`, and
`ChromaticInterval.transposePitch` set `.octave = undefined` on a
"not yet implemented in m21j" path -- which would have crashed
`nameWithOctave` (`undefined.toString()`).  So the octaveless concept
existed only as dead branches.

Now `_octave` is undefined until an octave is given; `.octave` reports
`defaults.pitchOctave` (new, 4) in that case and `.octaveIsImplicit`
tells the two apart.  `implicitOctave` becomes a deprecated synonym for
`.octave`.  Implicitness survives cloning, transposition and enharmonic
respelling, as in m21p.

Behavior changes, all matching m21p:

- `new Pitch('C').nameWithOctave` is 'C', not 'C4' (likewise
  `unicodeNameWithOctave`, `toString()`, `Note.nameWithOctave`).
- `new Pitch('C').eq(new Pitch('C4'))` is false; `eq` compares the
  stored octave, not the reported one.
- `KeySignature.alteredPitches` and `Key.tonic` are octaveless.
- `Pitch(3)` (a pitchClass) is octaveless; `Pitch(65)` (midi) is not.
- `nameWithOctave = 'C'` sets octaveIsImplicit instead of throwing on a
  null regex match.

Places that need a real octave pin one down rather than inheriting the
default, again as m21p's intervalNetwork does: `AbstractScale`'s
`getRealization` and `getPitchFromNodeDegree` (so `Key('E-')`, whose
tonic is octaveless, still realizes E-4..E-5 and `pitchFromDegree(5)`
still climbs), and `Interval.transposePitch`, which transposes with an
octave and forgets it afterwards so intervals wider than an octave
spell correctly.

MusicXML export reads `.octave` directly now that `implicitOctave` is
just a synonym.

AI-assisted (Claude)
music21p keeps `octave = None` only as a deprecation shim (removed in
v13); m21j has no such history to preserve, so the setter takes a plain
number. `octaveIsImplicit = true` is the way to make a Pitch octaveless.

AI-assisted (Claude)
The three interval transposePitch methods and _getEnharmonicHelper build
a separate pitch, so the source flag is still valid at the end; one extra
dereference is cheaper than the local.

simplifyEnharmonic keeps its local: returnObj is `this` when inPlace, so
setting .ps clears the flag before it would be read.

AI-assisted (Claude)
An implicit octave reports defaults.pitchOctave, which is the same
anchor `pKeep.octave = 4` supplied, so the loop can hand out the
transposed pitch itself instead of an explicit copy.

AI-assisted (Claude)
@mscuthbert
mscuthbert merged commit 3c43c47 into master Sep 2, 2026
4 checks passed
@mscuthbert
mscuthbert deleted the octave-is-implicit branch September 2, 2026 08:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant