Skip to content

[patch] Stop Pitch octaves wrapping into range, and reject whitespace or '+' in the octave - #402

Merged
matt-edmondson merged 1 commit into
mainfrom
fix/307-pitch-octave-range
Oct 9, 2026
Merged

matt-edmondson merged 1 commit into
mainfrom
fix/307-pitch-octave-range

Conversation

@matt-edmondson

Copy link
Copy Markdown
Contributor

Fixes #307

What was wrong

  • Pitch.TryParse and Pitch.Create(NoteLetter, Accidental, int octave) computed ((octave + 1) * 12) + letter + accidental in unchecked int. A huge octave wrapped back into 0..127: "C1073741823", "C2147483647" and "C-1073741825" all parsed as C-1, and the typed factory returned C-1 for those octaves instead of throwing.
  • The octave was parsed with NumberStyles.Integer, which allows leading and trailing whitespace and a leading +. So "C 4 " and "C+4" parsed and then printed back as "C4", which breaks the canonical-text round trip.

Change (Semantics.Music/Pitch.cs)

  • A private MidiOf(octave, letter, accidental) computes the MIDI number in long, so no octave can wrap. Both TryParse and the typed Create use it before the 0..127 range check. I chose long arithmetic over a hard-coded octave window so the range check stays the only rule.
  • Create(NoteLetter, Accidental, int) now throws ArgumentOutOfRangeException for an out-of-range result itself, naming octave. This is the documented exception type; before, it came from Create(int) naming midi.
  • TryParse parses the octave with NumberStyles.AllowLeadingSign only, and rejects an explicit +. So a leading - (needed for octave -1) is the only thing accepted besides digits, and whitespace is rejected.

Tests (PitchTests)

  • TryParse_RejectsWrappingOrNonCanonicalOctave: C1073741823, C-1073741825, C2147483647, C-2147483648, C 4, C 4 , C4 , C+4, plus C and C# with no octave.
  • TryParse_AcceptsNegativeOctave: C-1 still parses to MIDI 0 and prints as C-1.
  • Create_FromLetter_RejectsOctaveThatWouldWrap: 1073741823, int.MaxValue and int.MinValue throw ArgumentOutOfRangeException.

With Pitch.cs reverted, 11 of these cases fail. With the fix, the full Semantics.Test suite passes: 1494 passed, 0 failed, 8 skipped. Semantics.Music builds for every target framework.

TimeSignature and Velocity also parse with NumberStyles.Integer. I left them out of this PR to keep it scoped to the issue.

🤖 Generated with Claude Code

https://claude.ai/code/session_019RYN9jT6szUiySkywFKCA1


Generated by Claude Code

… or '+' in the octave

Fixes #307

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RYN9jT6szUiySkywFKCA1
@sonarqubecloud

sonarqubecloud Bot commented Oct 8, 2026

Copy link
Copy Markdown

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.

Pitch.TryParse("C1073741823") succeeds as C-1: octave arithmetic wraps, and "C 4 " is accepted too

2 participants