diff --git a/toonz/sources/include/tools/inputmanager.h b/toonz/sources/include/tools/inputmanager.h
index 63d3560..5cbe005 100644
--- a/toonz/sources/include/tools/inputmanager.h
+++ b/toonz/sources/include/tools/inputmanager.h
@@ -1,3 +1,5 @@
+#pragma once
+
 #ifndef INPUTMANAGER_INCLUDED
 #define INPUTMANAGER_INCLUDED
 
@@ -153,7 +155,7 @@ public:
   virtual void modifyTrack(
     const TTrackP &track,
     const TInputSavePoint::Holder &savePoint,
-    TTrackList &outTracks ) { }
+    TTrackList &outTracks );
   virtual void modifyTracks(
     const TTrackList &tracks,
     const TInputSavePoint::Holder &savePoint,
@@ -161,7 +163,7 @@ public:
 
   virtual void modifyHover(
     const TPointD &hover,
-    THoverList &outHovers ) { }
+    THoverList &outHovers );
   virtual void modifyHovers(
     const THoverList &hovers,
     THoverList &outHovers );
diff --git a/toonz/sources/include/tools/modifiertest.h b/toonz/sources/include/tools/modifiertest.h
new file mode 100644
index 0000000..b328e0f
--- /dev/null
+++ b/toonz/sources/include/tools/modifiertest.h
@@ -0,0 +1,61 @@
+#pragma once
+
+#ifndef MODIFIERTEST_INCLUDED
+#define MODIFIERTEST_INCLUDED
+
+// TnzTools includes
+#include <tools/inputmanager.h>
+
+// std includes
+#include <cmath>
+
+
+#undef DVAPI
+#undef DVVAR
+#ifdef TNZTOOLS_EXPORTS
+#define DVAPI DV_EXPORT_API
+#define DVVAR DV_EXPORT_VAR
+#else
+#define DVAPI DV_IMPORT_API
+#define DVVAR DV_IMPORT_VAR
+#endif
+
+
+//===================================================================
+
+//*****************************************************************************************
+//    TModifierTest definition
+//*****************************************************************************************
+
+class TModifierTest: public TInputModifier {
+public:
+  class Handler: public TTrackHandler {
+  public:
+    std::vector<double> angles;
+    Handler(TTrack &original): TTrackHandler(original) { }
+  };
+
+  class Modifier: public TTrackModifier {
+  public:
+    double angle;
+    double radius;
+    double speed;
+
+    Modifier(TTrackHandler &handler, double angle, double radius, double speed = 0.25);
+    TTrackPoint calcPoint(double originalIndex);
+  };
+
+public:
+  int count;
+  double radius;
+
+  explicit TModifierTest();
+
+  void modifyTrack(
+    const TTrackP &track,
+    const TInputSavePoint::Holder &savePoint,
+    TTrackList &outTracks ) override;
+};
+
+
+#endif
diff --git a/toonz/sources/include/tools/track.h b/toonz/sources/include/tools/track.h
index 2883a19..0efccce 100644
--- a/toonz/sources/include/tools/track.h
+++ b/toonz/sources/include/tools/track.h
@@ -181,12 +181,10 @@ public:
   inline int ceilIndex(double index) const
     { return clampIndex(ceilIndexNoClamp(index)); }
 
-  int floorIndex(double index, double &outFrac) const;
+  int floorIndex(double index, double *outFrac) const;
 
-  inline const TTrackPoint& floorPoint(double index, double &outFrac) const
+  inline const TTrackPoint& floorPoint(double index, double *outFrac = NULL) const
     { return point(floorIndex(index, outFrac)); }
-  inline const TTrackPoint& floorPoint(double index) const
-    { return point(floorIndex(index)); }
   inline const TTrackPoint& ceilPoint(double index) const
     { return point(ceilIndex(index)); }
 
@@ -256,19 +254,19 @@ public:
 
   inline double originalIndexByIndex(double index) const {
     double frac;
-    const TTrackPoint &p0 = floorPoint(index, frac);
+    const TTrackPoint &p0 = floorPoint(index, &frac);
     const TTrackPoint &p1 = ceilPoint(index);
     return interpolationLinear(p0.originalIndex, p1.originalIndex, frac);
   }
   inline double timeByIndex(double index) const {
     double frac;
-    const TTrackPoint &p0 = floorPoint(index, frac);
+    const TTrackPoint &p0 = floorPoint(index, &frac);
     const TTrackPoint &p1 = ceilPoint(index);
     return interpolationLinear(p0.time, p1.time, frac);
   }
   inline double lengthByIndex(double index) const {
     double frac;
-    const TTrackPoint &p0 = floorPoint(index, frac);
+    const TTrackPoint &p0 = floorPoint(index, &frac);
     const TTrackPoint &p1 = ceilPoint(index);
     return interpolationLinear(p0.length, p1.length, frac);
   }
@@ -280,7 +278,7 @@ public:
 
   inline TTrackPoint interpolateLinear(double index) const {
     double frac;
-    const TTrackPoint &p0 = floorPoint(index, frac);
+    const TTrackPoint &p0 = floorPoint(index, &frac);
     const TTrackPoint &p1 = ceilPoint(index);
     return interpolationLinear(p0, p1, frac);
   }
diff --git a/toonz/sources/tnztools/CMakeLists.txt b/toonz/sources/tnztools/CMakeLists.txt
index 113061e..c426de8 100644
--- a/toonz/sources/tnztools/CMakeLists.txt
+++ b/toonz/sources/tnztools/CMakeLists.txt
@@ -48,6 +48,7 @@ set(HEADERS
     ../include/tools/inputstate.h
     ../include/tools/track.h
     ../include/tools/inputmanager.h
+    ../include/tools/modifiertest.h
 )
 
 set(SOURCES
@@ -115,6 +116,7 @@ set(SOURCES
     inputstate.cpp
     track.cpp
     inputmanager.cpp
+    modifiertest.cpp
 )
 
 set(RESOURCES tnztools.qrc)
diff --git a/toonz/sources/tnztools/inputmanager.cpp b/toonz/sources/tnztools/inputmanager.cpp
index 13069a7..61f7b2a 100644
--- a/toonz/sources/tnztools/inputmanager.cpp
+++ b/toonz/sources/tnztools/inputmanager.cpp
@@ -23,6 +23,49 @@ TInputModifier::setManager(TInputManager *manager) {
 
 
 void
+TInputModifier::modifyTrack(
+  const TTrackP &track,
+  const TInputSavePoint::Holder &savePoint,
+  TTrackList &outTracks )
+{
+  if (!track->handler) {
+      track->handler = new TTrackHandler(*track);
+      track->handler->tracks.push_back(
+        new TTrack(
+          new TTrackModifier(*track->handler) ));
+  }
+
+  outTracks.insert(
+    outTracks.end(),
+    track->handler->tracks.begin(),
+    track->handler->tracks.end() );
+  if (!track->changed())
+    return;
+
+  int start = track->size() - track->pointsAdded;
+  if (start < 0) start = 0;
+
+  for(TTrackList::const_iterator ti = track->handler->tracks.begin(); ti != track->handler->tracks.end(); ++ti) {
+    TTrack &subTrack = **ti;
+
+    // remove points
+    if (start < track->size()) {
+      subTrack.pointsRemoved += subTrack.size() - start;
+      subTrack.truncate(start);
+    }
+
+    // add points
+    for(int i = start; i < track->size(); ++i)
+      subTrack.push_back( subTrack.modifier->calcPoint(i) );
+    subTrack.pointsAdded += subTrack.size() - start;
+  }
+
+  track->pointsRemoved = 0;
+  track->pointsAdded = 0;
+}
+
+
+void
 TInputModifier::modifyTracks(
     const TTrackList &tracks,
     const TInputSavePoint::Holder &savePoint,
@@ -34,6 +77,15 @@ TInputModifier::modifyTracks(
 
 
 void
+TInputModifier::modifyHover(
+  const TPointD &hover,
+  THoverList &outHovers )
+{
+  outHovers.push_back(hover);
+}
+
+
+void
 TInputModifier::modifyHovers(
   const THoverList &hovers,
   THoverList &outHovers )
diff --git a/toonz/sources/tnztools/modifiertest.cpp b/toonz/sources/tnztools/modifiertest.cpp
new file mode 100644
index 0000000..c74cf17
--- /dev/null
+++ b/toonz/sources/tnztools/modifiertest.cpp
@@ -0,0 +1,157 @@
+
+
+#include <tools/modifiertest.h>
+
+
+//*****************************************************************************************
+//    TModifierTest::Modifier implementation
+//*****************************************************************************************
+
+
+TModifierTest::Modifier::Modifier(
+  TTrackHandler &handler,
+  double angle,
+  double radius,
+  double speed
+):
+  TTrackModifier(handler),
+  angle(angle),
+  radius(radius),
+  speed(speed)
+{ }
+
+
+TTrackPoint
+TModifierTest::Modifier::calcPoint(double originalIndex) {
+  TTrackPoint p = TTrackModifier::calcPoint(originalIndex);
+
+  if (p.length > 2.0) {
+    double frac;
+    int i0 = original.floorIndex(originalIndex, &frac);
+    int i1 = original.ceilIndex(originalIndex);
+    if (i0 < 0) return p;
+
+    if (Handler *handler = dynamic_cast<Handler*>(&this->handler)) {
+      double angle = this->angle + speed*TTrack::interpolationLinear(
+        handler->angles[i0], handler->angles[i1], frac);
+      double radius = 2.0*this->radius*p.pressure;
+      double s = sin(angle);
+      double c = cos(angle);
+
+      TPointD tangent = TPointD(1.0, 0.0); // original.calcTangent(originalIndex, fabs(2.0*this->radius/speed));
+      p.position.x += radius*(c*tangent.x - s*tangent.y);
+      p.position.y += radius*(s*tangent.x + c*tangent.y);
+      p.pressure   *= 0.5*(1.0 + c);
+    }
+  } else {
+    p.pressure = 0.0;
+  }
+
+  return p;
+}
+
+
+//*****************************************************************************************
+//    TModifierTest implementation
+//*****************************************************************************************
+
+
+TModifierTest::TModifierTest():
+  count(1),
+  radius(40.0)
+{ }
+
+
+void
+TModifierTest::modifyTrack(
+  const TTrackP &track,
+  const TInputSavePoint::Holder &savePoint,
+  TTrackList &outTracks )
+{
+  const double segmentSize = M_PI/180.0*10.0;
+
+  if (!track->handler) {
+    if (track->getCurrentKeyState().isPressed(TKey(Qt::Key_T))) {
+      // TModifierTest::Handler for spiro
+      track->handler = new Handler(*track);
+      for(int i = 0; i < count; ++i)
+        track->handler->tracks.push_back(
+          new TTrack(
+            new Modifier(
+              *track->handler,
+              i*2.0*M_PI/(double)count,
+              radius,
+              2.0 )));
+    }
+  }
+
+  Handler *handler = dynamic_cast<Handler*>(track->handler.getPointer());
+  if (!handler) {
+    TInputModifier::modifyTrack(track, savePoint, outTracks);
+    return;
+  }
+
+  outTracks.insert(
+    outTracks.end(),
+    track->handler->tracks.begin(),
+    track->handler->tracks.end() );
+  if (!track->changed())
+    return;
+
+  int start = track->size() - track->pointsAdded;
+  if (start < 0) start = 0;
+
+  // remove angles
+  if (start < (int)handler->angles.size())
+    handler->angles.erase(
+      handler->angles.begin() + start,
+      handler->angles.end() );
+
+  // add angles
+  for(int i = start; i < track->size(); ++i) {
+    if (i > 0) {
+      double dl = (*track)[i].length - (*track)[i-1].length;
+      double da = (*track)[i].pressure > TTrack::epsilon
+                ? dl/(2.0*radius*(*track)[i].pressure) : 0.0;
+      handler->angles.push_back(handler->angles[i-1] + da);
+    } else {
+      handler->angles.push_back(0.0);
+    }
+  }
+
+  // process sub-tracks
+  for(TTrackList::const_iterator ti = handler->tracks.begin(); ti != handler->tracks.end(); ++ti) {
+    TTrack &subTrack = **ti;
+
+    // remove points
+    int subStart = subTrack.floorIndex(subTrack.indexByOriginalIndex(start));
+    if (subStart < 0) subStart = 0;
+    if (subStart < subTrack.size() && subTrack[subStart].originalIndex + TTrack::epsilon < start)
+      ++subStart;
+
+    if (subStart < subTrack.size()) {
+      subTrack.pointsRemoved += subTrack.size() - subStart;
+      subTrack.truncate(subStart);
+    }
+
+    // add points
+    for(int i = start; i < track->size(); ++i) {
+      if (i > 0) {
+        double prevAngle = handler->angles[i-1];
+        double nextAngle = handler->angles[i];
+        if (fabs(nextAngle - prevAngle) > 1.5*segmentSize) {
+          double step = segmentSize/fabs(nextAngle - prevAngle);
+          double end = 1.0 - 0.5*step;
+          for(double frac = step; frac < end; frac += step)
+            subTrack.push_back( subTrack.modifier->calcPoint((double)i - 1.0 + frac) );
+        }
+      }
+      subTrack.push_back( subTrack.modifier->calcPoint(i) );
+    }
+    subTrack.pointsAdded += subTrack.size() - subStart;
+  }
+
+  track->pointsRemoved = 0;
+  track->pointsAdded = 0;
+}
+
diff --git a/toonz/sources/tnztools/track.cpp b/toonz/sources/tnztools/track.cpp
index 27d8b1e..6193c8a 100644
--- a/toonz/sources/tnztools/track.cpp
+++ b/toonz/sources/tnztools/track.cpp
@@ -54,6 +54,7 @@ TTrack::TTrack(const TTrackModifierP &modifier):
   buttonHistory(modifier->original.buttonHistory),
   hasPressure(modifier->original.hasPressure),
   hasTilt(modifier->original.hasTilt),
+  modifier(modifier),
   pointsRemoved(),
   pointsAdded()
   { }
@@ -71,13 +72,17 @@ TTrack::level() const
   { return original() ? original()->level() + 1 : 0; }
 
 int
-TTrack::floorIndex(double index, double &outFrac) const {
+TTrack::floorIndex(double index, double *outFrac) const {
   int i = (int)floor(index + epsilon);
-  if (i > size() - 1)
-    { outFrac = 0.0; return size() - 1; }
-  if (i < 0)
-    { outFrac = 0.0; return 0; }
-  outFrac = std::max(0.0, index - (double)i);
+  if (i > size() - 1) {
+    if (outFrac) *outFrac = 0.0;
+    return size() - 1;
+  }
+  if (i < 0) {
+    if (outFrac) *outFrac = 0.0;
+    return 0;
+  }
+  if (outFrac) *outFrac = std::max(0.0, index - (double)i);
   return i;
 }