     (enhanced-phase bar bar-phase bar-ratio))
   (snapshot-beat-within-bar [snapshot]
     (let [beat-size (/ 1 bpb)]
-      (inc (floor (/ (snapshot-bar-phase snapshot 1) beat-size)))))
+      (inc (int (floor (/ (snapshot-bar-phase snapshot 1) beat-size))))))
   (snapshot-down-beat? [snapshot]
     (let [beat-size (/ 1 bpb)]
-      (zero? (floor (/ (snapshot-bar-phase snapshot 1) beat-size))))))
-
-(deftype Metronome [start bar-start bpm bpb]
-
+      (zero? (floor (/ (snapshot-bar-phase snapshot 1) beat-size)))))
+  (snapshot-phrase-phase [snapshot phrase-ratio]
+    (enhanced-phase phrase phrase-phase phrase-ratio))
+  (snapshot-bar-within-phrase [snapshot]
+    (let [phrase-size (/ 1 bpp)]
+      (inc (int (floor (/ (snapshot-phrase-phase snapshot 1) phrase-size))))))
+  (snapshot-phrase-start? [snapshot]
+    (let [phrase-size (/ 1 bpp)]
+      (zero? (floor (/ (snapshot-phrase-phase snapshot 1) phrase-size))))))
+
+(defrecord Metronome [start bpm bpb bpp]
   IMetronome
   (metro-start [metro] @start)
   (metro-start [metro start-beat]
-    (let [new-start (round (- (now) (* start-beat (metro-tick metro))))]
-      (reset! start new-start)
-      (reset! bar-start new-start)  ; Keep phases consistent
-      new-start))
-  (metro-bar-start [metro] @bar-start)
+    (let [new-start (round (- (now) (* (dec start-beat) (metro-tick metro))))]
+      (reset! start new-start)))
   (metro-bar-start [metro start-bar]
-    (let [new-bar-start (round (- (now) (* start-bar (metro-tock metro))))]
-      (reset! bar-start new-bar-start)
-      (reset! start new-bar-start)  ; Keep phases consistent
-      new-bar-start))
-  (metro-tick  [metro] (beat-ms 1 @bpm))
-  (metro-tock  [metro] (beat-ms @bpb @bpm))
-  (metro-beat  [metro] (marker-number (now) @start (metro-tick metro)))
-  (metro-beat  [metro b] (+ (* b (metro-tick metro)) @start))
+    (let [phase (metro-beat-phase metro)
+          shift (* (metro-tick metro) (if (> phase 0.5) (dec phase) phase))
+          new-bar-start (round (+ shift (- (now) (* (dec start-bar) (metro-tock metro)))))]
+      (reset! start new-bar-start)))
+  (metro-phrase-start [metro start-phrase]
+    (let [phase (metro-beat-phase metro)
+          shift (* (metro-tick metro) (if (> phase 0.5) (dec phase) phase))
+          new-phrase-start (round (+ shift (- (now) (* (dec start-phrase) (metro-ding metro)))))]
+      (reset! start new-phrase-start)))
+
+  (metro-tick [metro] (beat-ms 1 @bpm))
+  (metro-tock [metro] (beat-ms @bpb @bpm))
+  (metro-ding [metro] (beat-ms (* @bpb @bpp) @bpm))
+
+  (metro-beat [metro] (marker-number (now) @start (metro-tick metro)))
+  (metro-beat [metro b] (+ (* b (metro-tick metro)) @start))
   (metro-beat-phase [metro]
     (marker-phase (now) @start (metro-tick metro)))
   (metro-beat-phase [metro phase]
     (let [delta (- phase (metro-beat-phase metro))
           shift (round (* (metro-tick metro) (if (> delta 0.5) (dec delta)
                                                  (if (< delta -0.5) (inc delta) delta))))]
-      (swap! start - shift)
-      (swap! bar-start - shift)))
-  (metro-bar   [metro] (marker-number (now) @bar-start (metro-tock metro)))
-  (metro-bar   [metro b] (+ (* b (metro-tock metro)) @bar-start))
+      (swap! start - shift)))
+  
+  (metro-bar [metro] (marker-number (now) @start (metro-tock metro)))
+  (metro-bar [metro b] (+ (* b (metro-tock metro)) @start))
   (metro-bar-phase [metro]
-    (marker-phase (now) @bar-start (metro-tock metro)))
+    (marker-phase (now) @start (metro-tock metro)))
   (metro-bar-phase [metro phase]
     (let [delta (- phase (metro-bar-phase metro))
           shift (round (* (metro-tock metro) (if (> delta 0.5) (dec delta)
                                                  (if (< delta -0.5) (inc delta) delta))))]
-      (swap! start - shift)
-      (swap! bar-start - shift)))
-  (metro-bpm   [metro] @bpm)
-  (metro-bpm   [metro new-bpm]
-    (let [cur-beat      (metro-beat metro)
-          cur-bar       (metro-bar metro)
-          new-tick      (beat-ms 1 new-bpm)
-          new-tock      (* @bpb new-tick)
-          new-start     (round (- (metro-beat metro cur-beat) (* new-tick cur-beat)))
-          new-bar-start (round (- (metro-bar metro cur-bar) (* new-tock cur-bar)))]
+      (swap! start - shift)))
+  
+  (metro-phrase [metro] (marker-number (now) @start (metro-ding metro)))
+  (metro-phrase [metro b] (+ (* b (metro-ding metro)) @start))
+  (metro-phrase-phase [metro]
+    (marker-phase (now) @start (metro-ding metro)))
+  (metro-phrase-phase [metro phase]
+    (let [delta (- phase (metro-phrase-phase metro))
+          shift (round (* (metro-ding metro) (if (> delta 0.5) (dec delta)
+                                                 (if (< delta -0.5) (inc delta) delta))))]
+      (swap! start - shift)))
+  
+  (metro-bpm [metro] @bpm)
+  (metro-bpm [metro new-bpm]
+    (let [cur-beat (metro-beat metro)
+          new-tick (beat-ms 1 new-bpm)
+          new-start (round (- (metro-beat metro cur-beat) (* new-tick cur-beat)))]
       (reset! start new-start)
-      (reset! bar-start new-bar-start)
       (reset! bpm new-bpm))
     [:bpm new-bpm])
-  (metro-bpb   [metro] @bpb)
-  (metro-bpb   [metro new-bpb]
-    (let [cur-bar       (metro-bar metro)
-          new-tock      (beat-ms new-bpb @bpm)
-          new-bar-start (- (metro-bar metro cur-bar) (* new-tock cur-bar))]
-      (reset! bar-start new-bar-start)
+  (metro-bpb [metro] @bpb)
+  (metro-bpb [metro new-bpb]
+    (let [cur-bar (metro-bar metro)
+          new-tock (beat-ms new-bpb @bpm)
+          new-start (- (metro-bar metro cur-bar) (* new-tock cur-bar))]
+      (reset! start new-start)
       (reset! bpb new-bpb)))
   (metro-snapshot [metro]
-    (let [instant    (now)
-          beat       (marker-number instant @start (metro-tick metro))
-          bar        (marker-number instant @bar-start (metro-tock metro))
+    (let [instant (now)
+          beat (marker-number instant @start (metro-tick metro))
+          bar (marker-number instant @start (metro-tock metro))
+          phrase (marker-number instant @start (metro-ding metro))
           beat-phase (marker-phase instant @start (metro-tick metro))
-          bar-phase  (marker-phase instant @bar-start (metro-tock metro))]
-      (MetronomeSnapshot. @start @bar-start @bpm @bpb instant beat bar beat-phase bar-phase)))
-
-  clojure.lang.ILookup
-  (valAt [this key] (.valAt this key nil))
-  (valAt [this key not-found]
-    (cond (= key :start) @start
-          (= key :bpm) @bpm
-          :else not-found))
-
-  clojure.lang.IFn
-  (invoke [this] (metro-beat this))
-  (invoke [this arg]
-    (cond
-      (number? arg) (metro-beat this arg)
-      (= :bpm arg) (metro-bpm this) ;; (bpm this) fails.
-      :else (throw (Exception. (str "Unsupported metronome arg: " arg)))))
-  (invoke [this _ new-bpm] (metro-bpm this new-bpm)))
+          bar-phase (marker-phase instant @start (metro-tock metro))
+          phrase-phase (marker-phase instant @start (metro-ding metro))]
+      (MetronomeSnapshot. @start @bpm @bpb @bpp instant beat bar phrase beat-phase bar-phase phrase-phase)))
+  (metro-marker [metro]
+    (let [snap (metro-snapshot metro)]
+      (str (:phrase snap) "." (snapshot-bar-within-phrase snap) "." (snapshot-beat-within-bar snap)))))
 
 (defn metronome
-  "A metronome is a beat management function.  Tell it what BPM you want,
-  and it will output beat timestamps accordingly.  Call the returned function
-  with no arguments to get the next beat number, or pass it a beat number
-  to get the timestamp to play a note at that beat.
-
-  Metronome also works with bars. Set the number of beats per bar using
-  metro-bpb (defaults to 4). metro-bar returns a timestamp that can be used
-  to play a note relative to a specified bar.
-
-  (def m (metronome 128))
-  (m)          ; => <next beat number>
-  (m 200)      ; => <timestamp of beat 200>
-  (m :bpm)     ; => return the current bpm val
-  (m :bpm 140) ; => set bpm to 140"
-  [bpm]
+  "A metronome is a beat management tool. Tell it what BPM you want,
+  and it will compute beat, bar, and phrase timestamps accordingly.
+  See the IMetronome interface for full details."
+  [bpm & {:keys [bpb bpp] :or {bpb 4 bpp 8}}]
   (let [start (atom (now))
-        bar-start (atom @start)
-        bpm   (atom bpm)
-        bpb   (atom 4)]
-    (Metronome. start bar-start bpm bpb)))
-
-                                        ;== Grooves
-                                        ;
-                                        ; A groove represents a pattern of velocities and timing modifications that is
-                                        ; applied to a sequence of notes to adjust the feel.
-                                        ;
-                                        ; * swing
-                                        ; * jazz groove, latin groove
-                                        ; * techno grooves (hard on beat one)
-                                        ; * make something more driving, or more layed back...
-
+        bpm (atom bpm)
+        bpb (atom bpb)
+        bpp (atom bpp)]
+    (Metronome. start bpm bpb bpp)))
+

          adjusted-phase (* base-phase denominator)]
      (- (double adjusted-phase) (long adjusted-phase))))

(defrecord MetronomeSnapshot [start bar-start bpm bpb instant beat bar beat-phase bar-phase]
  ISnapshot
  (snapshot-beat-phase [snapshot]
    (snapshot-beat-phase snapshot 1))

  (:require [overtone.at-at :refer [now]]
            [clojure.math.numeric-tower :refer [round floor]]))

(defprotocol IMetronome
  (metro-start [metro] [metro start-beat]
    "Returns the start time of the metronome. Also restarts the
     metronome at start-beat if given.")
  (metro-bar-start [metro start-bar]
    "Restarts the metronome at start-bar, keeping the beat phase
    unchanged in case it is being synced to an external source.")
  (metro-phrase-start [metro start-phrase]
    "Restarts the metronome at start-phrase, keeping the beat phase
    unchanged in case it is being synced to an external source.")
  (metro-tick [metro]
    "Returns the duration of one beat in milleseconds.")
  (metro-tock [metro]
    "Returns the duration of one bar in milliseconds.")
  (metro-ding [metro]
    "Returns the duration of one phrase in milliseconds.")
  (metro-beat [metro] [metro beat]
    "Returns the next beat number or the timestamp (in milliseconds) of the
     given beat.")
  (metro-beat-phase [metro] [metro phase]
    "Returns the distance traveled into the current beat [0.0, 1.0), or
       adjusts the phase to match the one supplied.")
  (metro-bar [metro] [metro bar]
    "Returns the next bar number or the timestamp (in milliseconds) of the
     given bar")
  (metro-bar-phase [metro] [metro phase]
    "Returns the distance traveled into the current bar [0.0, 1.0), or
       adjusts the phase to match the one supplied.")
  (metro-phrase [metro] [metro phrase]
    "Returns the next phrase number or the timestamp (in milliseconds)
     of the given phrase")
  (metro-phrase-phase [metro] [metro phase]
    "Returns the distance traveled into the current phrase [0.0, 1.0),
       or adjusts the phase to match the one supplied.")
  (metro-bpb [metro] [metro new-bpb]
    "Get the current beats per bar or change it to new-bpb")
  (metro-bpp [metro] [metro new-bpp]
    "Get the current bars per phrase, or change it to new-bpp")
  (metro-bpm [metro] [metro new-bpm]
    "Get the current bpm or change the bpm to new-bpm.")
  (metro-snapshot [metro]
    "Take a snapshot of the current beat, bar, phrase, and phase state.")
  (metro-marker [metro]
    "Returns the current time as phrase.bar.beat"))


;; Rhythm

;; * a resting heart rate is 60-80 bpm
;; * around 150 induces an excited state

;; A rhythm system should let us refer to time in terms of rhythmic units
;; like beat, bar, measure, and it should convert these units to real time
;; units (ms) based on the current BPM and signature settings.

(defn beat-ms
  "Convert 'b' beats to milliseconds at the given 'bpm'."
  [b bpm] (* (/ 60000.0 bpm) b))

(defn- marker-number
  "Helper function to calculate the beat, bar, or phrase number in
  effect at a given moment, given a starting point and beat, bar,
  or phrase interval."
  [instant start interval]
  (inc (long (/ (- instant start) interval))))

(defn- marker-phase
  "Helper function to calculate the beat, bar, or phrase phase at a
  given moment, given a starting point and beat interval."
  [instant start interval]
  (let [marker-ratio (/ (- instant start) interval)]
    (- (double marker-ratio) (long marker-ratio))))
