Jacob Smolowe
Jacob Smolowe

Reputation: 369

Any way to write my protocol without using generics, or otherwise workaround using associated types?

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?

ScaleDetails Protocol

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]
}

ComboChord Protocol

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"
*/
}

Mode enums

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
        
        //...
    }
}

Major Scale

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
    }
}

DiminishedScale

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
    }
}

KeyName.Root

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
            }
        }
    }
    //...
}

Root

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)
    }
}

DetailChord

(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

Answers (1)

Rico Crescenzio
Rico Crescenzio

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

Related Questions