1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
|
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*!********************************************************************
Audacity: A Digital Audio Editor
DynamicRangeProcessorHistory.cpp
Matthieu Hodgkinson
**********************************************************************/
#include "DynamicRangeProcessorHistory.h"
#include "DynamicRangeProcessorTypes.h"
#include <algorithm>
#include <cassert>
#include <iterator>
DynamicRangeProcessorHistory::DynamicRangeProcessorHistory(double sampleRate)
: mSampleRate { sampleRate }
{
}
void DynamicRangeProcessorHistory::Push(
const std::vector<DynamicRangeProcessorOutputPacket>& packets)
{
if (packets.empty())
return;
if (!mFirstPacketFirstSampleIndex.has_value())
mFirstPacketFirstSampleIndex = packets.front().indexOfFirstSample;
const int numNewPackets = packets.size();
const auto lastPacketTime = !mSegments.empty() && !mSegments[0].empty() ?
std::make_optional(mSegments[0].back().time) :
std::nullopt;
const auto firstPacketToInsertIt =
std::find_if(packets.begin(), packets.end(), [&](const auto& packet) {
return !lastPacketTime.has_value() ||
GetPacketTime(packet) > *lastPacketTime;
});
if (firstPacketToInsertIt == packets.end())
return;
// Some packets can go lost in the transmission from audio to main thread.
const auto isContinuous = mExpectedNextPacketFirstSampleIndex.has_value() &&
firstPacketToInsertIt->indexOfFirstSample ==
*mExpectedNextPacketFirstSampleIndex;
if (mSegments.empty() || mBeginNewSegment || !isContinuous)
{
mSegments.emplace_back();
mBeginNewSegment = false;
}
mExpectedNextPacketFirstSampleIndex =
packets.back().indexOfFirstSample + packets.back().numSamples;
auto& lastSegment = mSegments.back();
std::transform(
firstPacketToInsertIt, packets.end(), std::back_inserter(lastSegment),
[&](const auto& packet) -> Packet {
const auto t = GetPacketTime(packet);
return { t, packet.targetCompressionDb, packet.actualCompressionDb,
packet.inputDb, packet.outputDb };
});
// Clean up older packets.
// Algorithmically it's not completely correct to only do this for the oldest
// segment, but in practice, `Push` is called much more often than
// `BeginNewSegment`, so a simpler implementation is sufficient.
const auto lastTime = lastSegment.back().time;
auto& firstSegment = mSegments.front();
const auto it = std::find_if(
firstSegment.begin(), firstSegment.end(),
[lastTime](const Packet& packet) {
// Extend a little bit the time window, to avoid the extremities of a
// display to tremble.
return lastTime - packet.time < maxTimeSeconds + 1.f;
});
firstSegment.erase(firstSegment.begin(), it);
if (firstSegment.empty())
mSegments.erase(mSegments.begin());
}
void DynamicRangeProcessorHistory::BeginNewSegment()
{
mBeginNewSegment = true;
}
const std::vector<DynamicRangeProcessorHistory::Segment>&
DynamicRangeProcessorHistory::GetSegments() const
{
return mSegments;
}
bool DynamicRangeProcessorHistory::IsEmpty() const
{
return std::all_of(
mSegments.begin(), mSegments.end(),
[](const auto& segment) { return segment.empty(); });
}
float DynamicRangeProcessorHistory::GetPacketTime(
const DynamicRangeProcessorOutputPacket& packet) const
{
assert(mFirstPacketFirstSampleIndex.has_value());
return (packet.indexOfFirstSample -
mFirstPacketFirstSampleIndex.value_or(0)) /
mSampleRate;
}
|