0

I want to index values returned from a function using a tuple containing an Int and a Double. The function works fine returning Double but when I read a tuple I get this error:

'flatMap' produces '[SegmentOfResult.Iterator.Element]', not the expected contextual result type '(pitchClass: Int, frequency: Double)'

I'm puzzled why .flatMap returns Double but not tuple. This link to flatMap(_:) makes sense for arrays but I can't see a connection with tuples that sheds light on this problem.

What is the best way to solve this ?

CLARIFICATION

As requested

tuning is a string array with fractions and decimals

    // test pitches: rational fractions and decimal numbers   
    let tuning = ["1/1", "200.0", "5/4", "500.0", "600.0", "700.0", "1000.0", "2/1"]

scaleFrequencies is a function that processes both number types and returns Double (i.e. frequency).

I have included code initialisation.

import UIKit

class Tuner {

        var tuning                  = [String]()
        let tonic: Double           = 261.626   // frequency of middle C
        var index                   = -1
        let centsPerOctave: Double  = 1200.0    // mandated by Scala tuning file format
        let formalOctave: Double    = 2.0       // Double for stretched-octave tunings

init(tuning: [String]) {
        self.tuning                 = tuning

        var notes: (pitchClass: Int, frequency: Double)
        notes = tuning.flatMap(scaleFrequencies)

     // print(frequency)

    }



func pitchClass(pitchClass: Int, _ frequency: Double) -> Int {
    return pitchClass
    }

func frequency(pitchClass: Int, _ frequency: Double) -> Double {
    return frequency
    }



func scaleFrequencies(s: String?) -> (Int, Double) {

    index                  += 1
    var frequency: Double  = 0.0        
    frequency              = processNumerics(numericString: numericString)

    return (index, frequency)
}
Greg
  • 1,750
  • 2
  • 29
  • 56
  • 4
    What is `tuning`? What is `scaleFrequencies`? Why do you expect `tuning.flatMap(scaleFrequencies)` to produce a tuple which can be assigned to `notes`? – jtbandes Jan 03 '17 at 07:01
  • Did you mean to pass `tuning` to your function, as in `scaleFrequencies(tuning)`? – jtbandes Jan 03 '17 at 07:37
  • each element of `tuning` is passed in turn to `scaleFrequencies.` From there it is passed to another function `processNumerics` where optionals are unwrapped (using guard and nil coalescing i.e. no forced unwrapping) - and `frequency` is calculated and returned – Greg Jan 03 '17 at 08:10
  • You could do something like: `notes = tuning.flatMap { scaleFrequencies($0) }`, but that returns a `[(Int, Double)]`, not a `(pitchClass: Int, frequency: Double)` (note, it's an array and no labels). – Rob Jan 03 '17 at 16:56
  • @Rob, I’ve given a more comprehensive answer that you may find useful – Greg Jan 05 '17 at 23:40
  • @jtbandes, I’ve given a more comprehensive answer that you may find useful – Greg Jan 05 '17 at 23:41

1 Answers1

0

The function currently works fine returning Double and already gives the variable I need, namely frequency. It is better not to change it. I will create a new class to add further functionality (i.e. mapping frequencies to keys of a MIDI keyboard). Thank you Rob and jtbandes for your input.

EDIT

It was more sensible to solve this without broadening the problem space. I found my solution once I questioned the need to return an argument to the function and asked instead how data could be presented from inside the function using only the available arguments. The solution addresses a related problem as identified by the accepted answer to another post and unwraps optional values without causing runtime errors as recommended on several other posts (here, here and here.)

Values are unwrapped using optional chaining and nil coalescing instead of forced unwrapping. Numbers that are valid according to these rules are converted to frequencies and mapped to a MIDI keyboard. Invalid tuning values and note strings marked with an ‘x’ (as specified by these rules) will generate a frequency of 0.0 Hz.

The solution allows Optional scale values that are read by the function to be compared more easily with frequencies returned as other functions are called.

e.g.

    Octave map

    0 Optional("x")
    0 : 0.0
    1 Optional("35/32")
    1 : 286.1534375
    2 Optional("x")
    2 : 0.0
    3 Optional("x")
    3 : 0.0
    4 Optional("5/4")
    4 : 327.0325
    5 Optional("21/16")
    5 : 343.384125
    6 Optional("x")
    6 : 0.0
    7 Optional("3/2")
    7 : 392.439
    8 Optional("x")
    8 : 0.0
    9 Optional("x")
    9 : 0.0
    10 Optional("7/4")
    10 : 457.8455
    11 Optional("15/8")
    11 : 490.54875

It also helps with reading frequencies in other octaves

    MIDI map

    Octave 0
    0 0 0.0
    1 1 8.942294921875
    2 2 0.0
    3 3 0.0
    4 4 10.219765625
    5 5 10.73075390625
    6 6 0.0
    7 7 12.26371875
    8 8 0.0
    9 9 0.0
    10 10 14.307671875
    11 11 15.3296484375

    Octave 1
    12 0 0.0
    13 1 17.88458984375
    14 2 0.0
    15 3 0.0
    16 4 20.43953125
    17 5 21.4615078125
    18 6 0.0
    19 7 24.5274375
    20 8 0.0
    21 9 0.0
    22 10 28.61534375
    23 11 30.659296875

    Octave 2
    24 0 0.0
    25 1 35.7691796875
    26 2 0.0

    etc

    Octave 9
    108 0 0.0
    109 1 4578.455
    110 2 0.0
    111 3 0.0
    112 4 5232.52
    113 5 5494.146
    114 6 0.0
    115 7 6279.024
    116 8 0.0
    117 9 0.0
    118 10 7325.528
    119 11 7848.78

    Octave 10
    120 0 0.0
    121 1 9156.91
    122 2 0.0
    123 3 0.0
    124 4 10465.04
    125 5 10988.292
    126 6 0.0

Developers of music apps may find this useful because it shows how to create a retuned MIDI map. The solution lets me unwrap numeric strings consisting of both fractions and decimals that specify the tuning of notes in musical scales that lie beyond the scope of a standard music keyboard. Its significance will not be lost on anyone who visits this site.

Here is the code

Tuner.swift

import UIKit

class Tuner         {

    var tuning                      = [String]()    // .scl
    var pitchClassFrequency         = Double()      // .scl
    let centsPerOctave: Double      = 1200.0        // .scl mandated by Scala tuning file format

    let formalOctave: Double        = 2.0           // .kbm/.scl Double for stretched-octave tunings
    var octaveMap                   = [Double]()    // .kbm/.scl
    var midiMap                     = [Double]()    // .kbm

    let sizeOfMap                   = 12            // .kbm
    let firstMIDIKey                = 0             // .kbm
    let lastMIDIKey                 = 127           // .kbm

    let referenceMIDIKey            = 60            // .kbm
    let referenceFrequency: Double  = 261.626       // .kbm frequency of middle C

    var indexMIDIKeys               = Int()
    var indexOctaveKeys             = Int()
    var currentKeyOctave            = Int()
    var index: Int                  = 0


init(tuning: [String]) {
    self.tuning                 = tuning

// SCL file format - create frequency map of notes for one octave
    print("Octave map")
    print("")
    let _                           = tuning.flatMap(scaleToFrequencies)

// KBM file format - create frequency map of MIDI keys 0-127
    print("")
    print("MIDI map")
    let _                           = createMIDIMap()

    }


func createMIDIMap()                                            {

    indexOctaveKeys             = firstMIDIKey   //  set indexOctaveKeys to pitchClass 0
    currentKeyOctave            = firstMIDIKey   //  set currentOctave to octave 0

    for indexMIDIKeys in firstMIDIKey...lastMIDIKey {
        let indexOctaveKeys     =      indexMIDIKeys  % sizeOfMap

        currentKeyOctave        = Int((indexMIDIKeys) / sizeOfMap)
        let frequency           = octaveMap[indexOctaveKeys] * 2**Double(currentKeyOctave)

        //        midiMap[i]              = octaveMap[indexMIDIKeys] * 2**Double(currentKeyOctave)
        if indexOctaveKeys == 0 {
            print("")
            print("Octave \(currentKeyOctave)")
        }
        print(indexMIDIKeys, indexOctaveKeys, frequency)
        }
    }


func scaleToFrequencies(s: String?)                             {

    var frequency: Double       = 0.0

    //  first process non-numerics.
    let numericString           = zapAllButNumbersSlashDotAndX(s: s)

    print(index, numericString as Any)        // eavesdrop on String?

    //  then process numerics.
    frequency                   = (processNumericsAndMap(numericString: numericString)) / Double(2)**Double(referenceMIDIKey / sizeOfMap)
    octaveMap.append(frequency)
    print(index,":",frequency * 2**Double(referenceMIDIKey / sizeOfMap))
    index += 1
    }


func processNumericsAndMap(numericString: String?) -> Double    {
    guard let slashToken    = ((numericString?.contains("/")) ?? nil),
        let dotToken        = ((numericString?.contains(".")) ?? nil),
        let xToken          = ((numericString?.contains("x")) ?? nil),
        slashToken          == false,
        dotToken            == true,
        xToken              == false
        else {
            guard let dotToken = ((numericString?.contains(".")) ?? nil),
                let xToken     = ((numericString?.contains("x")) ?? nil),
                dotToken       == false,
                xToken         == false
                else {
                    guard let xToken    = ((numericString?.contains("x")) ?? nil),
                        xToken          == false
                        else {
                            //  then it must be mapping.
                            let frequency   = 0.0
                            //                      print("[x] \(frequency) Hz")
                            return frequency
                    }
                    //  then process integer.
                    let frequency   = processInteger(s: numericString)
                    return frequency
            }
            //  then process fractional.
            let frequency           = processFractional(s: numericString)
            return frequency
        }
    //  process decimal.
    let frequency                   = processDecimal(s: numericString)
    return frequency
    }


func processFractional(s: String?) -> Double                    {
    let parts = s?.components(separatedBy: "/")
    guard parts?.count      == 2,
        let numerator       = Double((parts?[0])?.digits ?? "failNumerator"),
        let dividend        = Double((parts?[1])?.digits ?? "failDenominator"),
        dividend            != 0
        else {
            let frequency   = 0.0
            print("invalid ratio: frequency now being set to \(frequency) Hz")
            return frequency
        }
    let frequency           = referenceFrequency * (numerator / dividend)
    return frequency
    }


func processDecimal(s: String?) -> Double                       {

    let parts = s?.components(separatedBy: ".")
    guard parts?.count      == 2,
        let intervalValue   = Double(s ?? "failInterval"),
        let _               = Double((parts?[0])?.digits ?? "failDecimal")
        else {
            let frequency   = 0.0
            print("invalid cents value: frequency now being forced to \(frequency) Hz ")
            return frequency
         }
    let power               = intervalValue/centsPerOctave                      // value with explicit remainder
    let frequency           = referenceFrequency * (formalOctave**power)
    return frequency
    }


func processInteger(s: String?) -> Double                       {
    let frequency           = 0.0
    print("not cents, not ratio : frequency now being set to \(frequency) Hz ")
    return frequency
    }


func zapAllButNumbersSlashDotAndX(s: String?) -> String?        {

    var mixedString = s
    if mixedString != nil {
        mixedString = mixedString!
        }
        guard var _ = mixedString?.contains("/"),
        var _   = mixedString?.contains(".")
        else {
            let numberToken = mixedString
            return numberToken
            }
            guard let xToken = mixedString?.contains("x"),
                xToken == false
                else {
                    let xToken = "x"
                    return xToken
        }
    let notNumberCharacters = NSCharacterSet.decimalDigits.inverted
    let numericString = s?.trimmingCharacters(in: notNumberCharacters) ?? "orElse"
    return numericString.stringByRemovingWhitespaces
    }

}


extension String    {
var stringByRemovingWhitespaces: String {
    return components(separatedBy: .whitespaces).joined(separator: "")
    }
}


extension String    {

var digits: String {
    return components(separatedBy: CharacterSet.decimalDigits.inverted).joined()
    }
}


precedencegroup Exponentiative {

    associativity: left
    higherThan: MultiplicationPrecedence

}


infix operator ** : Exponentiative


func ** (num: Double, power: Double) -> Double                      {
return pow(num, power)
}


func pitchClass(pitchClass: Int, _ frequency: Double) -> Int        {
    return pitchClass
}


func frequency(pitchClass: Int, _ frequency: Double) -> Double      {
    return frequency
}

ViewController.swift

import UIKit

class ViewController: UIViewController {

 // Hexany: 6-note scale of Erv Wilson

        let tuning = ["x", "35/32", "x", "x", "5/4", "21/16", "x", "3/2", "x", "x", "7/4", "15/8"]


 // Diatonic scale: rational fractions
 //       let tuning = [ "1/1", "9/8", "5/4", "4/3", "3/2", "27/16", "15/8", "2/1"]

 // Mohajira: rational fractions
 //    let tuning = [ "21/20", "9/8", "6/5", "49/40", "4/3", "7/5", "3/2", "8/5", "49/30", "9/5", "11/6", "2/1"]

 // Diatonic scale: 12-tET
 //    let tuning = [ "0.0", "200.0", "400.0", "500", "700.0", "900.0", "1100.0", "1200.0"]


override func viewDidLoad() {
    super.viewDidLoad()

    _ = Tuner(tuning: tuning)


    }
}
Community
  • 1
  • 1
Greg
  • 1,750
  • 2
  • 29
  • 56
  • The original answer was voted down possibly because it was incomplete. Hopefully my edit addresses this. – Greg Jan 05 '17 at 23:32