13
0

Re-work fade operation to be closer to Mixbus; things

below the top region's fades are implicitly faded in
the opposite sense; restore short crossfades option.


git-svn-id: svn://localhost/ardour2/branches/3.0@12022 d708f5d6-7413-0410-9779-e7cbd77b26cf
This commit is contained in:
Carl Hetherington 2012-04-18 22:22:47 +00:00
parent 95f5c3cc80
commit 2072261bb0
4 changed files with 67 additions and 24 deletions

View File

@ -149,6 +149,17 @@ struct ReadSorter {
}
};
/** A segment of region that needs to be read */
struct Segment {
Segment (boost::shared_ptr<AudioRegion> r, Evoral::Range<framepos_t> a) : region (r), range (a) {}
boost::shared_ptr<AudioRegion> region; ///< the region
Evoral::Range<framepos_t> 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<framepos_t> done;
/* This will be a list of the bits of regions that we need to read */
list<Segment> 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<AudioRegion> ar = boost::dynamic_pointer_cast<AudioRegion> (*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<framepos_t> to_do = Evoral::subtract (region_range, done);
Evoral::RangeList<framepos_t> 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<framepos_t>::List t = to_do.get ();
Evoral::RangeList<framepos_t>::List t = region_to_do.get ();
for (Evoral::RangeList<framepos_t>::List::iterator i = t.begin(); i != t.end(); ++i) {
Evoral::Range<framepos_t> 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<framepos_t>::List::iterator j = t.begin(); j != t.end(); ++j) {
Evoral::Range<framepos_t> 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<Segment>::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;
}

View File

@ -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];
}
@ -541,6 +551,12 @@ AudioRegion::_read_at (const SourceList& srcs, framecnt_t limit,
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];
}

View File

@ -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<AudioRegion> ar0 = boost::dynamic_pointer_cast<AudioRegion> (_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);
}
}

View File

@ -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 ();