[dasher: 9/28] Refactor control-mode slowdown, optimize Turbo Mode, remove LP_BOOSTFACTOR



commit efb1f2308b88e346bfd9e6728d7f524b54653efc
Author: Alan Lawrence <acl33 inf phy cam ac uk>
Date:   Wed Sep 7 08:41:52 2011 +0100

    Refactor control-mode slowdown, optimize Turbo Mode, remove LP_BOOSTFACTOR
    
    Adding CDasherNode::SpeedMul(), using in DynamicFilter::FrameSpeedMul (which
     also does slow start + turbo mode), via CDasherModel::Get_node_under_crosshair
     (which returns last-output node)
    
    This will cause some minor changes when inside control nodes (e.g. two-push
     guide areas being drawn wrong)....

 Src/DasherCore/ControlManager.cpp            |   11 -------
 Src/DasherCore/ControlManager.h              |    4 +--
 Src/DasherCore/DasherModel.cpp               |   15 +++-------
 Src/DasherCore/DasherModel.h                 |    5 +++
 Src/DasherCore/DasherNode.h                  |   11 +++++--
 Src/DasherCore/DefaultFilter.cpp             |    4 +-
 Src/DasherCore/DynamicButtons.cpp            |    2 +-
 Src/DasherCore/DynamicFilter.cpp             |   38 +++++++++++++++++++------
 Src/DasherCore/DynamicFilter.h               |   20 ++++++++++++-
 Src/DasherCore/FrameRate.cpp                 |   23 +++++----------
 Src/DasherCore/FrameRate.h                   |   23 +++++++---------
 Src/DasherCore/OneButtonDynamicFilter.cpp    |    2 +-
 Src/DasherCore/Parameters.cpp                |    1 -
 Src/DasherCore/Parameters.h                  |    2 +-
 Src/DasherCore/TwoButtonDynamicFilter.cpp    |   14 +++------
 Src/DasherCore/TwoButtonDynamicFilter.h      |    2 +-
 Src/DasherCore/TwoPushDynamicFilter.cpp      |   35 +++++++++++++----------
 Src/iPhone/Classes/CDasherInterfaceBridge.mm |    2 +-
 18 files changed, 116 insertions(+), 98 deletions(-)
---
diff --git a/Src/DasherCore/ControlManager.cpp b/Src/DasherCore/ControlManager.cpp
index 5e8828b..458a905 100644
--- a/Src/DasherCore/ControlManager.cpp
+++ b/Src/DasherCore/ControlManager.cpp
@@ -124,17 +124,6 @@ void CControlBase::CContNode::Output() {
   m_pTemplate->happen(this);
 }
 
-void CControlBase::CContNode::Enter() {
-  // Slow down to half the speed we were at. This also disables auto-speed-control.
-  m_pMgr->SetLongParameter(LP_BOOSTFACTOR, 50);
-}
-
-
-void CControlBase::CContNode::Leave() {
-  // Now speed back up, by doubling the speed we were at in control mode
-  m_pMgr->SetLongParameter(LP_BOOSTFACTOR, 100);
-}
-
 const vector<CControlBase::NodeTemplate *> &CControlParser::parsedNodes() {
   return m_vParsed;
 }
diff --git a/Src/DasherCore/ControlManager.h b/Src/DasherCore/ControlManager.h
index 37c9beb..a07b58f 100644
--- a/Src/DasherCore/ControlManager.h
+++ b/Src/DasherCore/ControlManager.h
@@ -63,6 +63,7 @@ namespace Dasher {
       CContNode(CDasherNode *pParent, int iOffset, unsigned int iLbnd, unsigned int iHbnd, NodeTemplate *pTemplate, CControlBase *pMgr);
 
       bool bShove() {return false;}
+      double SpeedMul() {return 0.5;}
       ///
       /// Provide children for the supplied node
       ///
@@ -72,9 +73,6 @@ namespace Dasher {
 
       virtual void Output();
 
-      virtual void Enter();
-      virtual void Leave();
-
     private:
       NodeTemplate *m_pTemplate;
       CControlBase *m_pMgr;
diff --git a/Src/DasherCore/DasherModel.cpp b/Src/DasherCore/DasherModel.cpp
index 1127e8d..a2d5d63 100644
--- a/Src/DasherCore/DasherModel.cpp
+++ b/Src/DasherCore/DasherModel.cpp
@@ -75,8 +75,6 @@ CDasherModel::CDasherModel(CSettingsUser *pCreateFrom)
 }
 
 CDasherModel::~CDasherModel() {
-  if (m_pLastOutput) m_pLastOutput->Leave();
-
   if(oldroots.size() > 0) {
     delete oldroots[0];
     oldroots.clear();
@@ -203,8 +201,6 @@ void CDasherModel::SetOffset(int iOffset, CAlphabetManager *pMgr, CDasherView *p
   // (if we have a root, only rebuild to move location or if bForce says to)
   if (m_Root && iOffset == GetOffset() && !bForce) return;
 
-  if (m_pLastOutput) m_pLastOutput->Leave();
-
   ClearRootQueue();
   delete m_Root;
 
@@ -215,7 +211,6 @@ void CDasherModel::SetOffset(int iOffset, CAlphabetManager *pMgr, CDasherView *p
     // rather than a character node (responsible for the last said preceding character),
     // but even so, it seems fair enough to say we've "seen" the root:
     m_Root->SetFlag(NF_SEEN, true);
-    m_Root->Enter();
     // (of course, we don't do Output() - the context contains it already!)
     m_pLastOutput = m_Root;
 
@@ -250,6 +245,10 @@ int CDasherModel::GetOffset() {
   return m_pLastOutput ? m_pLastOutput->offset()+1 : m_Root ? m_Root->offset()+1 : 0;
 };
 
+CDasherNode *CDasherModel::Get_node_under_crosshair() {
+  return m_pLastOutput;
+}
+
 void CDasherModel::Get_new_root_coords(dasherint X, dasherint Y, dasherint &r1, dasherint &r2, int iSteps, dasherint iMinSize) {
   DASHER_ASSERT(m_Root != NULL);
   // Avoid X == 0, as this corresponds to infinite zoom
@@ -432,8 +431,6 @@ void CDasherModel::OutputTo(CDasherNode *pNewNode) {
   //first, recurse back up to last seen node (must be processed ancestor-first)
   if (pNewNode && !pNewNode->GetFlag(NF_SEEN)) {
     OutputTo(pNewNode->Parent());
-    if (pNewNode->Parent()) pNewNode->Parent()->Leave();
-    pNewNode->Enter();
 
     m_pLastOutput = pNewNode;
     pNewNode->Output();
@@ -446,12 +443,10 @@ void CDasherModel::OutputTo(CDasherNode *pNewNode) {
       // so we should encounter it on the way back out to the root, _before_ null
       m_pLastOutput->SetFlag(NF_COMMITTED, false);
       m_pLastOutput->Undo();
-      m_pLastOutput->Leave(); //Should we? I think so, but the old code didn't...?
       m_pLastOutput->SetFlag(NF_SEEN, false);
 
       m_pLastOutput = m_pLastOutput->Parent();
-      if (m_pLastOutput) m_pLastOutput->Enter();
-      else DASHER_ASSERT (!pNewNode); //both null
+      DASHER_ASSERT(m_pLastOutput || !pNewNode); //if m_pLastOutput null, then pNewNode is too.
     }
   }
 }
diff --git a/Src/DasherCore/DasherModel.h b/Src/DasherCore/DasherModel.h
index 529bab3..78af168 100644
--- a/Src/DasherCore/DasherModel.h
+++ b/Src/DasherCore/DasherModel.h
@@ -154,6 +154,11 @@ class Dasher::CDasherModel: private CSettingsUser, public Observable<CDasherNode
 
   /// @}
 
+  /// Returns the node that was under the crosshair in the
+  /// last frame that was rendered. (I.e., this is the last
+  /// node output.)
+  CDasherNode *Get_node_under_crosshair();
+  
   ///
   /// This is pretty horrible - a rethink of the start/reset mechanism
   /// is definitely in order. Used to prevent the root node from being
diff --git a/Src/DasherCore/DasherNode.h b/Src/DasherCore/DasherNode.h
index b48cfb5..4984d60 100644
--- a/Src/DasherCore/DasherNode.h
+++ b/Src/DasherCore/DasherNode.h
@@ -95,6 +95,14 @@ class Dasher::CDasherNode:private NoClones {
   /// (Default implementation returns true, subclasses should override if appropriate)
   virtual bool bShove() {return true;}
 
+  ///Multiplier to apply to the speed (in dynamic modes, inc. default mouse
+  /// control) when the crosshair is inside this node (but not inside any child.)
+  /// This creates a sort of "viscosity", i.e. makes some nodes harder to move
+  /// through than others - used to slow down movement inside control nodes,
+  /// making it harder to make mistakes therein. The default just returns 1.0,
+  /// i.e. no change.
+  virtual double SpeedMul() {return 1.0;}
+  
   inline int offset() const {return m_iOffset;}
   CDasherNode *onlyChildRendered; //cache that only one child was rendered (as it filled the screen)
 
@@ -249,9 +257,6 @@ class Dasher::CDasherNode:private NoClones {
     return SymbolProb(0,m_pLabel->m_strText,0.0);
   }
 
-  virtual void Enter() {};
-  virtual void Leave() {};
-
   virtual CDasherNode *RebuildParent() {
     return 0;
   };
diff --git a/Src/DasherCore/DefaultFilter.cpp b/Src/DasherCore/DefaultFilter.cpp
index 8707e45..300a1b2 100644
--- a/Src/DasherCore/DefaultFilter.cpp
+++ b/Src/DasherCore/DefaultFilter.cpp
@@ -129,13 +129,13 @@ bool CDefaultFilter::Timer(unsigned long Time, CDasherView *pView, CDasherInput
       }
     }
 
-    double dSpeedMul(SlowStartSpeedMul(Time));
+    double dSpeedMul(FrameSpeedMul(m_pDasherModel, Time));
     if (m_bTurbo) dSpeedMul*=1.75;
     
     OneStepTowards(m_pDasherModel, m_iLastX,m_iLastY, Time, dSpeedMul);
     bDidSomething = true;
 
-    if (GetLongParameter(LP_BOOSTFACTOR)==100 && dSpeedMul==1.0)
+    if (dSpeedMul==1.0)
       m_pAutoSpeedControl->SpeedControl(m_iLastX, m_iLastY, pView);
   }
 
diff --git a/Src/DasherCore/DynamicButtons.cpp b/Src/DasherCore/DynamicButtons.cpp
index f241b1c..109102e 100644
--- a/Src/DasherCore/DynamicButtons.cpp
+++ b/Src/DasherCore/DynamicButtons.cpp
@@ -39,7 +39,7 @@ bool CDynamicButtons::Timer(unsigned long iTime, CDasherView *pDasherView, CDash
   }
   if (isPaused()) return false;
   if (isReversing()) {
-    OneStepTowards(m_pDasherModel, 41943,2048, iTime, SlowStartSpeedMul(iTime));
+    OneStepTowards(m_pDasherModel, 41943,2048, iTime, FrameSpeedMul(m_pDasherModel, iTime));
     return true;
   }
   //moving forwards. Check auto speed control...
diff --git a/Src/DasherCore/DynamicFilter.cpp b/Src/DasherCore/DynamicFilter.cpp
index 026f17c..5ebff90 100644
--- a/Src/DasherCore/DynamicFilter.cpp
+++ b/Src/DasherCore/DynamicFilter.cpp
@@ -23,28 +23,48 @@
 
 using namespace Dasher;
 
-CDynamicFilter::CDynamicFilter(CSettingsUser *pCreator, CDasherInterfaceBase *pInterface, CFrameRate *pFramerate, ModuleID_t iID, const char *szName) : CInputFilter(pInterface, iID, szName), CSettingsUser(pCreator), m_pFramerate(pFramerate) {
+CDynamicFilter::CDynamicFilter(CSettingsUser *pCreator, CDasherInterfaceBase *pInterface, CFrameRate *pFramerate, ModuleID_t iID, const char *szName)
+: CInputFilter(pInterface, iID, szName), CSettingsUser(pCreator),
+m_pFramerate(pFramerate), m_dLastBits(-1) {
 }
 
 bool CDynamicFilter::OneStepTowards(CDasherModel *pModel, myint y1, myint y2, unsigned long iTime, double dSpeedMul) {
   if (dSpeedMul<=0.0) return false; //going nowhere
   m_pFramerate->RecordFrame(iTime); //Hmmm, even if we don't do anything else?
 
-  double dRXMax = m_pFramerate->GetMaxZoomFactor();
-  // Adjust for slow start etc. TODO: can we fix to use integer math (or at least no pow?)
-  if (dSpeedMul!=1.0) dRXMax=pow(dRXMax, dSpeedMul);
+  //The maximum number of bits we should allow to be entered in this frame:
+  // (after adjusting for slow start, turbo mode, control node slowdown, etc.)
+  double dBits = m_pFramerate->GetMaxBitsPerFrame()*dSpeedMul;
+
+  //Compute max expansion, i.e. the minimum size we should allow the range 0..MAX_Y
+  // to be shrunk down to, for this frame. We cache the most-recent result to
+  // avoid an exp() (and a division): in the majority of cases this doesn't change
+  // between frames, but only does so when the maxbitrate changes, or dspeedmul
+  // changes (e.g. continuously during slow start, or when entering/leaving turbo
+  // mode or a control node).
+  if (dBits != m_dLastBits) m_iLastMinSize = static_cast<myint>(CDasherModel::MAX_Y / exp(m_dLastBits = dBits));
+  //However, note measurements on iPhone suggest even one exp() per frame is not
+  // a significant overhead; so the caching may be unnecessary, but it's easy.
   
-  pModel->OneStepTowards(y1, y2, static_cast<int>(m_pFramerate->Steps() / dSpeedMul), static_cast<myint>(CDasherModel::MAX_Y/dRXMax));
+  //If we wanted to take things further we could generalize this cache to cover
+  // exp()s done in the dynamic button modes too, and thus to allow them to adjust
+  // lag, guide markers, etc., according to the dSpeedMul in use. (And/or
+  // to do slow-start more efficiently by interpolating cache values.)
+  pModel->OneStepTowards(y1, y2,
+                         static_cast<int>(m_pFramerate->Steps() / dSpeedMul),
+                         m_iLastMinSize);
   return true;
 }
 
-double CDynamicFilter::SlowStartSpeedMul(unsigned long iTime) {
+double CDynamicFilter::FrameSpeedMul(CDasherModel *pModel, unsigned long iTime) {
+  CDasherNode *n = pModel->Get_node_under_crosshair();
+  double d = n ? n->SpeedMul() : 1.0;
   if(GetBoolParameter(BP_SLOW_START)) {
     if ((iTime - m_iStartTime) < GetLongParameter(LP_SLOW_START_TIME))
-      return 0.1 * (1 + 9 * ((iTime - m_iStartTime) / static_cast<double>(GetLongParameter(LP_SLOW_START_TIME))));
+      d *= 0.1 * (1 + 9 * ((iTime - m_iStartTime) / static_cast<double>(GetLongParameter(LP_SLOW_START_TIME))));
   }
-  //no slow start, or finished.
-  return 1.0;
+  //else, no slow start, or finished.
+  return d;
 }
 
 void CDynamicFilter::Unpause(unsigned long Time) {
diff --git a/Src/DasherCore/DynamicFilter.h b/Src/DasherCore/DynamicFilter.h
index 33791e9..0569a57 100644
--- a/Src/DasherCore/DynamicFilter.h
+++ b/Src/DasherCore/DynamicFilter.h
@@ -40,18 +40,34 @@ class CDynamicFilter : public CInputFilter, public CSettingsUser {
   virtual bool supportsPause() {return true;}
 
  protected:
+  ///wraps Model's one-step method to compute the number of steps and minsize
+  /// (from framerate) that the Model requires, from just the frame time and a
+  /// multiplier to speed.
+  /// \param dSpeedMul multiply normal speed of movement by this; 1.0 = normal speed,
+  /// 0.0 = go nowhere. This allows for slow start, turbo mode, control nodes being
+  /// more "viscous", etc. Values <=0.0 will result in no movement
+  /// \return true if dSpeedMul>0.0, false if <=0.0.
   bool OneStepTowards(CDasherModel *pModel, myint y1, myint y2, unsigned long iTime, double dSpeedMul);
-  double SlowStartSpeedMul(unsigned long iTime);
+  
+  ///Calculates a multiplier by which to adjust our speed (for a given frame).
+  /// Defalut implementation implements slow-start (i.e. a multiplier increasing
+  /// from zero to one over the slow-start-time) as well as by checking the speedMul
+  /// of the node under the cursor.
+  virtual double FrameSpeedMul(CDasherModel *pModel, unsigned long iTime);
 
   /// Starts moving.  Clears BP_DASHER_PAUSED.
   /// (But does nothing if BP_DASHER_PAUSED is currently set).
   /// \param Time Time in ms, used to keep a constant frame rate and
   /// initialize slow start.
   void Unpause(unsigned long iTime);
+  
+  CFrameRate * const m_pFramerate;
  private:
-  CFrameRate *m_pFramerate;
   //Time at which Unpause() was called, used for Slow Start.
   unsigned long m_iStartTime;
+  //Number of bits (we allowed) to be entered in previous frame - to cache exp()
+  double m_dLastBits;
+  double m_iLastMinSize;
 };
 }
 #endif
diff --git a/Src/DasherCore/FrameRate.cpp b/Src/DasherCore/FrameRate.cpp
index e2b951b..49242bd 100644
--- a/Src/DasherCore/FrameRate.cpp
+++ b/Src/DasherCore/FrameRate.cpp
@@ -11,7 +11,6 @@ CFrameRate::CFrameRate(CSettingsUser *pCreator) :
   m_iTime = 0;
 
   //try and carry on from where we left off at last run
-  HandleEvent(LP_FRAMERATE);
   HandleEvent(LP_MAX_BITRATE);
   //calls UpdateSteps(), which sets m_dRXMax and m_iSteps
 }
@@ -40,6 +39,7 @@ void CFrameRate::RecordFrame(unsigned long Time)
     double dFrNow;
     if(m_iTime2 - m_iTime > 0) {
       dFrNow = m_iFrames * 1000.0 / (m_iTime2 - m_iTime);
+      //LP_FRAMERATE records a decaying average, smoothed 50:50 with previous value
       SetLongParameter(LP_FRAMERATE, long(GetLongParameter(LP_FRAMERATE) + (dFrNow*100))/2);
       m_iTime = m_iTime2;
       m_iFrames = 0;
@@ -48,7 +48,7 @@ void CFrameRate::RecordFrame(unsigned long Time)
 
     UpdateSteps(dFrNow);
 
-    DASHER_TRACEOUTPUT("Fr %f Steps %d Samples %d Time2 %d rxmax %f\n", dFrNow, m_iSteps, m_iSamples, m_iTime2, m_dRXmax);
+    DASHER_TRACEOUTPUT("Fr %f Steps %d Samples %d Time2 %d maxbits %f\n", dFrNow, m_iSteps, m_iSamples, m_iTime2, m_dFrameBits);
 
   }
 }
@@ -56,13 +56,9 @@ void CFrameRate::RecordFrame(unsigned long Time)
 void CFrameRate::HandleEvent(int iParameter) {
 
   switch (iParameter) {
-  case LP_MAX_BITRATE: // Delibarate fallthrough
-  case LP_BOOSTFACTOR:
-    m_dMaxbitrate=(GetLongParameter(LP_MAX_BITRATE) * GetLongParameter(LP_BOOSTFACTOR)) / 10000.0;
+  case LP_MAX_BITRATE:
     UpdateSteps(GetLongParameter(LP_FRAMERATE) / 100.0); //use the decaying average as current
     break;
-  case LP_FRAMERATE:
-    m_dFrDecay = GetLongParameter(LP_FRAMERATE) / 100.0;
   }
 }
 
@@ -70,16 +66,13 @@ const double LN2 = log(2.0);
 const double STEPS_COEFF = -log(0.2) / LN2;
 
 void CFrameRate::UpdateSteps(double dFrNow) {
+  const double dMaxbitrate = GetLongParameter(LP_MAX_BITRATE) / 100.0;
     // Update auxiliary variables - even if we didn't recalc the framerate
     //   (means we reach sensible values more quickly after first loading)
-    m_dRXmax = exp(m_dMaxbitrate * LN2 / dFrNow);
+    m_dFrameBits = dMaxbitrate * LN2 / dFrNow;
     
-    // Note that m_iSteps is smoothed here - 50:50 interpolation with
-    // previous value
-    m_iSteps = std::max(1,(int)(STEPS_COEFF * m_dFrDecay / m_dMaxbitrate));
-
-    // If the framerate slows to < 4 then we end up with steps < 1 ! 
-    if(m_iSteps == 0)
-      m_iSteps = 1;
+    //Calculate m_iSteps from the decaying-average framerate, and ensure
+    // it is at least 1 (else, if framerate slows to <4, we get 0 steps!)
+    m_iSteps = std::max(1,(int)(STEPS_COEFF * (GetLongParameter(LP_FRAMERATE)/100.0) / dMaxbitrate));
 
 }
diff --git a/Src/DasherCore/FrameRate.h b/Src/DasherCore/FrameRate.h
index 8f66a6f..31ed87b 100644
--- a/Src/DasherCore/FrameRate.h
+++ b/Src/DasherCore/FrameRate.h
@@ -28,20 +28,20 @@ public:
   virtual void HandleEvent(int iParameter);
 
   ///The maximum amount by which one frame may zoom in. Used as a hard
-  /// upper-bound on the approximate one-step calculation done in DasherModel
-  /// to ensure we never exceed the set LP_MAX_BITRATE.
-  double GetMaxZoomFactor() {
-    return m_dRXmax;
+  /// upper-bound on the speed of movement (however far to RHS the cursor is),
+  /// calculated from the most-recent (instantaneous) framerate rather than
+  /// the decaying average used for the Steps() parameter.
+  double GetMaxBitsPerFrame() {
+    return m_dFrameBits;
   }
 
+  ///The number of frames, in which we will attempt to bring
+  /// the target location (under the cursor, or in dynamic button
+  /// modes) to the crosshair. See DJW thesis.
   int Steps() const {
     return m_iSteps;
   }; 
 
-  double Bitrate() const {
-    return m_dMaxbitrate;
-  }
-
   ///
   /// Reset the framerate class
   /// TODO: Need to check semantics here
@@ -55,12 +55,9 @@ public:
   void RecordFrame(unsigned long Time);
 
   bool OneStepTowards(CDasherModel *pModel, myint y1, myint y2, unsigned long iTime, double dSpeedMul);
-  double SlowStartSpeedMul(unsigned long iTime);
   
 private:
-  double m_dFrDecay;            // current frame rate (cache of LP_FRAMERATE/100.0)
-  double m_dMaxbitrate;         // the maximum rate of entering information (cache)
-  double m_dRXmax;              // the maximum zoomin per frame
+  double m_dFrameBits;              // the maximum zoomin per frame
   ///number of frames that have been sampled
   int m_iFrames;
   ///time at which first sampled frame was rendered
@@ -68,7 +65,7 @@ private:
   ///number of frames over which we will compute average framerate
   int m_iSamples;
 
-  int m_iSteps;                 // the 'Steps' parameter. See djw thesis.
+  int m_iSteps;
 
   ///updates m_dRXMax and m_iSteps
   /// \param dFrNow current (non-decaying-average) framerate (if available!)
diff --git a/Src/DasherCore/OneButtonDynamicFilter.cpp b/Src/DasherCore/OneButtonDynamicFilter.cpp
index dec1b31..a72a49a 100644
--- a/Src/DasherCore/OneButtonDynamicFilter.cpp
+++ b/Src/DasherCore/OneButtonDynamicFilter.cpp
@@ -109,7 +109,7 @@ void COneButtonDynamicFilter::KeyUp(unsigned long Time, int iId, CDasherView *pD
 }
 
 bool COneButtonDynamicFilter::TimerImpl(unsigned long Time, CDasherView *m_pDasherView, CDasherModel *m_pDasherModel, CExpansionPolicy **pol) {
-  OneStepTowards(m_pDasherModel, m_iTargetX[m_iTarget], m_iTargetY[m_iTarget], Time, SlowStartSpeedMul(Time));
+  OneStepTowards(m_pDasherModel, m_iTargetX[m_iTarget], m_iTargetY[m_iTarget], Time, FrameSpeedMul(m_pDasherModel, Time));
   return true;
 }
 
diff --git a/Src/DasherCore/Parameters.cpp b/Src/DasherCore/Parameters.cpp
index 9be39c1..bf81a98 100644
--- a/Src/DasherCore/Parameters.cpp
+++ b/Src/DasherCore/Parameters.cpp
@@ -108,7 +108,6 @@ const lp_table longparamtable[] = {
 #else
   {LP_NONLINEAR_X, "NonLinearX", PERS, 5, "Nonlinear compression of X-axis (0 = none, higher = more extreme)"},
 #endif
-  {LP_BOOSTFACTOR, "BoostFactor", !PERS, 100, "Boost/brake factor (multiplied by 100)"},
   {LP_AUTOSPEED_SENSITIVITY, "AutospeedSensitivity", PERS, 100, "Sensitivity of automatic speed control (percent)"},
   {LP_SOCKET_PORT, "SocketPort", PERS, 20320, "UDP/TCP socket to use for network socket input"},
   {LP_SOCKET_INPUT_X_MIN, "SocketInputXMinTimes1000", PERS, 0, "Bottom of range of X values expected from network input"},
diff --git a/Src/DasherCore/Parameters.h b/Src/DasherCore/Parameters.h
index 0a171bd..2b77da3 100644
--- a/Src/DasherCore/Parameters.h
+++ b/Src/DasherCore/Parameters.h
@@ -55,7 +55,7 @@ enum {
   LP_LM_WORD_ALPHA, LP_USER_LOG_LEVEL_MASK, 
   LP_ZOOMSTEPS, LP_B, LP_S, LP_BUTTON_SCAN_TIME, LP_R, LP_RIGHTZOOM,
   LP_NODE_BUDGET, LP_OUTLINE_WIDTH, LP_MIN_NODE_SIZE, LP_NONLINEAR_X,
-  LP_BOOSTFACTOR, LP_AUTOSPEED_SENSITIVITY, LP_SOCKET_PORT, LP_SOCKET_INPUT_X_MIN, LP_SOCKET_INPUT_X_MAX,
+  LP_AUTOSPEED_SENSITIVITY, LP_SOCKET_PORT, LP_SOCKET_INPUT_X_MIN, LP_SOCKET_INPUT_X_MAX,
   LP_SOCKET_INPUT_Y_MIN, LP_SOCKET_INPUT_Y_MAX, LP_INPUT_FILTER, 
   LP_CIRCLE_PERCENT, LP_TWO_BUTTON_OFFSET, LP_HOLD_TIME, LP_MULTIPRESS_TIME,
   LP_SLOW_START_TIME, LP_CONVERSION_ORDER, LP_CONVERSION_TYPE,
diff --git a/Src/DasherCore/TwoButtonDynamicFilter.cpp b/Src/DasherCore/TwoButtonDynamicFilter.cpp
index ad3b4ea..9182827 100644
--- a/Src/DasherCore/TwoButtonDynamicFilter.cpp
+++ b/Src/DasherCore/TwoButtonDynamicFilter.cpp
@@ -51,7 +51,7 @@ static SModuleSettings sSettings[] = {
 CTwoButtonDynamicFilter::CTwoButtonDynamicFilter(CSettingsUser *pCreator, CDasherInterfaceBase *pInterface, CFrameRate *pFramerate)
   : CButtonMultiPress(pCreator, pInterface, pFramerate, 14, _("Two Button Dynamic Mode")), m_iMouseButton(-1)
 {
-  //ensure that m_dLagMul is properly initialised
+  //ensure that m_dLagBits is properly initialised
   HandleEvent(LP_DYNAMIC_BUTTON_LAG);
 }
 
@@ -118,7 +118,7 @@ void CTwoButtonDynamicFilter::KeyUp(unsigned long Time, int iId, CDasherView *pV
 }
 
 bool CTwoButtonDynamicFilter::TimerImpl(unsigned long Time, CDasherView *m_pDasherView, CDasherModel *m_pDasherModel, CExpansionPolicy **pol) {
-  OneStepTowards(m_pDasherModel, 100,2048, Time, SlowStartSpeedMul(Time));
+  OneStepTowards(m_pDasherModel, 100,2048, Time, FrameSpeedMul(m_pDasherModel, Time));
   return true;
 }
 
@@ -153,7 +153,7 @@ void CTwoButtonDynamicFilter::ActionButton(unsigned long iTime, int iButton, int
     return;
   }
   //fell through to apply offset
-  ApplyOffset(pModel,dFactor * GetLongParameter(LP_TWO_BUTTON_OFFSET) * m_dLagMul);
+  ApplyOffset(pModel,dFactor * GetLongParameter(LP_TWO_BUTTON_OFFSET) * exp(m_dLagBits * FrameSpeedMul(pModel, iTime)));
   pModel->ResetNats();
   
   if(CUserLogBase *pUserLog=m_pInterface->GetUserLogPtr())
@@ -175,14 +175,10 @@ bool CTwoButtonDynamicFilter::GetMinWidth(int &iMinWidth) {
 void CTwoButtonDynamicFilter::HandleEvent(int iParameter)
 {
   switch (iParameter) {
-  case LP_MAX_BITRATE:
-  case LP_BOOSTFACTOR: // Deliberate fallthrough
+  case LP_MAX_BITRATE:// Deliberate fallthrough
   case LP_DYNAMIC_BUTTON_LAG:
-    {
-      double dMaxRate = GetLongParameter(LP_MAX_BITRATE) * GetLongParameter(LP_BOOSTFACTOR) / 10000.0;
-      m_dLagMul = exp(dMaxRate * GetLongParameter(LP_DYNAMIC_BUTTON_LAG)/1000.0);
+      m_dLagBits = GetLongParameter(LP_MAX_BITRATE)/100.0 * GetLongParameter(LP_DYNAMIC_BUTTON_LAG)/1000.0;
       //and fallthrough again:
-    }
   case LP_TWO_BUTTON_OFFSET:
       m_bDecorationChanged = true;
   }
diff --git a/Src/DasherCore/TwoButtonDynamicFilter.h b/Src/DasherCore/TwoButtonDynamicFilter.h
index d4669b4..ab7fe77 100644
--- a/Src/DasherCore/TwoButtonDynamicFilter.h
+++ b/Src/DasherCore/TwoButtonDynamicFilter.h
@@ -50,7 +50,7 @@ class CTwoButtonDynamicFilter : public CButtonMultiPress {
   unsigned int maxClickCount() {return GetBoolParameter(BP_2B_INVERT_DOUBLE) ? 3 : 2;}
   virtual bool TimerImpl(unsigned long Time, CDasherView *m_pDasherView, CDasherModel *m_pDasherModel, CExpansionPolicy **pol);
   virtual void ActionButton(unsigned long iTime, int iButton, int iType, CDasherModel *pModel);
-  double m_dLagMul;
+  double m_dLagBits;
   ///id of physical key, whose pressing we have emulated, in response
   /// to a mouse down event on one or other half of the canvas...
   int m_iMouseButton;
diff --git a/Src/DasherCore/TwoPushDynamicFilter.cpp b/Src/DasherCore/TwoPushDynamicFilter.cpp
index e7feef7..059241b 100644
--- a/Src/DasherCore/TwoPushDynamicFilter.cpp
+++ b/Src/DasherCore/TwoPushDynamicFilter.cpp
@@ -135,12 +135,15 @@ m_dLogUpMul = log(dOuter / (double)GetLongParameter(LP_TWO_PUSH_UP));
 m_dLogDownMul = log(dOuter / (double)GetLongParameter(LP_TWO_PUSH_DOWN));
 //cout << "bitsUp " << m_dLogUpMul << " bitsDown " << m_dLogDownMul << "\n";
     } //and fallthrough
-    case LP_TWO_PUSH_TOLERANCE:
+    case LP_TWO_PUSH_TOLERANCE: //deliberate fallthrough
     case LP_MAX_BITRATE:
-    case LP_BOOSTFACTOR: // Deliberate fallthrough
     {
-double dMaxRate = GetLongParameter(LP_MAX_BITRATE) * GetLongParameter(LP_BOOSTFACTOR) / 10000.0;
-double dPressBits = dMaxRate * (double) GetLongParameter(LP_TWO_PUSH_TOLERANCE) / 1000.0;
+      //dPressBits just measures the number of bits which would be output in the
+      // tolerance time, at full (100%) speed; note it does not take account of
+      // the SpeedMul (viscosity) of the node under the cursor (or Slow Start, etc.)
+      // - iow, when we are moving slowly for such a reason, we'll be proportionately
+      // _more_ tolerant of user inaccuracy in button pushing...
+double dPressBits = GetLongParameter(LP_MAX_BITRATE)/100.0 * (double) GetLongParameter(LP_TWO_PUSH_TOLERANCE) / 1000.0;
 //cout << "Max Bitrate changed - now " << dMaxRate << " user accuracy " << dPressBits;
 m_dMinShortTwoPushTime = m_dLogUpMul - dPressBits;
 //cout << "bits; minShort " << m_dMinShortTwoPushTime;
@@ -154,16 +157,17 @@ m_dMaxLongTwoPushTime = m_dLogDownMul + dPressBits;
 m_bDecorationChanged = true;
    }  //and fallthrough again
    case LP_DYNAMIC_BUTTON_LAG:
-   {
-     double dMaxRate = GetLongParameter(LP_MAX_BITRATE) * GetLongParameter(LP_BOOSTFACTOR) / 10000.0;
-     m_dLagBits = dMaxRate * GetLongParameter(LP_DYNAMIC_BUTTON_LAG)/1000.0;
+     m_dLagBits = GetLongParameter(LP_MAX_BITRATE)/100.0 * GetLongParameter(LP_DYNAMIC_BUTTON_LAG)/1000.0;
 //cout << " lag (" << m_dLagBits[0] << ", " << m_dLagBits[1] << ", " << m_dLagBits[2] << ", " << m_dLagBits[3] << ")";
+      //these areas should really be calculated using short/long push-times modified by the
+      // current FrameSpeedMul, which we'd have to do every frame. For now I'm not, so the guide
+      // areas will be wrong when the speed multiplier is other than 1.0. TODO reconsider, esp.
+      // wrt. possibly memoizing exp() in CDynamicFilter?
      m_aaiGuideAreas[0][0] = 2048 - GetLongParameter(LP_TWO_PUSH_UP)*exp(m_dMaxShortTwoPushTime);
      m_aaiGuideAreas[0][1] = 2048 - GetLongParameter(LP_TWO_PUSH_UP)*exp(m_dMinShortTwoPushTime);
      m_aaiGuideAreas[1][0] = 2048 + GetLongParameter(LP_TWO_PUSH_DOWN)*exp(m_dMinLongTwoPushTime);
      m_aaiGuideAreas[1][1] = 2048 + GetLongParameter(LP_TWO_PUSH_DOWN)*exp(m_dMaxLongTwoPushTime);
      break;
-   }
   }
 }
 
@@ -223,6 +227,7 @@ bool doSet(int &var, const int val)
 bool CTwoPushDynamicFilter::TimerImpl(unsigned long iTime, CDasherView *m_pDasherView, CDasherModel *m_pDasherModel, CExpansionPolicy **pol)
 {
   DASHER_ASSERT(isRunning());
+  const double dSpeedMul(FrameSpeedMul(m_pDasherModel, iTime));
   if (m_dNatsSinceFirstPush > -std::numeric_limits<double>::infinity()) // first button has been pushed
   {
     double dLogGrowth(m_pDasherModel->GetNats() - m_dNatsSinceFirstPush), dOuter(GetLongParameter(LP_TWO_PUSH_OUTER)),
@@ -236,14 +241,14 @@ bool CTwoPushDynamicFilter::TimerImpl(unsigned long iTime, CDasherView *m_pDashe
     double dUpBits = (m_dLogUpMul * dOuter + dLogGrowth * dUp) / (dOuter + dUp);
     double dDownBits = (m_dLogDownMul * dOuter + dLogGrowth * dDown) / (dOuter + dDown);
     
-    // (note it's actually slightly more complicated even than that, we have to add in m_dLagBits too!)
-
     double dUpDist = exp( dUpBits ) * dUp;
     double dDownDist = exp( dDownBits ) * dDown;
-    m_aiTarget[0] = dUpDist * exp(m_dLagBits);
-    m_aiTarget[1] = -dDownDist * exp(m_dLagBits);
-    m_bDecorationChanged |= doSet(m_aiMarker[0], 2048 - exp(m_dLagBits + dLogGrowth) * dUp);
-    m_bDecorationChanged |= doSet(m_aiMarker[1], 2048 + exp(m_dLagBits + dLogGrowth) * dDown);
+    // (note it's actually slightly more complicated even than that, we have to add in m_dLagBits too!)
+    
+    m_aiTarget[0] = dUpDist * exp(m_dLagBits * dSpeedMul);
+    m_aiTarget[1] = -dDownDist * exp(m_dLagBits * dSpeedMul);
+    m_bDecorationChanged |= doSet(m_aiMarker[0], 2048 - exp(m_dLagBits*dSpeedMul + dLogGrowth) * dUp);
+    m_bDecorationChanged |= doSet(m_aiMarker[1], 2048 + exp(m_dLagBits*dSpeedMul + dLogGrowth) * dDown);
     if (dLogGrowth > m_dMaxLongTwoPushTime)
     {
 //cout << " growth " << dLogGrowth << " - reversing\n";
@@ -256,7 +261,7 @@ bool CTwoPushDynamicFilter::TimerImpl(unsigned long iTime, CDasherView *m_pDashe
       m_bDecorationChanged |= doSet(m_iActiveMarker, 1 /*down*/);
     else m_bDecorationChanged |= doSet(m_iActiveMarker, -1 /*in middle (neither/both) or too short*/);
   }
-  OneStepTowards(m_pDasherModel, 100, 2048, iTime, SlowStartSpeedMul(iTime));
+  OneStepTowards(m_pDasherModel, 100, 2048, iTime, dSpeedMul);
   return true;
 }
 
diff --git a/Src/iPhone/Classes/CDasherInterfaceBridge.mm b/Src/iPhone/Classes/CDasherInterfaceBridge.mm
index 1b8c3c9..f0f8604 100644
--- a/Src/iPhone/Classes/CDasherInterfaceBridge.mm
+++ b/Src/iPhone/Classes/CDasherInterfaceBridge.mm
@@ -186,7 +186,7 @@ void CDasherInterfaceBridge::HandleEvent(int iParameter) {
   // user defaults controller which is observing the user defaults and will be notified when
   // the parameter is actually written by COSXSettingsStore.
   //NSLog(@"CParameterNotificationEvent, m_iParameter: %d", parameterEvent->m_iParameter);
-  if (iParameter == LP_MAX_BITRATE || iParameter == LP_BOOSTFACTOR)
+  if (iParameter == LP_MAX_BITRATE)
     [dasherApp notifySpeedChange];
   else if (iParameter == SP_ALPHABET_ID)
     [dasherApp setAlphabet:GetActiveAlphabet()];



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]