Reputation: 369
I have a protocol ScaleDetails
, and I want several different Scale structs to conform (i.e. MajorScale
, DiminishedScale
, etc.). Then I want to have a different protocol, ComboChord
, which has a variable scale
of type ScaleDetails
, which would be a different scale depending on another variable elsewhere. The problem is I can't assign my scale
var in ComboChord
to type ScaleDetails
because I can't figure out how to write my protocol without using associated types.
Each different scale uses a different Mode enum (e.g. Mode.SevenDeg
for MajorScale
and Mode.TwoDeg
for DiminishedScale
). And each scale has functions that return an instance or instances of that specific scale struct. So I don't see a way forward without using generics in my ScaleDetails
protocol. I'm trying to write this without using classes if possible but I'm willing to use them if necessary. I'm also really trying to avoid typecasting for my scales because I don't want to have to list each scale possibility every time I call it. How can I fix this?
protocol ScaleDetails {
associatedtype ScaleMode where ScaleMode: RawRepresentable
associatedtype T
//...
var root: Root { get set }
var mode: ScaleMode { get set }
//...
init(_ root: KeyName.Root, mode: ScaleMode)
func translated(by offset: Int) -> T
func enharmSwapped() -> T
//...
mutating func switchMode(mode: ScaleMode)
func getParallelModes(root: KeyName.Root) -> [T]
func getParentModes() -> [T]
}
protocol ComboChord {
var scale: ScaleDetails // doesn't work
static var validCombos: [[Int]] { get }
var quality: Suffix { get }
var uprStrNotes: [Int] { get }
/*
error: "Protocol can only be used as a generic constraint
because it has Self or associatedType requirements"
*/
}
enum Mode {
enum TwoDeg: Int, CaseIterable, ModeProtocol {
case one = 0, two
//...
}
enum SevenDeg: Int, CaseIterable, ModeProtocol {
case one = 0, two, three, four, five, six, seven
//...
}
}
struct MajorScale: ScaleDetails {
var root: Root
var mode: Mode.SevenDeg
//...
init(_ root: KeyName.Root, mode: Mode.SevenDeg = .one) {
self.root = Root(root)
self.mode = mode
self.enharm = root.r.enharm
}
func translated(by offset: Int) -> MajorScale {
//...do things to the scale
return MajorScale(newRootKey, mode: mode)
}
//...
mutating func switchMode(mode: Mode.SevenDeg) {
self.mode = mode
}
//...
func enharmSwapped() -> MajorScale {
//...do things to the scale
return MajorScale(newRootKey, mode: mode)
}
func getParallelModes(root: KeyName.Root) -> [MajorScale] {
return Mode.SevenDeg.allCases.map {MajorScale(root, mode: $0)}
}
func getParentModes() -> [MajorScale] {
//...code to get array of scales
return parentModes
}
}
struct DiminishedScale: ScaleDetails {
var root: Root
var mode: Mode.TwoDeg
//...
init(_ root: KeyName.Root, mode: Mode.TwoDeg = .one) {
self.root = Root(root)
self.mode = mode
self.enharm = root.r.enharm
}
func translated(by offset: Int) -> DiminishedScale {
//...do things to the scale
return MajorScale(newRootKey, mode: mode)
}
//...
mutating func switchMode(mode: Mode.TwoDeg) {
self.mode = mode
}
//...
func enharmSwapped() -> DiminishedScale {
//...do things to the scale
return MajorScale(newRootKey, mode: mode)
}
func getParallelModes(root: KeyName.Root) -> [DiminishedScale] {
return Mode.TwoDeg.allCases.map {DiminishedScale(root, mode: $0)}
}
func getParentModes() -> [DiminishedScale] {
//...code to get array of scales
return parentModes
}
}
enum KeyName: String, CaseIterable {
case c = "C"
case d = "D"
case e = "E"
case f = "F"
case g = "G"
case a = "A"
case b = "B"
case cB = "C\u{266D}"
case dB = "D\u{266D}"
case eB = "E\u{266D}"
case fB = "F\u{266D}"
case gB = "G\u{266D}"
case aB = "A\u{266D}"
case bB = "B\u{266D}"
case cSh = "C\u{266F}"
case dSh = "D\u{266F}"
case eSh = "E\u{266F}"
case fSh = "F\u{266F}"
case gSh = "G\u{266F}"
case aSh = "A\u{266F}"
case bSh = "B\u{266F}"
case c_bb = "C\u{266D}\u{266D}"
case d_bb = "D\u{266D}\u{266D}"
case e_bb = "E\u{266D}\u{266D}"
case f_bb = "F\u{266D}\u{266D}"
case g_bb = "G\u{266D}\u{266D}"
case a_bb = "A\u{266D}\u{266D}"
case b_bb = "B\u{266D}\u{266D}"
case cX = "Cx"
case dX = "Dx"
case eX = "Ex"
case fX = "Fx"
case gX = "Gx"
case aX = "Ax"
case bX = "Bx"
enum Root {
case c, d, e, f, g, a, b
case cB, dB, eB, fB, gB, aB, bB
case cSh, dSh, eSh, fSh, gSh, aSh, bSh
var r: KeyName {
switch self {
case .c:
return .c
case .d:
return .d
case .e:
return .e
case .f:
return .f
case .g:
return .g
case .a:
return .a
case .b:
return .b
case .cB:
return .cB
case .dB:
return .dB
case .eB:
return .eB
case .fB:
return .fB
case .gB:
return .gB
case .aB:
return .aB
case .bB:
return .bB
case .cSh:
return .cSh
case .dSh:
return .dSh
case .eSh:
return .eSh
case .fSh:
return .fSh
case .gSh:
return .gSh
case .aSh:
return .aSh
case .bSh:
return .bSh
}
}
init(_ key: KeyName) {
switch key {
case .c:
self = .c
case .d:
self = .d
case .e:
self = .e
case .f:
self = .f
case .g:
self = .g
case .a:
self = .a
case .b:
self = .b
case .cB:
self = .cB
case .dB:
self = .dB
case .eB:
self = .eB
case .fB:
self = .fB
case .gB:
self = .gB
case .aB:
self = .aB
case .bB:
self = .bB
case .cSh:
self = .cSh
case .dSh:
self = .dSh
case .eSh:
self = .eSh
case .fSh:
self = .fSh
case .gSh:
self = .gSh
case .aSh:
self = .aSh
case .bSh:
self = .bSh
default:
self = .c
}
}
}
//...
}
struct Root: Note {
var noteName: String {
return key.name
}
var rootKey: KeyName.Root
var noteNum: NoteNum
var num: Int {return noteNum.num}
var enharm: Enharmonic
var degName: (name: DegName.Name, short: DegName.Short, long: DegName.Long) {
return (name: DegName.Name(.root), short: DegName.Short(.root), long: DegName.Long(.root))
}
var key: KeyName
init(noteNum: NoteNum = .zero, enharm: Enharmonic = .flat) {
self.noteNum = noteNum
self.enharm = enharm
var ks: KeySwitch {
return KeySwitch(enharm: enharm)
}
var rs: RootSwitch {
return RootSwitch(enharm: enharm)
}
switch noteNum {
case .zero:
self.key = ks.pickKey(.c, .bSh)
self.rootKey = rs.pickRoot(.c, .bSh)
case .one:
self.key = ks.pickKey(.dB, .cSh)
self.rootKey = rs.pickRoot(.dB, .cSh)
case .two:
self.key = .d
self.rootKey = .d
case .three:
self.key = ks.pickKey(.eB, .dSh)
self.rootKey = rs.pickRoot(.eB, .dSh)
case .four:
self.key = ks.pickKey(.fB, .e)
self.rootKey = rs.pickRoot(.fB, .e)
case .five:
self.key = ks.pickKey(.f, .eSh)
self.rootKey = rs.pickRoot(.f, .eSh)
case .six:
self.key = ks.pickKey(.gB, .fSh)
self.rootKey = rs.pickRoot(.gB, .fSh)
case .seven:
self.key = .g
self.rootKey = .g
case .eight:
self.key = ks.pickKey(.aB, .gSh)
self.rootKey = rs.pickRoot(.aB, .gSh)
case .nine:
self.key = .a
self.rootKey = .a
case .ten:
self.key = ks.pickKey(.bB, .aSh)
self.rootKey = rs.pickRoot(.bB, .aSh)
case .eleven:
self.key = ks.pickKey(.cB, .b)
self.rootKey = rs.pickRoot(.cB, .b)
}
}
init(_ key: KeyName.Root) {
self.key = key.r
self.rootKey = key
self.noteNum = self.key.noteNum
self.enharm = self.key.enharm
}
mutating func kSW(ks: KeySwitch) {
switch noteNum {
case .zero:
self.key = ks.pickKey(.c, .bSh)
case .one:
self.key = ks.pickKey(.dB, .cSh)
case .two:
self.key = .d
case .three:
self.key = ks.pickKey(.eB, .dSh)
case .four:
self.key = ks.pickKey(.fB, .e)
case .five:
self.key = ks.pickKey(.f, .eSh)
case .six:
self.key = ks.pickKey(.gB, .fSh)
case .seven:
self.key = .g
case .eight:
self.key = ks.pickKey(.aB, .gSh)
case .nine:
self.key = .a
case .ten:
self.key = ks.pickKey(.bB, .aSh)
case .eleven:
self.key = ks.pickKey(.cB, .b)
}
}
mutating func swapEnharm() {
enharm = enharm == .flat ? .sharp : .flat
kSW(ks: KeySwitch(enharm: enharm))
rootKey = KeyName.Root(key)
}
func enharmSwapped() -> Root {
return Root(noteNum: noteNum, enharm: enharm == .flat ? .sharp : .flat)
}
}
(heavily pared down for length...original is about 1400 lines)
struct DetailChord {
enum DCType {
case dom7, ma7, mi7, mi7b5, dim7, error
}
init(resultChord: ResultChord?, verdict: Verdict = .goodToGo) {
self.verdict = verdict
self.dRC = resultChord
if let rc = dRC {
switch rc.baseChord.lowerQual {
case .dom7:
dct = .dom7
case .ma7:
dct = .ma7
case .mi7:
dct = .mi7
case .mi7_b5:
dct = .mi7b5
case .dim7:
dct = .dim7
}
} else {
dct = .error
}
}
var dct: DCType
var dRC: ResultChord?
var verdict: Verdict
var validCombos: [[[Int]]] {
switch dct {
case .dom7:
return Dom7.validCombos
case .ma7:
return Maj7.validCombos
case .mi7:
return Min7.validCombos
case .mi7b5:
return Min7_b5.validCombos
case .dim7:
return Dim7.validCombos
default:
return [[[]]]
}
}
var tension: [[Int]] {
switch dct {
case .dom7:
return Dom7.tension
case .ma7:
return Maj7.tension
case .mi7:
return Min7.tension
case .mi7b5:
return Min7_b5.tension
case .dim7:
return Dim7.tension
default:
return [[]]
}
}
func qualSwitcher(validCombos: [[[Int]]], degrees: [Int]) -> Int {
var validCombosIndex = Int()
for (index, combos) in validCombos.enumerated() {
if combos.contains(degrees) {
validCombosIndex = index
break
}
}
return validCombosIndex
}
var chord: ComboChord {
mutating get {
if let rc = dRC {
let degrees = rc.degrees
let degreesUnsorted = rc.degreesUnsorted
let baseChord = rc.baseChord
let degSet = rc.degSet
var validCombosIndex = Int()
func tensionOrGoodToGo(getComboChord: GetComboChord) -> ComboChord {
if !tension.contains(degrees) { // good to go!
verdict = .goodToGo
validCombosIndex = qualSwitcher(validCombos: validCombos, degrees: degrees)
return getComboChord(degSet, validCombosIndex)
} else { // tension!
verdict = .tension
if let tensionQual = rc.baseChord.qualSuffix as? Suffix.Lower {
return TensionChord(tensionQual, rc.degreesUnsorted)
} else {
return ErrorChord()
}
}
}
switch dct {
case .dom7:
return tensionOrGoodToGo(getComboChord: Dom7.getComboChord(degSet:validCombosIndex:))
case .ma7:
return tensionOrGoodToGo(getComboChord: Maj7.getComboChord(degSet:validCombosIndex:))
case .mi7:
return tensionOrGoodToGo(getComboChord: Min7.getComboChord(degSet:validCombosIndex:))
case .mi7b5:
return tensionOrGoodToGo(getComboChord: Min7_b5.getComboChord(degSet:validCombosIndex:))
case .dim7:
return tensionOrGoodToGo(getComboChord: Dim7.getComboChord(degSet:validCombosIndex:))
case .error:
return ErrorChord()
}
} else {
print("rc not found!")
dct = .error
return ErrorChord()
}
}
}
struct Dom7: DetailChordType {
static let matches = Mixolydian.validCombos + LydianDom.validCombos + HalfWholeDim.validCombos + AltP5.validCombos + MelodicMinor_Chord.validCombos + HexatonicSh9Sh11_Chord.validCombos + HalfWholeDimFlat13_Chord.validCombos
static let validCombos: [[[Int]]] = [Mixolydian.validCombos, LydianDom.validCombos, HalfWholeDim.validCombos, AltP5.validCombos, MelodicMinor_Chord.validCombos, HexatonicSh9Sh11_Chord.validCombos, HalfWholeDimFlat13_Chord.validCombos]
static let tension = [[0, 1, 4, 5, 7, 8, 10], [0, 4, 7, 8, 10, 11], [0, 4, 5, 7, 9, 10], [0, 2, 4, 7, 10, 11], [0, 2, 4, 5, 7, 10], [0, 3, 4, 6, 7, 10, 11], [0, 2, 4, 5, 7, 9, 10], [0, 4, 7, 10, 11], [0, 4, 5, 7, 8, 10], [0, 3, 4, 7, 8, 10, 11], [0, 1, 4, 5, 7, 10], [0, 2, 4, 6, 7, 10, 11], [0, 1, 4, 5, 7, 9, 10], [0, 3, 4, 7, 10, 11], [0, 2, 4, 5, 7, 8, 10], [0, 4, 5, 7, 8, 10, 11], [0, 2, 4, 7, 8, 10, 11], [0, 2, 4, 5, 7, 10, 11], [0, 3, 4, 5, 7, 10], [0, 4, 5, 7, 10], [0, 4, 6, 7, 10, 11], [0, 4, 7, 9, 10, 11], [0, 1, 4, 6, 7, 10, 11]]
static let tensionTones = [5, 11]
static func getComboChord(degSet: Set<Int>, validCombosIndex: Int) -> ComboChord {
switch validCombosIndex {
case 0: // mixolydian
switch degSet {
case let degs where degs.isSuperset(of: Dom7.Mixolydian.XIII.uprStrNotes): // 13
return Dom7.Mixolydian.xiii
// sus4/sus2 on 2nd, sus2 on 5th, min or sus 4 triad on maj6
case let degs where degs.isSuperset(of: Dom7.Mixolydian.Nine.uprStrNotes): // 9
// sus2 on root, min or sus4 triad on p5
return Dom7.Mixolydian.nine
default: // 7
//maj triad on root, dim triad on 3rd
return Dom7.Mixolydian.seven
}
case 1: // lydian dominant
// similar structure to case 0
case 2: // half-whole dim
// similar structure to case 0
case 3:
// similar structure to case 0
case 4: // 5th mode melodic minor
// similar structure to case 0
case 5: // hexatonicSh9Sh11
// similar structure to case 0
default: // half-whole dim(b13)
// similar structure to case 0
}
}
var chordType: ComboChord = Mixolydian.seven
enum Mixolydian: ComboChord {
case seven, nine, xiii
var scale: ScaleDetails {
switch self {
default:
return Major(mode: .five, modeOffset: 0)
}
}
static let validCombos: [[Int]] = Seven.validCombos + Nine.validCombos + XIII.validCombos
struct Seven {
static let validCombos = [[0, 4, 7, 10]]
static let uprStrNotes: [Int] = []
}
struct Nine {
static let validCombos: [[Int]] = [[0, 2, 4, 7, 10]]
static let uprStrNotes: [Int] = [2]
}
struct XIII {
static let validCombos: [[Int]] = [[0, 2, 4, 7, 9, 10], [0, 4, 7, 9, 10]]
static let uprStrNotes: [Int] = [9]
}
var quality: Suffix {
switch self {
case .seven:
return .sev
case .nine:
return .nine
case .xiii:
return .xiii
}
}
var uprStrNotes: [Int] {
switch self {
case .seven:
return Seven.uprStrNotes
case .nine:
return Nine.uprStrNotes
case .xiii:
return XIII.uprStrNotes
}
}
}
enum LydianDom: ComboChord {
// same structure as Mixolydian
}
enum HalfWholeDim: ComboChord {
// same structure as Mixolydian
}
enum AltP5: ComboChord {
// same structure as Mixolydian
}
enum HexatonicSh9Sh11_Chord: ComboChord {
// same structure as Mixolydian
}
enum MelodicMinor_Chord: ComboChord {
// same structure as Mixolydian
}
enum HalfWholeDimFlat13_Chord: ComboChord {
// same structure as Mixolydian
}
}
struct Maj7: DetailChordType {
// same structure as Dom7
}
struct Min7: DetailChordType {
// same structure as Dom7
}
struct Min7_b5: DetailChordType {
// same structure as Dom7
}
struct Dim7: DetailChordType {
// same structure as Dom7
}
}
Upvotes: 0
Views: 40
Reputation: 4226
Not sure if it will fit your usage, but maybe you can make ComboChord
generic as well
protocol ComboChord {
associatedtype Scale: ScaleDetails
var scale: Scale { get }
}
And then use it like this
struct Chord: ComboChord {
var scale: DiminishedScale
}
Upvotes: 1