diff --git a/libs/ardour/audio_playlist.cc b/libs/ardour/audio_playlist.cc index bcb0219e2a..47b2a5809a 100644 --- a/libs/ardour/audio_playlist.cc +++ b/libs/ardour/audio_playlist.cc @@ -149,6 +149,17 @@ struct ReadSorter { } }; +/** A segment of region that needs to be read */ +struct Segment { + Segment (boost::shared_ptr r, Evoral::Range a) : region (r), range (a) {} + + boost::shared_ptr region; ///< the region + Evoral::Range range; ///< range of the region to read, in session frames +}; + +/** @param start Start position in session frames. + * @param cnt Number of frames to read. + */ ARDOUR::framecnt_t AudioPlaylist::read (Sample *buf, Sample *mixdown_buffer, float *gain_buffer, framepos_t start, framecnt_t cnt, unsigned chan_n) @@ -184,16 +195,18 @@ AudioPlaylist::read (Sample *buf, Sample *mixdown_buffer, float *gain_buffer, fr all->sort (ReadSorter ()); /* This will be a list of the bits of our read range that we have - read completely (ie for which no more regions need to be read). + handled completely (ie for which no more regions need to be read). It is a list of ranges in session frames. */ Evoral::RangeList done; + + /* This will be a list of the bits of regions that we need to read */ + list to_do; + /* Now go through the `all' list filling in `to_do' and `done' */ for (RegionList::iterator i = all->begin(); i != all->end(); ++i) { boost::shared_ptr ar = boost::dynamic_pointer_cast (*i); - /* Trim region range to the bit we are reading */ - /* Work out which bits of this region need to be read; first, trim to the range we are reading... */ @@ -202,19 +215,17 @@ AudioPlaylist::read (Sample *buf, Sample *mixdown_buffer, float *gain_buffer, fr region_range.to = min (region_range.to, start + cnt - 1); /* ... and then remove the bits that are already done */ - Evoral::RangeList to_do = Evoral::subtract (region_range, done); + Evoral::RangeList region_to_do = Evoral::subtract (region_range, done); /* Read those bits, adding their bodies (the parts between end-of-fade-in and start-of-fade-out) to the `done' list. */ - Evoral::RangeList::List t = to_do.get (); + Evoral::RangeList::List t = region_to_do.get (); - for (Evoral::RangeList::List::iterator i = t.begin(); i != t.end(); ++i) { - Evoral::Range d = *i; - - /* Read the whole range, possibly including fades */ - ar->read_at (buf + d.from - start, mixdown_buffer, gain_buffer, d.from, d.to - d.from + 1, chan_n); + for (Evoral::RangeList::List::iterator j = t.begin(); j != t.end(); ++j) { + Evoral::Range d = *j; + to_do.push_back (Segment (ar, d)); if (ar->opaque ()) { /* Cut this range down to just the body and mark it done */ @@ -228,6 +239,11 @@ AudioPlaylist::read (Sample *buf, Sample *mixdown_buffer, float *gain_buffer, fr } } + /* Now go backwards through the to_do list doing the actual reads */ + for (list::reverse_iterator i = to_do.rbegin(); i != to_do.rend(); ++i) { + i->region->read_at (buf + i->range.from - start, mixdown_buffer, gain_buffer, i->range.from, i->range.to - i->range.from + 1, chan_n); + } + return cnt; } diff --git a/libs/ardour/audioregion.cc b/libs/ardour/audioregion.cc index 617678d634..e4a7504ec1 100644 --- a/libs/ardour/audioregion.cc +++ b/libs/ardour/audioregion.cc @@ -385,6 +385,10 @@ AudioRegion::_read_at (const SourceList& srcs, framecnt_t limit, The caller has verified that we cover the desired section. */ + /* See doc/region_read.svg for a drawing which might help to explain + what is going on. + */ + assert (cnt >= 0); if (n_channels() == 0) { @@ -478,7 +482,7 @@ AudioRegion::_read_at (const SourceList& srcs, framecnt_t limit, /* READ DATA FROM THE SOURCE INTO mixdown_buffer. We can never read directly into buf, since it may contain data - from a transparent region `above' this one in the stack; we + from a transparent region `below' this one in the stack; we must always mix. */ @@ -532,6 +536,12 @@ AudioRegion::_read_at (const SourceList& srcs, framecnt_t limit, if (fade_in_limit != 0) { _fade_in->curve().get_vector (internal_offset, internal_offset + fade_in_limit, gain_buffer, fade_in_limit); + /* Fade the current data out */ + for (framecnt_t n = 0; n < fade_in_limit; ++n) { + buf[n] *= 1 - gain_buffer[n]; + } + + /* Mix our newly-read data in, with the fade */ for (framecnt_t n = 0; n < fade_in_limit; ++n) { buf[n] += mixdown_buffer[n] * gain_buffer[n]; } @@ -540,7 +550,13 @@ AudioRegion::_read_at (const SourceList& srcs, framecnt_t limit, if (fade_out_limit != 0) { framecnt_t const curve_offset = fade_interval_start - (limit - _fade_out->back()->when); _fade_out->curve().get_vector (curve_offset, curve_offset + fade_out_limit, gain_buffer, fade_out_limit); - + + /* Fade the current data in */ + for (framecnt_t n = 0, m = fade_out_offset; n < fade_out_limit; ++n, ++m) { + buf[m] *= 1 - gain_buffer[n]; + } + + /* Mix our newly-read data in, with the fade */ for (framecnt_t n = 0, m = fade_out_offset; n < fade_out_limit; ++n, ++m) { buf[m] += mixdown_buffer[m] * gain_buffer[n]; } diff --git a/libs/ardour/test/playlist_read_test.cc b/libs/ardour/test/playlist_read_test.cc index 55c6abe4ad..e016b3adc6 100644 --- a/libs/ardour/test/playlist_read_test.cc +++ b/libs/ardour/test/playlist_read_test.cc @@ -74,8 +74,8 @@ PlaylistReadTest::overlappingReadTest () { /* Overlapping read; ar0 and ar1 are both 1024 frames long, ar0 starts at 0, ar1 starts at 128. We test a read from 0 to 256, which should consist - of the start of ar0, with its fade in, followed by ar1's fade in (mixed with ar0) - and some more of ar1. + of the start of ar0, with its fade in, followed by ar1's fade in (mixed with ar0 + faded out with the inverse gain), and some more of ar1. */ boost::shared_ptr ar0 = boost::dynamic_pointer_cast (_region[0]); @@ -102,9 +102,11 @@ PlaylistReadTest::overlappingReadTest () /* ar0's fade in */ for (int i = 0; i < 64; ++i) { /* Note: this specific float casting is necessary so that the rounding - is done here the same as it is done in AudioPlaylist. + is done here the same as it is done in AudioPlaylist; the gain factor + must be computed using double precision, with the result then cast + to float. */ - CPPUNIT_ASSERT_DOUBLES_EQUAL (float (i * float (i / 63.0)), _buf[i], 1e-16); + CPPUNIT_ASSERT_DOUBLES_EQUAL (float (i * float (i / (double) 63)), _buf[i], 1e-16); } /* bit of ar0 */ @@ -112,10 +114,12 @@ PlaylistReadTest::overlappingReadTest () CPPUNIT_ASSERT_EQUAL (i, int (_buf[i])); } - /* ar1's fade in */ + /* ar1's fade in with faded-out ar0 */ for (int i = 0; i < 64; ++i) { /* Similar carry-on to above with float rounding */ - CPPUNIT_ASSERT_DOUBLES_EQUAL (i + 128 + float (i * float (i / 63.0)), _buf[i + 128], 1e-4); + float const from_ar0 = (128 + i) * float (1 - float (i / (double) 63)); + float const from_ar1 = i * float (i / (double) 63); + CPPUNIT_ASSERT_DOUBLES_EQUAL (from_ar0 + from_ar1, _buf[i + 128], 1e-16); } } @@ -143,10 +147,14 @@ PlaylistReadTest::transparentReadTest () _apl->read (_buf, _mbuf, _gbuf, 0, 1024, 0); - /* ar0 and ar1 fade-ins, mixed */ + /* ar0 and ar1 fade-ins; ar1 is on top, so its fade in will implicitly + fade in ar0 + */ for (int i = 0; i < 64; ++i) { - float const fade = i / 63.0; - CPPUNIT_ASSERT_DOUBLES_EQUAL (float (i * fade) * 2, _buf[i], 1e-16); + float const fade = i / (double) 63; + float const ar0 = i * fade * (1 - fade); + float const ar1 = i * fade; + CPPUNIT_ASSERT_DOUBLES_EQUAL (ar0 + ar1, _buf[i], 1e-16); } /* ar0 and ar1 bodies, mixed */ @@ -154,10 +162,12 @@ PlaylistReadTest::transparentReadTest () CPPUNIT_ASSERT_DOUBLES_EQUAL (float (i * 2), _buf[i], 1e-16); } - /* ar0 and ar1 fade-outs, mixed */ + /* ar0 and ar1 fade-outs, mixed (with implicit fade-in of ar0) */ for (int i = (1024 - 64); i < 1024; ++i) { - float const fade = (1023 - i) / 63.0; - CPPUNIT_ASSERT_DOUBLES_EQUAL (float (i * fade) * 2, _buf[i], 1e-16); + float const fade = (1023 - i) / (double) 63; + float const ar0 = i * fade * (1 - fade); + float const ar1 = i * fade; + CPPUNIT_ASSERT_DOUBLES_EQUAL (ar0 + ar1, _buf[i], 1e-16); } } diff --git a/libs/ardour/test/playlist_read_test.h b/libs/ardour/test/playlist_read_test.h index 9328d926e0..416ac41a88 100644 --- a/libs/ardour/test/playlist_read_test.h +++ b/libs/ardour/test/playlist_read_test.h @@ -5,6 +5,7 @@ class PlaylistReadTest : public TestNeedingPlaylistAndRegions { CPPUNIT_TEST_SUITE (PlaylistReadTest); CPPUNIT_TEST (singleReadTest); + CPPUNIT_TEST (overlappingReadTest); CPPUNIT_TEST (transparentReadTest); CPPUNIT_TEST_SUITE_END ();