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:
Paul Licameli 2020-07-02 19:11:38 -04:00 committed by GitHub
parent 1fcb77ebce
commit 127696879d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 1254 additions and 921 deletions

View File

@ -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);

View File

@ -263,6 +263,7 @@ list( APPEND SOURCES
SpectrumAnalyst.h
SplashDialog.cpp
SplashDialog.h
SqliteSampleBlock.cpp
SseMathFuncs.cpp
SseMathFuncs.h
Tags.cpp

View File

@ -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);

View File

@ -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;

View File

@ -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 );

View File

@ -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;
}

View File

@ -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

View File

@ -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++;
}

View File

@ -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,

881
src/SqliteSampleBlock.cpp Normal file
View File

@ -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;

View File

@ -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{

View File

@ -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:

79
src/TrackFactory.cpp Normal file
View File

@ -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 );
}

56
src/TrackFactory.h Normal file
View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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);

View File

@ -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;

View File

@ -11,7 +11,6 @@
#include "../CommonCommandFlags.h"
#include "../Menus.h"
#include "../Project.h"
#include "../ProjectFileIO.h"
#include "../commands/CommandContext.h"
#include <wx/frame.h>