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:
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
31
libs/ardour/test/resampled_source.cc
Normal file
31
libs/ardour/test/resampled_source.cc
Normal 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]);
|
||||
}
|
||||
}
|
||||
12
libs/ardour/test/resampled_source.h
Normal file
12
libs/ardour/test/resampled_source.h
Normal 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
BIN
libs/ardour/test/test.wav
Executable file
Binary file not shown.
@@ -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']
|
||||
|
||||
Reference in New Issue
Block a user