mirror of
https://github.com/cookiengineer/audacity
synced 2025-04-29 15:19:44 +02:00
Merge pull request #186 from mmmaisel/realtime-compressor2
Merge Max Maisel's branch that adds their Dynamic Compressor effect to Tenacity. The commit has been already polished extensively, as it was originally intended to be merged in Audacity. Signed-off-by: Panagiotis Vasilopoulos <hello@alwayslivid.com> Reference-to: https://github.com/tenacityteam/tenacity/pull/186
This commit is contained in:
commit
f06ac9bd96
25
scripts/debug/compressor2_buffers.m
Normal file
25
scripts/debug/compressor2_buffers.m
Normal file
@ -0,0 +1,25 @@
|
||||
%% Debug Compressor v2 pipeline buffers
|
||||
buffer_ids = [1,2,3,4,5];
|
||||
prefix = '/tmp';
|
||||
|
||||
figure(1);
|
||||
for k = 1:length(buffer_ids)
|
||||
subplot(length(buffer_ids), 1, k)
|
||||
bfile = fopen(sprintf('%s/envbuf.%d.bin', prefix, buffer_ids(k)));
|
||||
env = fread(bfile, 'float').';
|
||||
bfile = fopen(sprintf('%s/blockbuf.%d.bin', prefix, buffer_ids(k)));
|
||||
block_raw = fread(bfile, 'float').';
|
||||
|
||||
sizes = reshape(block_raw(1:12), 3, 4);
|
||||
capacity = (1:4).*sizes(3,:);
|
||||
track_size = horzcat(0, capacity(1:3)) + sizes(1,:);
|
||||
block = block_raw(13:end);
|
||||
|
||||
plot(block, 'b', 'linewidth', 3);
|
||||
hold on;
|
||||
plot(circshift(env, length(env)/3), 'r');
|
||||
stem(capacity, ones(1, length(capacity)), 'g');
|
||||
stem(track_size, 1.5.*ones(1, length(capacity)), 'b');
|
||||
ylim([-2 2]);
|
||||
hold off;
|
||||
end
|
61
scripts/debug/compressor2_trace.m
Normal file
61
scripts/debug/compressor2_trace.m
Normal file
@ -0,0 +1,61 @@
|
||||
## plot realtime trace data from Compressor2 effect
|
||||
|
||||
stereo = true;
|
||||
bfile = fopen("/tmp/audio.out");
|
||||
|
||||
if stereo
|
||||
width = 14;
|
||||
else
|
||||
width = 12;
|
||||
end
|
||||
|
||||
raw_data = reshape(fread(bfile, 'float'), width, []).';
|
||||
|
||||
data = struct;
|
||||
data.threshold_DB = raw_data(:,1);
|
||||
data.ratio = raw_data(:,2);
|
||||
data.kneewidth_DB = raw_data(:,3);
|
||||
data.attack_time = raw_data(:,4);
|
||||
data.release_time = raw_data(:,5);
|
||||
data.lookahead_time = raw_data(:,6);
|
||||
data.lookbehind_time = raw_data(:,7);
|
||||
data.output_gain_DB = raw_data(:,8);
|
||||
|
||||
if stereo
|
||||
data.in = horzcat(raw_data(:,9), raw_data(:,10));
|
||||
data.env = raw_data(:,11);
|
||||
data.gain = raw_data(:,12);
|
||||
data.out = horzcat(raw_data(:,13), raw_data(:,14));
|
||||
else
|
||||
data.in = raw_data(:,9);
|
||||
data.env = raw_data(:,10);
|
||||
data.gain = raw_data(:,11);
|
||||
data.out = raw_data(:,12);
|
||||
end
|
||||
|
||||
figure(1);
|
||||
plot(data.in.*100, 'b');
|
||||
hold on;
|
||||
plot(data.out.*100, 'g');
|
||||
plot(data.threshold_DB, 'r');
|
||||
plot(data.ratio, 'r');
|
||||
plot(data.kneewidth_DB, 'r');
|
||||
plot(data.attack_time.*10, 'c', "linewidth", 2);
|
||||
plot(data.release_time.*10, 'c', "linewidth", 2);
|
||||
plot(data.lookahead_time, 'm');
|
||||
plot(data.lookbehind_time, 'm');
|
||||
plot(data.output_gain_DB, 'r');
|
||||
plot(data.env.*100, 'k', "linewidth", 2);
|
||||
plot(data.gain.*50, 'k', "linestyle", '--');
|
||||
hold off;
|
||||
grid;
|
||||
|
||||
if stereo
|
||||
legend("in*100", "in*100", "out*100", "out*100", "threshold", "ratio", ...
|
||||
"kneewidth", "attack*10", "release*10", "lookahead", "lookbehind", ...
|
||||
"out_gain", "env*100", "gain*50");
|
||||
else
|
||||
legend("in*100", "out*100", "threshold", "ratio", ...
|
||||
"kneewidth", "attack*10", "release*10", "lookahead", "lookbehind", ...
|
||||
"out_gain", "env*100", "gain*50");
|
||||
end
|
@ -433,6 +433,8 @@ list( APPEND SOURCES PRIVATE
|
||||
effects/ClickRemoval.h
|
||||
effects/Compressor.cpp
|
||||
effects/Compressor.h
|
||||
effects/Compressor2.cpp
|
||||
effects/Compressor2.h
|
||||
effects/Contrast.cpp
|
||||
effects/Contrast.h
|
||||
effects/Distortion.cpp
|
||||
@ -948,6 +950,8 @@ list( APPEND SOURCES PRIVATE
|
||||
widgets/Overlay.h
|
||||
widgets/OverlayPanel.cpp
|
||||
widgets/OverlayPanel.h
|
||||
widgets/Plot.cpp
|
||||
widgets/Plot.h
|
||||
widgets/PopupMenuTable.cpp
|
||||
widgets/PopupMenuTable.h
|
||||
widgets/ProgressDialog.cpp
|
||||
@ -955,6 +959,8 @@ list( APPEND SOURCES PRIVATE
|
||||
widgets/ReadOnlyText.h
|
||||
widgets/Ruler.cpp
|
||||
widgets/Ruler.h
|
||||
widgets/SliderTextCtrl.cpp
|
||||
widgets/SliderTextCtrl.h
|
||||
widgets/UnwritableLocationErrorDialog.cpp
|
||||
widgets/UnwritableLocationErrorDialog.h
|
||||
widgets/Warning.cpp
|
||||
|
@ -121,6 +121,9 @@ for registering for changes.
|
||||
#include "widgets/wxTextCtrlWrapper.h"
|
||||
#include "AllThemeResources.h"
|
||||
|
||||
#include "widgets/Plot.h"
|
||||
#include "widgets/SliderTextCtrl.h"
|
||||
|
||||
#if wxUSE_ACCESSIBILITY
|
||||
#include "widgets/WindowAccessible.h"
|
||||
#endif
|
||||
@ -613,6 +616,31 @@ wxSlider * ShuttleGuiBase::AddSlider(
|
||||
return pSlider;
|
||||
}
|
||||
|
||||
SliderTextCtrl* ShuttleGuiBase::AddSliderTextCtrl(
|
||||
const TranslatableString &Prompt, double pos, double Max, double Min,
|
||||
int precision, double* value, double scale, double offset)
|
||||
{
|
||||
HandleOptionality( Prompt );
|
||||
AddPrompt( Prompt );
|
||||
UseUpId();
|
||||
if( mShuttleMode != eIsCreating )
|
||||
return wxDynamicCast(wxWindow::FindWindowById( miId, mpDlg), SliderTextCtrl);
|
||||
SliderTextCtrl * pSlider;
|
||||
mpWind = pSlider = safenew SliderTextCtrl(GetParent(), miId,
|
||||
pos, Min, Max, precision, scale, offset, wxDefaultPosition, wxDefaultSize,
|
||||
GetStyle( SliderTextCtrl::HORIZONTAL ),
|
||||
value
|
||||
);
|
||||
#if wxUSE_ACCESSIBILITY
|
||||
// so that name can be set on a standard control
|
||||
mpWind->SetAccessible(safenew WindowAccessible(mpWind));
|
||||
#endif
|
||||
mpWind->SetName(wxStripMenuCodes(Prompt.Translation()));
|
||||
miProp=1;
|
||||
UpdateSizers();
|
||||
return pSlider;
|
||||
}
|
||||
|
||||
wxSpinCtrl * ShuttleGuiBase::AddSpinCtrl(
|
||||
const TranslatableString &Prompt, int Value, int Max, int Min)
|
||||
{
|
||||
@ -750,6 +778,33 @@ void ShuttleGuiBase::AddConstTextBox(
|
||||
UpdateSizers();
|
||||
}
|
||||
|
||||
Plot* ShuttleGuiBase::AddPlot( const TranslatableString &Prompt,
|
||||
double x_min, double x_max, double y_min, double y_max,
|
||||
const TranslatableString& x_label, const TranslatableString& y_label,
|
||||
int x_format, int y_format, int count)
|
||||
{
|
||||
HandleOptionality( Prompt );
|
||||
AddPrompt( Prompt );
|
||||
UseUpId();
|
||||
if( mShuttleMode != eIsCreating )
|
||||
return wxDynamicCast(wxWindow::FindWindowById(miId, mpDlg), Plot);
|
||||
Plot* pPlot;
|
||||
mpWind = pPlot = safenew Plot(GetParent(), miId,
|
||||
x_min, x_max, y_min, y_max, x_label, y_label,
|
||||
x_format, y_format, count,
|
||||
wxDefaultPosition, wxDefaultSize,
|
||||
GetStyle( SliderTextCtrl::HORIZONTAL )
|
||||
);
|
||||
#if wxUSE_ACCESSIBILITY
|
||||
// so that name can be set on a standard control
|
||||
mpWind->SetAccessible(safenew WindowAccessible(mpWind));
|
||||
#endif
|
||||
mpWind->SetName(wxStripMenuCodes(Prompt.Translation()));
|
||||
miProp=1;
|
||||
UpdateSizers();
|
||||
return pPlot;
|
||||
}
|
||||
|
||||
wxListBox * ShuttleGuiBase::AddListBox(const wxArrayStringEx &choices)
|
||||
{
|
||||
UseUpId();
|
||||
|
@ -28,6 +28,8 @@
|
||||
class ChoiceSetting;
|
||||
|
||||
class wxArrayStringEx;
|
||||
class Plot;
|
||||
class SliderTextCtrl;
|
||||
|
||||
|
||||
const int nMaxNestedSizers = 20;
|
||||
@ -263,6 +265,10 @@ public:
|
||||
int Value, int Max, int Min);
|
||||
wxTreeCtrl * AddTree();
|
||||
|
||||
SliderTextCtrl* AddSliderTextCtrl(
|
||||
const TranslatableString &Prompt, double pos, double Max, double Min = 0,
|
||||
int precision = 2, double* value = NULL, double scale = 0, double offset = 0);
|
||||
|
||||
// Pass the same initValue to the sequence of calls to AddRadioButton and
|
||||
// AddRadioButtonToGroup.
|
||||
// The radio button is filled if selector == initValue
|
||||
@ -343,6 +349,11 @@ public:
|
||||
void AddConstTextBox(
|
||||
const TranslatableString &Caption, const TranslatableString & Value );
|
||||
|
||||
Plot* AddPlot( const TranslatableString &Prompt,
|
||||
double x_min, double x_max, double y_min, double y_max,
|
||||
const TranslatableString& x_label, const TranslatableString& y_label,
|
||||
int x_format = 1, int y_format = 1, int count = 1 );
|
||||
|
||||
//-- Start and end functions. These are used for sizer, or other window containers
|
||||
// and create the appropriate widget.
|
||||
void StartHorizontalLay(int PositionFlags=wxALIGN_CENTRE, int iProp=1);
|
||||
|
1765
src/effects/Compressor2.cpp
Normal file
1765
src/effects/Compressor2.cpp
Normal file
File diff suppressed because it is too large
Load Diff
294
src/effects/Compressor2.h
Normal file
294
src/effects/Compressor2.h
Normal file
@ -0,0 +1,294 @@
|
||||
/**********************************************************************
|
||||
|
||||
Audacity: A Digital Audio Editor
|
||||
|
||||
Compressor2.h
|
||||
|
||||
Max Maisel (based on Compressor effect)
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#ifndef __AUDACITY_EFFECT_COMPRESSOR2__
|
||||
#define __AUDACITY_EFFECT_COMPRESSOR2__
|
||||
|
||||
#include <wx/checkbox.h>
|
||||
#include <wx/choice.h>
|
||||
#include <wx/event.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/string.h>
|
||||
#include <wx/textctrl.h>
|
||||
|
||||
#include "Effect.h"
|
||||
|
||||
class Plot;
|
||||
class ShuttleGui;
|
||||
class SliderTextCtrl;
|
||||
|
||||
class SamplePreprocessor
|
||||
{
|
||||
public:
|
||||
virtual float ProcessSample(float value) = 0;
|
||||
virtual float ProcessSample(float valueL, float valueR) = 0;
|
||||
virtual void Reset(float value = 0) = 0;
|
||||
virtual void SetWindowSize(size_t windowSize) = 0;
|
||||
};
|
||||
|
||||
class SlidingRmsPreprocessor : public SamplePreprocessor
|
||||
{
|
||||
public:
|
||||
SlidingRmsPreprocessor(size_t windowSize, float gain = 2.0);
|
||||
|
||||
virtual float ProcessSample(float value);
|
||||
virtual float ProcessSample(float valueL, float valueR);
|
||||
virtual void Reset(float value = 0);
|
||||
virtual void SetWindowSize(size_t windowSize);
|
||||
|
||||
static const size_t REFRESH_WINDOW_EVERY = 1048576; // 1 MB
|
||||
|
||||
private:
|
||||
float mSum;
|
||||
float mGain;
|
||||
std::vector<float> mWindow;
|
||||
size_t mPos;
|
||||
size_t mInsertCount;
|
||||
|
||||
inline float DoProcessSample(float value);
|
||||
void Refresh();
|
||||
};
|
||||
|
||||
class SlidingMaxPreprocessor : public SamplePreprocessor
|
||||
{
|
||||
public:
|
||||
SlidingMaxPreprocessor(size_t windowSize);
|
||||
|
||||
virtual float ProcessSample(float value);
|
||||
virtual float ProcessSample(float valueL, float valueR);
|
||||
virtual void Reset(float value = 0);
|
||||
virtual void SetWindowSize(size_t windowSize);
|
||||
|
||||
private:
|
||||
std::vector<float> mWindow;
|
||||
std::vector<float> mMaxes;
|
||||
size_t mPos;
|
||||
|
||||
inline float DoProcessSample(float value);
|
||||
};
|
||||
|
||||
class EnvelopeDetector
|
||||
{
|
||||
public:
|
||||
EnvelopeDetector(size_t buffer_size);
|
||||
|
||||
float ProcessSample(float value);
|
||||
size_t GetBlockSize() const;
|
||||
const float* GetBuffer(int idx) const;
|
||||
|
||||
virtual void CalcInitialCondition(float value);
|
||||
inline float InitialCondition() const { return mInitialCondition; }
|
||||
inline size_t InitialConditionSize() const { return mInitialBlockSize; }
|
||||
|
||||
virtual void Reset(float value = 0) = 0;
|
||||
virtual void SetParams(float sampleRate, float attackTime,
|
||||
float releaseTime) = 0;
|
||||
|
||||
virtual float AttackFactor();
|
||||
virtual float DecayFactor();
|
||||
|
||||
protected:
|
||||
size_t mPos;
|
||||
float mInitialCondition;
|
||||
size_t mInitialBlockSize;
|
||||
std::vector<float> mLookaheadBuffer;
|
||||
std::vector<float> mProcessingBuffer;
|
||||
std::vector<float> mProcessedBuffer;
|
||||
|
||||
virtual void Follow() = 0;
|
||||
};
|
||||
|
||||
class ExpFitEnvelopeDetector : public EnvelopeDetector
|
||||
{
|
||||
public:
|
||||
ExpFitEnvelopeDetector(float rate, float attackTime, float releaseTime,
|
||||
size_t buffer_size);
|
||||
|
||||
virtual void Reset(float value);
|
||||
virtual void SetParams(float sampleRate, float attackTime,
|
||||
float releaseTime);
|
||||
|
||||
private:
|
||||
double mAttackFactor;
|
||||
double mReleaseFactor;
|
||||
|
||||
virtual void Follow();
|
||||
};
|
||||
|
||||
class Pt1EnvelopeDetector : public EnvelopeDetector
|
||||
{
|
||||
public:
|
||||
Pt1EnvelopeDetector(float rate, float attackTime, float releaseTime,
|
||||
size_t buffer_size, bool correctGain = true);
|
||||
virtual void CalcInitialCondition(float value);
|
||||
|
||||
virtual void Reset(float value);
|
||||
virtual void SetParams(float sampleRate, float attackTime,
|
||||
float releaseTime);
|
||||
virtual float AttackFactor();
|
||||
virtual float DecayFactor();
|
||||
|
||||
private:
|
||||
bool mCorrectGain;
|
||||
double mGainCorrection;
|
||||
double mAttackFactor;
|
||||
double mReleaseFactor;
|
||||
|
||||
virtual void Follow();
|
||||
};
|
||||
|
||||
struct PipelineBuffer
|
||||
{
|
||||
public:
|
||||
sampleCount trackPos;
|
||||
size_t trackSize;
|
||||
size_t size;
|
||||
|
||||
inline float* operator[](size_t idx)
|
||||
{ return mBlockBuffer[idx].get(); }
|
||||
|
||||
void pad_to(size_t len, float value, bool stereo);
|
||||
void swap(PipelineBuffer& other);
|
||||
void init(size_t size, bool stereo);
|
||||
void fill(float value, bool stereo);
|
||||
inline size_t capacity() const { return mCapacity; }
|
||||
void free();
|
||||
|
||||
private:
|
||||
size_t mCapacity;
|
||||
Floats mBlockBuffer[2];
|
||||
};
|
||||
|
||||
class EffectCompressor2 final : public Effect
|
||||
{
|
||||
public:
|
||||
static const ComponentInterfaceSymbol Symbol;
|
||||
|
||||
EffectCompressor2();
|
||||
virtual ~EffectCompressor2();
|
||||
|
||||
// ComponentInterface implementation
|
||||
|
||||
ComponentInterfaceSymbol GetSymbol() override;
|
||||
TranslatableString GetDescription() override;
|
||||
ManualPageID ManualPage() override;
|
||||
|
||||
// EffectDefinitionInterface implementation
|
||||
|
||||
EffectType GetType() override;
|
||||
bool SupportsRealtime() override;
|
||||
|
||||
// EffectClientInterface implementation
|
||||
|
||||
unsigned GetAudioInCount() override;
|
||||
unsigned GetAudioOutCount() override;
|
||||
bool RealtimeInitialize() override;
|
||||
bool RealtimeAddProcessor(unsigned numChannels, float sampleRate) override;
|
||||
bool RealtimeFinalize() override;
|
||||
size_t RealtimeProcess(int group, float **inbuf, float **outbuf,
|
||||
size_t numSamples) override;
|
||||
bool DefineParams( ShuttleParams & S ) override;
|
||||
bool GetAutomationParameters(CommandParameters & parms) override;
|
||||
bool SetAutomationParameters(CommandParameters & parms) override;
|
||||
RegistryPaths GetFactoryPresets() override;
|
||||
bool LoadFactoryPreset(int id) override;
|
||||
|
||||
// Effect implementation
|
||||
|
||||
bool CheckWhetherSkipEffect() override;
|
||||
bool Startup() override;
|
||||
bool Process() override;
|
||||
void PopulateOrExchange(ShuttleGui & S) override;
|
||||
bool TransferDataToWindow() override;
|
||||
bool TransferDataFromWindow() override;
|
||||
|
||||
private:
|
||||
// EffectCompressor2 implementation
|
||||
double CompressorGain(double env);
|
||||
std::unique_ptr<SamplePreprocessor> InitPreprocessor(
|
||||
double rate, bool preview = false);
|
||||
std::unique_ptr<EnvelopeDetector> InitEnvelope(
|
||||
double rate, size_t blockSize = 0, bool preview = false);
|
||||
size_t CalcBufferSize(double sampleRate);
|
||||
|
||||
inline size_t CalcLookaheadLength(double rate);
|
||||
inline size_t CalcWindowLength(double rate);
|
||||
|
||||
void AllocPipeline();
|
||||
void AllocRealtimePipeline();
|
||||
void FreePipeline();
|
||||
void SwapPipeline();
|
||||
bool ProcessOne(TrackIterRange<WaveTrack> range);
|
||||
bool LoadPipeline(TrackIterRange<WaveTrack> range, size_t len);
|
||||
void FillPipeline();
|
||||
void ProcessPipeline();
|
||||
inline float PreprocSample(PipelineBuffer& pbuf, size_t rp);
|
||||
inline float EnvelopeSample(PipelineBuffer& pbuf, size_t rp);
|
||||
inline void CompressSample(float env, size_t wp);
|
||||
bool PipelineHasData();
|
||||
void DrainPipeline();
|
||||
void StorePipeline(TrackIterRange<WaveTrack> range);
|
||||
|
||||
bool UpdateProgress();
|
||||
void OnUpdateUI(wxCommandEvent & evt);
|
||||
void UpdateUI();
|
||||
void UpdateCompressorPlot();
|
||||
void UpdateResponsePlot();
|
||||
void UpdateRealtimeParams();
|
||||
|
||||
static const int TAU_FACTOR = 5;
|
||||
static const size_t MIN_BUFFER_CAPACITY = 1048576; // 1MB
|
||||
|
||||
static const size_t PIPELINE_DEPTH = 4;
|
||||
PipelineBuffer mPipeline[PIPELINE_DEPTH];
|
||||
|
||||
double mCurT0;
|
||||
double mCurT1;
|
||||
double mProgressVal;
|
||||
double mTrackLen;
|
||||
bool mProcStereo;
|
||||
|
||||
std::mutex mRealtimeMutex;
|
||||
std::unique_ptr<SamplePreprocessor> mPreproc;
|
||||
std::unique_ptr<EnvelopeDetector> mEnvelope;
|
||||
|
||||
int mAlgorithm;
|
||||
int mCompressBy;
|
||||
bool mStereoInd;
|
||||
|
||||
double mThresholdDB;
|
||||
double mRatio;
|
||||
double mKneeWidthDB;
|
||||
double mAttackTime;
|
||||
double mReleaseTime;
|
||||
double mLookaheadTime;
|
||||
double mLookbehindTime;
|
||||
double mOutputGainDB;
|
||||
|
||||
// cached intermediate values
|
||||
size_t mLookaheadLength;
|
||||
|
||||
static const size_t RESPONSE_PLOT_SAMPLES = 200;
|
||||
static const size_t RESPONSE_PLOT_TIME = 5;
|
||||
static const size_t RESPONSE_PLOT_STEP_START = 2;
|
||||
static const size_t RESPONSE_PLOT_STEP_STOP = 3;
|
||||
|
||||
bool mIgnoreGuiEvents;
|
||||
Plot* mGainPlot;
|
||||
Plot* mResponsePlot;
|
||||
wxChoice* mAlgorithmCtrl;
|
||||
wxChoice* mPreprocCtrl;
|
||||
SliderTextCtrl* mAttackTimeCtrl;
|
||||
SliderTextCtrl* mLookaheadTimeCtrl;
|
||||
|
||||
DECLARE_EVENT_TABLE()
|
||||
};
|
||||
|
||||
#endif
|
134
src/widgets/Plot.cpp
Normal file
134
src/widgets/Plot.cpp
Normal file
@ -0,0 +1,134 @@
|
||||
/**********************************************************************
|
||||
|
||||
Audacity: A Digital Audio Editor
|
||||
|
||||
Plot.cpp
|
||||
|
||||
Max Maisel
|
||||
|
||||
*******************************************************************//**
|
||||
|
||||
\class Plot
|
||||
\brief A customizable generic plot widget.
|
||||
|
||||
*//*******************************************************************/
|
||||
|
||||
|
||||
#include "Plot.h"
|
||||
#include "Ruler.h"
|
||||
#include "../AColor.h"
|
||||
#include "../Theme.h"
|
||||
#include "../AllThemeResources.h"
|
||||
|
||||
#include <wx/brush.h>
|
||||
#include <wx/dcclient.h>
|
||||
#include <wx/dcmemory.h>
|
||||
|
||||
Plot::Plot(wxWindow *parent, wxWindowID winid,
|
||||
float x_min, float x_max, float y_min, float y_max,
|
||||
const TranslatableString& xlabel, const TranslatableString& ylabel,
|
||||
int xformat, int yformat, int count,
|
||||
const wxPoint& pos, const wxSize& size, long style)
|
||||
:
|
||||
wxPanelWrapper(parent, winid, pos, size, style),
|
||||
m_xmin(x_min), m_xmax(x_max), m_ymin(y_min), m_ymax(y_max),
|
||||
m_plots(count)
|
||||
{
|
||||
m_xruler = std::unique_ptr<Ruler>(safenew Ruler);
|
||||
m_xruler->SetOrientation(wxHORIZONTAL);
|
||||
m_xruler->SetFormat(static_cast<Ruler::RulerFormat>(xformat));
|
||||
m_xruler->SetUnits(xlabel);
|
||||
m_xruler->SetFlip(true);
|
||||
|
||||
m_yruler = std::unique_ptr<Ruler>(safenew Ruler);
|
||||
m_yruler->SetOrientation(wxVERTICAL);
|
||||
m_yruler->SetFormat(static_cast<Ruler::RulerFormat>(yformat));
|
||||
m_yruler->SetUnits(ylabel);
|
||||
}
|
||||
|
||||
void Plot::OnPaint(wxPaintEvent & evt)
|
||||
{
|
||||
wxPaintDC dc(this);
|
||||
|
||||
int width, height;
|
||||
GetSize(&width, &height);
|
||||
|
||||
#if defined(__WXMSW__)
|
||||
dc.Clear();
|
||||
#endif
|
||||
|
||||
// Ruler
|
||||
int w = 0;
|
||||
int h = 0;
|
||||
|
||||
m_xruler->SetBounds(0, 0, width, height);
|
||||
m_xruler->SetRange(m_xmin, m_xmax);
|
||||
m_xruler->GetMaxSize(NULL, &h);
|
||||
|
||||
m_yruler->SetBounds(0, 0, width, height);
|
||||
m_yruler->SetRange(m_ymax, m_ymin);
|
||||
m_yruler->GetMaxSize(&w, NULL);
|
||||
|
||||
m_xruler->SetBounds(w, height - h, width, height);
|
||||
m_yruler->SetBounds(0, 0, w, height - h);
|
||||
|
||||
m_xruler->SetTickColour( theTheme.Colour( clrGraphLabels ));
|
||||
m_yruler->SetTickColour( theTheme.Colour( clrGraphLabels ));
|
||||
|
||||
wxRect border;
|
||||
border.x = w;
|
||||
border.y = 0;
|
||||
border.width = width - w;
|
||||
border.height = height - h + 1;
|
||||
|
||||
dc.SetBrush(*wxWHITE_BRUSH);
|
||||
dc.SetPen(*wxTRANSPARENT_PEN);
|
||||
dc.DrawRectangle(border);
|
||||
|
||||
m_xruler->DrawGrid(dc, border.height, true, true, border.x, border.y);
|
||||
m_yruler->DrawGrid(dc, border.width, true, true, border.x, border.y);
|
||||
|
||||
for(const auto& plot : m_plots)
|
||||
{
|
||||
wxASSERT(plot.xdata.size() == plot.ydata.size());
|
||||
if(plot.xdata.size() == 0)
|
||||
continue;
|
||||
dc.SetPen(*plot.pen);
|
||||
|
||||
size_t xsize = plot.xdata.size();
|
||||
for(size_t i = 1; i < xsize; ++i)
|
||||
{
|
||||
AColor::Line(dc,
|
||||
XToScreen(plot.xdata[i-1], border),
|
||||
YToScreen(plot.ydata[i-1], border),
|
||||
XToScreen(plot.xdata[i], border),
|
||||
YToScreen(plot.ydata[i], border));
|
||||
}
|
||||
}
|
||||
|
||||
dc.SetBrush(*wxTRANSPARENT_BRUSH);
|
||||
dc.SetPen(*wxBLACK_PEN);
|
||||
dc.DrawRectangle(border);
|
||||
m_xruler->Draw(dc);
|
||||
m_yruler->Draw(dc);
|
||||
}
|
||||
|
||||
void Plot::OnSize(wxSizeEvent & evt)
|
||||
{
|
||||
Refresh(false);
|
||||
}
|
||||
|
||||
int Plot::XToScreen(float x, wxRect& rect)
|
||||
{
|
||||
return rect.x + lrint((x-m_xmin)*rect.width/(m_xmax-m_xmin));
|
||||
}
|
||||
|
||||
int Plot::YToScreen(float y, wxRect& rect)
|
||||
{
|
||||
return rect.y + rect.height - lrint((y-m_ymin)*rect.height/(m_ymax-m_ymin));
|
||||
}
|
||||
|
||||
BEGIN_EVENT_TABLE(Plot, wxPanelWrapper)
|
||||
EVT_PAINT(Plot::OnPaint)
|
||||
EVT_SIZE(Plot::OnSize)
|
||||
END_EVENT_TABLE()
|
58
src/widgets/Plot.h
Normal file
58
src/widgets/Plot.h
Normal file
@ -0,0 +1,58 @@
|
||||
/**********************************************************************
|
||||
|
||||
Audacity: A Digital Audio Editor
|
||||
|
||||
Plot.h
|
||||
|
||||
Max Maisel
|
||||
|
||||
This class is a generic plot.
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#ifndef __AUDACITY_PLOT__
|
||||
#define __AUDACITY_PLOT__
|
||||
|
||||
#include "wxPanelWrapper.h" // to inherit
|
||||
|
||||
#include "MemoryX.h"
|
||||
|
||||
class Ruler;
|
||||
|
||||
struct PlotData
|
||||
{
|
||||
std::unique_ptr<wxPen> pen;
|
||||
std::vector<float> xdata;
|
||||
std::vector<float> ydata;
|
||||
};
|
||||
|
||||
class Plot : public wxPanelWrapper
|
||||
{
|
||||
public:
|
||||
Plot(wxWindow *parent, wxWindowID winid,
|
||||
float x_min, float x_max, float y_min, float y_max,
|
||||
const TranslatableString& xlabel, const TranslatableString& ylabel,
|
||||
int xformat = 1, int yformat = 1, //Ruler::RealFormat
|
||||
int count = 1, const wxPoint& pos = wxDefaultPosition,
|
||||
const wxSize& size = wxDefaultSize,
|
||||
long style = wxTAB_TRAVERSAL | wxNO_BORDER);
|
||||
|
||||
inline PlotData* GetPlotData(int id)
|
||||
{ return &m_plots[id]; }
|
||||
|
||||
private:
|
||||
void OnPaint(wxPaintEvent & evt);
|
||||
void OnSize(wxSizeEvent & evt);
|
||||
|
||||
float m_xmin, m_xmax;
|
||||
float m_ymin, m_ymax;
|
||||
std::vector<PlotData> m_plots;
|
||||
std::unique_ptr<Ruler> m_xruler, m_yruler;
|
||||
|
||||
int XToScreen(float x, wxRect& rect);
|
||||
int YToScreen(float y, wxRect& rect);
|
||||
|
||||
DECLARE_EVENT_TABLE()
|
||||
};
|
||||
|
||||
#endif
|
167
src/widgets/SliderTextCtrl.cpp
Normal file
167
src/widgets/SliderTextCtrl.cpp
Normal file
@ -0,0 +1,167 @@
|
||||
/**********************************************************************
|
||||
|
||||
Audacity: A Digital Audio Editor
|
||||
|
||||
SliderTextCtrl.cpp
|
||||
|
||||
Max Maisel
|
||||
|
||||
*******************************************************************//**
|
||||
|
||||
\class SliderTextCtrl
|
||||
\brief A slider with connected text box.
|
||||
|
||||
*//*******************************************************************/
|
||||
|
||||
|
||||
#include "SliderTextCtrl.h"
|
||||
|
||||
#include <wx/defs.h>
|
||||
#include <wx/panel.h>
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/slider.h>
|
||||
#include <wx/textctrl.h>
|
||||
#include <wx/valnum.h>
|
||||
|
||||
wxDEFINE_EVENT(cEVT_SLIDERTEXT, wxCommandEvent);
|
||||
|
||||
SliderTextCtrl::SliderTextCtrl(wxWindow *parent, wxWindowID winid,
|
||||
double value, double min, double max, int precision, double scale,
|
||||
double offset, const wxPoint& pos, const wxSize& size, long style,
|
||||
double* varValue)
|
||||
: wxPanelWrapper(parent, winid, pos, size, wxWS_EX_VALIDATE_RECURSIVELY)
|
||||
{
|
||||
m_log = style & LOG;
|
||||
m_int = style & INT;
|
||||
m_value = value;
|
||||
m_min = min;
|
||||
m_max = max;
|
||||
m_zero = -std::numeric_limits<double>::infinity();
|
||||
m_offset = offset;
|
||||
|
||||
if(m_int)
|
||||
{
|
||||
precision = 0;
|
||||
m_format = "%d";
|
||||
}
|
||||
else
|
||||
m_format = wxString::Format("%%.%df", precision);
|
||||
|
||||
if(scale == 0)
|
||||
m_scale = pow(10, precision);
|
||||
else
|
||||
m_scale = scale;
|
||||
|
||||
wxFloatingPointValidator<double> validator(precision, varValue);
|
||||
|
||||
if(m_log)
|
||||
{
|
||||
if(min <= 0.0)
|
||||
{
|
||||
m_zero = -double(precision) - 1.0 / m_scale;
|
||||
min = m_zero;
|
||||
}
|
||||
else
|
||||
min = log10(min + m_offset);
|
||||
|
||||
if(value <= 0.0)
|
||||
value = m_zero;
|
||||
else
|
||||
value = log10(value + m_offset);
|
||||
max = log10(max + m_offset);
|
||||
}
|
||||
|
||||
m_sizer = safenew wxBoxSizer(
|
||||
style & HORIZONTAL ? wxHORIZONTAL : wxVERTICAL);
|
||||
m_slider = safenew wxSlider(this, ID_SLIDER,
|
||||
round(value * m_scale), floor(min * m_scale), ceil(max * m_scale),
|
||||
wxDefaultPosition, wxDefaultSize,
|
||||
style & HORIZONTAL ? wxSL_HORIZONTAL : wxSL_VERTICAL);
|
||||
m_textbox = safenew wxTextCtrl(this, ID_TEXTBOX, wxEmptyString,
|
||||
wxDefaultPosition, wxDefaultSize, 0, validator);
|
||||
|
||||
m_textbox->ChangeValue(FormatValue());
|
||||
m_textbox->Bind(wxEVT_KILL_FOCUS, &SliderTextCtrl::OnKillFocus, this);
|
||||
|
||||
m_sizer->Add(m_slider, 1, wxEXPAND);
|
||||
m_sizer->Add(m_textbox, 0, wxEXPAND);
|
||||
|
||||
SetSizer(m_sizer);
|
||||
}
|
||||
|
||||
void SliderTextCtrl::SetMinTextboxWidth(int width)
|
||||
{
|
||||
wxSize size = GetMinSize();
|
||||
size.SetWidth(width);
|
||||
m_textbox->SetMinSize(size);
|
||||
}
|
||||
|
||||
double SliderTextCtrl::GetValue() const
|
||||
{
|
||||
return m_value;
|
||||
}
|
||||
|
||||
void SliderTextCtrl::SetValue(double value)
|
||||
{
|
||||
m_value = value;
|
||||
m_textbox->ChangeValue(FormatValue());
|
||||
}
|
||||
|
||||
void SliderTextCtrl::OnTextChange(wxCommandEvent& event)
|
||||
{
|
||||
double value;
|
||||
m_textbox->GetValue().ToDouble(&value);
|
||||
m_value = std::min(value, m_max);
|
||||
m_value = std::max(m_value, m_min);
|
||||
if(m_log)
|
||||
{
|
||||
if(m_value == 0.0)
|
||||
value = m_zero;
|
||||
else
|
||||
value = log10(m_value + m_offset);
|
||||
}
|
||||
m_slider->SetValue(round(value * m_scale));
|
||||
event.SetEventType(cEVT_SLIDERTEXT);
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
void SliderTextCtrl::OnSlider(wxCommandEvent& event)
|
||||
{
|
||||
m_value = m_slider->GetValue() / m_scale;
|
||||
if(m_log)
|
||||
{
|
||||
if(m_value <= m_zero)
|
||||
m_value = 0.0;
|
||||
else
|
||||
{
|
||||
m_value = pow(10.0, m_value) - m_offset;
|
||||
m_value = std::max(m_min, m_value);
|
||||
m_value = std::min(m_max, m_value);
|
||||
}
|
||||
}
|
||||
m_textbox->ChangeValue(FormatValue());
|
||||
m_textbox->SetSelection(-1, -1);
|
||||
event.SetEventType(cEVT_SLIDERTEXT);
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
void SliderTextCtrl::OnKillFocus(wxFocusEvent& _)
|
||||
{
|
||||
m_textbox->ChangeValue(FormatValue());
|
||||
wxCommandEvent event(cEVT_SLIDERTEXT, GetId());
|
||||
wxPostEvent(GetParent(), event);
|
||||
}
|
||||
|
||||
wxString SliderTextCtrl::FormatValue() const
|
||||
{
|
||||
int v = m_value;
|
||||
if(m_int)
|
||||
return wxString::Format(m_format, v);
|
||||
else
|
||||
return wxString::Format(m_format, m_value);
|
||||
}
|
||||
|
||||
BEGIN_EVENT_TABLE(SliderTextCtrl, wxControl)
|
||||
EVT_TEXT(ID_TEXTBOX, SliderTextCtrl::OnTextChange)
|
||||
EVT_SLIDER(ID_SLIDER, SliderTextCtrl::OnSlider)
|
||||
END_EVENT_TABLE()
|
79
src/widgets/SliderTextCtrl.h
Normal file
79
src/widgets/SliderTextCtrl.h
Normal file
@ -0,0 +1,79 @@
|
||||
/**********************************************************************
|
||||
|
||||
Audacity: A Digital Audio Editor
|
||||
|
||||
SliderTextCtrl.h
|
||||
|
||||
Max Maisel
|
||||
|
||||
This class is a custom slider.
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#ifndef __AUDACITY_SLIDERTEXTCTRL__
|
||||
#define __AUDACITY_SLIDERTEXTCTRL__
|
||||
|
||||
#include "wxPanelWrapper.h" // to inherit
|
||||
|
||||
class wxSizer;
|
||||
class wxSlider;
|
||||
class wxTextCtrl;
|
||||
|
||||
wxDECLARE_EVENT(cEVT_SLIDERTEXT, wxCommandEvent);
|
||||
|
||||
#define EVT_SLIDERTEXT(winid, func) wx__DECLARE_EVT1( \
|
||||
cEVT_SLIDERTEXT, winid, wxCommandEventHandler(func))
|
||||
|
||||
class SliderTextCtrl : public wxPanelWrapper
|
||||
{
|
||||
public:
|
||||
enum Styles
|
||||
{
|
||||
HORIZONTAL = 1,
|
||||
VERTICAL = 2,
|
||||
LOG = 4,
|
||||
INT = 8,
|
||||
};
|
||||
|
||||
SliderTextCtrl(wxWindow *parent, wxWindowID winid,
|
||||
double value, double min, double max, int precision = 2,
|
||||
double scale = 0, double offset = 0,
|
||||
const wxPoint& pos = wxDefaultPosition,
|
||||
const wxSize& size = wxDefaultSize, long style = HORIZONTAL,
|
||||
double* varValue = NULL);
|
||||
|
||||
void SetMinTextboxWidth(int width);
|
||||
|
||||
double GetValue() const;
|
||||
void SetValue(double value);
|
||||
|
||||
private:
|
||||
void OnTextChange(wxCommandEvent& event);
|
||||
void OnSlider(wxCommandEvent& event);
|
||||
void OnKillFocus(wxFocusEvent& event);
|
||||
wxString FormatValue() const;
|
||||
|
||||
enum
|
||||
{
|
||||
ID_SLIDER = 1,
|
||||
ID_TEXTBOX
|
||||
};
|
||||
|
||||
wxSizer* m_sizer;
|
||||
wxSlider* m_slider;
|
||||
wxTextCtrl* m_textbox;
|
||||
|
||||
bool m_log;
|
||||
bool m_int;
|
||||
double m_value;
|
||||
double m_scale;
|
||||
double m_min;
|
||||
double m_max;
|
||||
double m_zero;
|
||||
double m_offset;
|
||||
wxString m_format;
|
||||
|
||||
DECLARE_EVENT_TABLE()
|
||||
};
|
||||
|
||||
#endif
|
1
tests/octave/.gitignore
vendored
Normal file
1
tests/octave/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
*.wav
|
305
tests/octave/compressor2_test.m
Normal file
305
tests/octave/compressor2_test.m
Normal file
@ -0,0 +1,305 @@
|
||||
## Audacity Compressor2 effect unit test
|
||||
#
|
||||
# Max Maisel
|
||||
#
|
||||
# This tests the Compressor effect with various pseudo-random mono and stereo
|
||||
# noise sequences and sinewaves. The test sequences have different amplitudes
|
||||
# per channel and sometimes a DC component.
|
||||
#
|
||||
# Avoid large parameters for AttackTime, ReleaseTime and LookaroundTime in
|
||||
# this script as settling behaviour is different and will cause test failure.
|
||||
#
|
||||
|
||||
pkg load signal;
|
||||
pkg load image;
|
||||
|
||||
printf("Running Compressor effect tests.\n");
|
||||
|
||||
EXPORT_TEST_SIGNALS = true;
|
||||
|
||||
## PT1 envelope helper function for symmetric attack and release times.
|
||||
function y = env_PT1(x, fs, t_ar, gain = 0)
|
||||
T = 1/(t_ar*fs);
|
||||
si = mean(mean(abs(x(1:fs*t_ar/20,:))));
|
||||
c = (gain != 0) * gain + (gain == 0) * (1.0 + exp(t_ar/30.0));
|
||||
y = c*filter(T, [1 T-1], mean(abs(x), 2), si*c);
|
||||
end
|
||||
|
||||
## PT1 envelope helper function for asymmetric attack and release times.
|
||||
# This function is much slower than the symmetric counterpart.
|
||||
function y = env_PT1_asym(x, fs, t_a, t_r, gain = 0)
|
||||
C_a = 1.0 / (fs*t_a);
|
||||
C_r = 1.0 / (fs*t_r);
|
||||
si = mean(mean(abs(x(1:fs*t_a/20,:))));
|
||||
c = (gain != 0) * gain + (gain == 0) * (1.0 + exp(t_a/30.0));
|
||||
|
||||
x_m = mean(abs(x), 2);
|
||||
y = zeros(length(x_m), 1);
|
||||
level = si;
|
||||
|
||||
for k = 1:1:length(x_m)
|
||||
if x_m(k) >= level
|
||||
level = level + C_a * (x_m(k) - level);
|
||||
else
|
||||
level = level + C_r * (x_m(k) - level);
|
||||
end
|
||||
y(k) = c * level;
|
||||
end
|
||||
end
|
||||
|
||||
## Compressor gain helper function
|
||||
function gain = comp_gain(env, thresh_DB, ratio, kneeW_DB, outG_DB)
|
||||
env_DB = 20*log10(env);
|
||||
kneeCond_DB = 2*(env_DB-thresh_DB);
|
||||
|
||||
belowKnee = kneeCond_DB < -kneeW_DB;
|
||||
aboveKnee = kneeCond_DB >= kneeW_DB;
|
||||
# & is element-wise &&
|
||||
withinKnee = (kneeCond_DB >= -kneeW_DB) & (kneeCond_DB < kneeW_DB);
|
||||
|
||||
gain_DB = zeros(size(env));
|
||||
gain_DB(belowKnee) = outG_DB;
|
||||
gain_DB(aboveKnee) = thresh_DB + ...
|
||||
(env_DB(aboveKnee) - thresh_DB) / ratio + ...
|
||||
outG_DB - env_DB(aboveKnee);
|
||||
# Prevent division by zero
|
||||
kneeW_DB(kneeW_DB==0) = 0.000001;
|
||||
gain_DB(withinKnee) = (1/ratio-1) * ...
|
||||
(env_DB(withinKnee) - thresh_DB + kneeW_DB/2).^2 / ...
|
||||
(2*kneeW_DB) + outG_DB;
|
||||
|
||||
gain = 10.^(gain_DB/20);
|
||||
end
|
||||
|
||||
# Ignore first samples due to settling effects helper function
|
||||
function y = settled(x, fs = 44100, tau = 1, both = 0)
|
||||
y = x(round(3*fs*tau):length(x)-round(3*fs*tau*both),:);
|
||||
end
|
||||
|
||||
# XXX: This Octave function is REALLY slow.
|
||||
# Maximum value of n*fs < 10000
|
||||
function y = lookaround_RMS(x, fs, n1, n2)
|
||||
kernel = cat(1, zeros(n2*fs,1), ones(n1*fs, 1), ones(n2*fs, 1), zeros(n1*fs, 1));
|
||||
y = zeros(size(x));
|
||||
for i=1:1:size(x)(2)
|
||||
y(:,i) = conv(x(:,i).^2, kernel, 'same')./(n1+n2)/fs;
|
||||
end
|
||||
|
||||
y = 2*sqrt(sum(y, 2)./size(y)(2));
|
||||
end
|
||||
|
||||
# XXX: This Octave function is REALLY slow.
|
||||
# Maximum value of n*fs < 10000
|
||||
function y = lookaround_max(x, fs, n1, n2)
|
||||
kernel = cat(1, zeros(n2*fs,1), ones(n1*fs, 1), ones(n2*fs, 1), zeros(n1*fs, 1));
|
||||
x = mean(abs(x), 2);
|
||||
y = imdilate(x, kernel);
|
||||
end
|
||||
|
||||
################################################################################
|
||||
|
||||
## Test Compressor, mono thresholding
|
||||
CURRENT_TEST = "Compressor2, mono thresholding";
|
||||
fs = 44100;
|
||||
|
||||
randn("seed", 1);
|
||||
x1 = 0.01*randn(30*fs,1) .* sin(2*pi/fs/35*(1:1:30*fs)).';
|
||||
|
||||
remove_all_tracks();
|
||||
x = export_to_aud(x1, fs, name = "Compressor-threshold-test.wav");
|
||||
aud_do("DynamicCompressor: Threshold=-20 Algorithm=0 AttackTime=0.1 ReleaseTime=0.3 LookaheadTime=0 LookbehindTime=0 KneeWidth=0\n");
|
||||
y = import_from_aud(1);
|
||||
|
||||
# All input samples are below threshold so output must be equal to input.
|
||||
do_test_equ(x, y);
|
||||
|
||||
## Test Compressor, mono compression PT1 - no lookaround
|
||||
CURRENT_TEST = "Compressor2, mono compression PT1";
|
||||
x1 = x1.*10;
|
||||
remove_all_tracks();
|
||||
x = export_to_aud(x1, fs);
|
||||
aud_do("DynamicCompressor: Threshold=-20 Algorithm=1 CompressBy=0 Ratio=2.5 AttackTime=0.5 ReleaseTime=0.5 LookaheadTime=0.0 LookbehindTime=0.0 KneeWidth=12\n");
|
||||
y = import_from_aud(1);
|
||||
|
||||
do_test_equ(settled(y, fs, 1), ...
|
||||
comp_gain(settled(env_PT1(x, fs, 0.5, 1), fs, 1), -20, 2.5, 12, 0).* ...
|
||||
settled(x, fs, 1));
|
||||
|
||||
## Test Compressor, mono compression PT1 - sinewave - no lookaround
|
||||
CURRENT_TEST = "Compressor2, mono compression PT1 - sinewave";
|
||||
|
||||
x2 = sin(2*pi*300/fs*(1:1:20*fs)).';
|
||||
remove_all_tracks();
|
||||
x = export_to_aud(x2, fs, "Compressor-mono-sine-test.wav");
|
||||
aud_do("DynamicCompressor: Threshold=-23 Algorithm=1 CompressBy=1 Ratio=2.5 AttackTime=0.5 ReleaseTime=0.5 LookaheadTime=0 LookbehindTime=0 KneeWidth=12\n");
|
||||
y = import_from_aud(1);
|
||||
|
||||
# Gain factor 2 because we compress by RMS but do not use lookaround_RMS as
|
||||
# lookaround is zero.
|
||||
do_test_equ(settled(y, fs, 1), ...
|
||||
comp_gain(settled(2*env_PT1(x, fs, 0.5), fs, 1), -23, 2.5, 12, 0).* ...
|
||||
settled(x, fs, 1));
|
||||
|
||||
## Test Compressor, mono compression PT1 - faded sinewave - medium signal
|
||||
CURRENT_TEST = "Compressor2, mono compression PT1 - faded sinewave - medium signal";
|
||||
|
||||
x2 = sin(2*pi*300/fs*(1:1:50*fs)).' .* horzcat(1:1:25*fs, 25*fs:-1:1).' ./ (25*fs);
|
||||
remove_all_tracks();
|
||||
x = export_to_aud(x2, fs, "Compressor-mono-sine-test.wav");
|
||||
aud_do("DynamicCompressor: Threshold=-10 Algorithm=1 CompressBy=0 Ratio=100 AttackTime=0.01 ReleaseTime=0.01 LookaheadTime=0 LookbehindTime=0 KneeWidth=0\n");
|
||||
y = import_from_aud(1);
|
||||
|
||||
do_test_equ(settled(y, fs, 1), ...
|
||||
comp_gain(settled(env_PT1(x, fs, 0.01, 1), fs, 1), -10, 100, 0, 0).* ...
|
||||
settled(x, fs, 1));
|
||||
|
||||
## Test Compressor, mono compression PT1 - faded sinewave - 50 sec signal - no lookaround
|
||||
CURRENT_TEST = "Compressor2, mono compression PT1 - faded sinewave - long signal";
|
||||
|
||||
x2 = vertcat(x2, x2);
|
||||
remove_all_tracks();
|
||||
x = export_to_aud(x2, fs, "Compressor-mono-sine-test.wav");
|
||||
aud_do("DynamicCompressor: Threshold=-10 Algorithm=1 CompressBy=0 Ratio=100 AttackTime=0.01 ReleaseTime=0.01 LookaheadTime=0 LookbehindTime=0 KneeWidth=0\n");
|
||||
y = import_from_aud(1);
|
||||
|
||||
do_test_equ(settled(y, fs, 1), ...
|
||||
comp_gain(settled(env_PT1(x, fs, 0.01, 1), fs, 1), -10, 100, 0, 0).* ...
|
||||
settled(x, fs, 1));
|
||||
|
||||
## Test Compressor, mono compression PT1 - sinewave - no lookaround - long attack time
|
||||
CURRENT_TEST = "Compressor2, mono compression PT1 - sinewave - asymetric attack / release";
|
||||
|
||||
x2 = sin(2*pi*300/fs*(1:1:20*fs)).';
|
||||
remove_all_tracks();
|
||||
x = export_to_aud(x2, fs, "Compressor-mono-sine-test.wav");
|
||||
aud_do("DynamicCompressor: Threshold=-6 Algorithm=1 CompressBy=0 Ratio=2.0 AttackTime=1.0 ReleaseTime=0.3 LookaheadTime=0 LookbehindTime=0 KneeWidth=0 OutputGain=0\n");
|
||||
y = import_from_aud(1);
|
||||
|
||||
do_test_equ(settled(y, fs, 1), ...
|
||||
comp_gain(settled(env_PT1_asym(x, fs, 1.0, 0.3, 1.0), fs, 1), -6, 2.0, 0, 0).*
|
||||
settled(x, fs, 1));
|
||||
|
||||
## Test Compressor, mono lookaround max
|
||||
CURRENT_TEST = "Compressor2, mono asymmetric lookaround max";
|
||||
remove_all_tracks();
|
||||
x = export_to_aud(x1, fs);
|
||||
aud_do("DynamicCompressor: Threshold=-17 Algorithm=1 CompressBy=0 Ratio=1.2 AttackTime=0.3 ReleaseTime=0.3 LookaheadTime=0.2 LookbehindTime=0.1 KneeWidth=5 OutputGain=1\n");
|
||||
y = import_from_aud(1);
|
||||
|
||||
do_test_equ(settled(y, fs, 0.6), ...
|
||||
comp_gain(settled(env_PT1(lookaround_max(x, fs, 0.2, 0.1), fs, 0.3, 1), fs, 0.6), ...
|
||||
-17, 1.2, 5, 1).*settled(x, fs, 0.6));
|
||||
|
||||
## Test Compressor, mono lookaround RMS
|
||||
CURRENT_TEST = "Compressor2, mono asymmetric lookaround RMS";
|
||||
remove_all_tracks();
|
||||
x = export_to_aud(x1, fs);
|
||||
aud_do("DynamicCompressor: Threshold=-20 Algorithm=1 CompressBy=1 Ratio=3 AttackTime=1 ReleaseTime=1 LookaheadTime=0.1 LookbehindTime=0.2 KneeWidth=3 OutputGain=2\n");
|
||||
y = import_from_aud(1);
|
||||
|
||||
do_test_equ(settled(y, fs, 2), ...
|
||||
comp_gain(settled(env_PT1(lookaround_RMS(x, fs, 0.1, 0.2), fs, 1), fs, 2), -20, 3, 3, 2) ...
|
||||
.*settled(x, fs, 2));
|
||||
|
||||
## Test Compressor, mono lookaround max with selection
|
||||
CURRENT_TEST = "Compressor2, mono lookaround max with selection";
|
||||
remove_all_tracks();
|
||||
x = export_to_aud(x1, fs);
|
||||
|
||||
aud_do("Select: Start=2 End=5 Mode=Set\n");
|
||||
aud_do("DynamicCompressor: Threshold=-17 Algorithm=1 CompressBy=0 Ratio=1.2 AttackTime=0.3 ReleaseTime=0.3 LookaheadTime=0.2 LookbehindTime=0.2 KneeWidth=5 OutputGain=0.5\n");
|
||||
y = import_from_aud(1);
|
||||
x = x(2*fs+1:5*fs);
|
||||
|
||||
do_test_equ(settled(y, fs, 0.1), ...
|
||||
comp_gain(settled(env_PT1(lookaround_max(x, fs, 0.2, 0.2), fs, 0.3, 1), fs, 0.1), ...
|
||||
-17, 1.2, 5, 0.5).*settled(x, fs, 0.1));
|
||||
|
||||
## Test Compressor, mono, ultra short attack time
|
||||
CURRENT_TEST = "Compressor2, mono, ultra short attack time";
|
||||
remove_all_tracks();
|
||||
x = export_to_aud(x1, fs);
|
||||
aud_do("DynamicCompressor: Threshold=-20 Algorithm=1 CompressBy=0 Ratio=2 AttackTime=0.0001 ReleaseTime=0.0001 LookaheadTime=0 LookbehindTime=0 KneeWidth=10\n");
|
||||
y = import_from_aud(2);
|
||||
|
||||
# XXX: use larger epsilon due to numerical issues
|
||||
# (float in audacity vs double in octave vs wav files for exchange)
|
||||
do_test_equ(settled(y, fs, 1), ...
|
||||
comp_gain(settled(env_PT1(x, fs, 0.00001, 1), fs), -20, 2, 10, 0) ...
|
||||
.*settled(x, fs, 1), "", 0.15);
|
||||
|
||||
## Test Compressor, stereo compression PT1 - no lookaround
|
||||
randn("seed", 2);
|
||||
x1 = 0.2*randn(35*fs, 2);
|
||||
x1(:,1) = x1(:,1) .* sin(2*pi/fs/35*(1:1:35*fs)).';
|
||||
x1(:,2) = x1(:,2) .* (sin(2*pi/fs/75*(1:1:35*fs)).' + 0.1);
|
||||
|
||||
CURRENT_TEST = "Compressor2, stereo compression PT1";
|
||||
remove_all_tracks();
|
||||
x = export_to_aud(x1, fs, "Compressor-stereo-test.wav");
|
||||
aud_do("DynamicCompressor: Threshold=-20 Algorithm=1 CompressBy=0 Ratio=2 AttackTime=0.5 ReleaseTime=0.5 LookaheadTime=0 LookbehindTime=0 KneeWidth=10\n");
|
||||
y = import_from_aud(2);
|
||||
|
||||
do_test_equ(settled(y, fs, 1), ...
|
||||
comp_gain(settled(env_PT1(x, fs, 0.5, 1), fs), -20, 2, 10, 0) ...
|
||||
.*settled(x, fs, 1), "stereo dependent");
|
||||
|
||||
remove_all_tracks();
|
||||
x = export_to_aud(x1, fs);
|
||||
aud_do("DynamicCompressor: Threshold=-20 Algorithm=1 Ratio=2 AttackTime=0.5 ReleaseTime=0.5 LookaheadTime=0 LookbehindTime=0 KneeWidth=10 StereoIndependent=1\n");
|
||||
y = import_from_aud(2);
|
||||
|
||||
do_test_equ(settled(y(:,1), fs, 1), ...
|
||||
comp_gain(settled(env_PT1(x(:,1), fs, 0.5, 1), fs, 1), -20, 2, 10, 0) ...
|
||||
.*settled(x(:,1), fs, 1), "channel 1");
|
||||
do_test_equ(settled(y(:,2), fs, 1), ...
|
||||
comp_gain(settled(env_PT1(x(:,2), fs, 0.5, 1), fs, 1), -20, 2, 10, 0) ...
|
||||
.*settled(x(:,2), fs, 1), "channel 2");
|
||||
|
||||
## Test Compressor, stereo compression PT1 - sinewave
|
||||
CURRENT_TEST = "Compressor2, stereo compression PT1 - sinewave";
|
||||
x2 = sin(2*pi*300/fs*(1:1:20*fs)).';
|
||||
x2 = [x2, sin(2*pi*310/fs*(1:1:20*fs)).'];
|
||||
|
||||
remove_all_tracks();
|
||||
x = export_to_aud(x2, fs, "Compressor-stereo-sine-test.wav");
|
||||
aud_do("DynamicCompressor: Threshold=-20 Algorithm=1 CompressBy=0 Ratio=2 AttackTime=0.5 ReleaseTime=0.5 LookaheadTime=0 LookbehindTime=0 KneeWidth=10\n");
|
||||
y = import_from_aud(2);
|
||||
|
||||
do_test_equ(settled(y, fs, 1), ...
|
||||
comp_gain(settled(env_PT1(x, fs, 0.5, 1), fs, 1), -20, 2, 10, 0) ...
|
||||
.*settled(x, fs, 1), "stereo dependent");
|
||||
|
||||
remove_all_tracks();
|
||||
x = export_to_aud(x2, fs);
|
||||
aud_do("DynamicCompressor: Threshold=-20 Algorithm=1 Ratio=2 AttackTime=0.5 ReleaseTime=0.5 LookaheadTime=0 LookbehindTime=0 KneeWidth=10 StereoIndependent=1\n");
|
||||
y = import_from_aud(2);
|
||||
|
||||
do_test_equ(settled(y(:,1), fs, 1), ...
|
||||
comp_gain(settled(env_PT1(x(:,1), fs, 0.5, 1), fs, 1), -20, 2, 10, 0) ...
|
||||
.*settled(x(:,1), fs, 1), "channel 1");
|
||||
do_test_equ(settled(y(:,2), fs, 1), ...
|
||||
comp_gain(settled(env_PT1(x(:,2), fs, 0.5, 1), fs, 1), -20, 2, 10, 0) ...
|
||||
.*settled(x(:,2), fs, 1), "channel 2");
|
||||
|
||||
## Test Compressor, stereo lookaround max
|
||||
CURRENT_TEST = "Compressor2, stereo lookaround max";
|
||||
remove_all_tracks();
|
||||
x = export_to_aud(x1, fs);
|
||||
aud_do("DynamicCompressor: Threshold=-17 Algorithm=1 Ratio=1.2 AttackTime=0.3 ReleaseTime=0.3 LookaheadTime=0.2 LookbehindTime=0.2 KneeWidth=5 OutputGain=1\n");
|
||||
y = import_from_aud(2);
|
||||
|
||||
do_test_equ(settled(y, fs, 0.6), ...
|
||||
comp_gain(settled(env_PT1(lookaround_max(x, fs, 0.2, 0.2), fs, 0.3, 1), fs, 0.6), ...
|
||||
-17, 1.2, 5, 1).*settled(x, fs, 0.6));
|
||||
|
||||
## Test Compressor, stereo lookaround RMS
|
||||
CURRENT_TEST = "Compressor2, stereo lookaround RMS";
|
||||
remove_all_tracks();
|
||||
x = export_to_aud(x1, fs);
|
||||
aud_do("DynamicCompressor: Threshold=-20 Algorithm=1 Ratio=3 AttackTime=1 ReleaseTime=1 LookaheadTime=0.1 LookbehindTime=0.1 KneeWidth=3 CompressBy=1 OutputGain=1.3\n");
|
||||
y = import_from_aud(2);
|
||||
|
||||
do_test_equ(settled(y, fs, 2.5), ...
|
||||
comp_gain(settled(env_PT1(lookaround_RMS(x, fs, 0.1, 0.1), fs, 1), fs, 2.5), -20, 3, 3, 1.3) ...
|
||||
.*settled(x, fs, 2.5));
|
@ -125,20 +125,12 @@ end
|
||||
## Test Loudness LUFS mode: block to short and all silent
|
||||
CURRENT_TEST = "Loudness LUFS mode, short silent block";
|
||||
fs= 44100;
|
||||
x = zeros(ceil(fs*0.35), 2);
|
||||
audiowrite(TMP_FILENAME, x, fs);
|
||||
if EXPORT_TEST_SIGNALS
|
||||
audiowrite(cstrcat(pwd(), "/Loudness-LUFS-silence-test.wav"), x, fs);
|
||||
end
|
||||
x1 = zeros(ceil(fs*0.35), 2);
|
||||
|
||||
remove_all_tracks();
|
||||
aud_do(cstrcat("Import2: Filename=\"", TMP_FILENAME, "\"\n"));
|
||||
select_tracks(0, 100);
|
||||
x = export_to_aud(x1, fs, "Loudness-LUFS-silence-test.wav");
|
||||
aud_do("LoudnessNormalization: LUFSLevel=-23 DualMono=1 NormalizeTo=0 StereoIndependent=0\n");
|
||||
aud_do(cstrcat("Export2: Filename=\"", TMP_FILENAME, "\" NumChannels=2\n"));
|
||||
system("sync");
|
||||
|
||||
y = audioread(TMP_FILENAME);
|
||||
y = import_from_aud(2);
|
||||
do_test_equ(y, x, "identity");
|
||||
|
||||
## Test Loudness LUFS mode: stereo dependent
|
||||
@ -146,76 +138,50 @@ CURRENT_TEST = "Loudness LUFS mode, keep DC and stereo balance";
|
||||
randn("seed", 1);
|
||||
# Include some silence in the test signal to test loudness gating
|
||||
# and vary the overall loudness over time.
|
||||
x = [0.1*randn(15*fs, 2).', zeros(5*fs, 2).', 0.1*randn(15*fs, 2).'].';
|
||||
x(:,1) = x(:,1) .* sin(2*pi/fs/35*(1:1:35*fs)).' .* 1.2;
|
||||
x(:,2) = x(:,2) .* sin(2*pi/fs/35*(1:1:35*fs)).';
|
||||
audiowrite(TMP_FILENAME, x, fs);
|
||||
if EXPORT_TEST_SIGNALS
|
||||
audiowrite(cstrcat(pwd(), "/Loudness-LUFS-stereo-test.wav"), x, fs);
|
||||
end
|
||||
x1 = [0.1*randn(15*fs, 2).', zeros(5*fs, 2).', 0.1*randn(15*fs, 2).'].';
|
||||
x1(:,1) = x1(:,1) .* sin(2*pi/fs/35*(1:1:35*fs)).' .* 1.2;
|
||||
x1(:,2) = x1(:,2) .* sin(2*pi/fs/35*(1:1:35*fs)).';
|
||||
|
||||
remove_all_tracks();
|
||||
aud_do(cstrcat("Import2: Filename=\"", TMP_FILENAME, "\"\n"));
|
||||
select_tracks(0, 100);
|
||||
x = export_to_aud(x1, fs, "Loudness-LUFS-stereo-test.wav");
|
||||
aud_do("LoudnessNormalization: LUFSLevel=-23 DualMono=1 NormalizeTo=0 StereoIndependent=0\n");
|
||||
aud_do(cstrcat("Export2: Filename=\"", TMP_FILENAME, "\" NumChannels=2\n"));
|
||||
system("sync");
|
||||
|
||||
y = audioread(TMP_FILENAME);
|
||||
y = import_from_aud(2);
|
||||
do_test_equ(calc_LUFS(y, fs), -23, "loudness", LUFS_epsilon);
|
||||
do_test_neq(calc_LUFS(y(:,1), fs), calc_LUFS(y(:,2), fs), "stereo balance", 1);
|
||||
|
||||
## Test Loudness LUFS mode, stereo independent
|
||||
CURRENT_TEST = "Loudness LUFS mode, stereo independence";
|
||||
audiowrite(TMP_FILENAME, x, fs);
|
||||
remove_all_tracks();
|
||||
aud_do(cstrcat("Import2: Filename=\"", TMP_FILENAME, "\"\n"));
|
||||
select_tracks(0, 100);
|
||||
x = export_to_aud(x1, fs);
|
||||
aud_do("LoudnessNormalization: LUFSLevel=-23 DualMono=0 NormalizeTo=0 StereoIndependent=1\n");
|
||||
aud_do(cstrcat("Export2: Filename=\"", TMP_FILENAME, "\" NumChannels=2\n"));
|
||||
system("sync");
|
||||
|
||||
y = audioread(TMP_FILENAME);
|
||||
y = import_from_aud(2);
|
||||
# Independently processed stereo channels have half the target loudness.
|
||||
do_test_equ(calc_LUFS(y(:,1), fs), -26, "channel 1 loudness", LUFS_epsilon);
|
||||
do_test_equ(calc_LUFS(y(:,2), fs), -26, "channel 2 loudness", LUFS_epsilon);
|
||||
|
||||
## Test Loudness LUFS mode: mono as mono
|
||||
CURRENT_TEST = "Test Loudness LUFS mode: mono as mono";
|
||||
x = x(:,1);
|
||||
audiowrite(TMP_FILENAME, x, fs);
|
||||
if EXPORT_TEST_SIGNALS
|
||||
audiowrite(cstrcat(pwd(), "/Loudness-LUFS-mono-test.wav"), x, fs);
|
||||
end
|
||||
x1 = x1(:,1);
|
||||
|
||||
remove_all_tracks();
|
||||
aud_do(cstrcat("Import2: Filename=\"", TMP_FILENAME, "\"\n"));
|
||||
select_tracks(0, 100);
|
||||
x = export_to_aud(x1, fs, "Loudness-LUFS-mono-test.wav");
|
||||
aud_do("LoudnessNormalization: LUFSLevel=-26 DualMono=0 NormalizeTo=0 StereoIndependent=1\n");
|
||||
aud_do(cstrcat("Export2: Filename=\"", TMP_FILENAME, "\" NumChannels=1\n"));
|
||||
system("sync");
|
||||
|
||||
y = audioread(TMP_FILENAME);
|
||||
y = import_from_aud(1);
|
||||
do_test_equ(calc_LUFS(y, fs), -26, "loudness", LUFS_epsilon);
|
||||
|
||||
## Test Loudness LUFS mode: mono as dual-mono
|
||||
CURRENT_TEST = "Test Loudness LUFS mode: mono as dual-mono";
|
||||
audiowrite(TMP_FILENAME, x, fs);
|
||||
|
||||
remove_all_tracks();
|
||||
aud_do(cstrcat("Import2: Filename=\"", TMP_FILENAME, "\"\n"));
|
||||
select_tracks(0, 100);
|
||||
x = export_to_aud(x1, fs);
|
||||
aud_do("LoudnessNormalization: LUFSLevel=-26 DualMono=1 NormalizeTo=0 StereoIndependent=0\n");
|
||||
aud_do(cstrcat("Export2: Filename=\"", TMP_FILENAME, "\" NumChannels=1\n"));
|
||||
system("sync");
|
||||
|
||||
y = audioread(TMP_FILENAME);
|
||||
y = import_from_aud(1);
|
||||
# This shall be 3 LU quieter as it is compared to strict spec.
|
||||
do_test_equ(calc_LUFS(y, fs), -29, "loudness", LUFS_epsilon);
|
||||
|
||||
## Test Loudness LUFS mode: multi-rate project
|
||||
CURRENT_TEST = "Test Loudness LUFS mode: multi-rate project";
|
||||
audiowrite(TMP_FILENAME, x, fs);
|
||||
audiowrite(TMP_FILENAME, x1, fs);
|
||||
x = audioread(TMP_FILENAME);
|
||||
|
||||
remove_all_tracks();
|
||||
aud_do(cstrcat("Import2: Filename=\"", TMP_FILENAME, "\"\n"));
|
||||
@ -228,6 +194,7 @@ audiowrite(TMP_FILENAME, x1, fs1);
|
||||
if EXPORT_TEST_SIGNALS
|
||||
audiowrite(cstrcat(pwd(), "/Loudness-LUFS-stereo-test-8kHz.wav"), x1, fs1);
|
||||
end
|
||||
x1 = audioread(TMP_FILENAME);
|
||||
|
||||
aud_do(cstrcat("Import2: Filename=\"", TMP_FILENAME, "\"\n"));
|
||||
select_tracks(0, 100);
|
||||
@ -255,36 +222,22 @@ do_test_neq(calc_LUFS(y1(:,1), fs), calc_LUFS(y1(:,2), fs), "stereo balance trac
|
||||
CURRENT_TEST = "Loudness RMS mode, stereo independent";
|
||||
randn("seed", 1);
|
||||
fs= 44100;
|
||||
x = 0.1*randn(30*fs, 2);
|
||||
x(:,1) = x(:,1) * 0.6;
|
||||
audiowrite(TMP_FILENAME, x, fs);
|
||||
if EXPORT_TEST_SIGNALS
|
||||
audiowrite(cstrcat(pwd(), "/Loudness-RMS-test.wav"), x, fs);
|
||||
end
|
||||
x1 = 0.1*randn(30*fs, 2);
|
||||
x1(:,1) = x1(:,1) * 0.6;
|
||||
|
||||
remove_all_tracks();
|
||||
aud_do(cstrcat("Import2: Filename=\"", TMP_FILENAME, "\"\n"));
|
||||
select_tracks(0, 100);
|
||||
x = export_to_aud(x1, fs, "Loudness-RMS-test.wav");
|
||||
aud_do("LoudnessNormalization: RMSLevel=-20 DualMono=0 NormalizeTo=1 StereoIndependent=1\n");
|
||||
aud_do(cstrcat("Export2: Filename=\"", TMP_FILENAME, "\" NumChannels=2\n"));
|
||||
system("sync");
|
||||
|
||||
y = audioread(TMP_FILENAME);
|
||||
y = import_from_aud(2);
|
||||
do_test_equ(20*log10(sqrt(sum(y(:,1).*y(:,1)/length(y)))), -20, "channel 1 RMS");
|
||||
do_test_equ(20*log10(sqrt(sum(y(:,2).*y(:,2)/length(y)))), -20, "channel 2 RMS");
|
||||
|
||||
## Test Loudness RMS mode: stereo dependent
|
||||
CURRENT_TEST = "Loudness RMS mode, stereo dependent";
|
||||
audiowrite(TMP_FILENAME, x, fs);
|
||||
|
||||
remove_all_tracks();
|
||||
aud_do(cstrcat("Import2: Filename=\"", TMP_FILENAME, "\"\n"));
|
||||
select_tracks(0, 100);
|
||||
x = export_to_aud(x1, fs);
|
||||
aud_do("LoudnessNormalization: RMSLevel=-22 DualMono=1 NormalizeTo=1 StereoIndependent=0\n");
|
||||
aud_do(cstrcat("Export2: Filename=\"", TMP_FILENAME, "\" NumChannels=2\n"));
|
||||
system("sync");
|
||||
|
||||
y = audioread(TMP_FILENAME);
|
||||
y = import_from_aud(2);
|
||||
# Stereo RMS must be calculated in quadratic domain.
|
||||
do_test_equ(20*log10(sqrt(sum(rms(y).^2)/size(y)(2))), -22, "RMS");
|
||||
do_test_neq(20*log10(rms(y(:,1))), 20*log10(rms(y(:,2))), "stereo balance", 1);
|
||||
|
@ -29,10 +29,13 @@ if nargin == 2
|
||||
end
|
||||
|
||||
## Initialization and helper functions
|
||||
global TMP_FILENAME;
|
||||
global EXPORT_TEST_SIGNALS;
|
||||
UID=num2str(getuid());
|
||||
PIPE_TO_PATH=strcat("/tmp/audacity_script_pipe.to.", UID);
|
||||
PIPE_FROM_PATH=strcat("/tmp/audacity_script_pipe.from.", UID);
|
||||
TMP_FILENAME=strcat(pwd(), "/tmp.wav");
|
||||
EXPORT_TEST_SIGNALS = false;
|
||||
|
||||
printf("Open scripting pipes, this may freeze if Audacity does not run...\n");
|
||||
|
||||
@ -74,6 +77,27 @@ function select_tracks(num, count)
|
||||
aud_do(sprintf("SelectTracks: Track=%d TrackCount=%d Mode=Set\n", num, count));
|
||||
end
|
||||
|
||||
function x_in = import_from_aud(channels)
|
||||
global TMP_FILENAME;
|
||||
aud_do(cstrcat("Export2: Filename=\"", TMP_FILENAME, "\" NumChannels=", ...
|
||||
num2str(channels), "\n"));
|
||||
system("sync");
|
||||
x_in = audioread(TMP_FILENAME);
|
||||
end
|
||||
|
||||
function x_out = export_to_aud(x, fs, name = "")
|
||||
global TMP_FILENAME;
|
||||
global EXPORT_TEST_SIGNALS;
|
||||
audiowrite(TMP_FILENAME, x, fs);
|
||||
if EXPORT_TEST_SIGNALS && length(name) != 0
|
||||
audiowrite(cstrcat(pwd(), "/", name), x, fs);
|
||||
end
|
||||
# Read it back to avoid quantization-noise in tests
|
||||
x_out = audioread(TMP_FILENAME);
|
||||
aud_do(cstrcat("Import2: Filename=\"", TMP_FILENAME, "\"\n"));
|
||||
select_tracks(0, 100);
|
||||
end
|
||||
|
||||
## Float equal comparison helper
|
||||
function [ret] = float_eq(x, y, eps=0.001)
|
||||
ret = abs(x - y) < eps;
|
||||
@ -99,41 +123,43 @@ function plot_failure(x, y)
|
||||
plot(x, 'r')
|
||||
hold on
|
||||
plot(y, 'b')
|
||||
plot(log10(abs(x-y)), 'g')
|
||||
delta = abs(x-y);
|
||||
max(delta)
|
||||
plot(log10(delta), 'g')
|
||||
hold off
|
||||
legend("Audacity", "Octave", "log-delta", "location", "southeast")
|
||||
input("Press enter to continue", "s")
|
||||
end
|
||||
|
||||
function do_test_equ(x, y, msg, eps=0.001, skip = false)
|
||||
function do_test_equ(x, y, msg = "", eps = 0.001, skip = false)
|
||||
cmp = all(all(float_eq(x, y, eps)));
|
||||
if do_test(cmp, msg, skip) == 0
|
||||
plot_failure(x, y);
|
||||
end
|
||||
end
|
||||
|
||||
function do_test_neq(x, y, msg, eps=0.001, skip = false)
|
||||
function do_test_neq(x, y, msg = "", eps = 0.001, skip = false)
|
||||
cmp = all(all(!float_eq(x, y, eps)));
|
||||
if do_test(cmp, msg, skip) == 0
|
||||
plot_failure(x, y);
|
||||
end
|
||||
end
|
||||
|
||||
function do_test_gte(x, y, msg, skip = false)
|
||||
function do_test_gte(x, y, msg = "", skip = false)
|
||||
cmp = all(all(x >= y));
|
||||
if do_test(cmp, msg, skip) == 0
|
||||
plot_failure(x, y);
|
||||
end
|
||||
end
|
||||
|
||||
function do_test_lte(x, y, msg, skip = false)
|
||||
function do_test_lte(x, y, msg = "", skip = false)
|
||||
cmp = all(all(x <= y));
|
||||
if do_test(cmp, msg, skip) == 0
|
||||
plot_failure(x, y);
|
||||
end
|
||||
end
|
||||
|
||||
function result = do_test(result, msg, skip = false)
|
||||
function result = do_test(result, msg = "", skip = false)
|
||||
global TESTS_RUN;
|
||||
global TESTS_FAILED;
|
||||
global TESTS_SKIPPED;
|
||||
|
Loading…
x
Reference in New Issue
Block a user