From f4ac9430f3fc2c405334f3f02fe08a5782454396 Mon Sep 17 00:00:00 2001 From: Carl Hetherington Date: Fri, 9 Apr 2010 14:11:47 +0000 Subject: [PATCH] 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 --- libs/ardour/ardour/audiosource.h | 3 ++ libs/ardour/ardour/importable_source.h | 2 + libs/ardour/ardour/resampled_source.h | 8 +++- libs/ardour/ardour/silentfilesource.h | 2 + libs/ardour/ardour/sndfileimportable.h | 1 + libs/ardour/ardour/sndfilesource.h | 2 + libs/ardour/import.cc | 50 ++++++++++++++++++++- libs/ardour/resampled_source.cc | 60 ++++++++++++++----------- libs/ardour/sndfileimportable.cc | 8 ++++ libs/ardour/sndfilesource.cc | 7 +++ libs/ardour/test/resampled_source.cc | 31 +++++++++++++ libs/ardour/test/resampled_source.h | 12 +++++ libs/ardour/test/test.wav | Bin 0 -> 17720 bytes libs/ardour/wscript | 1 + 14 files changed, 158 insertions(+), 29 deletions(-) create mode 100644 libs/ardour/test/resampled_source.cc create mode 100644 libs/ardour/test/resampled_source.h create mode 100755 libs/ardour/test/test.wav diff --git a/libs/ardour/ardour/audiosource.h b/libs/ardour/ardour/audiosource.h index e78fb049d7..9677986449 100644 --- a/libs/ardour/ardour/audiosource.h +++ b/libs/ardour/ardour/audiosource.h @@ -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; diff --git a/libs/ardour/ardour/importable_source.h b/libs/ardour/ardour/importable_source.h index 801e7888a9..7df346a782 100644 --- a/libs/ardour/ardour/importable_source.h +++ b/libs/ardour/ardour/importable_source.h @@ -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; }; } diff --git a/libs/ardour/ardour/resampled_source.h b/libs/ardour/ardour/resampled_source.h index efa1458152..b61303b65c 100644 --- a/libs/ardour/ardour/resampled_source.h +++ b/libs/ardour/ardour/resampled_source.h @@ -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 source; float* input; + int _src_type; SRC_STATE* src_state; SRC_DATA src_data; }; diff --git a/libs/ardour/ardour/silentfilesource.h b/libs/ardour/ardour/silentfilesource.h index 9b20f6f1b0..23128c4025 100644 --- a/libs/ardour/ardour/silentfilesource.h +++ b/libs/ardour/ardour/silentfilesource.h @@ -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; diff --git a/libs/ardour/ardour/sndfileimportable.h b/libs/ardour/ardour/sndfileimportable.h index 95c6d80ebb..6e308415c9 100644 --- a/libs/ardour/ardour/sndfileimportable.h +++ b/libs/ardour/ardour/sndfileimportable.h @@ -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; diff --git a/libs/ardour/ardour/sndfilesource.h b/libs/ardour/ardour/sndfilesource.h index 19dcb13536..96f39de2c8 100644 --- a/libs/ardour/ardour/sndfilesource.h +++ b/libs/ardour/ardour/sndfilesource.h @@ -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; diff --git a/libs/ardour/import.cc b/libs/ardour/import.cc index e05d162a56..61eb959829 100644 --- a/libs/ardour/import.cc +++ b/libs/ardour/import.cc @@ -277,8 +277,48 @@ write_audio_data_to_new_files (ImportableSource* source, ImportStatus& status, channel_data.push_back(boost::shared_array(new Sample[nframes])); } - uint read_count = 0; + float gain = 1; + + boost::shared_ptr s = boost::dynamic_pointer_cast (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); } } diff --git a/libs/ardour/resampled_source.cc b/libs/ardour/resampled_source.cc index 57811312f1..675a0e426d 100644 --- a/libs/ardour/resampled_source.cc +++ b/libs/ardour/resampled_source.cc @@ -30,48 +30,33 @@ const uint32_t ResampledImportableSource::blocksize = 16384U; ResampledImportableSource::ResampledImportableSource (boost::shared_ptr 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 ; + input = new float[blocksize]; + seek (0); + src_data.src_ratio = ((float) rate) / source->samplerate(); - - input = new float[blocksize]; } 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; +} + diff --git a/libs/ardour/sndfileimportable.cc b/libs/ardour/sndfileimportable.cc index cc68f3ea01..758e8955c2 100644 --- a/libs/ardour/sndfileimportable.cc +++ b/libs/ardour/sndfileimportable.cc @@ -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); +} diff --git a/libs/ardour/sndfilesource.cc b/libs/ardour/sndfilesource.cc index 05de692016..fa709cde24 100644 --- a/libs/ardour/sndfilesource.cc +++ b/libs/ardour/sndfilesource.cc @@ -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); +} diff --git a/libs/ardour/test/resampled_source.cc b/libs/ardour/test/resampled_source.cc new file mode 100644 index 0000000000..02bd7ff1a8 --- /dev/null +++ b/libs/ardour/test/resampled_source.cc @@ -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 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]); + } +} diff --git a/libs/ardour/test/resampled_source.h b/libs/ardour/test/resampled_source.h new file mode 100644 index 0000000000..c836968987 --- /dev/null +++ b/libs/ardour/test/resampled_source.h @@ -0,0 +1,12 @@ +#include +#include + +class ResampledSourceTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE (ResampledSourceTest); + CPPUNIT_TEST (seekTest); + CPPUNIT_TEST_SUITE_END (); + +public: + void seekTest (); +}; diff --git a/libs/ardour/test/test.wav b/libs/ardour/test/test.wav new file mode 100755 index 0000000000000000000000000000000000000000..369531cc912e077bb5ca79bc4f0429f247aa6fec GIT binary patch literal 17720 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 literal 0 HcmV?d00001 diff --git a/libs/ardour/wscript b/libs/ardour/wscript index e49c1b1284..ce9d41d795 100644 --- a/libs/ardour/wscript +++ b/libs/ardour/wscript @@ -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'] -- 2.30.2