mirror of
https://github.com/cookiengineer/audacity
synced 2025-06-29 06:38:38 +02:00
... Regression happened at 3f1fd8ced0fb5fe617ad2dd7808cd1ae2ca2c0f3 The heights were written to file, but not read back again. This fix was done carefully to avoid making dependency cycles. We don't want Track to depend on TrackView or TrackControls. It's not great that LabelTrack, NoteTrack, TimeTrack, and WaveTrack now have those dependencies, but at least they don't make cycles. It would be better to figure out how to attach the view and controls to the track with ClientData, then just invoke BuildAll to repopulate the view and controls, so that they are non-null when you reach Track::HandleCommonXMLAttribute.
309 lines
8.1 KiB
C++
309 lines
8.1 KiB
C++
/**********************************************************************
|
|
|
|
Audacity: A Digital Audio Editor
|
|
|
|
TimeTrack.cpp
|
|
|
|
Dr William Bland
|
|
|
|
*******************************************************************//*!
|
|
|
|
\class TimeTrack
|
|
\brief A kind of Track used to 'warp time'
|
|
|
|
*//*******************************************************************/
|
|
|
|
#include "Audacity.h"
|
|
#include "TimeTrack.h"
|
|
|
|
#include "Experimental.h"
|
|
|
|
#include <cfloat>
|
|
#include <wx/wxcrtvararg.h>
|
|
#include <wx/dc.h>
|
|
#include <wx/intl.h>
|
|
#include "widgets/Ruler.h"
|
|
#include "Envelope.h"
|
|
#include "Project.h"
|
|
#include "ProjectSettings.h"
|
|
#include "ProjectFileIORegistry.h"
|
|
|
|
#include "tracks/ui/TrackView.h"
|
|
#include "tracks/ui/TrackControls.h"
|
|
|
|
|
|
//TODO-MB: are these sensible values?
|
|
#define TIMETRACK_MIN 0.01
|
|
#define TIMETRACK_MAX 10.0
|
|
|
|
std::shared_ptr<TimeTrack> TrackFactory::NewTimeTrack()
|
|
{
|
|
return std::make_shared<TimeTrack>(mDirManager, mZoomInfo);
|
|
}
|
|
|
|
static ProjectFileIORegistry::Entry registerFactory{
|
|
wxT( "timetrack" ),
|
|
[]( AudacityProject &project ){
|
|
auto &trackFactory = TrackFactory::Get( project );
|
|
auto &tracks = TrackList::Get( project );
|
|
auto result = tracks.Add(trackFactory.NewTimeTrack());
|
|
TrackView::Get( *result );
|
|
TrackControls::Get( *result );
|
|
return result;
|
|
}
|
|
};
|
|
|
|
TimeTrack::TimeTrack(const std::shared_ptr<DirManager> &projDirManager, const ZoomInfo *zoomInfo):
|
|
Track(projDirManager)
|
|
, mZoomInfo(zoomInfo)
|
|
{
|
|
mEnvelope = std::make_unique<BoundedEnvelope>(true, TIMETRACK_MIN, TIMETRACK_MAX, 1.0);
|
|
|
|
SetRangeLower( 0.9 );
|
|
SetRangeUpper( 1.1 );
|
|
mDisplayLog = false;
|
|
|
|
mEnvelope->SetTrackLen(DBL_MAX);
|
|
mEnvelope->SetOffset(0);
|
|
|
|
SetDefaultName(_("Time Track"));
|
|
SetName(GetDefaultName());
|
|
|
|
mRuler = std::make_unique<Ruler>();
|
|
mRuler->SetUseZoomInfo(0, mZoomInfo);
|
|
mRuler->SetLabelEdges(false);
|
|
mRuler->SetFormat(Ruler::TimeFormat);
|
|
}
|
|
|
|
TimeTrack::TimeTrack(const TimeTrack &orig, double *pT0, double *pT1)
|
|
: Track(orig)
|
|
, mZoomInfo(orig.mZoomInfo)
|
|
{
|
|
Init(orig); // this copies the TimeTrack metadata (name, range, etc)
|
|
|
|
auto len = DBL_MAX;
|
|
if (pT0 && pT1) {
|
|
len = *pT1 - *pT0;
|
|
mEnvelope = std::make_unique<BoundedEnvelope>( *orig.mEnvelope, *pT0, *pT1 );
|
|
}
|
|
else
|
|
mEnvelope = std::make_unique<BoundedEnvelope>( *orig.mEnvelope );
|
|
|
|
SetRangeLower(orig.GetRangeLower());
|
|
SetRangeUpper(orig.GetRangeUpper());
|
|
|
|
mEnvelope->SetTrackLen( len );
|
|
mEnvelope->SetOffset(0);
|
|
|
|
///@TODO: Give Ruler:: a copy-constructor instead of this?
|
|
mRuler = std::make_unique<Ruler>();
|
|
mRuler->SetUseZoomInfo(0, mZoomInfo);
|
|
mRuler->SetLabelEdges(false);
|
|
mRuler->SetFormat(Ruler::TimeFormat);
|
|
}
|
|
|
|
// Copy the track metadata but not the contents.
|
|
void TimeTrack::Init(const TimeTrack &orig)
|
|
{
|
|
Track::Init(orig);
|
|
SetDefaultName(orig.GetDefaultName());
|
|
SetName(orig.GetName());
|
|
SetDisplayLog(orig.GetDisplayLog());
|
|
}
|
|
|
|
TimeTrack::~TimeTrack()
|
|
{
|
|
}
|
|
|
|
double TimeTrack::GetRangeLower() const
|
|
{
|
|
return mEnvelope->GetRangeLower();
|
|
}
|
|
|
|
double TimeTrack::GetRangeUpper() const
|
|
{
|
|
return mEnvelope->GetRangeUpper();
|
|
}
|
|
|
|
void TimeTrack::SetRangeLower(double lower)
|
|
{
|
|
mEnvelope->SetRangeLower( lower );
|
|
}
|
|
|
|
void TimeTrack::SetRangeUpper(double upper)
|
|
{
|
|
mEnvelope->SetRangeUpper( upper );
|
|
}
|
|
|
|
Track::Holder TimeTrack::Cut( double t0, double t1 )
|
|
{
|
|
auto result = Copy( t0, t1, false );
|
|
Clear( t0, t1 );
|
|
return result;
|
|
}
|
|
|
|
Track::Holder TimeTrack::Copy( double t0, double t1, bool ) const
|
|
{
|
|
return std::make_shared<TimeTrack>( *this, &t0, &t1 );
|
|
}
|
|
|
|
void TimeTrack::Clear(double t0, double t1)
|
|
{
|
|
auto sampleTime = 1.0 / ProjectSettings::Get( *GetActiveProject() ).GetRate();
|
|
mEnvelope->CollapseRegion( t0, t1, sampleTime );
|
|
}
|
|
|
|
void TimeTrack::Paste(double t, const Track * src)
|
|
{
|
|
bool bOk = src && src->TypeSwitch< bool >( [&] (const TimeTrack *tt) {
|
|
auto sampleTime = 1.0 / ProjectSettings::Get( *GetActiveProject() ).GetRate();
|
|
mEnvelope->PasteEnvelope
|
|
(t, tt->mEnvelope.get(), sampleTime);
|
|
return true;
|
|
} );
|
|
|
|
if (! bOk )
|
|
// THROW_INCONSISTENCY_EXCEPTION // ?
|
|
(void)0;// intentionally do nothing.
|
|
}
|
|
|
|
void TimeTrack::Silence(double WXUNUSED(t0), double WXUNUSED(t1))
|
|
{
|
|
}
|
|
|
|
void TimeTrack::InsertSilence(double t, double len)
|
|
{
|
|
mEnvelope->InsertSpace(t, len);
|
|
}
|
|
|
|
Track::Holder TimeTrack::Clone() const
|
|
{
|
|
return std::make_shared<TimeTrack>(*this);
|
|
}
|
|
|
|
bool TimeTrack::GetInterpolateLog() const
|
|
{
|
|
return mEnvelope->GetExponential();
|
|
}
|
|
|
|
void TimeTrack::SetInterpolateLog(bool interpolateLog) {
|
|
mEnvelope->SetExponential(interpolateLog);
|
|
}
|
|
|
|
bool TimeTrack::HandleXMLTag(const wxChar *tag, const wxChar **attrs)
|
|
{
|
|
if (!wxStrcmp(tag, wxT("timetrack"))) {
|
|
mRescaleXMLValues = true; // will be set to false if upper/lower is found
|
|
long nValue;
|
|
while(*attrs) {
|
|
const wxChar *attr = *attrs++;
|
|
const wxChar *value = *attrs++;
|
|
|
|
if (!value)
|
|
break;
|
|
|
|
const wxString strValue = value;
|
|
if (this->Track::HandleCommonXMLAttribute(attr, strValue))
|
|
;
|
|
else if (!wxStrcmp(attr, wxT("rangelower")))
|
|
{
|
|
SetRangeLower( Internat::CompatibleToDouble(value) );
|
|
mRescaleXMLValues = false;
|
|
}
|
|
else if (!wxStrcmp(attr, wxT("rangeupper")))
|
|
{
|
|
SetRangeUpper( Internat::CompatibleToDouble(value) );
|
|
mRescaleXMLValues = false;
|
|
}
|
|
else if (!wxStrcmp(attr, wxT("displaylog")) &&
|
|
XMLValueChecker::IsGoodInt(strValue) && strValue.ToLong(&nValue))
|
|
{
|
|
SetDisplayLog(nValue != 0);
|
|
//TODO-MB: This causes a graphical glitch, TrackPanel should probably be Refresh()ed after loading.
|
|
// I don't know where to do this though.
|
|
}
|
|
else if (!wxStrcmp(attr, wxT("interpolatelog")) &&
|
|
XMLValueChecker::IsGoodInt(strValue) && strValue.ToLong(&nValue))
|
|
{
|
|
SetInterpolateLog(nValue != 0);
|
|
}
|
|
|
|
} // while
|
|
if(mRescaleXMLValues)
|
|
mEnvelope->SetRange(0.0, 1.0); // this will be restored to the actual range later
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void TimeTrack::HandleXMLEndTag(const wxChar * WXUNUSED(tag))
|
|
{
|
|
if(mRescaleXMLValues)
|
|
{
|
|
mRescaleXMLValues = false;
|
|
mEnvelope->RescaleValues(GetRangeLower(), GetRangeUpper());
|
|
mEnvelope->SetRange(TIMETRACK_MIN, TIMETRACK_MAX);
|
|
}
|
|
}
|
|
|
|
XMLTagHandler *TimeTrack::HandleXMLChild(const wxChar *tag)
|
|
{
|
|
if (!wxStrcmp(tag, wxT("envelope")))
|
|
return mEnvelope.get();
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void TimeTrack::WriteXML(XMLWriter &xmlFile) const
|
|
// may throw
|
|
{
|
|
xmlFile.StartTag(wxT("timetrack"));
|
|
this->Track::WriteCommonXMLAttributes( xmlFile );
|
|
|
|
//xmlFile.WriteAttr(wxT("channel"), mChannel);
|
|
//xmlFile.WriteAttr(wxT("offset"), mOffset, 8);
|
|
xmlFile.WriteAttr(wxT("rangelower"), GetRangeLower(), 12);
|
|
xmlFile.WriteAttr(wxT("rangeupper"), GetRangeUpper(), 12);
|
|
xmlFile.WriteAttr(wxT("displaylog"), GetDisplayLog());
|
|
xmlFile.WriteAttr(wxT("interpolatelog"), GetInterpolateLog());
|
|
|
|
mEnvelope->WriteXML(xmlFile);
|
|
|
|
xmlFile.EndTag(wxT("timetrack"));
|
|
}
|
|
|
|
void TimeTrack::testMe()
|
|
{
|
|
GetEnvelope()->Flatten(0.0);
|
|
GetEnvelope()->InsertOrReplace(0.0, 0.2);
|
|
GetEnvelope()->InsertOrReplace(5.0 - 0.001, 0.2);
|
|
GetEnvelope()->InsertOrReplace(5.0 + 0.001, 1.3);
|
|
GetEnvelope()->InsertOrReplace(10.0, 1.3);
|
|
|
|
double value1 = GetEnvelope()->Integral(2.0, 13.0);
|
|
double expected1 = (5.0 - 2.0) * 0.2 + (13.0 - 5.0) * 1.3;
|
|
double value2 = GetEnvelope()->IntegralOfInverse(2.0, 13.0);
|
|
double expected2 = (5.0 - 2.0) / 0.2 + (13.0 - 5.0) / 1.3;
|
|
if( fabs(value1 - expected1) > 0.01 )
|
|
{
|
|
wxPrintf( "TimeTrack: Integral failed! expected %f got %f\n", expected1, value1);
|
|
}
|
|
if( fabs(value2 - expected2) > 0.01 )
|
|
{
|
|
wxPrintf( "TimeTrack: IntegralOfInverse failed! expected %f got %f\n", expected2, value2);
|
|
}
|
|
|
|
/*double reqt0 = 10.0 - .1;
|
|
double reqt1 = 10.0 + .1;
|
|
double t0 = warp( reqt0 );
|
|
double t1 = warp( reqt1 );
|
|
if( t0 > t1 )
|
|
{
|
|
wxPrintf( "TimeTrack: Warping reverses an interval! [%.2f,%.2f] -> [%.2f,%.2f]\n",
|
|
reqt0, reqt1,
|
|
t0, t1 );
|
|
}*/
|
|
}
|
|
|