diff --git a/examples/usb-basic.py b/examples/usb-basic.py
index 30bd077017dc46c0b39e6c334bdcd13f5a7665d3..42f2b5aa690d18cf3449e57d56c1779d266ac2db 100755
--- a/examples/usb-basic.py
+++ b/examples/usb-basic.py
@@ -59,5 +59,5 @@ while True:
 		#time.sleep(0.1)
 
 	print
-	time.sleep(2)
+	time.sleep(0.1)
 
diff --git a/firmware/fadecandy.cpp b/firmware/fadecandy.cpp
index f29414f889de872f2d328f0ce15f717d05e0b331..5ad420d32ebe71d2bffc0693abb05dffd2511887 100644
--- a/firmware/fadecandy.cpp
+++ b/firmware/fadecandy.cpp
@@ -39,6 +39,29 @@ static OctoWS2811z leds(LEDS_PER_STRIP, ledBuffer, WS2811_800kHz);
 static int8_t residual[CHANNELS_TOTAL];
 
 
+static uint32_t calculateInterpCoefficient()
+{
+    /*
+     * Calculate our interpolation coefficient. This is a value between
+     * 0x0000 and 0x10000, representing some point in between fbPrev and fbNext.
+     *
+     * We timestamp each frame at the moment its final packet has been received.
+     * In other words, fbNew has no valid timestamp yet, and fbPrev/fbNext both
+     * have timestamps in the recent past.
+     *
+     * fbNext's timestamp indicates when both fbPrev and fbNext entered their current
+     * position in the keyframe queue. The difference between fbPrev and fbNext indicate
+     * how long the interpolation between those keyframes should take.
+     */
+
+    uint32_t now = millis();
+    uint32_t tsPrev = buffers.fbPrev->timestamp;
+    uint32_t tsNext = buffers.fbNext->timestamp;
+
+    uint32_t scaled = ((now - tsNext) << 16) / (tsNext - tsPrev);
+    return std::min<uint32_t>(scaled, 0x10000);
+}
+
 ALWAYS_INLINE static inline uint32_t lutInterpolate(const uint16_t *lut, uint32_t arg)
 {
     /*
@@ -54,7 +77,7 @@ ALWAYS_INLINE static inline uint32_t lutInterpolate(const uint16_t *lut, uint32_
     return (lut[index] * invAlpha + lut[index + 1] * alpha) >> 8;
 }
 
-static inline uint32_t updatePixel(uint32_t icPrev, uint32_t icNext,
+static uint32_t updatePixel(uint32_t icPrev, uint32_t icNext,
     const uint8_t *pixelPrev, const uint8_t *pixelNext,
     const uint16_t *lut, int8_t *pResidual)
 {
@@ -416,8 +439,7 @@ extern "C" int main()
 
     while (1) {
         buffers.handleUSB();
-
-        updateDrawBuffer((millis() << 4) & 0xFFFF);
+        updateDrawBuffer(calculateInterpCoefficient());
         leds.show();
 
         // Optionally disable dithering by clearing our residual buffer every frame.
diff --git a/firmware/fc_usb.cpp b/firmware/fc_usb.cpp
index de1ec1f7d6641b3b5bbf56ba1cfe3ec826caa5f3..364223918eee025ee3cb74552dad9e435e1dda10 100644
--- a/firmware/fc_usb.cpp
+++ b/firmware/fc_usb.cpp
@@ -86,6 +86,7 @@ void fcBuffers::handleUSB()
 void fcBuffers::finalizeFramebuffer()
 {
     fcFramebuffer *recycle = fbPrev;
+    fbNew->timestamp = millis();
     fbPrev = fbNext;
     fbNext = fbNew;
     fbNew = recycle;
diff --git a/firmware/fc_usb.h b/firmware/fc_usb.h
index e975cdec53779f74bb29bfe6b52b29b6c6171153..5d69081777ef654095905c3e63188c5de5d72d0f 100644
--- a/firmware/fc_usb.h
+++ b/firmware/fc_usb.h
@@ -36,6 +36,7 @@ template <unsigned tSize>
 struct fcPacketBuffer
 {
     usb_packet_t *packets[tSize];
+    uint32_t timestamp;
 
     fcPacketBuffer()
     {