Prevent clipping during the import of files from sources that have
authorCarl Hetherington <carl@carlh.net>
Fri, 9 Apr 2010 14:11:47 +0000 (14:11 +0000)
committerCarl Hetherington <carl@carlh.net>
Fri, 9 Apr 2010 14:11:47 +0000 (14:11 +0000)
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

14 files changed:
libs/ardour/ardour/audiosource.h
libs/ardour/ardour/importable_source.h
libs/ardour/ardour/resampled_source.h
libs/ardour/ardour/silentfilesource.h
libs/ardour/ardour/sndfileimportable.h
libs/ardour/ardour/sndfilesource.h
libs/ardour/import.cc
libs/ardour/resampled_source.cc
libs/ardour/sndfileimportable.cc
libs/ardour/sndfilesource.cc
libs/ardour/test/resampled_source.cc [new file with mode: 0644]
libs/ardour/test/resampled_source.h [new file with mode: 0644]
libs/ardour/test/test.wav [new file with mode: 0755]
libs/ardour/wscript

index e78fb049d7aa7e58b3fdfd8e6478162d744f1dc2..96779864493a22cc0f1e51881c88367196a08242 100644 (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;
index 801e7888a96f98103df58db40c95144ba95a6517..7df346a782ddde412cfa1d7f42e0fbfd3880d065 100644 (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;
 };
 
 }
index efa14581528565938388af880b9dfde47c9df045..b61303b65ca7efe18975f036232bb458eeb16391 100644 (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;
 };
index 9b20f6f1b0e02666027cca6a59e4a94d12437ecb..23128c40256abd86402fa28d50bbf2dfeea8dcca 100644 (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;
 
index 95c6d80ebb7322cd194d60792f50a6067c783100..6e308415c97072956d6a1c80994aebfcdb1dfdc2 100644 (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;
index 19dcb135363317de098b1b697139550e63f6d06c..96f39de2c8e2b358fd5d8c8c4d6f16c2c9f77a5a 100644 (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;
 
index e05d162a568a6880f188dd36c67b7a51af944039..61eb959829878318a2c9f4c5ff7401e6ae0d24ef 100644 (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);
        }
 }
 
index 57811312f15b5167ade5b0f46da18d0be9a4b8e7..675a0e426dc083473f11c03e5af2cea4c9d7ebc8 100644 (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 ;
+       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;
+}
+       
index cc68f3ea01ebce146a7b72b13de57e898e21b8cd..758e8955c2b82e3187fc4ff029061d911ee6d188 100644 (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);
+}
index 05de692016117e465ebd1b9388f853163744bd6b..fa709cde24ffeb29f043d1a79576f5bd5ceeed42 100644 (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);
+}
diff --git a/libs/ardour/test/resampled_source.cc b/libs/ardour/test/resampled_source.cc
new file mode 100644 (file)
index 0000000..02bd7ff
--- /dev/null
@@ -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]);
+       }
+}
diff --git a/libs/ardour/test/resampled_source.h b/libs/ardour/test/resampled_source.h
new file mode 100644 (file)
index 0000000..c836968
--- /dev/null
@@ -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 ();
+};
diff --git a/libs/ardour/test/test.wav b/libs/ardour/test/test.wav
new file mode 100755 (executable)
index 0000000..369531c
Binary files /dev/null and b/libs/ardour/test/test.wav differ
index e49c1b12848407baffdefbb7a4e33a78a94d9f90..ce9d41d7957ad82d829bfa483c4252c6900ab0a3 100644 (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']