diff --git a/Sources/Tonic/Note.swift b/Sources/Tonic/Note.swift index 8f592f1..0f5eb01 100644 --- a/Sources/Tonic/Note.swift +++ b/Sources/Tonic/Note.swift @@ -82,19 +82,31 @@ 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..23625a9 100644 --- a/Tests/TonicTests/NoteTests.swift +++ b/Tests/TonicTests/NoteTests.swift @@ -162,4 +162,21 @@ 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) + } }