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;
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;
};
}
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;
};
bool destructive() const { return false; }
bool can_be_analysed() const { return false; }
+ bool clamped_at_unity() const { return false; }
+
protected:
friend class SourceFactory;
nframes_t samplerate() const;
void seek (nframes_t pos);
nframes64_t natural_position() const;
+ bool clamped_at_unity () const;
protected:
SF_INFO sf_info;
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;
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) {
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 */
}
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);
}
}
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 ()
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))) {
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;
+}
+
{
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);
+}
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);
+}
--- /dev/null
+#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]);
+ }
+}
--- /dev/null
+#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 ();
+};
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']