 
 ease = (s, x0, x1) ->x0 + s * (x1 - x0)
 
-easeAttr = (path, aname, s, defaultValue) ->
-  return defaultValue unless path and aname of path[0]
-  ease(s, path[0][aname], path[1][aname])
+easeAttr = (path, aname, t, defaultValue) ->
+  return defaultValue unless path
+  parseKeyframeSelector = (k) ->
+    return parseFloat(k) / 100 if k.match(/^(.+)%$/)
+    {'from': 0, 'to': 1}[k] or parseFloat(k)
+  frames = ({s: parseKeyframeSelector(k), v: properties[aname]} for k, properties of path when aname of properties)
+  frames.sort ({s:a}, {s:b}) -> a - b
+  f1 = _.find frames, ({s}) -> t <= s
+  f0 = _.find frames.reverse(), ({s}) -> s <= t
+  return defaultValue unless f0 or f1
+  return f0.v if t <= f0.s
+  return f1.v if f1.s <= t
+  s = (t - f0.s) / (f1.s - f0.s)
+  return ease(s, f0.v, f1.v)
 
+# t is a time value. Find the sprite.path keyframes that bracket it,
+# and interpolate between them.
 sprite_position_at = (sprite, t) ->
-  s = Math.min(1, (t - sprite.t0) / (sprite.t1 - sprite.t0))
-  s = Math.min(1, t - sprite.t0) unless 't1' of sprite
-  {
+  if 't1' of sprite
+    s = Math.min(1, (t - sprite.t0) / (sprite.t1 - sprite.t0))
+  else
+    s = Math.min(1, t - sprite.t0)
+  return {
     x: easeAttr(sprite.path, 'x', s, 0)
     y: easeAttr(sprite.path, 'y', s, 0)
   }
 
 write_animation_frames = (options) ->
-  {step_size, sprites, end_padding} = options
-  step_size ||= 1
-  t_end = Math.max(_.chain(sprites).pluck('t1').compact().value()...)
-  t_end += (end_padding or 0)
-  t = 0
-  with_book (options.title || "animation frames"), (book) ->
-    ctx = book.context
+  defaults = {step_size: 1, end_padding: 0, title:  "animation frames"}
+  {step_size, sprites, end_padding} = _.extend defaults, options
+  t_end = Math.max _.chain(sprites).pluck('t1').compact().value()...
+  t_end += end_padding
+  with_book options.title, (book) ->
+    t = 0
     while t < t_end
       book.add_page ->
         with_page width: 640, height: 480, ->
-          current_sprites = (sprite for sprite in sprites when (sprite.t0 or 0) <= t < (sprite.t1 or t_end))
+          current_sprites = _.filter sprites, (sprite) ->
+            (sprite.t0 or 0) <= t < (sprite.t1 or t_end) and sprite.visible != false
           for sprite in _.sortBy(current_sprites, (s) -> (s.z_index or 0))
-            s = Math.min(1, (t - sprite.t0) / (sprite.t1 - sprite.t0))
+            s = Math.min 1, (t - sprite.t0) / (sprite.t1 - sprite.t0)
             s = t - sprite.t0 unless sprite.t1
-            {x, y} = sprite_position_at(sprite, t)
+            {x, y} = sprite_position_at sprite, t
             with_graphics_context (ctx) ->
               ctx.translate x, y
               sprite.draw ctx, s

  speed ||= (if quick then 10 else 1)
  chord = Chords[0]
  title = "#{chord.name} Chord Shapes"

  with_animation_context
    title: "Chord Shape Animation Frames"

  , (animation) ->

    master_diagram = animation.make_sprite
      name: "master"
      t0: 0
      t1: 13
      z_index: 1
      visible: not just_one
      draw: (ctx, s) ->
        s *= 12
        pitch = Math.floor s
        pitch = Math.min 11, pitch
        bend = s - pitch
        positions = finger_positions_on_chord chord, pitch
        draw_chord_diagram ctx, positions, dy: bend * ChordDiagramStyle.fret_height

        pitch = (pitch + 1) % 12
        bend -= 1
        positions = finger_positions_on_chord chord, pitch
        draw_chord_diagram ctx, positions, dy: bend * ChordDiagramStyle.fret_height

      , (sprite) ->
