audacia/src/WaveTrack.cpp

2824 lines
83 KiB
C++
Raw Permalink Normal View History

/**********************************************************************
Audacity: A Digital Audio Editor
WaveTrack.cpp
Dominic Mazzoni
*******************************************************************//**
\class WaveTrack
\brief A Track that contains audio waveform data.
*//****************************************************************//**
\class WaveTrack::Location
\brief Used only by WaveTrack, a special way to hold location that
can accommodate merged regions.
*//****************************************************************/
/*!
@class WaveTrackFactory
@brief Used to create or clone a WaveTrack, with appropriate context
from the project that will own the track.
*/
2015-04-08 16:15:30 +00:00
#include "WaveTrack.h"
#include "WaveClip.h"
#include <wx/defs.h>
#include <wx/intl.h>
#include <wx/debug.h>
2012-04-24 22:44:55 +00:00
#include <float.h>
#include <math.h>
#include <algorithm>
#include "float_cast.h"
#include "Envelope.h"
#include "Sequence.h"
#include "ProjectFileIORegistry.h"
2019-05-29 15:31:40 +00:00
#include "ProjectSettings.h"
#include "Prefs.h"
#include "effects/TimeWarper.h"
#include "prefs/QualitySettings.h"
#include "prefs/SpectrogramSettings.h"
#include "prefs/TracksPrefs.h"
#include "prefs/TracksBehaviorsPrefs.h"
#include "prefs/WaveformSettings.h"
#include "InconsistencyException.h"
#include "tracks/ui/TrackView.h"
#include "tracks/ui/TrackControls.h"
using std::max;
static ProjectFileIORegistry::Entry registerFactory{
wxT( "wavetrack" ),
[]( AudacityProject &project ){
auto &trackFactory = WaveTrackFactory::Get( project );
auto &tracks = TrackList::Get( project );
auto result = tracks.Add(trackFactory.NewWaveTrack());
TrackView::Get( *result );
TrackControls::Get( *result );
return result;
}
};
WaveTrack::Holder WaveTrackFactory::DuplicateWaveTrack(const WaveTrack &orig)
{
return std::static_pointer_cast<WaveTrack>( orig.Duplicate() );
}
WaveTrack::Holder WaveTrackFactory::NewWaveTrack(sampleFormat format, double rate)
{
if (format == (sampleFormat)0)
format = QualitySettings::SampleFormatChoice();
if (rate == 0)
rate = mSettings.GetRate();
Unitary changes (#599) * Define SampleBlockFactory replacing static members of SampleBlock... ... This will become an abstract base class * Sequence and WaveTrack only store SampleBlockFactory not Project... ... This adds a dependency from Track to SampleBlock which temporarily enlarges a cycle in the dependency graph * Register a global factory of SampleBlockFactory... ... so that later we can make an abstract SampleBlockFactory, separate from the concrete implementation in terms of sqlite, and inject the dependency at startup avoiding static dependency * New concrete classes SqliteSampleBlock, SqliteSampleBlockFactory... ... separated from abstract base classes and put into a new source file, breaking dependency cycles, and perhaps allowing easy reimplementation for other databases in the future. Note that the new file is a header-less plug-in! Nothing depends on it. It uses static initialization to influence the program's behavior. * Compile dependency on sqlite3.h limited to just two .cpp files... ... these are ProjectFileIO.cpp and SqliteSampleBlock.cpp. But there is still close cooperation of ProjectFileIO and SqliteSampleBlock.cpp. This suggests that these files ought to be merged, and perhaps ProjectFileIO also needs to be split into abstract and concrete classes, and there should be another injection of a factory function at startup. That will make the choice of database implementation even more modular. Also removed one unnecessary inclusion of ProjectFileIO.h * Fix crashes cutting and pasting cross-project... ... in case the source project is closed before the paste happens. This caused destruction of the ProjectFileIO object and a closing of the sqlite database with the sample data in it, leaving dangling references in the SqliteSampleBlock objects. The fix is that the SqliteSampleBlockFactory object holds a shared_ptr to the ProjectFileIO object. So the clipboard may own WaveTracks, which own WaveClips, which own Sequences, which own SqliteSampleBlockFactories, which keep the ProjectFileIO and the database connection alive until the clipboard is cleared. The consequence of the fix is delayed closing of the entire database associated with the source project. If the source project is reopened before the clipboard is cleared, will there be correct concurrent access to the same persistent store? My preliminary trials suggest this is so (reopening a saved project, deleting from it, closing it again -- the clipboard contents are still unchanged and available).
2020-07-02 23:11:38 +00:00
return std::make_shared<WaveTrack> ( mpFactory, format, rate );
}
Unitary changes (#599) * Define SampleBlockFactory replacing static members of SampleBlock... ... This will become an abstract base class * Sequence and WaveTrack only store SampleBlockFactory not Project... ... This adds a dependency from Track to SampleBlock which temporarily enlarges a cycle in the dependency graph * Register a global factory of SampleBlockFactory... ... so that later we can make an abstract SampleBlockFactory, separate from the concrete implementation in terms of sqlite, and inject the dependency at startup avoiding static dependency * New concrete classes SqliteSampleBlock, SqliteSampleBlockFactory... ... separated from abstract base classes and put into a new source file, breaking dependency cycles, and perhaps allowing easy reimplementation for other databases in the future. Note that the new file is a header-less plug-in! Nothing depends on it. It uses static initialization to influence the program's behavior. * Compile dependency on sqlite3.h limited to just two .cpp files... ... these are ProjectFileIO.cpp and SqliteSampleBlock.cpp. But there is still close cooperation of ProjectFileIO and SqliteSampleBlock.cpp. This suggests that these files ought to be merged, and perhaps ProjectFileIO also needs to be split into abstract and concrete classes, and there should be another injection of a factory function at startup. That will make the choice of database implementation even more modular. Also removed one unnecessary inclusion of ProjectFileIO.h * Fix crashes cutting and pasting cross-project... ... in case the source project is closed before the paste happens. This caused destruction of the ProjectFileIO object and a closing of the sqlite database with the sample data in it, leaving dangling references in the SqliteSampleBlock objects. The fix is that the SqliteSampleBlockFactory object holds a shared_ptr to the ProjectFileIO object. So the clipboard may own WaveTracks, which own WaveClips, which own Sequences, which own SqliteSampleBlockFactories, which keep the ProjectFileIO and the database connection alive until the clipboard is cleared. The consequence of the fix is delayed closing of the entire database associated with the source project. If the source project is reopened before the clipboard is cleared, will there be correct concurrent access to the same persistent store? My preliminary trials suggest this is so (reopening a saved project, deleting from it, closing it again -- the clipboard contents are still unchanged and available).
2020-07-02 23:11:38 +00:00
WaveTrack::WaveTrack( const SampleBlockFactoryPtr &pFactory,
sampleFormat format, double rate )
: PlayableTrack()
Unitary changes (#599) * Define SampleBlockFactory replacing static members of SampleBlock... ... This will become an abstract base class * Sequence and WaveTrack only store SampleBlockFactory not Project... ... This adds a dependency from Track to SampleBlock which temporarily enlarges a cycle in the dependency graph * Register a global factory of SampleBlockFactory... ... so that later we can make an abstract SampleBlockFactory, separate from the concrete implementation in terms of sqlite, and inject the dependency at startup avoiding static dependency * New concrete classes SqliteSampleBlock, SqliteSampleBlockFactory... ... separated from abstract base classes and put into a new source file, breaking dependency cycles, and perhaps allowing easy reimplementation for other databases in the future. Note that the new file is a header-less plug-in! Nothing depends on it. It uses static initialization to influence the program's behavior. * Compile dependency on sqlite3.h limited to just two .cpp files... ... these are ProjectFileIO.cpp and SqliteSampleBlock.cpp. But there is still close cooperation of ProjectFileIO and SqliteSampleBlock.cpp. This suggests that these files ought to be merged, and perhaps ProjectFileIO also needs to be split into abstract and concrete classes, and there should be another injection of a factory function at startup. That will make the choice of database implementation even more modular. Also removed one unnecessary inclusion of ProjectFileIO.h * Fix crashes cutting and pasting cross-project... ... in case the source project is closed before the paste happens. This caused destruction of the ProjectFileIO object and a closing of the sqlite database with the sample data in it, leaving dangling references in the SqliteSampleBlock objects. The fix is that the SqliteSampleBlockFactory object holds a shared_ptr to the ProjectFileIO object. So the clipboard may own WaveTracks, which own WaveClips, which own Sequences, which own SqliteSampleBlockFactories, which keep the ProjectFileIO and the database connection alive until the clipboard is cleared. The consequence of the fix is delayed closing of the entire database associated with the source project. If the source project is reopened before the clipboard is cleared, will there be correct concurrent access to the same persistent store? My preliminary trials suggest this is so (reopening a saved project, deleting from it, closing it again -- the clipboard contents are still unchanged and available).
2020-07-02 23:11:38 +00:00
, mpFactory(pFactory)
{
mLegacyProjectFileOffset = 0;
mFormat = format;
mRate = (int) rate;
mGain = 1.0;
mPan = 0.0;
mOldGain[0] = 0.0;
mOldGain[1] = 0.0;
mWaveColorIndex = 0;
SetDefaultName(TracksPrefs::GetDefaultAudioTrackNamePreference());
SetName(GetDefaultName());
mDisplayMin = -1.0;
mDisplayMax = 1.0;
mSpectrumMin = mSpectrumMax = -1; // so values will default to settings
mLastScaleType = -1;
mLastdBRange = -1;
}
WaveTrack::WaveTrack(const WaveTrack &orig):
PlayableTrack(orig)
Unitary changes (#599) * Define SampleBlockFactory replacing static members of SampleBlock... ... This will become an abstract base class * Sequence and WaveTrack only store SampleBlockFactory not Project... ... This adds a dependency from Track to SampleBlock which temporarily enlarges a cycle in the dependency graph * Register a global factory of SampleBlockFactory... ... so that later we can make an abstract SampleBlockFactory, separate from the concrete implementation in terms of sqlite, and inject the dependency at startup avoiding static dependency * New concrete classes SqliteSampleBlock, SqliteSampleBlockFactory... ... separated from abstract base classes and put into a new source file, breaking dependency cycles, and perhaps allowing easy reimplementation for other databases in the future. Note that the new file is a header-less plug-in! Nothing depends on it. It uses static initialization to influence the program's behavior. * Compile dependency on sqlite3.h limited to just two .cpp files... ... these are ProjectFileIO.cpp and SqliteSampleBlock.cpp. But there is still close cooperation of ProjectFileIO and SqliteSampleBlock.cpp. This suggests that these files ought to be merged, and perhaps ProjectFileIO also needs to be split into abstract and concrete classes, and there should be another injection of a factory function at startup. That will make the choice of database implementation even more modular. Also removed one unnecessary inclusion of ProjectFileIO.h * Fix crashes cutting and pasting cross-project... ... in case the source project is closed before the paste happens. This caused destruction of the ProjectFileIO object and a closing of the sqlite database with the sample data in it, leaving dangling references in the SqliteSampleBlock objects. The fix is that the SqliteSampleBlockFactory object holds a shared_ptr to the ProjectFileIO object. So the clipboard may own WaveTracks, which own WaveClips, which own Sequences, which own SqliteSampleBlockFactories, which keep the ProjectFileIO and the database connection alive until the clipboard is cleared. The consequence of the fix is delayed closing of the entire database associated with the source project. If the source project is reopened before the clipboard is cleared, will there be correct concurrent access to the same persistent store? My preliminary trials suggest this is so (reopening a saved project, deleting from it, closing it again -- the clipboard contents are still unchanged and available).
2020-07-02 23:11:38 +00:00
, mpFactory( orig.mpFactory )
, mpSpectrumSettings(orig.mpSpectrumSettings
2016-08-10 05:34:44 +00:00
? std::make_unique<SpectrogramSettings>(*orig.mpSpectrumSettings)
: nullptr
)
, mpWaveformSettings(orig.mpWaveformSettings
2016-08-10 05:34:44 +00:00
? std::make_unique<WaveformSettings>(*orig.mpWaveformSettings)
: nullptr
)
{
mLastScaleType = -1;
mLastdBRange = -1;
mLegacyProjectFileOffset = 0;
Init(orig);
for (const auto &clip : orig.mClips)
2016-11-26 20:20:28 +00:00
mClips.push_back
Unitary changes (#599) * Define SampleBlockFactory replacing static members of SampleBlock... ... This will become an abstract base class * Sequence and WaveTrack only store SampleBlockFactory not Project... ... This adds a dependency from Track to SampleBlock which temporarily enlarges a cycle in the dependency graph * Register a global factory of SampleBlockFactory... ... so that later we can make an abstract SampleBlockFactory, separate from the concrete implementation in terms of sqlite, and inject the dependency at startup avoiding static dependency * New concrete classes SqliteSampleBlock, SqliteSampleBlockFactory... ... separated from abstract base classes and put into a new source file, breaking dependency cycles, and perhaps allowing easy reimplementation for other databases in the future. Note that the new file is a header-less plug-in! Nothing depends on it. It uses static initialization to influence the program's behavior. * Compile dependency on sqlite3.h limited to just two .cpp files... ... these are ProjectFileIO.cpp and SqliteSampleBlock.cpp. But there is still close cooperation of ProjectFileIO and SqliteSampleBlock.cpp. This suggests that these files ought to be merged, and perhaps ProjectFileIO also needs to be split into abstract and concrete classes, and there should be another injection of a factory function at startup. That will make the choice of database implementation even more modular. Also removed one unnecessary inclusion of ProjectFileIO.h * Fix crashes cutting and pasting cross-project... ... in case the source project is closed before the paste happens. This caused destruction of the ProjectFileIO object and a closing of the sqlite database with the sample data in it, leaving dangling references in the SqliteSampleBlock objects. The fix is that the SqliteSampleBlockFactory object holds a shared_ptr to the ProjectFileIO object. So the clipboard may own WaveTracks, which own WaveClips, which own Sequences, which own SqliteSampleBlockFactories, which keep the ProjectFileIO and the database connection alive until the clipboard is cleared. The consequence of the fix is delayed closing of the entire database associated with the source project. If the source project is reopened before the clipboard is cleared, will there be correct concurrent access to the same persistent store? My preliminary trials suggest this is so (reopening a saved project, deleting from it, closing it again -- the clipboard contents are still unchanged and available).
2020-07-02 23:11:38 +00:00
( std::make_unique<WaveClip>( *clip, mpFactory, true ) );
}
// Copy the track metadata but not the contents.
void WaveTrack::Init(const WaveTrack &orig)
{
PlayableTrack::Init(orig);
Unitary changes (#599) * Define SampleBlockFactory replacing static members of SampleBlock... ... This will become an abstract base class * Sequence and WaveTrack only store SampleBlockFactory not Project... ... This adds a dependency from Track to SampleBlock which temporarily enlarges a cycle in the dependency graph * Register a global factory of SampleBlockFactory... ... so that later we can make an abstract SampleBlockFactory, separate from the concrete implementation in terms of sqlite, and inject the dependency at startup avoiding static dependency * New concrete classes SqliteSampleBlock, SqliteSampleBlockFactory... ... separated from abstract base classes and put into a new source file, breaking dependency cycles, and perhaps allowing easy reimplementation for other databases in the future. Note that the new file is a header-less plug-in! Nothing depends on it. It uses static initialization to influence the program's behavior. * Compile dependency on sqlite3.h limited to just two .cpp files... ... these are ProjectFileIO.cpp and SqliteSampleBlock.cpp. But there is still close cooperation of ProjectFileIO and SqliteSampleBlock.cpp. This suggests that these files ought to be merged, and perhaps ProjectFileIO also needs to be split into abstract and concrete classes, and there should be another injection of a factory function at startup. That will make the choice of database implementation even more modular. Also removed one unnecessary inclusion of ProjectFileIO.h * Fix crashes cutting and pasting cross-project... ... in case the source project is closed before the paste happens. This caused destruction of the ProjectFileIO object and a closing of the sqlite database with the sample data in it, leaving dangling references in the SqliteSampleBlock objects. The fix is that the SqliteSampleBlockFactory object holds a shared_ptr to the ProjectFileIO object. So the clipboard may own WaveTracks, which own WaveClips, which own Sequences, which own SqliteSampleBlockFactories, which keep the ProjectFileIO and the database connection alive until the clipboard is cleared. The consequence of the fix is delayed closing of the entire database associated with the source project. If the source project is reopened before the clipboard is cleared, will there be correct concurrent access to the same persistent store? My preliminary trials suggest this is so (reopening a saved project, deleting from it, closing it again -- the clipboard contents are still unchanged and available).
2020-07-02 23:11:38 +00:00
mpFactory = orig.mpFactory;
mFormat = orig.mFormat;
mWaveColorIndex = orig.mWaveColorIndex;
mRate = orig.mRate;
mGain = orig.mGain;
mPan = orig.mPan;
mOldGain[0] = 0.0;
mOldGain[1] = 0.0;
SetDefaultName(orig.GetDefaultName());
SetName(orig.GetName());
mDisplayMin = orig.mDisplayMin;
mDisplayMax = orig.mDisplayMax;
mSpectrumMin = orig.mSpectrumMin;
mSpectrumMax = orig.mSpectrumMax;
mDisplayLocationsCache.clear();
}
2018-01-10 18:20:31 +00:00
void WaveTrack::Reinit(const WaveTrack &orig)
{
Init(orig);
{
auto &settings = orig.mpSpectrumSettings;
if (settings)
mpSpectrumSettings = std::make_unique<SpectrogramSettings>(*settings);
else
mpSpectrumSettings.reset();
}
{
auto &settings = orig.mpWaveformSettings;
if (settings)
mpWaveformSettings = std::make_unique<WaveformSettings>(*settings);
else
mpWaveformSettings.reset();
}
this->SetOffset(orig.GetOffset());
}
void WaveTrack::Merge(const Track &orig)
{
2017-04-11 22:16:03 +00:00
orig.TypeSwitch( [&](const WaveTrack *pwt) {
const WaveTrack &wt = *pwt;
mGain = wt.mGain;
mPan = wt.mPan;
mDisplayMin = wt.mDisplayMin;
mDisplayMax = wt.mDisplayMax;
SetSpectrogramSettings(wt.mpSpectrumSettings
? std::make_unique<SpectrogramSettings>(*wt.mpSpectrumSettings) : nullptr);
SetWaveformSettings
(wt.mpWaveformSettings ? std::make_unique<WaveformSettings>(*wt.mpWaveformSettings) : nullptr);
2017-04-11 22:16:03 +00:00
});
PlayableTrack::Merge(orig);
}
WaveTrack::~WaveTrack()
2014-06-03 20:30:19 +00:00
{
}
double WaveTrack::GetOffset() const
{
return GetStartTime();
}
/*! @excsafety{No-fail} */
void WaveTrack::SetOffset(double o)
{
double delta = o - GetOffset();
for (const auto &clip : mClips)
// assume No-fail-guarantee
clip->SetOffset(clip->GetOffset() + delta);
mOffset = o;
}
2018-09-20 16:39:05 +00:00
auto WaveTrack::GetChannelIgnoringPan() const -> ChannelType {
return mChannel;
}
2018-09-20 16:39:05 +00:00
auto WaveTrack::GetChannel() const -> ChannelType
2017-04-02 22:07:13 +00:00
{
if( mChannel != Track::MonoChannel )
return mChannel;
auto pan = GetPan();
if( pan < -0.99 )
return Track::LeftChannel;
if( pan > 0.99 )
return Track::RightChannel;
return mChannel;
}
void WaveTrack::SetPanFromChannelType()
{
if( mChannel == Track::LeftChannel )
SetPan( -1.0f );
else if( mChannel == Track::RightChannel )
SetPan( 1.0f );
};
void WaveTrack::SetLastScaleType() const
{
mLastScaleType = GetWaveformSettings().scaleType;
}
void WaveTrack::SetLastdBRange() const
{
mLastdBRange = GetWaveformSettings().dBRange;
}
void WaveTrack::GetDisplayBounds(float *min, float *max) const
{
*min = mDisplayMin;
*max = mDisplayMax;
}
void WaveTrack::SetDisplayBounds(float min, float max) const
{
mDisplayMin = min;
mDisplayMax = max;
}
void WaveTrack::GetSpectrumBounds(float *min, float *max) const
{
const double rate = GetRate();
const SpectrogramSettings &settings = GetSpectrogramSettings();
const SpectrogramSettings::ScaleType type = settings.scaleType;
const float top = (rate / 2.);
float bottom;
if (type == SpectrogramSettings::stLinear)
bottom = 0.0f;
else if (type == SpectrogramSettings::stPeriod) {
// special case
const auto half = settings.GetFFTLength() / 2;
// EAC returns no data for below this frequency:
const float bin2 = rate / half;
bottom = bin2;
}
else
// logarithmic, etc.
bottom = 1.0f;
{
float spectrumMax = mSpectrumMax;
if (spectrumMax < 0)
spectrumMax = settings.maxFreq;
if (spectrumMax < 0)
*max = top;
else
*max = std::max(bottom, std::min(top, spectrumMax));
}
{
float spectrumMin = mSpectrumMin;
if (spectrumMin < 0)
spectrumMin = settings.minFreq;
if (spectrumMin < 0)
*min = std::max(bottom, top / 1000.0f);
else
*min = std::max(bottom, std::min(top, spectrumMin));
}
}
void WaveTrack::SetSpectrumBounds(float min, float max) const
{
mSpectrumMin = min;
mSpectrumMax = max;
}
int WaveTrack::ZeroLevelYCoordinate(wxRect rect) const
{
return rect.GetTop() +
(int)((mDisplayMax / (mDisplayMax - mDisplayMin)) * rect.height);
}
template< typename Container >
static Container MakeIntervals(const std::vector<WaveClipHolder> &clips)
{
Container result;
for (const auto &clip: clips) {
result.emplace_back( clip->GetStartTime(), clip->GetEndTime(),
std::make_unique<WaveTrack::IntervalData>( clip ) );
}
return result;
}
Track::Holder WaveTrack::PasteInto( AudacityProject &project ) const
{
auto &trackFactory = WaveTrackFactory::Get( project );
auto &pSampleBlockFactory = trackFactory.GetSampleBlockFactory();
auto pNewTrack = EmptyCopy( pSampleBlockFactory );
pNewTrack->Paste(0.0, this);
return pNewTrack;
}
auto WaveTrack::GetIntervals() const -> ConstIntervals
{
return MakeIntervals<ConstIntervals>( mClips );
}
auto WaveTrack::GetIntervals() -> Intervals
{
return MakeIntervals<Intervals>( mClips );
}
Track::Holder WaveTrack::Clone() const
{
return std::make_shared<WaveTrack>( *this );
}
double WaveTrack::GetRate() const
{
return mRate;
}
void WaveTrack::SetRate(double newRate)
{
wxASSERT( newRate > 0 );
newRate = std::max( 1.0, newRate );
auto ratio = mRate / newRate;
mRate = (int) newRate;
for (const auto &clip : mClips) {
clip->SetRate((int)newRate);
clip->SetOffset( clip->GetOffset() * ratio );
}
}
float WaveTrack::GetGain() const
{
return mGain;
}
void WaveTrack::SetGain(float newGain)
{
if (mGain != newGain) {
mGain = newGain;
Notify();
}
}
float WaveTrack::GetPan() const
{
return mPan;
}
void WaveTrack::SetPan(float newPan)
{
if (newPan > 1.0)
newPan = 1.0;
else if (newPan < -1.0)
newPan = -1.0;
if ( mPan != newPan ) {
mPan = newPan;
Notify();
}
}
float WaveTrack::GetChannelGain(int channel) const
{
float left = 1.0;
float right = 1.0;
if (mPan < 0)
right = (mPan + 1.0);
else if (mPan > 0)
left = 1.0 - mPan;
if ((channel%2) == 0)
return left*mGain;
else
return right*mGain;
}
float WaveTrack::GetOldChannelGain(int channel) const
{
return mOldGain[channel%2];
}
void WaveTrack::SetOldChannelGain(int channel, float gain)
{
mOldGain[channel % 2] = gain;
}
/*! @excsafety{Strong} */
void WaveTrack::SetWaveColorIndex(int colorIndex)
{
for (const auto &clip : mClips)
clip->SetColourIndex( colorIndex );
mWaveColorIndex = colorIndex;
}
2020-08-28 19:07:04 +00:00
sampleCount WaveTrack::GetNumSamples() const
{
sampleCount result{ 0 };
for (const auto& clip : mClips)
result += clip->GetNumSamples();
return result;
}
/*! @excsafety{Weak} -- Might complete on only some clips */
2020-08-28 19:07:04 +00:00
void WaveTrack::ConvertToSampleFormat(sampleFormat format,
const std::function<void(size_t)> & progressReport)
{
2020-08-28 19:07:04 +00:00
for (const auto& clip : mClips)
clip->ConvertToSampleFormat(format, progressReport);
mFormat = format;
}
2020-08-28 19:07:04 +00:00
2017-02-22 19:23:35 +00:00
bool WaveTrack::IsEmpty(double t0, double t1) const
{
if (t0 > t1)
return true;
2017-10-09 04:37:10 +00:00
//wxPrintf("Searching for overlap in %.6f...%.6f\n", t0, t1);
for (const auto &clip : mClips)
{
if (!clip->BeforeClip(t1) && !clip->AfterClip(t0)) {
2017-10-09 04:37:10 +00:00
//wxPrintf("Overlapping clip: %.6f...%.6f\n",
// clip->GetStartTime(),
// clip->GetEndTime());
// We found a clip that overlaps this region
return false;
}
}
2017-10-09 04:37:10 +00:00
//wxPrintf("No overlap found\n");
// Otherwise, no clips overlap this region
return true;
}
Track::Holder WaveTrack::Cut(double t0, double t1)
{
if (t1 < t0)
THROW_INCONSISTENCY_EXCEPTION;
auto tmp = Copy(t0, t1);
Clear(t0, t1);
return tmp;
}
/*! @excsafety{Strong} */
Track::Holder WaveTrack::SplitCut(double t0, double t1)
{
if (t1 < t0)
THROW_INCONSISTENCY_EXCEPTION;
// SplitCut is the same as 'Copy', then 'SplitDelete'
auto tmp = Copy(t0, t1);
SplitDelete(t0, t1);
return tmp;
}
2016-02-26 15:39:39 +00:00
#if 0
Track::Holder WaveTrack::CutAndAddCutLine(double t0, double t1)
{
if (t1 < t0)
THROW_INCONSISTENCY_EXCEPTION;
// Cut is the same as 'Copy', then 'Delete'
auto tmp = Copy(t0, t1);
ClearAndAddCutLine(t0, t1);
return tmp;
}
2016-02-26 15:39:39 +00:00
#endif
//Trim trims within a clip, rather than trimming everything.
//If a bound is outside a clip, it trims everything.
/*! @excsafety{Weak} */
void WaveTrack::Trim (double t0, double t1)
{
bool inside0 = false;
bool inside1 = false;
//Keeps track of the offset of the first clip greater than
// the left selection t0.
double firstGreaterOffset = -1;
for (const auto &clip : mClips)
{
//Find the first clip greater than the offset.
//If we end up clipping the entire track, this is useful.
2014-06-03 20:30:19 +00:00
if(firstGreaterOffset < 0 &&
clip->GetStartTime() >= t0)
firstGreaterOffset = clip->GetStartTime();
if(t1 > clip->GetStartTime() && t1 < clip->GetEndTime())
{
clip->Clear(t1,clip->GetEndTime());
inside1 = true;
}
if(t0 > clip->GetStartTime() && t0 < clip->GetEndTime())
{
clip->Clear(clip->GetStartTime(),t0);
clip->SetOffset(t0);
inside0 = true;
}
}
//if inside0 is false, then the left selector was between
//clips, so DELETE everything to its left.
if(!inside1 && t1 < GetEndTime())
Clear(t1,GetEndTime());
if(!inside0 && t0 > GetStartTime())
SplitDelete(GetStartTime(), t0);
}
WaveTrack::Holder WaveTrack::EmptyCopy(
Unitary changes (#599) * Define SampleBlockFactory replacing static members of SampleBlock... ... This will become an abstract base class * Sequence and WaveTrack only store SampleBlockFactory not Project... ... This adds a dependency from Track to SampleBlock which temporarily enlarges a cycle in the dependency graph * Register a global factory of SampleBlockFactory... ... so that later we can make an abstract SampleBlockFactory, separate from the concrete implementation in terms of sqlite, and inject the dependency at startup avoiding static dependency * New concrete classes SqliteSampleBlock, SqliteSampleBlockFactory... ... separated from abstract base classes and put into a new source file, breaking dependency cycles, and perhaps allowing easy reimplementation for other databases in the future. Note that the new file is a header-less plug-in! Nothing depends on it. It uses static initialization to influence the program's behavior. * Compile dependency on sqlite3.h limited to just two .cpp files... ... these are ProjectFileIO.cpp and SqliteSampleBlock.cpp. But there is still close cooperation of ProjectFileIO and SqliteSampleBlock.cpp. This suggests that these files ought to be merged, and perhaps ProjectFileIO also needs to be split into abstract and concrete classes, and there should be another injection of a factory function at startup. That will make the choice of database implementation even more modular. Also removed one unnecessary inclusion of ProjectFileIO.h * Fix crashes cutting and pasting cross-project... ... in case the source project is closed before the paste happens. This caused destruction of the ProjectFileIO object and a closing of the sqlite database with the sample data in it, leaving dangling references in the SqliteSampleBlock objects. The fix is that the SqliteSampleBlockFactory object holds a shared_ptr to the ProjectFileIO object. So the clipboard may own WaveTracks, which own WaveClips, which own Sequences, which own SqliteSampleBlockFactories, which keep the ProjectFileIO and the database connection alive until the clipboard is cleared. The consequence of the fix is delayed closing of the entire database associated with the source project. If the source project is reopened before the clipboard is cleared, will there be correct concurrent access to the same persistent store? My preliminary trials suggest this is so (reopening a saved project, deleting from it, closing it again -- the clipboard contents are still unchanged and available).
2020-07-02 23:11:38 +00:00
const SampleBlockFactoryPtr &pFactory ) const
{
Unitary changes (#599) * Define SampleBlockFactory replacing static members of SampleBlock... ... This will become an abstract base class * Sequence and WaveTrack only store SampleBlockFactory not Project... ... This adds a dependency from Track to SampleBlock which temporarily enlarges a cycle in the dependency graph * Register a global factory of SampleBlockFactory... ... so that later we can make an abstract SampleBlockFactory, separate from the concrete implementation in terms of sqlite, and inject the dependency at startup avoiding static dependency * New concrete classes SqliteSampleBlock, SqliteSampleBlockFactory... ... separated from abstract base classes and put into a new source file, breaking dependency cycles, and perhaps allowing easy reimplementation for other databases in the future. Note that the new file is a header-less plug-in! Nothing depends on it. It uses static initialization to influence the program's behavior. * Compile dependency on sqlite3.h limited to just two .cpp files... ... these are ProjectFileIO.cpp and SqliteSampleBlock.cpp. But there is still close cooperation of ProjectFileIO and SqliteSampleBlock.cpp. This suggests that these files ought to be merged, and perhaps ProjectFileIO also needs to be split into abstract and concrete classes, and there should be another injection of a factory function at startup. That will make the choice of database implementation even more modular. Also removed one unnecessary inclusion of ProjectFileIO.h * Fix crashes cutting and pasting cross-project... ... in case the source project is closed before the paste happens. This caused destruction of the ProjectFileIO object and a closing of the sqlite database with the sample data in it, leaving dangling references in the SqliteSampleBlock objects. The fix is that the SqliteSampleBlockFactory object holds a shared_ptr to the ProjectFileIO object. So the clipboard may own WaveTracks, which own WaveClips, which own Sequences, which own SqliteSampleBlockFactories, which keep the ProjectFileIO and the database connection alive until the clipboard is cleared. The consequence of the fix is delayed closing of the entire database associated with the source project. If the source project is reopened before the clipboard is cleared, will there be correct concurrent access to the same persistent store? My preliminary trials suggest this is so (reopening a saved project, deleting from it, closing it again -- the clipboard contents are still unchanged and available).
2020-07-02 23:11:38 +00:00
auto result = std::make_shared<WaveTrack>( pFactory, mFormat, mRate );
result->Init(*this);
Unitary changes (#599) * Define SampleBlockFactory replacing static members of SampleBlock... ... This will become an abstract base class * Sequence and WaveTrack only store SampleBlockFactory not Project... ... This adds a dependency from Track to SampleBlock which temporarily enlarges a cycle in the dependency graph * Register a global factory of SampleBlockFactory... ... so that later we can make an abstract SampleBlockFactory, separate from the concrete implementation in terms of sqlite, and inject the dependency at startup avoiding static dependency * New concrete classes SqliteSampleBlock, SqliteSampleBlockFactory... ... separated from abstract base classes and put into a new source file, breaking dependency cycles, and perhaps allowing easy reimplementation for other databases in the future. Note that the new file is a header-less plug-in! Nothing depends on it. It uses static initialization to influence the program's behavior. * Compile dependency on sqlite3.h limited to just two .cpp files... ... these are ProjectFileIO.cpp and SqliteSampleBlock.cpp. But there is still close cooperation of ProjectFileIO and SqliteSampleBlock.cpp. This suggests that these files ought to be merged, and perhaps ProjectFileIO also needs to be split into abstract and concrete classes, and there should be another injection of a factory function at startup. That will make the choice of database implementation even more modular. Also removed one unnecessary inclusion of ProjectFileIO.h * Fix crashes cutting and pasting cross-project... ... in case the source project is closed before the paste happens. This caused destruction of the ProjectFileIO object and a closing of the sqlite database with the sample data in it, leaving dangling references in the SqliteSampleBlock objects. The fix is that the SqliteSampleBlockFactory object holds a shared_ptr to the ProjectFileIO object. So the clipboard may own WaveTracks, which own WaveClips, which own Sequences, which own SqliteSampleBlockFactories, which keep the ProjectFileIO and the database connection alive until the clipboard is cleared. The consequence of the fix is delayed closing of the entire database associated with the source project. If the source project is reopened before the clipboard is cleared, will there be correct concurrent access to the same persistent store? My preliminary trials suggest this is so (reopening a saved project, deleting from it, closing it again -- the clipboard contents are still unchanged and available).
2020-07-02 23:11:38 +00:00
result->mpFactory = pFactory ? pFactory : mpFactory;
return result;
}
Track::Holder WaveTrack::Copy(double t0, double t1, bool forClipboard) const
{
if (t1 < t0)
THROW_INCONSISTENCY_EXCEPTION;
auto result = EmptyCopy();
WaveTrack *newTrack = result.get();
2016-11-26 20:20:28 +00:00
// PRL: Why shouldn't cutlines be copied and pasted too? I don't know, but
// that was the old behavior. But this function is also used by the
// Duplicate command and I changed its behavior in that case.
for (const auto &clip : mClips)
{
if (t0 <= clip->GetStartTime() && t1 >= clip->GetEndTime())
{
// Whole clip is in copy region
2017-10-09 04:37:10 +00:00
//wxPrintf("copy: clip %i is in copy region\n", (int)clip);
2014-06-03 20:30:19 +00:00
newTrack->mClips.push_back
Unitary changes (#599) * Define SampleBlockFactory replacing static members of SampleBlock... ... This will become an abstract base class * Sequence and WaveTrack only store SampleBlockFactory not Project... ... This adds a dependency from Track to SampleBlock which temporarily enlarges a cycle in the dependency graph * Register a global factory of SampleBlockFactory... ... so that later we can make an abstract SampleBlockFactory, separate from the concrete implementation in terms of sqlite, and inject the dependency at startup avoiding static dependency * New concrete classes SqliteSampleBlock, SqliteSampleBlockFactory... ... separated from abstract base classes and put into a new source file, breaking dependency cycles, and perhaps allowing easy reimplementation for other databases in the future. Note that the new file is a header-less plug-in! Nothing depends on it. It uses static initialization to influence the program's behavior. * Compile dependency on sqlite3.h limited to just two .cpp files... ... these are ProjectFileIO.cpp and SqliteSampleBlock.cpp. But there is still close cooperation of ProjectFileIO and SqliteSampleBlock.cpp. This suggests that these files ought to be merged, and perhaps ProjectFileIO also needs to be split into abstract and concrete classes, and there should be another injection of a factory function at startup. That will make the choice of database implementation even more modular. Also removed one unnecessary inclusion of ProjectFileIO.h * Fix crashes cutting and pasting cross-project... ... in case the source project is closed before the paste happens. This caused destruction of the ProjectFileIO object and a closing of the sqlite database with the sample data in it, leaving dangling references in the SqliteSampleBlock objects. The fix is that the SqliteSampleBlockFactory object holds a shared_ptr to the ProjectFileIO object. So the clipboard may own WaveTracks, which own WaveClips, which own Sequences, which own SqliteSampleBlockFactories, which keep the ProjectFileIO and the database connection alive until the clipboard is cleared. The consequence of the fix is delayed closing of the entire database associated with the source project. If the source project is reopened before the clipboard is cleared, will there be correct concurrent access to the same persistent store? My preliminary trials suggest this is so (reopening a saved project, deleting from it, closing it again -- the clipboard contents are still unchanged and available).
2020-07-02 23:11:38 +00:00
(std::make_unique<WaveClip>(*clip, mpFactory, ! forClipboard));
WaveClip *const newClip = newTrack->mClips.back().get();
newClip->Offset(-t0);
}
else if (t1 > clip->GetStartTime() && t0 < clip->GetEndTime())
{
// Clip is affected by command
2017-10-09 04:37:10 +00:00
//wxPrintf("copy: clip %i is affected by command\n", (int)clip);
2014-06-03 20:30:19 +00:00
const double clip_t0 = std::max(t0, clip->GetStartTime());
const double clip_t1 = std::min(t1, clip->GetEndTime());
2018-04-16 17:31:17 +00:00
auto newClip = std::make_unique<WaveClip>
Unitary changes (#599) * Define SampleBlockFactory replacing static members of SampleBlock... ... This will become an abstract base class * Sequence and WaveTrack only store SampleBlockFactory not Project... ... This adds a dependency from Track to SampleBlock which temporarily enlarges a cycle in the dependency graph * Register a global factory of SampleBlockFactory... ... so that later we can make an abstract SampleBlockFactory, separate from the concrete implementation in terms of sqlite, and inject the dependency at startup avoiding static dependency * New concrete classes SqliteSampleBlock, SqliteSampleBlockFactory... ... separated from abstract base classes and put into a new source file, breaking dependency cycles, and perhaps allowing easy reimplementation for other databases in the future. Note that the new file is a header-less plug-in! Nothing depends on it. It uses static initialization to influence the program's behavior. * Compile dependency on sqlite3.h limited to just two .cpp files... ... these are ProjectFileIO.cpp and SqliteSampleBlock.cpp. But there is still close cooperation of ProjectFileIO and SqliteSampleBlock.cpp. This suggests that these files ought to be merged, and perhaps ProjectFileIO also needs to be split into abstract and concrete classes, and there should be another injection of a factory function at startup. That will make the choice of database implementation even more modular. Also removed one unnecessary inclusion of ProjectFileIO.h * Fix crashes cutting and pasting cross-project... ... in case the source project is closed before the paste happens. This caused destruction of the ProjectFileIO object and a closing of the sqlite database with the sample data in it, leaving dangling references in the SqliteSampleBlock objects. The fix is that the SqliteSampleBlockFactory object holds a shared_ptr to the ProjectFileIO object. So the clipboard may own WaveTracks, which own WaveClips, which own Sequences, which own SqliteSampleBlockFactories, which keep the ProjectFileIO and the database connection alive until the clipboard is cleared. The consequence of the fix is delayed closing of the entire database associated with the source project. If the source project is reopened before the clipboard is cleared, will there be correct concurrent access to the same persistent store? My preliminary trials suggest this is so (reopening a saved project, deleting from it, closing it again -- the clipboard contents are still unchanged and available).
2020-07-02 23:11:38 +00:00
(*clip, mpFactory, ! forClipboard, clip_t0, clip_t1);
2017-10-09 04:37:10 +00:00
//wxPrintf("copy: clip_t0=%f, clip_t1=%f\n", clip_t0, clip_t1);
newClip->Offset(-t0);
if (newClip->GetOffset() < 0)
newClip->SetOffset(0);
newTrack->mClips.push_back(std::move(newClip)); // transfer ownership
}
}
2014-06-03 20:30:19 +00:00
// AWD, Oct 2009: If the selection ends in whitespace, create a placeholder
// clip representing that whitespace
// PRL: Only if we want the track for pasting into other tracks. Not if it
// goes directly into a project as in the Duplicate command.
if (forClipboard &&
newTrack->GetEndTime() + 1.0 / newTrack->GetRate() < t1 - t0)
{
Unitary changes (#599) * Define SampleBlockFactory replacing static members of SampleBlock... ... This will become an abstract base class * Sequence and WaveTrack only store SampleBlockFactory not Project... ... This adds a dependency from Track to SampleBlock which temporarily enlarges a cycle in the dependency graph * Register a global factory of SampleBlockFactory... ... so that later we can make an abstract SampleBlockFactory, separate from the concrete implementation in terms of sqlite, and inject the dependency at startup avoiding static dependency * New concrete classes SqliteSampleBlock, SqliteSampleBlockFactory... ... separated from abstract base classes and put into a new source file, breaking dependency cycles, and perhaps allowing easy reimplementation for other databases in the future. Note that the new file is a header-less plug-in! Nothing depends on it. It uses static initialization to influence the program's behavior. * Compile dependency on sqlite3.h limited to just two .cpp files... ... these are ProjectFileIO.cpp and SqliteSampleBlock.cpp. But there is still close cooperation of ProjectFileIO and SqliteSampleBlock.cpp. This suggests that these files ought to be merged, and perhaps ProjectFileIO also needs to be split into abstract and concrete classes, and there should be another injection of a factory function at startup. That will make the choice of database implementation even more modular. Also removed one unnecessary inclusion of ProjectFileIO.h * Fix crashes cutting and pasting cross-project... ... in case the source project is closed before the paste happens. This caused destruction of the ProjectFileIO object and a closing of the sqlite database with the sample data in it, leaving dangling references in the SqliteSampleBlock objects. The fix is that the SqliteSampleBlockFactory object holds a shared_ptr to the ProjectFileIO object. So the clipboard may own WaveTracks, which own WaveClips, which own Sequences, which own SqliteSampleBlockFactories, which keep the ProjectFileIO and the database connection alive until the clipboard is cleared. The consequence of the fix is delayed closing of the entire database associated with the source project. If the source project is reopened before the clipboard is cleared, will there be correct concurrent access to the same persistent store? My preliminary trials suggest this is so (reopening a saved project, deleting from it, closing it again -- the clipboard contents are still unchanged and available).
2020-07-02 23:11:38 +00:00
auto placeholder = std::make_unique<WaveClip>(mpFactory,
newTrack->GetSampleFormat(),
static_cast<int>(newTrack->GetRate()),
0 /*colourindex*/);
placeholder->SetIsPlaceholder(true);
placeholder->InsertSilence(0, (t1 - t0) - newTrack->GetEndTime());
placeholder->Offset(newTrack->GetEndTime());
newTrack->mClips.push_back(std::move(placeholder)); // transfer ownership
}
return result;
}
Track::Holder WaveTrack::CopyNonconst(double t0, double t1)
{
return Copy(t0, t1);
}
/*! @excsafety{Strong} */
void WaveTrack::Clear(double t0, double t1)
{
HandleClear(t0, t1, false, false);
}
/*! @excsafety{Strong} */
void WaveTrack::ClearAndAddCutLine(double t0, double t1)
{
HandleClear(t0, t1, true, false);
}
const SpectrogramSettings &WaveTrack::GetSpectrogramSettings() const
{
if (mpSpectrumSettings)
return *mpSpectrumSettings;
else
return SpectrogramSettings::defaults();
}
SpectrogramSettings &WaveTrack::GetSpectrogramSettings()
{
if (mpSpectrumSettings)
return *mpSpectrumSettings;
else
return SpectrogramSettings::defaults();
}
SpectrogramSettings &WaveTrack::GetIndependentSpectrogramSettings()
{
if (!mpSpectrumSettings)
mpSpectrumSettings =
std::make_unique<SpectrogramSettings>(SpectrogramSettings::defaults());
return *mpSpectrumSettings;
}
void WaveTrack::SetSpectrogramSettings(std::unique_ptr<SpectrogramSettings> &&pSettings)
{
if (mpSpectrumSettings != pSettings) {
mpSpectrumSettings = std::move(pSettings);
}
}
void WaveTrack::UseSpectralPrefs( bool bUse )
{
if( bUse ){
if( !mpSpectrumSettings )
return;
// reset it, and next we will be getting the defaults.
mpSpectrumSettings.reset();
}
else {
if( mpSpectrumSettings )
return;
GetIndependentSpectrogramSettings();
}
}
const WaveformSettings &WaveTrack::GetWaveformSettings() const
{
// Create on demand
return const_cast<WaveTrack*>(this)->GetWaveformSettings();
}
WaveformSettings &WaveTrack::GetWaveformSettings()
{
// Create on demand
if (!mpWaveformSettings)
mpWaveformSettings = std::make_unique<WaveformSettings>(WaveformSettings::defaults());
return *mpWaveformSettings;
}
void WaveTrack::SetWaveformSettings(std::unique_ptr<WaveformSettings> &&pSettings)
{
if (mpWaveformSettings != pSettings) {
mpWaveformSettings = std::move(pSettings);
}
}
//
// ClearAndPaste() is a specialized version of HandleClear()
// followed by Paste() and is used mostly by effects that
// can't replace track data directly using Get()/Set().
//
2020-04-11 07:08:33 +00:00
// HandleClear() removes any cut/split lines with the
// cleared range, but, in most cases, effects want to preserve
// the existing cut/split lines, so they are saved before the
// HandleClear()/Paste() and restored after.
//
// If the pasted track overlaps two or more clips, then it will
// be pasted with visible split lines. Normally, effects do not
// want these extra lines, so they may be merged out.
//
/*! @excsafety{Weak} -- This WaveTrack remains destructible in case of AudacityException.
But some of its cutline clips may have been destroyed. */
void WaveTrack::ClearAndPaste(double t0, // Start of time to clear
double t1, // End of time to clear
const Track *src, // What to paste
bool preserve, // Whether to reinsert splits/cuts
bool merge, // Whether to remove 'extra' splits
const TimeWarper *effectWarper // How does time change
)
{
double dur = std::min(t1 - t0, src->GetEndTime());
// If duration is 0, then it's just a plain paste
if (dur == 0.0) {
// use Weak-guarantee
Paste(t0, src);
return;
}
std::vector<EnvPoint> envPoints;
std::vector<double> splits;
WaveClipHolders cuts;
// If provided time warper was NULL, use a default one that does nothing
IdentityTimeWarper localWarper;
const TimeWarper *warper = (effectWarper ? effectWarper : &localWarper);
// Align to a sample
t0 = LongSamplesToTime(TimeToLongSamples(t0));
t1 = LongSamplesToTime(TimeToLongSamples(t1));
// Save the cut/split lines whether preserving or not since merging
// needs to know if a clip boundary is being crossed since Paste()
// will add split lines around the pasted clip if so.
for (const auto &clip : mClips) {
double st;
// Remember clip boundaries as locations to split
st = LongSamplesToTime(TimeToLongSamples(clip->GetStartTime()));
if (st >= t0 && st <= t1 && !make_iterator_range(splits).contains(st)) {
splits.push_back(st);
}
st = LongSamplesToTime(TimeToLongSamples(clip->GetEndTime()));
if (st >= t0 && st <= t1 && !make_iterator_range(splits).contains(st)) {
splits.push_back(st);
}
// Search for cut lines
auto &cutlines = clip->GetCutLines();
// May erase from cutlines, so don't use range-for
for (auto it = cutlines.begin(); it != cutlines.end(); ) {
WaveClip *cut = it->get();
double cs = LongSamplesToTime(TimeToLongSamples(clip->GetOffset() +
cut->GetOffset()));
// Remember cut point
if (cs >= t0 && cs <= t1) {
// Remember the absolute offset and add to our cuts array.
cut->SetOffset(cs);
cuts.push_back(std::move(*it)); // transfer ownership!
it = cutlines.erase(it);
}
else
++it;
}
// Save the envelope points
const auto &env = *clip->GetEnvelope();
for (size_t i = 0, numPoints = env.GetNumberOfPoints(); i < numPoints; ++i) {
envPoints.push_back(env[i]);
}
}
const auto tolerance = 2.0 / GetRate();
// Now, clear the selection
HandleClear(t0, t1, false, false);
{
// And paste in the NEW data
Paste(t0, src);
{
// First, merge the NEW clip(s) in with the existing clips
if (merge && splits.size() > 0)
{
// Now t1 represents the absolute end of the pasted data.
t1 = t0 + src->GetEndTime();
// Get a sorted array of the clips
auto clips = SortedClipArray();
// Scan the sorted clips for the first clip whose start time
// exceeds the pasted regions end time.
{
WaveClip *prev = nullptr;
for (const auto clip : clips) {
// Merge this clip and the previous clip if the end time
// falls within it and this isn't the first clip in the track.
if (fabs(t1 - clip->GetStartTime()) < tolerance) {
if (prev)
MergeClips(GetClipIndex(prev), GetClipIndex(clip));
break;
}
prev = clip;
}
}
}
// Refill the array since clips have changed.
auto clips = SortedClipArray();
{
// Scan the sorted clips to look for the start of the pasted
// region.
WaveClip *prev = nullptr;
for (const auto clip : clips) {
if (prev) {
// It must be that clip is what was pasted and it begins where
// prev ends.
// use Weak-guarantee
MergeClips(GetClipIndex(prev), GetClipIndex(clip));
break;
}
if (fabs(t0 - clip->GetEndTime()) < tolerance)
// Merge this clip and the next clip if the start time
// falls within it and this isn't the last clip in the track.
prev = clip;
else
prev = nullptr;
}
}
}
// Restore cut/split lines
if (preserve) {
// Restore the split lines, transforming the position appropriately
for (const auto split: splits) {
SplitAt(warper->Warp(split));
}
// Restore the saved cut lines, also transforming if time altered
for (const auto &clip : mClips) {
double st;
double et;
st = clip->GetStartTime();
et = clip->GetEndTime();
// Scan the cuts for any that live within this clip
for (auto it = cuts.begin(); it != cuts.end();) {
WaveClip *cut = it->get();
double cs = cut->GetOffset();
// Offset the cut from the start of the clip and add it to
// this clips cutlines.
if (cs >= st && cs <= et) {
cut->SetOffset(warper->Warp(cs) - st);
clip->GetCutLines().push_back( std::move(*it) ); // transfer ownership!
it = cuts.erase(it);
}
else
++it;
}
}
}
// Restore the envelope points
for (auto point : envPoints) {
auto t = warper->Warp(point.GetT());
if (auto clip = GetClipAtTime(t))
clip->GetEnvelope()->Insert(t, point.GetVal());
}
}
}
/*! @excsafety{Strong} */
void WaveTrack::SplitDelete(double t0, double t1)
{
bool addCutLines = false;
bool split = true;
HandleClear(t0, t1, addCutLines, split);
}
namespace
{
WaveClipHolders::const_iterator
FindClip(const WaveClipHolders &list, const WaveClip *clip, int *distance = nullptr)
{
if (distance)
*distance = 0;
auto it = list.begin();
for (const auto end = list.end(); it != end; ++it)
{
if (it->get() == clip)
break;
if (distance)
++*distance;
}
return it;
}
WaveClipHolders::iterator
FindClip(WaveClipHolders &list, const WaveClip *clip, int *distance = nullptr)
{
if (distance)
*distance = 0;
auto it = list.begin();
for (const auto end = list.end(); it != end; ++it)
{
if (it->get() == clip)
break;
if (distance)
++*distance;
}
return it;
}
}
2017-07-09 18:09:33 +00:00
std::shared_ptr<WaveClip> WaveTrack::RemoveAndReturnClip(WaveClip* clip)
{
// Be clear about who owns the clip!!
auto it = FindClip(mClips, clip);
if (it != mClips.end()) {
auto result = std::move(*it); // Array stops owning the clip, before we shrink it
mClips.erase(it);
return result;
}
else
return {};
}
bool WaveTrack::AddClip(const std::shared_ptr<WaveClip> &clip)
{
if (clip->GetSequence()->GetFactory() != this->mpFactory)
return false;
// Uncomment the following line after we correct the problem of zero-length clips
//if (CanInsertClip(clip))
mClips.push_back(clip); // transfer ownership
return true;
}
/*! @excsafety{Strong} */
void WaveTrack::HandleClear(double t0, double t1,
bool addCutLines, bool split)
{
// For debugging, use an ASSERT so that we stop
// closer to the problem.
wxASSERT( t1 >= t0 );
if (t1 < t0)
THROW_INCONSISTENCY_EXCEPTION;
bool editClipCanMove = GetEditClipsCanMove();
WaveClipPointers clipsToDelete;
WaveClipHolders clipsToAdd;
2014-06-03 20:30:19 +00:00
// We only add cut lines when deleting in the middle of a single clip
// The cut line code is not really prepared to handle other situations
if (addCutLines)
{
for (const auto &clip : mClips)
{
if (!clip->BeforeClip(t1) && !clip->AfterClip(t0) &&
(clip->BeforeClip(t0) || clip->AfterClip(t1)))
{
addCutLines = false;
break;
}
}
2014-06-03 20:30:19 +00:00
}
for (const auto &clip : mClips)
{
if (clip->BeforeClip(t0) && clip->AfterClip(t1))
{
// Whole clip must be deleted - remember this
clipsToDelete.push_back(clip.get());
}
else if (!clip->BeforeClip(t1) && !clip->AfterClip(t0))
{
// Clip data is affected by command
if (addCutLines)
{
// Don't modify this clip in place, because we want a strong
// guarantee, and might modify another clip
clipsToDelete.push_back( clip.get() );
Unitary changes (#599) * Define SampleBlockFactory replacing static members of SampleBlock... ... This will become an abstract base class * Sequence and WaveTrack only store SampleBlockFactory not Project... ... This adds a dependency from Track to SampleBlock which temporarily enlarges a cycle in the dependency graph * Register a global factory of SampleBlockFactory... ... so that later we can make an abstract SampleBlockFactory, separate from the concrete implementation in terms of sqlite, and inject the dependency at startup avoiding static dependency * New concrete classes SqliteSampleBlock, SqliteSampleBlockFactory... ... separated from abstract base classes and put into a new source file, breaking dependency cycles, and perhaps allowing easy reimplementation for other databases in the future. Note that the new file is a header-less plug-in! Nothing depends on it. It uses static initialization to influence the program's behavior. * Compile dependency on sqlite3.h limited to just two .cpp files... ... these are ProjectFileIO.cpp and SqliteSampleBlock.cpp. But there is still close cooperation of ProjectFileIO and SqliteSampleBlock.cpp. This suggests that these files ought to be merged, and perhaps ProjectFileIO also needs to be split into abstract and concrete classes, and there should be another injection of a factory function at startup. That will make the choice of database implementation even more modular. Also removed one unnecessary inclusion of ProjectFileIO.h * Fix crashes cutting and pasting cross-project... ... in case the source project is closed before the paste happens. This caused destruction of the ProjectFileIO object and a closing of the sqlite database with the sample data in it, leaving dangling references in the SqliteSampleBlock objects. The fix is that the SqliteSampleBlockFactory object holds a shared_ptr to the ProjectFileIO object. So the clipboard may own WaveTracks, which own WaveClips, which own Sequences, which own SqliteSampleBlockFactories, which keep the ProjectFileIO and the database connection alive until the clipboard is cleared. The consequence of the fix is delayed closing of the entire database associated with the source project. If the source project is reopened before the clipboard is cleared, will there be correct concurrent access to the same persistent store? My preliminary trials suggest this is so (reopening a saved project, deleting from it, closing it again -- the clipboard contents are still unchanged and available).
2020-07-02 23:11:38 +00:00
auto newClip = std::make_unique<WaveClip>( *clip, mpFactory, true );
newClip->ClearAndAddCutLine( t0, t1 );
clipsToAdd.push_back( std::move( newClip ) );
2016-11-26 20:20:28 +00:00
}
else
{
if (split) {
// Three cases:
if (clip->BeforeClip(t0)) {
// Delete from the left edge
// Don't modify this clip in place, because we want a strong
// guarantee, and might modify another clip
clipsToDelete.push_back( clip.get() );
Unitary changes (#599) * Define SampleBlockFactory replacing static members of SampleBlock... ... This will become an abstract base class * Sequence and WaveTrack only store SampleBlockFactory not Project... ... This adds a dependency from Track to SampleBlock which temporarily enlarges a cycle in the dependency graph * Register a global factory of SampleBlockFactory... ... so that later we can make an abstract SampleBlockFactory, separate from the concrete implementation in terms of sqlite, and inject the dependency at startup avoiding static dependency * New concrete classes SqliteSampleBlock, SqliteSampleBlockFactory... ... separated from abstract base classes and put into a new source file, breaking dependency cycles, and perhaps allowing easy reimplementation for other databases in the future. Note that the new file is a header-less plug-in! Nothing depends on it. It uses static initialization to influence the program's behavior. * Compile dependency on sqlite3.h limited to just two .cpp files... ... these are ProjectFileIO.cpp and SqliteSampleBlock.cpp. But there is still close cooperation of ProjectFileIO and SqliteSampleBlock.cpp. This suggests that these files ought to be merged, and perhaps ProjectFileIO also needs to be split into abstract and concrete classes, and there should be another injection of a factory function at startup. That will make the choice of database implementation even more modular. Also removed one unnecessary inclusion of ProjectFileIO.h * Fix crashes cutting and pasting cross-project... ... in case the source project is closed before the paste happens. This caused destruction of the ProjectFileIO object and a closing of the sqlite database with the sample data in it, leaving dangling references in the SqliteSampleBlock objects. The fix is that the SqliteSampleBlockFactory object holds a shared_ptr to the ProjectFileIO object. So the clipboard may own WaveTracks, which own WaveClips, which own Sequences, which own SqliteSampleBlockFactories, which keep the ProjectFileIO and the database connection alive until the clipboard is cleared. The consequence of the fix is delayed closing of the entire database associated with the source project. If the source project is reopened before the clipboard is cleared, will there be correct concurrent access to the same persistent store? My preliminary trials suggest this is so (reopening a saved project, deleting from it, closing it again -- the clipboard contents are still unchanged and available).
2020-07-02 23:11:38 +00:00
auto newClip = std::make_unique<WaveClip>( *clip, mpFactory, true );
newClip->Clear(clip->GetStartTime(), t1);
newClip->Offset(t1-clip->GetStartTime());
clipsToAdd.push_back( std::move( newClip ) );
}
else if (clip->AfterClip(t1)) {
// Delete to right edge
// Don't modify this clip in place, because we want a strong
// guarantee, and might modify another clip
clipsToDelete.push_back( clip.get() );
Unitary changes (#599) * Define SampleBlockFactory replacing static members of SampleBlock... ... This will become an abstract base class * Sequence and WaveTrack only store SampleBlockFactory not Project... ... This adds a dependency from Track to SampleBlock which temporarily enlarges a cycle in the dependency graph * Register a global factory of SampleBlockFactory... ... so that later we can make an abstract SampleBlockFactory, separate from the concrete implementation in terms of sqlite, and inject the dependency at startup avoiding static dependency * New concrete classes SqliteSampleBlock, SqliteSampleBlockFactory... ... separated from abstract base classes and put into a new source file, breaking dependency cycles, and perhaps allowing easy reimplementation for other databases in the future. Note that the new file is a header-less plug-in! Nothing depends on it. It uses static initialization to influence the program's behavior. * Compile dependency on sqlite3.h limited to just two .cpp files... ... these are ProjectFileIO.cpp and SqliteSampleBlock.cpp. But there is still close cooperation of ProjectFileIO and SqliteSampleBlock.cpp. This suggests that these files ought to be merged, and perhaps ProjectFileIO also needs to be split into abstract and concrete classes, and there should be another injection of a factory function at startup. That will make the choice of database implementation even more modular. Also removed one unnecessary inclusion of ProjectFileIO.h * Fix crashes cutting and pasting cross-project... ... in case the source project is closed before the paste happens. This caused destruction of the ProjectFileIO object and a closing of the sqlite database with the sample data in it, leaving dangling references in the SqliteSampleBlock objects. The fix is that the SqliteSampleBlockFactory object holds a shared_ptr to the ProjectFileIO object. So the clipboard may own WaveTracks, which own WaveClips, which own Sequences, which own SqliteSampleBlockFactories, which keep the ProjectFileIO and the database connection alive until the clipboard is cleared. The consequence of the fix is delayed closing of the entire database associated with the source project. If the source project is reopened before the clipboard is cleared, will there be correct concurrent access to the same persistent store? My preliminary trials suggest this is so (reopening a saved project, deleting from it, closing it again -- the clipboard contents are still unchanged and available).
2020-07-02 23:11:38 +00:00
auto newClip = std::make_unique<WaveClip>( *clip, mpFactory, true );
newClip->Clear(t0, clip->GetEndTime());
clipsToAdd.push_back( std::move( newClip ) );
}
else {
// Delete in the middle of the clip...we actually create two
// NEW clips out of the left and right halves...
// left
2016-11-26 20:20:28 +00:00
clipsToAdd.push_back
Unitary changes (#599) * Define SampleBlockFactory replacing static members of SampleBlock... ... This will become an abstract base class * Sequence and WaveTrack only store SampleBlockFactory not Project... ... This adds a dependency from Track to SampleBlock which temporarily enlarges a cycle in the dependency graph * Register a global factory of SampleBlockFactory... ... so that later we can make an abstract SampleBlockFactory, separate from the concrete implementation in terms of sqlite, and inject the dependency at startup avoiding static dependency * New concrete classes SqliteSampleBlock, SqliteSampleBlockFactory... ... separated from abstract base classes and put into a new source file, breaking dependency cycles, and perhaps allowing easy reimplementation for other databases in the future. Note that the new file is a header-less plug-in! Nothing depends on it. It uses static initialization to influence the program's behavior. * Compile dependency on sqlite3.h limited to just two .cpp files... ... these are ProjectFileIO.cpp and SqliteSampleBlock.cpp. But there is still close cooperation of ProjectFileIO and SqliteSampleBlock.cpp. This suggests that these files ought to be merged, and perhaps ProjectFileIO also needs to be split into abstract and concrete classes, and there should be another injection of a factory function at startup. That will make the choice of database implementation even more modular. Also removed one unnecessary inclusion of ProjectFileIO.h * Fix crashes cutting and pasting cross-project... ... in case the source project is closed before the paste happens. This caused destruction of the ProjectFileIO object and a closing of the sqlite database with the sample data in it, leaving dangling references in the SqliteSampleBlock objects. The fix is that the SqliteSampleBlockFactory object holds a shared_ptr to the ProjectFileIO object. So the clipboard may own WaveTracks, which own WaveClips, which own Sequences, which own SqliteSampleBlockFactories, which keep the ProjectFileIO and the database connection alive until the clipboard is cleared. The consequence of the fix is delayed closing of the entire database associated with the source project. If the source project is reopened before the clipboard is cleared, will there be correct concurrent access to the same persistent store? My preliminary trials suggest this is so (reopening a saved project, deleting from it, closing it again -- the clipboard contents are still unchanged and available).
2020-07-02 23:11:38 +00:00
( std::make_unique<WaveClip>( *clip, mpFactory, true ) );
clipsToAdd.back()->Clear(t0, clip->GetEndTime());
// right
2016-11-26 20:20:28 +00:00
clipsToAdd.push_back
Unitary changes (#599) * Define SampleBlockFactory replacing static members of SampleBlock... ... This will become an abstract base class * Sequence and WaveTrack only store SampleBlockFactory not Project... ... This adds a dependency from Track to SampleBlock which temporarily enlarges a cycle in the dependency graph * Register a global factory of SampleBlockFactory... ... so that later we can make an abstract SampleBlockFactory, separate from the concrete implementation in terms of sqlite, and inject the dependency at startup avoiding static dependency * New concrete classes SqliteSampleBlock, SqliteSampleBlockFactory... ... separated from abstract base classes and put into a new source file, breaking dependency cycles, and perhaps allowing easy reimplementation for other databases in the future. Note that the new file is a header-less plug-in! Nothing depends on it. It uses static initialization to influence the program's behavior. * Compile dependency on sqlite3.h limited to just two .cpp files... ... these are ProjectFileIO.cpp and SqliteSampleBlock.cpp. But there is still close cooperation of ProjectFileIO and SqliteSampleBlock.cpp. This suggests that these files ought to be merged, and perhaps ProjectFileIO also needs to be split into abstract and concrete classes, and there should be another injection of a factory function at startup. That will make the choice of database implementation even more modular. Also removed one unnecessary inclusion of ProjectFileIO.h * Fix crashes cutting and pasting cross-project... ... in case the source project is closed before the paste happens. This caused destruction of the ProjectFileIO object and a closing of the sqlite database with the sample data in it, leaving dangling references in the SqliteSampleBlock objects. The fix is that the SqliteSampleBlockFactory object holds a shared_ptr to the ProjectFileIO object. So the clipboard may own WaveTracks, which own WaveClips, which own Sequences, which own SqliteSampleBlockFactories, which keep the ProjectFileIO and the database connection alive until the clipboard is cleared. The consequence of the fix is delayed closing of the entire database associated with the source project. If the source project is reopened before the clipboard is cleared, will there be correct concurrent access to the same persistent store? My preliminary trials suggest this is so (reopening a saved project, deleting from it, closing it again -- the clipboard contents are still unchanged and available).
2020-07-02 23:11:38 +00:00
( std::make_unique<WaveClip>( *clip, mpFactory, true ) );
WaveClip *const right = clipsToAdd.back().get();
right->Clear(clip->GetStartTime(), t1);
right->Offset(t1 - clip->GetStartTime());
clipsToDelete.push_back(clip.get());
}
}
else {
// (We are not doing a split cut)
// Don't modify this clip in place, because we want a strong
// guarantee, and might modify another clip
clipsToDelete.push_back( clip.get() );
Unitary changes (#599) * Define SampleBlockFactory replacing static members of SampleBlock... ... This will become an abstract base class * Sequence and WaveTrack only store SampleBlockFactory not Project... ... This adds a dependency from Track to SampleBlock which temporarily enlarges a cycle in the dependency graph * Register a global factory of SampleBlockFactory... ... so that later we can make an abstract SampleBlockFactory, separate from the concrete implementation in terms of sqlite, and inject the dependency at startup avoiding static dependency * New concrete classes SqliteSampleBlock, SqliteSampleBlockFactory... ... separated from abstract base classes and put into a new source file, breaking dependency cycles, and perhaps allowing easy reimplementation for other databases in the future. Note that the new file is a header-less plug-in! Nothing depends on it. It uses static initialization to influence the program's behavior. * Compile dependency on sqlite3.h limited to just two .cpp files... ... these are ProjectFileIO.cpp and SqliteSampleBlock.cpp. But there is still close cooperation of ProjectFileIO and SqliteSampleBlock.cpp. This suggests that these files ought to be merged, and perhaps ProjectFileIO also needs to be split into abstract and concrete classes, and there should be another injection of a factory function at startup. That will make the choice of database implementation even more modular. Also removed one unnecessary inclusion of ProjectFileIO.h * Fix crashes cutting and pasting cross-project... ... in case the source project is closed before the paste happens. This caused destruction of the ProjectFileIO object and a closing of the sqlite database with the sample data in it, leaving dangling references in the SqliteSampleBlock objects. The fix is that the SqliteSampleBlockFactory object holds a shared_ptr to the ProjectFileIO object. So the clipboard may own WaveTracks, which own WaveClips, which own Sequences, which own SqliteSampleBlockFactories, which keep the ProjectFileIO and the database connection alive until the clipboard is cleared. The consequence of the fix is delayed closing of the entire database associated with the source project. If the source project is reopened before the clipboard is cleared, will there be correct concurrent access to the same persistent store? My preliminary trials suggest this is so (reopening a saved project, deleting from it, closing it again -- the clipboard contents are still unchanged and available).
2020-07-02 23:11:38 +00:00
auto newClip = std::make_unique<WaveClip>( *clip, mpFactory, true );
// clip->Clear keeps points < t0 and >= t1 via Envelope::CollapseRegion
newClip->Clear(t0,t1);
clipsToAdd.push_back( std::move( newClip ) );
}
}
}
}
// Only now, change the contents of this track
// use No-fail-guarantee for the rest
for (const auto &clip : mClips)
{
if (clip->BeforeClip(t1))
{
// Clip is "behind" the region -- offset it unless we're splitting
// or we're using the "don't move other clips" mode
if (!split && editClipCanMove)
clip->Offset(-(t1-t0));
}
}
for (const auto &clip: clipsToDelete)
{
auto myIt = FindClip(mClips, clip);
if (myIt != mClips.end())
mClips.erase(myIt); // deletes the clip!
else
wxASSERT(false);
}
for (auto &clip: clipsToAdd)
mClips.push_back(std::move(clip)); // transfer ownership
}
void WaveTrack::SyncLockAdjust(double oldT1, double newT1)
{
if (newT1 > oldT1) {
// Insert space within the track
// JKC: This is a rare case where using >= rather than > on a float matters.
// GetEndTime() looks through the clips and may give us EXACTLY the same
// value as T1, when T1 was set to be at the end of one of those clips.
if (oldT1 >= GetEndTime())
return;
// If track is empty at oldT1 insert whitespace; otherwise, silence
if (IsEmpty(oldT1, oldT1))
{
// Check if clips can move
bool clipsCanMove = true;
gPrefs->Read(wxT("/GUI/EditClipCanMove"), &clipsCanMove);
if (clipsCanMove) {
auto tmp = Cut (oldT1, GetEndTime() + 1.0/GetRate());
Paste(newT1, tmp.get());
}
return;
}
else {
// AWD: Could just use InsertSilence() on its own here, but it doesn't
// follow EditClipCanMove rules (Paste() does it right)
auto tmp = std::make_shared<WaveTrack>(
Unitary changes (#599) * Define SampleBlockFactory replacing static members of SampleBlock... ... This will become an abstract base class * Sequence and WaveTrack only store SampleBlockFactory not Project... ... This adds a dependency from Track to SampleBlock which temporarily enlarges a cycle in the dependency graph * Register a global factory of SampleBlockFactory... ... so that later we can make an abstract SampleBlockFactory, separate from the concrete implementation in terms of sqlite, and inject the dependency at startup avoiding static dependency * New concrete classes SqliteSampleBlock, SqliteSampleBlockFactory... ... separated from abstract base classes and put into a new source file, breaking dependency cycles, and perhaps allowing easy reimplementation for other databases in the future. Note that the new file is a header-less plug-in! Nothing depends on it. It uses static initialization to influence the program's behavior. * Compile dependency on sqlite3.h limited to just two .cpp files... ... these are ProjectFileIO.cpp and SqliteSampleBlock.cpp. But there is still close cooperation of ProjectFileIO and SqliteSampleBlock.cpp. This suggests that these files ought to be merged, and perhaps ProjectFileIO also needs to be split into abstract and concrete classes, and there should be another injection of a factory function at startup. That will make the choice of database implementation even more modular. Also removed one unnecessary inclusion of ProjectFileIO.h * Fix crashes cutting and pasting cross-project... ... in case the source project is closed before the paste happens. This caused destruction of the ProjectFileIO object and a closing of the sqlite database with the sample data in it, leaving dangling references in the SqliteSampleBlock objects. The fix is that the SqliteSampleBlockFactory object holds a shared_ptr to the ProjectFileIO object. So the clipboard may own WaveTracks, which own WaveClips, which own Sequences, which own SqliteSampleBlockFactories, which keep the ProjectFileIO and the database connection alive until the clipboard is cleared. The consequence of the fix is delayed closing of the entire database associated with the source project. If the source project is reopened before the clipboard is cleared, will there be correct concurrent access to the same persistent store? My preliminary trials suggest this is so (reopening a saved project, deleting from it, closing it again -- the clipboard contents are still unchanged and available).
2020-07-02 23:11:38 +00:00
mpFactory, GetSampleFormat(), GetRate() );
tmp->InsertSilence(0.0, newT1 - oldT1);
tmp->Flush();
Paste(oldT1, tmp.get());
}
}
else if (newT1 < oldT1) {
Clear(newT1, oldT1);
}
}
/*! @excsafety{Weak} */
void WaveTrack::Paste(double t0, const Track *src)
{
bool editClipCanMove = GetEditClipsCanMove();
2017-04-11 22:16:03 +00:00
bool bOk = src && src->TypeSwitch< bool >( [&](const WaveTrack *other) {
//
// Pasting is a bit complicated, because with the existence of multiclip mode,
// we must guess the behaviour the user wants.
//
// Currently, two modes are implemented:
//
// - If a single clip should be pasted, and it should be pasted inside another
// clip, no NEW clips are generated. The audio is simply inserted.
// This resembles the old (pre-multiclip support) behaviour. However, if
// the clip is pasted outside of any clip, a NEW clip is generated. This is
// the only behaviour which is different to what was done before, but it
// shouldn't confuse users too much.
//
// - If multiple clips should be pasted, or a single clip that does not fill
// the duration of the pasted track, these are always pasted as single
2021-01-12 09:54:34 +00:00
// clips, and the current clip is split, when necessary. This may seem
2017-04-11 22:16:03 +00:00
// strange at first, but it probably is better than trying to auto-merge
// anything. The user can still merge the clips by hand (which should be a
// simple command reachable by a hotkey or single mouse click).
//
if (other->GetNumClips() == 0)
return true;
//wxPrintf("paste: we have at least one clip\n");
bool singleClipMode = (other->GetNumClips() == 1 &&
other->GetStartTime() == 0.0);
const double insertDuration = other->GetEndTime();
if( insertDuration != 0 && insertDuration < 1.0/mRate )
// PRL: I added this check to avoid violations of preconditions in other WaveClip and Sequence
// methods, but allow the value 0 so I don't subvert the purpose of commit
// 739422ba70ceb4be0bb1829b6feb0c5401de641e which causes append-recording always to make
// a new clip.
return true;
//wxPrintf("Check if we need to make room for the pasted data\n");
// Make room for the pasted data
if (editClipCanMove) {
if (!singleClipMode) {
// We need to insert multiple clips, so split the current clip and
// move everything to the right, then try to paste again
if (!IsEmpty(t0, GetEndTime())) {
auto tmp = Cut(t0, GetEndTime()+1.0/mRate);
Paste(t0 + insertDuration, tmp.get());
}
}
2017-04-11 22:16:03 +00:00
else {
// We only need to insert one single clip, so just move all clips
// to the right of the paste point out of the way
for (const auto &clip : mClips)
{
if (clip->GetStartTime() > t0-(1.0/mRate))
clip->Offset(insertDuration);
}
}
}
2017-04-11 22:16:03 +00:00
if (singleClipMode)
{
// Single clip mode
// wxPrintf("paste: checking for single clip mode!\n");
2014-06-03 20:30:19 +00:00
2017-04-11 22:16:03 +00:00
WaveClip *insideClip = NULL;
2017-04-11 22:16:03 +00:00
for (const auto &clip : mClips)
{
2017-04-11 22:16:03 +00:00
if (editClipCanMove)
{
2017-04-11 22:16:03 +00:00
if (clip->WithinClip(t0))
{
//wxPrintf("t0=%.6f: inside clip is %.6f ... %.6f\n",
// t0, clip->GetStartTime(), clip->GetEndTime());
insideClip = clip.get();
break;
}
}
2017-04-11 22:16:03 +00:00
else
{
2017-04-11 22:16:03 +00:00
// If clips are immovable we also allow prepending to clips
if (clip->WithinClip(t0) ||
TimeToLongSamples(t0) == clip->GetStartSample())
{
insideClip = clip.get();
break;
}
}
}
2017-04-11 22:16:03 +00:00
if (insideClip)
{
2017-04-11 22:16:03 +00:00
// Exhibit traditional behaviour
//wxPrintf("paste: traditional behaviour\n");
if (!editClipCanMove)
{
2017-04-11 22:16:03 +00:00
// We did not move other clips out of the way already, so
// check if we can paste without having to move other clips
for (const auto &clip : mClips)
{
if (clip->GetStartTime() > insideClip->GetStartTime() &&
insideClip->GetEndTime() + insertDuration >
clip->GetStartTime())
// Strong-guarantee in case of this path
2017-04-11 22:16:03 +00:00
// not that it matters.
throw SimpleMessageBoxException{
ExceptionType::BadUserAction,
XO("There is not enough room available to paste the selection"),
XO("Warning"),
"Error:_Insufficient_space_in_track"
2017-04-11 22:16:03 +00:00
};
}
}
2017-04-11 22:16:03 +00:00
insideClip->Paste(t0, other->GetClipByIndex(0));
return true;
}
2014-06-03 20:30:19 +00:00
2017-04-11 22:16:03 +00:00
// Just fall through and exhibit NEW behaviour
2017-04-11 22:16:03 +00:00
}
2017-04-11 22:16:03 +00:00
// Insert NEW clips
//wxPrintf("paste: multi clip mode!\n");
2017-04-11 22:16:03 +00:00
if (!editClipCanMove && !IsEmpty(t0, t0+insertDuration-1.0/mRate))
// Strong-guarantee in case of this path
2017-04-11 22:16:03 +00:00
// not that it matters.
throw SimpleMessageBoxException{
ExceptionType::BadUserAction,
XO("There is not enough room available to paste the selection"),
XO("Warning"),
"Error:_Insufficient_space_in_track"
2017-04-11 22:16:03 +00:00
};
2017-04-11 22:16:03 +00:00
for (const auto &clip : other->mClips)
{
2017-04-11 22:16:03 +00:00
// AWD Oct. 2009: Don't actually paste in placeholder clips
if (!clip->GetIsPlaceholder())
{
auto newClip =
Unitary changes (#599) * Define SampleBlockFactory replacing static members of SampleBlock... ... This will become an abstract base class * Sequence and WaveTrack only store SampleBlockFactory not Project... ... This adds a dependency from Track to SampleBlock which temporarily enlarges a cycle in the dependency graph * Register a global factory of SampleBlockFactory... ... so that later we can make an abstract SampleBlockFactory, separate from the concrete implementation in terms of sqlite, and inject the dependency at startup avoiding static dependency * New concrete classes SqliteSampleBlock, SqliteSampleBlockFactory... ... separated from abstract base classes and put into a new source file, breaking dependency cycles, and perhaps allowing easy reimplementation for other databases in the future. Note that the new file is a header-less plug-in! Nothing depends on it. It uses static initialization to influence the program's behavior. * Compile dependency on sqlite3.h limited to just two .cpp files... ... these are ProjectFileIO.cpp and SqliteSampleBlock.cpp. But there is still close cooperation of ProjectFileIO and SqliteSampleBlock.cpp. This suggests that these files ought to be merged, and perhaps ProjectFileIO also needs to be split into abstract and concrete classes, and there should be another injection of a factory function at startup. That will make the choice of database implementation even more modular. Also removed one unnecessary inclusion of ProjectFileIO.h * Fix crashes cutting and pasting cross-project... ... in case the source project is closed before the paste happens. This caused destruction of the ProjectFileIO object and a closing of the sqlite database with the sample data in it, leaving dangling references in the SqliteSampleBlock objects. The fix is that the SqliteSampleBlockFactory object holds a shared_ptr to the ProjectFileIO object. So the clipboard may own WaveTracks, which own WaveClips, which own Sequences, which own SqliteSampleBlockFactories, which keep the ProjectFileIO and the database connection alive until the clipboard is cleared. The consequence of the fix is delayed closing of the entire database associated with the source project. If the source project is reopened before the clipboard is cleared, will there be correct concurrent access to the same persistent store? My preliminary trials suggest this is so (reopening a saved project, deleting from it, closing it again -- the clipboard contents are still unchanged and available).
2020-07-02 23:11:38 +00:00
std::make_unique<WaveClip>( *clip, mpFactory, true );
2017-04-11 22:16:03 +00:00
newClip->Resample(mRate);
newClip->Offset(t0);
newClip->MarkChanged();
mClips.push_back(std::move(newClip)); // transfer ownership
}
}
2017-04-11 22:16:03 +00:00
return true;
} );
if( !bOk )
// THROW_INCONSISTENCY_EXCEPTION; // ?
(void)0;// Empty if intentional.
}
void WaveTrack::Silence(double t0, double t1)
{
if (t1 < t0)
THROW_INCONSISTENCY_EXCEPTION;
auto start = (sampleCount)floor(t0 * mRate + 0.5);
auto len = (sampleCount)floor(t1 * mRate + 0.5) - start;
for (const auto &clip : mClips)
{
auto clipStart = clip->GetStartSample();
auto clipEnd = clip->GetEndSample();
if (clipEnd > start && clipStart < start+len)
{
// Clip sample region and Get/Put sample region overlap
auto samplesToCopy = start+len - clipStart;
if (samplesToCopy > clip->GetNumSamples())
samplesToCopy = clip->GetNumSamples();
auto startDelta = clipStart - start;
decltype(startDelta) inclipDelta = 0;
if (startDelta < 0)
{
inclipDelta = -startDelta; // make positive value
samplesToCopy -= inclipDelta;
startDelta = 0;
}
clip->GetSequence()->SetSilence(inclipDelta, samplesToCopy);
clip->MarkChanged();
}
}
}
/*! @excsafety{Strong} */
void WaveTrack::InsertSilence(double t, double len)
{
// Nothing to do, if length is zero.
// Fixes Bug 1626
if( len == 0 )
return;
if (len <= 0)
THROW_INCONSISTENCY_EXCEPTION;
if (mClips.empty())
{
// Special case if there is no clip yet
Unitary changes (#599) * Define SampleBlockFactory replacing static members of SampleBlock... ... This will become an abstract base class * Sequence and WaveTrack only store SampleBlockFactory not Project... ... This adds a dependency from Track to SampleBlock which temporarily enlarges a cycle in the dependency graph * Register a global factory of SampleBlockFactory... ... so that later we can make an abstract SampleBlockFactory, separate from the concrete implementation in terms of sqlite, and inject the dependency at startup avoiding static dependency * New concrete classes SqliteSampleBlock, SqliteSampleBlockFactory... ... separated from abstract base classes and put into a new source file, breaking dependency cycles, and perhaps allowing easy reimplementation for other databases in the future. Note that the new file is a header-less plug-in! Nothing depends on it. It uses static initialization to influence the program's behavior. * Compile dependency on sqlite3.h limited to just two .cpp files... ... these are ProjectFileIO.cpp and SqliteSampleBlock.cpp. But there is still close cooperation of ProjectFileIO and SqliteSampleBlock.cpp. This suggests that these files ought to be merged, and perhaps ProjectFileIO also needs to be split into abstract and concrete classes, and there should be another injection of a factory function at startup. That will make the choice of database implementation even more modular. Also removed one unnecessary inclusion of ProjectFileIO.h * Fix crashes cutting and pasting cross-project... ... in case the source project is closed before the paste happens. This caused destruction of the ProjectFileIO object and a closing of the sqlite database with the sample data in it, leaving dangling references in the SqliteSampleBlock objects. The fix is that the SqliteSampleBlockFactory object holds a shared_ptr to the ProjectFileIO object. So the clipboard may own WaveTracks, which own WaveClips, which own Sequences, which own SqliteSampleBlockFactories, which keep the ProjectFileIO and the database connection alive until the clipboard is cleared. The consequence of the fix is delayed closing of the entire database associated with the source project. If the source project is reopened before the clipboard is cleared, will there be correct concurrent access to the same persistent store? My preliminary trials suggest this is so (reopening a saved project, deleting from it, closing it again -- the clipboard contents are still unchanged and available).
2020-07-02 23:11:38 +00:00
auto clip = std::make_unique<WaveClip>(mpFactory, mFormat, mRate, this->GetWaveColorIndex());
clip->InsertSilence(0, len);
// use No-fail-guarantee
mClips.push_back( std::move( clip ) );
return;
}
else {
// Assume at most one clip contains t
const auto end = mClips.end();
const auto it = std::find_if( mClips.begin(), end,
[&](const WaveClipHolder &clip) { return clip->WithinClip(t); } );
// use Strong-guarantee
if (it != end)
it->get()->InsertSilence(t, len);
// use No-fail-guarantee
for (const auto &clip : mClips)
{
if (clip->BeforeClip(t))
clip->Offset(len);
}
}
}
//Performs the opposite of Join
//Analyses selected region for possible Joined clips and disjoins them
/*! @excsafety{Weak} */
void WaveTrack::Disjoin(double t0, double t1)
{
auto minSamples = TimeToLongSamples( WAVETRACK_MERGE_POINT_TOLERANCE );
2016-04-14 16:17:59 +00:00
const size_t maxAtOnce = 1048576;
Floats buffer{ maxAtOnce };
Regions regions;
2014-06-03 20:30:19 +00:00
wxBusyCursor busy;
2014-06-03 20:30:19 +00:00
for (const auto &clip : mClips)
{
double startTime = clip->GetStartTime();
double endTime = clip->GetEndTime();
if( endTime < t0 || startTime > t1 )
continue;
if( t0 > startTime )
startTime = t0;
if( t1 < endTime )
endTime = t1;
2014-06-03 20:30:19 +00:00
//simply look for a sequence of zeroes and if the sequence
//is greater than minimum number, split-DELETE the region
2014-06-03 20:30:19 +00:00
sampleCount seqStart = -1;
sampleCount start, end;
clip->TimeToSamplesClip( startTime, &start );
clip->TimeToSamplesClip( endTime, &end );
2014-06-03 20:30:19 +00:00
auto len = ( end - start );
for( decltype(len) done = 0; done < len; done += maxAtOnce )
{
2016-08-24 16:13:53 +00:00
auto numSamples = limitSampleBufferSize( maxAtOnce, len - done );
2014-06-03 20:30:19 +00:00
2016-04-14 16:17:59 +00:00
clip->GetSamples( ( samplePtr )buffer.get(), floatSample, start + done,
numSamples );
2016-08-24 16:13:53 +00:00
for( decltype(numSamples) i = 0; i < numSamples; i++ )
{
auto curSamplePos = start + done + i;
//start a NEW sequence
if( buffer[ i ] == 0.0 && seqStart == -1 )
seqStart = curSamplePos;
else if( buffer[ i ] != 0.0 || curSamplePos == end - 1 )
{
if( seqStart != -1 )
{
decltype(end) seqEnd;
2014-06-03 20:30:19 +00:00
//consider the end case, where selection ends in zeroes
if( curSamplePos == end - 1 && buffer[ i ] == 0.0 )
seqEnd = end;
else
seqEnd = curSamplePos;
if( seqEnd - seqStart + 1 > minSamples )
{
2016-02-01 21:01:14 +00:00
regions.push_back(Region(
seqStart.as_double() / GetRate()
+ clip->GetStartTime(),
seqEnd.as_double() / GetRate()
+ clip->GetStartTime()));
}
seqStart = -1;
}
}
}
}
}
2016-02-01 21:01:14 +00:00
for( unsigned int i = 0; i < regions.size(); i++ )
{
2016-02-01 21:01:14 +00:00
const Region &region = regions.at(i);
SplitDelete(region.start, region.end );
}
}
/*! @excsafety{Weak} */
void WaveTrack::Join(double t0, double t1)
{
// Merge all WaveClips overlapping selection into one
WaveClipPointers clipsToDelete;
WaveClip *newClip;
for (const auto &clip: mClips)
{
if (clip->GetStartTime() < t1-(1.0/mRate) &&
clip->GetEndTime()-(1.0/mRate) > t0) {
// Put in sorted order
auto it = clipsToDelete.begin(), end = clipsToDelete.end();
for (; it != end; ++it)
if ((*it)->GetStartTime() > clip->GetStartTime())
break;
2017-10-09 04:37:10 +00:00
//wxPrintf("Insert clip %.6f at position %d\n", clip->GetStartTime(), i);
clipsToDelete.insert(it, clip.get());
}
}
//if there are no clips to DELETE, nothing to do
if( clipsToDelete.size() == 0 )
return;
2014-06-03 20:30:19 +00:00
newClip = CreateClip();
double t = clipsToDelete[0]->GetOffset();
newClip->SetOffset(t);
for (const auto &clip : clipsToDelete)
{
2017-10-09 04:37:10 +00:00
//wxPrintf("t=%.6f adding clip (offset %.6f, %.6f ... %.6f)\n",
// t, clip->GetOffset(), clip->GetStartTime(), clip->GetEndTime());
if (clip->GetOffset() - t > (1.0 / mRate)) {
double addedSilence = (clip->GetOffset() - t);
2017-10-09 04:37:10 +00:00
//wxPrintf("Adding %.6f seconds of silence\n");
auto offset = clip->GetOffset();
auto value = clip->GetEnvelope()->GetValue( offset );
newClip->AppendSilence( addedSilence, value );
t += addedSilence;
}
2017-10-09 04:37:10 +00:00
//wxPrintf("Pasting at %.6f\n", t);
newClip->Paste(t, clip);
2014-06-03 20:30:19 +00:00
t = newClip->GetEndTime();
auto it = FindClip(mClips, clip);
mClips.erase(it); // deletes the clip
}
}
/*! @excsafety{Partial}
-- Some prefix (maybe none) of the buffer is appended,
and no content already flushed to disk is lost. */
bool WaveTrack::Append(constSamplePtr buffer, sampleFormat format,
size_t len, unsigned int stride /* = 1 */)
{
return RightmostOrNewClip()->Append(buffer, format, len, stride);
}
sampleCount WaveTrack::GetBlockStart(sampleCount s) const
{
for (const auto &clip : mClips)
{
const auto startSample = (sampleCount)floor(0.5 + clip->GetStartTime()*mRate);
const auto endSample = startSample + clip->GetNumSamples();
if (s >= startSample && s < endSample)
return startSample + clip->GetSequence()->GetBlockStart(s - startSample);
}
return -1;
}
size_t WaveTrack::GetBestBlockSize(sampleCount s) const
{
auto bestBlockSize = GetMaxBlockSize();
for (const auto &clip : mClips)
{
auto startSample = (sampleCount)floor(clip->GetStartTime()*mRate + 0.5);
auto endSample = startSample + clip->GetNumSamples();
if (s >= startSample && s < endSample)
{
bestBlockSize = clip->GetSequence()->GetBestBlockSize(s - startSample);
break;
}
}
return bestBlockSize;
}
size_t WaveTrack::GetMaxBlockSize() const
{
2016-08-22 20:21:41 +00:00
decltype(GetMaxBlockSize()) maxblocksize = 0;
for (const auto &clip : mClips)
{
2016-08-22 20:21:41 +00:00
maxblocksize = std::max(maxblocksize, clip->GetSequence()->GetMaxBlockSize());
}
if (maxblocksize == 0)
{
// We really need the maximum block size, so create a
// temporary sequence to get it.
Unitary changes (#599) * Define SampleBlockFactory replacing static members of SampleBlock... ... This will become an abstract base class * Sequence and WaveTrack only store SampleBlockFactory not Project... ... This adds a dependency from Track to SampleBlock which temporarily enlarges a cycle in the dependency graph * Register a global factory of SampleBlockFactory... ... so that later we can make an abstract SampleBlockFactory, separate from the concrete implementation in terms of sqlite, and inject the dependency at startup avoiding static dependency * New concrete classes SqliteSampleBlock, SqliteSampleBlockFactory... ... separated from abstract base classes and put into a new source file, breaking dependency cycles, and perhaps allowing easy reimplementation for other databases in the future. Note that the new file is a header-less plug-in! Nothing depends on it. It uses static initialization to influence the program's behavior. * Compile dependency on sqlite3.h limited to just two .cpp files... ... these are ProjectFileIO.cpp and SqliteSampleBlock.cpp. But there is still close cooperation of ProjectFileIO and SqliteSampleBlock.cpp. This suggests that these files ought to be merged, and perhaps ProjectFileIO also needs to be split into abstract and concrete classes, and there should be another injection of a factory function at startup. That will make the choice of database implementation even more modular. Also removed one unnecessary inclusion of ProjectFileIO.h * Fix crashes cutting and pasting cross-project... ... in case the source project is closed before the paste happens. This caused destruction of the ProjectFileIO object and a closing of the sqlite database with the sample data in it, leaving dangling references in the SqliteSampleBlock objects. The fix is that the SqliteSampleBlockFactory object holds a shared_ptr to the ProjectFileIO object. So the clipboard may own WaveTracks, which own WaveClips, which own Sequences, which own SqliteSampleBlockFactories, which keep the ProjectFileIO and the database connection alive until the clipboard is cleared. The consequence of the fix is delayed closing of the entire database associated with the source project. If the source project is reopened before the clipboard is cleared, will there be correct concurrent access to the same persistent store? My preliminary trials suggest this is so (reopening a saved project, deleting from it, closing it again -- the clipboard contents are still unchanged and available).
2020-07-02 23:11:38 +00:00
maxblocksize = Sequence{ mpFactory, mFormat }.GetMaxBlockSize();
}
wxASSERT(maxblocksize > 0);
return maxblocksize;
}
size_t WaveTrack::GetIdealBlockSize()
{
2015-01-04 19:44:54 +00:00
return NewestOrNewClip()->GetSequence()->GetIdealBlockSize();
}
/*! @excsafety{Mixed} */
/*! @excsafety{No-fail} -- The rightmost clip will be in a flushed state. */
/*! @excsafety{Partial}
-- Some initial portion (maybe none) of the append buffer of the rightmost
clip gets appended; no previously saved contents are lost. */
void WaveTrack::Flush()
{
2015-01-04 19:44:54 +00:00
// After appending, presumably. Do this to the clip that gets appended.
RightmostOrNewClip()->Flush();
}
bool WaveTrack::HandleXMLTag(const wxChar *tag, const wxChar **attrs)
{
if (!wxStrcmp(tag, wxT("wavetrack"))) {
double dblValue;
long nValue;
while(*attrs) {
const wxChar *attr = *attrs++;
const wxChar *value = *attrs++;
2014-06-03 20:30:19 +00:00
if (!value)
break;
2014-06-03 20:30:19 +00:00
const wxString strValue = value;
if (!wxStrcmp(attr, wxT("rate")))
{
// mRate is an int, but "rate" in the project file is a float.
2014-06-03 20:30:19 +00:00
if (!XMLValueChecker::IsGoodString(strValue) ||
!Internat::CompatibleToDouble(strValue, &dblValue) ||
(dblValue < 1.0) || (dblValue > 1000000.0)) // allow a large range to be read
return false;
mRate = lrint(dblValue);
}
2014-06-03 20:30:19 +00:00
else if (!wxStrcmp(attr, wxT("offset")) &&
XMLValueChecker::IsGoodString(strValue) &&
Internat::CompatibleToDouble(strValue, &dblValue))
{
// Offset is only relevant for legacy project files. The value
// is cached until the actual WaveClip containing the legacy
// track is created.
mLegacyProjectFileOffset = dblValue;
}
else if (this->PlayableTrack::HandleXMLAttribute(attr, value))
{}
else if (this->Track::HandleCommonXMLAttribute(attr, strValue))
;
2014-06-03 20:30:19 +00:00
else if (!wxStrcmp(attr, wxT("gain")) &&
XMLValueChecker::IsGoodString(strValue) &&
Internat::CompatibleToDouble(strValue, &dblValue))
mGain = dblValue;
2014-06-03 20:30:19 +00:00
else if (!wxStrcmp(attr, wxT("pan")) &&
XMLValueChecker::IsGoodString(strValue) &&
Internat::CompatibleToDouble(strValue, &dblValue) &&
(dblValue >= -1.0) && (dblValue <= 1.0))
mPan = dblValue;
else if (!wxStrcmp(attr, wxT("channel")))
{
2014-06-03 20:30:19 +00:00
if (!XMLValueChecker::IsGoodInt(strValue) || !strValue.ToLong(&nValue) ||
!XMLValueChecker::IsValidChannel(nValue))
return false;
2018-09-20 16:39:05 +00:00
mChannel = static_cast<Track::ChannelType>( nValue );
}
2014-06-03 20:30:19 +00:00
else if (!wxStrcmp(attr, wxT("linked")) &&
XMLValueChecker::IsGoodInt(strValue) && strValue.ToLong(&nValue))
SetLinked(nValue != 0);
else if (!wxStrcmp(attr, wxT("colorindex")) &&
XMLValueChecker::IsGoodString(strValue) &&
strValue.ToLong(&nValue))
// Don't use SetWaveColorIndex as it sets the clips too.
mWaveColorIndex = nValue;
else if (!wxStrcmp(attr, wxT("sampleformat")) &&
XMLValueChecker::IsGoodInt(strValue) &&
strValue.ToLong(&nValue) &&
XMLValueChecker::IsValidSampleFormat(nValue))
mFormat = static_cast<sampleFormat>(nValue);
} // while
return true;
}
return false;
}
void WaveTrack::HandleXMLEndTag(const wxChar * WXUNUSED(tag))
{
// In case we opened a pre-multiclip project, we need to
// simulate closing the waveclip tag.
2015-01-04 19:44:54 +00:00
NewestOrNewClip()->HandleXMLEndTag(wxT("waveclip"));
}
XMLTagHandler *WaveTrack::HandleXMLChild(const wxChar *tag)
{
//
// This is legacy code (1.2 and previous) and is not called for NEW projects!
//
if (!wxStrcmp(tag, wxT("sequence")) || !wxStrcmp(tag, wxT("envelope")))
{
// This is a legacy project, so set the cached offset
2015-01-04 19:44:54 +00:00
NewestOrNewClip()->SetOffset(mLegacyProjectFileOffset);
2014-06-03 20:30:19 +00:00
// Legacy project file tracks are imported as one single wave clip
if (!wxStrcmp(tag, wxT("sequence")))
2015-01-04 19:44:54 +00:00
return NewestOrNewClip()->GetSequence();
else if (!wxStrcmp(tag, wxT("envelope")))
2015-01-04 19:44:54 +00:00
return NewestOrNewClip()->GetEnvelope();
}
2014-06-03 20:30:19 +00:00
// JKC... for 1.1.0, one step better than what we had, but still badly broken.
//If we see a waveblock at this level, we'd better generate a sequence.
if( !wxStrcmp( tag, wxT("waveblock" )))
{
// This is a legacy project, so set the cached offset
2015-01-04 19:44:54 +00:00
NewestOrNewClip()->SetOffset(mLegacyProjectFileOffset);
Sequence *pSeq = NewestOrNewClip()->GetSequence();
return pSeq;
}
//
// This is for the NEW file format (post-1.2)
//
if (!wxStrcmp(tag, wxT("waveclip")))
return CreateClip();
else
return NULL;
}
2017-02-22 19:23:35 +00:00
void WaveTrack::WriteXML(XMLWriter &xmlFile) const
// may throw
{
xmlFile.StartTag(wxT("wavetrack"));
this->Track::WriteCommonXMLAttributes( xmlFile );
xmlFile.WriteAttr(wxT("channel"), mChannel);
xmlFile.WriteAttr(wxT("linked"), mLinked);
this->PlayableTrack::WriteXMLAttributes(xmlFile);
xmlFile.WriteAttr(wxT("rate"), mRate);
xmlFile.WriteAttr(wxT("gain"), (double)mGain);
xmlFile.WriteAttr(wxT("pan"), (double)mPan);
xmlFile.WriteAttr(wxT("colorindex"), mWaveColorIndex );
xmlFile.WriteAttr(wxT("sampleformat"), static_cast<long>(mFormat) );
2014-06-03 20:30:19 +00:00
for (const auto &clip : mClips)
{
clip->WriteXML(xmlFile);
}
xmlFile.EndTag(wxT("wavetrack"));
}
bool WaveTrack::GetErrorOpening()
{
for (const auto &clip : mClips)
if (clip->GetSequence()->GetErrorOpening())
return true;
return false;
}
bool WaveTrack::CloseLock()
{
for (const auto &clip : mClips)
clip->CloseLock();
return true;
}
AUDACITY_DLL_API sampleCount WaveTrack::TimeToLongSamples(double t0) const
{
return sampleCount( floor(t0 * mRate + 0.5) );
}
double WaveTrack::LongSamplesToTime(sampleCount pos) const
{
return pos.as_double() / mRate;
}
double WaveTrack::GetStartTime() const
{
bool found = false;
double best = 0.0;
if (mClips.empty())
return 0;
for (const auto &clip : mClips)
if (!found)
{
found = true;
best = clip->GetStartTime();
}
else if (clip->GetStartTime() < best)
best = clip->GetStartTime();
return best;
}
double WaveTrack::GetEndTime() const
{
bool found = false;
double best = 0.0;
if (mClips.empty())
return 0;
for (const auto &clip : mClips)
if (!found)
{
found = true;
best = clip->GetEndTime();
}
else if (clip->GetEndTime() > best)
best = clip->GetEndTime();
return best;
}
//
// Getting/setting samples. The sample counts here are
// expressed relative to t=0.0 at the track's sample rate.
//
std::pair<float, float> WaveTrack::GetMinMax(
double t0, double t1, bool mayThrow) const
{
std::pair<float, float> results {
// we need these at extremes to make sure we find true min and max
FLT_MAX, -FLT_MAX
};
bool clipFound = false;
if (t0 > t1) {
if (mayThrow)
THROW_INCONSISTENCY_EXCEPTION;
return results;
}
if (t0 == t1)
return results;
for (const auto &clip: mClips)
{
if (t1 >= clip->GetStartTime() && t0 <= clip->GetEndTime())
{
clipFound = true;
auto clipResults = clip->GetMinMax(t0, t1, mayThrow);
if (clipResults.first < results.first)
results.first = clipResults.first;
if (clipResults.second > results.second)
results.second = clipResults.second;
}
}
if(!clipFound)
{
results = { 0.f, 0.f }; // sensible defaults if no clips found
}
return results;
}
float WaveTrack::GetRMS(double t0, double t1, bool mayThrow) const
{
if (t0 > t1) {
if (mayThrow)
THROW_INCONSISTENCY_EXCEPTION;
return 0.f;
}
if (t0 == t1)
return 0.f;
double sumsq = 0.0;
sampleCount length = 0;
for (const auto &clip: mClips)
{
// If t1 == clip->GetStartTime() or t0 == clip->GetEndTime(), then the clip
// is not inside the selection, so we don't want it.
// if (t1 >= clip->GetStartTime() && t0 <= clip->GetEndTime())
if (t1 >= clip->GetStartTime() && t0 <= clip->GetEndTime())
{
sampleCount clipStart, clipEnd;
float cliprms = clip->GetRMS(t0, t1, mayThrow);
clip->TimeToSamplesClip(wxMax(t0, clip->GetStartTime()), &clipStart);
clip->TimeToSamplesClip(wxMin(t1, clip->GetEndTime()), &clipEnd);
sumsq += cliprms * cliprms * (clipEnd - clipStart).as_float();
length += (clipEnd - clipStart);
}
}
return length > 0 ? sqrt(sumsq / length.as_double()) : 0.0;
}
bool WaveTrack::Get(samplePtr buffer, sampleFormat format,
sampleCount start, size_t len, fillFormat fill,
bool mayThrow, sampleCount * pNumWithinClips) const
{
// Simple optimization: When this buffer is completely contained within one clip,
// don't clear anything (because we won't have to). Otherwise, just clear
// everything to be on the safe side.
bool doClear = true;
bool result = true;
sampleCount samplesCopied = 0;
for (const auto &clip: mClips)
{
if (start >= clip->GetStartSample() && start+len <= clip->GetEndSample())
{
doClear = false;
break;
}
}
if (doClear)
{
2017-10-21 15:23:55 +00:00
// Usually we fill in empty space with zero
if( fill == fillZero )
ClearSamples(buffer, format, 0, len);
// but we don't have to.
else if( fill==fillTwo )
{
wxASSERT( format==floatSample );
float * pBuffer = (float*)buffer;
for(size_t i=0;i<len;i++)
pBuffer[i]=2.0f;
}
else
{
wxFAIL_MSG(wxT("Invalid fill format"));
}
}
// Iterate the clips. They are not necessarily sorted by time.
for (const auto &clip: mClips)
{
auto clipStart = clip->GetStartSample();
auto clipEnd = clip->GetEndSample();
if (clipEnd > start && clipStart < start+len)
{
// Clip sample region and Get/Put sample region overlap
2016-09-02 16:50:40 +00:00
auto samplesToCopy =
std::min( start+len - clipStart, clip->GetNumSamples() );
auto startDelta = clipStart - start;
decltype(startDelta) inclipDelta = 0;
if (startDelta < 0)
{
inclipDelta = -startDelta; // make positive value
samplesToCopy -= inclipDelta;
// samplesToCopy is now either len or
// (clipEnd - clipStart) - (start - clipStart)
// == clipEnd - start > 0
// samplesToCopy is not more than len
//
startDelta = 0;
// startDelta is zero
}
else {
2020-04-11 07:08:33 +00:00
// startDelta is nonnegative and less than len
// samplesToCopy is positive and not more than len
}
if (!clip->GetSamples(
(samplePtr)(((char*)buffer) +
startDelta.as_size_t() *
SAMPLE_SIZE(format)),
format, inclipDelta, samplesToCopy.as_size_t(), mayThrow ))
result = false;
else
samplesCopied += samplesToCopy;
}
}
if( pNumWithinClips )
*pNumWithinClips = samplesCopied;
return result;
}
/*! @excsafety{Weak} */
void WaveTrack::Set(constSamplePtr buffer, sampleFormat format,
sampleCount start, size_t len)
{
for (const auto &clip: mClips)
{
auto clipStart = clip->GetStartSample();
auto clipEnd = clip->GetEndSample();
if (clipEnd > start && clipStart < start+len)
{
// Clip sample region and Get/Put sample region overlap
2016-09-02 16:50:40 +00:00
auto samplesToCopy =
std::min( start+len - clipStart, clip->GetNumSamples() );
auto startDelta = clipStart - start;
decltype(startDelta) inclipDelta = 0;
if (startDelta < 0)
{
inclipDelta = -startDelta; // make positive value
samplesToCopy -= inclipDelta;
// samplesToCopy is now either len or
// (clipEnd - clipStart) - (start - clipStart)
// == clipEnd - start > 0
// samplesToCopy is not more than len
//
startDelta = 0;
// startDelta is zero
}
else {
2020-04-11 07:08:33 +00:00
// startDelta is nonnegative and less than len
// samplesToCopy is positive and not more than len
}
clip->SetSamples(
(constSamplePtr)(((const char*)buffer) +
startDelta.as_size_t() *
SAMPLE_SIZE(format)),
format, inclipDelta, samplesToCopy.as_size_t() );
clip->MarkChanged();
}
}
}
void WaveTrack::GetEnvelopeValues(double *buffer, size_t bufferLen,
double t0) const
{
// The output buffer corresponds to an unbroken span of time which the callers expect
// to be fully valid. As clips are processed below, the output buffer is updated with
// envelope values from any portion of a clip, start, end, middle, or none at all.
// Since this does not guarantee that the entire buffer is filled with values we need
// to initialize the entire buffer to a default value.
//
// This does mean that, in the cases where a usable clip is located, the buffer value will
// be set twice. Unfortunately, there is no easy way around this since the clips are not
// stored in increasing time order. If they were, we could just track the time as the
// buffer is filled.
for (decltype(bufferLen) i = 0; i < bufferLen; i++)
{
buffer[i] = 1.0;
}
double startTime = t0;
auto tstep = 1.0 / mRate;
double endTime = t0 + tstep * bufferLen;
for (const auto &clip: mClips)
{
// IF clip intersects startTime..endTime THEN...
auto dClipStartTime = clip->GetStartTime();
auto dClipEndTime = clip->GetEndTime();
if ((dClipStartTime < endTime) && (dClipEndTime > startTime))
{
auto rbuf = buffer;
auto rlen = bufferLen;
auto rt0 = t0;
if (rt0 < dClipStartTime)
{
// This is not more than the number of samples in
// (endTime - startTime) which is bufferLen:
auto nDiff = (sampleCount)floor((dClipStartTime - rt0) * mRate + 0.5);
auto snDiff = nDiff.as_size_t();
rbuf += snDiff;
wxASSERT(snDiff <= rlen);
rlen -= snDiff;
rt0 = dClipStartTime;
}
if (rt0 + rlen*tstep > dClipEndTime)
{
auto nClipLen = clip->GetEndSample() - clip->GetStartSample();
2014-06-03 20:30:19 +00:00
if (nClipLen <= 0) // Testing for bug 641, this problem is consistently '== 0', but doesn't hurt to check <.
return;
2014-06-03 20:30:19 +00:00
// This check prevents problem cited in http://bugzilla.audacityteam.org/show_bug.cgi?id=528#c11,
// Gale's cross_fade_out project, which was already corrupted by bug 528.
// This conditional prevents the previous write past the buffer end, in clip->GetEnvelope() call.
2015-02-19 11:08:58 +00:00
// Never increase rlen here.
// PRL bug 827: rewrote it again
rlen = limitSampleBufferSize( rlen, nClipLen );
rlen = std::min(rlen, size_t(floor(0.5 + (dClipEndTime - rt0) / tstep)));
}
// Samples are obtained for the purpose of rendering a wave track,
// so quantize time
clip->GetEnvelope()->GetValues(rbuf, rlen, rt0, tstep);
}
}
}
WaveClip* WaveTrack::GetClipAtSample(sampleCount sample)
{
for (const auto &clip: mClips)
{
auto start = clip->GetStartSample();
auto len = clip->GetNumSamples();
if (sample >= start && sample < start + len)
return clip.get();
}
return NULL;
}
// When the time is both the end of a clip and the start of the next clip, the
// latter clip is returned.
WaveClip* WaveTrack::GetClipAtTime(double time)
{
const auto clips = SortedClipArray();
auto p = std::find_if(clips.rbegin(), clips.rend(), [&] (WaveClip* const& clip) {
return time >= clip->GetStartTime() && time <= clip->GetEndTime(); });
// When two clips are immediately next to each other, the GetEndTime() of the first clip
// and the GetStartTime() of the second clip may not be exactly equal due to rounding errors.
// If "time" is the end time of the first of two such clips, and the end time is slightly
// less than the start time of the second clip, then the first rather than the
// second clip is found by the above code. So correct this.
2017-11-04 17:34:39 +00:00
if (p != clips.rend() && p != clips.rbegin() &&
time == (*p)->GetEndTime() &&
(*p)->SharesBoundaryWithNextClip(*(p-1))) {
p--;
}
return p != clips.rend() ? *p : nullptr;
}
Envelope* WaveTrack::GetEnvelopeAtTime(double time)
{
WaveClip* clip = GetClipAtTime(time);
if (clip)
return clip->GetEnvelope();
else
return NULL;
}
Sequence* WaveTrack::GetSequenceAtTime(double time)
{
WaveClip* clip = GetClipAtTime(time);
if (clip)
return clip->GetSequence();
else
return NULL;
}
WaveClip* WaveTrack::CreateClip()
{
Unitary changes (#599) * Define SampleBlockFactory replacing static members of SampleBlock... ... This will become an abstract base class * Sequence and WaveTrack only store SampleBlockFactory not Project... ... This adds a dependency from Track to SampleBlock which temporarily enlarges a cycle in the dependency graph * Register a global factory of SampleBlockFactory... ... so that later we can make an abstract SampleBlockFactory, separate from the concrete implementation in terms of sqlite, and inject the dependency at startup avoiding static dependency * New concrete classes SqliteSampleBlock, SqliteSampleBlockFactory... ... separated from abstract base classes and put into a new source file, breaking dependency cycles, and perhaps allowing easy reimplementation for other databases in the future. Note that the new file is a header-less plug-in! Nothing depends on it. It uses static initialization to influence the program's behavior. * Compile dependency on sqlite3.h limited to just two .cpp files... ... these are ProjectFileIO.cpp and SqliteSampleBlock.cpp. But there is still close cooperation of ProjectFileIO and SqliteSampleBlock.cpp. This suggests that these files ought to be merged, and perhaps ProjectFileIO also needs to be split into abstract and concrete classes, and there should be another injection of a factory function at startup. That will make the choice of database implementation even more modular. Also removed one unnecessary inclusion of ProjectFileIO.h * Fix crashes cutting and pasting cross-project... ... in case the source project is closed before the paste happens. This caused destruction of the ProjectFileIO object and a closing of the sqlite database with the sample data in it, leaving dangling references in the SqliteSampleBlock objects. The fix is that the SqliteSampleBlockFactory object holds a shared_ptr to the ProjectFileIO object. So the clipboard may own WaveTracks, which own WaveClips, which own Sequences, which own SqliteSampleBlockFactories, which keep the ProjectFileIO and the database connection alive until the clipboard is cleared. The consequence of the fix is delayed closing of the entire database associated with the source project. If the source project is reopened before the clipboard is cleared, will there be correct concurrent access to the same persistent store? My preliminary trials suggest this is so (reopening a saved project, deleting from it, closing it again -- the clipboard contents are still unchanged and available).
2020-07-02 23:11:38 +00:00
mClips.push_back(std::make_unique<WaveClip>(mpFactory, mFormat, mRate, GetWaveColorIndex()));
return mClips.back().get();
}
2015-01-04 19:44:54 +00:00
WaveClip* WaveTrack::NewestOrNewClip()
{
if (mClips.empty()) {
WaveClip *clip = CreateClip();
clip->SetOffset(mOffset);
return clip;
}
else
return mClips.back().get();
}
/*! @excsafety{No-fail} */
2015-01-04 19:44:54 +00:00
WaveClip* WaveTrack::RightmostOrNewClip()
{
if (mClips.empty()) {
2015-01-04 19:44:54 +00:00
WaveClip *clip = CreateClip();
clip->SetOffset(mOffset);
return clip;
}
else
{
auto it = mClips.begin();
WaveClip *rightmost = (*it++).get();
2015-01-04 19:44:54 +00:00
double maxOffset = rightmost->GetOffset();
for (auto end = mClips.end(); it != end; ++it)
2015-01-04 19:44:54 +00:00
{
WaveClip *clip = it->get();
2015-01-04 19:44:54 +00:00
double offset = clip->GetOffset();
if (maxOffset < offset)
maxOffset = offset, rightmost = clip;
}
return rightmost;
}
}
int WaveTrack::GetClipIndex(const WaveClip* clip) const
{
int result;
FindClip(mClips, clip, &result);
return result;
}
WaveClip* WaveTrack::GetClipByIndex(int index)
{
if(index < (int)mClips.size())
return mClips[index].get();
else
return nullptr;
}
const WaveClip* WaveTrack::GetClipByIndex(int index) const
{
return const_cast<WaveTrack&>(*this).GetClipByIndex(index);
}
int WaveTrack::GetNumClips() const
{
return mClips.size();
}
bool WaveTrack::CanOffsetClips(
const std::vector<WaveClip*> &clips,
double amount,
double *allowedAmount /* = NULL */)
{
if (allowedAmount)
*allowedAmount = amount;
const auto &moving = [&](WaveClip *clip){
// linear search might be improved, but expecting few moving clips
// compared with the fixed clips
return clips.end() != std::find( clips.begin(), clips.end(), clip );
};
for (const auto &c: mClips) {
if ( moving( c.get() ) )
continue;
for (const auto clip : clips) {
if (c->GetStartTime() < clip->GetEndTime() + amount &&
c->GetEndTime() > clip->GetStartTime() + amount)
{
if (!allowedAmount)
return false; // clips overlap
if (amount > 0)
{
if (c->GetStartTime()-clip->GetEndTime() < *allowedAmount)
*allowedAmount = c->GetStartTime()-clip->GetEndTime();
if (*allowedAmount < 0)
*allowedAmount = 0;
} else
{
if (c->GetEndTime()-clip->GetStartTime() > *allowedAmount)
*allowedAmount = c->GetEndTime()-clip->GetStartTime();
if (*allowedAmount > 0)
*allowedAmount = 0;
}
}
}
}
if (allowedAmount)
{
if (*allowedAmount == amount)
return true;
// Check if the NEW calculated amount would not violate
// any other constraint
if (!CanOffsetClips(clips, *allowedAmount, nullptr)) {
*allowedAmount = 0; // play safe and don't allow anything
return false;
}
else
return true;
} else
return true;
}
bool WaveTrack::CanInsertClip(
WaveClip* clip, double &slideBy, double &tolerance) const
{
for (const auto &c : mClips)
{
double d1 = c->GetStartTime() - (clip->GetEndTime()+slideBy);
double d2 = (clip->GetStartTime()+slideBy) - c->GetEndTime();
if ( (d1<0) && (d2<0) )
{
// clips overlap.
// Try to rescue it.
// The rescue logic is not perfect, and will typically
// move the clip at most once.
// We divide by 1000 rather than set to 0, to allow for
// a second 'micro move' that is really about rounding error.
if( -d1 < tolerance ){
// right edge of clip overlaps slightly.
// slide clip left a small amount.
slideBy +=d1;
tolerance /=1000;
} else if( -d2 < tolerance ){
// left edge of clip overlaps slightly.
// slide clip right a small amount.
slideBy -= d2;
tolerance /=1000;
}
else
return false; // clips overlap No tolerance left.
}
}
return true;
}
/*! @excsafety{Weak} */
void WaveTrack::Split( double t0, double t1 )
{
SplitAt( t0 );
if( t0 != t1 )
SplitAt( t1 );
}
/*! @excsafety{Weak} */
void WaveTrack::SplitAt(double t)
{
for (const auto &c : mClips)
{
if (c->WithinClip(t))
{
t = LongSamplesToTime(TimeToLongSamples(t)); // put t on a sample
Unitary changes (#599) * Define SampleBlockFactory replacing static members of SampleBlock... ... This will become an abstract base class * Sequence and WaveTrack only store SampleBlockFactory not Project... ... This adds a dependency from Track to SampleBlock which temporarily enlarges a cycle in the dependency graph * Register a global factory of SampleBlockFactory... ... so that later we can make an abstract SampleBlockFactory, separate from the concrete implementation in terms of sqlite, and inject the dependency at startup avoiding static dependency * New concrete classes SqliteSampleBlock, SqliteSampleBlockFactory... ... separated from abstract base classes and put into a new source file, breaking dependency cycles, and perhaps allowing easy reimplementation for other databases in the future. Note that the new file is a header-less plug-in! Nothing depends on it. It uses static initialization to influence the program's behavior. * Compile dependency on sqlite3.h limited to just two .cpp files... ... these are ProjectFileIO.cpp and SqliteSampleBlock.cpp. But there is still close cooperation of ProjectFileIO and SqliteSampleBlock.cpp. This suggests that these files ought to be merged, and perhaps ProjectFileIO also needs to be split into abstract and concrete classes, and there should be another injection of a factory function at startup. That will make the choice of database implementation even more modular. Also removed one unnecessary inclusion of ProjectFileIO.h * Fix crashes cutting and pasting cross-project... ... in case the source project is closed before the paste happens. This caused destruction of the ProjectFileIO object and a closing of the sqlite database with the sample data in it, leaving dangling references in the SqliteSampleBlock objects. The fix is that the SqliteSampleBlockFactory object holds a shared_ptr to the ProjectFileIO object. So the clipboard may own WaveTracks, which own WaveClips, which own Sequences, which own SqliteSampleBlockFactories, which keep the ProjectFileIO and the database connection alive until the clipboard is cleared. The consequence of the fix is delayed closing of the entire database associated with the source project. If the source project is reopened before the clipboard is cleared, will there be correct concurrent access to the same persistent store? My preliminary trials suggest this is so (reopening a saved project, deleting from it, closing it again -- the clipboard contents are still unchanged and available).
2020-07-02 23:11:38 +00:00
auto newClip = std::make_unique<WaveClip>( *c, mpFactory, true );
c->Clear(t, c->GetEndTime());
newClip->Clear(c->GetStartTime(), t);
2014-06-03 20:30:19 +00:00
//offset the NEW clip by the splitpoint (noting that it is already offset to c->GetStartTime())
sampleCount here = llrint(floor(((t - c->GetStartTime()) * mRate) + 0.5));
newClip->Offset(here.as_double()/(double)mRate);
// This could invalidate the iterators for the loop! But we return
// at once so it's okay
mClips.push_back(std::move(newClip)); // transfer ownership
2017-04-02 14:42:07 +00:00
return;
}
}
}
void WaveTrack::UpdateLocationsCache() const
{
auto clips = SortedClipArray();
2014-06-03 20:30:19 +00:00
mDisplayLocationsCache.clear();
2014-06-03 20:30:19 +00:00
// Count number of display locations
int num = 0;
{
const WaveClip *prev = nullptr;
for (const auto clip : clips)
{
num += clip->NumCutLines();
2014-06-03 20:30:19 +00:00
if (prev && fabs(prev->GetEndTime() -
clip->GetStartTime()) < WAVETRACK_MERGE_POINT_TOLERANCE)
++num;
prev = clip;
}
}
if (num == 0)
return;
// Alloc necessary number of display locations
mDisplayLocationsCache.reserve(num);
// Add all display locations to cache
int curpos = 0;
2014-06-03 20:30:19 +00:00
const WaveClip *previousClip = nullptr;
for (const auto clip: clips)
{
for (const auto &cc : clip->GetCutLines())
{
// Add cut line expander point
mDisplayLocationsCache.push_back(WaveTrackLocation{
clip->GetOffset() + cc->GetOffset(),
WaveTrackLocation::locationCutLine
});
curpos++;
}
2014-06-03 20:30:19 +00:00
if (previousClip)
{
if (fabs(previousClip->GetEndTime() - clip->GetStartTime())
< WAVETRACK_MERGE_POINT_TOLERANCE)
{
// Add merge point
mDisplayLocationsCache.push_back(WaveTrackLocation{
previousClip->GetEndTime(),
WaveTrackLocation::locationMergePoint,
GetClipIndex(previousClip),
GetClipIndex(clip)
});
curpos++;
}
}
previousClip = clip;
}
2014-06-03 20:30:19 +00:00
wxASSERT(curpos == num);
}
// Expand cut line (that is, re-insert audio, then DELETE audio saved in cut line)
/*! @excsafety{Strong} */
void WaveTrack::ExpandCutLine(double cutLinePosition, double* cutlineStart,
double* cutlineEnd)
{
bool editClipCanMove = GetEditClipsCanMove();
2014-06-03 20:30:19 +00:00
// Find clip which contains this cut line
double start = 0, end = 0;
auto pEnd = mClips.end();
auto pClip = std::find_if( mClips.begin(), pEnd,
[&](const WaveClipHolder &clip) {
return clip->FindCutLine(cutLinePosition, &start, &end); } );
if (pClip != pEnd)
{
auto &clip = *pClip;
if (!editClipCanMove)
{
// We are not allowed to move the other clips, so see if there
// is enough room to expand the cut line
for (const auto &clip2: mClips)
{
if (clip2->GetStartTime() > clip->GetStartTime() &&
clip->GetEndTime() + end - start > clip2->GetStartTime())
// Strong-guarantee in case of this path
throw SimpleMessageBoxException{
ExceptionType::BadUserAction,
XO("There is not enough room available to expand the cut line"),
XO("Warning"),
"Error:_Insufficient_space_in_track"
};
}
}
2014-06-03 20:30:19 +00:00
clip->ExpandCutLine(cutLinePosition);
// Strong-guarantee provided that the following gives No-fail-guarantee
if (cutlineStart)
*cutlineStart = start;
if (cutlineEnd)
*cutlineEnd = end;
// Move clips which are to the right of the cut line
if (editClipCanMove)
{
for (const auto &clip2 : mClips)
{
if (clip2->GetStartTime() > clip->GetStartTime())
clip2->Offset(end - start);
}
}
}
}
bool WaveTrack::RemoveCutLine(double cutLinePosition)
{
for (const auto &clip : mClips)
if (clip->RemoveCutLine(cutLinePosition))
return true;
return false;
}
/*! @excsafety{Strong} */
void WaveTrack::MergeClips(int clipidx1, int clipidx2)
{
WaveClip* clip1 = GetClipByIndex(clipidx1);
WaveClip* clip2 = GetClipByIndex(clipidx2);
if (!clip1 || !clip2) // Could happen if one track of a linked pair had a split and the other didn't.
return; // Don't throw, just do nothing.
2014-06-03 20:30:19 +00:00
// Append data from second clip to first clip
// use Strong-guarantee
clip1->Paste(clip1->GetEndTime(), clip2);
// use No-fail-guarantee for the rest
// Delete second clip
auto it = FindClip(mClips, clip2);
mClips.erase(it);
}
/*! @excsafety{Weak} -- Partial completion may leave clips at differing sample rates!
*/
void WaveTrack::Resample(int rate, ProgressDialog *progress)
{
for (const auto &clip : mClips)
clip->Resample(rate, progress);
2014-06-03 20:30:19 +00:00
mRate = rate;
}
namespace {
template < typename Cont1, typename Cont2 >
Cont1 FillSortedClipArray(const Cont2& mClips)
{
Cont1 clips;
for (const auto &clip : mClips)
clips.push_back(clip.get());
std::sort(clips.begin(), clips.end(),
[](const WaveClip *a, const WaveClip *b)
{ return a->GetStartTime() < b->GetStartTime(); });
return clips;
}
}
WaveClipPointers WaveTrack::SortedClipArray()
{
return FillSortedClipArray<WaveClipPointers>(mClips);
}
2014-06-03 20:30:19 +00:00
WaveClipConstPointers WaveTrack::SortedClipArray() const
{
return FillSortedClipArray<WaveClipConstPointers>(mClips);
}
///Deletes all clips' wavecaches. Careful, This may not be threadsafe.
void WaveTrack::ClearWaveCaches()
{
for (const auto &clip : mClips)
clip->ClearWaveCache();
}
WaveTrackCache::~WaveTrackCache()
{
}
void WaveTrackCache::SetTrack(const std::shared_ptr<const WaveTrack> &pTrack)
{
if (mPTrack != pTrack) {
if (pTrack) {
mBufferSize = pTrack->GetMaxBlockSize();
if (!mPTrack ||
mPTrack->GetMaxBlockSize() != mBufferSize) {
Free();
2016-04-14 16:17:59 +00:00
mBuffers[0].data = Floats{ mBufferSize };
mBuffers[1].data = Floats{ mBufferSize };
}
}
else
Free();
mPTrack = pTrack;
mNValidBuffers = 0;
}
}
const float *WaveTrackCache::GetFloats(
sampleCount start, size_t len, bool mayThrow)
{
constexpr auto format = floatSample;
if (format == floatSample && len > 0) {
const auto end = start + len;
bool fillFirst = (mNValidBuffers < 1);
bool fillSecond = (mNValidBuffers < 2);
// Discard cached results that we no longer need
if (mNValidBuffers > 0 &&
(end <= mBuffers[0].start ||
start >= mBuffers[mNValidBuffers - 1].end())) {
// Complete miss
fillFirst = true;
fillSecond = true;
}
else if (mNValidBuffers == 2 &&
start >= mBuffers[1].start &&
end > mBuffers[1].end()) {
// Request starts in the second buffer and extends past it.
// Discard the first buffer.
// (But don't deallocate the buffer space.)
mBuffers[0] .swap ( mBuffers[1] );
fillSecond = true;
mNValidBuffers = 1;
}
else if (mNValidBuffers > 0 &&
start < mBuffers[0].start &&
0 <= mPTrack->GetBlockStart(start)) {
// Request is not a total miss but starts before the cache,
// and there is a clip to fetch from.
// Not the access pattern for drawing spectrogram or playback,
// but maybe scrubbing causes this.
// Move the first buffer into second place, and later
// refill the first.
// (This case might be useful when marching backwards through
// the track, as with scrubbing.)
mBuffers[0] .swap ( mBuffers[1] );
fillFirst = true;
fillSecond = false;
// Cache is not in a consistent state yet
mNValidBuffers = 0;
}
// Refill buffers as needed
if (fillFirst) {
const auto start0 = mPTrack->GetBlockStart(start);
if (start0 >= 0) {
const auto len0 = mPTrack->GetBestBlockSize(start0);
wxASSERT(len0 <= mBufferSize);
if (!mPTrack->GetFloats(
mBuffers[0].data.get(), start0, len0,
fillZero, mayThrow))
return nullptr;
mBuffers[0].start = start0;
mBuffers[0].len = len0;
if (!fillSecond &&
mBuffers[0].end() != mBuffers[1].start)
fillSecond = true;
// Keep the partially updated state consistent:
mNValidBuffers = fillSecond ? 1 : 2;
}
else {
// Request may fall between the clips of a track.
// Invalidate all. WaveTrack::Get() will return zeroes.
mNValidBuffers = 0;
fillSecond = false;
}
}
wxASSERT(!fillSecond || mNValidBuffers > 0);
if (fillSecond) {
mNValidBuffers = 1;
const auto end0 = mBuffers[0].end();
if (end > end0) {
const auto start1 = mPTrack->GetBlockStart(end0);
if (start1 == end0) {
const auto len1 = mPTrack->GetBestBlockSize(start1);
wxASSERT(len1 <= mBufferSize);
if (!mPTrack->GetFloats(mBuffers[1].data.get(), start1, len1, fillZero, mayThrow))
return nullptr;
mBuffers[1].start = start1;
mBuffers[1].len = len1;
mNValidBuffers = 2;
}
}
}
wxASSERT(mNValidBuffers < 2 || mBuffers[0].end() == mBuffers[1].start);
samplePtr buffer = nullptr; // will point into mOverlapBuffer
auto remaining = len;
// Possibly get an initial portion that is uncached
// This may be negative
const auto initLen =
mNValidBuffers < 1 ? sampleCount( len )
: std::min(sampleCount( len ), mBuffers[0].start - start);
if (initLen > 0) {
// This might be fetching zeroes between clips
mOverlapBuffer.Resize(len, format);
// initLen is not more than len:
auto sinitLen = initLen.as_size_t();
if (!mPTrack->GetFloats(
// See comment below about casting
reinterpret_cast<float *>(mOverlapBuffer.ptr()),
start, sinitLen, fillZero, mayThrow))
return nullptr;
wxASSERT( sinitLen <= remaining );
remaining -= sinitLen;
start += initLen;
buffer = mOverlapBuffer.ptr() + sinitLen * SAMPLE_SIZE(format);
}
// Now satisfy the request from the buffers
for (int ii = 0; ii < mNValidBuffers && remaining > 0; ++ii) {
const auto starti = start - mBuffers[ii].start;
// Treatment of initLen above establishes this loop invariant,
// and statements below preserve it:
wxASSERT(starti >= 0);
// This may be negative
const auto leni =
std::min( sampleCount( remaining ), mBuffers[ii].len - starti );
if (initLen <= 0 && leni == len) {
// All is contiguous already. We can completely avoid copying
// leni is nonnegative, therefore start falls within mBuffers[ii],
// so starti is bounded between 0 and buffer length
return mBuffers[ii].data.get() + starti.as_size_t() ;
}
else if (leni > 0) {
// leni is nonnegative, therefore start falls within mBuffers[ii]
// But we can't satisfy all from one buffer, so copy
if (!buffer) {
mOverlapBuffer.Resize(len, format);
buffer = mOverlapBuffer.ptr();
}
// leni is positive and not more than remaining
const size_t size = sizeof(float) * leni.as_size_t();
// starti is less than mBuffers[ii].len and nonnegative
2016-04-14 16:17:59 +00:00
memcpy(buffer, mBuffers[ii].data.get() + starti.as_size_t(), size);
wxASSERT( leni <= remaining );
remaining -= leni.as_size_t();
start += leni;
buffer += size;
}
}
if (remaining > 0) {
// Very big request!
// Fall back to direct fetch
if (!buffer) {
mOverlapBuffer.Resize(len, format);
buffer = mOverlapBuffer.ptr();
}
// See comment below about casting
if (!mPTrack->GetFloats( reinterpret_cast<float*>(buffer),
start, remaining, fillZero, mayThrow))
return 0;
}
// Overlap buffer was meant for the more general support of sample formats
// besides float, which explains the cast
return reinterpret_cast<const float*>(mOverlapBuffer.ptr());
}
else {
#if 0
// Cache works only for float format.
mOverlapBuffer.Resize(len, format);
if (mPTrack->Get(mOverlapBuffer.ptr(), format, start, len, fillZero, mayThrow))
return mOverlapBuffer.ptr();
#else
// No longer handling other than float format. Therefore len is 0.
#endif
return nullptr;
}
}
void WaveTrackCache::Free()
{
mBuffers[0].Free();
mBuffers[1].Free();
mOverlapBuffer.Free();
mNValidBuffers = 0;
}
auto WaveTrack::AllClipsIterator::operator ++ () -> AllClipsIterator &
{
// The unspecified sequence is a post-order, but there is no
// promise whether sister nodes are ordered in time.
if ( !mStack.empty() ) {
auto &pair = mStack.back();
if ( ++pair.first == pair.second ) {
mStack.pop_back();
}
else
push( (*pair.first)->GetCutLines() );
}
return *this;
}
void WaveTrack::AllClipsIterator::push( WaveClipHolders &clips )
{
auto pClips = &clips;
while (!pClips->empty()) {
auto first = pClips->begin();
mStack.push_back( Pair( first, pClips->end() ) );
pClips = &(*first)->GetCutLines();
}
}
#include "SampleBlock.h"
void VisitBlocks(TrackList &tracks, BlockVisitor visitor,
SampleBlockIDSet *pIDs)
{
for (auto wt : tracks.Any< const WaveTrack >()) {
// Scan all clips within current track
for(const auto &clip : wt->GetAllClips()) {
// Scan all sample blocks within current clip
auto blocks = clip->GetSequenceBlockArray();
for (const auto &block : *blocks) {
auto &pBlock = block.sb;
if ( pBlock ) {
if ( pIDs && !pIDs->insert(pBlock->GetBlockID()).second )
continue;
if ( visitor )
visitor( *pBlock );
}
}
}
}
}
void InspectBlocks(const TrackList &tracks, BlockInspector inspector,
SampleBlockIDSet *pIDs)
{
VisitBlocks(
const_cast<TrackList &>(tracks), std::move( inspector ), pIDs );
}
#include "Project.h"
#include "SampleBlock.h"
static auto TrackFactoryFactory = []( AudacityProject &project ) {
return std::make_shared< WaveTrackFactory >(
ProjectSettings::Get( project ),
SampleBlockFactory::New( project ) );
};
static const AudacityProject::AttachedObjects::RegisteredFactory key2{
TrackFactoryFactory
};
WaveTrackFactory &WaveTrackFactory::Get( AudacityProject &project )
{
return project.AttachedObjects::Get< WaveTrackFactory >( key2 );
}
const WaveTrackFactory &WaveTrackFactory::Get( const AudacityProject &project )
{
return Get( const_cast< AudacityProject & >( project ) );
}
WaveTrackFactory &WaveTrackFactory::Reset( AudacityProject &project )
{
auto result = TrackFactoryFactory( project );
project.AttachedObjects::Assign( key2, result );
return *result;
}
void WaveTrackFactory::Destroy( AudacityProject &project )
{
project.AttachedObjects::Assign( key2, nullptr );
}