2010-01-23 19:44:49 +00:00
|
|
|
/**********************************************************************
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
2020-08-23 01:16:27 +00:00
|
|
|
*//****************************************************************/
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2020-08-23 01:16:27 +00:00
|
|
|
/*!
|
|
|
|
@class WaveTrackFactory
|
|
|
|
@brief Used to create or clone a WaveTrack, with appropriate context
|
|
|
|
from the project that will own the track.
|
|
|
|
*/
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
|
2015-04-08 16:15:30 +00:00
|
|
|
#include "WaveTrack.h"
|
2018-11-11 17:27:44 +00:00
|
|
|
|
2020-06-19 19:43:09 +00:00
|
|
|
|
2018-11-11 17:27:44 +00:00
|
|
|
|
2018-12-05 02:54:40 +00:00
|
|
|
#include "WaveClip.h"
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
#include <wx/defs.h>
|
|
|
|
#include <wx/intl.h>
|
|
|
|
#include <wx/debug.h>
|
|
|
|
|
2012-04-24 22:44:55 +00:00
|
|
|
#include <float.h>
|
2010-01-23 19:44:49 +00:00
|
|
|
#include <math.h>
|
|
|
|
#include <algorithm>
|
|
|
|
|
|
|
|
#include "float_cast.h"
|
|
|
|
|
|
|
|
#include "Envelope.h"
|
|
|
|
#include "Sequence.h"
|
|
|
|
|
2019-04-29 18:35:52 +00:00
|
|
|
#include "ProjectFileIORegistry.h"
|
2019-05-29 15:31:40 +00:00
|
|
|
#include "ProjectSettings.h"
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
#include "Prefs.h"
|
|
|
|
|
|
|
|
#include "effects/TimeWarper.h"
|
2021-05-15 18:39:26 +00:00
|
|
|
#include "prefs/QualitySettings.h"
|
2019-06-05 15:58:19 +00:00
|
|
|
#include "prefs/SpectrogramSettings.h"
|
2017-07-21 21:22:48 +00:00
|
|
|
#include "prefs/TracksPrefs.h"
|
2020-03-04 18:52:10 +00:00
|
|
|
#include "prefs/TracksBehaviorsPrefs.h"
|
2019-06-05 15:58:19 +00:00
|
|
|
#include "prefs/WaveformSettings.h"
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2016-12-25 13:40:15 +00:00
|
|
|
#include "InconsistencyException.h"
|
|
|
|
|
2020-01-15 00:58:38 +00:00
|
|
|
#include "tracks/ui/TrackView.h"
|
|
|
|
#include "tracks/ui/TrackControls.h"
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
using std::max;
|
|
|
|
|
2019-04-29 18:35:52 +00:00
|
|
|
static ProjectFileIORegistry::Entry registerFactory{
|
|
|
|
wxT( "wavetrack" ),
|
|
|
|
[]( AudacityProject &project ){
|
2020-08-22 23:44:49 +00:00
|
|
|
auto &trackFactory = WaveTrackFactory::Get( project );
|
2019-05-06 23:00:10 +00:00
|
|
|
auto &tracks = TrackList::Get( project );
|
2020-01-15 00:58:38 +00:00
|
|
|
auto result = tracks.Add(trackFactory.NewWaveTrack());
|
|
|
|
TrackView::Get( *result );
|
|
|
|
TrackControls::Get( *result );
|
|
|
|
return result;
|
2019-04-29 18:35:52 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-08-22 23:44:49 +00:00
|
|
|
WaveTrack::Holder WaveTrackFactory::DuplicateWaveTrack(const WaveTrack &orig)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2018-11-19 04:07:05 +00:00
|
|
|
return std::static_pointer_cast<WaveTrack>( orig.Duplicate() );
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-08-22 23:44:49 +00:00
|
|
|
WaveTrack::Holder WaveTrackFactory::NewWaveTrack(sampleFormat format, double rate)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2020-01-07 21:00:36 +00:00
|
|
|
if (format == (sampleFormat)0)
|
2021-05-15 18:39:26 +00:00
|
|
|
format = QualitySettings::SampleFormatChoice();
|
2020-01-07 21:00:36 +00:00
|
|
|
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 );
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
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,
|
2020-07-02 16:42:25 +00:00
|
|
|
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)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
mLegacyProjectFileOffset = 0;
|
|
|
|
|
|
|
|
mFormat = format;
|
|
|
|
mRate = (int) rate;
|
|
|
|
mGain = 1.0;
|
|
|
|
mPan = 0.0;
|
2018-08-24 21:51:31 +00:00
|
|
|
mOldGain[0] = 0.0;
|
|
|
|
mOldGain[1] = 0.0;
|
2017-11-04 17:46:31 +00:00
|
|
|
mWaveColorIndex = 0;
|
2017-07-21 21:22:48 +00:00
|
|
|
SetDefaultName(TracksPrefs::GetDefaultAudioTrackNamePreference());
|
2010-01-23 19:44:49 +00:00
|
|
|
SetName(GetDefaultName());
|
|
|
|
mDisplayMin = -1.0;
|
|
|
|
mDisplayMax = 1.0;
|
2015-09-06 22:09:16 +00:00
|
|
|
mSpectrumMin = mSpectrumMax = -1; // so values will default to settings
|
2015-06-22 00:03:15 +00:00
|
|
|
mLastScaleType = -1;
|
2015-08-27 20:06:13 +00:00
|
|
|
mLastdBRange = -1;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2016-02-08 20:59:50 +00:00
|
|
|
WaveTrack::WaveTrack(const WaveTrack &orig):
|
2017-01-08 08:36:12 +00:00
|
|
|
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 )
|
2015-06-13 16:13:55 +00:00
|
|
|
, mpSpectrumSettings(orig.mpSpectrumSettings
|
2016-08-10 05:34:44 +00:00
|
|
|
? std::make_unique<SpectrogramSettings>(*orig.mpSpectrumSettings)
|
|
|
|
: nullptr
|
|
|
|
)
|
2015-06-21 14:41:28 +00:00
|
|
|
, mpWaveformSettings(orig.mpWaveformSettings
|
2016-08-10 05:34:44 +00:00
|
|
|
? std::make_unique<WaveformSettings>(*orig.mpWaveformSettings)
|
|
|
|
: nullptr
|
|
|
|
)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2015-06-22 00:03:15 +00:00
|
|
|
mLastScaleType = -1;
|
2015-08-27 20:06:13 +00:00
|
|
|
mLastdBRange = -1;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
mLegacyProjectFileOffset = 0;
|
|
|
|
|
|
|
|
Init(orig);
|
|
|
|
|
2016-02-20 02:39:37 +00:00
|
|
|
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 ) );
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Copy the track metadata but not the contents.
|
|
|
|
void WaveTrack::Init(const WaveTrack &orig)
|
|
|
|
{
|
2017-01-08 08:36:12 +00:00
|
|
|
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;
|
2020-07-02 16:42:25 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
mFormat = orig.mFormat;
|
2017-11-04 17:46:31 +00:00
|
|
|
mWaveColorIndex = orig.mWaveColorIndex;
|
2010-01-23 19:44:49 +00:00
|
|
|
mRate = orig.mRate;
|
|
|
|
mGain = orig.mGain;
|
|
|
|
mPan = orig.mPan;
|
2018-08-24 21:19:49 +00:00
|
|
|
mOldGain[0] = 0.0;
|
|
|
|
mOldGain[1] = 0.0;
|
2010-01-23 19:44:49 +00:00
|
|
|
SetDefaultName(orig.GetDefaultName());
|
|
|
|
SetName(orig.GetName());
|
|
|
|
mDisplayMin = orig.mDisplayMin;
|
|
|
|
mDisplayMax = orig.mDisplayMax;
|
2015-09-06 22:09:16 +00:00
|
|
|
mSpectrumMin = orig.mSpectrumMin;
|
|
|
|
mSpectrumMax = orig.mSpectrumMax;
|
2016-02-20 00:09:36 +00:00
|
|
|
mDisplayLocationsCache.clear();
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
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());
|
|
|
|
}
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
void WaveTrack::Merge(const Track &orig)
|
|
|
|
{
|
2017-04-11 22:16:03 +00:00
|
|
|
orig.TypeSwitch( [&](const WaveTrack *pwt) {
|
|
|
|
const WaveTrack &wt = *pwt;
|
2015-06-13 16:13:55 +00:00
|
|
|
mGain = wt.mGain;
|
|
|
|
mPan = wt.mPan;
|
2016-07-15 12:06:45 +00:00
|
|
|
mDisplayMin = wt.mDisplayMin;
|
|
|
|
mDisplayMax = wt.mDisplayMax;
|
2015-06-13 16:13:55 +00:00
|
|
|
SetSpectrogramSettings(wt.mpSpectrumSettings
|
2016-04-08 05:56:06 +00:00
|
|
|
? std::make_unique<SpectrogramSettings>(*wt.mpSpectrumSettings) : nullptr);
|
2015-06-21 14:41:28 +00:00
|
|
|
SetWaveformSettings
|
2016-04-08 05:56:06 +00:00
|
|
|
(wt.mpWaveformSettings ? std::make_unique<WaveformSettings>(*wt.mpWaveformSettings) : nullptr);
|
2017-04-11 22:16:03 +00:00
|
|
|
});
|
2017-01-08 08:36:12 +00:00
|
|
|
PlayableTrack::Merge(orig);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
WaveTrack::~WaveTrack()
|
2014-06-03 20:30:19 +00:00
|
|
|
{
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2015-03-28 18:46:40 +00:00
|
|
|
double WaveTrack::GetOffset() const
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
return GetStartTime();
|
|
|
|
}
|
|
|
|
|
2020-08-22 19:33:03 +00:00
|
|
|
/*! @excsafety{No-fail} */
|
2010-01-23 19:44:49 +00:00
|
|
|
void WaveTrack::SetOffset(double o)
|
|
|
|
{
|
|
|
|
double delta = o - GetOffset();
|
|
|
|
|
2016-02-20 02:39:37 +00:00
|
|
|
for (const auto &clip : mClips)
|
2020-08-22 19:33:03 +00:00
|
|
|
// assume No-fail-guarantee
|
2010-01-23 19:44:49 +00:00
|
|
|
clip->SetOffset(clip->GetOffset() + delta);
|
|
|
|
|
|
|
|
mOffset = o;
|
|
|
|
}
|
|
|
|
|
2018-09-20 16:39:05 +00:00
|
|
|
auto WaveTrack::GetChannelIgnoringPan() const -> ChannelType {
|
2018-08-25 11:41:16 +00:00
|
|
|
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 );
|
|
|
|
};
|
|
|
|
|
2016-02-27 18:04:34 +00:00
|
|
|
void WaveTrack::SetLastScaleType() const
|
2015-08-26 11:24:20 +00:00
|
|
|
{
|
|
|
|
mLastScaleType = GetWaveformSettings().scaleType;
|
|
|
|
}
|
|
|
|
|
2016-02-27 18:04:34 +00:00
|
|
|
void WaveTrack::SetLastdBRange() const
|
2015-08-27 20:06:13 +00:00
|
|
|
{
|
|
|
|
mLastdBRange = GetWaveformSettings().dBRange;
|
|
|
|
}
|
|
|
|
|
2015-09-06 22:09:16 +00:00
|
|
|
void WaveTrack::GetDisplayBounds(float *min, float *max) const
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
*min = mDisplayMin;
|
|
|
|
*max = mDisplayMax;
|
|
|
|
}
|
|
|
|
|
2016-02-27 18:04:34 +00:00
|
|
|
void WaveTrack::SetDisplayBounds(float min, float max) const
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
mDisplayMin = min;
|
|
|
|
mDisplayMax = max;
|
|
|
|
}
|
|
|
|
|
2015-09-06 22:09:16 +00:00
|
|
|
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
|
2016-09-08 18:28:34 +00:00
|
|
|
const auto half = settings.GetFFTLength() / 2;
|
2015-09-06 22:09:16 +00:00
|
|
|
// 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));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-27 18:04:34 +00:00
|
|
|
void WaveTrack::SetSpectrumBounds(float min, float max) const
|
2015-09-06 22:09:16 +00:00
|
|
|
{
|
|
|
|
mSpectrumMin = min;
|
|
|
|
mSpectrumMax = max;
|
|
|
|
}
|
|
|
|
|
2016-10-28 15:56:15 +00:00
|
|
|
int WaveTrack::ZeroLevelYCoordinate(wxRect rect) const
|
|
|
|
{
|
|
|
|
return rect.GetTop() +
|
|
|
|
(int)((mDisplayMax / (mDisplayMax - mDisplayMin)) * rect.height);
|
|
|
|
}
|
|
|
|
|
2020-09-09 11:35:58 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2021-01-29 05:25:33 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2020-09-09 11:35:58 +00:00
|
|
|
auto WaveTrack::GetIntervals() const -> ConstIntervals
|
|
|
|
{
|
|
|
|
return MakeIntervals<ConstIntervals>( mClips );
|
|
|
|
}
|
|
|
|
|
|
|
|
auto WaveTrack::GetIntervals() -> Intervals
|
|
|
|
{
|
|
|
|
return MakeIntervals<Intervals>( mClips );
|
|
|
|
}
|
|
|
|
|
2018-11-19 01:15:26 +00:00
|
|
|
Track::Holder WaveTrack::Clone() const
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2018-11-19 04:07:05 +00:00
|
|
|
return std::make_shared<WaveTrack>( *this );
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
double WaveTrack::GetRate() const
|
|
|
|
{
|
|
|
|
return mRate;
|
|
|
|
}
|
|
|
|
|
|
|
|
void WaveTrack::SetRate(double newRate)
|
|
|
|
{
|
2017-05-04 12:32:23 +00:00
|
|
|
wxASSERT( newRate > 0 );
|
|
|
|
newRate = std::max( 1.0, newRate );
|
|
|
|
auto ratio = mRate / newRate;
|
2010-01-23 19:44:49 +00:00
|
|
|
mRate = (int) newRate;
|
2017-05-04 12:32:23 +00:00
|
|
|
for (const auto &clip : mClips) {
|
2016-02-20 02:39:37 +00:00
|
|
|
clip->SetRate((int)newRate);
|
2017-05-04 12:32:23 +00:00
|
|
|
clip->SetOffset( clip->GetOffset() * ratio );
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
float WaveTrack::GetGain() const
|
|
|
|
{
|
|
|
|
return mGain;
|
|
|
|
}
|
|
|
|
|
|
|
|
void WaveTrack::SetGain(float newGain)
|
|
|
|
{
|
2018-02-05 22:56:46 +00:00
|
|
|
if (mGain != newGain) {
|
|
|
|
mGain = newGain;
|
|
|
|
Notify();
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
float WaveTrack::GetPan() const
|
|
|
|
{
|
|
|
|
return mPan;
|
|
|
|
}
|
|
|
|
|
|
|
|
void WaveTrack::SetPan(float newPan)
|
|
|
|
{
|
|
|
|
if (newPan > 1.0)
|
2018-02-05 22:56:46 +00:00
|
|
|
newPan = 1.0;
|
2010-01-23 19:44:49 +00:00
|
|
|
else if (newPan < -1.0)
|
2018-02-05 22:56:46 +00:00
|
|
|
newPan = -1.0;
|
|
|
|
|
|
|
|
if ( mPan != newPan ) {
|
2010-01-23 19:44:49 +00:00
|
|
|
mPan = newPan;
|
2018-02-05 22:56:46 +00:00
|
|
|
Notify();
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2015-03-28 18:46:40 +00:00
|
|
|
float WaveTrack::GetChannelGain(int channel) const
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2018-08-24 21:19:49 +00:00
|
|
|
float WaveTrack::GetOldChannelGain(int channel) const
|
|
|
|
{
|
|
|
|
return mOldGain[channel%2];
|
|
|
|
}
|
|
|
|
|
|
|
|
void WaveTrack::SetOldChannelGain(int channel, float gain)
|
|
|
|
{
|
|
|
|
mOldGain[channel % 2] = gain;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2020-08-22 19:33:03 +00:00
|
|
|
/*! @excsafety{Strong} */
|
2017-11-04 17:46:31 +00:00
|
|
|
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;
|
|
|
|
}
|
2017-11-04 17:46:31 +00:00
|
|
|
|
2020-08-22 19:33:03 +00:00
|
|
|
/*! @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)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2020-08-28 19:07:04 +00:00
|
|
|
for (const auto& clip : mClips)
|
|
|
|
clip->ConvertToSampleFormat(format, progressReport);
|
2010-01-23 19:44:49 +00:00
|
|
|
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
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2017-11-19 03:18:49 +00:00
|
|
|
if (t0 > t1)
|
|
|
|
return true;
|
|
|
|
|
2017-10-09 04:37:10 +00:00
|
|
|
//wxPrintf("Searching for overlap in %.6f...%.6f\n", t0, t1);
|
2016-02-20 02:39:37 +00:00
|
|
|
for (const auto &clip : mClips)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2010-04-15 21:01:50 +00:00
|
|
|
if (!clip->BeforeClip(t1) && !clip->AfterClip(t0)) {
|
2017-10-09 04:37:10 +00:00
|
|
|
//wxPrintf("Overlapping clip: %.6f...%.6f\n",
|
2010-01-23 19:44:49 +00:00
|
|
|
// 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");
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
// Otherwise, no clips overlap this region
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2016-03-02 20:36:44 +00:00
|
|
|
Track::Holder WaveTrack::Cut(double t0, double t1)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
if (t1 < t0)
|
2016-11-07 19:27:31 +00:00
|
|
|
THROW_INCONSISTENCY_EXCEPTION;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2016-03-02 20:36:44 +00:00
|
|
|
auto tmp = Copy(t0, t1);
|
|
|
|
|
2017-03-23 15:10:14 +00:00
|
|
|
Clear(t0, t1);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2016-09-15 11:39:46 +00:00
|
|
|
return tmp;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2020-08-22 19:33:03 +00:00
|
|
|
/*! @excsafety{Strong} */
|
2016-03-02 20:36:44 +00:00
|
|
|
Track::Holder WaveTrack::SplitCut(double t0, double t1)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
if (t1 < t0)
|
2016-11-07 19:27:31 +00:00
|
|
|
THROW_INCONSISTENCY_EXCEPTION;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
// SplitCut is the same as 'Copy', then 'SplitDelete'
|
2016-03-02 20:36:44 +00:00
|
|
|
auto tmp = Copy(t0, t1);
|
2017-03-31 19:00:16 +00:00
|
|
|
|
2017-03-22 17:25:55 +00:00
|
|
|
SplitDelete(t0, t1);
|
2016-03-02 20:36:44 +00:00
|
|
|
|
2016-09-15 11:39:46 +00:00
|
|
|
return tmp;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2016-02-26 15:39:39 +00:00
|
|
|
#if 0
|
2016-03-02 20:36:44 +00:00
|
|
|
Track::Holder WaveTrack::CutAndAddCutLine(double t0, double t1)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
if (t1 < t0)
|
2016-11-07 19:27:31 +00:00
|
|
|
THROW_INCONSISTENCY_EXCEPTION;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
// Cut is the same as 'Copy', then 'Delete'
|
2016-03-02 20:36:44 +00:00
|
|
|
auto tmp = Copy(t0, t1);
|
2017-03-31 19:00:16 +00:00
|
|
|
|
2016-11-07 19:27:31 +00:00
|
|
|
ClearAndAddCutLine(t0, t1);
|
2016-03-02 20:36:44 +00:00
|
|
|
|
2016-09-15 11:39:46 +00:00
|
|
|
return tmp;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2016-02-26 15:39:39 +00:00
|
|
|
#endif
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//Trim trims within a clip, rather than trimming everything.
|
|
|
|
//If a bound is outside a clip, it trims everything.
|
2020-08-22 19:33:03 +00:00
|
|
|
/*! @excsafety{Weak} */
|
2017-03-22 17:25:55 +00:00
|
|
|
void WaveTrack::Trim (double t0, double t1)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
bool inside0 = false;
|
|
|
|
bool inside1 = false;
|
|
|
|
//Keeps track of the offset of the first clip greater than
|
|
|
|
// the left selection t0.
|
|
|
|
double firstGreaterOffset = -1;
|
|
|
|
|
2016-02-20 02:39:37 +00:00
|
|
|
for (const auto &clip : mClips)
|
2010-02-16 20:50:38 +00:00
|
|
|
{
|
|
|
|
//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 &&
|
2010-01-23 19:44:49 +00:00
|
|
|
clip->GetStartTime() >= t0)
|
2010-02-16 20:50:38 +00:00
|
|
|
firstGreaterOffset = clip->GetStartTime();
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2010-02-16 20:50:38 +00:00
|
|
|
if(t1 > clip->GetStartTime() && t1 < clip->GetEndTime())
|
|
|
|
{
|
2017-03-22 18:55:07 +00:00
|
|
|
clip->Clear(t1,clip->GetEndTime());
|
2010-02-16 20:50:38 +00:00
|
|
|
inside1 = true;
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2010-02-16 20:50:38 +00:00
|
|
|
if(t0 > clip->GetStartTime() && t0 < clip->GetEndTime())
|
|
|
|
{
|
2017-03-22 18:55:07 +00:00
|
|
|
clip->Clear(clip->GetStartTime(),t0);
|
2010-02-16 20:50:38 +00:00
|
|
|
clip->SetOffset(t0);
|
|
|
|
inside0 = true;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2010-02-16 20:50:38 +00:00
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
//if inside0 is false, then the left selector was between
|
2016-02-14 23:50:45 +00:00
|
|
|
//clips, so DELETE everything to its left.
|
2017-04-29 19:39:41 +00:00
|
|
|
if(!inside1 && t1 < GetEndTime())
|
2017-03-23 15:10:14 +00:00
|
|
|
Clear(t1,GetEndTime());
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2017-11-19 03:18:49 +00:00
|
|
|
if(!inside0 && t0 > GetStartTime())
|
|
|
|
SplitDelete(GetStartTime(), t0);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2020-05-18 11:53:01 +00:00
|
|
|
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
|
Bug2346: Complete fix...
... without making undesirable dependency cycles.
Eliminate calls to NewWaveTrack in effects, but in Edit>Copy too, which was
not mentioned in the bug report. (Copying a track, deselecting all, and pasting
preserved CLIP colors, but not the TRACK color setting which applies to newly
generated clips.)
Instead, always use the new function WaveTrack::EmptyCopy from the track to be
later replaced, getting color information.
NewWaveTrack is still used in benchmark test, import, the Track menu
commands that make new tracks, recording to new tracks, and generators without
a selection, where there is no track to copy from.
Also when deserializing tracks from the .aup file, in which case the saved
color is later retrieved from the file.
Also, in mix-and-render, where other logic decides whether to copy colors
afterward.
See commit a9658e6ef7f7eaefce4dc37a93d389cca7705f41
2020-03-11 01:40:14 +00:00
|
|
|
{
|
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 );
|
Bug2346: Complete fix...
... without making undesirable dependency cycles.
Eliminate calls to NewWaveTrack in effects, but in Edit>Copy too, which was
not mentioned in the bug report. (Copying a track, deselecting all, and pasting
preserved CLIP colors, but not the TRACK color setting which applies to newly
generated clips.)
Instead, always use the new function WaveTrack::EmptyCopy from the track to be
later replaced, getting color information.
NewWaveTrack is still used in benchmark test, import, the Track menu
commands that make new tracks, recording to new tracks, and generators without
a selection, where there is no track to copy from.
Also when deserializing tracks from the .aup file, in which case the saved
color is later retrieved from the file.
Also, in mix-and-render, where other logic decides whether to copy colors
afterward.
See commit a9658e6ef7f7eaefce4dc37a93d389cca7705f41
2020-03-11 01:40:14 +00:00
|
|
|
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;
|
Bug2346: Complete fix...
... without making undesirable dependency cycles.
Eliminate calls to NewWaveTrack in effects, but in Edit>Copy too, which was
not mentioned in the bug report. (Copying a track, deselecting all, and pasting
preserved CLIP colors, but not the TRACK color setting which applies to newly
generated clips.)
Instead, always use the new function WaveTrack::EmptyCopy from the track to be
later replaced, getting color information.
NewWaveTrack is still used in benchmark test, import, the Track menu
commands that make new tracks, recording to new tracks, and generators without
a selection, where there is no track to copy from.
Also when deserializing tracks from the .aup file, in which case the saved
color is later retrieved from the file.
Also, in mix-and-render, where other logic decides whether to copy colors
afterward.
See commit a9658e6ef7f7eaefce4dc37a93d389cca7705f41
2020-03-11 01:40:14 +00:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2016-11-26 13:48:08 +00:00
|
|
|
Track::Holder WaveTrack::Copy(double t0, double t1, bool forClipboard) const
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2017-04-30 01:41:38 +00:00
|
|
|
if (t1 < t0)
|
2016-11-07 19:27:31 +00:00
|
|
|
THROW_INCONSISTENCY_EXCEPTION;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
Bug2346: Complete fix...
... without making undesirable dependency cycles.
Eliminate calls to NewWaveTrack in effects, but in Edit>Copy too, which was
not mentioned in the bug report. (Copying a track, deselecting all, and pasting
preserved CLIP colors, but not the TRACK color setting which applies to newly
generated clips.)
Instead, always use the new function WaveTrack::EmptyCopy from the track to be
later replaced, getting color information.
NewWaveTrack is still used in benchmark test, import, the Track menu
commands that make new tracks, recording to new tracks, and generators without
a selection, where there is no track to copy from.
Also when deserializing tracks from the .aup file, in which case the saved
color is later retrieved from the file.
Also, in mix-and-render, where other logic decides whether to copy colors
afterward.
See commit a9658e6ef7f7eaefce4dc37a93d389cca7705f41
2020-03-11 01:40:14 +00:00
|
|
|
auto result = EmptyCopy();
|
2018-11-19 04:07:05 +00:00
|
|
|
WaveTrack *newTrack = result.get();
|
2010-01-23 19:44:49 +00:00
|
|
|
|
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.
|
|
|
|
|
2016-02-20 02:39:37 +00:00
|
|
|
for (const auto &clip : mClips)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
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
|
|
|
|
2016-02-20 23:43:08 +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));
|
2016-02-20 23:43:08 +00:00
|
|
|
WaveClip *const newClip = newTrack->mClips.back().get();
|
2010-01-23 19:44:49 +00:00
|
|
|
newClip->Offset(-t0);
|
2016-02-20 23:43:08 +00:00
|
|
|
}
|
2016-11-26 16:47:45 +00:00
|
|
|
else if (t1 > clip->GetStartTime() && t0 < clip->GetEndTime())
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
// 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
|
|
|
|
2016-11-26 16:47:45 +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);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2017-10-09 04:37:10 +00:00
|
|
|
//wxPrintf("copy: clip_t0=%f, clip_t1=%f\n", clip_t0, clip_t1);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
newClip->Offset(-t0);
|
|
|
|
if (newClip->GetOffset() < 0)
|
|
|
|
newClip->SetOffset(0);
|
|
|
|
|
2016-11-26 16:47:45 +00:00
|
|
|
newTrack->mClips.push_back(std::move(newClip)); // transfer ownership
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
// AWD, Oct 2009: If the selection ends in whitespace, create a placeholder
|
|
|
|
// clip representing that whitespace
|
2016-11-26 13:48:08 +00:00
|
|
|
// 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)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
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,
|
2016-02-20 23:43:08 +00:00
|
|
|
newTrack->GetSampleFormat(),
|
2017-11-04 17:46:31 +00:00
|
|
|
static_cast<int>(newTrack->GetRate()),
|
|
|
|
0 /*colourindex*/);
|
2010-01-23 19:44:49 +00:00
|
|
|
placeholder->SetIsPlaceholder(true);
|
2017-03-22 18:55:07 +00:00
|
|
|
placeholder->InsertSilence(0, (t1 - t0) - newTrack->GetEndTime());
|
|
|
|
placeholder->Offset(newTrack->GetEndTime());
|
|
|
|
newTrack->mClips.push_back(std::move(placeholder)); // transfer ownership
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2016-09-15 11:39:46 +00:00
|
|
|
return result;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2016-03-02 20:36:44 +00:00
|
|
|
Track::Holder WaveTrack::CopyNonconst(double t0, double t1)
|
2016-02-27 22:29:21 +00:00
|
|
|
{
|
2016-03-02 20:36:44 +00:00
|
|
|
return Copy(t0, t1);
|
2016-02-27 22:29:21 +00:00
|
|
|
}
|
|
|
|
|
2020-08-22 19:33:03 +00:00
|
|
|
/*! @excsafety{Strong} */
|
2017-03-23 15:10:14 +00:00
|
|
|
void WaveTrack::Clear(double t0, double t1)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2017-03-23 15:10:14 +00:00
|
|
|
HandleClear(t0, t1, false, false);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2020-08-22 19:33:03 +00:00
|
|
|
/*! @excsafety{Strong} */
|
2017-03-22 17:25:55 +00:00
|
|
|
void WaveTrack::ClearAndAddCutLine(double t0, double t1)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2017-03-22 17:25:55 +00:00
|
|
|
HandleClear(t0, t1, true, false);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2015-06-13 16:13:55 +00:00
|
|
|
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 =
|
2016-04-08 05:56:06 +00:00
|
|
|
std::make_unique<SpectrogramSettings>(SpectrogramSettings::defaults());
|
2015-06-13 16:13:55 +00:00
|
|
|
return *mpSpectrumSettings;
|
|
|
|
}
|
|
|
|
|
2016-04-08 05:56:06 +00:00
|
|
|
void WaveTrack::SetSpectrogramSettings(std::unique_ptr<SpectrogramSettings> &&pSettings)
|
2015-06-13 16:13:55 +00:00
|
|
|
{
|
|
|
|
if (mpSpectrumSettings != pSettings) {
|
2016-04-08 05:56:06 +00:00
|
|
|
mpSpectrumSettings = std::move(pSettings);
|
2015-06-13 16:13:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-18 16:55:26 +00:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2015-06-21 14:41:28 +00:00
|
|
|
const WaveformSettings &WaveTrack::GetWaveformSettings() const
|
|
|
|
{
|
2020-10-10 15:14:20 +00:00
|
|
|
// Create on demand
|
|
|
|
return const_cast<WaveTrack*>(this)->GetWaveformSettings();
|
2015-06-21 14:41:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
WaveformSettings &WaveTrack::GetWaveformSettings()
|
|
|
|
{
|
2020-10-10 15:14:20 +00:00
|
|
|
// Create on demand
|
2015-06-21 14:41:28 +00:00
|
|
|
if (!mpWaveformSettings)
|
2016-04-08 05:56:06 +00:00
|
|
|
mpWaveformSettings = std::make_unique<WaveformSettings>(WaveformSettings::defaults());
|
2015-06-21 14:41:28 +00:00
|
|
|
return *mpWaveformSettings;
|
|
|
|
}
|
|
|
|
|
2016-04-08 05:56:06 +00:00
|
|
|
void WaveTrack::SetWaveformSettings(std::unique_ptr<WaveformSettings> &&pSettings)
|
2015-06-21 14:41:28 +00:00
|
|
|
{
|
|
|
|
if (mpWaveformSettings != pSettings) {
|
2016-04-08 05:56:06 +00:00
|
|
|
mpWaveformSettings = std::move(pSettings);
|
2015-06-21 14:41:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
//
|
|
|
|
// ClearAndPaste() is a specialized version of HandleClear()
|
2010-08-08 00:26:07 +00:00
|
|
|
// followed by Paste() and is used mostly by effects that
|
2010-01-23 19:44:49 +00:00
|
|
|
// 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
|
2010-01-23 19:44:49 +00:00
|
|
|
// cleared range, but, in most cases, effects want to preserve
|
|
|
|
// the existing cut/split lines, so they are saved before the
|
2010-08-08 00:26:07 +00:00
|
|
|
// HandleClear()/Paste() and restored after.
|
2010-01-23 19:44:49 +00:00
|
|
|
//
|
|
|
|
// 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.
|
|
|
|
//
|
2020-08-22 19:33:03 +00:00
|
|
|
/*! @excsafety{Weak} -- This WaveTrack remains destructible in case of AudacityException.
|
|
|
|
But some of its cutline clips may have been destroyed. */
|
2017-03-22 17:25:55 +00:00
|
|
|
void WaveTrack::ClearAndPaste(double t0, // Start of time to clear
|
2010-01-23 19:44:49 +00:00
|
|
|
double t1, // End of time to clear
|
2016-03-02 17:47:52 +00:00
|
|
|
const Track *src, // What to paste
|
2010-01-23 19:44:49 +00:00
|
|
|
bool preserve, // Whether to reinsert splits/cuts
|
|
|
|
bool merge, // Whether to remove 'extra' splits
|
2016-12-16 17:37:15 +00:00
|
|
|
const TimeWarper *effectWarper // How does time change
|
2010-01-23 19:44:49 +00:00
|
|
|
)
|
|
|
|
{
|
2017-04-02 14:42:22 +00:00
|
|
|
double dur = std::min(t1 - t0, src->GetEndTime());
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
// If duration is 0, then it's just a plain paste
|
|
|
|
if (dur == 0.0) {
|
2020-08-22 19:33:03 +00:00
|
|
|
// use Weak-guarantee
|
2017-03-23 15:10:14 +00:00
|
|
|
Paste(t0, src);
|
2017-03-22 17:25:55 +00:00
|
|
|
return;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2020-08-09 00:40:02 +00:00
|
|
|
std::vector<EnvPoint> envPoints;
|
2018-02-02 19:49:46 +00:00
|
|
|
std::vector<double> splits;
|
2017-04-02 14:42:22 +00:00
|
|
|
WaveClipHolders cuts;
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
// If provided time warper was NULL, use a default one that does nothing
|
2016-02-01 01:39:24 +00:00
|
|
|
IdentityTimeWarper localWarper;
|
2016-12-16 17:37:15 +00:00
|
|
|
const TimeWarper *warper = (effectWarper ? effectWarper : &localWarper);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
// Align to a sample
|
|
|
|
t0 = LongSamplesToTime(TimeToLongSamples(t0));
|
|
|
|
t1 = LongSamplesToTime(TimeToLongSamples(t1));
|
|
|
|
|
|
|
|
// Save the cut/split lines whether preserving or not since merging
|
2010-08-08 00:26:07 +00:00
|
|
|
// needs to know if a clip boundary is being crossed since Paste()
|
2010-01-23 19:44:49 +00:00
|
|
|
// will add split lines around the pasted clip if so.
|
2016-02-20 02:39:37 +00:00
|
|
|
for (const auto &clip : mClips) {
|
2010-01-23 19:44:49 +00:00
|
|
|
double st;
|
|
|
|
|
|
|
|
// Remember clip boundaries as locations to split
|
|
|
|
st = LongSamplesToTime(TimeToLongSamples(clip->GetStartTime()));
|
2018-02-02 19:49:46 +00:00
|
|
|
if (st >= t0 && st <= t1 && !make_iterator_range(splits).contains(st)) {
|
|
|
|
splits.push_back(st);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
st = LongSamplesToTime(TimeToLongSamples(clip->GetEndTime()));
|
2018-02-02 19:49:46 +00:00
|
|
|
if (st >= t0 && st <= t1 && !make_iterator_range(splits).contains(st)) {
|
|
|
|
splits.push_back(st);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Search for cut lines
|
2016-08-11 22:56:32 +00:00
|
|
|
auto &cutlines = clip->GetCutLines();
|
2016-02-20 02:39:37 +00:00
|
|
|
// May erase from cutlines, so don't use range-for
|
2016-08-11 22:56:32 +00:00
|
|
|
for (auto it = cutlines.begin(); it != cutlines.end(); ) {
|
2016-02-20 23:43:08 +00:00
|
|
|
WaveClip *cut = it->get();
|
2010-01-23 19:44:49 +00:00
|
|
|
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);
|
2016-02-20 23:43:08 +00:00
|
|
|
cuts.push_back(std::move(*it)); // transfer ownership!
|
|
|
|
it = cutlines.erase(it);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2016-02-20 02:39:37 +00:00
|
|
|
else
|
|
|
|
++it;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2020-08-09 00:40:02 +00:00
|
|
|
|
|
|
|
// 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]);
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2016-12-04 19:16:03 +00:00
|
|
|
const auto tolerance = 2.0 / GetRate();
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
// Now, clear the selection
|
2017-03-22 17:25:55 +00:00
|
|
|
HandleClear(t0, t1, false, false);
|
|
|
|
{
|
2016-02-13 15:43:16 +00:00
|
|
|
// And paste in the NEW data
|
2017-03-23 15:10:14 +00:00
|
|
|
Paste(t0, src);
|
|
|
|
{
|
2016-02-13 15:43:16 +00:00
|
|
|
// First, merge the NEW clip(s) in with the existing clips
|
2018-02-02 19:49:46 +00:00
|
|
|
if (merge && splits.size() > 0)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
// Now t1 represents the absolute end of the pasted data.
|
|
|
|
t1 = t0 + src->GetEndTime();
|
|
|
|
|
|
|
|
// Get a sorted array of the clips
|
2016-08-11 17:06:24 +00:00
|
|
|
auto clips = SortedClipArray();
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
// Scan the sorted clips for the first clip whose start time
|
|
|
|
// exceeds the pasted regions end time.
|
2016-08-11 17:06:24 +00:00
|
|
|
{
|
|
|
|
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.
|
2016-12-04 19:16:03 +00:00
|
|
|
if (fabs(t1 - clip->GetStartTime()) < tolerance) {
|
2017-03-22 17:25:55 +00:00
|
|
|
if (prev)
|
|
|
|
MergeClips(GetClipIndex(prev), GetClipIndex(clip));
|
2016-08-11 17:06:24 +00:00
|
|
|
break;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2016-08-11 17:06:24 +00:00
|
|
|
prev = clip;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
2017-04-02 14:42:22 +00:00
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2017-04-02 14:42:22 +00:00
|
|
|
// Refill the array since clips have changed.
|
|
|
|
auto clips = SortedClipArray();
|
2016-08-11 17:06:24 +00:00
|
|
|
|
2017-04-02 14:42:22 +00:00
|
|
|
{
|
|
|
|
// 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.
|
2020-08-22 19:33:03 +00:00
|
|
|
// use Weak-guarantee
|
2017-04-02 14:42:22 +00:00
|
|
|
MergeClips(GetClipIndex(prev), GetClipIndex(clip));
|
|
|
|
break;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2017-04-02 14:42:22 +00:00
|
|
|
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;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
2017-04-02 14:42:22 +00:00
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2017-04-02 14:42:22 +00:00
|
|
|
// Restore cut/split lines
|
|
|
|
if (preserve) {
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2017-04-02 14:42:22 +00:00
|
|
|
// Restore the split lines, transforming the position appropriately
|
|
|
|
for (const auto split: splits) {
|
|
|
|
SplitAt(warper->Warp(split));
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2017-04-02 14:42:22 +00:00
|
|
|
// 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);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2017-04-02 14:42:22 +00:00
|
|
|
else
|
|
|
|
++it;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-08-09 00:40:02 +00:00
|
|
|
|
|
|
|
// Restore the envelope points
|
|
|
|
for (auto point : envPoints) {
|
|
|
|
auto t = warper->Warp(point.GetT());
|
2021-02-26 13:04:27 +00:00
|
|
|
if (auto clip = GetClipAtTime(t))
|
|
|
|
clip->GetEnvelope()->Insert(t, point.GetVal());
|
2020-08-09 00:40:02 +00:00
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-22 19:33:03 +00:00
|
|
|
/*! @excsafety{Strong} */
|
2017-03-22 17:25:55 +00:00
|
|
|
void WaveTrack::SplitDelete(double t0, double t1)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
bool addCutLines = false;
|
|
|
|
bool split = true;
|
2017-03-22 17:25:55 +00:00
|
|
|
HandleClear(t0, t1, addCutLines, split);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2016-02-20 02:39:37 +00:00
|
|
|
namespace
|
|
|
|
{
|
2016-08-11 17:06:24 +00:00
|
|
|
WaveClipHolders::const_iterator
|
|
|
|
FindClip(const WaveClipHolders &list, const WaveClip *clip, int *distance = nullptr)
|
2016-02-20 02:39:37 +00:00
|
|
|
{
|
|
|
|
if (distance)
|
|
|
|
*distance = 0;
|
|
|
|
auto it = list.begin();
|
|
|
|
for (const auto end = list.end(); it != end; ++it)
|
|
|
|
{
|
2016-02-20 23:43:08 +00:00
|
|
|
if (it->get() == clip)
|
2016-02-20 02:39:37 +00:00
|
|
|
break;
|
|
|
|
if (distance)
|
|
|
|
++*distance;
|
|
|
|
}
|
|
|
|
return it;
|
|
|
|
}
|
|
|
|
|
2016-08-11 17:06:24 +00:00
|
|
|
WaveClipHolders::iterator
|
|
|
|
FindClip(WaveClipHolders &list, const WaveClip *clip, int *distance = nullptr)
|
2016-02-20 02:39:37 +00:00
|
|
|
{
|
|
|
|
if (distance)
|
|
|
|
*distance = 0;
|
|
|
|
auto it = list.begin();
|
|
|
|
for (const auto end = list.end(); it != end; ++it)
|
|
|
|
{
|
2016-02-20 23:43:08 +00:00
|
|
|
if (it->get() == clip)
|
2016-02-20 02:39:37 +00:00
|
|
|
break;
|
|
|
|
if (distance)
|
|
|
|
++*distance;
|
|
|
|
}
|
|
|
|
return it;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-09 18:09:33 +00:00
|
|
|
std::shared_ptr<WaveClip> WaveTrack::RemoveAndReturnClip(WaveClip* clip)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2016-02-20 23:43:08 +00:00
|
|
|
// Be clear about who owns the clip!!
|
2016-02-20 02:39:37 +00:00
|
|
|
auto it = FindClip(mClips, clip);
|
2016-02-20 23:43:08 +00:00
|
|
|
if (it != mClips.end()) {
|
|
|
|
auto result = std::move(*it); // Array stops owning the clip, before we shrink it
|
2016-02-20 02:39:37 +00:00
|
|
|
mClips.erase(it);
|
2016-02-20 23:43:08 +00:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
return {};
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2020-09-16 06:01:48 +00:00
|
|
|
bool WaveTrack::AddClip(const std::shared_ptr<WaveClip> &clip)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2020-09-16 06:01:48 +00:00
|
|
|
if (clip->GetSequence()->GetFactory() != this->mpFactory)
|
|
|
|
return false;
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
// Uncomment the following line after we correct the problem of zero-length clips
|
|
|
|
//if (CanInsertClip(clip))
|
2020-09-16 06:01:48 +00:00
|
|
|
mClips.push_back(clip); // transfer ownership
|
|
|
|
|
|
|
|
return true;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2020-08-22 19:33:03 +00:00
|
|
|
/*! @excsafety{Strong} */
|
2017-03-22 17:25:55 +00:00
|
|
|
void WaveTrack::HandleClear(double t0, double t1,
|
2010-01-23 19:44:49 +00:00
|
|
|
bool addCutLines, bool split)
|
|
|
|
{
|
2021-05-15 10:35:39 +00:00
|
|
|
// For debugging, use an ASSERT so that we stop
|
|
|
|
// closer to the problem.
|
|
|
|
wxASSERT( t1 >= t0 );
|
2010-01-23 19:44:49 +00:00
|
|
|
if (t1 < t0)
|
2016-11-07 19:27:31 +00:00
|
|
|
THROW_INCONSISTENCY_EXCEPTION;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2020-03-04 18:52:10 +00:00
|
|
|
bool editClipCanMove = GetEditClipsCanMove();
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2016-08-11 17:06:24 +00:00
|
|
|
WaveClipPointers clipsToDelete;
|
|
|
|
WaveClipHolders clipsToAdd;
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +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)
|
|
|
|
{
|
2016-02-20 02:39:37 +00:00
|
|
|
for (const auto &clip : mClips)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
if (!clip->BeforeClip(t1) && !clip->AfterClip(t0) &&
|
|
|
|
(clip->BeforeClip(t0) || clip->AfterClip(t1)))
|
|
|
|
{
|
|
|
|
addCutLines = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2014-06-03 20:30:19 +00:00
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2016-02-20 02:39:37 +00:00
|
|
|
for (const auto &clip : mClips)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
if (clip->BeforeClip(t0) && clip->AfterClip(t1))
|
|
|
|
{
|
|
|
|
// Whole clip must be deleted - remember this
|
2016-02-20 23:43:08 +00:00
|
|
|
clipsToDelete.push_back(clip.get());
|
|
|
|
}
|
|
|
|
else if (!clip->BeforeClip(t1) && !clip->AfterClip(t0))
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
// Clip data is affected by command
|
|
|
|
if (addCutLines)
|
|
|
|
{
|
2017-04-02 02:20:38 +00:00
|
|
|
// 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 );
|
2017-04-02 02:20:38 +00:00
|
|
|
newClip->ClearAndAddCutLine( t0, t1 );
|
|
|
|
clipsToAdd.push_back( std::move( newClip ) );
|
2016-11-26 20:20:28 +00:00
|
|
|
}
|
|
|
|
else
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
if (split) {
|
|
|
|
// Three cases:
|
|
|
|
|
|
|
|
if (clip->BeforeClip(t0)) {
|
|
|
|
// Delete from the left edge
|
2017-04-02 02:20:38 +00:00
|
|
|
|
|
|
|
// 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 );
|
2017-04-02 02:20:38 +00:00
|
|
|
newClip->Clear(clip->GetStartTime(), t1);
|
|
|
|
newClip->Offset(t1-clip->GetStartTime());
|
|
|
|
|
|
|
|
clipsToAdd.push_back( std::move( newClip ) );
|
|
|
|
}
|
|
|
|
else if (clip->AfterClip(t1)) {
|
2010-01-23 19:44:49 +00:00
|
|
|
// Delete to right edge
|
2017-04-02 02:20:38 +00:00
|
|
|
|
|
|
|
// 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 );
|
2017-04-02 02:20:38 +00:00
|
|
|
newClip->Clear(t0, clip->GetEndTime());
|
|
|
|
|
|
|
|
clipsToAdd.push_back( std::move( newClip ) );
|
|
|
|
}
|
|
|
|
else {
|
2010-01-23 19:44:49 +00:00
|
|
|
// Delete in the middle of the clip...we actually create two
|
2016-02-13 15:43:16 +00:00
|
|
|
// NEW clips out of the left and right halves...
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2016-02-20 23:43:08 +00:00
|
|
|
// 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 ) );
|
2016-02-20 23:43:08 +00:00
|
|
|
clipsToAdd.back()->Clear(t0, clip->GetEndTime());
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2016-02-20 23:43:08 +00:00
|
|
|
// 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 ) );
|
2016-02-20 23:43:08 +00:00
|
|
|
WaveClip *const right = clipsToAdd.back().get();
|
2010-01-23 19:44:49 +00:00
|
|
|
right->Clear(clip->GetStartTime(), t1);
|
2016-02-20 23:43:08 +00:00
|
|
|
right->Offset(t1 - clip->GetStartTime());
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2016-02-20 23:43:08 +00:00
|
|
|
clipsToDelete.push_back(clip.get());
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
2017-04-02 02:20:38 +00:00
|
|
|
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 );
|
2017-04-02 02:20:38 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
// clip->Clear keeps points < t0 and >= t1 via Envelope::CollapseRegion
|
2017-03-22 18:55:07 +00:00
|
|
|
newClip->Clear(t0,t1);
|
2017-04-02 02:20:38 +00:00
|
|
|
|
|
|
|
clipsToAdd.push_back( std::move( newClip ) );
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
2017-04-02 02:20:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Only now, change the contents of this track
|
2020-08-22 19:33:03 +00:00
|
|
|
// use No-fail-guarantee for the rest
|
2017-04-02 02:20:38 +00:00
|
|
|
|
|
|
|
for (const auto &clip : mClips)
|
|
|
|
{
|
2010-02-10 18:55:45 +00:00
|
|
|
if (clip->BeforeClip(t1))
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
// 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));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-20 02:39:37 +00:00
|
|
|
for (const auto &clip: clipsToDelete)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2016-02-20 02:39:37 +00:00
|
|
|
auto myIt = FindClip(mClips, clip);
|
|
|
|
if (myIt != mClips.end())
|
2016-02-20 23:43:08 +00:00
|
|
|
mClips.erase(myIt); // deletes the clip!
|
2016-02-20 02:39:37 +00:00
|
|
|
else
|
|
|
|
wxASSERT(false);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2016-02-20 23:43:08 +00:00
|
|
|
for (auto &clip: clipsToAdd)
|
|
|
|
mClips.push_back(std::move(clip)); // transfer ownership
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2017-03-23 15:10:14 +00:00
|
|
|
void WaveTrack::SyncLockAdjust(double oldT1, double newT1)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2010-02-16 20:50:38 +00:00
|
|
|
if (newT1 > oldT1) {
|
|
|
|
// Insert space within the track
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2013-09-18 20:55:04 +00:00
|
|
|
// 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())
|
2017-03-23 15:10:14 +00:00
|
|
|
return;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2010-02-16 20:50:38 +00:00
|
|
|
// 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) {
|
2016-03-02 20:36:44 +00:00
|
|
|
auto tmp = Cut (oldT1, GetEndTime() + 1.0/GetRate());
|
2010-02-16 20:50:38 +00:00
|
|
|
|
2017-03-23 15:10:14 +00:00
|
|
|
Paste(newT1, tmp.get());
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2017-03-23 15:10:14 +00:00
|
|
|
return;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2010-02-16 20:50:38 +00:00
|
|
|
else {
|
|
|
|
// AWD: Could just use InsertSilence() on its own here, but it doesn't
|
|
|
|
// follow EditClipCanMove rules (Paste() does it right)
|
2019-05-22 11:42:08 +00:00
|
|
|
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() );
|
2010-02-16 20:50:38 +00:00
|
|
|
|
2017-03-23 15:10:14 +00:00
|
|
|
tmp->InsertSilence(0.0, newT1 - oldT1);
|
2010-02-16 20:50:38 +00:00
|
|
|
tmp->Flush();
|
2017-03-23 15:10:14 +00:00
|
|
|
Paste(oldT1, tmp.get());
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
2010-02-16 20:50:38 +00:00
|
|
|
else if (newT1 < oldT1) {
|
2017-03-23 15:10:14 +00:00
|
|
|
Clear(newT1, oldT1);
|
2010-02-16 20:50:38 +00:00
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2020-08-22 19:33:03 +00:00
|
|
|
/*! @excsafety{Weak} */
|
2017-03-23 15:10:14 +00:00
|
|
|
void WaveTrack::Paste(double t0, const Track *src)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2020-03-04 18:52:10 +00:00
|
|
|
bool editClipCanMove = GetEditClipsCanMove();
|
2017-11-19 03:18:49 +00:00
|
|
|
|
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());
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
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);
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2017-04-11 22:16:03 +00:00
|
|
|
for (const auto &clip : mClips)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2017-04-11 22:16:03 +00:00
|
|
|
if (editClipCanMove)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
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;
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2017-04-11 22:16:03 +00:00
|
|
|
else
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
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;
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-11 22:16:03 +00:00
|
|
|
if (insideClip)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2017-04-11 22:16:03 +00:00
|
|
|
// Exhibit traditional behaviour
|
|
|
|
//wxPrintf("paste: traditional behaviour\n");
|
|
|
|
if (!editClipCanMove)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
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())
|
2020-08-22 19:33:03 +00:00
|
|
|
// Strong-guarantee in case of this path
|
2017-04-11 22:16:03 +00:00
|
|
|
// not that it matters.
|
|
|
|
throw SimpleMessageBoxException{
|
2020-09-14 16:43:28 +00:00
|
|
|
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
|
|
|
};
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2017-04-11 22:16:03 +00:00
|
|
|
|
|
|
|
insideClip->Paste(t0, other->GetClipByIndex(0));
|
|
|
|
return true;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2017-04-11 22:16:03 +00:00
|
|
|
// Just fall through and exhibit NEW behaviour
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2017-04-11 22:16:03 +00:00
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2017-04-11 22:16:03 +00:00
|
|
|
// Insert NEW clips
|
|
|
|
//wxPrintf("paste: multi clip mode!\n");
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2017-04-11 22:16:03 +00:00
|
|
|
if (!editClipCanMove && !IsEmpty(t0, t0+insertDuration-1.0/mRate))
|
2020-08-22 19:33:03 +00:00
|
|
|
// Strong-guarantee in case of this path
|
2017-04-11 22:16:03 +00:00
|
|
|
// not that it matters.
|
|
|
|
throw SimpleMessageBoxException{
|
2020-09-14 16:43:28 +00:00
|
|
|
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
|
|
|
};
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2017-04-11 22:16:03 +00:00
|
|
|
for (const auto &clip : other->mClips)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
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
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2017-04-11 22:16:03 +00:00
|
|
|
return true;
|
|
|
|
} );
|
|
|
|
|
|
|
|
if( !bOk )
|
|
|
|
// THROW_INCONSISTENCY_EXCEPTION; // ?
|
2018-10-10 18:09:10 +00:00
|
|
|
(void)0;// Empty if intentional.
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2017-03-23 15:10:14 +00:00
|
|
|
void WaveTrack::Silence(double t0, double t1)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
if (t1 < t0)
|
2016-11-07 19:27:31 +00:00
|
|
|
THROW_INCONSISTENCY_EXCEPTION;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2016-08-24 15:24:26 +00:00
|
|
|
auto start = (sampleCount)floor(t0 * mRate + 0.5);
|
|
|
|
auto len = (sampleCount)floor(t1 * mRate + 0.5) - start;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2016-02-20 02:39:37 +00:00
|
|
|
for (const auto &clip : mClips)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2016-08-24 15:24:26 +00:00
|
|
|
auto clipStart = clip->GetStartSample();
|
|
|
|
auto clipEnd = clip->GetEndSample();
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
if (clipEnd > start && clipStart < start+len)
|
|
|
|
{
|
|
|
|
// Clip sample region and Get/Put sample region overlap
|
2016-08-24 15:24:26 +00:00
|
|
|
auto samplesToCopy = start+len - clipStart;
|
2010-01-23 19:44:49 +00:00
|
|
|
if (samplesToCopy > clip->GetNumSamples())
|
|
|
|
samplesToCopy = clip->GetNumSamples();
|
2016-08-24 15:24:26 +00:00
|
|
|
auto startDelta = clipStart - start;
|
|
|
|
decltype(startDelta) inclipDelta = 0;
|
2010-01-23 19:44:49 +00:00
|
|
|
if (startDelta < 0)
|
|
|
|
{
|
|
|
|
inclipDelta = -startDelta; // make positive value
|
|
|
|
samplesToCopy -= inclipDelta;
|
|
|
|
startDelta = 0;
|
|
|
|
}
|
|
|
|
|
2017-03-22 18:55:07 +00:00
|
|
|
clip->GetSequence()->SetSilence(inclipDelta, samplesToCopy);
|
2010-01-23 19:44:49 +00:00
|
|
|
clip->MarkChanged();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-22 19:33:03 +00:00
|
|
|
/*! @excsafety{Strong} */
|
2017-03-23 15:10:14 +00:00
|
|
|
void WaveTrack::InsertSilence(double t, double len)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2017-05-07 18:09:37 +00:00
|
|
|
// Nothing to do, if length is zero.
|
|
|
|
// Fixes Bug 1626
|
|
|
|
if( len == 0 )
|
|
|
|
return;
|
2010-01-23 19:44:49 +00:00
|
|
|
if (len <= 0)
|
2016-11-07 19:27:31 +00:00
|
|
|
THROW_INCONSISTENCY_EXCEPTION;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2016-02-20 02:39:37 +00:00
|
|
|
if (mClips.empty())
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
// 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());
|
2017-03-23 15:10:14 +00:00
|
|
|
clip->InsertSilence(0, len);
|
2020-08-22 19:33:03 +00:00
|
|
|
// use No-fail-guarantee
|
2017-04-02 02:20:38 +00:00
|
|
|
mClips.push_back( std::move( clip ) );
|
2017-03-23 15:10:14 +00:00
|
|
|
return;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2017-04-02 02:20:38 +00:00
|
|
|
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); } );
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2020-08-22 19:33:03 +00:00
|
|
|
// use Strong-guarantee
|
2017-04-02 02:20:38 +00:00
|
|
|
if (it != end)
|
2017-03-22 18:55:07 +00:00
|
|
|
it->get()->InsertSilence(t, len);
|
2017-04-02 02:20:38 +00:00
|
|
|
|
2020-08-22 19:33:03 +00:00
|
|
|
// use No-fail-guarantee
|
2017-04-02 02:20:38 +00:00
|
|
|
for (const auto &clip : mClips)
|
|
|
|
{
|
|
|
|
if (clip->BeforeClip(t))
|
|
|
|
clip->Offset(len);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//Performs the opposite of Join
|
|
|
|
//Analyses selected region for possible Joined clips and disjoins them
|
2020-08-22 19:33:03 +00:00
|
|
|
/*! @excsafety{Weak} */
|
2017-03-22 17:25:55 +00:00
|
|
|
void WaveTrack::Disjoin(double t0, double t1)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2016-08-24 15:24:26 +00:00
|
|
|
auto minSamples = TimeToLongSamples( WAVETRACK_MERGE_POINT_TOLERANCE );
|
2016-04-14 16:17:59 +00:00
|
|
|
const size_t maxAtOnce = 1048576;
|
|
|
|
Floats buffer{ maxAtOnce };
|
2010-01-23 19:44:49 +00:00
|
|
|
Regions regions;
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
wxBusyCursor busy;
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2016-02-20 02:39:37 +00:00
|
|
|
for (const auto &clip : mClips)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
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
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
//simply look for a sequence of zeroes and if the sequence
|
2016-02-14 23:50:45 +00:00
|
|
|
//is greater than minimum number, split-DELETE the region
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
sampleCount seqStart = -1;
|
|
|
|
sampleCount start, end;
|
|
|
|
clip->TimeToSamplesClip( startTime, &start );
|
|
|
|
clip->TimeToSamplesClip( endTime, &end );
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2016-08-24 15:24:26 +00:00
|
|
|
auto len = ( end - start );
|
|
|
|
for( decltype(len) done = 0; done < len; done += maxAtOnce )
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
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,
|
2010-01-23 19:44:49 +00:00
|
|
|
numSamples );
|
2016-08-24 16:13:53 +00:00
|
|
|
for( decltype(numSamples) i = 0; i < numSamples; i++ )
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2016-08-24 15:24:26 +00:00
|
|
|
auto curSamplePos = start + done + i;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2016-02-13 15:43:16 +00:00
|
|
|
//start a NEW sequence
|
2010-01-23 19:44:49 +00:00
|
|
|
if( buffer[ i ] == 0.0 && seqStart == -1 )
|
|
|
|
seqStart = curSamplePos;
|
|
|
|
else if( buffer[ i ] != 0.0 || curSamplePos == end - 1 )
|
|
|
|
{
|
|
|
|
if( seqStart != -1 )
|
|
|
|
{
|
2016-08-24 15:24:26 +00:00
|
|
|
decltype(end) seqEnd;
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
//consider the end case, where selection ends in zeroes
|
|
|
|
if( curSamplePos == end - 1 && buffer[ i ] == 0.0 )
|
2011-06-01 17:11:42 +00:00
|
|
|
seqEnd = end;
|
2010-01-23 19:44:49 +00:00
|
|
|
else
|
2011-06-01 17:11:42 +00:00
|
|
|
seqEnd = curSamplePos;
|
2010-01-23 19:44:49 +00:00
|
|
|
if( seqEnd - seqStart + 1 > minSamples )
|
|
|
|
{
|
2016-02-01 21:01:14 +00:00
|
|
|
regions.push_back(Region(
|
2016-08-25 12:53:59 +00:00
|
|
|
seqStart.as_double() / GetRate()
|
|
|
|
+ clip->GetStartTime(),
|
|
|
|
seqEnd.as_double() / GetRate()
|
|
|
|
+ clip->GetStartTime()));
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
seqStart = -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-01 21:01:14 +00:00
|
|
|
for( unsigned int i = 0; i < regions.size(); i++ )
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2016-02-01 21:01:14 +00:00
|
|
|
const Region ®ion = regions.at(i);
|
|
|
|
SplitDelete(region.start, region.end );
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-22 19:33:03 +00:00
|
|
|
/*! @excsafety{Weak} */
|
2017-03-22 17:25:55 +00:00
|
|
|
void WaveTrack::Join(double t0, double t1)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
// Merge all WaveClips overlapping selection into one
|
|
|
|
|
2016-08-11 17:06:24 +00:00
|
|
|
WaveClipPointers clipsToDelete;
|
2010-01-23 19:44:49 +00:00
|
|
|
WaveClip *newClip;
|
|
|
|
|
2016-02-20 02:39:37 +00:00
|
|
|
for (const auto &clip: mClips)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
if (clip->GetStartTime() < t1-(1.0/mRate) &&
|
|
|
|
clip->GetEndTime()-(1.0/mRate) > t0) {
|
|
|
|
|
|
|
|
// Put in sorted order
|
2016-02-20 02:39:37 +00:00
|
|
|
auto it = clipsToDelete.begin(), end = clipsToDelete.end();
|
|
|
|
for (; it != end; ++it)
|
|
|
|
if ((*it)->GetStartTime() > clip->GetStartTime())
|
2010-01-23 19:44:49 +00:00
|
|
|
break;
|
2017-10-09 04:37:10 +00:00
|
|
|
//wxPrintf("Insert clip %.6f at position %d\n", clip->GetStartTime(), i);
|
2016-02-20 23:43:08 +00:00
|
|
|
clipsToDelete.insert(it, clip.get());
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-14 23:50:45 +00:00
|
|
|
//if there are no clips to DELETE, nothing to do
|
2016-02-20 02:39:37 +00:00
|
|
|
if( clipsToDelete.size() == 0 )
|
2017-03-22 17:25:55 +00:00
|
|
|
return;
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
newClip = CreateClip();
|
|
|
|
double t = clipsToDelete[0]->GetOffset();
|
|
|
|
newClip->SetOffset(t);
|
2016-02-20 02:39:37 +00:00
|
|
|
for (const auto &clip : clipsToDelete)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2017-10-09 04:37:10 +00:00
|
|
|
//wxPrintf("t=%.6f adding clip (offset %.6f, %.6f ... %.6f)\n",
|
2010-01-23 19:44:49 +00:00
|
|
|
// 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");
|
2017-05-06 04:57:38 +00:00
|
|
|
auto offset = clip->GetOffset();
|
|
|
|
auto value = clip->GetEnvelope()->GetValue( offset );
|
|
|
|
newClip->AppendSilence( addedSilence, value );
|
2010-01-23 19:44:49 +00:00
|
|
|
t += addedSilence;
|
|
|
|
}
|
|
|
|
|
2017-10-09 04:37:10 +00:00
|
|
|
//wxPrintf("Pasting at %.6f\n", t);
|
2017-03-22 18:55:07 +00:00
|
|
|
newClip->Paste(t, clip);
|
2017-05-06 04:57:38 +00:00
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
t = newClip->GetEndTime();
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2016-02-20 02:39:37 +00:00
|
|
|
auto it = FindClip(mClips, clip);
|
2016-02-20 23:43:08 +00:00
|
|
|
mClips.erase(it); // deletes the clip
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-22 19:33:03 +00:00
|
|
|
/*! @excsafety{Partial}
|
|
|
|
-- Some prefix (maybe none) of the buffer is appended,
|
|
|
|
and no content already flushed to disk is lost. */
|
2020-11-30 00:09:58 +00:00
|
|
|
bool WaveTrack::Append(constSamplePtr buffer, sampleFormat format,
|
2020-07-01 05:45:17 +00:00
|
|
|
size_t len, unsigned int stride /* = 1 */)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2020-07-10 23:05:48 +00:00
|
|
|
return RightmostOrNewClip()->Append(buffer, format, len, stride);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2015-03-28 18:46:40 +00:00
|
|
|
sampleCount WaveTrack::GetBlockStart(sampleCount s) const
|
|
|
|
{
|
2016-02-20 02:39:37 +00:00
|
|
|
for (const auto &clip : mClips)
|
2015-03-28 18:46:40 +00:00
|
|
|
{
|
2016-08-24 15:24:26 +00:00
|
|
|
const auto startSample = (sampleCount)floor(0.5 + clip->GetStartTime()*mRate);
|
|
|
|
const auto endSample = startSample + clip->GetNumSamples();
|
2015-03-28 18:46:40 +00:00
|
|
|
if (s >= startSample && s < endSample)
|
|
|
|
return startSample + clip->GetSequence()->GetBlockStart(s - startSample);
|
|
|
|
}
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2016-09-06 13:19:27 +00:00
|
|
|
size_t WaveTrack::GetBestBlockSize(sampleCount s) const
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2016-08-24 15:24:26 +00:00
|
|
|
auto bestBlockSize = GetMaxBlockSize();
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2016-02-20 02:39:37 +00:00
|
|
|
for (const auto &clip : mClips)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2016-08-24 15:24:26 +00:00
|
|
|
auto startSample = (sampleCount)floor(clip->GetStartTime()*mRate + 0.5);
|
|
|
|
auto endSample = startSample + clip->GetNumSamples();
|
2010-01-23 19:44:49 +00:00
|
|
|
if (s >= startSample && s < endSample)
|
|
|
|
{
|
2015-03-28 18:46:40 +00:00
|
|
|
bestBlockSize = clip->GetSequence()->GetBestBlockSize(s - startSample);
|
2010-01-23 19:44:49 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return bestBlockSize;
|
|
|
|
}
|
|
|
|
|
2016-09-06 13:19:27 +00:00
|
|
|
size_t WaveTrack::GetMaxBlockSize() const
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2016-08-22 20:21:41 +00:00
|
|
|
decltype(GetMaxBlockSize()) maxblocksize = 0;
|
2016-02-20 02:39:37 +00:00
|
|
|
for (const auto &clip : mClips)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2016-08-22 20:21:41 +00:00
|
|
|
maxblocksize = std::max(maxblocksize, clip->GetSequence()->GetMaxBlockSize());
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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();
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
wxASSERT(maxblocksize > 0);
|
|
|
|
|
|
|
|
return maxblocksize;
|
|
|
|
}
|
|
|
|
|
2016-09-06 13:19:27 +00:00
|
|
|
size_t WaveTrack::GetIdealBlockSize()
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2015-01-04 19:44:54 +00:00
|
|
|
return NewestOrNewClip()->GetSequence()->GetIdealBlockSize();
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2020-08-22 19:33:03 +00:00
|
|
|
/*! @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. */
|
2017-03-22 17:25:55 +00:00
|
|
|
void WaveTrack::Flush()
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2015-01-04 19:44:54 +00:00
|
|
|
// After appending, presumably. Do this to the clip that gets appended.
|
2017-03-22 17:25:55 +00:00
|
|
|
RightmostOrNewClip()->Flush();
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
if (!value)
|
|
|
|
break;
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +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) ||
|
2010-01-23 19:44:49 +00:00
|
|
|
!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) &&
|
2010-01-23 19:44:49 +00:00
|
|
|
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;
|
|
|
|
}
|
2017-03-30 20:00:27 +00:00
|
|
|
else if (this->PlayableTrack::HandleXMLAttribute(attr, value))
|
|
|
|
{}
|
2018-11-20 19:37:19 +00:00
|
|
|
else if (this->Track::HandleCommonXMLAttribute(attr, strValue))
|
|
|
|
;
|
2014-06-03 20:30:19 +00:00
|
|
|
else if (!wxStrcmp(attr, wxT("gain")) &&
|
|
|
|
XMLValueChecker::IsGoodString(strValue) &&
|
2010-01-23 19:44:49 +00:00
|
|
|
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) &&
|
2010-01-23 19:44:49 +00:00
|
|
|
(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) ||
|
2010-01-23 19:44:49 +00:00
|
|
|
!XMLValueChecker::IsValidChannel(nValue))
|
|
|
|
return false;
|
2018-09-20 16:39:05 +00:00
|
|
|
mChannel = static_cast<Track::ChannelType>( nValue );
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2014-06-03 20:30:19 +00:00
|
|
|
else if (!wxStrcmp(attr, wxT("linked")) &&
|
2010-01-23 19:44:49 +00:00
|
|
|
XMLValueChecker::IsGoodInt(strValue) && strValue.ToLong(&nValue))
|
|
|
|
SetLinked(nValue != 0);
|
2017-11-04 17:09:01 +00:00
|
|
|
else if (!wxStrcmp(attr, wxT("colorindex")) &&
|
|
|
|
XMLValueChecker::IsGoodString(strValue) &&
|
|
|
|
strValue.ToLong(&nValue))
|
|
|
|
// Don't use SetWaveColorIndex as it sets the clips too.
|
|
|
|
mWaveColorIndex = nValue;
|
2020-12-03 16:29:01 +00:00
|
|
|
else if (!wxStrcmp(attr, wxT("sampleformat")) &&
|
|
|
|
XMLValueChecker::IsGoodInt(strValue) &&
|
|
|
|
strValue.ToLong(&nValue) &&
|
|
|
|
XMLValueChecker::IsValidSampleFormat(nValue))
|
|
|
|
mFormat = static_cast<sampleFormat>(nValue);
|
2010-01-23 19:44:49 +00:00
|
|
|
} // while
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2013-08-25 21:51:26 +00:00
|
|
|
void WaveTrack::HandleXMLEndTag(const wxChar * WXUNUSED(tag))
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
// 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"));
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
XMLTagHandler *WaveTrack::HandleXMLChild(const wxChar *tag)
|
|
|
|
{
|
|
|
|
//
|
2016-02-13 15:43:16 +00:00
|
|
|
// This is legacy code (1.2 and previous) and is not called for NEW projects!
|
2010-01-23 19:44:49 +00:00
|
|
|
//
|
|
|
|
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
|
|
|
|
2010-01-23 19:44:49 +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();
|
2010-01-23 19:44:49 +00:00
|
|
|
else if (!wxStrcmp(tag, wxT("envelope")))
|
2015-01-04 19:44:54 +00:00
|
|
|
return NewestOrNewClip()->GetEnvelope();
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +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();
|
2010-01-23 19:44:49 +00:00
|
|
|
return pSeq;
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
2016-02-13 15:43:16 +00:00
|
|
|
// This is for the NEW file format (post-1.2)
|
2010-01-23 19:44:49 +00:00
|
|
|
//
|
|
|
|
if (!wxStrcmp(tag, wxT("waveclip")))
|
|
|
|
return CreateClip();
|
|
|
|
else
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2017-02-22 19:23:35 +00:00
|
|
|
void WaveTrack::WriteXML(XMLWriter &xmlFile) const
|
2016-12-01 22:03:40 +00:00
|
|
|
// may throw
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
xmlFile.StartTag(wxT("wavetrack"));
|
2018-11-20 19:37:19 +00:00
|
|
|
this->Track::WriteCommonXMLAttributes( xmlFile );
|
2010-01-23 19:44:49 +00:00
|
|
|
xmlFile.WriteAttr(wxT("channel"), mChannel);
|
|
|
|
xmlFile.WriteAttr(wxT("linked"), mLinked);
|
2017-03-30 20:00:27 +00:00
|
|
|
this->PlayableTrack::WriteXMLAttributes(xmlFile);
|
2010-01-23 19:44:49 +00:00
|
|
|
xmlFile.WriteAttr(wxT("rate"), mRate);
|
|
|
|
xmlFile.WriteAttr(wxT("gain"), (double)mGain);
|
|
|
|
xmlFile.WriteAttr(wxT("pan"), (double)mPan);
|
2017-11-04 17:09:01 +00:00
|
|
|
xmlFile.WriteAttr(wxT("colorindex"), mWaveColorIndex );
|
2020-12-03 16:29:01 +00:00
|
|
|
xmlFile.WriteAttr(wxT("sampleformat"), static_cast<long>(mFormat) );
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2016-02-20 02:39:37 +00:00
|
|
|
for (const auto &clip : mClips)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2016-02-20 02:39:37 +00:00
|
|
|
clip->WriteXML(xmlFile);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
xmlFile.EndTag(wxT("wavetrack"));
|
|
|
|
}
|
|
|
|
|
|
|
|
bool WaveTrack::GetErrorOpening()
|
|
|
|
{
|
2016-02-20 02:39:37 +00:00
|
|
|
for (const auto &clip : mClips)
|
|
|
|
if (clip->GetSequence()->GetErrorOpening())
|
2010-01-23 19:44:49 +00:00
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool WaveTrack::CloseLock()
|
|
|
|
{
|
2016-02-20 02:39:37 +00:00
|
|
|
for (const auto &clip : mClips)
|
|
|
|
clip->CloseLock();
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
AUDACITY_DLL_API sampleCount WaveTrack::TimeToLongSamples(double t0) const
|
|
|
|
{
|
2016-08-25 12:53:59 +00:00
|
|
|
return sampleCount( floor(t0 * mRate + 0.5) );
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2015-04-19 11:27:33 +00:00
|
|
|
double WaveTrack::LongSamplesToTime(sampleCount pos) const
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2016-08-25 12:53:59 +00:00
|
|
|
return pos.as_double() / mRate;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2015-03-28 18:46:40 +00:00
|
|
|
double WaveTrack::GetStartTime() const
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
bool found = false;
|
|
|
|
double best = 0.0;
|
|
|
|
|
2016-02-20 02:39:37 +00:00
|
|
|
if (mClips.empty())
|
2010-01-23 19:44:49 +00:00
|
|
|
return 0;
|
|
|
|
|
2016-02-20 02:39:37 +00:00
|
|
|
for (const auto &clip : mClips)
|
2010-01-23 19:44:49 +00:00
|
|
|
if (!found)
|
|
|
|
{
|
|
|
|
found = true;
|
2016-02-20 02:39:37 +00:00
|
|
|
best = clip->GetStartTime();
|
|
|
|
}
|
|
|
|
else if (clip->GetStartTime() < best)
|
|
|
|
best = clip->GetStartTime();
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
return best;
|
|
|
|
}
|
|
|
|
|
2015-03-28 18:46:40 +00:00
|
|
|
double WaveTrack::GetEndTime() const
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
bool found = false;
|
|
|
|
double best = 0.0;
|
|
|
|
|
2016-02-20 02:39:37 +00:00
|
|
|
if (mClips.empty())
|
2010-01-23 19:44:49 +00:00
|
|
|
return 0;
|
|
|
|
|
2016-02-20 02:39:37 +00:00
|
|
|
for (const auto &clip : mClips)
|
2010-01-23 19:44:49 +00:00
|
|
|
if (!found)
|
|
|
|
{
|
|
|
|
found = true;
|
2016-02-20 02:39:37 +00:00
|
|
|
best = clip->GetEndTime();
|
|
|
|
}
|
|
|
|
else if (clip->GetEndTime() > best)
|
|
|
|
best = clip->GetEndTime();
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
return best;
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// Getting/setting samples. The sample counts here are
|
|
|
|
// expressed relative to t=0.0 at the track's sample rate.
|
|
|
|
//
|
|
|
|
|
2016-12-25 13:40:15 +00:00
|
|
|
std::pair<float, float> WaveTrack::GetMinMax(
|
|
|
|
double t0, double t1, bool mayThrow) const
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2016-12-25 13:40:15 +00:00
|
|
|
std::pair<float, float> results {
|
|
|
|
// we need these at extremes to make sure we find true min and max
|
|
|
|
FLT_MAX, -FLT_MAX
|
|
|
|
};
|
2012-05-02 21:37:45 +00:00
|
|
|
bool clipFound = false;
|
|
|
|
|
2016-12-25 13:40:15 +00:00
|
|
|
if (t0 > t1) {
|
|
|
|
if (mayThrow)
|
2016-11-07 19:27:31 +00:00
|
|
|
THROW_INCONSISTENCY_EXCEPTION;
|
2016-12-25 13:40:15 +00:00
|
|
|
return results;
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
if (t0 == t1)
|
2016-12-25 13:40:15 +00:00
|
|
|
return results;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2016-02-20 02:39:37 +00:00
|
|
|
for (const auto &clip: mClips)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
if (t1 >= clip->GetStartTime() && t0 <= clip->GetEndTime())
|
|
|
|
{
|
2012-05-02 21:37:45 +00:00
|
|
|
clipFound = true;
|
2016-12-25 13:40:15 +00:00
|
|
|
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;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-05-02 21:37:45 +00:00
|
|
|
if(!clipFound)
|
|
|
|
{
|
2016-12-25 13:40:15 +00:00
|
|
|
results = { 0.f, 0.f }; // sensible defaults if no clips found
|
2012-05-02 21:37:45 +00:00
|
|
|
}
|
|
|
|
|
2016-12-25 13:40:15 +00:00
|
|
|
return results;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2016-12-25 13:40:15 +00:00
|
|
|
float WaveTrack::GetRMS(double t0, double t1, bool mayThrow) const
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2016-12-25 13:40:15 +00:00
|
|
|
if (t0 > t1) {
|
|
|
|
if (mayThrow)
|
2016-11-07 19:27:31 +00:00
|
|
|
THROW_INCONSISTENCY_EXCEPTION;
|
2016-12-25 13:40:15 +00:00
|
|
|
return 0.f;
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
if (t0 == t1)
|
2016-12-25 13:40:15 +00:00
|
|
|
return 0.f;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
double sumsq = 0.0;
|
|
|
|
sampleCount length = 0;
|
|
|
|
|
2016-02-20 02:39:37 +00:00
|
|
|
for (const auto &clip: mClips)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2016-06-21 21:05:49 +00:00
|
|
|
// 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())
|
2016-02-20 02:39:37 +00:00
|
|
|
if (t1 >= clip->GetStartTime() && t0 <= clip->GetEndTime())
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2014-05-13 23:36:39 +00:00
|
|
|
sampleCount clipStart, clipEnd;
|
|
|
|
|
2016-12-25 13:40:15 +00:00
|
|
|
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);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
2016-12-25 13:40:15 +00:00
|
|
|
return length > 0 ? sqrt(sumsq / length.as_double()) : 0.0;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool WaveTrack::Get(samplePtr buffer, sampleFormat format,
|
2017-03-20 14:54:03 +00:00
|
|
|
sampleCount start, size_t len, fillFormat fill,
|
2020-03-21 19:45:50 +00:00
|
|
|
bool mayThrow, sampleCount * pNumWithinClips) const
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
// Simple optimization: When this buffer is completely contained within one clip,
|
2012-03-25 16:19:08 +00:00
|
|
|
// don't clear anything (because we won't have to). Otherwise, just clear
|
2010-01-23 19:44:49 +00:00
|
|
|
// everything to be on the safe side.
|
|
|
|
bool doClear = true;
|
2017-03-20 14:54:03 +00:00
|
|
|
bool result = true;
|
2018-03-25 14:42:14 +00:00
|
|
|
sampleCount samplesCopied = 0;
|
2016-02-20 02:39:37 +00:00
|
|
|
for (const auto &clip: mClips)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
if (start >= clip->GetStartSample() && start+len <= clip->GetEndSample())
|
|
|
|
{
|
|
|
|
doClear = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (doClear)
|
2012-03-25 16:19:08 +00:00
|
|
|
{
|
2017-10-21 15:23:55 +00:00
|
|
|
// Usually we fill in empty space with zero
|
2012-03-25 16:19:08 +00:00
|
|
|
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;
|
2017-12-08 11:26:09 +00:00
|
|
|
for(size_t i=0;i<len;i++)
|
2012-03-25 16:19:08 +00:00
|
|
|
pBuffer[i]=2.0f;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
wxFAIL_MSG(wxT("Invalid fill format"));
|
|
|
|
}
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2020-03-21 19:45:50 +00:00
|
|
|
// Iterate the clips. They are not necessarily sorted by time.
|
2016-02-20 02:39:37 +00:00
|
|
|
for (const auto &clip: mClips)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2016-08-24 15:24:26 +00:00
|
|
|
auto clipStart = clip->GetStartSample();
|
|
|
|
auto clipEnd = clip->GetEndSample();
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
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() );
|
2016-08-24 15:24:26 +00:00
|
|
|
auto startDelta = clipStart - start;
|
|
|
|
decltype(startDelta) inclipDelta = 0;
|
2010-01-23 19:44:49 +00:00
|
|
|
if (startDelta < 0)
|
|
|
|
{
|
|
|
|
inclipDelta = -startDelta; // make positive value
|
|
|
|
samplesToCopy -= inclipDelta;
|
2016-08-31 04:49:22 +00:00
|
|
|
// samplesToCopy is now either len or
|
|
|
|
// (clipEnd - clipStart) - (start - clipStart)
|
|
|
|
// == clipEnd - start > 0
|
|
|
|
// samplesToCopy is not more than len
|
|
|
|
//
|
2010-01-23 19:44:49 +00:00
|
|
|
startDelta = 0;
|
2016-08-31 04:49:22 +00:00
|
|
|
// startDelta is zero
|
|
|
|
}
|
|
|
|
else {
|
2020-04-11 07:08:33 +00:00
|
|
|
// startDelta is nonnegative and less than len
|
2016-08-31 04:49:22 +00:00
|
|
|
// samplesToCopy is positive and not more than len
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2016-08-31 04:49:22 +00:00
|
|
|
if (!clip->GetSamples(
|
|
|
|
(samplePtr)(((char*)buffer) +
|
|
|
|
startDelta.as_size_t() *
|
|
|
|
SAMPLE_SIZE(format)),
|
2017-03-20 14:54:03 +00:00
|
|
|
format, inclipDelta, samplesToCopy.as_size_t(), mayThrow ))
|
|
|
|
result = false;
|
2018-03-25 14:42:14 +00:00
|
|
|
else
|
|
|
|
samplesCopied += samplesToCopy;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
2020-03-21 19:45:50 +00:00
|
|
|
if( pNumWithinClips )
|
|
|
|
*pNumWithinClips = samplesCopied;
|
2017-03-20 14:54:03 +00:00
|
|
|
return result;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2020-08-22 19:33:03 +00:00
|
|
|
/*! @excsafety{Weak} */
|
2020-11-30 00:09:58 +00:00
|
|
|
void WaveTrack::Set(constSamplePtr buffer, sampleFormat format,
|
2016-09-06 13:19:27 +00:00
|
|
|
sampleCount start, size_t len)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2016-02-20 02:39:37 +00:00
|
|
|
for (const auto &clip: mClips)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2016-08-24 15:24:26 +00:00
|
|
|
auto clipStart = clip->GetStartSample();
|
|
|
|
auto clipEnd = clip->GetEndSample();
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
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() );
|
2016-08-24 15:24:26 +00:00
|
|
|
auto startDelta = clipStart - start;
|
|
|
|
decltype(startDelta) inclipDelta = 0;
|
2010-01-23 19:44:49 +00:00
|
|
|
if (startDelta < 0)
|
|
|
|
{
|
|
|
|
inclipDelta = -startDelta; // make positive value
|
|
|
|
samplesToCopy -= inclipDelta;
|
2016-08-31 04:49:22 +00:00
|
|
|
// samplesToCopy is now either len or
|
|
|
|
// (clipEnd - clipStart) - (start - clipStart)
|
|
|
|
// == clipEnd - start > 0
|
|
|
|
// samplesToCopy is not more than len
|
|
|
|
//
|
2010-01-23 19:44:49 +00:00
|
|
|
startDelta = 0;
|
2016-08-31 04:49:22 +00:00
|
|
|
// startDelta is zero
|
|
|
|
}
|
|
|
|
else {
|
2020-04-11 07:08:33 +00:00
|
|
|
// startDelta is nonnegative and less than len
|
2016-08-31 04:49:22 +00:00
|
|
|
// samplesToCopy is positive and not more than len
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2017-03-22 18:55:07 +00:00
|
|
|
clip->SetSamples(
|
2020-11-30 00:09:58 +00:00
|
|
|
(constSamplePtr)(((const char*)buffer) +
|
2016-08-31 04:49:22 +00:00
|
|
|
startDelta.as_size_t() *
|
|
|
|
SAMPLE_SIZE(format)),
|
2017-03-22 18:55:07 +00:00
|
|
|
format, inclipDelta, samplesToCopy.as_size_t() );
|
2010-01-23 19:44:49 +00:00
|
|
|
clip->MarkChanged();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-20 21:56:41 +00:00
|
|
|
void WaveTrack::GetEnvelopeValues(double *buffer, size_t bufferLen,
|
|
|
|
double t0) const
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2014-12-22 05:03:08 +00:00
|
|
|
// 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.
|
|
|
|
//
|
2017-05-26 18:08:27 +00:00
|
|
|
// This does mean that, in the cases where a usable clip is located, the buffer value will
|
2014-12-22 05:03:08 +00:00
|
|
|
// 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.
|
2016-08-24 11:56:33 +00:00
|
|
|
for (decltype(bufferLen) i = 0; i < bufferLen; i++)
|
2014-12-22 05:03:08 +00:00
|
|
|
{
|
|
|
|
buffer[i] = 1.0;
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
double startTime = t0;
|
2016-08-20 21:56:41 +00:00
|
|
|
auto tstep = 1.0 / mRate;
|
|
|
|
double endTime = t0 + tstep * bufferLen;
|
2016-02-20 02:39:37 +00:00
|
|
|
for (const auto &clip: mClips)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2010-02-12 16:10:22 +00:00
|
|
|
// IF clip intersects startTime..endTime THEN...
|
2016-08-20 21:56:41 +00:00
|
|
|
auto dClipStartTime = clip->GetStartTime();
|
|
|
|
auto dClipEndTime = clip->GetEndTime();
|
2012-07-24 22:21:46 +00:00
|
|
|
if ((dClipStartTime < endTime) && (dClipEndTime > startTime))
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2016-08-20 21:56:41 +00:00
|
|
|
auto rbuf = buffer;
|
|
|
|
auto rlen = bufferLen;
|
|
|
|
auto rt0 = t0;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2012-07-24 22:21:46 +00:00
|
|
|
if (rt0 < dClipStartTime)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2016-08-31 04:49:22 +00:00
|
|
|
// This is not more than the number of samples in
|
|
|
|
// (endTime - startTime) which is bufferLen:
|
2016-08-24 15:24:26 +00:00
|
|
|
auto nDiff = (sampleCount)floor((dClipStartTime - rt0) * mRate + 0.5);
|
2016-08-31 04:49:22 +00:00
|
|
|
auto snDiff = nDiff.as_size_t();
|
|
|
|
rbuf += snDiff;
|
|
|
|
wxASSERT(snDiff <= rlen);
|
|
|
|
rlen -= snDiff;
|
2012-07-24 22:21:46 +00:00
|
|
|
rt0 = dClipStartTime;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2012-07-24 22:21:46 +00:00
|
|
|
if (rt0 + rlen*tstep > dClipEndTime)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2016-08-20 21:56:41 +00:00
|
|
|
auto nClipLen = clip->GetEndSample() - clip->GetStartSample();
|
2012-07-20 03:31:10 +00:00
|
|
|
|
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;
|
2013-08-24 20:30:47 +00:00
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
// This check prevents problem cited in http://bugzilla.audacityteam.org/show_bug.cgi?id=528#c11,
|
2012-07-20 03:31:10 +00:00
|
|
|
// Gale's cross_fade_out project, which was already corrupted by bug 528.
|
2012-07-24 22:21:46 +00:00
|
|
|
// 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
|
2016-08-21 22:05:43 +00:00
|
|
|
rlen = limitSampleBufferSize( rlen, nClipLen );
|
2016-08-20 21:56:41 +00:00
|
|
|
rlen = std::min(rlen, size_t(floor(0.5 + (dClipEndTime - rt0) / tstep)));
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2017-05-26 18:08:27 +00:00
|
|
|
// Samples are obtained for the purpose of rendering a wave track,
|
|
|
|
// so quantize time
|
2010-01-23 19:44:49 +00:00
|
|
|
clip->GetEnvelope()->GetValues(rbuf, rlen, rt0, tstep);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-04-25 18:10:32 +00:00
|
|
|
WaveClip* WaveTrack::GetClipAtSample(sampleCount sample)
|
|
|
|
{
|
2016-02-20 02:39:37 +00:00
|
|
|
for (const auto &clip: mClips)
|
2011-04-25 18:10:32 +00:00
|
|
|
{
|
2016-08-24 15:24:26 +00:00
|
|
|
auto start = clip->GetStartSample();
|
|
|
|
auto len = clip->GetNumSamples();
|
2011-04-25 18:10:32 +00:00
|
|
|
|
|
|
|
if (sample >= start && sample < start + len)
|
2016-02-20 23:43:08 +00:00
|
|
|
return clip.get();
|
2011-04-25 18:10:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2017-05-10 08:06:34 +00:00
|
|
|
// When the time is both the end of a clip and the start of the next clip, the
|
|
|
|
// latter clip is returned.
|
2017-03-04 12:12:17 +00:00
|
|
|
WaveClip* WaveTrack::GetClipAtTime(double time)
|
|
|
|
{
|
2017-05-10 08:06:34 +00:00
|
|
|
|
2017-03-04 12:12:17 +00:00
|
|
|
const auto clips = SortedClipArray();
|
2017-05-10 08:06:34 +00:00
|
|
|
auto p = std::find_if(clips.rbegin(), clips.rend(), [&] (WaveClip* const& clip) {
|
2017-03-04 12:12:17 +00:00
|
|
|
return time >= clip->GetStartTime() && time <= clip->GetEndTime(); });
|
|
|
|
|
2017-05-10 08:06:34 +00:00
|
|
|
// 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() &&
|
2017-05-10 08:06:34 +00:00
|
|
|
time == (*p)->GetEndTime() &&
|
2017-05-16 13:23:07 +00:00
|
|
|
(*p)->SharesBoundaryWithNextClip(*(p-1))) {
|
2017-05-10 08:06:34 +00:00
|
|
|
p--;
|
|
|
|
}
|
|
|
|
|
|
|
|
return p != clips.rend() ? *p : nullptr;
|
2017-03-04 12:12:17 +00:00
|
|
|
}
|
|
|
|
|
2020-11-20 03:26:10 +00:00
|
|
|
Envelope* WaveTrack::GetEnvelopeAtTime(double time)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2020-11-20 03:26:10 +00:00
|
|
|
WaveClip* clip = GetClipAtTime(time);
|
2010-01-23 19:44:49 +00:00
|
|
|
if (clip)
|
|
|
|
return clip->GetEnvelope();
|
|
|
|
else
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2020-11-20 03:26:10 +00:00
|
|
|
Sequence* WaveTrack::GetSequenceAtTime(double time)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2020-11-20 03:26:10 +00:00
|
|
|
WaveClip* clip = GetClipAtTime(time);
|
2010-01-23 19:44:49 +00:00
|
|
|
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()));
|
2016-02-20 23:43:08 +00:00
|
|
|
return mClips.back().get();
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2015-01-04 19:44:54 +00:00
|
|
|
WaveClip* WaveTrack::NewestOrNewClip()
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2016-02-20 02:39:37 +00:00
|
|
|
if (mClips.empty()) {
|
2010-01-23 19:44:49 +00:00
|
|
|
WaveClip *clip = CreateClip();
|
|
|
|
clip->SetOffset(mOffset);
|
|
|
|
return clip;
|
|
|
|
}
|
|
|
|
else
|
2016-02-20 23:43:08 +00:00
|
|
|
return mClips.back().get();
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2020-08-22 19:33:03 +00:00
|
|
|
/*! @excsafety{No-fail} */
|
2015-01-04 19:44:54 +00:00
|
|
|
WaveClip* WaveTrack::RightmostOrNewClip()
|
|
|
|
{
|
2016-02-20 02:39:37 +00:00
|
|
|
if (mClips.empty()) {
|
2015-01-04 19:44:54 +00:00
|
|
|
WaveClip *clip = CreateClip();
|
|
|
|
clip->SetOffset(mOffset);
|
|
|
|
return clip;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-02-20 02:39:37 +00:00
|
|
|
auto it = mClips.begin();
|
2016-02-20 23:43:08 +00:00
|
|
|
WaveClip *rightmost = (*it++).get();
|
2015-01-04 19:44:54 +00:00
|
|
|
double maxOffset = rightmost->GetOffset();
|
2016-02-20 02:39:37 +00:00
|
|
|
for (auto end = mClips.end(); it != end; ++it)
|
2015-01-04 19:44:54 +00:00
|
|
|
{
|
2016-02-20 23:43:08 +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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-11 17:06:24 +00:00
|
|
|
int WaveTrack::GetClipIndex(const WaveClip* clip) const
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2016-02-20 02:39:37 +00:00
|
|
|
int result;
|
|
|
|
FindClip(mClips, clip, &result);
|
|
|
|
return result;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
WaveClip* WaveTrack::GetClipByIndex(int index)
|
|
|
|
{
|
2016-02-20 02:39:37 +00:00
|
|
|
if(index < (int)mClips.size())
|
2016-02-20 23:43:08 +00:00
|
|
|
return mClips[index].get();
|
2010-01-23 19:44:49 +00:00
|
|
|
else
|
2016-02-20 23:43:08 +00:00
|
|
|
return nullptr;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2016-08-11 17:06:24 +00:00
|
|
|
const WaveClip* WaveTrack::GetClipByIndex(int index) const
|
|
|
|
{
|
|
|
|
return const_cast<WaveTrack&>(*this).GetClipByIndex(index);
|
|
|
|
}
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
int WaveTrack::GetNumClips() const
|
|
|
|
{
|
2016-02-20 02:39:37 +00:00
|
|
|
return mClips.size();
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2020-09-18 12:40:54 +00:00
|
|
|
bool WaveTrack::CanOffsetClips(
|
|
|
|
const std::vector<WaveClip*> &clips,
|
|
|
|
double amount,
|
|
|
|
double *allowedAmount /* = NULL */)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
if (allowedAmount)
|
|
|
|
*allowedAmount = amount;
|
|
|
|
|
2020-09-18 12:40:54 +00:00
|
|
|
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 );
|
|
|
|
};
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2020-09-18 12:40:54 +00:00
|
|
|
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)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2020-09-18 12:40:54 +00:00
|
|
|
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;
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (allowedAmount)
|
|
|
|
{
|
|
|
|
if (*allowedAmount == amount)
|
|
|
|
return true;
|
|
|
|
|
2016-02-13 15:43:16 +00:00
|
|
|
// Check if the NEW calculated amount would not violate
|
2010-01-23 19:44:49 +00:00
|
|
|
// any other constraint
|
2020-09-18 12:40:54 +00:00
|
|
|
if (!CanOffsetClips(clips, *allowedAmount, nullptr)) {
|
2010-01-23 19:44:49 +00:00
|
|
|
*allowedAmount = 0; // play safe and don't allow anything
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
return true;
|
|
|
|
} else
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-09-18 13:42:15 +00:00
|
|
|
bool WaveTrack::CanInsertClip(
|
|
|
|
WaveClip* clip, double &slideBy, double &tolerance) const
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2016-02-20 02:39:37 +00:00
|
|
|
for (const auto &c : mClips)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2017-08-19 21:46:33 +00:00
|
|
|
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.
|
2017-08-23 17:15:00 +00:00
|
|
|
// 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.
|
2017-08-19 21:46:33 +00:00
|
|
|
if( -d1 < tolerance ){
|
2017-08-23 17:15:00 +00:00
|
|
|
// right edge of clip overlaps slightly.
|
|
|
|
// slide clip left a small amount.
|
2017-08-19 21:46:33 +00:00
|
|
|
slideBy +=d1;
|
|
|
|
tolerance /=1000;
|
2017-08-23 17:15:00 +00:00
|
|
|
} else if( -d2 < tolerance ){
|
|
|
|
// left edge of clip overlaps slightly.
|
|
|
|
// slide clip right a small amount.
|
2017-08-19 21:46:33 +00:00
|
|
|
slideBy -= d2;
|
|
|
|
tolerance /=1000;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
return false; // clips overlap No tolerance left.
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-08-22 19:33:03 +00:00
|
|
|
/*! @excsafety{Weak} */
|
2017-03-22 17:25:55 +00:00
|
|
|
void WaveTrack::Split( double t0, double t1 )
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2017-03-22 17:25:55 +00:00
|
|
|
SplitAt( t0 );
|
|
|
|
if( t0 != t1 )
|
|
|
|
SplitAt( t1 );
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2020-08-22 19:33:03 +00:00
|
|
|
/*! @excsafety{Weak} */
|
2017-03-22 17:25:55 +00:00
|
|
|
void WaveTrack::SplitAt(double t)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2016-02-20 02:39:37 +00:00
|
|
|
for (const auto &c : mClips)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
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 );
|
2017-03-22 18:55:07 +00:00
|
|
|
c->Clear(t, c->GetEndTime());
|
|
|
|
newClip->Clear(c->GetStartTime(), t);
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2016-02-13 15:43:16 +00:00
|
|
|
//offset the NEW clip by the splitpoint (noting that it is already offset to c->GetStartTime())
|
2010-01-23 19:44:49 +00:00
|
|
|
sampleCount here = llrint(floor(((t - c->GetStartTime()) * mRate) + 0.5));
|
2016-08-25 12:53:59 +00:00
|
|
|
newClip->Offset(here.as_double()/(double)mRate);
|
2016-02-20 02:39:37 +00:00
|
|
|
// This could invalidate the iterators for the loop! But we return
|
|
|
|
// at once so it's okay
|
2016-02-20 23:43:08 +00:00
|
|
|
mClips.push_back(std::move(newClip)); // transfer ownership
|
2017-04-02 14:42:07 +00:00
|
|
|
return;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-27 17:12:40 +00:00
|
|
|
void WaveTrack::UpdateLocationsCache() const
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2016-08-11 17:06:24 +00:00
|
|
|
auto clips = SortedClipArray();
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2016-02-20 00:09:36 +00:00
|
|
|
mDisplayLocationsCache.clear();
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
// Count number of display locations
|
2016-02-20 00:09:36 +00:00
|
|
|
int num = 0;
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2016-08-11 17:06:24 +00:00
|
|
|
const WaveClip *prev = nullptr;
|
|
|
|
for (const auto clip : clips)
|
|
|
|
{
|
|
|
|
num += clip->NumCutLines();
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2016-08-11 17:06:24 +00:00
|
|
|
if (prev && fabs(prev->GetEndTime() -
|
|
|
|
clip->GetStartTime()) < WAVETRACK_MERGE_POINT_TOLERANCE)
|
|
|
|
++num;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2016-08-11 17:06:24 +00:00
|
|
|
prev = clip;
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2016-02-20 00:09:36 +00:00
|
|
|
if (num == 0)
|
2010-01-23 19:44:49 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
// Alloc necessary number of display locations
|
2016-02-20 00:09:36 +00:00
|
|
|
mDisplayLocationsCache.reserve(num);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
// Add all display locations to cache
|
|
|
|
int curpos = 0;
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2016-08-11 17:06:24 +00:00
|
|
|
const WaveClip *previousClip = nullptr;
|
|
|
|
for (const auto clip: clips)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2016-08-11 22:56:32 +00:00
|
|
|
for (const auto &cc : clip->GetCutLines())
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
// Add cut line expander point
|
2016-02-20 00:09:36 +00:00
|
|
|
mDisplayLocationsCache.push_back(WaveTrackLocation{
|
2016-02-20 02:39:37 +00:00
|
|
|
clip->GetOffset() + cc->GetOffset(),
|
2016-02-20 00:09:36 +00:00
|
|
|
WaveTrackLocation::locationCutLine
|
|
|
|
});
|
2010-01-23 19:44:49 +00:00
|
|
|
curpos++;
|
|
|
|
}
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2016-08-11 17:06:24 +00:00
|
|
|
if (previousClip)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
if (fabs(previousClip->GetEndTime() - clip->GetStartTime())
|
|
|
|
< WAVETRACK_MERGE_POINT_TOLERANCE)
|
|
|
|
{
|
|
|
|
// Add merge point
|
2016-02-20 00:09:36 +00:00
|
|
|
mDisplayLocationsCache.push_back(WaveTrackLocation{
|
2016-08-11 17:06:24 +00:00
|
|
|
previousClip->GetEndTime(),
|
2016-02-20 00:09:36 +00:00
|
|
|
WaveTrackLocation::locationMergePoint,
|
2016-02-20 02:39:37 +00:00
|
|
|
GetClipIndex(previousClip),
|
|
|
|
GetClipIndex(clip)
|
2016-02-20 00:09:36 +00:00
|
|
|
});
|
2010-01-23 19:44:49 +00:00
|
|
|
curpos++;
|
|
|
|
}
|
|
|
|
}
|
2016-08-11 17:06:24 +00:00
|
|
|
|
|
|
|
previousClip = clip;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2016-02-20 00:09:36 +00:00
|
|
|
wxASSERT(curpos == num);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2016-02-14 23:50:45 +00:00
|
|
|
// Expand cut line (that is, re-insert audio, then DELETE audio saved in cut line)
|
2020-08-22 19:33:03 +00:00
|
|
|
/*! @excsafety{Strong} */
|
2017-03-22 17:25:55 +00:00
|
|
|
void WaveTrack::ExpandCutLine(double cutLinePosition, double* cutlineStart,
|
2010-01-23 19:44:49 +00:00
|
|
|
double* cutlineEnd)
|
|
|
|
{
|
2020-03-04 18:52:10 +00:00
|
|
|
bool editClipCanMove = GetEditClipsCanMove();
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
// Find clip which contains this cut line
|
2017-03-24 19:01:19 +00:00
|
|
|
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)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2017-03-24 19:01:19 +00:00
|
|
|
auto &clip = *pClip;
|
|
|
|
if (!editClipCanMove)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2017-03-24 19:01:19 +00:00
|
|
|
// 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)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2017-03-24 19:01:19 +00:00
|
|
|
if (clip2->GetStartTime() > clip->GetStartTime() &&
|
|
|
|
clip->GetEndTime() + end - start > clip2->GetStartTime())
|
2020-08-22 19:33:03 +00:00
|
|
|
// Strong-guarantee in case of this path
|
2017-03-24 19:01:19 +00:00
|
|
|
throw SimpleMessageBoxException{
|
2020-09-14 16:43:28 +00:00
|
|
|
XO("There is not enough room available to expand the cut line"),
|
|
|
|
XO("Warning"),
|
|
|
|
"Error:_Insufficient_space_in_track"
|
2017-03-24 19:01:19 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2017-03-22 18:55:07 +00:00
|
|
|
clip->ExpandCutLine(cutLinePosition);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2020-08-22 19:33:03 +00:00
|
|
|
// Strong-guarantee provided that the following gives No-fail-guarantee
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2017-03-24 19:01:19 +00:00
|
|
|
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)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2017-03-24 19:01:19 +00:00
|
|
|
if (clip2->GetStartTime() > clip->GetStartTime())
|
|
|
|
clip2->Offset(end - start);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool WaveTrack::RemoveCutLine(double cutLinePosition)
|
|
|
|
{
|
2016-02-20 02:39:37 +00:00
|
|
|
for (const auto &clip : mClips)
|
|
|
|
if (clip->RemoveCutLine(cutLinePosition))
|
2010-01-23 19:44:49 +00:00
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-08-22 19:33:03 +00:00
|
|
|
/*! @excsafety{Strong} */
|
2017-03-22 17:25:55 +00:00
|
|
|
void WaveTrack::MergeClips(int clipidx1, int clipidx2)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
WaveClip* clip1 = GetClipByIndex(clipidx1);
|
|
|
|
WaveClip* clip2 = GetClipByIndex(clipidx2);
|
|
|
|
|
2016-01-24 01:31:11 +00:00
|
|
|
if (!clip1 || !clip2) // Could happen if one track of a linked pair had a split and the other didn't.
|
2017-03-22 17:25:55 +00:00
|
|
|
return; // Don't throw, just do nothing.
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
// Append data from second clip to first clip
|
2020-08-22 19:33:03 +00:00
|
|
|
// use Strong-guarantee
|
2017-03-22 18:55:07 +00:00
|
|
|
clip1->Paste(clip1->GetEndTime(), clip2);
|
|
|
|
|
2020-08-22 19:33:03 +00:00
|
|
|
// use No-fail-guarantee for the rest
|
2010-01-23 19:44:49 +00:00
|
|
|
// Delete second clip
|
2016-02-20 02:39:37 +00:00
|
|
|
auto it = FindClip(mClips, clip2);
|
|
|
|
mClips.erase(it);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2020-08-22 19:33:03 +00:00
|
|
|
/*! @excsafety{Weak} -- Partial completion may leave clips at differing sample rates!
|
|
|
|
*/
|
2017-03-22 17:25:55 +00:00
|
|
|
void WaveTrack::Resample(int rate, ProgressDialog *progress)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2016-02-20 02:39:37 +00:00
|
|
|
for (const auto &clip : mClips)
|
2017-03-22 18:55:07 +00:00
|
|
|
clip->Resample(rate, progress);
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
mRate = rate;
|
|
|
|
}
|
|
|
|
|
2016-08-11 17:06:24 +00:00
|
|
|
namespace {
|
|
|
|
template < typename Cont1, typename Cont2 >
|
|
|
|
Cont1 FillSortedClipArray(const Cont2& mClips)
|
|
|
|
{
|
|
|
|
Cont1 clips;
|
2016-02-20 23:43:08 +00:00
|
|
|
for (const auto &clip : mClips)
|
|
|
|
clips.push_back(clip.get());
|
2016-08-11 17:06:24 +00:00
|
|
|
std::sort(clips.begin(), clips.end(),
|
|
|
|
[](const WaveClip *a, const WaveClip *b)
|
|
|
|
{ return a->GetStartTime() < b->GetStartTime(); });
|
|
|
|
return clips;
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2016-08-11 17:06:24 +00:00
|
|
|
WaveClipPointers WaveTrack::SortedClipArray()
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2016-08-11 17:06:24 +00:00
|
|
|
return FillSortedClipArray<WaveClipPointers>(mClips);
|
|
|
|
}
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2016-08-11 17:06:24 +00:00
|
|
|
WaveClipConstPointers WaveTrack::SortedClipArray() const
|
|
|
|
{
|
|
|
|
return FillSortedClipArray<WaveClipConstPointers>(mClips);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
///Deletes all clips' wavecaches. Careful, This may not be threadsafe.
|
2016-04-08 05:56:06 +00:00
|
|
|
void WaveTrack::ClearWaveCaches()
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2016-02-20 02:39:37 +00:00
|
|
|
for (const auto &clip : mClips)
|
|
|
|
clip->ClearWaveCache();
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2015-03-28 18:46:40 +00:00
|
|
|
WaveTrackCache::~WaveTrackCache()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2017-08-24 15:44:36 +00:00
|
|
|
void WaveTrackCache::SetTrack(const std::shared_ptr<const WaveTrack> &pTrack)
|
2015-03-28 18:46:40 +00:00
|
|
|
{
|
|
|
|
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 };
|
2015-03-28 18:46:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
Free();
|
|
|
|
mPTrack = pTrack;
|
|
|
|
mNValidBuffers = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-23 21:25:55 +00:00
|
|
|
const float *WaveTrackCache::GetFloats(
|
2016-12-22 22:08:29 +00:00
|
|
|
sampleCount start, size_t len, bool mayThrow)
|
2015-03-28 18:46:40 +00:00
|
|
|
{
|
2021-05-23 21:25:55 +00:00
|
|
|
constexpr auto format = floatSample;
|
2015-03-28 18:46:40 +00:00
|
|
|
if (format == floatSample && len > 0) {
|
2016-08-24 15:24:26 +00:00
|
|
|
const auto end = start + len;
|
2015-03-28 18:46:40 +00:00
|
|
|
|
|
|
|
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.)
|
2017-02-21 14:07:25 +00:00
|
|
|
mBuffers[0] .swap ( mBuffers[1] );
|
2015-03-28 18:46:40 +00:00
|
|
|
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.)
|
2017-02-21 14:07:25 +00:00
|
|
|
mBuffers[0] .swap ( mBuffers[1] );
|
2015-03-28 18:46:40 +00:00
|
|
|
fillFirst = true;
|
2016-09-01 22:03:45 +00:00
|
|
|
fillSecond = false;
|
2015-03-28 18:46:40 +00:00
|
|
|
// Cache is not in a consistent state yet
|
|
|
|
mNValidBuffers = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Refill buffers as needed
|
|
|
|
if (fillFirst) {
|
2016-08-24 15:24:26 +00:00
|
|
|
const auto start0 = mPTrack->GetBlockStart(start);
|
2015-03-28 18:46:40 +00:00
|
|
|
if (start0 >= 0) {
|
2016-08-24 15:24:26 +00:00
|
|
|
const auto len0 = mPTrack->GetBestBlockSize(start0);
|
2015-03-28 18:46:40 +00:00
|
|
|
wxASSERT(len0 <= mBufferSize);
|
2016-12-22 22:08:29 +00:00
|
|
|
if (!mPTrack->Get(
|
|
|
|
samplePtr(mBuffers[0].data.get()), floatSample, start0, len0,
|
|
|
|
fillZero, mayThrow))
|
2021-05-23 21:25:55 +00:00
|
|
|
return nullptr;
|
2015-03-28 18:46:40 +00:00
|
|
|
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;
|
2016-08-24 15:24:26 +00:00
|
|
|
const auto end0 = mBuffers[0].end();
|
2015-03-28 18:46:40 +00:00
|
|
|
if (end > end0) {
|
2016-08-24 15:24:26 +00:00
|
|
|
const auto start1 = mPTrack->GetBlockStart(end0);
|
2015-03-28 18:46:40 +00:00
|
|
|
if (start1 == end0) {
|
2016-08-24 15:24:26 +00:00
|
|
|
const auto len1 = mPTrack->GetBestBlockSize(start1);
|
2015-03-28 18:46:40 +00:00
|
|
|
wxASSERT(len1 <= mBufferSize);
|
2017-10-21 01:47:40 +00:00
|
|
|
if (!mPTrack->Get(samplePtr(mBuffers[1].data.get()), floatSample, start1, len1, fillZero, mayThrow))
|
2021-05-23 21:25:55 +00:00
|
|
|
return nullptr;
|
2015-03-28 18:46:40 +00:00
|
|
|
mBuffers[1].start = start1;
|
|
|
|
mBuffers[1].len = len1;
|
|
|
|
mNValidBuffers = 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
wxASSERT(mNValidBuffers < 2 || mBuffers[0].end() == mBuffers[1].start);
|
|
|
|
|
|
|
|
samplePtr buffer = 0;
|
2016-08-24 15:24:26 +00:00
|
|
|
auto remaining = len;
|
2015-03-28 18:46:40 +00:00
|
|
|
|
|
|
|
// Possibly get an initial portion that is uncached
|
2016-08-21 18:34:26 +00:00
|
|
|
|
|
|
|
// This may be negative
|
|
|
|
const auto initLen =
|
|
|
|
mNValidBuffers < 1 ? sampleCount( len )
|
|
|
|
: std::min(sampleCount( len ), mBuffers[0].start - start);
|
|
|
|
|
2015-03-28 18:46:40 +00:00
|
|
|
if (initLen > 0) {
|
|
|
|
// This might be fetching zeroes between clips
|
|
|
|
mOverlapBuffer.Resize(len, format);
|
2016-08-31 04:49:22 +00:00
|
|
|
// initLen is not more than len:
|
|
|
|
auto sinitLen = initLen.as_size_t();
|
2017-10-21 01:47:40 +00:00
|
|
|
if (!mPTrack->Get(mOverlapBuffer.ptr(), format, start, sinitLen,
|
|
|
|
fillZero, mayThrow))
|
2021-05-23 21:25:55 +00:00
|
|
|
return nullptr;
|
2016-08-31 04:49:22 +00:00
|
|
|
wxASSERT( sinitLen <= remaining );
|
|
|
|
remaining -= sinitLen;
|
2015-03-28 18:46:40 +00:00
|
|
|
start += initLen;
|
2016-08-31 04:49:22 +00:00
|
|
|
buffer = mOverlapBuffer.ptr() + sinitLen * SAMPLE_SIZE(format);
|
2015-03-28 18:46:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Now satisfy the request from the buffers
|
|
|
|
for (int ii = 0; ii < mNValidBuffers && remaining > 0; ++ii) {
|
2016-08-24 15:24:26 +00:00
|
|
|
const auto starti = start - mBuffers[ii].start;
|
2016-08-31 04:49:22 +00:00
|
|
|
// Treatment of initLen above establishes this loop invariant,
|
|
|
|
// and statements below preserve it:
|
|
|
|
wxASSERT(starti >= 0);
|
|
|
|
|
|
|
|
// This may be negative
|
2016-08-24 15:24:26 +00:00
|
|
|
const auto leni =
|
|
|
|
std::min( sampleCount( remaining ), mBuffers[ii].len - starti );
|
2016-09-01 22:03:45 +00:00
|
|
|
if (initLen <= 0 && leni == len) {
|
2015-03-28 18:46:40 +00:00
|
|
|
// All is contiguous already. We can completely avoid copying
|
2016-08-31 04:49:22 +00:00
|
|
|
// leni is nonnegative, therefore start falls within mBuffers[ii],
|
|
|
|
// so starti is bounded between 0 and buffer length
|
2021-05-23 21:25:55 +00:00
|
|
|
return mBuffers[ii].data.get() + starti.as_size_t() ;
|
2015-03-28 18:46:40 +00:00
|
|
|
}
|
|
|
|
else if (leni > 0) {
|
2016-08-31 04:49:22 +00:00
|
|
|
// leni is nonnegative, therefore start falls within mBuffers[ii]
|
|
|
|
// But we can't satisfy all from one buffer, so copy
|
2015-03-28 18:46:40 +00:00
|
|
|
if (buffer == 0) {
|
|
|
|
mOverlapBuffer.Resize(len, format);
|
|
|
|
buffer = mOverlapBuffer.ptr();
|
|
|
|
}
|
2016-08-31 04:49:22 +00:00
|
|
|
// 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);
|
2016-08-31 04:49:22 +00:00
|
|
|
wxASSERT( leni <= remaining );
|
|
|
|
remaining -= leni.as_size_t();
|
2015-03-28 18:46:40 +00:00
|
|
|
start += leni;
|
|
|
|
buffer += size;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (remaining > 0) {
|
|
|
|
// Very big request!
|
|
|
|
// Fall back to direct fetch
|
2016-07-04 20:52:02 +00:00
|
|
|
if (buffer == 0) {
|
|
|
|
mOverlapBuffer.Resize(len, format);
|
|
|
|
buffer = mOverlapBuffer.ptr();
|
|
|
|
}
|
2017-10-21 01:47:40 +00:00
|
|
|
if (!mPTrack->Get(buffer, format, start, remaining, fillZero, mayThrow))
|
2015-07-15 04:33:53 +00:00
|
|
|
return 0;
|
2015-03-28 18:46:40 +00:00
|
|
|
}
|
|
|
|
|
2021-05-23 21:25:55 +00:00
|
|
|
// 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;
|
2015-03-28 18:46:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void WaveTrackCache::Free()
|
|
|
|
{
|
|
|
|
mBuffers[0].Free();
|
|
|
|
mBuffers[1].Free();
|
|
|
|
mOverlapBuffer.Free();
|
|
|
|
mNValidBuffers = 0;
|
|
|
|
}
|
2018-12-05 02:54:40 +00:00
|
|
|
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
}
|
2020-07-23 16:17:29 +00:00
|
|
|
|
|
|
|
#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 );
|
|
|
|
}
|
2020-08-22 23:44:49 +00:00
|
|
|
|
|
|
|
#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 );
|
|
|
|
}
|