Skip to content

Commit

Permalink
Merge pull request #62 from orchetect/dev
Browse files Browse the repository at this point in the history
Dev merge
  • Loading branch information
orchetect authored Dec 17, 2021
2 parents c9a654f + 23563aa commit 7599a32
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 19 deletions.
34 changes: 24 additions & 10 deletions Sources/MIDIKit/Note/MIDI Note Layout.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import Foundation

extension MIDI {

public typealias NoteRange = ClosedRange<MIDI.UInt7>
public typealias NoteNumberRange = ClosedRange<MIDI.UInt7>

}

extension MIDI.NoteRange {
extension MIDI.NoteNumberRange {

/// All 128 notes (C-2...G8, or 0...127)
@inline(__always)
Expand All @@ -25,21 +25,35 @@ extension MIDI.NoteRange {

extension MIDI {

public enum PianoKeyType {

case white
case black

}
public typealias NoteRange = ClosedRange<MIDI.Note>

}

extension MIDI.NoteRange {

/// All 128 notes (C-2...G8, or 0...127)
@inline(__always)
public static let all: Self = MIDI.Note(0)...MIDI.Note(127)

/// 88-key piano keyboard note range: (A-1...C7, or 12...108)
@inline(__always)
public static let eightyEightKeys: Self = MIDI.Note(21)...MIDI.Note(108)

}

extension MIDI.Note {

public var pianoKey: MIDI.PianoKeyType {
/// Returns `true` if note is sharp (has a ♯ accidental). On a piano keyboard, this would be a black key.
@inline(__always)
public var isSharp: Bool {

let octaveMod = number % 12
return [0,2,4,5,7,9,11].contains(octaveMod) ? .white : .black
return [1,3,6,8,10].contains(octaveMod)


// this also works, but the math above may be slightly more performant,
// since the `name` property would have to call `Name.convert(noteNumber:)`
//return name.isSharp

}

Expand Down
52 changes: 51 additions & 1 deletion Sources/MIDIKit/Note/MIDI Note Name.swift
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ extension MIDI.Note {

}

/// Semitone offset, originating from note C
/// Semitone offset originating from note C, ascending.
public var scaleOffset: Int {

switch self {
Expand All @@ -220,6 +220,56 @@ extension MIDI.Note {

}

/// Returns `true` if note is sharp (has a ♯ accidental). On a piano keyboard, this would be a black key.
public var isSharp: Bool {

switch self {
case .A,
.B,
.C,
.D,
.E,
.F,
.G:
return false

case .A_sharp,
.C_sharp,
.D_sharp,
.F_sharp,
.G_sharp:
return true
}

}

/// Returns note name and octave for the MIDI note number.
/// Returns `nil` if MIDI note number is invalid.
internal static func convert(noteNumber: MIDI.UInt7) -> (name: Self, octave: Int) {
// UInt7 is guaranteed to be a valid MIDI note number

let octave = (noteNumber.intValue / 12) - 2

switch noteNumber.intValue % 12 {
case 9: return (name: .A, octave: octave)
case 10: return (name: .A_sharp, octave: octave)
case 11: return (name: .B, octave: octave)
case 0: return (name: .C, octave: octave)
case 1: return (name: .C_sharp, octave: octave)
case 2: return (name: .D, octave: octave)
case 3: return (name: .D_sharp, octave: octave)
case 4: return (name: .E, octave: octave)
case 5: return (name: .F, octave: octave)
case 6: return (name: .F_sharp, octave: octave)
case 7: return (name: .G, octave: octave)
case 8: return (name: .G_sharp, octave: octave)
default:
// should never happen
assertionFailure("Modulus is broken.")
return (name: .C, octave: -2)
}
}

}

}
19 changes: 13 additions & 6 deletions Sources/MIDIKit/Note/MIDI Note.swift
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,7 @@ public extension MIDI {
public mutating func setNoteNumber(_ source: Name,
octave: Int) -> Bool {

let rootValue = 0

let noteNum = rootValue + ((octave + 2) * 12) + source.scaleOffset
let noteNum = ((octave + 2) * 12) + source.scaleOffset

guard let uint7 = MIDI.UInt7(exactly: noteNum) else { return false }

Expand Down Expand Up @@ -158,6 +156,16 @@ public extension MIDI {

}

/// Get the MIDI note name enum case.
public var name: Name {
Name.convert(noteNumber: number).name
}

/// Get the MIDI note name enum case.
public var octave: Int {
Name.convert(noteNumber: number).octave
}

/// Get the MIDI note name string (ie: "A-2" "C#6")
public func stringValue(
respellSharpAsFlat: Bool = false,
Expand Down Expand Up @@ -251,7 +259,7 @@ extension MIDI.Note: Strideable {
public func advanced(by n: Int) -> Self {

let val = (number.intValue + n)
.clamped(to: MIDI.NoteRange.all.lowerBound.intValue ... MIDI.NoteRange.all.upperBound.intValue)
.clamped(to: MIDI.NoteNumberRange.all.lowerBound.intValue ... MIDI.NoteNumberRange.all.upperBound.intValue)

return Self(number: val) ?? .init()

Expand All @@ -262,8 +270,7 @@ extension MIDI.Note: Strideable {
public extension MIDI.Note {

/// Returns an array of all 128 MIDI notes.
static let allNotes: [Self] =
MIDI.NoteRange.all.map { Self($0) }
static let allNotes: [Self] = MIDI.NoteRange.all.map { $0 }

}

Expand Down
18 changes: 16 additions & 2 deletions Tests/MIDIKitTests/Note/MIDI Note Tests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,20 @@ final class NoteTests: XCTestCase {
XCTAssertEqual(MIDI.Note(.G_sharp, octave: 8)?.number, nil)
}

func testNoteName() {
XCTAssertEqual(MIDI.Note(0).name, .C)
XCTAssertEqual(MIDI.Note(0).octave, -2)

XCTAssertEqual(MIDI.Note(59).name, .B)
XCTAssertEqual(MIDI.Note(59).octave, 2)

XCTAssertEqual(MIDI.Note(60).name, .C)
XCTAssertEqual(MIDI.Note(60).octave, 3)

XCTAssertEqual(MIDI.Note(127).name, .G)
XCTAssertEqual(MIDI.Note(127).octave, 8)
}

func testPianoKeyType_WhiteKeys() {

// generate white keys
Expand All @@ -232,7 +246,7 @@ final class NoteTests: XCTestCase {
// test white keys

XCTAssertEqual(whiteKeyNotes.count, 75)
XCTAssert(whiteKeyNotes.allSatisfy { $0.pianoKey == .white })
XCTAssert(whiteKeyNotes.allSatisfy { !$0.isSharp })

}

Expand All @@ -255,7 +269,7 @@ final class NoteTests: XCTestCase {
// test black keys

XCTAssertEqual(blackKeyNotes.count, 53)
XCTAssert(blackKeyNotes.allSatisfy { $0.pianoKey == .black })
XCTAssert(blackKeyNotes.allSatisfy { $0.isSharp })

}

Expand Down

0 comments on commit 7599a32

Please sign in to comment.