3

I am currently using map property with a closure in Swift to extract linear factors from an array and calculate a list of musical frequencies spanning one octave.

    let tonic: Double   = 261.626 // middle C
    let factors         = [  1.0,   1.125, 1.25,  1.333, 1.5,   1.625,   1.875]

    let frequencies     = factors.map { $0 * tonic }
    print(frequencies)

    // [261.62599999999998, 294.32925, 327.03249999999997, 348.74745799999994, 392.43899999999996, 425.14224999999999, 490.54874999999993]

I want to do this by making the closure extract two integers from a string and divide them to form each factor. The string comes from an SCL tuning file and might look something like this:

    //                       C      D      E      F      G      A        B 

    let ratios          = [ "1/1", "9/8", "5/4", "4/3", "3/2", "27/16", "15/8"]

Can this be done ?

SOLUTION

Thankfully, yes it can. In three Swift statements tuning ratios represented as fractions since before Ptolemy can be coverted into precise frequencies. A slight modification to the accepted answer makes it possible to derive the list of frequencies. Here is the code

import UIKit

class ViewController: UIViewController {

// Diatonic scale
let ratios = [ "1/1", "9/8", "5/4", "4/3", "3/2", "27/16", "15/8"]

// Mohajira scale
// let ratios = [ "21/20", "9/8", "6/5", "49/40", "4/3", "7/5", "3/2", "8/5", "49/30", "9/5", "11/6", "2/1"]


override func viewDidLoad() {
    super.viewDidLoad()

    _ = Tuning(ratios: ratios)

    }
}

Tuning Class

import UIKit

class Tuning {

    let tonic   = 261.626       // frequency of middle C (in Hertz)

    var ratios  = [String]()

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

    let frequencies = ratios.map { s -> Double in
        let integers = s.characters.split(separator: "/").map(String.init).map({ Double($0) })
        return (integers[0]!/integers[1]!) * tonic
    }

    print("// \(frequencies)")

    }
}

And here is the list of frequencies in Hertz corresponding to notes of the diatonic scale

     C           D           E           F           G           A           B     
    [261.626007, 294.329254, 327.032501, 348.834686, 392.439026, 441.493896, 490.548767]

It works for other scales with pitches not usually found on a black-and-white-note music keyboard Mohajira scale created by Jacques Dudon

    //                     D                      F             G                                     C'
  let ratios = [ "21/20", "9/8", "6/5", "49/40", "4/3", "7/5", "3/2", "8/5", "49/30", "9/5", "11/6", "2/1"]

And here is a list of frequencies produced

    //                      D                                         F                                       G                                                                                                   C'
    // [274.70729999999998, 294.32925, 313.95119999999997, 320.49185, 348.83466666666664, 366.27639999999997, 392.43899999999996, 418.60159999999996, 427.32246666666663, 470.92679999999996, 479.64766666666662, 523.25199999999995]

Disclaimer

Currently the closure only handles rational scales. To fully comply with Scala SCL format it must also be able to distinguish between strings with fractions and strings with a decimal point and interpret the latter using cents, i.e. logarithmic rather than linear factors.

Thank you KangKang Adrian and Atem

Community
  • 1
  • 1
Greg
  • 1,750
  • 2
  • 29
  • 56

3 Answers3

2
let ratios = [ "1/1", "9/8", "5/4", "4/3", "3/2", "27/16", "15/8"]

let factors = ratios.map { s -> Float in
    let integers = s.characters.split(separator: "/").map(String.init).map({ Float($0) })
    return integers[0]!/integers[1]!
}
halftrue
  • 149
  • 2
  • 7
  • Worked first time! Changing `Float` to `Double` and multiplying by `tonic` (see *first statement in my code*) printed a list of frequencies. This has to be there accepted answer. Thank you & Merry Christmas, to you, Artem and Adrian – Greg Dec 24 '16 at 11:54
  • you might be able to answer this too - http://stackoverflow.com/q/41336818/2348597 – Greg Dec 26 '16 at 22:20
1

If I understand your question, you can do something like that:

func linearFactors(from string: String) -> Double? {
    let components = string.components(separatedBy: "/").flatMap { Double($0) }
    if let numerator = components.first, let denominator = components.last {
        return numerator / denominator
    }
    return nil
}
Artem Novichkov
  • 2,356
  • 2
  • 24
  • 34
  • `flatMap { Int($0) }` is more safe. – Eendje Dec 24 '16 at 09:53
  • @Artem, closure embedded in the function seems to separate / from the integers. Not sure about the forced unwrapping. Thinking ... – Greg Dec 24 '16 at 09:53
  • @Artem, breaking string element into 3 (Int, /, Int), which Int is inside the closure { Int($0) } numerator or denominator ? surely func should return Double (like factors in the question) ? – Greg Dec 24 '16 at 11:26
  • 1
    First component in array is numerator, and second is denominator. I have updated my answer with optional returning of dividing. Check it please. – Artem Novichkov Dec 24 '16 at 11:33
  • @Artem, you might like to answer this http://stackoverflow.com/q/41336818/2348597 – Greg Dec 26 '16 at 22:22
1

Convert ratios to array of double

let ratios = [ "1/1", "9/8", "5/4", "4/3", "3/2", "27/16", "15/8"]

let array = ratios.flatMap { element in
    let parts = element.components(separatedBy: "/")
    guard parts.count == 2, 
          let dividend = Double(parts[0]), 
          let divisor = Double(parts[1]), 
          divisor != 0
    else {
        return nil
    }
    return parts[0] / parts[1]
}
Adrian Bobrowski
  • 2,681
  • 1
  • 16
  • 26