mirror of
https://github.com/cookiengineer/audacity
synced 2025-10-21 14:02:57 +02:00
Split EnvelopeEditor.cpp from Envelope.cpp...
... Envelope drops down out of the big strongly connected component, the new piece stays behind
This commit is contained in:
375
src/Envelope.cpp
375
src/Envelope.cpp
@@ -11,12 +11,13 @@
|
||||
*******************************************************************//**
|
||||
|
||||
\class Envelope
|
||||
\brief Draggable curve used in TrackPanel for varying amplification.
|
||||
\brief Piecewise linear or piecewise exponential function from double to double
|
||||
|
||||
This class manages an envelope - i.e. a piecewise linear funtion
|
||||
This class manages an envelope - i.e. a function
|
||||
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.
|
||||
waveform, but it is also used to shape the Equalization curve, and in
|
||||
TimeTrack to determine a time warp.
|
||||
|
||||
*//****************************************************************//**
|
||||
|
||||
@@ -30,21 +31,14 @@ a draggable point type.
|
||||
|
||||
#include "Experimental.h"
|
||||
|
||||
#include "ViewInfo.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#include <wx/wxcrtvararg.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 "TrackArtist.h"
|
||||
|
||||
static const double VALUE_TOLERANCE = 0.001;
|
||||
|
||||
Envelope::Envelope(bool exponential, double minValue, double maxValue, double defaultValue)
|
||||
@@ -307,91 +301,6 @@ static double Limit( double Lo, double Value, double Hi )
|
||||
}
|
||||
#endif
|
||||
|
||||
/// TODO: This should probably move to track artist.
|
||||
static 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);
|
||||
}
|
||||
}
|
||||
|
||||
#include "TrackPanelDrawingContext.h"
|
||||
|
||||
/// TODO: This should probably move to track artist.
|
||||
void Envelope::DrawPoints
|
||||
(TrackPanelDrawingContext &context, const wxRect & r,
|
||||
bool dB, double dBRange,
|
||||
float zoomMin, float zoomMax, bool mirrored) const
|
||||
{
|
||||
auto &dc = context.dc;
|
||||
const auto artist = TrackArtist::Get( context );
|
||||
const auto &zoomInfo = *artist->pZoomInfo;
|
||||
|
||||
bool highlight = false;
|
||||
#ifdef EXPERIMENTAL_TRACK_PANEL_HIGHLIGHTING
|
||||
auto target = dynamic_cast<EnvelopeHandle*>(context.target.get());
|
||||
highlight = target && target->GetEnvelope() == this;
|
||||
#endif
|
||||
wxPen &pen = highlight ? AColor::uglyPen : AColor::envelopePen;
|
||||
dc.SetPen( pen );
|
||||
dc.SetBrush(*wxWHITE_BRUSH);
|
||||
|
||||
for (int i = 0; i < (int)mEnv.size(); i++) {
|
||||
const double time = mEnv[i].GetT() + mOffset;
|
||||
const wxInt64 position = zoomInfo.TimeToPosition(time);
|
||||
if (position >= 0 && position < r.width) {
|
||||
// Change colour if this is the draggable point...
|
||||
if (i == mDragPoint) {
|
||||
dc.SetPen( pen );
|
||||
dc.SetBrush(AColor::envelopeBrush);
|
||||
}
|
||||
|
||||
double v = mEnv[i].GetVal();
|
||||
int x = (int)(position);
|
||||
int y, y2;
|
||||
|
||||
y = GetWaveYPos(v, zoomMin, zoomMax, r.height, dB,
|
||||
true, dBRange, false);
|
||||
if (!mirrored) {
|
||||
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
|
||||
// TrackArt::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( pen );
|
||||
dc.SetBrush(*wxWHITE_BRUSH);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Envelope::HandleXMLTag(const wxChar *tag, const wxChar **attrs)
|
||||
{
|
||||
// Return unless it's the envelope tag.
|
||||
@@ -447,208 +356,6 @@ void Envelope::WriteXML(XMLWriter &xmlFile) const
|
||||
xmlFile.EndTag(wxT("envelope"));
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
inline int SQR(int x) { return x * x; }
|
||||
}
|
||||
|
||||
/// 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 EnvelopeEditor::ValueOfPixel( int y, int height, bool upper,
|
||||
bool dB, double dBRange,
|
||||
float zoomMin, float zoomMax)
|
||||
{
|
||||
float v = ::ValueOfPixel(y, height, 0 != mContourOffset, dB, dBRange, zoomMin, zoomMax);
|
||||
|
||||
// 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 mEnvelope.ClampValue(v);
|
||||
else
|
||||
return mEnvelope.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 EnvelopeEditor::HandleMouseButtonDown(const wxMouseEvent & event, wxRect & r,
|
||||
const ZoomInfo &zoomInfo,
|
||||
bool dB, double dBRange,
|
||||
float zoomMin, float zoomMax)
|
||||
{
|
||||
int ctr = (int)(r.height * zoomMax / (zoomMax - zoomMin));
|
||||
bool upper = !mMirrored || (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();
|
||||
|
||||
int bestNum = -1;
|
||||
int bestDistSqr = 100; // Must be within 10 pixel radius.
|
||||
|
||||
// Member variables hold state that will be needed in dragging.
|
||||
mButton = event.GetButton();
|
||||
mContourOffset = false;
|
||||
|
||||
// wxLogDebug(wxT("Y:%i Height:%i Offset:%i"), y, height, mContourOffset );
|
||||
int len = mEnvelope.GetNumberOfPoints();
|
||||
|
||||
// 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
|
||||
const double time = mEnvelope[i].GetT() + mEnvelope.GetOffset();
|
||||
const wxInt64 position = zoomInfo.TimeToPosition(time);
|
||||
if (position >= 0 && position < r.width) {
|
||||
|
||||
int x = (int)(position);
|
||||
int y[4];
|
||||
int numControlPoints;
|
||||
|
||||
// Outer control points
|
||||
double value = mEnvelope[i].GetVal();
|
||||
y[0] = GetWaveYPos(value, zoomMin, zoomMax, r.height,
|
||||
dB, true, dBRange, false);
|
||||
y[1] = GetWaveYPos(-value, zoomMin, zoomMax, r.height,
|
||||
dB, true, dBRange, false);
|
||||
|
||||
// Inner control points(contour)
|
||||
y[2] = GetWaveYPos(value, zoomMin, zoomMax, r.height,
|
||||
dB, false, dBRange, false);
|
||||
y[3] = GetWaveYPos(-value -.00000001, zoomMin, zoomMax,
|
||||
r.height, dB, false, dBRange, false);
|
||||
|
||||
numControlPoints = 4;
|
||||
|
||||
if (y[2] > y[3])
|
||||
numControlPoints = 2;
|
||||
|
||||
if (!mMirrored)
|
||||
numControlPoints = 1;
|
||||
|
||||
const int deltaXSquared = SQR(x - (event.m_x - r.x));
|
||||
for(int j=0; j<numControlPoints; j++){
|
||||
|
||||
const int dSqr = deltaXSquared + SQR(y[j] - (event.m_y - r.y));
|
||||
if (dSqr < bestDistSqr) {
|
||||
bestNum = i;
|
||||
bestDistSqr = dSqr;
|
||||
mContourOffset = (bool)(j > 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bestNum >= 0) {
|
||||
mEnvelope.SetDragPoint(bestNum);
|
||||
}
|
||||
else {
|
||||
// TODO: Extract this into a function CreateNewPoint
|
||||
const double when = zoomInfo.PositionToTime(event.m_x, r.x);
|
||||
|
||||
// if (when <= 0 || when >= mTrackLen)
|
||||
// return false;
|
||||
|
||||
const double v = mEnvelope.GetValue( when );
|
||||
|
||||
int ct = GetWaveYPos( v, zoomMin, zoomMax, r.height, dB,
|
||||
false, dBRange, false) ;
|
||||
int cb = GetWaveYPos( -v-.000000001, zoomMin, zoomMax, r.height, dB,
|
||||
false, dBRange, false) ;
|
||||
if (ct <= cb || !mMirrored) {
|
||||
int t = GetWaveYPos( v, zoomMin, zoomMax, r.height, dB,
|
||||
true, dBRange, false) ;
|
||||
int b = GetWaveYPos( -v, zoomMin, zoomMax, r.height, dB,
|
||||
true, dBRange, false) ;
|
||||
|
||||
ct = (t + ct) / 2;
|
||||
cb = (b + cb) / 2;
|
||||
|
||||
if (mMirrored &&
|
||||
(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, dBRange,
|
||||
zoomMin, zoomMax);
|
||||
|
||||
mEnvelope.SetDragPoint(mEnvelope.InsertOrReplace(when, newVal));
|
||||
mDirty = true;
|
||||
}
|
||||
|
||||
mUpper = upper;
|
||||
|
||||
// const int dragPoint = mEnvelope.GetDragPoint();
|
||||
// mInitialVal = mEnvelope[dragPoint].GetVal();
|
||||
// mInitialY = event.m_y+mContourOffset;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void EnvelopeEditor::MoveDragPoint(const wxMouseEvent & event, wxRect & r,
|
||||
const ZoomInfo &zoomInfo, bool dB, double dBRange,
|
||||
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, dBRange,
|
||||
zoomMin, zoomMax);
|
||||
|
||||
// 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 = zoomInfo.PositionToTime(event.m_x, r.x) - mEnvelope.GetOffset();
|
||||
mEnvelope.MoveDragPoint(newWhen, newVal);
|
||||
}
|
||||
|
||||
bool EnvelopeEditor::HandleDragging(const wxMouseEvent & event, wxRect & r,
|
||||
const ZoomInfo &zoomInfo, bool dB, double dBRange,
|
||||
float zoomMin, float zoomMax,
|
||||
float WXUNUSED(eMin), float WXUNUSED(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).
|
||||
// ...we're dragging it.
|
||||
MoveDragPoint( event, r, zoomInfo, dB, dBRange, zoomMin, zoomMax);
|
||||
return true;
|
||||
}
|
||||
|
||||
if(!mEnvelope.GetDragPointValid())
|
||||
// IF we already know we're deleting THEN no envelope point to update.
|
||||
return false;
|
||||
|
||||
// Invalidate the point
|
||||
mEnvelope.SetDragPointValid(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Exit dragging mode and delete dragged point if neccessary.
|
||||
bool EnvelopeEditor::HandleMouseButtonUp()
|
||||
{
|
||||
mEnvelope.ClearDragPoint();
|
||||
mButton = wxMOUSE_BTN_NONE;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Envelope::Delete( int point )
|
||||
{
|
||||
mEnv.erase(mEnv.begin() + point);
|
||||
@@ -659,22 +366,6 @@ void Envelope::Insert(int point, const EnvPoint &p)
|
||||
mEnv.insert(mEnv.begin() + point, p);
|
||||
}
|
||||
|
||||
// Returns true if parent needs to be redrawn
|
||||
bool EnvelopeEditor::MouseEvent(const wxMouseEvent & event, wxRect & r,
|
||||
const ZoomInfo &zoomInfo, bool dB, double dBRange,
|
||||
float zoomMin, float zoomMax)
|
||||
{
|
||||
if (event.ButtonDown() && mButton == wxMOUSE_BTN_NONE)
|
||||
return HandleMouseButtonDown( event, r, zoomInfo, dB, dBRange,
|
||||
zoomMin, zoomMax);
|
||||
if (event.Dragging() && mEnvelope.GetDragPoint() >= 0)
|
||||
return HandleDragging( event, r, zoomInfo, dB, dBRange,
|
||||
zoomMin, zoomMax);
|
||||
if (event.ButtonUp() && event.GetButton() == mButton)
|
||||
return HandleMouseButtonUp();
|
||||
return false;
|
||||
}
|
||||
|
||||
void Envelope::CollapseRegion( double t0, double t1, double sampleDur )
|
||||
// NOFAIL-GUARANTEE
|
||||
{
|
||||
@@ -1346,48 +1037,6 @@ void Envelope::GetValuesRelative
|
||||
}
|
||||
}
|
||||
|
||||
void Envelope::GetValues
|
||||
( double alignedTime, double sampleDur,
|
||||
double *buffer, int bufferLen, int leftOffset,
|
||||
const ZoomInfo &zoomInfo )
|
||||
const
|
||||
{
|
||||
// Getting many envelope values, corresponding to pixel columns, which may
|
||||
// not be uniformly spaced in time when there is a fisheye.
|
||||
|
||||
double prevDiscreteTime=0.0, prevSampleVal=0.0, nextSampleVal=0.0;
|
||||
for ( int xx = 0; xx < bufferLen; ++xx ) {
|
||||
auto time = zoomInfo.PositionToTime( xx, -leftOffset );
|
||||
if ( sampleDur <= 0 )
|
||||
// Sample interval not defined (as for time track)
|
||||
buffer[xx] = GetValue( time );
|
||||
else {
|
||||
// The level of zoom-in may resolve individual samples.
|
||||
// If so, then instead of evaluating the envelope directly,
|
||||
// we draw a piecewise curve with knees at each sample time.
|
||||
// This actually makes clearer what happens as you drag envelope
|
||||
// points and make discontinuities.
|
||||
auto leftDiscreteTime = alignedTime +
|
||||
sampleDur * floor( ( time - alignedTime ) / sampleDur );
|
||||
if ( xx == 0 || leftDiscreteTime != prevDiscreteTime ) {
|
||||
prevDiscreteTime = leftDiscreteTime;
|
||||
prevSampleVal =
|
||||
GetValue( prevDiscreteTime, sampleDur );
|
||||
nextSampleVal =
|
||||
GetValue( prevDiscreteTime + sampleDur, sampleDur );
|
||||
}
|
||||
auto ratio = ( time - leftDiscreteTime ) / sampleDur;
|
||||
if ( GetExponential() )
|
||||
buffer[ xx ] = exp(
|
||||
( 1.0 - ratio ) * log( prevSampleVal )
|
||||
+ ratio * log( nextSampleVal ) );
|
||||
else
|
||||
buffer[ xx ] =
|
||||
( 1.0 - ratio ) * prevSampleVal + ratio * nextSampleVal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// relative time
|
||||
int Envelope::NumberOfPointsAfter(double t) const
|
||||
{
|
||||
@@ -1806,19 +1455,3 @@ void Envelope::testMe()
|
||||
checkResult( 18, NextPointAfter( 0 ), 5 );
|
||||
checkResult( 19, NextPointAfter( 5 ), 10 );
|
||||
}
|
||||
|
||||
EnvelopeEditor::EnvelopeEditor(Envelope &envelope, bool mirrored)
|
||||
: mEnvelope(envelope)
|
||||
, mMirrored(mirrored)
|
||||
, mContourOffset(-1)
|
||||
// , mInitialVal(-1.0)
|
||||
// , mInitialY(-1)
|
||||
, mUpper(false)
|
||||
, mButton(wxMOUSE_BTN_NONE)
|
||||
, mDirty(false)
|
||||
{
|
||||
}
|
||||
|
||||
EnvelopeEditor::~EnvelopeEditor()
|
||||
{
|
||||
}
|
||||
|
Reference in New Issue
Block a user