diff --git a/Sources/Tonic/Accidental.swift b/Sources/Tonic/Accidental.swift index 26e31cd..d93e0ca 100644 --- a/Sources/Tonic/Accidental.swift +++ b/Sources/Tonic/Accidental.swift @@ -5,7 +5,7 @@ import Foundation /// A way to describe modification to a ``Note`` or ``NoteClass`` /// /// A semitone offset applied to a note that does not change the letter of the note, just the pitch. -public enum Accidental: Int8, CaseIterable, Equatable, Hashable { +public enum Accidental: Int8, CaseIterable, Equatable, Hashable, Codable { static var count: Int { Accidental.allCases.count } static var naturalIndex: Int { count / 2 } diff --git a/Sources/Tonic/BitSet.swift b/Sources/Tonic/BitSet.swift index afe2239..64e8817 100644 --- a/Sources/Tonic/BitSet.swift +++ b/Sources/Tonic/BitSet.swift @@ -3,7 +3,7 @@ import Foundation /// Interface to bit sets used to represent sets of pitches and sets of notes. -public protocol BitSet: Hashable { +public protocol BitSet: Hashable, Codable { init() func isSet(bit: Int) -> Bool mutating func add(bit: Int) @@ -195,7 +195,7 @@ public protocol IntRepresentable { var intValue: Int { get } } -public struct BitSetAdapter: Hashable { +public struct BitSetAdapter: Hashable, Codable { public var bits: B public init() { diff --git a/Sources/Tonic/Chord.swift b/Sources/Tonic/Chord.swift index edc9f81..6cc397d 100644 --- a/Sources/Tonic/Chord.swift +++ b/Sources/Tonic/Chord.swift @@ -6,7 +6,7 @@ import Foundation /// /// A representation of a chord as a set of note classes, with a root note class, /// and an inversion defined by the lowest note in the chord. -public struct Chord: Equatable { +public struct Chord: Equatable, Codable { /// Root note class of the chord public let root: NoteClass diff --git a/Sources/Tonic/ChordType.swift b/Sources/Tonic/ChordType.swift index a54682f..ab51f55 100644 --- a/Sources/Tonic/ChordType.swift +++ b/Sources/Tonic/ChordType.swift @@ -3,7 +3,8 @@ import Foundation /// Chord type as defined by a set of intervals from a root note class -public enum ChordType: String, CaseIterable { +public enum ChordType: String, CaseIterable, Codable { + /// Major Triad: Major Third, Perfect Fifth case majorTriad diff --git a/Sources/Tonic/Interval.swift b/Sources/Tonic/Interval.swift index d09f7f3..276e290 100644 --- a/Sources/Tonic/Interval.swift +++ b/Sources/Tonic/Interval.swift @@ -7,7 +7,7 @@ import Foundation /// An interval distance is measured in degree (number of letters away) and /// quality of the interval (essentialy number of semitones away). /// Some Intervals refer to the same difference in pitch. -public enum Interval: Int, CaseIterable { +public enum Interval: Int, CaseIterable, Codable { /// Perfect Unison case P1 diff --git a/Sources/Tonic/Key.swift b/Sources/Tonic/Key.swift index 7ccca44..85abed0 100644 --- a/Sources/Tonic/Key.swift +++ b/Sources/Tonic/Key.swift @@ -5,7 +5,7 @@ import Foundation /// The key is the set of notes that are played in a composition, or portion of a composition. /// /// A key is composed of a Root ``Note``, and a ``Scale``. -public struct Key: Equatable { +public struct Key: Equatable, Codable { /// The primary note class of the key, also known as the tonic public let root: NoteClass diff --git a/Sources/Tonic/Letter.swift b/Sources/Tonic/Letter.swift index 98155cf..4bb183f 100644 --- a/Sources/Tonic/Letter.swift +++ b/Sources/Tonic/Letter.swift @@ -6,7 +6,7 @@ import Foundation /// /// These letters can be modified by adding an ``Accidental`` to describe any ``NoteClass``. /// And by specificying an octave, you can create any ``Note``. -public enum Letter: Int, CaseIterable, Equatable, Hashable { +public enum Letter: Int, CaseIterable, Equatable, Hashable, Codable { case C, D, E, F, G, A, B var baseNote: UInt8 { diff --git a/Sources/Tonic/Note.swift b/Sources/Tonic/Note.swift index 32cde05..28fd4d4 100644 --- a/Sources/Tonic/Note.swift +++ b/Sources/Tonic/Note.swift @@ -3,7 +3,7 @@ import Foundation /// A pitch with a particular spelling. -public struct Note: Equatable, Hashable { +public struct Note: Equatable, Hashable, Codable { /// Base name for the note public var noteClass: NoteClass = .init(.C, accidental: .natural) diff --git a/Sources/Tonic/NoteClass.swift b/Sources/Tonic/NoteClass.swift index 9715eac..5ff1bba 100644 --- a/Sources/Tonic/NoteClass.swift +++ b/Sources/Tonic/NoteClass.swift @@ -5,7 +5,7 @@ import Foundation public typealias NoteClassSet = BitSetAdapter /// A note letter and accidental which spell a note. This leaves out the octave of the note. -public struct NoteClass: Equatable, Hashable { +public struct NoteClass: Equatable, Hashable, Codable { /// Letter of the note class public var letter: Letter diff --git a/Sources/Tonic/Pitch.swift b/Sources/Tonic/Pitch.swift index b329964..a528444 100644 --- a/Sources/Tonic/Pitch.swift +++ b/Sources/Tonic/Pitch.swift @@ -27,7 +27,7 @@ public extension PitchSet { /// /// We want to use a notion of pitch that lends itself to combinatorial algorithms, /// as opposed to using e.g. a fundamental frequency. -public struct Pitch: Equatable, Hashable { +public struct Pitch: Equatable, Hashable, Codable { /// MIDI Note Number 0-127 public var midiNoteNumber: Int8 diff --git a/Sources/Tonic/Scale.swift b/Sources/Tonic/Scale.swift index 831a95d..1084dd7 100644 --- a/Sources/Tonic/Scale.swift +++ b/Sources/Tonic/Scale.swift @@ -32,3 +32,25 @@ public struct Scale: OptionSet, Hashable { rawValue = r } } + +/// Automatic synthesis of Codable would use OptionSets RawRepresentable Conformance to de- and encode objects. +/// Unfortunatly this will lead to the loss of the "description" property. That's why we decided to create explicit codable support. +extension Scale: Codable { + private enum CodingKeys: String, CodingKey { + case intervals + case description + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let intervals = try container.decode([Interval].self, forKey: .intervals) + let description = try container.decode(String.self, forKey: .description) + self = .init(intervals: intervals, description: description) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(intervals, forKey: .intervals) + try container.encode(description, forKey: .description) + } +}