Prevent clipping during the import of files from sources that have

amplitudes greater than 1 when data is being stored in files that
are clamped. e.g. when importing hot sources and resampling them
when the session file format is integer.


git-svn-id: svn://localhost/ardour2/branches/3.0@6879 d708f5d6-7413-0410-9779-e7cbd77b26cf
This commit is contained in:
Carl Hetherington
2010-04-09 14:11:47 +00:00
parent 77c09fc824
commit f4ac9430f3
14 changed files with 159 additions and 30 deletions

View File

@@ -100,6 +100,9 @@ class AudioSource : virtual public Source,
int prepare_for_peakfile_writes ();
void done_with_peakfile_writes (bool done = true);
/** @return true if the each source sample s must be clamped to -1 < s < 1 */
virtual bool clamped_at_unity () const = 0;
protected:
static bool _build_missing_peakfiles;
static bool _build_peakfiles;

View File

@@ -37,6 +37,8 @@ public:
virtual nframes_t samplerate() const = 0;
virtual void seek (nframes_t pos) = 0;
virtual nframes64_t natural_position() const = 0;
virtual bool clamped_at_unity () const = 0;
};
}

View File

@@ -39,14 +39,20 @@ class ResampledImportableSource : public ImportableSource
uint32_t channels() const { return source->channels(); }
nframes_t length() const { return source->length(); }
nframes_t samplerate() const { return source->samplerate(); }
void seek (nframes_t pos) { source->seek (pos); }
void seek (nframes_t);
nframes64_t natural_position() const { return source->natural_position(); }
bool clamped_at_unity () const {
/* resampling may generate inter-sample peaks with magnitude > 1 */
return false;
}
static const uint32_t blocksize;
private:
boost::shared_ptr<ImportableSource> source;
float* input;
int _src_type;
SRC_STATE* src_state;
SRC_DATA src_data;
};

View File

@@ -36,6 +36,8 @@ public:
bool destructive() const { return false; }
bool can_be_analysed() const { return false; }
bool clamped_at_unity() const { return false; }
protected:
friend class SourceFactory;

View File

@@ -39,6 +39,7 @@ class SndFileImportableSource : public ImportableSource {
nframes_t samplerate() const;
void seek (nframes_t pos);
nframes64_t natural_position() const;
bool clamped_at_unity () const;
protected:
SF_INFO sf_info;

View File

@@ -57,6 +57,8 @@ class SndFileSource : public AudioFileSource {
bool one_of_several_channels () const;
bool clamped_at_unity () const;
static void setup_standard_crossfades (Session const &, nframes_t sample_rate);
static const Source::Flag default_writable_flags;

View File

@@ -277,8 +277,48 @@ write_audio_data_to_new_files (ImportableSource* source, ImportStatus& status,
channel_data.push_back(boost::shared_array<Sample>(new Sample[nframes]));
}
uint read_count = 0;
float gain = 1;
boost::shared_ptr<AudioSource> s = boost::dynamic_pointer_cast<AudioSource> (newfiles[0]);
assert (s);
status.progress = 0.0f;
float progress_multiplier = 1;
float progress_base = 0;
if (!source->clamped_at_unity() && s->clamped_at_unity()) {
/* The source we are importing from can return sample values with a magnitude greater than 1,
and the file we are writing the imported data to cannot handle such values. Compute the gain
factor required to normalize the input sources to have a magnitude of less than 1.
*/
float peak = 0;
uint read_count = 0;
while (!status.cancel) {
nframes_t const nread = source->read (data.get(), nframes);
if (nread == 0) {
break;
}
peak = compute_peak (data.get(), nread, peak);
read_count += nread;
status.progress = 0.5 * read_count / (source->ratio() * source->length() * channels);
}
if (peak >= 1) {
/* we are out of range: compute a gain to fix it */
gain = (1 - FLT_EPSILON) / peak;
}
source->seek (0);
progress_multiplier = 0.5;
progress_base = 0.5;
}
uint read_count = 0;
while (!status.cancel) {
@@ -289,6 +329,12 @@ write_audio_data_to_new_files (ImportableSource* source, ImportStatus& status,
if ((nread = source->read (data.get(), nframes)) == 0) {
break;
}
if (gain != 1) {
/* here is the gain fix for out-of-range sample values that we computed earlier */
apply_gain_to_buffer (data.get(), nread, gain);
}
nfread = nread / channels;
/* de-interleave */
@@ -310,7 +356,7 @@ write_audio_data_to_new_files (ImportableSource* source, ImportStatus& status,
}
read_count += nread;
status.progress = read_count / (source->ratio () * source->length() * channels);
status.progress = progress_base + progress_multiplier * read_count / (source->ratio () * source->length() * channels);
}
}

View File

@@ -30,48 +30,33 @@ const uint32_t ResampledImportableSource::blocksize = 16384U;
ResampledImportableSource::ResampledImportableSource (boost::shared_ptr<ImportableSource> src, nframes_t rate, SrcQuality srcq)
: source (src)
, src_state (0)
{
int err;
source->seek (0);
/* Initialize the sample rate converter. */
int src_type = SRC_SINC_BEST_QUALITY;
_src_type = SRC_SINC_BEST_QUALITY;
switch (srcq) {
case SrcBest:
src_type = SRC_SINC_BEST_QUALITY;
_src_type = SRC_SINC_BEST_QUALITY;
break;
case SrcGood:
src_type = SRC_SINC_MEDIUM_QUALITY;
_src_type = SRC_SINC_MEDIUM_QUALITY;
break;
case SrcQuick:
src_type = SRC_SINC_FASTEST;
_src_type = SRC_SINC_FASTEST;
break;
case SrcFast:
src_type = SRC_ZERO_ORDER_HOLD;
_src_type = SRC_ZERO_ORDER_HOLD;
break;
case SrcFastest:
src_type = SRC_LINEAR;
_src_type = SRC_LINEAR;
break;
}
if ((src_state = src_new (src_type, source->channels(), &err)) == 0) {
error << string_compose(_("Import: src_new() failed : %1"), src_strerror (err)) << endmsg ;
throw failed_constructor ();
}
src_data.end_of_input = 0 ; /* Set this later. */
/* Start with zero to force load in while loop. */
src_data.input_frames = 0 ;
src_data.data_in = input ;
src_data.src_ratio = ((float) rate) / source->samplerate();
input = new float[blocksize];
seek (0);
src_data.src_ratio = ((float) rate) / source->samplerate();
}
ResampledImportableSource::~ResampledImportableSource ()
@@ -106,7 +91,7 @@ ResampledImportableSource::read (Sample* output, nframes_t nframes)
if (!src_data.end_of_input) {
src_data.output_frames = nframes / source->channels();
} else {
src_data.output_frames = src_data.input_frames;
src_data.output_frames = std::min ((nframes_t) src_data.input_frames, nframes / source->channels());
}
if ((err = src_process (src_state, &src_data))) {
@@ -126,3 +111,26 @@ ResampledImportableSource::read (Sample* output, nframes_t nframes)
return src_data.output_frames_gen * source->channels();
}
void
ResampledImportableSource::seek (nframes_t pos)
{
source->seek (pos);
/* and reset things so that we start from scratch with the conversion */
if (src_state) {
src_delete (src_state);
}
int err;
if ((src_state = src_new (_src_type, source->channels(), &err)) == 0) {
error << string_compose(_("Import: src_new() failed : %1"), src_strerror (err)) << endmsg ;
throw failed_constructor ();
}
src_data.input_frames = 0;
src_data.data_in = input;
src_data.end_of_input = 0;
}

View File

@@ -80,3 +80,11 @@ SndFileImportableSource::natural_position () const
{
return timecode;
}
bool
SndFileImportableSource::clamped_at_unity () const
{
int const sub = sf_info.format & SF_FORMAT_SUBMASK;
/* XXX: this may not be the full list of formats that are unclamped */
return (sub != SF_FORMAT_FLOAT && sub != SF_FORMAT_DOUBLE);
}

View File

@@ -833,3 +833,10 @@ SndFileSource::one_of_several_channels () const
return _info.channels > 1;
}
bool
SndFileSource::clamped_at_unity () const
{
int const sub = _info.format & SF_FORMAT_SUBMASK;
/* XXX: this may not be the full list of formats that are unclamped */
return (sub != SF_FORMAT_FLOAT && sub != SF_FORMAT_DOUBLE);
}

View File

@@ -0,0 +1,31 @@
#include "ardour/resampled_source.h"
#include "ardour/sndfileimportable.h"
#include "resampled_source.h"
CPPUNIT_TEST_SUITE_REGISTRATION (ResampledSourceTest);
using namespace ARDOUR;
void
ResampledSourceTest::seekTest ()
{
boost::shared_ptr<SndFileImportableSource> s (new SndFileImportableSource ("../../libs/ardour/test/test.wav"));
ResampledImportableSource r (s, 48000, SrcBest);
/* Make sure that seek (0) has the desired effect, ie that
given the same input you get the same output after seek (0)
as you got when the Source was newly created.
*/
Sample A[64];
r.read (A, 64);
r.seek (0);
Sample B[64];
r.read (B, 64);
for (int i = 0; i < 64; ++i) {
CPPUNIT_ASSERT (A[i] == B[i]);
}
}

View File

@@ -0,0 +1,12 @@
#include <cppunit/TestFixture.h>
#include <cppunit/extensions/HelperMacros.h>
class ResampledSourceTest : public CppUnit::TestFixture
{
CPPUNIT_TEST_SUITE (ResampledSourceTest);
CPPUNIT_TEST (seekTest);
CPPUNIT_TEST_SUITE_END ();
public:
void seekTest ();
};

BIN
libs/ardour/test/test.wav Executable file

Binary file not shown.

View File

@@ -330,6 +330,7 @@ def build(bld):
test/bbt_test.cpp
test/interpolation_test.cpp
test/midi_clock_slave_test.cpp
test/resampled_source.cc
test/testrunner.cpp
'''.split()
testobj.includes = obj.includes + ['test', '../pbd']