/* Copyright (C) 2013-2021 Carl Hetherington This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /** @file src/audio_merger.cc * @brief AudioMerger class. */ #include "audio_merger.h" #include "dcpomatic_time.h" #include using std::cout; using std::list; using std::make_pair; using std::make_shared; using std::max; using std::min; using std::pair; using std::shared_ptr; using boost::optional; using namespace dcpomatic; AudioMerger::AudioMerger (int frame_rate) : _frame_rate (frame_rate) { } Frame AudioMerger::frames (DCPTime t) const { return t.frames_floor (_frame_rate); } /** Pull audio up to a given time; after this call, no more data can be pushed * before the specified time. * @param time Time to pull up to. * @return Blocks of merged audio up to `time'. */ list, DCPTime>> AudioMerger::pull (DCPTime time) { list, DCPTime>> out; list new_buffers; _buffers.sort ([](Buffer const& a, Buffer const& b) { return a.time < b.time; }); for (auto i: _buffers) { if (i.period().to <= time) { /* Completely within the pull period */ DCPOMATIC_ASSERT (i.audio->frames() > 0); out.push_back (make_pair (i.audio, i.time)); } else if (i.time < time) { /* Overlaps the end of the pull period */ int32_t const overlap = frames(DCPTime(time - i.time)); /* Though time > i.time, overlap could be 0 if the difference in time is less than one frame */ if (overlap > 0) { auto audio = make_shared(i.audio, overlap, 0); out.push_back (make_pair(audio, i.time)); i.audio->trim_start (overlap); i.time += DCPTime::from_frames(overlap, _frame_rate); DCPOMATIC_ASSERT (i.audio->frames() > 0); new_buffers.push_back (i); } } else { /* Not involved */ DCPOMATIC_ASSERT (i.audio->frames() > 0); new_buffers.push_back (i); } } _buffers = new_buffers; for (auto const& i: out) { DCPOMATIC_ASSERT (i.first->frames() > 0); } return out; } /** Push some data into the merger at a given time */ void AudioMerger::push (std::shared_ptr audio, DCPTime time) { DCPOMATIC_ASSERT (audio->frames() > 0); DCPTimePeriod period (time, time + DCPTime::from_frames (audio->frames(), _frame_rate)); /* Mix any overlapping parts of this new block with existing ones */ for (auto i: _buffers) { auto overlap = i.period().overlap(period); if (overlap) { int32_t const offset = frames(DCPTime(overlap->from - i.time)); int32_t const frames_to_mix = frames(overlap->duration()); if (i.time < time) { i.audio->accumulate_frames(audio.get(), frames_to_mix, 0, offset); } else { i.audio->accumulate_frames(audio.get(), frames_to_mix, offset, 0); } } } list periods; for (auto i: _buffers) { periods.push_back (i.period()); } /* Add the non-overlapping parts */ for (auto i: subtract(period, periods)) { auto before = _buffers.end(); auto after = _buffers.end(); for (auto j = _buffers.begin(); j != _buffers.end(); ++j) { if (j->period().to == i.from) { before = j; } if (j->period().from == i.to) { after = j; } } /* Get the part of audio that we want to use */ auto part = make_shared(audio, frames(i.to) - frames(i.from), frames(DCPTime(i.from - time))); if (before == _buffers.end() && after == _buffers.end()) { if (part->frames() > 0) { /* New buffer */ _buffers.push_back (Buffer (part, time, _frame_rate)); } } else if (before != _buffers.end() && after == _buffers.end()) { /* We have an existing buffer before this one; append new data to it */ before->audio->append (part); } else if (before ==_buffers.end() && after != _buffers.end()) { /* We have an existing buffer after this one; append it to the new data and replace */ part->append (after->audio); after->audio = part; after->time = time; } else { /* We have existing buffers both before and after; coalesce them all */ before->audio->append (part); before->audio->append (after->audio); _buffers.erase (after); } } } void AudioMerger::clear () { _buffers.clear (); }