From 6b75f7869844134bbcfd7f375b9495493b4bb449 Mon Sep 17 00:00:00 2001 From: Ken Date: Wed, 27 Aug 2025 16:19:27 -0700 Subject: [PATCH 1/2] Bugfix: Clamp noteNumber to MIDI range (0-127) to prevent crashes - Add early bounds checking for octave values outside -2 to 8 range. Fixes infinite loop for octaves below -2 - Add final bounds checking to ensure note values are within MIDI range. Prevents crash on cast to Int8 for values > 127 - Add test coverage --- Sources/Tonic/Note.swift | 40 +++++++++++++++++++++----------- Tests/TonicTests/NoteTests.swift | 18 ++++++++++++++ 2 files changed, 45 insertions(+), 13 deletions(-) diff --git a/Sources/Tonic/Note.swift b/Sources/Tonic/Note.swift index 8f592f1..934db45 100644 --- a/Sources/Tonic/Note.swift +++ b/Sources/Tonic/Note.swift @@ -82,19 +82,33 @@ public struct Note: Sendable, Equatable, Hashable, Codable { /// MIDI Note 0-127 starting at C public var noteNumber: Int8 { - let octaveBounds = ((octave + 2) * 12) ... ((octave + 3) * 12) - var note = Int(noteClass.letter.baseNote) + Int(noteClass.accidental.rawValue) - if noteClass.letter == .B && noteClass.accidental.rawValue > 0 { - note -= 12 - } - if noteClass.letter == .C && noteClass.accidental.rawValue < 0 { - note += 12 - } - while !octaveBounds.contains(note) { - note += 12 - } - return Int8(note) - } + if octave < -2 { + return 0 + } + if octave > 8 { + return 127 + } + + let octaveBounds = ((octave + 2) * 12) ... ((octave + 3) * 12) + var note = Int(noteClass.letter.baseNote) + Int(noteClass.accidental.rawValue) + if noteClass.letter == .B && noteClass.accidental.rawValue > 0 { + note -= 12 + } + if noteClass.letter == .C && noteClass.accidental.rawValue < 0 { + note += 12 + } + while !octaveBounds.contains(note) { + note += 12 + } + + if note < 0 { + return 0 + } + if note > 127 { + return 127 + } + return Int8(note) + } /// The pitch for the note public var pitch: Pitch { diff --git a/Tests/TonicTests/NoteTests.swift b/Tests/TonicTests/NoteTests.swift index def1ef3..1f00b3d 100644 --- a/Tests/TonicTests/NoteTests.swift +++ b/Tests/TonicTests/NoteTests.swift @@ -162,4 +162,22 @@ final class NoteTests: XCTestCase { let empty = NoteSet() XCTAssertNil(empty.first) } + + func testClampNoteBounds() { + + let bMinus3 = Note(.B, octave: -3) + XCTAssertEqual(bMinus3.noteNumber, 0) + + let cFlatMinus3 = Note(.C, accidental: .flat, octave: -3) + XCTAssertEqual(cFlatMinus3.noteNumber, 0) + + let a8 = Note(.A, octave: 8) + XCTAssertEqual(a8.noteNumber, 127) + + let gSharp8 = Note(.G, accidental: .sharp, octave: 8) + XCTAssertEqual(gSharp8.noteNumber, 127) + + let c9 = Note(.C, octave: 9) + XCTAssertEqual(c9.noteNumber, 127) + } } From 2707352e0f428e6055458ca5ea5521708392b394 Mon Sep 17 00:00:00 2001 From: Ken Date: Wed, 27 Aug 2025 17:14:21 -0700 Subject: [PATCH 2/2] Fix coding style violations --- Sources/Tonic/Note.swift | 2 -- Tests/TonicTests/NoteTests.swift | 3 +-- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/Sources/Tonic/Note.swift b/Sources/Tonic/Note.swift index 934db45..0f5eb01 100644 --- a/Sources/Tonic/Note.swift +++ b/Sources/Tonic/Note.swift @@ -88,7 +88,6 @@ public struct Note: Sendable, Equatable, Hashable, Codable { if octave > 8 { return 127 } - let octaveBounds = ((octave + 2) * 12) ... ((octave + 3) * 12) var note = Int(noteClass.letter.baseNote) + Int(noteClass.accidental.rawValue) if noteClass.letter == .B && noteClass.accidental.rawValue > 0 { @@ -100,7 +99,6 @@ public struct Note: Sendable, Equatable, Hashable, Codable { while !octaveBounds.contains(note) { note += 12 } - if note < 0 { return 0 } diff --git a/Tests/TonicTests/NoteTests.swift b/Tests/TonicTests/NoteTests.swift index 1f00b3d..23625a9 100644 --- a/Tests/TonicTests/NoteTests.swift +++ b/Tests/TonicTests/NoteTests.swift @@ -162,9 +162,8 @@ final class NoteTests: XCTestCase { let empty = NoteSet() XCTAssertNil(empty.first) } - - func testClampNoteBounds() { + func testClampNoteBounds() { let bMinus3 = Note(.B, octave: -3) XCTAssertEqual(bMinus3.noteNumber, 0)