Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
398 - PurelyFunctional.tv Newsletter

The well-tempered ergodox

This week, we're going to learn some guitar. Write a function that takes the string number and fret number and returns the note you get when plucking it. You can use the chart linked to at the bottom of this page as a guide.

;; third string, second fret
(note 3 2) => :A
;; first string, sixth fret
(note 1 6) => :A#

Open strings (no fret) are represented by 0.

You can represent sharps with a # and flats with a lowercase b.

There are obviously different ways to represent it. Try to find one that captures the underlying model of frets and intervals.

Thanks to this site for the challenge idea where it is considered Very Hard level in JavaScript.

@sztamas
Copy link

sztamas commented Oct 5, 2020

(def notes [:C :C# :D :D# :E :F :F# :G :G# :A :A# :B])

(def tuning [:E :B :G :D :A :E])

(defn string-notes [start]
  (->> notes
       cycle
       (drop-while #(not= start %))
       (take 25)))

(defn note [string fret]
  (if (and (<= 1 string 6) (<= 0 fret 24))
    (let [open-note (tuning (dec string))]
      (nth (string-notes open-note) fret))
    "Invalid input"))

@steffan-westcott
Copy link

steffan-westcott commented Oct 5, 2020

(def scale [:C :C#-Db :D :D#-Eb :E :F :F#-Gb :G :G#-Ab :A :A#-Bb :B])
(def open-notes [:E :B :G :D :A :E])

(defn note [string fret]
  (if-let [open-note (nth open-notes (dec string) nil)]
    (let [string-notes (->> scale cycle (drop-while #(not= open-note %)) (take 25))]
      (nth string-notes fret :invalid-fret))
    :invalid-string))

@steffan-westcott
Copy link

steffan-westcott commented Oct 5, 2020

Unicode includes musical symbols ♯ and ♭ so it's possible to write this:

(def scale [:C :C♯-D♭ :D :D♯-E♭ :E :F :F♯-G♭ :G :G♯-A♭ :A :A♯-B♭ :B])

@mchampine
Copy link

mchampine commented Oct 6, 2020

(defn note [s f]
  (nth (cycle [:E :F :F# :G :G# :A :A# :B :C :C# :D :D#])
       (+ (get [24 19 15 10 5 0] (dec s)) f)))

Note: the "magic values", 24, 19 etc are semitone offsets from low E.

@KingCode
Copy link

KingCode commented Oct 6, 2020

(def scale (cycle [:A :A#|Bb :B :C :C#|Db :D :D#|Eb :E :F :F#|Gb :G :G#|Ab]))
(def scale-offsets [7 2 10 5 0 7]) ;; E B G D A E
(def string-idx->offset (zipmap (range) scale-offsets))

(defn note [string fret]
   (let [ idx (-> string dec string-idx->offset (+ fret))]
      (nth scale idx)))

@zelark
Copy link

zelark commented Oct 6, 2020

(def tuning {1 :E 2 :B 3 :G 4 :D 5 :A 6 :E})
(def scale  (cycle [:C :C#/Db :D :D#/Eb :E :F :F#/Gb :G :G# :A :A#/Bb :B]))

(defn note [string fret]
  (if-let [open-note (tuning string)]
    (->> scale
         (drop-while (complement #{open-note}))
         (drop fret)
         (first))
    :invalid-input))


(= (note 3 2) :A)
(= (note 1 6) :A#/Bb)
(= (note 7 0) :invalid-input)

@michelemendel
Copy link

michelemendel commented Oct 7, 2020

(def notes [:C :C# :D :D# :E :F :F# :G :G# :A :A# :B])
(def tuning [:E :B :G :D :A :E])

(defn string-fret
  [guitar-string fret]
  (if (and
        (<= 1 guitar-string 6)
        (<= 0 fret 24))
    (let [open-note (tuning (dec guitar-string))
          pos-in-notes (mod (+ (.indexOf notes open-note) fret) 12)]
      (notes pos-in-notes))
    :invalid-input))

@alex-gerdom
Copy link

alex-gerdom commented Oct 13, 2020

Realized I forgot to post my submission for last weeks. Posting incase people are still doing feedback. I realize it's not quite to spec, but couldn't decide out how to pick if something is a sharp or a flat.

(def NOTES [:A :A#-Bb :B :C :C#-Db :D :D#-Eb :E :F :F#-Gb :G :G#-Ab])
(def STANDARD-TUNING [:E :B :G :D :A :E])

(defn string-notes [open-note]
  {:pre [((set NOTES) open-note)] ;; Prevent runaway on non-existent notes
   :docstring "Get infinite sequence of notes starting from provided open-note."}
  (->> (cycle NOTES)
       (drop-while #(not= % open-note))))

(defn note
  "Returns note produced by guitar played on provided string and fret.
   Optionally takes string tuning (defaults to standard [:E :B :G :D :A :E])."
  ([string fret]
   {:pre [(<= 1 string)]}
   (note string fret STANDARD-TUNING))
  ([string fret tuning]
   {:pre [(<= 1 string (count tuning))]}
   (let [guitar (map string-notes tuning)]
     (->> guitar
          ;; Focus on string
          (drop (dec string))
          (first)
          ;; Fret the string & get the note
          (drop fret)
          first))))

(deftest note-test
  (testing "Edabit examples"
    (is (= (note 2 10) :A))
    (is (thrown? java.lang.AssertionError (note 0 16)))
    (is (= (note 2 19) :F#-Gb))
    (is (= (note 3  0) :G))))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment