... which by default used GetActiveProject(); do that call instead at a higher level; so that AudacityCommand.cpp does not depend on Project.cpp. We lose the override of that for CompareAudioCommand which just did nothing. But in fact this overload of the base class method could only be used by EffectUIHost constructed from a command, which never happened: it was only ever constructed from effects. This frees two files from dependency cycles
159 lines
4.4 KiB
C++
159 lines
4.4 KiB
C++
/**********************************************************************
|
|
|
|
Audacity - A Digital Audio Editor
|
|
Copyright 1999-2018 Audacity Team
|
|
License: wxwidgets
|
|
|
|
Dan Horgan
|
|
James Crook
|
|
|
|
******************************************************************//**
|
|
|
|
\file CompareAudioCommand.cpp
|
|
\brief Contains definitions for CompareAudioCommand class
|
|
|
|
\class CompareAudioCommand
|
|
\brief Returns information about the amount of audio that is about a certain
|
|
threshold of difference in two selected tracks
|
|
|
|
*//*******************************************************************/
|
|
|
|
#include "../Audacity.h"
|
|
#include "CompareAudioCommand.h"
|
|
|
|
#include "../Project.h"
|
|
#include "../WaveTrack.h"
|
|
|
|
|
|
#include <float.h>
|
|
#include <wx/intl.h>
|
|
|
|
#include "../Shuttle.h"
|
|
#include "../ShuttleGui.h"
|
|
#include "../widgets/ErrorDialog.h"
|
|
#include "../widgets/valnum.h"
|
|
#include "CommandContext.h"
|
|
|
|
extern void RegisterCompareAudio( Registrar & R){
|
|
R.AddCommand( std::make_unique<CompareAudioCommand>() );
|
|
// std::unique_ptr<CommandOutputTargets> &&target
|
|
// return std::make_shared<CompareAudioCommand>(*this, std::move(target));
|
|
|
|
}
|
|
|
|
bool CompareAudioCommand::DefineParams( ShuttleParams & S ){
|
|
S.Define( errorThreshold, wxT("Threshold"), 0.0f, 0.0f, 0.01f, 1.0f );
|
|
return true;
|
|
}
|
|
|
|
void CompareAudioCommand::PopulateOrExchange(ShuttleGui & S)
|
|
{
|
|
S.AddSpace(0, 5);
|
|
|
|
S.StartMultiColumn(2, wxALIGN_CENTER);
|
|
{
|
|
S.TieTextBox(_("Threshold:"),errorThreshold);
|
|
}
|
|
S.EndMultiColumn();
|
|
}
|
|
|
|
// Update member variables with project selection data (and validate)
|
|
bool CompareAudioCommand::GetSelection(const CommandContext &context, AudacityProject &proj)
|
|
{
|
|
// Get the selected time interval
|
|
mT0 = proj.mViewInfo.selectedRegion.t0();
|
|
mT1 = proj.mViewInfo.selectedRegion.t1();
|
|
if (mT0 >= mT1)
|
|
{
|
|
context.Error(wxT("There is no selection!"));
|
|
return false;
|
|
}
|
|
|
|
// Get the selected tracks and check that there are at least two to
|
|
// compare
|
|
auto trackRange = proj.GetTracks()->Selected< const WaveTrack >();
|
|
mTrack0 = *trackRange.first;
|
|
if (mTrack0 == NULL)
|
|
{
|
|
context.Error(wxT("No tracks selected! Select two tracks to compare."));
|
|
return false;
|
|
}
|
|
mTrack1 = * ++ trackRange.first;
|
|
if (mTrack1 == NULL)
|
|
{
|
|
context.Error(wxT("Only one track selected! Select two tracks to compare."));
|
|
return false;
|
|
}
|
|
if ( * ++ trackRange.first )
|
|
{
|
|
context.Status(wxT("More than two tracks selected - only the first two will be compared."));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
double CompareAudioCommand::CompareSample(double value1, double value2)
|
|
{
|
|
return fabs(value1 - value2);
|
|
}
|
|
|
|
inline int min(int a, int b)
|
|
{
|
|
return (a < b) ? a : b;
|
|
}
|
|
|
|
bool CompareAudioCommand::Apply(const CommandContext & context)
|
|
{
|
|
if (!GetSelection(context, context.project))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
wxString msg = wxT("Comparing tracks '");
|
|
msg += mTrack0->GetName() + wxT("' and '")
|
|
+ mTrack1->GetName() + wxT("'.");
|
|
context.Status(msg);
|
|
|
|
long errorCount = 0;
|
|
// Initialize buffers for track data to be analyzed
|
|
auto buffSize = std::min(mTrack0->GetMaxBlockSize(), mTrack1->GetMaxBlockSize());
|
|
|
|
Floats buff0{ buffSize };
|
|
Floats buff1{ buffSize };
|
|
|
|
// Compare tracks block by block
|
|
auto s0 = mTrack0->TimeToLongSamples(mT0);
|
|
auto s1 = mTrack0->TimeToLongSamples(mT1);
|
|
auto position = s0;
|
|
auto length = s1 - s0;
|
|
while (position < s1)
|
|
{
|
|
// Get a block of data into the buffers
|
|
auto block = limitSampleBufferSize(
|
|
mTrack0->GetBestBlockSize(position), s1 - position
|
|
);
|
|
mTrack0->Get((samplePtr)buff0.get(), floatSample, position, block);
|
|
mTrack1->Get((samplePtr)buff1.get(), floatSample, position, block);
|
|
|
|
for (decltype(block) buffPos = 0; buffPos < block; ++buffPos)
|
|
{
|
|
if (CompareSample(buff0[buffPos], buff1[buffPos]) > errorThreshold)
|
|
{
|
|
++errorCount;
|
|
}
|
|
}
|
|
|
|
position += block;
|
|
context.Progress(
|
|
(position - s0).as_double() /
|
|
length.as_double()
|
|
);
|
|
}
|
|
|
|
// Output the results
|
|
double errorSeconds = mTrack0->LongSamplesToTime(errorCount);
|
|
context.Status(wxString::Format(wxT("%li"), errorCount));
|
|
context.Status(wxString::Format(wxT("%.4f"), errorSeconds));
|
|
context.Status(wxString::Format(wxT("Finished comparison: %li samples (%.3f seconds) exceeded the error threshold of %f."), errorCount, errorSeconds, errorThreshold));
|
|
return true;
|
|
}
|