diff --git a/examples/cpp/.gitignore b/examples/cpp/.gitignore
index bb3371f031f43db3624ce95e55186cd2feafeefa..50b27b4e720c250d23a81c014c6aa210ff189ded 100644
--- a/examples/cpp/.gitignore
+++ b/examples/cpp/.gitignore
@@ -6,4 +6,5 @@ rings_2d
 spokes
 dot
 mixer
+looper
 particle_trail
diff --git a/examples/cpp/Makefile b/examples/cpp/Makefile
index 2eb77f186d2db187f860309098248e584c303a60..fffa0d29fb839b15a6b2577def5deb7532c376e4 100644
--- a/examples/cpp/Makefile
+++ b/examples/cpp/Makefile
@@ -1,23 +1,24 @@
-PROGRAMS = simple rings spokes dot particle_trail mixer
+PROGRAMS = simple rings spokes dot particle_trail mixer looper
 
 # Important optimization options
-CFLAGS = -O3 -ffast-math -fno-rtti
+CXXFLAGS = -O3 -ffast-math -fno-rtti
 
 # Standard libraries
-LFLAGS = -lm -lstdc++ -lpthread
+#LDFLAGS = -lm -lstdc++ -lpthread
+LDFLAGS = -lm -lpthread
 
 # Debugging
-CFLAGS += -g -Wall
-LFLAGS += -g
+CXXFLAGS += -g -Wall
+LDFLAGS += -g
 
 # Annoying warnings on by default on Mac OS
-CFLAGS += -Wno-tautological-constant-out-of-range-compare -Wno-gnu-static-float-init
+CXXFLAGS += -Wno-tautological-constant-out-of-range-compare -Wno-gnu-static-float-init
 
 
 all: $(PROGRAMS)
 
 .cpp:
-	$(CC) $(CFLAGS) $< -o $@ $(LFLAGS)
+	$(CXX) $(CXXFLAGS) $< -o $@ $(LDFLAGS)
 
 .PHONY: clean all
 
diff --git a/examples/cpp/dot.cpp b/examples/cpp/dot.cpp
index 8cfacc00d78f46e633a183e45b52b57ea89c94ba..f10918407fbabb41d70894b23ec306d5804d4f34 100644
--- a/examples/cpp/dot.cpp
+++ b/examples/cpp/dot.cpp
@@ -6,7 +6,7 @@ int main(int argc, char **argv)
     EffectRunner r;
 
     DotEffect e("data/dot.png");
-    r.setEffect(&e);
+    r.addEffect(&e);
 
     // Defaults, overridable with command line options
     r.setLayout("../layouts/grid32x16z.json");
diff --git a/examples/cpp/lib/brightness.h b/examples/cpp/lib/brightness.h
index 072a2d8c7e9240b0c66d97e52abd69bda60fdb4b..5843d2aa289a0ce430a4203e07397c4dcd4af38b 100644
--- a/examples/cpp/lib/brightness.h
+++ b/examples/cpp/lib/brightness.h
@@ -51,7 +51,7 @@ public:
     float getTotalBrightnessDelta() const;
 
     virtual void beginFrame(const FrameInfo& f);
-    virtual void endFrame(const FrameInfo& f);
+    virtual bool endFrame(const FrameInfo& f);
     virtual void debug(const DebugInfo& f);
     virtual void shader(Vec3& rgb, const PixelInfo& p) const;
 
@@ -236,9 +236,10 @@ inline void Brightness::beginFrame(const FrameInfo& f)
     latestAverage = avg;
 }
 
-inline void Brightness::endFrame(const FrameInfo& f)
+inline bool Brightness::endFrame(const FrameInfo& f)
 {
     next.endFrame(f);
+    return Effect::endFrame(f);
 }
 
 inline void Brightness::debug(const DebugInfo& d)
diff --git a/examples/cpp/lib/effect.h b/examples/cpp/lib/effect.h
index 62de4e3a7bd28a9ecf1cfe5f46c1d48a55f5ed89..5c9f10d31abf517fb02ab16ba4d30f4088b810df 100644
--- a/examples/cpp/lib/effect.h
+++ b/examples/cpp/lib/effect.h
@@ -27,11 +27,11 @@
 
 #pragma once
 
-#include <math.h>
+#include <cmath>
 #include <unistd.h>
 #include <vector>
-#include <string.h>
-#include <stdlib.h>
+#include <cstring>
+#include <cstdlib>
 
 #include "nanoflann.h"  // Tiny KD-tree library
 #include "svl/SVL.h"
@@ -71,7 +71,7 @@ public:
 
     // Optional begin/end frame callbacks
     virtual void beginFrame(const FrameInfo& f);
-    virtual void endFrame(const FrameInfo& f);
+    virtual bool endFrame(const FrameInfo& f);
 
     // Optional callback, invoked once per second when verbose mode is enabled.
     // This can print parameters out to the console.
@@ -176,6 +176,10 @@ public:
 
         EffectRunner &runner;
     };
+
+    Effect(): number_frames(0), frame_count(0) {}
+    unsigned long number_frames;
+    unsigned long frame_count;
 };
 
 
@@ -302,10 +306,21 @@ inline Effect::DebugInfo::DebugInfo(EffectRunner &runner)
     : runner(runner) {}
 
 
-inline void Effect::beginFrame(const FrameInfo &f) {}
-inline void Effect::endFrame(const FrameInfo &f) {}
-inline void Effect::debug(const DebugInfo &f) {}
-inline void Effect::postProcess(const Vec3& rgb, const PixelInfo& p) {}
+inline void Effect::beginFrame( const FrameInfo & ) {}
+inline bool Effect::endFrame( const FrameInfo & )
+{
+   if( number_frames )
+   {
+      if( frame_count++ > number_frames )
+      {
+         frame_count = 0;
+         return true;
+      }
+   }
+   return false;
+}
+inline void Effect::debug( const DebugInfo & ) {}
+inline void Effect::postProcess( const Vec3&, const PixelInfo& ) {}
 
 
 static inline float sq(float a)
diff --git a/examples/cpp/lib/effect_mixer.h b/examples/cpp/lib/effect_mixer.h
index 413f11645bc6b0f28e96ca03ddbd0ea0b22c9c8e..4113f20b173f1f91d26155e85a0ba829371406f0 100644
--- a/examples/cpp/lib/effect_mixer.h
+++ b/examples/cpp/lib/effect_mixer.h
@@ -63,7 +63,7 @@ public:
     virtual void shader(Vec3& rgb, const PixelInfo& p) const;
     virtual void postProcess(const Vec3& rgb, const PixelInfo& p);
     virtual void beginFrame(const FrameInfo& f);
-    virtual void endFrame(const FrameInfo& f);
+    virtual bool endFrame(const FrameInfo& f);
     virtual void debug(const DebugInfo& d);
 
 private:
@@ -223,11 +223,13 @@ inline void EffectMixer::postProcess(const Vec3& rgb, const PixelInfo& p)
     }
 }
 
-inline void EffectMixer::endFrame(const FrameInfo& f)
+inline bool EffectMixer::endFrame(const FrameInfo& f)
 {
+    bool lastFrame = false;
     for (unsigned i = 0; i < channels.size(); ++i) {
-        channels[i].effect->endFrame(f);
+        lastFrame |= channels[i].effect->endFrame(f);
     }
+    return lastFrame | Effect::endFrame(f);
 }
 
 inline void EffectMixer::debug(const DebugInfo& d)
diff --git a/examples/cpp/lib/effect_runner.h b/examples/cpp/lib/effect_runner.h
index 347a848f14af5a8ce2b56278b95c7fb6a4a1573e..bee028a6413cbbb547d6a766be7bf574dd4e34dd 100644
--- a/examples/cpp/lib/effect_runner.h
+++ b/examples/cpp/lib/effect_runner.h
@@ -27,15 +27,15 @@
 
 #pragma once
 
-#include <math.h>
+#include <cmath>
 #include <unistd.h>
 #include <algorithm>
 #include <vector>
 #include <sys/time.h>
-#include <stdio.h>
-#include <string.h>
-#include <stdlib.h>
-#include <time.h>
+#include <cstdio>
+#include <cstring>
+#include <cstdlib>
+#include <ctime>
 
 #include "effect.h"
 #include "opc_client.h"
@@ -52,6 +52,7 @@ public:
     bool setServer(const char *hostport);
     bool setLayout(const char *filename);
     void setEffect(Effect* effect);
+    void addEffect(Effect* effect);
     void setMaxFrameRate(float fps);
     void setVerbose(bool verbose = true);
 
@@ -76,6 +77,7 @@ public:
     struct FrameStatus {
         float timeDelta;
         bool debugOutput;
+        bool lastFrame;
     };
 
     // Main loop body
@@ -87,7 +89,7 @@ public:
 
     // Simple argument parsing and optional main loop
     bool parseArguments(int argc, char **argv);
-    int main(int argc, char **argv);
+    int main(int argc, char **argv, bool loop = true);
 
 protected:
     // Extensibility for argument parsing
@@ -99,6 +101,7 @@ private:
     OPCClient opc;
     rapidjson::Document layout;
     Effect *effect;
+    std::vector<Effect*> effects;
     std::vector<uint8_t> frameBuffer;
     Effect::FrameInfo frameInfo;
 
@@ -123,7 +126,12 @@ private:
 
 
 inline EffectRunner::EffectRunner()
-    : effect(0),
+    : opc(),
+      layout(),
+      effect(0),
+      effects(),
+      frameBuffer(),
+      frameInfo(),
       minTimeDelta(0),
       currentDelay(0),
       filteredTimeDelta(0),
@@ -197,9 +205,16 @@ inline bool EffectRunner::hasLayout() const
 
 inline void EffectRunner::setEffect(Effect *effect)
 {
+    effects.clear();
+    effects.push_back( effect );
     this->effect = effect;
 }
 
+inline void EffectRunner::addEffect( Effect *effect )
+{
+    effects.push_back( effect );
+}
+
 inline Effect* EffectRunner::getEffect() const
 {
     return effect;
@@ -237,9 +252,12 @@ inline float EffectRunner::getPercentBusy() const
 
 inline void EffectRunner::run()
 {
-    while (true) {
-        doFrame();
+    FrameStatus status;
+    do
+    {
+        status = doFrame();
     }
+    while( !status.lastFrame );
 }
    
 inline EffectRunner::FrameStatus EffectRunner::doFrame()
@@ -265,8 +283,9 @@ inline EffectRunner::FrameStatus EffectRunner::doFrame(float timeDelta)
     FrameStatus frameStatus;
 
     // Effects may get a modified view of time
-    frameStatus.timeDelta = frameInfo.timeDelta = timeDelta * speed;
+    frameStatus.timeDelta   = frameInfo.timeDelta = timeDelta * speed;
     frameStatus.debugOutput = false;
+    frameStatus.lastFrame   = false;
 
     jitterStatsMin = std::min(jitterStatsMin, frameStatus.timeDelta);
     jitterStatsMax = std::max(jitterStatsMax, frameStatus.timeDelta);
@@ -296,7 +315,7 @@ inline EffectRunner::FrameStatus EffectRunner::doFrame(float timeDelta)
             opc.write(frameBuffer);
         }
 
-        effect->endFrame(frameInfo);
+        frameStatus.lastFrame = effect->endFrame(frameInfo);
     }
 
     // Low-pass filter for timeDelta, to estimate our frame rate
@@ -370,13 +389,22 @@ inline bool EffectRunner::parseArguments(int argc, char **argv)
     return true;
 }
 
-inline int EffectRunner::main(int argc, char **argv)
+inline int EffectRunner::main(int argc, char **argv, bool loop)
 {
     if (!parseArguments(argc, argv)) {
         return 1;
     }
 
-    run();
+    do
+    {
+       for( std::vector<Effect*>::iterator i( effects.begin() );
+            i != effects.end(); ++i )
+       {
+          effect = *i;
+          run();
+       }
+    }
+    while( loop );
     return 0;
 }
 
diff --git a/examples/cpp/looper.cpp b/examples/cpp/looper.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..235958e7fc554d720047c37f529028c86fee5ec0
--- /dev/null
+++ b/examples/cpp/looper.cpp
@@ -0,0 +1,41 @@
+#include "lib/effect_runner.h"
+#include "dot.h"
+#include "particle_trail.h"
+#include "rings.h"
+#include "spokes.h"
+
+int main(int argc, char **argv)
+{
+    EffectRunner r;
+
+    int fps = 300;
+    for( int i = 1; i < argc; ++i )
+    {
+        if (!strcmp(argv[i], "-fps") && (i+1 < argc)) {
+            fps = atoi(argv[++i]);
+            if (fps <= 0) {
+                fprintf(stderr, "Invalid frame rate\n");
+                return 1;
+            }
+        }
+    }
+
+    DotEffect dot("data/dot.png");
+    ParticleTrailEffect trail;
+    RingsEffect rings("data/glass.png");
+    SpokesEffect spokes;
+    dot.number_frames = fps * 3;
+    trail.number_frames = fps * 3;
+    rings.number_frames = fps * 3;
+    spokes.number_frames = fps * 3;
+
+    r.addEffect(&dot);
+    r.addEffect(&trail);
+    r.addEffect(&rings);
+    r.addEffect(&spokes);
+
+    // Defaults, overridable with command line options
+    r.setLayout("../layouts/grid32x16z.json");
+
+    return r.main(argc, argv);
+}
diff --git a/examples/cpp/particle_trail.cpp b/examples/cpp/particle_trail.cpp
index d0b3ff67ee731771b18456ba0351ed496bd0786b..168434106015e3eb393f95494489fac240eb765f 100644
--- a/examples/cpp/particle_trail.cpp
+++ b/examples/cpp/particle_trail.cpp
@@ -5,7 +5,7 @@ int main(int argc, char **argv)
 {
     EffectRunner r;
     ParticleTrailEffect e;
-    r.setEffect(&e);
+    r.addEffect(&e);
     r.setLayout("../layouts/grid32x16z.json");
     return r.main(argc, argv);
 }
diff --git a/examples/cpp/particle_trail.h b/examples/cpp/particle_trail.h
index 597361ee32daaba82cbb03f8746a96c260a77157..5f142beff23c0b9b7a0a78445be2e94bcde90d15 100644
--- a/examples/cpp/particle_trail.h
+++ b/examples/cpp/particle_trail.h
@@ -2,7 +2,7 @@
 
 #pragma once
 
-#include <math.h>
+#include <cmath>
 #include "lib/color.h"
 #include "lib/effect.h"
 #include "lib/particle.h"
diff --git a/examples/cpp/rings.cpp b/examples/cpp/rings.cpp
index f72b564275e3db7928f8a559eb51862a4bc06c72..9604b3172344c41eb9c8aa00a1c6a0aecda28561 100644
--- a/examples/cpp/rings.cpp
+++ b/examples/cpp/rings.cpp
@@ -6,7 +6,7 @@ int main(int argc, char **argv)
     RingsEffect e("data/glass.png");
 
     EffectRunner r;
-    r.setEffect(&e);
+    r.addEffect(&e);
 
     r.setLayout("../layouts/grid32x16z.json");
     return r.main(argc, argv);
diff --git a/examples/cpp/rings.h b/examples/cpp/rings.h
index b6ae179f89991ab161980fdc2d26bbfccdb69df1..31cc2bc59905bd32f91d16da13ee7f39408c52c1 100644
--- a/examples/cpp/rings.h
+++ b/examples/cpp/rings.h
@@ -12,9 +12,9 @@
 
 #pragma once
 
-#include <math.h>
-#include <time.h>
-#include <stdlib.h>
+#include <cmath>
+#include <ctime>
+#include <cstdlib>
 #include "lib/color.h"
 #include "lib/effect.h"
 #include "lib/noise.h"
@@ -176,7 +176,7 @@ public:
         }
     }
 
-    virtual void endFrame(const FrameInfo &f)
+    virtual bool endFrame(const FrameInfo &f)
     {
         // Per-frame brightness calculations.
         // Adjust threshold in brightness-determining noise function, in order
@@ -205,6 +205,7 @@ public:
             if (step < -thresholdStepLimit) step = -thresholdStepLimit;
             threshold += step;
         }
+        return Effect::endFrame(f);
     }
 
     virtual void debug(const DebugInfo &di)
diff --git a/examples/cpp/simple.cpp b/examples/cpp/simple.cpp
index b93dbbbabbedabfe883da573c139bb916f9aeeab..f65242c1c685638017a9ef15893e3bd828061337 100644
--- a/examples/cpp/simple.cpp
+++ b/examples/cpp/simple.cpp
@@ -34,11 +34,11 @@ int main(int argc, char **argv)
     EffectRunner r;
 
     MyEffect e;
-    r.setEffect(&e);
+    r.addEffect(&e);
 
     // Defaults, overridable with command line options
     r.setMaxFrameRate(100);
     r.setLayout("../layouts/grid32x16z.json");
 
     return r.main(argc, argv);
-}
\ No newline at end of file
+}
diff --git a/examples/cpp/spokes.cpp b/examples/cpp/spokes.cpp
index 3576d0015e7b711fa27660672252508a1aa33821..3af6952aef9a43641a33c694d3f61978aaeb8737 100644
--- a/examples/cpp/spokes.cpp
+++ b/examples/cpp/spokes.cpp
@@ -10,7 +10,7 @@ int main(int argc, char **argv)
     br.set(0.2);
 
     EffectRunner r;
-    r.setEffect(&br);
+    r.addEffect(&br);
     r.setLayout("../layouts/grid32x16z.json");
     return r.main(argc, argv);
 }
diff --git a/examples/cpp/spokes.h b/examples/cpp/spokes.h
index ac784693399b0f1b7d192c9eebaa745cda0c7b51..acab839ef891aa60de4d79e22b4912e0f9668fa3 100644
--- a/examples/cpp/spokes.h
+++ b/examples/cpp/spokes.h
@@ -6,7 +6,7 @@
 
 #pragma once
 
-#include <math.h>
+#include <cmath>
 #include "lib/color.h"
 #include "lib/effect.h"
 #include "lib/noise.h"