Skip to content

Commit

Permalink
Merge pull request #43 from valentinradu/master
Browse files Browse the repository at this point in the history
Added temperament (just, equal, pytha)
  • Loading branch information
vprtwn committed Jun 8, 2018
2 parents 4fa4cd8 + ae32163 commit c1bc4d2
Show file tree
Hide file tree
Showing 11 changed files with 373 additions and 59 deletions.
1 change: 1 addition & 0 deletions Carthage/Build
28 changes: 26 additions & 2 deletions MusicKit.xcodeproj/project.pbxproj
Expand Up @@ -91,6 +91,14 @@
96F2993F1A56654800D7B006 /* MusicKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96F2993E1A56654800D7B006 /* MusicKit.swift */; };
96F299411A5665CA00D7B006 /* Pitch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96F299401A5665CA00D7B006 /* Pitch.swift */; };
96F299431A56661300D7B006 /* Scale.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96F299421A56661300D7B006 /* Scale.swift */; };
B02B69571FF04FE900AC0862 /* Translation.swift in Sources */ = {isa = PBXBuildFile; fileRef = B02B69561FF04FE900AC0862 /* Translation.swift */; };
B02B69581FF04FE900AC0862 /* Translation.swift in Sources */ = {isa = PBXBuildFile; fileRef = B02B69561FF04FE900AC0862 /* Translation.swift */; };
B02B695A1FF077F600AC0862 /* TranslationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B02B69591FF077F600AC0862 /* TranslationTests.swift */; };
B02B695B1FF077F600AC0862 /* TranslationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B02B69591FF077F600AC0862 /* TranslationTests.swift */; };
B07867B41FEF41BC007F2AE3 /* Temperament.swift in Sources */ = {isa = PBXBuildFile; fileRef = B07867B31FEF41BC007F2AE3 /* Temperament.swift */; };
B07867B51FEF4942007F2AE3 /* Temperament.swift in Sources */ = {isa = PBXBuildFile; fileRef = B07867B31FEF41BC007F2AE3 /* Temperament.swift */; };
B07867B91FEF5937007F2AE3 /* TemperamentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B07867B81FEF5937007F2AE3 /* TemperamentTests.swift */; };
B07867BA1FEF5971007F2AE3 /* TemperamentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B07867B81FEF5937007F2AE3 /* TemperamentTests.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand All @@ -112,7 +120,7 @@

/* Begin PBXFileReference section */
960F015D1AFFA6AA006974AE /* MIDIMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MIDIMessage.swift; sourceTree = "<group>"; };
960F21F61B08FEC70088C194 /* UnalteredExtendedChords.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; name = UnalteredExtendedChords.playground; path = ../Playgrounds/UnalteredExtendedChords.playground; sourceTree = "<group>"; };
960F21F61B08FEC70088C194 /* UnalteredExtendedChords.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; name = UnalteredExtendedChords.playground; path = ../Playgrounds/UnalteredExtendedChords.playground; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
960F21F71B08FF320088C194 /* ChordExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChordExtension.swift; sourceTree = "<group>"; };
9610685E1AF3390500BF9E91 /* HarmonicPalette.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HarmonicPalette.swift; sourceTree = "<group>"; };
9625D8081B03702100C4142E /* ChordQuality.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChordQuality.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -166,7 +174,11 @@
96F2993E1A56654800D7B006 /* MusicKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MusicKit.swift; sourceTree = "<group>"; };
96F299401A5665CA00D7B006 /* Pitch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Pitch.swift; sourceTree = "<group>"; };
96F299421A56661300D7B006 /* Scale.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Scale.swift; sourceTree = "<group>"; };
96F299441A5666D100D7B006 /* FrameworkOverview.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; name = FrameworkOverview.playground; path = Playgrounds/FrameworkOverview.playground; sourceTree = SOURCE_ROOT; };
96F299441A5666D100D7B006 /* FrameworkOverview.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; name = FrameworkOverview.playground; path = Playgrounds/FrameworkOverview.playground; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
B02B69561FF04FE900AC0862 /* Translation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Translation.swift; sourceTree = "<group>"; };
B02B69591FF077F600AC0862 /* TranslationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranslationTests.swift; sourceTree = "<group>"; };
B07867B31FEF41BC007F2AE3 /* Temperament.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Temperament.swift; sourceTree = "<group>"; };
B07867B81FEF5937007F2AE3 /* TemperamentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemperamentTests.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -235,6 +247,8 @@
969220C31AF1E75A00560A20 /* Chroma.swift */,
96BB1B111AF458A0005830DC /* LetterName.swift */,
96BB1B141AF458D7005830DC /* Accidental.swift */,
B07867B31FEF41BC007F2AE3 /* Temperament.swift */,
B02B69561FF04FE900AC0862 /* Translation.swift */,
);
name = Pitch;
sourceTree = "<group>";
Expand Down Expand Up @@ -283,6 +297,8 @@
96DFB62D1AF6C0BB005EEBD6 /* ChromaTests.swift */,
96DFB63D1AF70E42005EEBD6 /* LetterNameTests.swift */,
96DFB6431AF710F0005EEBD6 /* AccidentalTests.swift */,
B07867B81FEF5937007F2AE3 /* TemperamentTests.swift */,
B02B69591FF077F600AC0862 /* TranslationTests.swift */,
);
name = Pitch;
sourceTree = "<group>";
Expand Down Expand Up @@ -578,9 +594,11 @@
96DFB6501AF716F7005EEBD6 /* MKUtilTests.swift in Sources */,
96DFB62F1AF6C0BB005EEBD6 /* ChromaTests.swift in Sources */,
96DFB6481AF7151C005EEBD6 /* ScaleTests.swift in Sources */,
B02B695B1FF077F600AC0862 /* TranslationTests.swift in Sources */,
96EC06451AFB0EA20061CC42 /* ChordNameExtendedTests.swift in Sources */,
96DFB6451AF710F0005EEBD6 /* AccidentalTests.swift in Sources */,
9636C2EB1AF6A899005941D4 /* PitchSetTests.swift in Sources */,
B07867BA1FEF5971007F2AE3 /* TemperamentTests.swift in Sources */,
96EC06421AFAF9F20061CC42 /* ChordNameBasicTests.swift in Sources */,
96DFB64B1AF71540005EEBD6 /* ChordTests.swift in Sources */,
9691BB301A57C78800421B56 /* PitchTests.swift in Sources */,
Expand All @@ -602,11 +620,13 @@
96EC063F1AFADF130061CC42 /* ChordName.swift in Sources */,
969A02B31A5C13BA003EB476 /* Chord.swift in Sources */,
961068601AF3390500BF9E91 /* HarmonicPalette.swift in Sources */,
B02B69581FF04FE900AC0862 /* Translation.swift in Sources */,
9625D80A1B03702100C4142E /* ChordQuality.swift in Sources */,
96A06D511AFF281400029013 /* MIDI.swift in Sources */,
964A7F5B1A58A85B000E2D5A /* PitchSet.swift in Sources */,
9693F6771A57C44A006270F4 /* Pitch.swift in Sources */,
96CC5A831B056BFC00727C00 /* ScaleQuality.swift in Sources */,
B07867B51FEF4942007F2AE3 /* Temperament.swift in Sources */,
9662BFF21B058568008A053A /* IntervalQuality.swift in Sources */,
965331341AF1C20000EEDA02 /* HarmonicFunction.swift in Sources */,
96BB1B161AF458D7005830DC /* Accidental.swift in Sources */,
Expand Down Expand Up @@ -635,11 +655,13 @@
96EC063E1AFADF130061CC42 /* ChordName.swift in Sources */,
964A7F5A1A58A85B000E2D5A /* PitchSet.swift in Sources */,
96F299431A56661300D7B006 /* Scale.swift in Sources */,
B02B69571FF04FE900AC0862 /* Translation.swift in Sources */,
9625D8091B03702100C4142E /* ChordQuality.swift in Sources */,
96A06D501AFF281400029013 /* MIDI.swift in Sources */,
9610685F1AF3390500BF9E91 /* HarmonicPalette.swift in Sources */,
965331331AF1C20000EEDA02 /* HarmonicFunction.swift in Sources */,
96CC5A821B056BFC00727C00 /* ScaleQuality.swift in Sources */,
B07867B41FEF41BC007F2AE3 /* Temperament.swift in Sources */,
9662BFF11B058568008A053A /* IntervalQuality.swift in Sources */,
969220C41AF1E75A00560A20 /* Chroma.swift in Sources */,
96BB1B151AF458D7005830DC /* Accidental.swift in Sources */,
Expand All @@ -664,9 +686,11 @@
96DFB64F1AF716F7005EEBD6 /* MKUtilTests.swift in Sources */,
96DFB62E1AF6C0BB005EEBD6 /* ChromaTests.swift in Sources */,
96DFB6471AF7151C005EEBD6 /* ScaleTests.swift in Sources */,
B02B695A1FF077F600AC0862 /* TranslationTests.swift in Sources */,
96EC06441AFB0EA20061CC42 /* ChordNameExtendedTests.swift in Sources */,
96DFB6441AF710F0005EEBD6 /* AccidentalTests.swift in Sources */,
9636C2EA1AF6A899005941D4 /* PitchSetTests.swift in Sources */,
B07867B91FEF5937007F2AE3 /* TemperamentTests.swift in Sources */,
96EC06411AFAF9F20061CC42 /* ChordNameBasicTests.swift in Sources */,
96DFB64A1AF71540005EEBD6 /* ChordTests.swift in Sources */,
96686DB51A573B7F00350A2D /* PitchTests.swift in Sources */,
Expand Down
22 changes: 9 additions & 13 deletions MusicKit/ChordName.swift
Expand Up @@ -41,7 +41,6 @@ extension Chord {
if count < 2 || count != gamutCount { return nil }

let bass = pitchSet[0]
let bassChromaOpt = bass.chroma
// pitch set with bass removed
var bassRemoved = pitchSet
_ = bassRemoved.remove(bass)
Expand Down Expand Up @@ -89,9 +88,9 @@ extension Chord {
let noBassOpt = _descriptor(bassRemoved, qualities: slashQs)
var slashNoBassOpt: ChordDescriptor? = nil
if let noBass = noBassOpt {
slashNoBassOpt = bassChromaOpt.map {
ChordDescriptor(root: noBass.root, quality: noBass.quality, bass: $0)
}
slashNoBassOpt = ChordDescriptor(root: noBass.root,
quality: noBass.quality,
bass: bass.chroma)
}
return slashNoBassOpt ?? fullOpt
}
Expand All @@ -108,16 +107,15 @@ extension Chord {
}

let bass = pitchSet.first()!
let bassChromaOpt = bass.chroma

let indices = pitchSet.semitoneIndices()
// check root position chords
for quality in qualities {
let _indices = MKUtil.collapse(MKUtil.semitoneIndices(quality.intervals))
if _indices == indices {
return bassChromaOpt.map {
ChordDescriptor(root: $0, quality: quality, bass: $0)
}
return ChordDescriptor(root: bass.chroma,
quality: quality,
bass: bass.chroma)
}
}
// check inversions
Expand All @@ -126,11 +124,9 @@ extension Chord {
for i in 1..<count {
let inversion = MKUtil.zero(MKUtil.invert(_indices, n: UInt(i)))
if inversion == indices {
if let rootChroma = pitchSet[count - i].chroma {
return bassChromaOpt.map {
ChordDescriptor(root: rootChroma, quality: quality, bass: $0)
}
}
return ChordDescriptor(root: pitchSet[count - i].chroma,
quality: quality,
bass: bass.chroma)
}
}
}
Expand Down
6 changes: 6 additions & 0 deletions MusicKit/MusicKit.swift
Expand Up @@ -5,4 +5,10 @@ import Foundation
public struct MusicKit {
/// The global value of concert A
public static var concertA: Double = 440.0
/// The global temperament
public static var temperament = Temperament.equal
/// The global base midi note. Used to map frequency to
/// midi notes, usually 69 (A4 in midi standard, which
/// normally corresponds to 440.0Hz concert A)
public static var baseMidiNote = 69.0
}
50 changes: 24 additions & 26 deletions MusicKit/Pitch.swift
Expand Up @@ -5,21 +5,19 @@ import Foundation
/// The auditory attribute of sound according to which sounds can be ordered
/// on a scale from low to high (ANSI 1994)
public struct Pitch: Comparable {
/// midi number to frequency
/// TODO: move this to a temperament enum
public static func mtof(_ midi: Float) -> Float {
let exponent = Double((Float(midi) - 69.0)/12.0)
let freq = pow(Double(2), exponent)*MusicKit.concertA
return Float(freq)
}

public let midi: Float

/// Creates a `Pitch` with the given MIDI number.
/// Note that non-integral MIDI numbers are allowed.
public init(midi: Float) {
self.midi = midi
}

/// Creates a `Pitch` with the given frequency in hz.
/// Note that non-integral MIDI numbers might result.
public init(frequency: Float) {
self.midi = Float(MusicKit.temperament.ftom(Double(frequency)))
}

/// Creates a `Pitch` with the given chroma (pitch class) and octave.
public init(chroma: Chroma, octave: UInt) {
Expand All @@ -28,28 +26,28 @@ public struct Pitch: Comparable {

/// The frequency of the pitch in Hz
public var frequency: Float {
return Pitch.mtof(self.midi)
return Float(MusicKit.temperament.mtof(Double(self.midi)))
}

/// A `Chroma`, or nil if the pitch doesn't align with the chromas
/// in the current tuning system.
public var chroma: Chroma? {
if self.midi - floor(self.midi) == 0 {
return Chroma(rawValue: UInt(self.midi)%12)
}
return nil
/// A `Chroma` in the current tuning system.
public var chroma: Chroma {
return Chroma(rawValue: UInt(self.midi.rounded()) % 12)!
}

var noteNameTuple: (LetterName, Accidental, Int)? {
return chroma.flatMap {
$0.names.first.map {
noteNameWithOctave(octaveNumber, nameTuple: $0) } }

/// A value between -0.5 and 0.5 representing
/// the current deviation (e.g. for midi 60.5
/// deviation is 0.5)
public var deviation: Float {
let a = self.midi.truncatingRemainder(dividingBy: 1.0)
return a >= 0.5 ? a - 1 : a
}

public var isAligned: Bool {
return deviation == 0
}

public var noteName: String? {
return noteNameTuple.map {
"\($0.0.description)\($0.1.description(true))\($0.2)"
}
public var noteName: (LetterName, Accidental, Int) {
return noteNameWithOctave(octaveNumber, nameTuple: chroma.names.first!)
}

/// Unadjusted octave number
Expand All @@ -76,7 +74,7 @@ public struct Pitch: Comparable {
// MARK: Printable
extension Pitch: CustomStringConvertible {
public var description: String {
return (noteName != nil) ? "\(noteName!)" : "\(frequency)Hz"
return "\(noteName.0.description)\(noteName.1.description(true))\(noteName.2)"
}
}

Expand Down
23 changes: 8 additions & 15 deletions MusicKit/PitchSetExtensions.swift
Expand Up @@ -8,9 +8,7 @@ extension PitchSet {
public func gamut() -> Set<Chroma> {
var set = Set<Chroma>()
for pitch in self.contents {
if let chroma = pitch.chroma {
set.insert(chroma)
}
set.insert(pitch.chroma)
}
return set
}
Expand Down Expand Up @@ -82,19 +80,17 @@ extension PitchSet {
}

/// Removes duplicate chroma from the pitch set, starting from the root.
/// Note that pitches without chroma will be ignored.
/// Note that pitches with a deviation will be ignored.
public mutating func dedupe() {
var gamut = Set<Chroma>()
var pitchesToRemove = PitchSet()
for i in 0..<count {
let p = self[i]
if let chroma = p.chroma {
if gamut.contains(chroma) {
pitchesToRemove.insert(p)
}
else {
gamut.insert(chroma)
}
if gamut.contains(p.chroma) {
pitchesToRemove.insert(p)
}
else {
gamut.insert(p.chroma)
}
}
for pitch in pitchesToRemove {
Expand Down Expand Up @@ -159,11 +155,8 @@ public func /(lhs: PitchSet, rhs: Chroma) -> PitchSet {
}
var lhs = lhs
let firstPitch = lhs[0]
if firstPitch.chroma == nil {
return lhs
}
var newFirstPitch = firstPitch
while (newFirstPitch.chroma.map { $0 == rhs } != Optional(true)) {
while (newFirstPitch.chroma != rhs) {
newFirstPitch = newFirstPitch--
}
lhs.insert(newFirstPitch)
Expand Down

0 comments on commit c1bc4d2

Please sign in to comment.