/**********************************************************************

  Audacity: A Digital Audio Editor

  Envelope.cpp

  Dominic Mazzoni (original author)
  Dr William Bland (integration - the Calculus kind)
  Monty (xiphmont) (important bug fixes)

*******************************************************************//**

\class Envelope
\brief Draggable curve used in TrackPanel for varying amplification.

  This class manages an envelope - i.e. a piecewise linear funtion
  that the user can edit by dragging control points around.  The
  envelope is most commonly used to control the amplitude of a
  waveform, but it is also used to shape the Equalization curve.

*//****************************************************************//**

\class EnvPoint
\brief EnvPoint, derived from XMLTagHandler, provides Envelope with 
a draggable point type.

*//*******************************************************************/

#include "Envelope.h"

#include <math.h>

#include <wx/dc.h>
#include <wx/brush.h>
#include <wx/event.h>
#include <wx/pen.h>
#include <wx/textfile.h>
#include <wx/log.h>

#include "AColor.h"
#include "Prefs.h"
#include "DirManager.h"
#include "TrackArtist.h"

Envelope::Envelope()
{
   mOffset = 0.0;
   mTrackLen = 0.0;

   // Anything with a sample rate of no more than 200 KHz
   // will have samples spaced apart by at least this amount,
   // "epsilon".  We use this to enforce that there aren't
   // allowed to be multiple control points at the same t
   // value.
   mTrackEpsilon = 1.0 / 200000.0;

   mDB = true;
   mDefaultValue = 1.0;
   mDragPoint = -1;
   mDirty = false;
   mIsDeleting = false;
   mMirror = true;
   
   mMinValue = 1.0e-7;
   mMaxValue = 2.0;

   mButton = wxMOUSE_BTN_NONE;
}

Envelope::~Envelope()
{
   mDragPoint = -1;// TODO: Remove - Isn't this line totally pointless since we're in the destructor?
   WX_CLEAR_ARRAY(mEnv);
}

void Envelope::Mirror(bool mirror)
{
   mMirror = mirror;
}

/// Rescale function for time tracks (could also be used for other tracks though).
/// This is used to load old time track project files where the envelope used a 0 to 1
/// range instead of storing the actual time track values. This function will change the range of the envelope
/// and rescale all envelope points accordingly (unlike SetRange, which clamps the envelope points to the new range).
/// @minValue - the new minimum value
/// @maxValue - the new maximum value
void Envelope::Rescale(double minValue, double maxValue)
{
   double oldMinValue = mMinValue;
   double oldMaxValue = mMaxValue;
   mMinValue = minValue;
   mMaxValue = maxValue;

   // rescale the default value
   double factor = (mDefaultValue - oldMinValue) / (oldMaxValue - oldMinValue);
   mDefaultValue = ClampValue(mMinValue + (mMaxValue - mMinValue) * factor);

   // rescale all points
   for( unsigned int i = 0; i < mEnv.Count(); i++ ) {
      factor = (mEnv[i]->GetVal() - oldMinValue) / (oldMaxValue - oldMinValue);
      mEnv[i]->SetVal(mMinValue + (mMaxValue - mMinValue) * factor);
   }

}

/// Flatten removes all points from the envelope to 
/// make it horizontal at a chosen y-value.
/// @value - the y-value for the flat envelope.
void Envelope::Flatten(double value)
{
   WX_CLEAR_ARRAY(mEnv);
   mDefaultValue = ClampValue(value);
}

void Envelope::SetRange(double minValue, double maxValue) {
   mMinValue = minValue;
   mMaxValue = maxValue;
   mDefaultValue = ClampValue(mDefaultValue);
   for( unsigned int i = 0; i < mEnv.Count(); i++ )
      mEnv[i]->SetVal(mEnv[i]->GetVal()); // this clamps the value to the new range
}

EnvPoint * Envelope::AddPointAtEnd( double t, double val )
{
   // JC: Gross! (allocating one point at a time.)
   // TODO: switch over to using an array of EnvPoints
   // rather than an array of pointers to EnvPoints.
   //    What value does that add?
   EnvPoint *pt = new EnvPoint(this, t, val);
   mEnv.Add(pt);
   return pt;
}

void Envelope::CopyFrom(const Envelope *e, double t0, double t1)
{
   wxASSERT( t0 < t1 );

   mOffset   = wxMax(t0, e->mOffset);
   mTrackLen = wxMin(t1, e->mOffset + e->mTrackLen) - mOffset;

   WX_CLEAR_ARRAY(mEnv);
   int len = e->mEnv.Count();
   int i = 0;

   // Skip the points that come before the copied region
   while( (i < len) && e->mOffset + e->mEnv[i]->GetT() <= t0)
      i++;

   // Create the point at 0 if it needs interpolated representation
   if (i>0) 
      AddPointAtEnd( 0, e->GetValue(mOffset) );

   // Copy points from inside the copied region
   while ( (i < len) && e->mOffset + e->mEnv[i]->GetT() - mOffset < mTrackLen) {
      AddPointAtEnd( e->mOffset + e->mEnv[i]->GetT() - mOffset, e->mEnv[i]->GetVal() );
      i++;
   }

   // Create the final point if it needs interpolated representation
   if (mTrackLen > 0 && i < len)
      AddPointAtEnd( mTrackLen, e->GetValue(mOffset + mTrackLen));
}

/// Limit() limits a double value to a range.
/// TODO: Move to a general utilities source file.
double Limit( double Lo, double Value, double Hi )
{
   if( Value < Lo )
      return Lo;
   if( Value > Hi )
      return Hi;
   return Value;
}

double Envelope::toDB(double value)
{
   if (value == 0)
      return 0;

   // TODO: Cache the gPrefs value.  Reading it every time is inefficient.
   double dBRange = gPrefs->Read(wxT("/GUI/EnvdBRange"), ENV_DB_RANGE);
   double sign = (value >= 0 ? 1 : -1);

   wxASSERT( dBRange > 0 );
   double db = 20 * log10(fabs(value));
   double val = (db + dBRange) / dBRange;

   val = Limit( 0.0, val, 1.0 );
   return sign * val;
}

double Envelope::fromDB(double value) const
{
   if (value == 0)
      return 0;

   double sign = (value >= 0 ? 1 : -1);
   // TODO: Cache the gPrefs value.  Reading it every time is inefficient.
   double dBRange = gPrefs->Read(wxT("/GUI/EnvdBRange"), ENV_DB_RANGE);
   return pow(10.0, ((fabs(value) * dBRange) - dBRange) / 20.0)*sign;;
}

/// TODO: This should probably move to track artist.
void DrawPoint(wxDC & dc, const wxRect & r, int x, int y, bool top)
{
   if (y >= 0 && y <= r.height) {
      wxRect circle(r.x + x, r.y + (top ? y - 1: y - 2), 4, 4);
      dc.DrawEllipse(circle);
   }
}

/// TODO: This should probably move to track artist.
void Envelope::DrawPoints(wxDC & dc, const wxRect & r, double h, double pps, bool dB,
                    float zoomMin, float zoomMax)
{
   h -= mOffset;

   wxASSERT( pps > 0 );
   double tright = h + (r.width / pps);
   // TODO: Cache the gPrefs value.  Reading it every time is inefficient.
   double dBRange = gPrefs->Read(wxT("/GUI/EnvdBRange"), ENV_DB_RANGE);

   dc.SetPen(AColor::envelopePen);
   dc.SetBrush(*wxWHITE_BRUSH);

   for (int i = 0; i < (int)mEnv.Count(); i++) {
      if (mEnv[i]->GetT() >= h && mEnv[i]->GetT() <= tright) {
         // Change colour if this is the draggable point...
         if (i == mDragPoint) {
            dc.SetPen(AColor::envelopePen);
            dc.SetBrush(AColor::envelopeBrush);
         }

         double v = mEnv[i]->GetVal();
         int x = int ((mEnv[i]->GetT() - h) * pps);
         int y, y2;

         y = GetWaveYPos(v, zoomMin, zoomMax, r.height, dB,
            true, dBRange, false);
         if (!mMirror) {
            DrawPoint(dc, r, x, y, true);
         }
         else {
            y2 = GetWaveYPos(-v-.000000001, zoomMin, zoomMax, r.height, dB,
               true, dBRange, false);

            // This follows the same logic as the envelop drawing in
            // TrackArtist::DrawEnvelope().
            // TODO: make this calculation into a reusable function.
            if (y2 - y < 9) {
               int value = (int)((zoomMax / (zoomMax - zoomMin)) * r.height);
               y = value - 4;
               y2 = value + 4;
            }

            DrawPoint(dc, r, x, y, true);
            DrawPoint(dc, r, x, y2, false);

            // Contour
            y = GetWaveYPos(v, zoomMin, zoomMax, r.height, dB,
               false, dBRange, false);
            y2 = GetWaveYPos(-v-.000000001, zoomMin, zoomMax, r.height, dB,
               false, dBRange, false);
            if (y <= y2) {
               DrawPoint(dc, r, x, y, true);
               DrawPoint(dc, r, x, y2, false);
            }
         }

         // Change colour back again if was the draggable point.
         if (i == mDragPoint) {
            dc.SetPen(AColor::envelopePen);
            dc.SetBrush(*wxWHITE_BRUSH);
         }
      }
   }
}

bool Envelope::HandleXMLTag(const wxChar *tag, const wxChar **attrs)
{
   // Return unless it's the envelope tag.
   if (wxStrcmp(tag, wxT("envelope")))
      return false;

   int numPoints = 0;
   long nValue = -1;

   while (*attrs) {
      const wxChar *attr = *attrs++;
      const wxChar *value = *attrs++;
      if (!value)
         break;
      const wxString strValue = value;
      if( !wxStrcmp(attr, wxT("numpoints")) && 
            XMLValueChecker::IsGoodInt(strValue) && strValue.ToLong(&nValue)) 
         numPoints = nValue;
   }
   if (numPoints < 0)
      return false;

   WX_CLEAR_ARRAY(mEnv);
   mEnv.Alloc(numPoints);
   return true;
}

XMLTagHandler *Envelope::HandleXMLChild(const wxChar *tag)
{
   if (wxStrcmp(tag, wxT("controlpoint"))) 
      return NULL;

   return AddPointAtEnd(0,0);
}

void Envelope::WriteXML(XMLWriter &xmlFile)
{
   unsigned int ctrlPt;

   xmlFile.StartTag(wxT("envelope"));
   xmlFile.WriteAttr(wxT("numpoints"), mEnv.GetCount());

   for (ctrlPt = 0; ctrlPt < mEnv.GetCount(); ctrlPt++) {
      xmlFile.StartTag(wxT("controlpoint"));
      xmlFile.WriteAttr(wxT("t"), mEnv[ctrlPt]->GetT(), 12);
      xmlFile.WriteAttr(wxT("val"), mEnv[ctrlPt]->GetVal(), 12);
      xmlFile.EndTag(wxT("controlpoint"));
   }

   xmlFile.EndTag(wxT("envelope"));
}

#ifndef SQR
#define SQR(X) ((X)*(X))
#endif

/// ValueOfPixel() converts a y position on screen to an envelope value.
/// @param y - y position, usually of the mouse.relative to the clip.
/// @param height - height of the rectangle we are in.
/// @upper - true if we are on the upper line, false if on lower.
/// @dB - display mode either linear or log.
/// @zoomMin - vertical scale, typically -1.0
/// @zoomMax - vertical scale, typically +1.0
float Envelope::ValueOfPixel( int y, int height, bool upper, bool dB,
                              float zoomMin, float zoomMax)
{
   float v;

   wxASSERT( height > 0 );
   v = zoomMax - (y/(float)height) * (zoomMax - zoomMin);

   if (mContourOffset) {
     if( v > 0.0 )
       v += .5;
     else
       v -= .5;
   }
   if (dB)
      v = fromDB(v);

   // MB: this is mostly equivalent to what the old code did, I'm not sure
   // if anything special is needed for asymmetric ranges
   if(upper)
      return ClampValue(v);
   else
      return ClampValue(-v);
}

/// HandleMouseButtonDown either finds an existing control point or adds a new one
/// which is then recorded as the point to drag.
/// This is slightly complicated by there possibly being four control points for 
/// a given time value:
/// We have an upper and lower envelope line.
/// Also we may be showing an inner envelope (at 0.5 the range).
bool Envelope::HandleMouseButtonDown(wxMouseEvent & event, wxRect & r,
                                     double h, double pps, bool dB,
                                     float zoomMin, float zoomMax)
{
   int ctr = (int)(r.height * zoomMax / (zoomMax - zoomMin));
   bool upper = !mMirror || (zoomMin >= 0.0) || (event.m_y - r.y < ctr);

   int clip_y = event.m_y - r.y;
   if(clip_y < 0) clip_y = 0; //keeps point in rect r, even if mouse isn't
   if(clip_y > r.GetBottom()) clip_y = r.GetBottom();

   double tleft = h - mOffset;
   double tright = tleft + (r.width / pps);
   int bestNum = -1;
   int bestDist = 10; // Must be within 10 pixel radius.

   // TODO: Cache the gPrefs value.  Reading it every time is inefficient.
   double dBr = gPrefs->Read(wxT("/GUI/EnvdBRange"), ENV_DB_RANGE);

   // Member variables hold state that will be needed in dragging.
   mButton        = event.GetButton();
   mIsDeleting    = false;
   mContourOffset = false;

   //   wxLogDebug(wxT("Y:%i Height:%i Offset:%i"), y, height, mContourOffset );
   int len = mEnv.Count();

   // TODO: extract this into a function FindNearestControlPoint()
   // TODO: also fix it so that we can drag the last point on an envelope.
   for (int i = 0; i < len; i++) { //search for control point nearest click
      if (mEnv[i]->GetT() >= tleft && mEnv[i]->GetT() <= tright) {
         
         int x = int ((mEnv[i]->GetT() + mOffset - h) * pps) + r.x;
         int y[4];
         int numControlPoints;

         // Outer control points
         y[0] = GetWaveYPos( mEnv[i]->GetVal(), zoomMin, zoomMax, r.height,
                                dB, true, dBr, false);
         y[1] = GetWaveYPos( -mEnv[i]->GetVal(), zoomMin, zoomMax, r.height,
                                dB, true, dBr, false);

         // Inner control points(contour)
         y[2] = GetWaveYPos( mEnv[i]->GetVal(), zoomMin, zoomMax, r.height,
                                dB, false, dBr, false);
         y[3] = GetWaveYPos( -mEnv[i]->GetVal()-.00000001, zoomMin, zoomMax, 
                                r.height, dB, false, dBr, false);

         numControlPoints = 4;

         if (y[2] > y[3])
            numControlPoints = 2;

         if (!mMirror)
            numControlPoints = 1;
         
         for(int j=0; j<numControlPoints; j++){
            
            int d = (int)(sqrt((double)(SQR(x-event.m_x) + SQR(y[j]-(event.m_y-r.y)))) + 0.5);
            if (d < bestDist) {
               bestNum = i;
               bestDist = d;
               mContourOffset = (bool)(j > 1);
            }
         }
      }
   }

   if (bestNum >= 0) {
      mDragPoint = bestNum;
   }
   else {
      // TODO: Extract this into a function CreateNewPoint
      double when = h + (event.m_x - r.x) / pps - mOffset;
      
      //      if (when <= 0 || when >= mTrackLen)
      //         return false;
      
      double v = GetValueAtX( event.m_x, r, h, pps );
      
      int ct = GetWaveYPos( v, zoomMin, zoomMax, r.height, dB, 
                               false, dBr, false) ;
      int cb = GetWaveYPos( -v-.000000001, zoomMin, zoomMax, r.height, dB, 
                               false, dBr, false) ;
      if( ct <= cb || !mMirror ){
         int t = GetWaveYPos( v, zoomMin, zoomMax, r.height, dB, 
                                 true, dBr, false) ;
         int b = GetWaveYPos( -v, zoomMin, zoomMax, r.height, dB, 
                                 true, dBr, false) ;

         ct = (t + ct) / 2;
         cb = (b + cb) / 2;

         if(mMirror &&
            (event.m_y - r.y) > ct &&
            ((event.m_y - r.y) < cb))
            mContourOffset = true;
         else
            mContourOffset = false;
      }

      double newVal = ValueOfPixel(clip_y, r.height, upper, dB,
                                   zoomMin, zoomMax);

      mDragPoint = Insert(when, newVal);
      mDirty = true;
   }

   mUpper = upper;

   mInitialWhen = mEnv[mDragPoint]->GetT();
   mInitialVal = mEnv[mDragPoint]->GetVal();

   mInitialX = event.m_x;
   mInitialY = event.m_y+mContourOffset;

   return true;
}

/// Mark dragged point for deletion.
/// It will be deleted on mouse button up.
void Envelope::MarkDragPointForDeletion()
{
   mIsDeleting = true;

   // We're going to be deleting the point; On
   // screen we show this by having the envelope move to
   // the position it will have after deletion of the point.
   // Without delting the point we move it left or right 
   // to the same position as the previous or next point.

   if( mEnv.Count()<=1) 
   {
      // There is only one point - just move it
      // off screen and at default height.
      // temporary state when dragging only! 
      mEnv[mDragPoint]->SetT(-1000000.0);
      mEnv[mDragPoint]->SetVal(mDefaultValue);
      return;
   }

   // Place it exactly on one of its neighbours.
   int iNeighbourPoint = mDragPoint + ((mDragPoint > 0) ? -1:+1);
   mEnv[mDragPoint]->SetT(mEnv[iNeighbourPoint]->GetT());
   mEnv[mDragPoint]->SetVal(mEnv[iNeighbourPoint]->GetVal());
}

void Envelope::MoveDraggedPoint( wxMouseEvent & event, wxRect & r,
                               double h, double pps, bool dB,
                               float zoomMin, float zoomMax)
{
   int clip_y = event.m_y - r.y;
   if(clip_y < 0) clip_y = 0;
   if(clip_y > r.height) clip_y = r.height;
   double newVal = ValueOfPixel(clip_y, r.height, mUpper, dB,
                                zoomMin, zoomMax);  

   wxASSERT( pps > 0 );
   // We no longer tolerate multiple envelope points at the same t.
   // epsilon is less than the time offset of a single sample
   // TODO: However because mTrackEpsilon assumes 200KHz this use
   // of epsilon is a tad bogus.  What we need to do instead is delete 
   // a duplicated point on a mouse up.
   double newWhen = mInitialWhen + (event.m_x - mInitialX) / pps;

   // We'll limit the drag point time to be between those of the preceding
   // and next envelope point.
   double limitLo = 0.0;
   double limitHi = mTrackLen;

   if (mDragPoint > 0)
      limitLo = mEnv[mDragPoint - 1]->GetT() + mTrackEpsilon;
   if (mDragPoint < (int)mEnv.Count() - 1 )
      limitHi = mEnv[mDragPoint + 1]->GetT() - mTrackEpsilon;

   newWhen = Limit( limitLo, newWhen, limitHi );
   newWhen = Limit( mTrackEpsilon, newWhen, mTrackLen - mTrackEpsilon);

   mEnv[mDragPoint]->SetT(newWhen);
   mEnv[mDragPoint]->SetVal(newVal);

}

bool Envelope::HandleDragging( wxMouseEvent & event, wxRect & r,
                               double h, double pps, bool dB,
                               float zoomMin, float zoomMax, float eMin, float eMax)
{
   mDirty = true;

   wxRect larger = r;
   larger.Inflate(10, 10);

   if (larger.Contains(event.m_x, event.m_y))
   {
      // IF we're in the rect THEN we're not deleting this point (anymore).
      mIsDeleting = false;
      // ...we're dragging it.
      MoveDraggedPoint( event, r,h,pps,dB, zoomMin, zoomMax);
      return true;
   }
   
   if(mIsDeleting )
      // IF we already know we're deleting THEN no envelope point to update.
      return false;

   MarkDragPointForDeletion();
   return true;
}

// Exit dragging mode and deletes dragged point if neccessary.
bool Envelope::HandleMouseButtonUp( wxMouseEvent & event, wxRect & r,
                                    double h, double pps, bool dB,
                                    float zoomMin, float zoomMax )
{
   if (mIsDeleting) {
      delete mEnv[mDragPoint];
      mEnv.RemoveAt(mDragPoint);
   }
   mDragPoint = -1;
   mButton = wxMOUSE_BTN_NONE;
   return true;
}

void Envelope::Delete( int point )
{
   delete mEnv[point];
   mEnv.RemoveAt(point);
}

// Returns true if parent needs to be redrawn
bool Envelope::MouseEvent(wxMouseEvent & event, wxRect & r,
                          double h, double pps, bool dB,
                          float zoomMin, float zoomMax)
{
   if (event.ButtonDown() && mButton == wxMOUSE_BTN_NONE)
      return HandleMouseButtonDown( event, r, h, pps,dB,
                                    zoomMin, zoomMax);
   if (event.Dragging() && mDragPoint >= 0) 
      return HandleDragging( event, r, h, pps,dB,
                             zoomMin, zoomMax);
   if (event.ButtonUp() && event.GetButton() == mButton)
      return HandleMouseButtonUp( event, r, h, pps, dB,
                                  zoomMin, zoomMax);
   return false;
}

void Envelope::CollapseRegion(double t0, double t1)
{
   // This gets called when somebody clears samples.  All of the
   // control points within the region disappear and the points
   // to the right get shifted over.

   t0 -= mOffset;
   t1 -= mOffset;

   t0 = Limit( 0, t0, mTrackLen );
   t1 = Limit( 0, t1, mTrackLen );

   int len = mEnv.Count();
   int i;

   // Remove points in deleted region.
   for (i = 0; i < len - 0; i++)
      if (mEnv[i]->GetT() >= t0 && mEnv[i]->GetT() < t1) {
         delete mEnv[i];
         mEnv.RemoveAt(i);
         len--;
         i--;
      }

   // Shift points left after deleted region.
   for (i = 0; i < len; i++)
      if (mEnv[i]->GetT() >= t1)
         mEnv[i]->SetT(mEnv[i]->GetT() - (t1 - t0));
   
   mTrackLen -= (t1-t0);
}

// This operation is trickier than it looks; the basic rub is that
// a track's envelope runs the range from t=0 to t=tracklen; the t=0
// envelope point applies to the first sample, but the t=tracklen
// envelope point applies one-past the last actual sample.
// Rather than going to a .5-offset-index, we special case the framing.
void Envelope::Paste(double t0, Envelope *e)
{
   bool pointsAdded = false;

// JC: The old analysis of cases and the resulting code here is way more complex than needed.
// TODO: simplify the analysis and simplify the code.

   if (e->mEnv.Count() == 0 && this->mEnv.Count() == 0 && e->mDefaultValue == this->mDefaultValue)
   {
      // msmeyer: The envelope is empty and has the same default value, so
      // there is nothing that must be inserted, just return. This avoids
      // the creation of unnecessary duplicate control points
      // MJS: but the envelope does get longer
      mTrackLen += e->mTrackLen;
      return;
   }
   if (this->mEnv.Count() != 0)
   {
      // inserting a clip with a possibly empty envelope into one with an envelope
      // so add end points to e, in case they are not there
      double leftval  = e->GetValue(0+e->mOffset);
      double rightval = e->GetValue(e->mTrackLen+e->mOffset);
      e->Insert(0, leftval);
      e->Insert(e->mTrackLen, rightval);
      pointsAdded = true;  // we need to delete them later so's not to corrupt 'e' for later use
   }
   
   t0 = wxMin(t0 - mOffset, mTrackLen);   // t0 now has origin of zero
   double deltat = e->mTrackLen;

   unsigned int i;
   unsigned int pos = 0;
   bool someToShift = false;
   bool atStart = false;
   bool beforeStart = false;
   bool atEnd = false;
   bool afterEnd = false;
   bool onPoint = false;
   unsigned int len = mEnv.Count();

   // get values to perform framing of the insertion 
   double splitval = GetValue(t0 + mOffset);

   if(len != 0) {   // Not case 10: there are point/s in the envelope


/*
Old analysis of cases:
(see discussions on audacity-devel around 19/8/7 - 23/8/7 and beyond, "Envelopes and 'Join'")
1  9     11  2    3 5  7   8   6 4   13              12
0-----0--0---0    -----0---0------       --(0)----

1   The insert point is at the beginning of the current env, and it is a control point.
2   The insert point is at the end of the current env, and it is a control point.
3   The insert point is at the beginning of the current env, and it is not a control point.
4   The insert point is at the end of the current env, and it is not a control point.
5   The insert point is not at a control point, and there is space either side.
6   As 5.
7   The insert point is at a control point, and there is space either side.
8   Same as 7.
9   Same as 5.
10  There are no points in the current envelope (commonly called by the 'undo' stuff, and not in the diagrams).
11  As 7.
12  Insert beyond the RH end of the current envelope (should not happen, at the moment)
13  Insert beyond the LH end of the current envelope (should not happen, at the moment)
*/

// JC: Simplified Analysis:
// In pasting in a clip we choose to preserve the envelope so that the loudness of the
// parts is unchanged.
//
// 1) This may introduce a discontnuity in the envelope at a boundary between the 
//    old and new clips.  In that case we must ensure there are envelope points 
//    at sample positions immediately before and immediately after the boundary.
// 2) If the points have the same value we only need one of them.
// 3) If the points have the same value AND it is the same as the value interpolated
//    from the rest of the envelope then we don't need it at all.
//
// We do the same for the left and right edge of the new clip.
//
// Even simpler: we could always add two points at a boundary and then call 
// RemoveUnneededPoints() (provided that function behaves correctly).  

      // See if existing points need shifting to the right, and what Case we are in
      for (i = 0; i < len; i++) {
         if (mEnv[i]->GetT() > t0)
            someToShift = true;
         else {
            pos = i; // last point not moved
            if ( fabs(mEnv[i]->GetT() - t0) - 1/500000.0 < 0.0 ) // close enough to a point
               onPoint = true;
         }
      }

      // In these statements, remember we subtracted mOffset from t0
      if( t0 < mTrackEpsilon )
         atStart = true;
      if( (mTrackLen - t0) < mTrackEpsilon )
         atEnd = true;
      if(0 > t0)
         beforeStart = true;  // Case 13
      if(mTrackLen < t0)
         afterEnd = true;  // Case 12

      // Now test for the various Cases, and try to do the right thing
      if(atStart) {   // insertion at the beginning
         if(onPoint) {  // first env point is at LH end
            mEnv[0]->SetT(mEnv[0]->GetT() + mTrackEpsilon);   // Case 1: move it R slightly to avoid duplicate point
            someToShift = true;  // there is now, even if there wasn't before
            //wxLogDebug(wxT("Case 1"));
         }
         else {
            Insert(t0 + mTrackEpsilon, splitval);   // Case 3: insert a point to maintain the envelope
            someToShift = true;
            //wxLogDebug(wxT("Case 3"));
         }
      }
      else {
         if(atEnd) { // insertion at the end
            if(onPoint) {  // last env point is at RH end, Case 2: 
               mEnv[0]->SetT(mEnv[0]->GetT() - mTrackEpsilon);  // move it L slightly to avoid duplicate point
               //wxLogDebug(wxT("Case 2"));
            }
            else {   // Case 4:
               Insert(t0 - mTrackEpsilon, splitval);   // insert a point to maintain the envelope
               //wxLogDebug(wxT("Case 4"));
            }
         }
         else {
            if(onPoint) {  // Case 7: move the point L and insert a new one to the R
               mEnv[pos]->SetT(mEnv[pos]->GetT() - mTrackEpsilon);
               Insert(t0 + mTrackEpsilon, splitval);
               someToShift = true;
               //wxLogDebug(wxT("Case 7"));
            }
            else {
               if( !beforeStart && !afterEnd ) {// Case 5: Insert points to L and R
                  Insert(t0 - mTrackEpsilon, splitval);
                  Insert(t0 + mTrackEpsilon, splitval);
                  someToShift = true;
                  //wxLogDebug(wxT("Case 5"));
               }
               else {
                  if( beforeStart ) {  // Case 13:
                     //wxLogDebug(wxT("Case 13"));
                  }
                  else {   // Case 12:
                     //wxLogDebug(wxT("Case 12"));
                  }
               }
            }
         }
      }

      // Now shift existing points to the right, if required
      if(someToShift) {
         len = mEnv.Count();  // it may well have changed
         for (i = 0; i < len; i++)
            if (mEnv[i]->GetT() > t0)
               mEnv[i]->SetT(mEnv[i]->GetT() + deltat);
      }
      mTrackLen += deltat;
   }
   else {   // Case 10:
      if( mTrackLen == 0 ) // creating a new envelope
      {
         mTrackLen = e->mTrackLen;
         mOffset = e->mOffset;
         //wxLogDebug(wxT("Case 10, new env/clip: mTrackLen %f mOffset %f t0 %f"), mTrackLen, mOffset, t0);
      }
      else
      {
         mTrackLen += e->mTrackLen;
         //wxLogDebug(wxT("Case 10, paste into current env: mTrackLen %f mOffset %f t0 %f"), mTrackLen, mOffset, t0);
      }
   }

   // Copy points from inside the selection
   len = e->mEnv.Count();
   for (i = 0; i < len; i++)
      pos=Insert(t0 + e->mEnv[i]->GetT(), e->mEnv[i]->GetVal());

/*   if(len != 0)
      for (i = 0; i < mEnv.Count(); i++)
         wxLogDebug(wxT("Fixed i %d when %.18f val %f"),i,mEnv[i]->GetT(),mEnv[i]->GetVal()); */

   if(pointsAdded)
      while(e->mEnv.Count() != 0)
         e->Delete(0);  // they were not there when we entered this
}

// Deletes 'unneeded' points, starting from the left.
// If 'time' is set and positive, just deletes points in a small region
// around that value.
// 'Unneeded' means that the envelope doesn't change by more than
// 'tolerence' without the point being there.
void Envelope::RemoveUnneededPoints(double time, double tolerence)
{
   unsigned int len = mEnv.Count();
   unsigned int i;
   double when, val, val1;

   if(mEnv.Count() == 0)
      return;

   for (i = 0; i < len; i++) {
      when = mEnv[i]->GetT();
      if(time >= 0)
      {
         if(fabs(when + mOffset - time) > 0.00025) // 2 samples at 8kHz, 11 at 44.1kHz
            continue;
      }
      val = mEnv[i]->GetVal();
      Delete(i);  // try it to see if it's doing anything
      val1 = GetValue(when + mOffset);
      bool bExcludePoint = true;
      if( fabs(val -val1) > tolerence )
      {
         Insert(when,val); // put it back, we needed it
         
         //Insert may have modified instead of inserting, if two points were at the same time.
         // in which case len needs to shrink i and len, because the array size decreased.
         bExcludePoint = (mEnv.Count() < len);
      }

      if( bExcludePoint ) {   // it made no difference so leave it out
         len--;
         i--;
      }
   }
}

void Envelope::InsertSpace(double t0, double tlen)
{
   unsigned int len = mEnv.Count();
   unsigned int i;

   for (i = 0; i < len; i++)
      if (mEnv[i]->GetT() > t0)
         mEnv[i]->SetT(mEnv[i]->GetT() + tlen);
   mTrackLen += tlen;
}

int Envelope::Move(double when, double value)
{
   int len = mEnv.Count();
   if (len == 0)
      return -1;

   int i = 0;
   while (i < len && when > mEnv[i]->GetT())
      i++;

   if (i >= len || when < mEnv[i]->GetT())
      return -1;

   mEnv[i]->SetVal(value);
   return 0;
}


int Envelope::GetNumberOfPoints() const
{
   return mEnv.Count();
}

void Envelope::GetPoints(double *bufferWhen,
                         double *bufferValue,
                         int bufferLen) const
{
   int n = mEnv.Count();
   if (n > bufferLen)
      n = bufferLen;
   int i;
   for (i = 0; i < n; i++) {
      bufferWhen[i] = mEnv[i]->GetT();
      bufferValue[i] = mEnv[i]->GetVal();
   }
}

// Private methods

// We no longer tolerate multiple envelope control points at the exact
// same t; the behavior can be well-defined, but it is still incorrect
// in that it vastly complicates paste operations behaving as a user
// reasonably expects.  The most common problem occurs pasting an
// envelope into another track; the boundary behavior causes the
// t=insert_point envelope level of the insertee to apply to sample 0
// of the inserted sample, causing a pop.  This most visibly manifests
// itself in undo and mixing when a v=1.0 sample magically shows
// up at boundaries causing a pop.

// Although this renders the name a slight misnomer, a duplicate
// 'replaces' the current control point.

/** @brief Add a control point to the envelope
 *
 * Control point positions start at zero and are measured in seconds from the
 * start of the envelope. The position of the envelope on the project-wide
 * time scale is store in seconds in Envelope::mOffset.
 * This is worth remembering.
 * If you call Envelope::Insert() from WaveClip, or anywhere else outside the
 * Envelope class that is using project timing, subtract the envelope's mOffset
 * from the time.
 * If you call Envelope::Insert() from within Envelope, don't subtract mOffset 
 * because you are working in relative time inside the envelope
 * @param when the time in seconds when the envelope point should be created.
 * @param value the envelope value to use at the given point.
 * @return the index of the new envelope point within array of envelope points.
 */
int Envelope::Insert(double when, double value)
{
#if defined(__WXDEBUG__)
   // in debug builds, do a spot of argument checking
   if(when > mTrackLen + 0.0000001)
   {
      wxString msg;
      msg = wxString::Format(wxT("when %.20f mTrackLen %.20f diff %.20f"), when, mTrackLen, when-mTrackLen);
      wxASSERT_MSG(when <= (mTrackLen), msg);
   }
   if(when < 0)
   {
      wxString msg;
      msg = wxString::Format(wxT("when %.20f mTrackLen %.20f"), when, mTrackLen);
      wxASSERT_MSG(when >= 0, msg);
   }
#endif

   int len = mEnv.Count();

   if (len && when < 0.0)
      return 0;
   if ((len > 1) && when > mTrackLen)
      return len - 1;

   if (when < 0.0)
      when = 0.0;
   if ((len>1) && when > mTrackLen)
      when = mTrackLen;

   int i = 0;
   
   while (i < len && when > mEnv[i]->GetT())
      i++;

   if(i < len && when == mEnv[i]->GetT()) {

     // modify existing
     mEnv[i]->SetVal(value);

   }
   else{

     // Add new
     EnvPoint *e = new EnvPoint(this, when, value);
     if (i < len) {
        mEnv.Insert(e, i);
     } else {
        mEnv.Add(e);
     }
   }
   return i;
}

// Control

void Envelope::SetOffset(double newOffset)
{
   mOffset = newOffset;
}

void Envelope::SetTrackLen(double trackLen)
{
   mTrackLen = trackLen;

   int len = mEnv.Count();
   for (int i = 0; i < len; i++)
      if (mEnv[i]->GetT() > mTrackLen) {
         delete mEnv[i];
         mEnv.RemoveAt(i);
         len--;
         i--;
      }
}

// Accessors
double Envelope::GetValue(double t) const
{
   double temp;

   GetValues(&temp, 1, t, 1.0);
   return temp;
}

// 'X' is in pixels and relative to track.
double Envelope::GetValueAtX(int x, const wxRect & r, double h, double pps)
{
   // Convert x to time.
   double t = (x - r.x) / pps + h ;//-mOffset;
   return GetValue(t);
}

/// @param Lo returns index before this time.
/// @param Hi returns index after this time.
void Envelope::BinarySearchForTime( int &Lo, int &Hi, double t ) const
{
   Lo = 0;
   Hi = mEnv.Count() - 1;
   // JC: Do we have a problem if the envelope only has one point??
   wxASSERT( Hi > Lo );
   while (Hi > (Lo + 1)) {
      int mid = (Lo + Hi) / 2;
      if (t < mEnv[mid]->GetT())
         Hi = mid;
      else
         Lo = mid;
   }
   wxASSERT( Hi == ( Lo+1 ));
}

/// GetInterpolationStartValueAtPoint() is used to select either the
/// envelope value or its log depending on whether we are doing linear
/// or log interpolation.  
/// @param iPoint index in env array to look at.
/// @return value there, or its (safe) log10.
double Envelope::GetInterpolationStartValueAtPoint( int iPoint ) const
{
   double v = mEnv[ iPoint ]->GetVal();
   if( !mDB )
      return v;
   else
      return log10(v);
}

void Envelope::GetValues(double *buffer, int bufferLen,
                         double t0, double tstep) const
{
   t0 -= mOffset;

   // JC: If bufferLen ==0 we have probably just allocated a zero sized buffer.
   wxASSERT( bufferLen > 0 );

   int len = mEnv.Count();

   double t = t0;
   double tprev, vprev, tnext = 0, vnext, vstep = 0;

   for (int b = 0; b < bufferLen; b++) {

      // Get easiest cases out the way first...
      // IF empty envelope THEN default value
      if (len <= 0) {
         buffer[b] = mDefaultValue;
         t += tstep;
         continue;
      }
      // IF before envelope THEN first value
      if (t <= mEnv[0]->GetT()) {
         buffer[b] = mEnv[0]->GetVal();
         t += tstep;
         continue;
      }
      // IF after envelope THEN last value
      if (t >= mEnv[len - 1]->GetT()) {
         buffer[b] = mEnv[len - 1]->GetVal();
         t += tstep;
         continue;
      }

      if (b == 0 || t > tnext) {

         // We're beyond our tnext, so find the next one.
         // Don't just increment lo or hi because we might
         // be zoomed far out and that could be a large number of
         // points to move over.  That's why we binary search.

         int lo,hi;
         BinarySearchForTime( lo, hi, t );
         tprev = mEnv[lo]->GetT();
         tnext = mEnv[hi]->GetT();

         vprev = GetInterpolationStartValueAtPoint( lo );
         vnext = GetInterpolationStartValueAtPoint( hi );
         
         // Interpolate, either linear or log depending on mDB.
         double dt = (tnext - tprev);
         double to = t - tprev;
         double v;
         if (dt > 0.0)
         {
            v = (vprev * (dt - to) + vnext * to) / dt;
            vstep = (vnext - vprev) * tstep / dt;
         }
         else
         {
            v = vnext;
            vstep = 0.0;
         }

         // An adjustment if logarithmic scale.
         if( mDB )
         {
            v = pow(10.0, v);
            vstep = pow( 10.0, vstep );
         }

         buffer[b] = v;
      } else {
         if (mDB){
            buffer[b] = buffer[b - 1] * vstep;
         }else{
            buffer[b] = buffer[b - 1] + vstep;
         }
      }

      t += tstep;
   }
}

int Envelope::NumberOfPointsAfter(double t)
{
   if( t >= mEnv[mEnv.Count()-1]->GetT() )
      return 0;
   else if( t < mEnv[0]->GetT() )
      return mEnv.Count();
   else
   {
      int lo,hi;
      BinarySearchForTime( lo, hi, t );

      if( mEnv[hi]->GetT() == t )
         return mEnv.Count() - (hi+1);
      else
         return mEnv.Count() - hi;
   }
}

double Envelope::NextPointAfter(double t)
{
   if( mEnv[mEnv.Count()-1]->GetT() < t )
      return t;
   else if( t < mEnv[0]->GetT() )
      return mEnv[0]->GetT();
   else
   {
      int lo,hi;
      BinarySearchForTime( lo, hi, t );
      if( mEnv[hi]->GetT() == t )
         return mEnv[hi+1]->GetT();
      else
         return mEnv[hi]->GetT();
   }
}

double Envelope::Average( double t0, double t1 )
{
  if( t0 == t1 )
    return GetValue( t0 );
  else
    return Integral( t0, t1 ) / (t1 - t0);
}

double Envelope::AverageOfInverse( double t0, double t1 )
{
  if( t0 == t1 )
    return 1.0 / GetValue( t0 );
  else
    return IntegralOfInverse( t0, t1 ) / (t1 - t0);
}

//
// Integration and debugging functions
//
// The functions below are used by the TimeTrack and possibly for
// other debugging.  They do not affect normal amplitude envelopes
// for waveforms, nor frequency envelopes for equalization.
// The 'Average' function also uses 'Integral'.
//

// A few helper functions to make the code below more readable.
static double InterpolatePoints(double y1, double y2, double factor, bool logarithmic)
{
   if(logarithmic)
      // you can use any base you want, it doesn't change the result
      return exp(log(y1) * (1.0 - factor) + log(y2) * factor);
   else
      return y1 * (1.0 - factor) + y2 * factor;
}
static double IntegrateInterpolated(double y1, double y2, double time, bool logarithmic)
{
   // Calculates: integral(interpolate(y1, y2, x), x = 0 .. time)
   // Integrating logarithmic interpolated segments is surprisingly simple. You can check this formula here:
   // http://www.wolframalpha.com/input/?i=integrate+10%5E%28log10%28y1%29*%28T-x%29%2FT%2Blog10%28y2%29*x%2FT%29+from+0+to+T
   // Again, the base you use for interpolation is irrelevant, the formula below should always use the natural
   // logarithm (i.e. 'log' in C/C++). If the denominator is too small, it's better to use linear interpolation
   // because the rounding errors would otherwise get too large. The threshold value is 1.0e-5 because at that
   // point the rounding errors become larger than the difference between linear and logarithmic (I tested this in Octave).
   if(logarithmic)
   {
      double l = log(y1 / y2);
      if(fabs(l) < 1.0e-5) // fall back to linear interpolation
         return (y1 + y2) * 0.5 * time;
      return (y1 - y2) / l * time;
   }
   else
   {
      return (y1 + y2) * 0.5 * time;
   }
}
static double IntegrateInverseInterpolated(double y1, double y2, double time, bool logarithmic)
{
   // Calculates: integral(1 / interpolate(y1, y2, x), x = 0 .. time)
   // This one is a bit harder. Linear:
   // http://www.wolframalpha.com/input/?i=integrate+1%2F%28y1*%28T-x%29%2FT%2By2*x%2FT%29+from+0+to+T
   // Logarithmic:
   // http://www.wolframalpha.com/input/?i=integrate+1%2F%2810%5E%28log10%28y1%29*%28T-x%29%2FT%2Blog10%28y2%29*x%2FT%29%29+from+0+to+T
   // Here both cases need a special case for y1 == y2. The threshold is 1.0e5 again, this is still the
   // best value in both cases.
   double l = log(y1 / y2);
   if(fabs(l) < 1.0e-5) // fall back to average
      return 2.0 / (y1 + y2) * time;
   if(logarithmic)
      return (y1 - y2) / (l * y1 * y2) * time;
   else
      return l / (y1 - y2) * time;
}
static double SolveIntegrateInverseInterpolated(double y1, double y2, double time, double area, bool logarithmic)
{
   // Calculates: solve (integral(1 / interpolate(y1, y2, x), x = 0 .. res) = area) for res
   // Don't try to derive these formulas by hand :). The threshold is 1.0e5 again.
   double a = area / time, res;
   if(logarithmic)
   {
      double l = log(y1 / y2);
      if(fabs(l) < 1.0e-5) // fall back to average
         res = a * (y1 + y2) * 0.5;
      else if(1.0 + a * y1 * l <= 0.0)
         res = 1.0;
      else
         res = log(1.0 + a * y1 * l) / l;
   }
   else
   {
      if(fabs(y2 - y1) < 1.0e-5) // fall back to average
         res = a * (y1 + y2) * 0.5;
      else
         res = y1 * (exp(a * (y2 - y1)) - 1.0) / (y2 - y1);
   }
   return std::max(0.0, std::min(1.0, res)) * time;
}

// We should be able to write a very efficient memoizer for this
// but make sure it gets reset when the envelope is changed.
double Envelope::Integral( double t0, double t1 )
{
   if(t0 == t1)
      return 0.0;
   if(t0 > t1)
   {
      return -Integral(t1, t0); // this makes more sense than returning the default value
   }
   
   unsigned int count = mEnv.Count();
   if(count == 0) // 'empty' envelope
      return (t1 - t0) * mDefaultValue;
   
   double total = 0.0, lastT, lastVal;
   unsigned int i; // this is the next point to check
   if(t0 < mEnv[0]->GetT()) // t0 preceding the first point
   {
      if(t1 <= mEnv[0]->GetT())
         return (t1 - t0) * mEnv[0]->GetVal();
      i = 1;
      lastT = mEnv[0]->GetT();
      lastVal = mEnv[0]->GetVal();
      total += (lastT - t0) * lastVal;
   }
   else if(t0 >= mEnv[count - 1]->GetT()) // t0 following the last point
   {
      return (t1 - t0) * mEnv[count - 1]->GetVal();
   }
   else // t0 enclosed by points
   {
      // Skip any points that come before t0 using binary search
      int lo, hi;
      BinarySearchForTime(lo, hi, t0);
      lastVal = InterpolatePoints(mEnv[lo]->GetVal(), mEnv[hi]->GetVal(), (t0 - mEnv[lo]->GetT()) / (mEnv[hi]->GetT() - mEnv[lo]->GetT()), mDB);
      lastT = t0;
      i = hi; // the point immediately after t0.
   }
   
   // loop through the rest of the envelope points until we get to t1
   while (1)
   {
      if(i >= count) // the requested range extends beyond the last point
      {
         return total + (t1 - lastT) * lastVal;
      }
      else if(mEnv[i]->GetT() >= t1) // this point follows the end of the range
      {
         double thisVal = InterpolatePoints(mEnv[i - 1]->GetVal(), mEnv[i]->GetVal(), (t1 - mEnv[i - 1]->GetT()) / (mEnv[i]->GetT() - mEnv[i - 1]->GetT()), mDB);
         return total + IntegrateInterpolated(lastVal, thisVal, t1 - lastT, mDB);
      }
      else // this point preceeds the end of the range
      {
         total += IntegrateInterpolated(lastVal, mEnv[i]->GetVal(), mEnv[i]->GetT() - lastT, mDB);
         lastT = mEnv[i]->GetT();
         lastVal = mEnv[i]->GetVal();
         i++;
      }
   }
}

double Envelope::IntegralOfInverse( double t0, double t1 )
{
   if(t0 == t1)
      return 0.0;
   if(t0 > t1)
   {
      return -IntegralOfInverse(t1, t0); // this makes more sense than returning the default value
   }
   
   unsigned int count = mEnv.Count();
   if(count == 0) // 'empty' envelope
      return (t1 - t0) / mDefaultValue;
   
   double total = 0.0, lastT, lastVal;
   unsigned int i; // this is the next point to check
   if(t0 < mEnv[0]->GetT()) // t0 preceding the first point
   {
      if(t1 <= mEnv[0]->GetT())
         return (t1 - t0) / mEnv[0]->GetVal();
      i = 1;
      lastT = mEnv[0]->GetT();
      lastVal = mEnv[0]->GetVal();
      total += (lastT - t0) / lastVal;
   }
   else if(t0 >= mEnv[count - 1]->GetT()) // t0 following the last point
   {
      return (t1 - t0) / mEnv[count - 1]->GetVal();
   }
   else // t0 enclosed by points
   {
      // Skip any points that come before t0 using binary search
      int lo, hi;
      BinarySearchForTime(lo, hi, t0);
      lastVal = InterpolatePoints(mEnv[lo]->GetVal(), mEnv[hi]->GetVal(), (t0 - mEnv[lo]->GetT()) / (mEnv[hi]->GetT() - mEnv[lo]->GetT()), mDB);
      lastT = t0;
      i = hi; // the point immediately after t0.
   }
   
   // loop through the rest of the envelope points until we get to t1
   while (1)
   {
      if(i >= count) // the requested range extends beyond the last point
      {
         return total + (t1 - lastT) / lastVal;
      }
      else if(mEnv[i]->GetT() >= t1) // this point follows the end of the range
      {
         double thisVal = InterpolatePoints(mEnv[i - 1]->GetVal(), mEnv[i]->GetVal(), (t1 - mEnv[i - 1]->GetT()) / (mEnv[i]->GetT() - mEnv[i - 1]->GetT()), mDB);
         return total + IntegrateInverseInterpolated(lastVal, thisVal, t1 - lastT, mDB);
      }
      else // this point preceeds the end of the range
      {
         total += IntegrateInverseInterpolated(lastVal, mEnv[i]->GetVal(), mEnv[i]->GetT() - lastT, mDB);
         lastT = mEnv[i]->GetT();
         lastVal = mEnv[i]->GetVal();
         i++;
      }
   }
}

double Envelope::SolveIntegralOfInverse( double t0, double area )
{
   if(area == 0.0)
      return t0;
   if(area < 0.0)
   {
      fprintf( stderr, "SolveIntegralOfInverse called with negative area, this is not supported!\n" );
      return t0;
   }
   
   unsigned int count = mEnv.Count();
   if(count == 0) // 'empty' envelope
      return t0 + area * mDefaultValue;
   
   double lastT, lastVal;
   unsigned int i; // this is the next point to check
   if(t0 < mEnv[0]->GetT()) // t0 preceding the first point
   {
      i = 1;
      lastT = mEnv[0]->GetT();
      lastVal = mEnv[0]->GetVal();
      double added = (lastT - t0) / lastVal;
      if(added >= area)
         return t0 + area * mEnv[0]->GetVal();
      area -= added;
   }
   else if(t0 >= mEnv[count - 1]->GetT()) // t0 following the last point
   {
      return t0 + area * mEnv[count - 1]->GetVal();
   }
   else // t0 enclosed by points
   {
      // Skip any points that come before t0 using binary search
      int lo, hi;
      BinarySearchForTime(lo, hi, t0);
      lastVal = InterpolatePoints(mEnv[lo]->GetVal(), mEnv[hi]->GetVal(), (t0 - mEnv[lo]->GetT()) / (mEnv[hi]->GetT() - mEnv[lo]->GetT()), mDB);
      lastT = t0;
      i = hi; // the point immediately after t0.
   }
   
   // loop through the rest of the envelope points until we get to t1
   while (1)
   {
      if(i >= count) // the requested range extends beyond the last point
      {
         return lastT + area * lastVal;
      }
      else
      {
         double added = IntegrateInverseInterpolated(lastVal, mEnv[i]->GetVal(), mEnv[i]->GetT() - lastT, mDB);
         if(added >= area)
            return lastT + SolveIntegrateInverseInterpolated(lastVal, mEnv[i]->GetVal(), mEnv[i]->GetT() - lastT, area, mDB);
         area -= added;
         lastT = mEnv[i]->GetT();
         lastVal = mEnv[i]->GetVal();
         i++;
      }
   }
}

void Envelope::print()
{
   for( unsigned int i = 0; i < mEnv.Count(); i++ )
      printf( "(%.2f, %.2f)\n", mEnv[i]->GetT(), mEnv[i]->GetVal() );
}

void checkResult( int n, double a, double b )
{
   if( (a-b > 0 ? a-b : b-a) > 0.0000001 )
   {
      printf( "Envelope:  Result #%d is: %f, should be %f\n", n, a, b );
      //exit( -1 );
   }
}

void Envelope::testMe()
{
   double t0=0, t1=0;

   SetInterpolateDB(false);
   Mirror(false);

   Flatten(0.5);
   checkResult( 1, Integral(0.0,100.0), 50);
   checkResult( 2, Integral(-10.0,10.0), 10);

   Flatten(0.5);
   checkResult( 3, Integral(0.0,100.0), 50);
   checkResult( 4, Integral(-10.0,10.0), 10);
   checkResult( 5, Integral(-20.0,-10.0), 5);

   Flatten(0.5);
   Insert( 5.0, 0.5 );
   checkResult( 6, Integral(0.0,100.0), 50);
   checkResult( 7, Integral(-10.0,10.0), 10);

   Flatten(0.0);
   Insert( 0.0, 0.0 );
   Insert( 5.0, 1.0 );
   Insert( 10.0, 0.0 );
   t0 = 10.0 - .1;
   t1 = 10.0 + .1;
   double result = Integral(0.0,t1);
   double resulta = Integral(0.0,t0);
   double resultb = Integral(t0,t1);
   // Integrals should be additive
   checkResult( 8, result - resulta - resultb, 0);

   Flatten(0.0);
   Insert( 0.0, 0.0 );
   Insert( 5.0, 1.0 );
   Insert( 10.0, 0.0 );
   t0 = 10.0 - .1;
   t1 = 10.0 + .1;
   checkResult( 9, Integral(0.0,t1), 5);
   checkResult( 10, Integral(0.0,t0), 4.999);
   checkResult( 11, Integral(t0,t1), .001);

   WX_CLEAR_ARRAY(mEnv);
   Insert( 0.0, 0.0 );
   Insert( 5.0, 1.0 );
   Insert( 10.0, 0.0 );
   checkResult( 12, NumberOfPointsAfter( -1 ), 3 );
   checkResult( 13, NumberOfPointsAfter( 0 ), 2 );
   checkResult( 14, NumberOfPointsAfter( 1 ), 2 );
   checkResult( 15, NumberOfPointsAfter( 5 ), 1 );
   checkResult( 16, NumberOfPointsAfter( 7 ), 1 );
   checkResult( 17, NumberOfPointsAfter( 10 ), 0 );
   checkResult( 18, NextPointAfter( 0 ), 5 );
   checkResult( 19, NextPointAfter( 5 ), 10 );
}

