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).
This commit is contained in:
parent
1fcb77ebce
commit
127696879d
|
@ -33,6 +33,7 @@ of the BlockFile system.
|
|||
#include <wx/valtext.h>
|
||||
#include <wx/intl.h>
|
||||
|
||||
#include "SampleBlock.h"
|
||||
#include "ShuttleGui.h"
|
||||
#include "Project.h"
|
||||
#include "WaveClip.h"
|
||||
|
@ -369,7 +370,7 @@ void BenchmarkDialog::OnRun( wxCommandEvent & WXUNUSED(event))
|
|||
ZoomInfo zoomInfo(0.0, ZoomInfo::GetDefaultZoom());
|
||||
const auto t =
|
||||
TrackFactory{ mSettings,
|
||||
mProject,
|
||||
SampleBlockFactory::New( mProject ),
|
||||
&zoomInfo }.NewWaveTrack(int16Sample);
|
||||
|
||||
t->SetRate(1);
|
||||
|
|
|
@ -263,6 +263,7 @@ list( APPEND SOURCES
|
|||
SpectrumAnalyst.h
|
||||
SplashDialog.cpp
|
||||
SplashDialog.h
|
||||
SqliteSampleBlock.cpp
|
||||
SseMathFuncs.cpp
|
||||
SseMathFuncs.h
|
||||
Tags.cpp
|
||||
|
|
|
@ -10,6 +10,7 @@ Paul Licameli split from AudacityProject.cpp
|
|||
|
||||
#include "ProjectFileIO.h"
|
||||
|
||||
#include <sqlite3.h>
|
||||
#include <wx/crt.h>
|
||||
#include <wx/frame.h>
|
||||
|
||||
|
@ -180,7 +181,8 @@ static const AudacityProject::AttachedObjects::RegisteredFactory sFileIOKey{
|
|||
|
||||
ProjectFileIO &ProjectFileIO::Get( AudacityProject &project )
|
||||
{
|
||||
return project.AttachedObjects::Get< ProjectFileIO >( sFileIOKey );
|
||||
auto &result = project.AttachedObjects::Get< ProjectFileIO >( sFileIOKey );
|
||||
return result;
|
||||
}
|
||||
|
||||
const ProjectFileIO &ProjectFileIO::Get( const AudacityProject &project )
|
||||
|
@ -188,8 +190,7 @@ const ProjectFileIO &ProjectFileIO::Get( const AudacityProject &project )
|
|||
return Get( const_cast< AudacityProject & >( project ) );
|
||||
}
|
||||
|
||||
ProjectFileIO::ProjectFileIO(AudacityProject &project)
|
||||
: mProject(project)
|
||||
ProjectFileIO::ProjectFileIO(AudacityProject &)
|
||||
{
|
||||
mDB = nullptr;
|
||||
|
||||
|
@ -200,6 +201,13 @@ ProjectFileIO::ProjectFileIO(AudacityProject &project)
|
|||
UpdatePrefs();
|
||||
}
|
||||
|
||||
void ProjectFileIO::Init( AudacityProject &project )
|
||||
{
|
||||
// This step can't happen in the ctor of ProjectFileIO because ctor of
|
||||
// AudacityProject wasn't complete
|
||||
mpProject = project.shared_from_this();
|
||||
}
|
||||
|
||||
ProjectFileIO::~ProjectFileIO()
|
||||
{
|
||||
if (mDB)
|
||||
|
@ -645,7 +653,11 @@ void ProjectFileIO::UpdatePrefs()
|
|||
// Pass a number in to show project number, or -1 not to.
|
||||
void ProjectFileIO::SetProjectTitle(int number)
|
||||
{
|
||||
auto &project = mProject;
|
||||
auto pProject = mpProject.lock();
|
||||
if (! pProject )
|
||||
return;
|
||||
|
||||
auto &project = *pProject;
|
||||
auto pWindow = project.GetFrame();
|
||||
if (!pWindow)
|
||||
{
|
||||
|
@ -692,15 +704,20 @@ const FilePath &ProjectFileIO::GetFileName() const
|
|||
|
||||
void ProjectFileIO::SetFileName(const FilePath &fileName)
|
||||
{
|
||||
auto pProject = mpProject.lock();
|
||||
if (! pProject )
|
||||
return;
|
||||
auto &project = *pProject;
|
||||
|
||||
mFileName = fileName;
|
||||
|
||||
if (mTemporary)
|
||||
{
|
||||
mProject.SetProjectName({});
|
||||
project.SetProjectName({});
|
||||
}
|
||||
else
|
||||
{
|
||||
mProject.SetProjectName(wxFileName(mFileName).GetName());
|
||||
project.SetProjectName(wxFileName(mFileName).GetName());
|
||||
}
|
||||
|
||||
SetProjectTitle();
|
||||
|
@ -708,7 +725,10 @@ void ProjectFileIO::SetFileName(const FilePath &fileName)
|
|||
|
||||
bool ProjectFileIO::HandleXMLTag(const wxChar *tag, const wxChar **attrs)
|
||||
{
|
||||
auto &project = mProject;
|
||||
auto pProject = mpProject.lock();
|
||||
if (! pProject )
|
||||
return false;
|
||||
auto &project = *pProject;
|
||||
auto &window = GetProjectFrame(project);
|
||||
auto &viewInfo = ViewInfo::Get(project);
|
||||
auto &settings = ProjectSettings::Get(project);
|
||||
|
@ -838,10 +858,14 @@ bool ProjectFileIO::HandleXMLTag(const wxChar *tag, const wxChar **attrs)
|
|||
|
||||
XMLTagHandler *ProjectFileIO::HandleXMLChild(const wxChar *tag)
|
||||
{
|
||||
auto pProject = mpProject.lock();
|
||||
if (! pProject )
|
||||
return nullptr;
|
||||
auto &project = *pProject;
|
||||
auto fn = ProjectFileIORegistry::Lookup(tag);
|
||||
if (fn)
|
||||
{
|
||||
return fn(mProject);
|
||||
return fn(project);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
|
@ -865,7 +889,10 @@ void ProjectFileIO::WriteXMLHeader(XMLWriter &xmlFile) const
|
|||
void ProjectFileIO::WriteXML(XMLWriter &xmlFile, const WaveTrackArray *tracks)
|
||||
// may throw
|
||||
{
|
||||
auto &proj = mProject;
|
||||
auto pProject = mpProject.lock();
|
||||
if (! pProject )
|
||||
THROW_INCONSISTENCY_EXCEPTION;
|
||||
auto &proj = *pProject;
|
||||
auto &tracklist = TrackList::Get(proj);
|
||||
auto &viewInfo = ViewInfo::Get(proj);
|
||||
auto &tags = Tags::Get(proj);
|
||||
|
|
|
@ -11,15 +11,16 @@ Paul Licameli split from AudacityProject.h
|
|||
#ifndef __AUDACITY_PROJECT_FILE_IO__
|
||||
#define __AUDACITY_PROJECT_FILE_IO__
|
||||
|
||||
#include <memory>
|
||||
#include "ClientData.h" // to inherit
|
||||
#include "Prefs.h" // to inherit
|
||||
#include "xml/XMLTagHandler.h" // to inherit
|
||||
|
||||
#include <sqlite3.h>
|
||||
struct sqlite3;
|
||||
|
||||
class AudacityProject;
|
||||
class AutoSaveFile;
|
||||
class SampleBlock;
|
||||
class SqliteSampleBlock;
|
||||
class WaveTrack;
|
||||
|
||||
using WaveTrackArray = std::vector < std::shared_ptr < WaveTrack > >;
|
||||
|
@ -30,12 +31,17 @@ class ProjectFileIO final
|
|||
: public ClientData::Base
|
||||
, public XMLTagHandler
|
||||
, private PrefsListener
|
||||
, public std::enable_shared_from_this<ProjectFileIO>
|
||||
{
|
||||
public:
|
||||
static ProjectFileIO &Get( AudacityProject &project );
|
||||
static const ProjectFileIO &Get( const AudacityProject &project );
|
||||
|
||||
explicit ProjectFileIO( AudacityProject &project );
|
||||
// unfortunate two-step construction needed because of
|
||||
// enable_shared_from_this
|
||||
void Init( AudacityProject &project );
|
||||
|
||||
ProjectFileIO( const ProjectFileIO & ) PROHIBITED;
|
||||
ProjectFileIO &operator=( const ProjectFileIO & ) PROHIBITED;
|
||||
~ProjectFileIO();
|
||||
|
@ -108,7 +114,7 @@ private:
|
|||
|
||||
private:
|
||||
// non-static data members
|
||||
AudacityProject &mProject;
|
||||
std::weak_ptr<AudacityProject> mpProject;
|
||||
|
||||
// The project's file path
|
||||
FilePath mFileName;
|
||||
|
@ -127,7 +133,7 @@ private:
|
|||
TranslatableString mLastError;
|
||||
TranslatableString mLibraryError;
|
||||
|
||||
friend SampleBlock;
|
||||
friend SqliteSampleBlock;
|
||||
};
|
||||
|
||||
class wxTopLevelWindow;
|
||||
|
|
|
@ -536,7 +536,9 @@ AudacityProject *ProjectManager::New()
|
|||
auto &window = ProjectWindow::Get( *p );
|
||||
InitProjectWindow( window );
|
||||
|
||||
ProjectFileIO::Get( *p ).SetProjectTitle();
|
||||
auto &projectFileIO = ProjectFileIO::Get( *p );
|
||||
projectFileIO.Init( *p );
|
||||
projectFileIO.SetProjectTitle();
|
||||
|
||||
MenuManager::Get( project ).CreateMenusAndCommands( project );
|
||||
|
||||
|
|
|
@ -9,746 +9,32 @@ SampleBlock.cpp
|
|||
#include "Audacity.h"
|
||||
#include "SampleBlock.h"
|
||||
|
||||
#include <float.h>
|
||||
|
||||
#include <wx/defs.h>
|
||||
|
||||
#include "ProjectFileIO.h"
|
||||
#include "SampleFormat.h"
|
||||
#include "xml/XMLWriter.h"
|
||||
|
||||
// static
|
||||
SampleBlockPtr SampleBlock::Create(AudacityProject *project,
|
||||
samplePtr src,
|
||||
size_t numsamples,
|
||||
sampleFormat srcformat)
|
||||
static SampleBlockFactoryFactory& installedFactory()
|
||||
{
|
||||
auto sb = std::make_shared<SampleBlock>(project);
|
||||
|
||||
if (sb)
|
||||
{
|
||||
if (sb->SetSamples(src, numsamples, srcformat))
|
||||
{
|
||||
return sb;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
static SampleBlockFactoryFactory theFactory;
|
||||
return theFactory;
|
||||
}
|
||||
|
||||
// static
|
||||
SampleBlockPtr SampleBlock::CreateSilent(AudacityProject *project,
|
||||
size_t numsamples,
|
||||
sampleFormat srcformat)
|
||||
SampleBlockFactoryFactory SampleBlockFactory::RegisterFactoryFactory(
|
||||
SampleBlockFactoryFactory newFactory )
|
||||
{
|
||||
auto sb = std::make_shared<SampleBlock>(project);
|
||||
|
||||
if (sb)
|
||||
{
|
||||
if (sb->SetSilent(numsamples, srcformat))
|
||||
{
|
||||
return sb;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
auto &theFactory = installedFactory();
|
||||
auto result = std::move( theFactory );
|
||||
theFactory = std::move( newFactory );
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// static
|
||||
SampleBlockPtr SampleBlock::CreateFromXML(AudacityProject *project,
|
||||
sampleFormat srcformat,
|
||||
const wxChar **attrs)
|
||||
SampleBlockFactoryPtr SampleBlockFactory::New( AudacityProject &project )
|
||||
{
|
||||
auto sb = std::make_shared<SampleBlock>(project);
|
||||
sb->mSampleFormat = srcformat;
|
||||
|
||||
int found = 0;
|
||||
|
||||
// loop through attrs, which is a null-terminated list of attribute-value pairs
|
||||
while(*attrs)
|
||||
{
|
||||
const wxChar *attr = *attrs++;
|
||||
const wxChar *value = *attrs++;
|
||||
|
||||
if (!value)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
const wxString strValue = value; // promote string, we need this for all
|
||||
double dblValue;
|
||||
long long nValue;
|
||||
|
||||
if (XMLValueChecker::IsGoodInt(strValue) && strValue.ToLongLong(&nValue) && (nValue >= 0))
|
||||
{
|
||||
if (wxStrcmp(attr, wxT("blockid")) == 0)
|
||||
{
|
||||
if (!sb->Load((SampleBlockID) nValue))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
found++;
|
||||
}
|
||||
else if (wxStrcmp(attr, wxT("samplecount")) == 0)
|
||||
{
|
||||
sb->mSampleCount = nValue;
|
||||
sb->mSampleBytes = sb->mSampleCount * SAMPLE_SIZE(sb->mSampleFormat);
|
||||
found++;
|
||||
}
|
||||
}
|
||||
else if (XMLValueChecker::IsGoodString(strValue) && Internat::CompatibleToDouble(strValue, &dblValue))
|
||||
{
|
||||
if (wxStricmp(attr, wxT("min")) == 0)
|
||||
{
|
||||
sb->mSumMin = dblValue;
|
||||
found++;
|
||||
}
|
||||
else if (wxStricmp(attr, wxT("max")) == 0)
|
||||
{
|
||||
sb->mSumMax = dblValue;
|
||||
found++;
|
||||
}
|
||||
else if ((wxStricmp(attr, wxT("rms")) == 0) && (dblValue >= 0.0))
|
||||
{
|
||||
sb->mSumRms = dblValue;
|
||||
found++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Were all attributes found?
|
||||
if (found != 5)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return sb;
|
||||
auto &factory = installedFactory();
|
||||
if ( ! factory )
|
||||
THROW_INCONSISTENCY_EXCEPTION;
|
||||
return factory( project );
|
||||
}
|
||||
|
||||
// static
|
||||
SampleBlockPtr SampleBlock::Get(AudacityProject *project,
|
||||
SampleBlockID sbid)
|
||||
{
|
||||
auto sb = std::make_shared<SampleBlock>(project);
|
||||
SampleBlockFactory::~SampleBlockFactory() = default;
|
||||
|
||||
if (sb)
|
||||
{
|
||||
if (!sb->Load(sbid))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
SampleBlock::~SampleBlock() = default;
|
||||
|
||||
return sb;
|
||||
}
|
||||
|
||||
SampleBlock::SampleBlock(AudacityProject *project)
|
||||
: mIO(ProjectFileIO::Get(*project))
|
||||
{
|
||||
mValid = false;
|
||||
mSilent = false;
|
||||
mRefCnt = 0;
|
||||
|
||||
mBlockID = 0;
|
||||
|
||||
mSampleFormat = floatSample;
|
||||
mSampleBytes = 0;
|
||||
mSampleCount = 0;
|
||||
|
||||
mSummary256Bytes = 0;
|
||||
mSummary64kBytes = 0;
|
||||
mSumMin = 0.0;
|
||||
mSumMax = 0.0;
|
||||
mSumRms = 0.0;
|
||||
}
|
||||
|
||||
SampleBlock::~SampleBlock()
|
||||
{
|
||||
if (mRefCnt == 0)
|
||||
{
|
||||
Delete();
|
||||
}
|
||||
}
|
||||
|
||||
void SampleBlock::Lock()
|
||||
{
|
||||
++mRefCnt;
|
||||
}
|
||||
|
||||
void SampleBlock::Unlock()
|
||||
{
|
||||
--mRefCnt;
|
||||
}
|
||||
|
||||
void SampleBlock::CloseLock()
|
||||
{
|
||||
Lock();
|
||||
}
|
||||
|
||||
SampleBlockID SampleBlock::GetBlockID()
|
||||
{
|
||||
return mBlockID;
|
||||
}
|
||||
|
||||
sampleFormat SampleBlock::GetSampleFormat() const
|
||||
{
|
||||
return mSampleFormat;
|
||||
}
|
||||
|
||||
size_t SampleBlock::GetSampleCount() const
|
||||
{
|
||||
return mSampleCount;
|
||||
}
|
||||
|
||||
size_t SampleBlock::GetSamples(samplePtr dest,
|
||||
sampleFormat destformat,
|
||||
size_t sampleoffset,
|
||||
size_t numsamples)
|
||||
{
|
||||
return GetBlob(dest,
|
||||
destformat,
|
||||
"samples",
|
||||
mSampleFormat,
|
||||
sampleoffset * SAMPLE_SIZE(mSampleFormat),
|
||||
numsamples * SAMPLE_SIZE(mSampleFormat)) / SAMPLE_SIZE(mSampleFormat);
|
||||
}
|
||||
|
||||
bool SampleBlock::SetSamples(samplePtr src,
|
||||
size_t numsamples,
|
||||
sampleFormat srcformat)
|
||||
{
|
||||
mSampleFormat = srcformat;
|
||||
|
||||
mSampleCount = numsamples;
|
||||
mSampleBytes = mSampleCount * SAMPLE_SIZE(mSampleFormat);
|
||||
mSamples.reinit(mSampleBytes);
|
||||
memcpy(mSamples.get(), src, mSampleBytes);
|
||||
|
||||
CalcSummary();
|
||||
|
||||
return Commit();
|
||||
}
|
||||
|
||||
bool SampleBlock::SetSilent(size_t numsamples, sampleFormat srcformat)
|
||||
{
|
||||
mSampleFormat = srcformat;
|
||||
|
||||
mSampleCount = numsamples;
|
||||
mSampleBytes = mSampleCount * SAMPLE_SIZE(mSampleFormat);
|
||||
mSamples.reinit(mSampleBytes);
|
||||
memset(mSamples.get(), 0, mSampleBytes);
|
||||
|
||||
CalcSummary();
|
||||
|
||||
mSilent = true;
|
||||
|
||||
return Commit();
|
||||
}
|
||||
|
||||
bool SampleBlock::GetSummary256(float *dest,
|
||||
size_t frameoffset,
|
||||
size_t numframes)
|
||||
{
|
||||
return GetSummary(dest, frameoffset, numframes, "summary256", mSummary256Bytes);
|
||||
}
|
||||
|
||||
bool SampleBlock::GetSummary64k(float *dest,
|
||||
size_t frameoffset,
|
||||
size_t numframes)
|
||||
{
|
||||
return GetSummary(dest, frameoffset, numframes, "summary64k", mSummary64kBytes);
|
||||
}
|
||||
|
||||
bool SampleBlock::GetSummary(float *dest,
|
||||
size_t frameoffset,
|
||||
size_t numframes,
|
||||
const char *srccolumn,
|
||||
size_t srcbytes)
|
||||
{
|
||||
return GetBlob(dest,
|
||||
floatSample,
|
||||
srccolumn,
|
||||
floatSample,
|
||||
frameoffset * 3 * SAMPLE_SIZE(floatSample),
|
||||
numframes * 3 * SAMPLE_SIZE(floatSample)) / 3 / SAMPLE_SIZE(floatSample);
|
||||
}
|
||||
|
||||
double SampleBlock::GetSumMin() const
|
||||
{
|
||||
return mSumMin;
|
||||
}
|
||||
|
||||
double SampleBlock::GetSumMax() const
|
||||
{
|
||||
return mSumMax;
|
||||
}
|
||||
|
||||
double SampleBlock::GetSumRms() const
|
||||
{
|
||||
return mSumRms;
|
||||
}
|
||||
|
||||
/// Retrieves the minimum, maximum, and maximum RMS of the
|
||||
/// specified sample data in this block.
|
||||
///
|
||||
/// @param start The offset in this block where the region should begin
|
||||
/// @param len The number of samples to include in the region
|
||||
MinMaxRMS SampleBlock::GetMinMaxRMS(size_t start, size_t len)
|
||||
{
|
||||
float min = FLT_MAX;
|
||||
float max = -FLT_MAX;
|
||||
float sumsq = 0;
|
||||
|
||||
if (!mValid && mBlockID)
|
||||
{
|
||||
Load(mBlockID);
|
||||
}
|
||||
|
||||
if (start < mSampleCount)
|
||||
{
|
||||
len = std::min(len, mSampleCount - start);
|
||||
|
||||
// TODO: actually use summaries
|
||||
SampleBuffer blockData(len, floatSample);
|
||||
float *samples = (float *) blockData.ptr();
|
||||
|
||||
size_t copied = GetBlob(samples,
|
||||
floatSample,
|
||||
"samples",
|
||||
mSampleFormat,
|
||||
start * SAMPLE_SIZE(mSampleFormat),
|
||||
len * SAMPLE_SIZE(mSampleFormat)) / SAMPLE_SIZE(mSampleFormat);
|
||||
for (size_t i = 0; i < copied; ++i, ++samples)
|
||||
{
|
||||
float sample = *samples;
|
||||
|
||||
if (sample > max)
|
||||
{
|
||||
max = sample;
|
||||
}
|
||||
|
||||
if (sample < min)
|
||||
{
|
||||
min = sample;
|
||||
}
|
||||
|
||||
sumsq += (sample * sample);
|
||||
}
|
||||
}
|
||||
|
||||
return { min, max, (float) sqrt(sumsq / len) };
|
||||
}
|
||||
|
||||
/// Retrieves the minimum, maximum, and maximum RMS of this entire
|
||||
/// block. This is faster than the other GetMinMax function since
|
||||
/// these values are already computed.
|
||||
MinMaxRMS SampleBlock::GetMinMaxRMS() const
|
||||
{
|
||||
return { (float) mSumMin, (float) mSumMax, (float) mSumRms };
|
||||
}
|
||||
|
||||
size_t SampleBlock::GetSpaceUsage() const
|
||||
{
|
||||
return mSampleCount * SAMPLE_SIZE(mSampleFormat);
|
||||
}
|
||||
|
||||
size_t SampleBlock::GetBlob(void *dest,
|
||||
sampleFormat destformat,
|
||||
const char *srccolumn,
|
||||
sampleFormat srcformat,
|
||||
size_t srcoffset,
|
||||
size_t srcbytes)
|
||||
{
|
||||
auto db = mIO.DB();
|
||||
|
||||
wxASSERT(mBlockID > 0);
|
||||
|
||||
if (!mValid && mBlockID)
|
||||
{
|
||||
Load(mBlockID);
|
||||
}
|
||||
|
||||
int rc;
|
||||
size_t minbytes = 0;
|
||||
|
||||
char sql[256];
|
||||
sqlite3_snprintf(sizeof(sql),
|
||||
sql,
|
||||
"SELECT %s FROM sampleblocks WHERE blockid = %d;",
|
||||
srccolumn,
|
||||
mBlockID);
|
||||
|
||||
sqlite3_stmt *stmt = nullptr;
|
||||
auto cleanup = finally([&]
|
||||
{
|
||||
if (stmt)
|
||||
{
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
});
|
||||
|
||||
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
|
||||
if (rc != SQLITE_OK)
|
||||
{
|
||||
wxLogDebug(wxT("SQLITE error %s"), sqlite3_errmsg(db));
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = sqlite3_step(stmt);
|
||||
if (rc != SQLITE_ROW)
|
||||
{
|
||||
wxLogDebug(wxT("SQLITE error %s"), sqlite3_errmsg(db));
|
||||
}
|
||||
else
|
||||
{
|
||||
samplePtr src = (samplePtr) sqlite3_column_blob(stmt, 0);
|
||||
size_t blobbytes = (size_t) sqlite3_column_bytes(stmt, 0);
|
||||
|
||||
srcoffset = std::min(srcoffset, blobbytes);
|
||||
minbytes = std::min(srcbytes, blobbytes - srcoffset);
|
||||
|
||||
if (srcoffset != 0)
|
||||
{
|
||||
srcoffset += 0;
|
||||
}
|
||||
CopySamples(src + srcoffset,
|
||||
srcformat,
|
||||
(samplePtr) dest,
|
||||
destformat,
|
||||
minbytes / SAMPLE_SIZE(srcformat));
|
||||
|
||||
dest = ((samplePtr) dest) + minbytes;
|
||||
}
|
||||
}
|
||||
|
||||
if (srcbytes - minbytes)
|
||||
{
|
||||
memset(dest, 0, srcbytes - minbytes);
|
||||
}
|
||||
|
||||
return srcbytes;
|
||||
}
|
||||
|
||||
bool SampleBlock::Load(SampleBlockID sbid)
|
||||
{
|
||||
auto db = mIO.DB();
|
||||
|
||||
wxASSERT(sbid > 0);
|
||||
|
||||
int rc;
|
||||
|
||||
mValid = false;
|
||||
mSummary256Bytes = 0;
|
||||
mSummary64kBytes = 0;
|
||||
mSampleCount = 0;
|
||||
mSampleBytes = 0;
|
||||
mSumMin = FLT_MAX;
|
||||
mSumMax = -FLT_MAX;
|
||||
mSumMin = 0.0;
|
||||
|
||||
char sql[256];
|
||||
sqlite3_snprintf(sizeof(sql),
|
||||
sql,
|
||||
"SELECT sampleformat, summin, summax, sumrms,"
|
||||
" length('summary256'), length('summary64k'), length('samples')"
|
||||
" FROM sampleblocks WHERE blockid = %d;",
|
||||
sbid);
|
||||
|
||||
sqlite3_stmt *stmt = nullptr;
|
||||
auto cleanup = finally([&]
|
||||
{
|
||||
if (stmt)
|
||||
{
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
});
|
||||
|
||||
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
|
||||
if (rc != SQLITE_OK)
|
||||
{
|
||||
wxLogDebug(wxT("SQLITE error %s"), sqlite3_errmsg(db));
|
||||
// handle error
|
||||
return false;
|
||||
}
|
||||
|
||||
rc = sqlite3_step(stmt);
|
||||
if (rc != SQLITE_ROW)
|
||||
{
|
||||
wxLogDebug(wxT("SQLITE error %s"), sqlite3_errmsg(db));
|
||||
// handle error
|
||||
return false;
|
||||
}
|
||||
|
||||
mBlockID = sbid;
|
||||
mSampleFormat = (sampleFormat) sqlite3_column_int(stmt, 0);
|
||||
mSumMin = sqlite3_column_double(stmt, 1);
|
||||
mSumMax = sqlite3_column_double(stmt, 2);
|
||||
mSumRms = sqlite3_column_double(stmt, 3);
|
||||
mSummary256Bytes = sqlite3_column_int(stmt, 4);
|
||||
mSummary64kBytes = sqlite3_column_int(stmt, 5);
|
||||
mSampleBytes = sqlite3_column_int(stmt, 6);
|
||||
mSampleCount = mSampleBytes / SAMPLE_SIZE(mSampleFormat);
|
||||
|
||||
mValid = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SampleBlock::Commit()
|
||||
{
|
||||
auto db = mIO.DB();
|
||||
int rc;
|
||||
|
||||
char sql[256];
|
||||
sqlite3_snprintf(sizeof(sql),
|
||||
sql,
|
||||
"INSERT INTO sampleblocks (%s) VALUES(?,?,?,?,?,?,?);",
|
||||
columns);
|
||||
|
||||
sqlite3_stmt *stmt = nullptr;
|
||||
auto cleanup = finally([&]
|
||||
{
|
||||
if (stmt)
|
||||
{
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
});
|
||||
|
||||
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
|
||||
if (rc != SQLITE_OK)
|
||||
{
|
||||
wxLogDebug(wxT("SQLITE error %s"), sqlite3_errmsg(db));
|
||||
// handle error
|
||||
return false;
|
||||
}
|
||||
|
||||
// BIND SQL sampleblocks
|
||||
sqlite3_bind_int(stmt, 1, mSampleFormat);
|
||||
sqlite3_bind_double(stmt, 2, mSumMin);
|
||||
sqlite3_bind_double(stmt, 3, mSumMax);
|
||||
sqlite3_bind_double(stmt, 4, mSumRms);
|
||||
sqlite3_bind_blob(stmt, 5, mSummary256.get(), mSummary256Bytes, SQLITE_STATIC);
|
||||
sqlite3_bind_blob(stmt, 6, mSummary64k.get(), mSummary64kBytes, SQLITE_STATIC);
|
||||
sqlite3_bind_blob(stmt, 7, mSamples.get(), mSampleBytes, SQLITE_STATIC);
|
||||
|
||||
rc = sqlite3_step(stmt);
|
||||
if (rc != SQLITE_DONE)
|
||||
{
|
||||
wxLogDebug(wxT("SQLITE error %s"), sqlite3_errmsg(db));
|
||||
// handle error
|
||||
return false;
|
||||
}
|
||||
|
||||
mBlockID = sqlite3_last_insert_rowid(db);
|
||||
|
||||
mSamples.reset();
|
||||
mSummary256.reset();
|
||||
mSummary64k.reset();
|
||||
|
||||
mValid = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SampleBlock::Delete()
|
||||
{
|
||||
auto db = mIO.DB();
|
||||
|
||||
if (mBlockID)
|
||||
{
|
||||
int rc;
|
||||
|
||||
char sql[256];
|
||||
sqlite3_snprintf(sizeof(sql),
|
||||
sql,
|
||||
"DELETE FROM sampleblocks WHERE blockid = %lld;",
|
||||
mBlockID);
|
||||
|
||||
rc = sqlite3_exec(db, sql, nullptr, nullptr, nullptr);
|
||||
if (rc != SQLITE_OK)
|
||||
{
|
||||
wxLogDebug(wxT("SQLITE error %s"), sqlite3_errmsg(db));
|
||||
// handle error
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SampleBlock::SaveXML(XMLWriter &xmlFile)
|
||||
{
|
||||
xmlFile.WriteAttr(wxT("blockid"), mBlockID);
|
||||
xmlFile.WriteAttr(wxT("samplecount"), mSampleCount);
|
||||
xmlFile.WriteAttr(wxT("len256"), mSummary256Bytes);
|
||||
xmlFile.WriteAttr(wxT("len64k"), mSummary64kBytes);
|
||||
xmlFile.WriteAttr(wxT("min"), mSumMin);
|
||||
xmlFile.WriteAttr(wxT("max"), mSumMax);
|
||||
xmlFile.WriteAttr(wxT("rms"), mSumRms);
|
||||
}
|
||||
|
||||
/// Calculates summary block data describing this sample data.
|
||||
///
|
||||
/// This method also has the side effect of setting the mSumMin,
|
||||
/// mSumMax, and mSumRms members of this class.
|
||||
///
|
||||
/// @param buffer A buffer containing the sample data to be analyzed
|
||||
/// @param len The length of the sample data
|
||||
/// @param format The format of the sample data.
|
||||
void SampleBlock::CalcSummary()
|
||||
{
|
||||
Floats samplebuffer;
|
||||
float *samples;
|
||||
|
||||
if (mSampleFormat == floatSample)
|
||||
{
|
||||
samples = (float *) mSamples.get();
|
||||
}
|
||||
else
|
||||
{
|
||||
samplebuffer.reinit((unsigned) mSampleCount);
|
||||
CopySamples(mSamples.get(),
|
||||
mSampleFormat,
|
||||
(samplePtr) samplebuffer.get(),
|
||||
floatSample,
|
||||
mSampleCount);
|
||||
samples = samplebuffer.get();
|
||||
}
|
||||
|
||||
int fields = 3; /* min, max, rms */
|
||||
int bytesPerFrame = fields * sizeof(float);
|
||||
int frames64k = (mSampleCount + 65535) / 65536;
|
||||
int frames256 = frames64k * 256;
|
||||
|
||||
mSummary256Bytes = frames256 * bytesPerFrame;
|
||||
mSummary64kBytes = frames64k * bytesPerFrame;
|
||||
|
||||
mSummary256.reinit(mSummary256Bytes);
|
||||
mSummary64k.reinit(mSummary64kBytes);
|
||||
|
||||
float *summary256 = (float *) mSummary256.get();
|
||||
float *summary64k = (float *) mSummary64k.get();
|
||||
|
||||
float min;
|
||||
float max;
|
||||
float sumsq;
|
||||
double totalSquares = 0.0;
|
||||
double fraction = 0.0;
|
||||
|
||||
// Recalc 256 summaries
|
||||
int sumLen = (mSampleCount + 255) / 256;
|
||||
int summaries = 256;
|
||||
|
||||
for (int i = 0; i < sumLen; ++i)
|
||||
{
|
||||
min = samples[i * 256];
|
||||
max = samples[i * 256];
|
||||
sumsq = min * min;
|
||||
|
||||
int jcount = 256;
|
||||
if (jcount > mSampleCount - i * 256)
|
||||
{
|
||||
jcount = mSampleCount - i * 256;
|
||||
fraction = 1.0 - (jcount / 256.0);
|
||||
}
|
||||
|
||||
for (int j = 1; j < jcount; ++j)
|
||||
{
|
||||
float f1 = samples[i * 256 + j];
|
||||
sumsq += f1 * f1;
|
||||
|
||||
if (f1 < min)
|
||||
{
|
||||
min = f1;
|
||||
}
|
||||
else if (f1 > max)
|
||||
{
|
||||
max = f1;
|
||||
}
|
||||
}
|
||||
|
||||
totalSquares += sumsq;
|
||||
|
||||
summary256[i * 3] = min;
|
||||
summary256[i * 3 + 1] = max;
|
||||
// The rms is correct, but this may be for less than 256 samples in last loop.
|
||||
summary256[i * 3 + 2] = (float) sqrt(sumsq / jcount);
|
||||
}
|
||||
|
||||
for (int i = sumLen; i < frames256; ++i)
|
||||
{
|
||||
// filling in the remaining bits with non-harming/contributing values
|
||||
// rms values are not "non-harming", so keep count of them:
|
||||
summaries--;
|
||||
summary256[i * 3] = FLT_MAX; // min
|
||||
summary256[i * 3 + 1] = -FLT_MAX; // max
|
||||
summary256[i * 3 + 2] = 0.0f; // rms
|
||||
}
|
||||
|
||||
// Calculate now while we can do it accurately
|
||||
mSumRms = sqrt(totalSquares / mSampleCount);
|
||||
|
||||
// Recalc 64K summaries
|
||||
sumLen = (mSampleCount + 65535) / 65536;
|
||||
|
||||
for (int i = 0; i < sumLen; ++i)
|
||||
{
|
||||
min = summary256[3 * i * 256];
|
||||
max = summary256[3 * i * 256 + 1];
|
||||
sumsq = summary256[3 * i * 256 + 2];
|
||||
sumsq *= sumsq;
|
||||
|
||||
for (int j = 1; j < 256; ++j)
|
||||
{
|
||||
// we can overflow the useful summary256 values here, but have put
|
||||
// non-harmful values in them
|
||||
if (summary256[3 * (i * 256 + j)] < min)
|
||||
{
|
||||
min = summary256[3 * (i * 256 + j)];
|
||||
}
|
||||
|
||||
if (summary256[3 * (i * 256 + j) + 1] > max)
|
||||
{
|
||||
max = summary256[3 * (i * 256 + j) + 1];
|
||||
}
|
||||
|
||||
float r1 = summary256[3 * (i * 256 + j) + 2];
|
||||
sumsq += r1 * r1;
|
||||
}
|
||||
|
||||
double denom = (i < sumLen - 1) ? 256.0 : summaries - fraction;
|
||||
float rms = (float) sqrt(sumsq / denom);
|
||||
|
||||
summary64k[i * 3] = min;
|
||||
summary64k[i * 3 + 1] = max;
|
||||
summary64k[i * 3 + 2] = rms;
|
||||
}
|
||||
|
||||
for (int i = sumLen; i < frames64k; ++i)
|
||||
{
|
||||
wxASSERT_MSG(false, wxT("Out of data for mSummaryInfo")); // Do we ever get here?
|
||||
|
||||
summary64k[i * 3] = 0.0f; // probably should be FLT_MAX, need a test case
|
||||
summary64k[i * 3 + 1] = 0.0f; // probably should be -FLT_MAX, need a test case
|
||||
summary64k[i * 3 + 2] = 0.0f; // just padding
|
||||
}
|
||||
|
||||
// Recalc block-level summary (mRMS already calculated)
|
||||
min = summary64k[0];
|
||||
max = summary64k[1];
|
||||
|
||||
for (int i = 1; i < sumLen; ++i)
|
||||
{
|
||||
if (summary64k[i * 3] < min)
|
||||
{
|
||||
min = summary64k[i * 3];
|
||||
}
|
||||
|
||||
if (summary64k[i * 3 + 1] > max)
|
||||
{
|
||||
max = summary64k[i * 3 + 1];
|
||||
}
|
||||
}
|
||||
|
||||
mSumMin = min;
|
||||
mSumMax = max;
|
||||
}
|
||||
|
|
|
@ -11,7 +11,8 @@ SampleBlock.h
|
|||
|
||||
#include "ClientData.h" // to inherit
|
||||
|
||||
#include <sqlite3.h>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
class AudacityProject;
|
||||
class ProjectFileIO;
|
||||
|
@ -19,7 +20,13 @@ class XMLWriter;
|
|||
|
||||
class SampleBlock;
|
||||
using SampleBlockPtr = std::shared_ptr<SampleBlock>;
|
||||
using SampleBlockID = sqlite3_int64;
|
||||
class SampleBlockFactory;
|
||||
using SampleBlockFactoryPtr = std::shared_ptr<SampleBlockFactory>;
|
||||
using SampleBlockFactoryFactory =
|
||||
std::function< SampleBlockFactoryPtr( AudacityProject& ) >;
|
||||
|
||||
//using SampleBlockID = sqlite3_int64; // Trying not to depend on sqlite headers
|
||||
using SampleBlockID = long long;
|
||||
|
||||
class MinMaxRMS
|
||||
{
|
||||
|
@ -29,107 +36,73 @@ public:
|
|||
float RMS;
|
||||
};
|
||||
|
||||
class SqliteSampleBlockFactory;
|
||||
|
||||
///\brief Abstract class allows access to contents of a block of sound samples,
|
||||
/// serialization as XML, and reference count management that can suppress
|
||||
/// reclamation of its storage
|
||||
class SampleBlock
|
||||
{
|
||||
public:
|
||||
SampleBlock(AudacityProject *project);
|
||||
virtual ~SampleBlock();
|
||||
|
||||
static SampleBlockPtr Get(AudacityProject *project,
|
||||
SampleBlockID sbid);
|
||||
virtual void Lock() = 0;
|
||||
virtual void Unlock() = 0;
|
||||
virtual void CloseLock() = 0;
|
||||
|
||||
virtual SampleBlockID GetBlockID() = 0;
|
||||
|
||||
static SampleBlockPtr Create(AudacityProject *project,
|
||||
samplePtr src,
|
||||
size_t numsamples,
|
||||
sampleFormat srcformat);
|
||||
|
||||
static SampleBlockPtr CreateSilent(AudacityProject *project,
|
||||
size_t numsamples,
|
||||
sampleFormat srcformat);
|
||||
|
||||
static SampleBlockPtr CreateFromXML(AudacityProject *project,
|
||||
sampleFormat srcformat,
|
||||
const wxChar **attrs);
|
||||
|
||||
void Lock();
|
||||
void Unlock();
|
||||
void CloseLock();
|
||||
|
||||
bool SetSamples(samplePtr src, size_t numsamples, sampleFormat srcformat);
|
||||
|
||||
bool SetSilent(size_t numsamples, sampleFormat srcformat);
|
||||
|
||||
bool Commit();
|
||||
|
||||
void Delete();
|
||||
|
||||
SampleBlockID GetBlockID();
|
||||
|
||||
size_t GetSamples(samplePtr dest,
|
||||
virtual size_t GetSamples(samplePtr dest,
|
||||
sampleFormat destformat,
|
||||
size_t sampleoffset,
|
||||
size_t numsamples);
|
||||
sampleFormat GetSampleFormat() const;
|
||||
size_t GetSampleCount() const;
|
||||
size_t numsamples) = 0;
|
||||
|
||||
bool GetSummary256(float *dest, size_t frameoffset, size_t numframes);
|
||||
bool GetSummary64k(float *dest, size_t frameoffset, size_t numframes);
|
||||
double GetSumMin() const;
|
||||
double GetSumMax() const;
|
||||
double GetSumRms() const;
|
||||
virtual size_t GetSampleCount() const = 0;
|
||||
|
||||
virtual bool
|
||||
GetSummary256(float *dest, size_t frameoffset, size_t numframes) = 0;
|
||||
virtual bool
|
||||
GetSummary64k(float *dest, size_t frameoffset, size_t numframes) = 0;
|
||||
|
||||
/// Gets extreme values for the specified region
|
||||
MinMaxRMS GetMinMaxRMS(size_t start, size_t len);
|
||||
virtual MinMaxRMS GetMinMaxRMS(size_t start, size_t len) = 0;
|
||||
|
||||
/// Gets extreme values for the entire block
|
||||
MinMaxRMS GetMinMaxRMS() const;
|
||||
virtual MinMaxRMS GetMinMaxRMS() const = 0;
|
||||
|
||||
size_t GetSpaceUsage() const;
|
||||
void SaveXML(XMLWriter &xmlFile);
|
||||
virtual size_t GetSpaceUsage() const = 0;
|
||||
|
||||
private:
|
||||
bool Load(SampleBlockID sbid);
|
||||
bool GetSummary(float *dest,
|
||||
size_t frameoffset,
|
||||
size_t numframes,
|
||||
const char *srccolumn,
|
||||
size_t srcbytes);
|
||||
size_t GetBlob(void *dest,
|
||||
sampleFormat destformat,
|
||||
const char *srccolumn,
|
||||
sampleFormat srcformat,
|
||||
size_t srcoffset,
|
||||
size_t srcbytes);
|
||||
void CalcSummary();
|
||||
virtual void SaveXML(XMLWriter &xmlFile) = 0;
|
||||
};
|
||||
|
||||
private:
|
||||
ProjectFileIO & mIO;
|
||||
bool mValid;
|
||||
bool mDirty;
|
||||
bool mSilent;
|
||||
int mRefCnt;
|
||||
///\brief abstract base class with methods to produce @ref SampleBlock objects
|
||||
class SampleBlockFactory
|
||||
{
|
||||
public:
|
||||
// Install global function that produces a sample block factory object for
|
||||
// a given project; the factory has methods that later make sample blocks.
|
||||
// Return the previously installed factory.
|
||||
static SampleBlockFactoryFactory RegisterFactoryFactory(
|
||||
SampleBlockFactoryFactory newFactory );
|
||||
|
||||
SampleBlockID mBlockID;
|
||||
// Invoke the installed factory (throw an exception if none was installed)
|
||||
static SampleBlockFactoryPtr New( AudacityProject &project );
|
||||
|
||||
ArrayOf<char> mSamples;
|
||||
size_t mSampleBytes;
|
||||
size_t mSampleCount;
|
||||
sampleFormat mSampleFormat;
|
||||
virtual ~SampleBlockFactory();
|
||||
|
||||
ArrayOf<char> mSummary256;
|
||||
size_t mSummary256Bytes;
|
||||
ArrayOf<char> mSummary64k;
|
||||
size_t mSummary64kBytes;
|
||||
double mSumMin;
|
||||
double mSumMax;
|
||||
double mSumRms;
|
||||
virtual SampleBlockPtr Get(SampleBlockID sbid) = 0;
|
||||
|
||||
const char *columns =
|
||||
"sampleformat, summin, summax, sumrms, summary256, summary64k, samples";
|
||||
virtual SampleBlockPtr Create(samplePtr src,
|
||||
size_t numsamples,
|
||||
sampleFormat srcformat) = 0;
|
||||
|
||||
#if defined(WORDS_BIGENDIAN)
|
||||
#error All sample block data is little endian...big endian not yet supported
|
||||
#endif
|
||||
virtual SampleBlockPtr CreateSilent(
|
||||
size_t numsamples,
|
||||
sampleFormat srcformat) = 0;
|
||||
|
||||
virtual SampleBlockPtr CreateFromXML(
|
||||
sampleFormat srcformat,
|
||||
const wxChar **attrs) = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -47,8 +47,9 @@
|
|||
size_t Sequence::sMaxDiskBlockSize = 1048576;
|
||||
|
||||
// Sequence methods
|
||||
Sequence::Sequence(AudacityProject *project, sampleFormat format)
|
||||
: mProject(project),
|
||||
Sequence::Sequence(
|
||||
const SampleBlockFactoryPtr &pFactory, sampleFormat format)
|
||||
: mpFactory(pFactory),
|
||||
mSampleFormat(format),
|
||||
mMinSamples(sMaxDiskBlockSize / SAMPLE_SIZE(mSampleFormat) / 2),
|
||||
mMaxSamples(mMinSamples * 2)
|
||||
|
@ -58,8 +59,9 @@ Sequence::Sequence(AudacityProject *project, sampleFormat format)
|
|||
// essentially a copy constructor - but you must pass in the
|
||||
// current project, because we might be copying from one
|
||||
// project to another
|
||||
Sequence::Sequence(const Sequence &orig, AudacityProject *project)
|
||||
: mProject(project),
|
||||
Sequence::Sequence(
|
||||
const Sequence &orig, const SampleBlockFactoryPtr &pFactory)
|
||||
: mpFactory(pFactory),
|
||||
mSampleFormat(orig.mSampleFormat),
|
||||
mMinSamples(orig.mMinSamples),
|
||||
mMaxSamples(orig.mMaxSamples)
|
||||
|
@ -211,7 +213,7 @@ bool Sequence::ConvertToSampleFormat(sampleFormat format)
|
|||
|
||||
// Using Blockify will handle the cases where len > the NEW mMaxSamples. Previous code did not.
|
||||
const auto blockstart = oldSeqBlock.start;
|
||||
Blockify(mProject, mMaxSamples, mSampleFormat,
|
||||
Blockify(*mpFactory, mMaxSamples, mSampleFormat,
|
||||
newBlockArray, blockstart, bufferNew.ptr(), len);
|
||||
}
|
||||
}
|
||||
|
@ -380,7 +382,7 @@ float Sequence::GetRMS(sampleCount start, sampleCount len, bool mayThrow) const
|
|||
|
||||
std::unique_ptr<Sequence> Sequence::Copy(sampleCount s0, sampleCount s1) const
|
||||
{
|
||||
auto dest = std::make_unique<Sequence>(mProject, mSampleFormat);
|
||||
auto dest = std::make_unique<Sequence>(mpFactory, mSampleFormat);
|
||||
if (s0 >= s1 || s0 >= mNumSamples || s1 < 0) {
|
||||
return dest;
|
||||
}
|
||||
|
@ -457,6 +459,8 @@ namespace {
|
|||
void Sequence::Paste(sampleCount s, const Sequence *src)
|
||||
// STRONG-GUARANTEE
|
||||
{
|
||||
auto &factory = *mpFactory;
|
||||
|
||||
if ((s < 0) || (s > mNumSamples))
|
||||
{
|
||||
wxLogError(
|
||||
|
@ -545,10 +549,10 @@ void Sequence::Paste(sampleCount s, const Sequence *src)
|
|||
splitPoint, length - splitPoint, true);
|
||||
|
||||
// largerBlockLen is not more than mMaxSamples...
|
||||
block.sb = SampleBlock::Create(mProject,
|
||||
buffer.ptr(),
|
||||
largerBlockLen.as_size_t(),
|
||||
mSampleFormat);
|
||||
block.sb = factory.Create(
|
||||
buffer.ptr(),
|
||||
largerBlockLen.as_size_t(),
|
||||
mSampleFormat);
|
||||
|
||||
// Don't make a duplicate array. We can still give STRONG-GUARANTEE
|
||||
// if we modify only one block in place.
|
||||
|
@ -594,7 +598,7 @@ void Sequence::Paste(sampleCount s, const Sequence *src)
|
|||
splitBlock, splitPoint,
|
||||
splitLen - splitPoint, true);
|
||||
|
||||
Blockify(mProject, mMaxSamples, mSampleFormat,
|
||||
Blockify(*mpFactory, mMaxSamples, mSampleFormat,
|
||||
newBlock, splitBlock.start, sumBuffer.ptr(), sum);
|
||||
} else {
|
||||
|
||||
|
@ -621,7 +625,7 @@ void Sequence::Paste(sampleCount s, const Sequence *src)
|
|||
src->Get(0, sampleBuffer.ptr() + splitPoint*sampleSize,
|
||||
mSampleFormat, 0, srcFirstTwoLen, true);
|
||||
|
||||
Blockify(mProject, mMaxSamples, mSampleFormat,
|
||||
Blockify(*mpFactory, mMaxSamples, mSampleFormat,
|
||||
newBlock, splitBlock.start, sampleBuffer.ptr(), leftLen);
|
||||
|
||||
for (i = 2; i < srcNumBlocks - 2; i++) {
|
||||
|
@ -635,7 +639,7 @@ void Sequence::Paste(sampleCount s, const Sequence *src)
|
|||
Read(sampleBuffer.ptr() + srcLastTwoLen * sampleSize, mSampleFormat,
|
||||
splitBlock, splitPoint, rightSplit, true);
|
||||
|
||||
Blockify(mProject, mMaxSamples, mSampleFormat,
|
||||
Blockify(*mpFactory, mMaxSamples, mSampleFormat,
|
||||
newBlock, s + lastStart, sampleBuffer.ptr(), rightLen);
|
||||
}
|
||||
|
||||
|
@ -657,6 +661,8 @@ void Sequence::SetSilence(sampleCount s0, sampleCount len)
|
|||
void Sequence::InsertSilence(sampleCount s0, sampleCount len)
|
||||
// STRONG-GUARANTEE
|
||||
{
|
||||
auto &factory = *mpFactory;
|
||||
|
||||
// Quick check to make sure that it doesn't overflow
|
||||
if (Overflows((mNumSamples.as_double()) + (len.as_double())))
|
||||
THROW_INCONSISTENCY_EXCEPTION;
|
||||
|
@ -669,7 +675,7 @@ void Sequence::InsertSilence(sampleCount s0, sampleCount len)
|
|||
// We make use of a SilentBlockFile, which takes up no
|
||||
// space on disk.
|
||||
|
||||
Sequence sTrack(mProject, mSampleFormat);
|
||||
Sequence sTrack(mpFactory, mSampleFormat);
|
||||
|
||||
auto idealSamples = GetIdealBlockSize();
|
||||
|
||||
|
@ -681,9 +687,9 @@ void Sequence::InsertSilence(sampleCount s0, sampleCount len)
|
|||
sTrack.mBlock.reserve(nBlocks.as_size_t());
|
||||
|
||||
if (len >= idealSamples) {
|
||||
auto silentFile = SampleBlock::CreateSilent(mProject,
|
||||
idealSamples,
|
||||
mSampleFormat);
|
||||
auto silentFile = factory.CreateSilent(
|
||||
idealSamples,
|
||||
mSampleFormat);
|
||||
while (len >= idealSamples) {
|
||||
sTrack.mBlock.push_back(SeqBlock(silentFile, pos));
|
||||
|
||||
|
@ -694,7 +700,7 @@ void Sequence::InsertSilence(sampleCount s0, sampleCount len)
|
|||
if (len != 0) {
|
||||
// len is not more than idealSamples:
|
||||
sTrack.mBlock.push_back(SeqBlock(
|
||||
SampleBlock::CreateSilent(mProject, len.as_size_t(), mSampleFormat), pos));
|
||||
factory.CreateSilent(len.as_size_t(), mSampleFormat), pos));
|
||||
pos += len;
|
||||
}
|
||||
|
||||
|
@ -759,13 +765,15 @@ size_t Sequence::GetBestBlockSize(sampleCount start) const
|
|||
|
||||
bool Sequence::HandleXMLTag(const wxChar *tag, const wxChar **attrs)
|
||||
{
|
||||
auto &factory = *mpFactory;
|
||||
|
||||
/* handle waveblock tag and its attributes */
|
||||
if (!wxStrcmp(tag, wxT("waveblock")))
|
||||
{
|
||||
SeqBlock wb;
|
||||
|
||||
// Give SampleBlock a go at the attributes first
|
||||
wb.sb = SampleBlock::CreateFromXML(mProject, mSampleFormat, attrs);
|
||||
wb.sb = factory.CreateFromXML(mSampleFormat, attrs);
|
||||
if (wb.sb == nullptr)
|
||||
{
|
||||
mErrorOpening = true;
|
||||
|
@ -1085,6 +1093,8 @@ void Sequence::SetSamples(samplePtr buffer, sampleFormat format,
|
|||
sampleCount start, sampleCount len)
|
||||
// STRONG-GUARANTEE
|
||||
{
|
||||
auto &factory = *mpFactory;
|
||||
|
||||
const auto size = mBlock.size();
|
||||
|
||||
if (start < 0 || start + len > mNumSamples)
|
||||
|
@ -1161,17 +1171,17 @@ void Sequence::SetSamples(samplePtr buffer, sampleFormat format,
|
|||
else
|
||||
ClearSamples(scratch.ptr(), mSampleFormat, bstart, blen);
|
||||
|
||||
block.sb = SampleBlock::Create(mProject,
|
||||
scratch.ptr(),
|
||||
fileLength,
|
||||
mSampleFormat);
|
||||
block.sb = factory.Create(
|
||||
scratch.ptr(),
|
||||
fileLength,
|
||||
mSampleFormat);
|
||||
}
|
||||
else {
|
||||
// Avoid reading the disk when the replacement is total
|
||||
if (useBuffer)
|
||||
block.sb = SampleBlock::Create(mProject, useBuffer, fileLength, mSampleFormat);
|
||||
block.sb = factory.Create(useBuffer, fileLength, mSampleFormat);
|
||||
else
|
||||
block.sb = SampleBlock::CreateSilent(mProject, fileLength, mSampleFormat);
|
||||
block.sb = factory.CreateSilent(fileLength, mSampleFormat);
|
||||
}
|
||||
|
||||
// blen might be zero for inconsistent Sequence...
|
||||
|
@ -1451,6 +1461,8 @@ void Sequence::Append(samplePtr buffer, sampleFormat format, size_t len)
|
|||
if (len == 0)
|
||||
return;
|
||||
|
||||
auto &factory = *mpFactory;
|
||||
|
||||
// Quick check to make sure that it doesn't overflow
|
||||
if (Overflows(mNumSamples.as_double() + ((double)len)))
|
||||
THROW_INCONSISTENCY_EXCEPTION;
|
||||
|
@ -1481,10 +1493,10 @@ void Sequence::Append(samplePtr buffer, sampleFormat format, size_t len)
|
|||
addLen);
|
||||
|
||||
const auto newLastBlockLen = length + addLen;
|
||||
SampleBlockPtr pBlock = SampleBlock::Create(mProject,
|
||||
buffer2.ptr(),
|
||||
newLastBlockLen,
|
||||
mSampleFormat);
|
||||
SampleBlockPtr pBlock = factory.Create(
|
||||
buffer2.ptr(),
|
||||
newLastBlockLen,
|
||||
mSampleFormat);
|
||||
SeqBlock newLastBlock(pBlock, lastBlock.start);
|
||||
|
||||
newBlock.push_back( newLastBlock );
|
||||
|
@ -1501,11 +1513,11 @@ void Sequence::Append(samplePtr buffer, sampleFormat format, size_t len)
|
|||
const auto addedLen = std::min(idealSamples, len);
|
||||
SampleBlockPtr pBlock;
|
||||
if (format == mSampleFormat) {
|
||||
pBlock = SampleBlock::Create(mProject, buffer, addedLen, mSampleFormat);
|
||||
pBlock = factory.Create(buffer, addedLen, mSampleFormat);
|
||||
}
|
||||
else {
|
||||
CopySamples(buffer, format, buffer2.ptr(), mSampleFormat, addedLen);
|
||||
pBlock = SampleBlock::Create(mProject, buffer2.ptr(), addedLen, mSampleFormat);
|
||||
pBlock = factory.Create(buffer2.ptr(), addedLen, mSampleFormat);
|
||||
}
|
||||
|
||||
newBlock.push_back(SeqBlock(pBlock, newNumSamples));
|
||||
|
@ -1526,12 +1538,13 @@ void Sequence::Append(samplePtr buffer, sampleFormat format, size_t len)
|
|||
#endif
|
||||
}
|
||||
|
||||
void Sequence::Blockify(AudacityProject *project,
|
||||
void Sequence::Blockify(SampleBlockFactory &factory,
|
||||
size_t mMaxSamples, sampleFormat mSampleFormat,
|
||||
BlockArray &list, sampleCount start, samplePtr buffer, size_t len)
|
||||
{
|
||||
if (len <= 0)
|
||||
return;
|
||||
|
||||
auto num = (len + (mMaxSamples - 1)) / mMaxSamples;
|
||||
list.reserve(list.size() + num);
|
||||
|
||||
|
@ -1543,7 +1556,7 @@ void Sequence::Blockify(AudacityProject *project,
|
|||
int newLen = ((i + 1) * len / num) - offset;
|
||||
samplePtr bufStart = buffer + (offset * SAMPLE_SIZE(mSampleFormat));
|
||||
|
||||
b.sb = SampleBlock::Create(project, bufStart, newLen, mSampleFormat);
|
||||
b.sb = factory.Create(bufStart, newLen, mSampleFormat);
|
||||
|
||||
list.push_back(b);
|
||||
}
|
||||
|
@ -1558,6 +1571,8 @@ void Sequence::Delete(sampleCount start, sampleCount len)
|
|||
if (len < 0 || start < 0 || start + len > mNumSamples)
|
||||
THROW_INCONSISTENCY_EXCEPTION;
|
||||
|
||||
auto &factory = *mpFactory;
|
||||
|
||||
const unsigned int numBlocks = mBlock.size();
|
||||
|
||||
const unsigned int b0 = FindBlock(start);
|
||||
|
@ -1599,7 +1614,7 @@ void Sequence::Delete(sampleCount start, sampleCount len)
|
|||
// is not more than the length of the block
|
||||
( pos + len ).as_size_t(), newLen - pos, true);
|
||||
|
||||
b.sb = SampleBlock::Create(mProject, scratch.ptr(), newLen, mSampleFormat);
|
||||
b.sb = factory.Create(scratch.ptr(), newLen, mSampleFormat);
|
||||
|
||||
// Don't make a duplicate array. We can still give STRONG-GUARANTEE
|
||||
// if we modify only one block in place.
|
||||
|
@ -1641,7 +1656,7 @@ void Sequence::Delete(sampleCount start, sampleCount len)
|
|||
ensureSampleBufferSize(scratch, mSampleFormat, scratchSize, preBufferLen);
|
||||
Read(scratch.ptr(), mSampleFormat, preBlock, 0, preBufferLen, true);
|
||||
auto pFile =
|
||||
SampleBlock::Create(mProject, scratch.ptr(), preBufferLen, mSampleFormat);
|
||||
factory.Create(scratch.ptr(), preBufferLen, mSampleFormat);
|
||||
|
||||
newBlock.push_back(SeqBlock(pFile, preBlock.start));
|
||||
} else {
|
||||
|
@ -1659,7 +1674,7 @@ void Sequence::Delete(sampleCount start, sampleCount len)
|
|||
preBlock, 0, preBufferLen, true);
|
||||
|
||||
newBlock.pop_back();
|
||||
Blockify(mProject, mMaxSamples, mSampleFormat,
|
||||
Blockify(*mpFactory, mMaxSamples, mSampleFormat,
|
||||
newBlock, prepreBlock.start, scratch.ptr(), sum);
|
||||
}
|
||||
}
|
||||
|
@ -1687,7 +1702,7 @@ void Sequence::Delete(sampleCount start, sampleCount len)
|
|||
auto pos = (start + len - postBlock.start).as_size_t();
|
||||
Read(scratch.ptr(), mSampleFormat, postBlock, pos, postBufferLen, true);
|
||||
auto file =
|
||||
SampleBlock::Create(mProject, scratch.ptr(), postBufferLen, mSampleFormat);
|
||||
factory.Create(scratch.ptr(), postBufferLen, mSampleFormat);
|
||||
|
||||
newBlock.push_back(SeqBlock(file, start));
|
||||
} else {
|
||||
|
@ -1704,7 +1719,7 @@ void Sequence::Delete(sampleCount start, sampleCount len)
|
|||
Read(scratch.ptr() + (postBufferLen * sampleSize), mSampleFormat,
|
||||
postpostBlock, 0, postpostLen, true);
|
||||
|
||||
Blockify(mProject, mMaxSamples, mSampleFormat,
|
||||
Blockify(*mpFactory, mMaxSamples, mSampleFormat,
|
||||
newBlock, start, scratch.ptr(), sum);
|
||||
b1++;
|
||||
}
|
||||
|
|
|
@ -18,8 +18,9 @@
|
|||
|
||||
#include "audacity/Types.h"
|
||||
|
||||
class AudacityProject;
|
||||
class SampleBlock;
|
||||
class SampleBlockFactory;
|
||||
using SampleBlockFactoryPtr = std::shared_ptr<SampleBlockFactory>;
|
||||
|
||||
// This is an internal data structure! For advanced use only.
|
||||
class SeqBlock {
|
||||
|
@ -60,9 +61,9 @@ class PROFILE_DLL_API Sequence final : public XMLTagHandler{
|
|||
// Constructor / Destructor / Duplicator
|
||||
//
|
||||
|
||||
Sequence(AudacityProject *project, sampleFormat format);
|
||||
Sequence(const SampleBlockFactoryPtr &pFactory, sampleFormat format);
|
||||
|
||||
Sequence(const Sequence &orig, AudacityProject *project);
|
||||
Sequence(const Sequence &orig, const SampleBlockFactoryPtr &pFactory);
|
||||
|
||||
Sequence& operator= (const Sequence&) PROHIBITED;
|
||||
|
||||
|
@ -103,7 +104,7 @@ class PROFILE_DLL_API Sequence final : public XMLTagHandler{
|
|||
void SetSilence(sampleCount s0, sampleCount len);
|
||||
void InsertSilence(sampleCount s0, sampleCount len);
|
||||
|
||||
AudacityProject *GetProject() { return mProject; }
|
||||
const SampleBlockFactoryPtr &GetFactory() { return mpFactory; }
|
||||
|
||||
//
|
||||
// XMLTagHandler callback methods for loading and saving
|
||||
|
@ -178,7 +179,7 @@ class PROFILE_DLL_API Sequence final : public XMLTagHandler{
|
|||
// Private variables
|
||||
//
|
||||
|
||||
AudacityProject *mProject;
|
||||
SampleBlockFactoryPtr mpFactory;
|
||||
|
||||
BlockArray mBlock;
|
||||
sampleFormat mSampleFormat;
|
||||
|
@ -211,7 +212,7 @@ class PROFILE_DLL_API Sequence final : public XMLTagHandler{
|
|||
// Accumulate NEW block files onto the end of a block array.
|
||||
// Does not change this sequence. The intent is to use
|
||||
// CommitChangesIfConsistent later.
|
||||
static void Blockify(AudacityProject *project,
|
||||
static void Blockify(SampleBlockFactory &factory,
|
||||
size_t maxSamples,
|
||||
sampleFormat format,
|
||||
BlockArray &list,
|
||||
|
|
|
@ -0,0 +1,881 @@
|
|||
/**********************************************************************
|
||||
|
||||
Audacity: A Digital Audio Editor
|
||||
|
||||
SqliteSampleBlock.cpp
|
||||
|
||||
Paul Licameli -- split from SampleBlock.cpp and SampleBlock.h
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#include <float.h>
|
||||
#include <sqlite3.h>
|
||||
|
||||
#include "SampleFormat.h"
|
||||
#include "ProjectFileIO.h"
|
||||
#include "xml/XMLTagHandler.h"
|
||||
|
||||
#include "SampleBlock.h" // to inherit
|
||||
|
||||
///\brief Implementation of @ref SampleBlock using Sqlite database
|
||||
class SqliteSampleBlock final : public SampleBlock
|
||||
{
|
||||
public:
|
||||
|
||||
explicit SqliteSampleBlock(AudacityProject *project);
|
||||
~SqliteSampleBlock() override;
|
||||
|
||||
void Lock() override;
|
||||
void Unlock() override;
|
||||
void CloseLock() override;
|
||||
|
||||
bool SetSamples(samplePtr src, size_t numsamples, sampleFormat srcformat);
|
||||
|
||||
bool SetSilent(size_t numsamples, sampleFormat srcformat);
|
||||
|
||||
bool Commit();
|
||||
|
||||
void Delete();
|
||||
|
||||
SampleBlockID GetBlockID() override;
|
||||
|
||||
size_t GetSamples(samplePtr dest,
|
||||
sampleFormat destformat,
|
||||
size_t sampleoffset,
|
||||
size_t numsamples) override;
|
||||
sampleFormat GetSampleFormat() const;
|
||||
size_t GetSampleCount() const override;
|
||||
|
||||
bool GetSummary256(float *dest, size_t frameoffset, size_t numframes) override;
|
||||
bool GetSummary64k(float *dest, size_t frameoffset, size_t numframes) override;
|
||||
double GetSumMin() const;
|
||||
double GetSumMax() const;
|
||||
double GetSumRms() const;
|
||||
|
||||
/// Gets extreme values for the specified region
|
||||
MinMaxRMS GetMinMaxRMS(size_t start, size_t len) override;
|
||||
|
||||
/// Gets extreme values for the entire block
|
||||
MinMaxRMS GetMinMaxRMS() const override;
|
||||
|
||||
size_t GetSpaceUsage() const override;
|
||||
void SaveXML(XMLWriter &xmlFile) override;
|
||||
|
||||
private:
|
||||
bool Load(SampleBlockID sbid);
|
||||
bool GetSummary(float *dest,
|
||||
size_t frameoffset,
|
||||
size_t numframes,
|
||||
const char *srccolumn,
|
||||
size_t srcbytes);
|
||||
size_t GetBlob(void *dest,
|
||||
sampleFormat destformat,
|
||||
const char *srccolumn,
|
||||
sampleFormat srcformat,
|
||||
size_t srcoffset,
|
||||
size_t srcbytes);
|
||||
void CalcSummary();
|
||||
|
||||
private:
|
||||
friend SqliteSampleBlockFactory;
|
||||
|
||||
ProjectFileIO & mIO;
|
||||
bool mValid;
|
||||
bool mDirty;
|
||||
bool mSilent;
|
||||
int mRefCnt;
|
||||
|
||||
SampleBlockID mBlockID;
|
||||
|
||||
ArrayOf<char> mSamples;
|
||||
size_t mSampleBytes;
|
||||
size_t mSampleCount;
|
||||
sampleFormat mSampleFormat;
|
||||
|
||||
ArrayOf<char> mSummary256;
|
||||
size_t mSummary256Bytes;
|
||||
ArrayOf<char> mSummary64k;
|
||||
size_t mSummary64kBytes;
|
||||
double mSumMin;
|
||||
double mSumMax;
|
||||
double mSumRms;
|
||||
|
||||
const char *columns =
|
||||
"sampleformat, summin, summax, sumrms, summary256, summary64k, samples";
|
||||
|
||||
#if defined(WORDS_BIGENDIAN)
|
||||
#error All sample block data is little endian...big endian not yet supported
|
||||
#endif
|
||||
};
|
||||
|
||||
///\brief Implementation of @ref SampleBlockFactory using Sqlite database
|
||||
class SqliteSampleBlockFactory final : public SampleBlockFactory
|
||||
{
|
||||
public:
|
||||
explicit SqliteSampleBlockFactory( AudacityProject &project );
|
||||
|
||||
~SqliteSampleBlockFactory() override;
|
||||
|
||||
SampleBlockPtr Get(SampleBlockID sbid) override;
|
||||
|
||||
SampleBlockPtr Create(samplePtr src,
|
||||
size_t numsamples,
|
||||
sampleFormat srcformat) override;
|
||||
|
||||
SampleBlockPtr CreateSilent(
|
||||
size_t numsamples,
|
||||
sampleFormat srcformat) override;
|
||||
|
||||
SampleBlockPtr CreateFromXML(
|
||||
sampleFormat srcformat,
|
||||
const wxChar **attrs) override;
|
||||
|
||||
private:
|
||||
AudacityProject &mProject;
|
||||
std::shared_ptr<ProjectFileIO> mpIO;
|
||||
};
|
||||
|
||||
SqliteSampleBlockFactory::SqliteSampleBlockFactory( AudacityProject &project )
|
||||
: mProject{ project }
|
||||
, mpIO{ ProjectFileIO::Get(project).shared_from_this() }
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
SqliteSampleBlockFactory::~SqliteSampleBlockFactory() = default;
|
||||
|
||||
SampleBlockPtr SqliteSampleBlockFactory::Create(
|
||||
samplePtr src, size_t numsamples, sampleFormat srcformat )
|
||||
{
|
||||
auto sb = std::make_shared<SqliteSampleBlock>(&mProject);
|
||||
|
||||
if (sb)
|
||||
{
|
||||
if (sb->SetSamples(src, numsamples, srcformat))
|
||||
{
|
||||
return sb;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SampleBlockPtr SqliteSampleBlockFactory::CreateSilent(
|
||||
size_t numsamples, sampleFormat srcformat )
|
||||
{
|
||||
auto sb = std::make_shared<SqliteSampleBlock>(&mProject);
|
||||
|
||||
if (sb)
|
||||
{
|
||||
if (sb->SetSilent(numsamples, srcformat))
|
||||
{
|
||||
return sb;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
SampleBlockPtr SqliteSampleBlockFactory::CreateFromXML(
|
||||
sampleFormat srcformat, const wxChar **attrs )
|
||||
{
|
||||
auto sb = std::make_shared<SqliteSampleBlock>(&mProject);
|
||||
sb->mSampleFormat = srcformat;
|
||||
|
||||
int found = 0;
|
||||
|
||||
// loop through attrs, which is a null-terminated list of attribute-value pairs
|
||||
while(*attrs)
|
||||
{
|
||||
const wxChar *attr = *attrs++;
|
||||
const wxChar *value = *attrs++;
|
||||
|
||||
if (!value)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
const wxString strValue = value; // promote string, we need this for all
|
||||
double dblValue;
|
||||
long long nValue;
|
||||
|
||||
if (XMLValueChecker::IsGoodInt(strValue) && strValue.ToLongLong(&nValue) && (nValue >= 0))
|
||||
{
|
||||
if (wxStrcmp(attr, wxT("blockid")) == 0)
|
||||
{
|
||||
if (!sb->Load((SampleBlockID) nValue))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
found++;
|
||||
}
|
||||
else if (wxStrcmp(attr, wxT("samplecount")) == 0)
|
||||
{
|
||||
sb->mSampleCount = nValue;
|
||||
sb->mSampleBytes = sb->mSampleCount * SAMPLE_SIZE(sb->mSampleFormat);
|
||||
found++;
|
||||
}
|
||||
}
|
||||
else if (XMLValueChecker::IsGoodString(strValue) && Internat::CompatibleToDouble(strValue, &dblValue))
|
||||
{
|
||||
if (wxStricmp(attr, wxT("min")) == 0)
|
||||
{
|
||||
sb->mSumMin = dblValue;
|
||||
found++;
|
||||
}
|
||||
else if (wxStricmp(attr, wxT("max")) == 0)
|
||||
{
|
||||
sb->mSumMax = dblValue;
|
||||
found++;
|
||||
}
|
||||
else if ((wxStricmp(attr, wxT("rms")) == 0) && (dblValue >= 0.0))
|
||||
{
|
||||
sb->mSumRms = dblValue;
|
||||
found++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Were all attributes found?
|
||||
if (found != 5)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return sb;
|
||||
}
|
||||
|
||||
SampleBlockPtr SqliteSampleBlockFactory::Get( SampleBlockID sbid )
|
||||
{
|
||||
auto sb = std::make_shared<SqliteSampleBlock>(&mProject);
|
||||
|
||||
if (sb)
|
||||
{
|
||||
if (!sb->Load(sbid))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return sb;
|
||||
}
|
||||
|
||||
SqliteSampleBlock::SqliteSampleBlock(AudacityProject *project)
|
||||
: mIO(ProjectFileIO::Get(*project))
|
||||
{
|
||||
mValid = false;
|
||||
mSilent = false;
|
||||
mRefCnt = 0;
|
||||
|
||||
mBlockID = 0;
|
||||
|
||||
mSampleFormat = floatSample;
|
||||
mSampleBytes = 0;
|
||||
mSampleCount = 0;
|
||||
|
||||
mSummary256Bytes = 0;
|
||||
mSummary64kBytes = 0;
|
||||
mSumMin = 0.0;
|
||||
mSumMax = 0.0;
|
||||
mSumRms = 0.0;
|
||||
}
|
||||
|
||||
SqliteSampleBlock::~SqliteSampleBlock()
|
||||
{
|
||||
if (mRefCnt == 0)
|
||||
{
|
||||
Delete();
|
||||
}
|
||||
}
|
||||
|
||||
void SqliteSampleBlock::Lock()
|
||||
{
|
||||
++mRefCnt;
|
||||
}
|
||||
|
||||
void SqliteSampleBlock::Unlock()
|
||||
{
|
||||
--mRefCnt;
|
||||
}
|
||||
|
||||
void SqliteSampleBlock::CloseLock()
|
||||
{
|
||||
Lock();
|
||||
}
|
||||
|
||||
SampleBlockID SqliteSampleBlock::GetBlockID()
|
||||
{
|
||||
return mBlockID;
|
||||
}
|
||||
|
||||
sampleFormat SqliteSampleBlock::GetSampleFormat() const
|
||||
{
|
||||
return mSampleFormat;
|
||||
}
|
||||
|
||||
size_t SqliteSampleBlock::GetSampleCount() const
|
||||
{
|
||||
return mSampleCount;
|
||||
}
|
||||
|
||||
size_t SqliteSampleBlock::GetSamples(samplePtr dest,
|
||||
sampleFormat destformat,
|
||||
size_t sampleoffset,
|
||||
size_t numsamples)
|
||||
{
|
||||
return GetBlob(dest,
|
||||
destformat,
|
||||
"samples",
|
||||
mSampleFormat,
|
||||
sampleoffset * SAMPLE_SIZE(mSampleFormat),
|
||||
numsamples * SAMPLE_SIZE(mSampleFormat)) / SAMPLE_SIZE(mSampleFormat);
|
||||
}
|
||||
|
||||
bool SqliteSampleBlock::SetSamples(samplePtr src,
|
||||
size_t numsamples,
|
||||
sampleFormat srcformat)
|
||||
{
|
||||
mSampleFormat = srcformat;
|
||||
|
||||
mSampleCount = numsamples;
|
||||
mSampleBytes = mSampleCount * SAMPLE_SIZE(mSampleFormat);
|
||||
mSamples.reinit(mSampleBytes);
|
||||
memcpy(mSamples.get(), src, mSampleBytes);
|
||||
|
||||
CalcSummary();
|
||||
|
||||
return Commit();
|
||||
}
|
||||
|
||||
bool SqliteSampleBlock::SetSilent(size_t numsamples, sampleFormat srcformat)
|
||||
{
|
||||
mSampleFormat = srcformat;
|
||||
|
||||
mSampleCount = numsamples;
|
||||
mSampleBytes = mSampleCount * SAMPLE_SIZE(mSampleFormat);
|
||||
mSamples.reinit(mSampleBytes);
|
||||
memset(mSamples.get(), 0, mSampleBytes);
|
||||
|
||||
CalcSummary();
|
||||
|
||||
mSilent = true;
|
||||
|
||||
return Commit();
|
||||
}
|
||||
|
||||
bool SqliteSampleBlock::GetSummary256(float *dest,
|
||||
size_t frameoffset,
|
||||
size_t numframes)
|
||||
{
|
||||
return GetSummary(dest, frameoffset, numframes, "summary256", mSummary256Bytes);
|
||||
}
|
||||
|
||||
bool SqliteSampleBlock::GetSummary64k(float *dest,
|
||||
size_t frameoffset,
|
||||
size_t numframes)
|
||||
{
|
||||
return GetSummary(dest, frameoffset, numframes, "summary64k", mSummary64kBytes);
|
||||
}
|
||||
|
||||
bool SqliteSampleBlock::GetSummary(float *dest,
|
||||
size_t frameoffset,
|
||||
size_t numframes,
|
||||
const char *srccolumn,
|
||||
size_t srcbytes)
|
||||
{
|
||||
return GetBlob(dest,
|
||||
floatSample,
|
||||
srccolumn,
|
||||
floatSample,
|
||||
frameoffset * 3 * SAMPLE_SIZE(floatSample),
|
||||
numframes * 3 * SAMPLE_SIZE(floatSample)) / 3 / SAMPLE_SIZE(floatSample);
|
||||
}
|
||||
|
||||
double SqliteSampleBlock::GetSumMin() const
|
||||
{
|
||||
return mSumMin;
|
||||
}
|
||||
|
||||
double SqliteSampleBlock::GetSumMax() const
|
||||
{
|
||||
return mSumMax;
|
||||
}
|
||||
|
||||
double SqliteSampleBlock::GetSumRms() const
|
||||
{
|
||||
return mSumRms;
|
||||
}
|
||||
|
||||
/// Retrieves the minimum, maximum, and maximum RMS of the
|
||||
/// specified sample data in this block.
|
||||
///
|
||||
/// @param start The offset in this block where the region should begin
|
||||
/// @param len The number of samples to include in the region
|
||||
MinMaxRMS SqliteSampleBlock::GetMinMaxRMS(size_t start, size_t len)
|
||||
{
|
||||
float min = FLT_MAX;
|
||||
float max = -FLT_MAX;
|
||||
float sumsq = 0;
|
||||
|
||||
if (!mValid && mBlockID)
|
||||
{
|
||||
Load(mBlockID);
|
||||
}
|
||||
|
||||
if (start < mSampleCount)
|
||||
{
|
||||
len = std::min(len, mSampleCount - start);
|
||||
|
||||
// TODO: actually use summaries
|
||||
SampleBuffer blockData(len, floatSample);
|
||||
float *samples = (float *) blockData.ptr();
|
||||
|
||||
size_t copied = GetBlob(samples,
|
||||
floatSample,
|
||||
"samples",
|
||||
mSampleFormat,
|
||||
start * SAMPLE_SIZE(mSampleFormat),
|
||||
len * SAMPLE_SIZE(mSampleFormat)) / SAMPLE_SIZE(mSampleFormat);
|
||||
for (size_t i = 0; i < copied; ++i, ++samples)
|
||||
{
|
||||
float sample = *samples;
|
||||
|
||||
if (sample > max)
|
||||
{
|
||||
max = sample;
|
||||
}
|
||||
|
||||
if (sample < min)
|
||||
{
|
||||
min = sample;
|
||||
}
|
||||
|
||||
sumsq += (sample * sample);
|
||||
}
|
||||
}
|
||||
|
||||
return { min, max, (float) sqrt(sumsq / len) };
|
||||
}
|
||||
|
||||
/// Retrieves the minimum, maximum, and maximum RMS of this entire
|
||||
/// block. This is faster than the other GetMinMax function since
|
||||
/// these values are already computed.
|
||||
MinMaxRMS SqliteSampleBlock::GetMinMaxRMS() const
|
||||
{
|
||||
return { (float) mSumMin, (float) mSumMax, (float) mSumRms };
|
||||
}
|
||||
|
||||
size_t SqliteSampleBlock::GetSpaceUsage() const
|
||||
{
|
||||
return mSampleCount * SAMPLE_SIZE(mSampleFormat);
|
||||
}
|
||||
|
||||
size_t SqliteSampleBlock::GetBlob(void *dest,
|
||||
sampleFormat destformat,
|
||||
const char *srccolumn,
|
||||
sampleFormat srcformat,
|
||||
size_t srcoffset,
|
||||
size_t srcbytes)
|
||||
{
|
||||
auto db = mIO.DB();
|
||||
|
||||
wxASSERT(mBlockID > 0);
|
||||
|
||||
if (!mValid && mBlockID)
|
||||
{
|
||||
Load(mBlockID);
|
||||
}
|
||||
|
||||
int rc;
|
||||
size_t minbytes = 0;
|
||||
|
||||
char sql[256];
|
||||
sqlite3_snprintf(sizeof(sql),
|
||||
sql,
|
||||
"SELECT %s FROM sampleblocks WHERE blockid = %d;",
|
||||
srccolumn,
|
||||
mBlockID);
|
||||
|
||||
sqlite3_stmt *stmt = nullptr;
|
||||
auto cleanup = finally([&]
|
||||
{
|
||||
if (stmt)
|
||||
{
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
});
|
||||
|
||||
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
|
||||
if (rc != SQLITE_OK)
|
||||
{
|
||||
wxLogDebug(wxT("SQLITE error %s"), sqlite3_errmsg(db));
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = sqlite3_step(stmt);
|
||||
if (rc != SQLITE_ROW)
|
||||
{
|
||||
wxLogDebug(wxT("SQLITE error %s"), sqlite3_errmsg(db));
|
||||
}
|
||||
else
|
||||
{
|
||||
samplePtr src = (samplePtr) sqlite3_column_blob(stmt, 0);
|
||||
size_t blobbytes = (size_t) sqlite3_column_bytes(stmt, 0);
|
||||
|
||||
srcoffset = std::min(srcoffset, blobbytes);
|
||||
minbytes = std::min(srcbytes, blobbytes - srcoffset);
|
||||
|
||||
if (srcoffset != 0)
|
||||
{
|
||||
srcoffset += 0;
|
||||
}
|
||||
CopySamples(src + srcoffset,
|
||||
srcformat,
|
||||
(samplePtr) dest,
|
||||
destformat,
|
||||
minbytes / SAMPLE_SIZE(srcformat));
|
||||
|
||||
dest = ((samplePtr) dest) + minbytes;
|
||||
}
|
||||
}
|
||||
|
||||
if (srcbytes - minbytes)
|
||||
{
|
||||
memset(dest, 0, srcbytes - minbytes);
|
||||
}
|
||||
|
||||
return srcbytes;
|
||||
}
|
||||
|
||||
bool SqliteSampleBlock::Load(SampleBlockID sbid)
|
||||
{
|
||||
auto db = mIO.DB();
|
||||
|
||||
wxASSERT(sbid > 0);
|
||||
|
||||
int rc;
|
||||
|
||||
mValid = false;
|
||||
mSummary256Bytes = 0;
|
||||
mSummary64kBytes = 0;
|
||||
mSampleCount = 0;
|
||||
mSampleBytes = 0;
|
||||
mSumMin = FLT_MAX;
|
||||
mSumMax = -FLT_MAX;
|
||||
mSumMin = 0.0;
|
||||
|
||||
char sql[256];
|
||||
sqlite3_snprintf(sizeof(sql),
|
||||
sql,
|
||||
"SELECT sampleformat, summin, summax, sumrms,"
|
||||
" length('summary256'), length('summary64k'), length('samples')"
|
||||
" FROM sampleblocks WHERE blockid = %d;",
|
||||
sbid);
|
||||
|
||||
sqlite3_stmt *stmt = nullptr;
|
||||
auto cleanup = finally([&]
|
||||
{
|
||||
if (stmt)
|
||||
{
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
});
|
||||
|
||||
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
|
||||
if (rc != SQLITE_OK)
|
||||
{
|
||||
wxLogDebug(wxT("SQLITE error %s"), sqlite3_errmsg(db));
|
||||
// handle error
|
||||
return false;
|
||||
}
|
||||
|
||||
rc = sqlite3_step(stmt);
|
||||
if (rc != SQLITE_ROW)
|
||||
{
|
||||
wxLogDebug(wxT("SQLITE error %s"), sqlite3_errmsg(db));
|
||||
// handle error
|
||||
return false;
|
||||
}
|
||||
|
||||
mBlockID = sbid;
|
||||
mSampleFormat = (sampleFormat) sqlite3_column_int(stmt, 0);
|
||||
mSumMin = sqlite3_column_double(stmt, 1);
|
||||
mSumMax = sqlite3_column_double(stmt, 2);
|
||||
mSumRms = sqlite3_column_double(stmt, 3);
|
||||
mSummary256Bytes = sqlite3_column_int(stmt, 4);
|
||||
mSummary64kBytes = sqlite3_column_int(stmt, 5);
|
||||
mSampleBytes = sqlite3_column_int(stmt, 6);
|
||||
mSampleCount = mSampleBytes / SAMPLE_SIZE(mSampleFormat);
|
||||
|
||||
mValid = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SqliteSampleBlock::Commit()
|
||||
{
|
||||
auto db = mIO.DB();
|
||||
int rc;
|
||||
|
||||
char sql[256];
|
||||
sqlite3_snprintf(sizeof(sql),
|
||||
sql,
|
||||
"INSERT INTO sampleblocks (%s) VALUES(?,?,?,?,?,?,?);",
|
||||
columns);
|
||||
|
||||
sqlite3_stmt *stmt = nullptr;
|
||||
auto cleanup = finally([&]
|
||||
{
|
||||
if (stmt)
|
||||
{
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
});
|
||||
|
||||
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
|
||||
if (rc != SQLITE_OK)
|
||||
{
|
||||
wxLogDebug(wxT("SQLITE error %s"), sqlite3_errmsg(db));
|
||||
// handle error
|
||||
return false;
|
||||
}
|
||||
|
||||
// BIND SQL sampleblocks
|
||||
sqlite3_bind_int(stmt, 1, mSampleFormat);
|
||||
sqlite3_bind_double(stmt, 2, mSumMin);
|
||||
sqlite3_bind_double(stmt, 3, mSumMax);
|
||||
sqlite3_bind_double(stmt, 4, mSumRms);
|
||||
sqlite3_bind_blob(stmt, 5, mSummary256.get(), mSummary256Bytes, SQLITE_STATIC);
|
||||
sqlite3_bind_blob(stmt, 6, mSummary64k.get(), mSummary64kBytes, SQLITE_STATIC);
|
||||
sqlite3_bind_blob(stmt, 7, mSamples.get(), mSampleBytes, SQLITE_STATIC);
|
||||
|
||||
rc = sqlite3_step(stmt);
|
||||
if (rc != SQLITE_DONE)
|
||||
{
|
||||
wxLogDebug(wxT("SQLITE error %s"), sqlite3_errmsg(db));
|
||||
// handle error
|
||||
return false;
|
||||
}
|
||||
|
||||
mBlockID = sqlite3_last_insert_rowid(db);
|
||||
|
||||
mSamples.reset();
|
||||
mSummary256.reset();
|
||||
mSummary64k.reset();
|
||||
|
||||
mValid = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SqliteSampleBlock::Delete()
|
||||
{
|
||||
auto db = mIO.DB();
|
||||
|
||||
if (mBlockID)
|
||||
{
|
||||
int rc;
|
||||
|
||||
char sql[256];
|
||||
sqlite3_snprintf(sizeof(sql),
|
||||
sql,
|
||||
"DELETE FROM sampleblocks WHERE blockid = %lld;",
|
||||
mBlockID);
|
||||
|
||||
rc = sqlite3_exec(db, sql, nullptr, nullptr, nullptr);
|
||||
if (rc != SQLITE_OK)
|
||||
{
|
||||
wxLogDebug(wxT("SQLITE error %s"), sqlite3_errmsg(db));
|
||||
// handle error
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SqliteSampleBlock::SaveXML(XMLWriter &xmlFile)
|
||||
{
|
||||
xmlFile.WriteAttr(wxT("blockid"), mBlockID);
|
||||
xmlFile.WriteAttr(wxT("samplecount"), mSampleCount);
|
||||
xmlFile.WriteAttr(wxT("len256"), mSummary256Bytes);
|
||||
xmlFile.WriteAttr(wxT("len64k"), mSummary64kBytes);
|
||||
xmlFile.WriteAttr(wxT("min"), mSumMin);
|
||||
xmlFile.WriteAttr(wxT("max"), mSumMax);
|
||||
xmlFile.WriteAttr(wxT("rms"), mSumRms);
|
||||
}
|
||||
|
||||
/// Calculates summary block data describing this sample data.
|
||||
///
|
||||
/// This method also has the side effect of setting the mSumMin,
|
||||
/// mSumMax, and mSumRms members of this class.
|
||||
///
|
||||
/// @param buffer A buffer containing the sample data to be analyzed
|
||||
/// @param len The length of the sample data
|
||||
/// @param format The format of the sample data.
|
||||
void SqliteSampleBlock::CalcSummary()
|
||||
{
|
||||
Floats samplebuffer;
|
||||
float *samples;
|
||||
|
||||
if (mSampleFormat == floatSample)
|
||||
{
|
||||
samples = (float *) mSamples.get();
|
||||
}
|
||||
else
|
||||
{
|
||||
samplebuffer.reinit((unsigned) mSampleCount);
|
||||
CopySamples(mSamples.get(),
|
||||
mSampleFormat,
|
||||
(samplePtr) samplebuffer.get(),
|
||||
floatSample,
|
||||
mSampleCount);
|
||||
samples = samplebuffer.get();
|
||||
}
|
||||
|
||||
int fields = 3; /* min, max, rms */
|
||||
int bytesPerFrame = fields * sizeof(float);
|
||||
int frames64k = (mSampleCount + 65535) / 65536;
|
||||
int frames256 = frames64k * 256;
|
||||
|
||||
mSummary256Bytes = frames256 * bytesPerFrame;
|
||||
mSummary64kBytes = frames64k * bytesPerFrame;
|
||||
|
||||
mSummary256.reinit(mSummary256Bytes);
|
||||
mSummary64k.reinit(mSummary64kBytes);
|
||||
|
||||
float *summary256 = (float *) mSummary256.get();
|
||||
float *summary64k = (float *) mSummary64k.get();
|
||||
|
||||
float min;
|
||||
float max;
|
||||
float sumsq;
|
||||
double totalSquares = 0.0;
|
||||
double fraction = 0.0;
|
||||
|
||||
// Recalc 256 summaries
|
||||
int sumLen = (mSampleCount + 255) / 256;
|
||||
int summaries = 256;
|
||||
|
||||
for (int i = 0; i < sumLen; ++i)
|
||||
{
|
||||
min = samples[i * 256];
|
||||
max = samples[i * 256];
|
||||
sumsq = min * min;
|
||||
|
||||
int jcount = 256;
|
||||
if (jcount > mSampleCount - i * 256)
|
||||
{
|
||||
jcount = mSampleCount - i * 256;
|
||||
fraction = 1.0 - (jcount / 256.0);
|
||||
}
|
||||
|
||||
for (int j = 1; j < jcount; ++j)
|
||||
{
|
||||
float f1 = samples[i * 256 + j];
|
||||
sumsq += f1 * f1;
|
||||
|
||||
if (f1 < min)
|
||||
{
|
||||
min = f1;
|
||||
}
|
||||
else if (f1 > max)
|
||||
{
|
||||
max = f1;
|
||||
}
|
||||
}
|
||||
|
||||
totalSquares += sumsq;
|
||||
|
||||
summary256[i * 3] = min;
|
||||
summary256[i * 3 + 1] = max;
|
||||
// The rms is correct, but this may be for less than 256 samples in last loop.
|
||||
summary256[i * 3 + 2] = (float) sqrt(sumsq / jcount);
|
||||
}
|
||||
|
||||
for (int i = sumLen; i < frames256; ++i)
|
||||
{
|
||||
// filling in the remaining bits with non-harming/contributing values
|
||||
// rms values are not "non-harming", so keep count of them:
|
||||
summaries--;
|
||||
summary256[i * 3] = FLT_MAX; // min
|
||||
summary256[i * 3 + 1] = -FLT_MAX; // max
|
||||
summary256[i * 3 + 2] = 0.0f; // rms
|
||||
}
|
||||
|
||||
// Calculate now while we can do it accurately
|
||||
mSumRms = sqrt(totalSquares / mSampleCount);
|
||||
|
||||
// Recalc 64K summaries
|
||||
sumLen = (mSampleCount + 65535) / 65536;
|
||||
|
||||
for (int i = 0; i < sumLen; ++i)
|
||||
{
|
||||
min = summary256[3 * i * 256];
|
||||
max = summary256[3 * i * 256 + 1];
|
||||
sumsq = summary256[3 * i * 256 + 2];
|
||||
sumsq *= sumsq;
|
||||
|
||||
for (int j = 1; j < 256; ++j)
|
||||
{
|
||||
// we can overflow the useful summary256 values here, but have put
|
||||
// non-harmful values in them
|
||||
if (summary256[3 * (i * 256 + j)] < min)
|
||||
{
|
||||
min = summary256[3 * (i * 256 + j)];
|
||||
}
|
||||
|
||||
if (summary256[3 * (i * 256 + j) + 1] > max)
|
||||
{
|
||||
max = summary256[3 * (i * 256 + j) + 1];
|
||||
}
|
||||
|
||||
float r1 = summary256[3 * (i * 256 + j) + 2];
|
||||
sumsq += r1 * r1;
|
||||
}
|
||||
|
||||
double denom = (i < sumLen - 1) ? 256.0 : summaries - fraction;
|
||||
float rms = (float) sqrt(sumsq / denom);
|
||||
|
||||
summary64k[i * 3] = min;
|
||||
summary64k[i * 3 + 1] = max;
|
||||
summary64k[i * 3 + 2] = rms;
|
||||
}
|
||||
|
||||
for (int i = sumLen; i < frames64k; ++i)
|
||||
{
|
||||
wxASSERT_MSG(false, wxT("Out of data for mSummaryInfo")); // Do we ever get here?
|
||||
|
||||
summary64k[i * 3] = 0.0f; // probably should be FLT_MAX, need a test case
|
||||
summary64k[i * 3 + 1] = 0.0f; // probably should be -FLT_MAX, need a test case
|
||||
summary64k[i * 3 + 2] = 0.0f; // just padding
|
||||
}
|
||||
|
||||
// Recalc block-level summary (mRMS already calculated)
|
||||
min = summary64k[0];
|
||||
max = summary64k[1];
|
||||
|
||||
for (int i = 1; i < sumLen; ++i)
|
||||
{
|
||||
if (summary64k[i * 3] < min)
|
||||
{
|
||||
min = summary64k[i * 3];
|
||||
}
|
||||
|
||||
if (summary64k[i * 3 + 1] > max)
|
||||
{
|
||||
max = summary64k[i * 3 + 1];
|
||||
}
|
||||
}
|
||||
|
||||
mSumMin = min;
|
||||
mSumMax = max;
|
||||
}
|
||||
|
||||
// Inject our database implementation at startup
|
||||
static struct Injector { Injector() {
|
||||
// Do this some time before the first project is created
|
||||
(void) SampleBlockFactory::RegisterFactoryFactory(
|
||||
[]( AudacityProject &project ){
|
||||
return std::make_shared<SqliteSampleBlockFactory>( project ); }
|
||||
);
|
||||
} } injector;
|
|
@ -1279,12 +1279,13 @@ bool TrackList::HasPendingTracks() const
|
|||
return false;
|
||||
}
|
||||
|
||||
#include "SampleBlock.h"
|
||||
#include "ViewInfo.h"
|
||||
static auto TrackFactoryFactory = []( AudacityProject &project ) {
|
||||
auto &viewInfo = ViewInfo::Get( project );
|
||||
return std::make_shared< TrackFactory >(
|
||||
ProjectSettings::Get( project ),
|
||||
project, &viewInfo );
|
||||
SampleBlockFactory::New( project ), &viewInfo );
|
||||
};
|
||||
|
||||
static const AudacityProject::AttachedObjects::RegisteredFactory key2{
|
||||
|
|
|
@ -1559,6 +1559,9 @@ private:
|
|||
std::vector< Updater > mUpdaters;
|
||||
};
|
||||
|
||||
class SampleBlockFactory;
|
||||
using SampleBlockFactoryPtr = std::shared_ptr<SampleBlockFactory>;
|
||||
|
||||
class AUDACITY_DLL_API TrackFactory final
|
||||
: public ClientData::Base
|
||||
{
|
||||
|
@ -1569,9 +1572,9 @@ class AUDACITY_DLL_API TrackFactory final
|
|||
static void Destroy( AudacityProject &project );
|
||||
|
||||
TrackFactory( const ProjectSettings &settings,
|
||||
AudacityProject &project, const ZoomInfo *zoomInfo)
|
||||
const SampleBlockFactoryPtr &pFactory, const ZoomInfo *zoomInfo)
|
||||
: mSettings{ settings }
|
||||
, mProject(project)
|
||||
, mpFactory(pFactory)
|
||||
, mZoomInfo(zoomInfo)
|
||||
{
|
||||
}
|
||||
|
@ -1580,7 +1583,7 @@ class AUDACITY_DLL_API TrackFactory final
|
|||
|
||||
private:
|
||||
const ProjectSettings &mSettings;
|
||||
AudacityProject &mProject;
|
||||
SampleBlockFactoryPtr mpFactory;
|
||||
const ZoomInfo *const mZoomInfo;
|
||||
friend class AudacityProject;
|
||||
public:
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
/**********************************************************************
|
||||
|
||||
Audacity: A Digital Audio Editor
|
||||
|
||||
TrackFactory.h
|
||||
|
||||
Paul Licameli -- split from Track.h
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#include "TrackFactory.h"
|
||||
|
||||
#include "LabelTrack.h"
|
||||
#include "NoteTrack.h"
|
||||
#include "TimeTrack.h"
|
||||
#include "ViewInfo.h"
|
||||
#include "WaveTrack.h"
|
||||
|
||||
LabelTrack::Holder TrackFactory::NewLabelTrack()
|
||||
{
|
||||
return std::make_shared<LabelTrack>();
|
||||
}
|
||||
|
||||
NoteTrack::Holder TrackFactory::NewNoteTrack()
|
||||
{
|
||||
return std::make_shared<NoteTrack>();
|
||||
}
|
||||
|
||||
std::shared_ptr<TimeTrack> TrackFactory::NewTimeTrack()
|
||||
{
|
||||
return std::make_shared<TimeTrack>(mZoomInfo);
|
||||
}
|
||||
|
||||
WaveTrack::Holder TrackFactory::DuplicateWaveTrack(const WaveTrack &orig)
|
||||
{
|
||||
return std::static_pointer_cast<WaveTrack>( orig.Duplicate() );
|
||||
}
|
||||
|
||||
WaveTrack::Holder TrackFactory::NewWaveTrack(sampleFormat format, double rate)
|
||||
{
|
||||
if (format == (sampleFormat)0)
|
||||
format = QualityPrefs::SampleFormatChoice();
|
||||
if (rate == 0)
|
||||
rate = mSettings.GetRate();
|
||||
return std::make_shared<WaveTrack> ( &mProject, format, rate );
|
||||
}
|
||||
|
||||
static auto TrackFactoryFactory = []( AudacityProject &project ) {
|
||||
auto &viewInfo = ViewInfo::Get( project );
|
||||
return std::make_shared< TrackFactory >(
|
||||
ProjectSettings::Get( project ),
|
||||
project, &viewInfo );
|
||||
};
|
||||
|
||||
static const AudacityProject::AttachedObjects::RegisteredFactory key2{
|
||||
TrackFactoryFactory
|
||||
};
|
||||
|
||||
TrackFactory &TrackFactory::Get( AudacityProject &project )
|
||||
{
|
||||
return project.AttachedObjects::Get< TrackFactory >( key2 );
|
||||
}
|
||||
|
||||
const TrackFactory &TrackFactory::Get( const AudacityProject &project )
|
||||
{
|
||||
return Get( const_cast< AudacityProject & >( project ) );
|
||||
}
|
||||
|
||||
TrackFactory &TrackFactory::Reset( AudacityProject &project )
|
||||
{
|
||||
auto result = TrackFactoryFactory( project );
|
||||
project.AttachedObjects::Assign( key2, result );
|
||||
return *result;
|
||||
}
|
||||
|
||||
void TrackFactory::Destroy( AudacityProject &project )
|
||||
{
|
||||
project.AttachedObjects::Assign( key2, nullptr );
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/**********************************************************************
|
||||
|
||||
Audacity: A Digital Audio Editor
|
||||
|
||||
TrackFactory.h
|
||||
|
||||
Paul Licameli -- split from Track.h
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#ifndef __AUDACITY_TRACK_FACTORY__
|
||||
#define __AUDACITY_TRACK_FACTORY__
|
||||
|
||||
#include "ClientData.h" // to inherit
|
||||
|
||||
class AudacityProject;
|
||||
|
||||
class AUDACITY_DLL_API TrackFactory final
|
||||
: public ClientData::Base
|
||||
{
|
||||
public:
|
||||
static TrackFactory &Get( AudacityProject &project );
|
||||
static const TrackFactory &Get( const AudacityProject &project );
|
||||
static TrackFactory &Reset( AudacityProject &project );
|
||||
static void Destroy( AudacityProject &project );
|
||||
|
||||
TrackFactory( const ProjectSettings &settings,
|
||||
AudacityProject &project, const ZoomInfo *zoomInfo)
|
||||
: mSettings{ settings }
|
||||
, mProject(project)
|
||||
, mZoomInfo(zoomInfo)
|
||||
{
|
||||
}
|
||||
TrackFactory( const TrackFactory & ) PROHIBITED;
|
||||
TrackFactory &operator=( const TrackFactory & ) PROHIBITED;
|
||||
|
||||
private:
|
||||
const ProjectSettings &mSettings;
|
||||
AudacityProject &mProject;
|
||||
const ZoomInfo *const mZoomInfo;
|
||||
friend class AudacityProject;
|
||||
public:
|
||||
// These methods are defined in WaveTrack.cpp, NoteTrack.cpp,
|
||||
// LabelTrack.cpp, and TimeTrack.cpp respectively
|
||||
std::shared_ptr<WaveTrack> DuplicateWaveTrack(const WaveTrack &orig);
|
||||
std::shared_ptr<WaveTrack> NewWaveTrack(sampleFormat format = (sampleFormat)0,
|
||||
double rate = 0);
|
||||
std::shared_ptr<LabelTrack> NewLabelTrack();
|
||||
std::shared_ptr<TimeTrack> NewTimeTrack();
|
||||
#if defined(USE_MIDI)
|
||||
std::shared_ptr<NoteTrack> NewNoteTrack();
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -119,12 +119,12 @@ static void ComputeSpectrumUsingRealFFTf
|
|||
}
|
||||
}
|
||||
|
||||
WaveClip::WaveClip(AudacityProject *project,
|
||||
WaveClip::WaveClip(const SampleBlockFactoryPtr &factory,
|
||||
sampleFormat format, int rate, int colourIndex)
|
||||
{
|
||||
mRate = rate;
|
||||
mColourIndex = colourIndex;
|
||||
mSequence = std::make_unique<Sequence>(project, format);
|
||||
mSequence = std::make_unique<Sequence>(factory, format);
|
||||
|
||||
mEnvelope = std::make_unique<Envelope>(true, 1e-7, 2.0, 1.0);
|
||||
|
||||
|
@ -134,7 +134,7 @@ WaveClip::WaveClip(AudacityProject *project,
|
|||
}
|
||||
|
||||
WaveClip::WaveClip(const WaveClip& orig,
|
||||
AudacityProject *project,
|
||||
const SampleBlockFactoryPtr &factory,
|
||||
bool copyCutlines)
|
||||
{
|
||||
// essentially a copy constructor - but you must pass in the
|
||||
|
@ -144,7 +144,7 @@ WaveClip::WaveClip(const WaveClip& orig,
|
|||
mOffset = orig.mOffset;
|
||||
mRate = orig.mRate;
|
||||
mColourIndex = orig.mColourIndex;
|
||||
mSequence = std::make_unique<Sequence>(*orig.mSequence, project);
|
||||
mSequence = std::make_unique<Sequence>(*orig.mSequence, factory);
|
||||
|
||||
mEnvelope = std::make_unique<Envelope>(*orig.mEnvelope);
|
||||
|
||||
|
@ -155,13 +155,13 @@ WaveClip::WaveClip(const WaveClip& orig,
|
|||
if ( copyCutlines )
|
||||
for (const auto &clip: orig.mCutLines)
|
||||
mCutLines.push_back
|
||||
( std::make_unique<WaveClip>( *clip, project, true ) );
|
||||
( std::make_unique<WaveClip>( *clip, factory, true ) );
|
||||
|
||||
mIsPlaceholder = orig.GetIsPlaceholder();
|
||||
}
|
||||
|
||||
WaveClip::WaveClip(const WaveClip& orig,
|
||||
AudacityProject *project,
|
||||
const SampleBlockFactoryPtr &factory,
|
||||
bool copyCutlines,
|
||||
double t0, double t1)
|
||||
{
|
||||
|
@ -199,7 +199,7 @@ WaveClip::WaveClip(const WaveClip& orig,
|
|||
if (cutlinePosition >= t0 && cutlinePosition <= t1)
|
||||
{
|
||||
auto newCutLine =
|
||||
std::make_unique< WaveClip >( *clip, project, true );
|
||||
std::make_unique< WaveClip >( *clip, factory, true );
|
||||
newCutLine->SetOffset( cutlinePosition - t0 );
|
||||
mCutLines.push_back(std::move(newCutLine));
|
||||
}
|
||||
|
@ -1338,7 +1338,7 @@ XMLTagHandler *WaveClip::HandleXMLChild(const wxChar *tag)
|
|||
{
|
||||
// Nested wave clips are cut lines
|
||||
mCutLines.push_back(
|
||||
std::make_unique<WaveClip>(mSequence->GetProject(),
|
||||
std::make_unique<WaveClip>(mSequence->GetFactory(),
|
||||
mSequence->GetSampleFormat(), mRate, 0 /*colourindex*/));
|
||||
return mCutLines.back().get();
|
||||
}
|
||||
|
@ -1374,7 +1374,7 @@ void WaveClip::Paste(double t0, const WaveClip* other)
|
|||
if (clipNeedsResampling || clipNeedsNewFormat)
|
||||
{
|
||||
newClip =
|
||||
std::make_unique<WaveClip>(*other, mSequence->GetProject(), true);
|
||||
std::make_unique<WaveClip>(*other, mSequence->GetFactory(), true);
|
||||
if (clipNeedsResampling)
|
||||
// The other clip's rate is different from ours, so resample
|
||||
newClip->Resample(mRate);
|
||||
|
@ -1395,7 +1395,7 @@ void WaveClip::Paste(double t0, const WaveClip* other)
|
|||
{
|
||||
newCutlines.push_back(
|
||||
std::make_unique<WaveClip>
|
||||
( *cutline, mSequence->GetProject(),
|
||||
( *cutline, mSequence->GetFactory(),
|
||||
// Recursively copy cutlines of cutlines. They don't need
|
||||
// their offsets adjusted.
|
||||
true));
|
||||
|
@ -1532,7 +1532,7 @@ void WaveClip::ClearAndAddCutLine(double t0, double t1)
|
|||
const double clip_t1 = std::min( t1, GetEndTime() );
|
||||
|
||||
auto newClip = std::make_unique< WaveClip >
|
||||
(*this, mSequence->GetProject(), true, clip_t0, clip_t1);
|
||||
(*this, mSequence->GetFactory(), true, clip_t0, clip_t1);
|
||||
|
||||
newClip->SetOffset( clip_t0 - mOffset );
|
||||
|
||||
|
@ -1702,7 +1702,7 @@ void WaveClip::Resample(int rate, ProgressDialog *progress)
|
|||
auto numSamples = mSequence->GetNumSamples();
|
||||
|
||||
auto newSequence =
|
||||
std::make_unique<Sequence>(mSequence->GetProject(), mSequence->GetSampleFormat());
|
||||
std::make_unique<Sequence>(mSequence->GetFactory(), mSequence->GetSampleFormat());
|
||||
|
||||
/**
|
||||
* We want to keep going as long as we have something to feed the resampler
|
||||
|
|
|
@ -23,12 +23,13 @@
|
|||
|
||||
#include <vector>
|
||||
|
||||
class AudacityProject;
|
||||
class BlockArray;
|
||||
class BlockFile;
|
||||
using BlockFilePtr = std::shared_ptr<BlockFile>;
|
||||
class Envelope;
|
||||
class ProgressDialog;
|
||||
class SampleBlockFactory;
|
||||
using SampleBlockFactoryPtr = std::shared_ptr<SampleBlockFactory>;
|
||||
class Sequence;
|
||||
class SpectrogramSettings;
|
||||
class WaveCache;
|
||||
|
@ -179,19 +180,19 @@ private:
|
|||
|
||||
public:
|
||||
// typical constructor
|
||||
WaveClip(AudacityProject *project, sampleFormat format,
|
||||
WaveClip(const SampleBlockFactoryPtr &factory, sampleFormat format,
|
||||
int rate, int colourIndex);
|
||||
|
||||
// essentially a copy constructor - but you must pass in the
|
||||
// current project, because we might be copying
|
||||
// from one project to another
|
||||
WaveClip(const WaveClip& orig,
|
||||
AudacityProject *project,
|
||||
const SampleBlockFactoryPtr &factory,
|
||||
bool copyCutlines);
|
||||
|
||||
// Copy only a range from the given WaveClip
|
||||
WaveClip(const WaveClip& orig,
|
||||
AudacityProject *project,
|
||||
const SampleBlockFactoryPtr &factory,
|
||||
bool copyCutlines,
|
||||
double t0, double t1);
|
||||
|
||||
|
|
|
@ -90,13 +90,13 @@ WaveTrack::Holder TrackFactory::NewWaveTrack(sampleFormat format, double rate)
|
|||
format = QualityPrefs::SampleFormatChoice();
|
||||
if (rate == 0)
|
||||
rate = mSettings.GetRate();
|
||||
return std::make_shared<WaveTrack> ( &mProject, format, rate );
|
||||
return std::make_shared<WaveTrack> ( mpFactory, format, rate );
|
||||
}
|
||||
|
||||
WaveTrack::WaveTrack( AudacityProject *project,
|
||||
WaveTrack::WaveTrack( const SampleBlockFactoryPtr &pFactory,
|
||||
sampleFormat format, double rate )
|
||||
: PlayableTrack()
|
||||
, mProject(project)
|
||||
, mpFactory(pFactory)
|
||||
{
|
||||
mLegacyProjectFileOffset = 0;
|
||||
|
||||
|
@ -118,6 +118,7 @@ WaveTrack::WaveTrack( AudacityProject *project,
|
|||
|
||||
WaveTrack::WaveTrack(const WaveTrack &orig):
|
||||
PlayableTrack(orig)
|
||||
, mpFactory( orig.mpFactory )
|
||||
, mpSpectrumSettings(orig.mpSpectrumSettings
|
||||
? std::make_unique<SpectrogramSettings>(*orig.mpSpectrumSettings)
|
||||
: nullptr
|
||||
|
@ -136,14 +137,14 @@ WaveTrack::WaveTrack(const WaveTrack &orig):
|
|||
|
||||
for (const auto &clip : orig.mClips)
|
||||
mClips.push_back
|
||||
( std::make_unique<WaveClip>( *clip, mProject, true ) );
|
||||
( std::make_unique<WaveClip>( *clip, mpFactory, true ) );
|
||||
}
|
||||
|
||||
// Copy the track metadata but not the contents.
|
||||
void WaveTrack::Init(const WaveTrack &orig)
|
||||
{
|
||||
PlayableTrack::Init(orig);
|
||||
mProject = orig.mProject;
|
||||
mpFactory = orig.mpFactory;
|
||||
|
||||
mFormat = orig.mFormat;
|
||||
mWaveColorIndex = orig.mWaveColorIndex;
|
||||
|
@ -533,11 +534,11 @@ void WaveTrack::Trim (double t0, double t1)
|
|||
|
||||
|
||||
WaveTrack::Holder WaveTrack::EmptyCopy(
|
||||
AudacityProject *pProject ) const
|
||||
const SampleBlockFactoryPtr &pFactory ) const
|
||||
{
|
||||
auto result = std::make_shared<WaveTrack>( mProject, mFormat, mRate );
|
||||
auto result = std::make_shared<WaveTrack>( pFactory, mFormat, mRate );
|
||||
result->Init(*this);
|
||||
result->mProject = pProject ? pProject : mProject;
|
||||
result->mpFactory = pFactory ? pFactory : mpFactory;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -561,7 +562,7 @@ Track::Holder WaveTrack::Copy(double t0, double t1, bool forClipboard) const
|
|||
//wxPrintf("copy: clip %i is in copy region\n", (int)clip);
|
||||
|
||||
newTrack->mClips.push_back
|
||||
(std::make_unique<WaveClip>(*clip, mProject, ! forClipboard));
|
||||
(std::make_unique<WaveClip>(*clip, mpFactory, ! forClipboard));
|
||||
WaveClip *const newClip = newTrack->mClips.back().get();
|
||||
newClip->Offset(-t0);
|
||||
}
|
||||
|
@ -574,7 +575,7 @@ Track::Holder WaveTrack::Copy(double t0, double t1, bool forClipboard) const
|
|||
const double clip_t1 = std::min(t1, clip->GetEndTime());
|
||||
|
||||
auto newClip = std::make_unique<WaveClip>
|
||||
(*clip, mProject, ! forClipboard, clip_t0, clip_t1);
|
||||
(*clip, mpFactory, ! forClipboard, clip_t0, clip_t1);
|
||||
|
||||
//wxPrintf("copy: clip_t0=%f, clip_t1=%f\n", clip_t0, clip_t1);
|
||||
|
||||
|
@ -593,7 +594,7 @@ Track::Holder WaveTrack::Copy(double t0, double t1, bool forClipboard) const
|
|||
if (forClipboard &&
|
||||
newTrack->GetEndTime() + 1.0 / newTrack->GetRate() < t1 - t0)
|
||||
{
|
||||
auto placeholder = std::make_unique<WaveClip>(mProject,
|
||||
auto placeholder = std::make_unique<WaveClip>(mpFactory,
|
||||
newTrack->GetSampleFormat(),
|
||||
static_cast<int>(newTrack->GetRate()),
|
||||
0 /*colourindex*/);
|
||||
|
@ -985,7 +986,7 @@ void WaveTrack::HandleClear(double t0, double t1,
|
|||
// Don't modify this clip in place, because we want a strong
|
||||
// guarantee, and might modify another clip
|
||||
clipsToDelete.push_back( clip.get() );
|
||||
auto newClip = std::make_unique<WaveClip>( *clip, mProject, true );
|
||||
auto newClip = std::make_unique<WaveClip>( *clip, mpFactory, true );
|
||||
newClip->ClearAndAddCutLine( t0, t1 );
|
||||
clipsToAdd.push_back( std::move( newClip ) );
|
||||
}
|
||||
|
@ -1000,7 +1001,7 @@ void WaveTrack::HandleClear(double t0, double t1,
|
|||
// Don't modify this clip in place, because we want a strong
|
||||
// guarantee, and might modify another clip
|
||||
clipsToDelete.push_back( clip.get() );
|
||||
auto newClip = std::make_unique<WaveClip>( *clip, mProject, true );
|
||||
auto newClip = std::make_unique<WaveClip>( *clip, mpFactory, true );
|
||||
newClip->Clear(clip->GetStartTime(), t1);
|
||||
newClip->Offset(t1-clip->GetStartTime());
|
||||
|
||||
|
@ -1012,7 +1013,7 @@ void WaveTrack::HandleClear(double t0, double t1,
|
|||
// Don't modify this clip in place, because we want a strong
|
||||
// guarantee, and might modify another clip
|
||||
clipsToDelete.push_back( clip.get() );
|
||||
auto newClip = std::make_unique<WaveClip>( *clip, mProject, true );
|
||||
auto newClip = std::make_unique<WaveClip>( *clip, mpFactory, true );
|
||||
newClip->Clear(t0, clip->GetEndTime());
|
||||
|
||||
clipsToAdd.push_back( std::move( newClip ) );
|
||||
|
@ -1023,12 +1024,12 @@ void WaveTrack::HandleClear(double t0, double t1,
|
|||
|
||||
// left
|
||||
clipsToAdd.push_back
|
||||
( std::make_unique<WaveClip>( *clip, mProject, true ) );
|
||||
( std::make_unique<WaveClip>( *clip, mpFactory, true ) );
|
||||
clipsToAdd.back()->Clear(t0, clip->GetEndTime());
|
||||
|
||||
// right
|
||||
clipsToAdd.push_back
|
||||
( std::make_unique<WaveClip>( *clip, mProject, true ) );
|
||||
( std::make_unique<WaveClip>( *clip, mpFactory, true ) );
|
||||
WaveClip *const right = clipsToAdd.back().get();
|
||||
right->Clear(clip->GetStartTime(), t1);
|
||||
right->Offset(t1 - clip->GetStartTime());
|
||||
|
@ -1042,7 +1043,7 @@ void WaveTrack::HandleClear(double t0, double t1,
|
|||
// Don't modify this clip in place, because we want a strong
|
||||
// guarantee, and might modify another clip
|
||||
clipsToDelete.push_back( clip.get() );
|
||||
auto newClip = std::make_unique<WaveClip>( *clip, mProject, true );
|
||||
auto newClip = std::make_unique<WaveClip>( *clip, mpFactory, true );
|
||||
|
||||
// clip->Clear keeps points < t0 and >= t1 via Envelope::CollapseRegion
|
||||
newClip->Clear(t0,t1);
|
||||
|
@ -1108,7 +1109,7 @@ void WaveTrack::SyncLockAdjust(double oldT1, double newT1)
|
|||
// AWD: Could just use InsertSilence() on its own here, but it doesn't
|
||||
// follow EditClipCanMove rules (Paste() does it right)
|
||||
auto tmp = std::make_shared<WaveTrack>(
|
||||
mProject, GetSampleFormat(), GetRate() );
|
||||
mpFactory, GetSampleFormat(), GetRate() );
|
||||
|
||||
tmp->InsertSilence(0.0, newT1 - oldT1);
|
||||
tmp->Flush();
|
||||
|
@ -1263,7 +1264,7 @@ void WaveTrack::Paste(double t0, const Track *src)
|
|||
if (!clip->GetIsPlaceholder())
|
||||
{
|
||||
auto newClip =
|
||||
std::make_unique<WaveClip>( *clip, mProject, true );
|
||||
std::make_unique<WaveClip>( *clip, mpFactory, true );
|
||||
newClip->Resample(mRate);
|
||||
newClip->Offset(t0);
|
||||
newClip->MarkChanged();
|
||||
|
@ -1325,7 +1326,7 @@ void WaveTrack::InsertSilence(double t, double len)
|
|||
if (mClips.empty())
|
||||
{
|
||||
// Special case if there is no clip yet
|
||||
auto clip = std::make_unique<WaveClip>(mProject, mFormat, mRate, this->GetWaveColorIndex());
|
||||
auto clip = std::make_unique<WaveClip>(mpFactory, mFormat, mRate, this->GetWaveColorIndex());
|
||||
clip->InsertSilence(0, len);
|
||||
// use NOFAIL-GUARANTEE
|
||||
mClips.push_back( std::move( clip ) );
|
||||
|
@ -1536,7 +1537,7 @@ size_t WaveTrack::GetMaxBlockSize() const
|
|||
{
|
||||
// We really need the maximum block size, so create a
|
||||
// temporary sequence to get it.
|
||||
maxblocksize = Sequence{ mProject, mFormat }.GetMaxBlockSize();
|
||||
maxblocksize = Sequence{ mpFactory, mFormat }.GetMaxBlockSize();
|
||||
}
|
||||
|
||||
wxASSERT(maxblocksize > 0);
|
||||
|
@ -2112,7 +2113,7 @@ Sequence* WaveTrack::GetSequenceAtX(int xcoord)
|
|||
|
||||
WaveClip* WaveTrack::CreateClip()
|
||||
{
|
||||
mClips.push_back(std::make_unique<WaveClip>(mProject, mFormat, mRate, GetWaveColorIndex()));
|
||||
mClips.push_back(std::make_unique<WaveClip>(mpFactory, mFormat, mRate, GetWaveColorIndex()));
|
||||
return mClips.back().get();
|
||||
}
|
||||
|
||||
|
@ -2272,7 +2273,7 @@ void WaveTrack::SplitAt(double t)
|
|||
if (c->WithinClip(t))
|
||||
{
|
||||
t = LongSamplesToTime(TimeToLongSamples(t)); // put t on a sample
|
||||
auto newClip = std::make_unique<WaveClip>( *c, mProject, true );
|
||||
auto newClip = std::make_unique<WaveClip>( *c, mpFactory, true );
|
||||
c->Clear(t, c->GetEndTime());
|
||||
newClip->Clear(c->GetStartTime(), t);
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
|
||||
class ProgressDialog;
|
||||
|
||||
class SampleBlockFactory;
|
||||
class SpectrogramSettings;
|
||||
class WaveformSettings;
|
||||
class TimeWarper;
|
||||
|
@ -68,7 +69,8 @@ public:
|
|||
// Constructor / Destructor / Duplicator
|
||||
//
|
||||
|
||||
WaveTrack(AudacityProject *project, sampleFormat format, double rate);
|
||||
WaveTrack(
|
||||
const SampleBlockFactoryPtr &pFactory, sampleFormat format, double rate);
|
||||
WaveTrack(const WaveTrack &orig);
|
||||
|
||||
// overwrite data excluding the sample sequence but including display
|
||||
|
@ -159,11 +161,11 @@ private:
|
|||
|
||||
// Make another track copying format, rate, color, etc. but containing no
|
||||
// clips
|
||||
// It is important to pass the correct project (that for the project
|
||||
// It is important to pass the correct factory (that for the project
|
||||
// which will own the copy) in the unusual case that a track is copied from
|
||||
// another project or the clipboard. For copies within one project, the
|
||||
// default will do.
|
||||
Holder EmptyCopy(AudacityProject *pProject = {} ) const;
|
||||
Holder EmptyCopy(const SampleBlockFactoryPtr &pFactory = {} ) const;
|
||||
|
||||
// If forClipboard is true,
|
||||
// and there is no clip at the end time of the selection, then the result
|
||||
|
@ -577,9 +579,7 @@ private:
|
|||
// Private variables
|
||||
//
|
||||
|
||||
// AS: Note that the mProject is mutable. This is
|
||||
// mostly to support "Duplicate" of const objects
|
||||
mutable AudacityProject *mProject;
|
||||
SampleBlockFactoryPtr mpFactory;
|
||||
|
||||
wxCriticalSection mFlushCriticalSection;
|
||||
wxCriticalSection mAppendCriticalSection;
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
#include "../CommonCommandFlags.h"
|
||||
#include "../Menus.h"
|
||||
#include "../Project.h"
|
||||
#include "../ProjectFileIO.h"
|
||||
#include "../commands/CommandContext.h"
|
||||
|
||||
#include <wx/frame.h>
|
||||
|
|
Loading…
Reference in New Issue