   i.e. the ionian/major scale has an interval sequence of 2 2 1 2 2 2 1
        therefore the 4th degree is (+ 2 2 1 2) semitones from the start of the
        scale."
-  ([n] (nth-interval :ionian n))
+  ([n] (nth-interval :scale n))
   ([scale n]
      (reduce + (take n (cycle (scale SCALE))))))
 
-(def DEGREE {:i   1
-             :ii  2
-             :iii 3
-             :iv  4
-             :v   5
-             :vi  6
-             :vii 7})
+(def DEGREE {:i     1
+             :ii    2
+             :iii   3
+             :iv    4
+             :v     5
+             :vi    6
+             :vii   7
+             :_     nil})
+
+(defn resolve-degree
+  [degree]
+  (if (some #{degree} (keys DEGREE))
+    (degree DEGREE)
+    (throw (Exception. (str "Unable to resolve degree: " degree ". Was expecting a roman numeral in the range :i -> :vii or the nil-note symbol :_")))))
 
 (defn degree->interval
-  "Converts the degree of a scale given as a roman numeral keyword or integer
-   and converts it to the number of intervals (semitones) from the tonic of the
-   specified scale."
-  [scale degree & [shift]]
-  (let [shift (or shift 0)]
-    (cond 
-      (.endsWith (name degree) ".") 
-      (degree->interval scale (keyword (chop (name degree))) (- shift 12))
-
-      (.endsWith (name degree) "*") 
-      (degree->interval scale (keyword (chop (name degree))) (+ shift 12))
-
-      :default
-      (+ shift
-         (if-let [deg (DEGREE degree)]
-           (nth-interval scale (dec deg))
-           (nth-interval scale (dec degree)))))))
+  "Converts the degree of a scale given as a roman numeral keyword and converts
+  it to the number of intervals (semitones) from the tonic of the specified
+  scale."
+  ([degree scale] (degree->interval degree scale 0))
+  ([degree scale shift]
+     (cond
+      (nil? degree) nil
+      (= :_ degree) nil
+
+      (number? degree) (+ shift (nth-interval scale (dec degree)))
+
+      (keyword? degree) (cond
+                         (.endsWith (name degree) "-")
+                         (degree->interval (keyword (chop (name degree))) scale (- shift 12))
+
+                         (.endsWith (name degree) "+")
+                         (degree->interval (keyword (chop (name degree))) scale (+ shift 12))
+
+                         :default
+                         (+ shift (nth-interval scale (dec (resolve-degree degree))))))))
+
+(defn degrees->pitches
+  "Convert intervals to pitches in MIDI number format.  Supports nested collections."
+  [degrees scale root]
+  (let [root (resolve-note root)
+        _ (when (nil? root)
+            (throw (Exception. (str "root resolved to a nil value. degrees->pitches requires a non-nil root."))))]
+    (map (fn [degree]
+           (cond
+            (coll? degree) (degrees degree scale root)
+            (nil? degree) nil
+            :default (let [interval (degree->interval degree scale)]
+                       (if (nil? interval) nil
+                           (+ root interval)))))
+         degrees)))
+
+(defn resolve-degrees
+  "Either maps the degrees to integers if they're keywords using the map DEGREE
+  or leaves them unmodified"
+  [degrees]
+  (map #(if (keyword? %) (DEGREE %) %) degrees))
+
+(defn degrees
+  [ds scale root]
+  (let [ds (resolve-degrees)
+        ]))
+
+(defn scale
+  ([root scale-name] (scale root scale-name (range 1 8) 4))
+  ([root scale-name degrees] (scale root scale-name degrees 4))
+  ([root scale-name degrees octave]
+     (let [root (resolve-note root)
+           degrees (resolve-degrees degrees)
+           scale (resolve-scale scale-name)
+           base (octave-note octave root)]
+       (map #(+ % base) scale))))
 
 (def CHORD
-  (let [major  [0 4 7]
-        minor  [0 3 7]
-        major7 [0 4 7 11]
-        dom7   [0 4 7 10]
-        minor7 [0 3 7 10]
-        aug    [0 4 8]
-        dim    [0 3 6]
-        dim7   [0 3 6 9]]
+  (let [major  #{0 4 7}
+        minor  #{0 3 7}
+        major7 #{0 4 7 11}
+        dom7   #{0 4 7 10}
+        minor7 #{0 3 7 10}
+        aug    #{0 4 8}
+        dim    #{0 3 6}
+        dim7   #{0 3 6 9}]
     {:major      major
      :M          major
      :minor      minor

     :dim7       dim7
     :i7         dim7}))

(defn chord
  "Returns a set of notes for the specified chord at the specified octave
  (defaulting to 4).

           :A  9  :a  9
           :A# 10 :a# 10 :Bb 10 :bb 10
           :B  11 :b  11})


(defn resolve-note
  "Resolves note to MIDI number format. Resolves upper and lower-case keywords
  and strings in MIDI note format. If given an integer or nil, returns them
  unmodified. All other inputs will raise an exception.

  Usage examples:

  (resolve-note \"C4\")  ;=> 60
  (resolve-note \"C#4\") ;=> 61
  (resolve-note \"eb2\") ;=> 39
  (resolve-note :F#7)    ;=> 102
  (resolve-note :db5)    ;=> 73
  (resolve-note 60)      ;=> 60
  (resolve-note nil)     ;=> nil"

  [note]
  (cond
   (nil? note) nil
   (integer? note) (if (>= note 0)
                    note
                    (throw (Exception.
                            (str "Unable to resolve note: " note ". Value is out of range. Lowest value is 0"))))
   (keyword? note) (resolve-note (name note))
   (string? note) (let [midi-note-re #"\A([a-gA-G][#b]?)([-0-9]+\Z)"
                        separated (re-find midi-note-re note)
                        _ (when (nil? separated)
                            (throw (Exception.
                                    (str "Unable to resolve note: " note ". Does not appear to be in MIDI format i.e. C#4"))))

                        [_ pitch-class octave] separated
                        octave (Integer. octave)
                        _ (when (< octave -1)
                            (throw (Exception.
                                    (str "Unable to resolve note: " note ". Octave is out of range. Lowest octave value is -1"))))
                        _ (println "pc: " pitch-class)
                        interval (NOTE (keyword pitch-class))
                        _                        (println "int" interval)

                        _ (when (nil? interval)
                            (throw (Exception.
                                    (str "Unable to resolve note: " note ". Not a valid pitch class such as C or Db"))))]
                    (+ interval 12 (* 12 octave)))
   :else (throw (Exception. (str "Unable to resolve note: " note ". Wasn't a recognised format (either an integer, keyword, string or nil)")))))

;; * Each note in a scale acts as either a generator or a collector of other notes,
;; depending on their relations in time within a sequence.
