Files
oak-editor/app/audio/audiovisualwaveform.cpp
T
itsmattkc 3ffb2fde1c audiomonitor: use mipmapped waveform
Since we have these now, may as well use them. This should
improve general playback performance since the audio monitor
will be able to iterate far fewer
2021-04-14 15:25:38 +10:00

397 lines
13 KiB
C++

/***
Olive - Non-Linear Video Editor
Copyright (C) 2020 Olive Team
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 3 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, see <http://www.gnu.org/licenses/>.
***/
#include "audiovisualwaveform.h"
#include <QDebug>
#include "config/config.h"
#include "common/functiontimer.h"
namespace olive {
AudioVisualWaveform::AudioVisualWaveform() :
channels_(0)
{
// Must be a power of 2
static const rational kMinimumSampleRate = rational(1, 8);
static const rational kMaximumSampleRate = 8192;
for (rational i=kMinimumSampleRate; i<=kMaximumSampleRate; i*=2) {
mipmapped_data_.insert({i, Sample()});
}
}
void AudioVisualWaveform::OverwriteSamplesFromBuffer(SampleBufferPtr samples, int sample_rate, const rational &start, double target_rate, Sample& data, int &start_index, int &samples_length)
{
start_index = time_to_samples(start, target_rate);
samples_length = time_to_samples(static_cast<double>(samples->sample_count()) / static_cast<double>(sample_rate), target_rate);
int end_index = start_index + samples_length;
if (data.size() < end_index) {
data.resize(end_index);
}
int chunk_size = sample_rate / target_rate;
for (int i=0; i<samples_length; i+=channels_) {
int src_index = (i * chunk_size) / channels_;
Sample summary = SumSamples(samples,
src_index,
qMin(chunk_size, samples->sample_count() - src_index));
memcpy(&data.data()[i + start_index],
summary.constData(),
summary.size() * sizeof(SamplePerChannel));
}
}
void AudioVisualWaveform::OverwriteSamplesFromMipmap(const AudioVisualWaveform::Sample &input, double input_sample_rate, int &input_start, int &input_length, const rational &start, double output_rate, AudioVisualWaveform::Sample &output_data)
{
int start_index = time_to_samples(start, output_rate);
int samples_length = time_to_samples(static_cast<double>(input_length / channels_) / input_sample_rate, output_rate);
int end_index = start_index + samples_length;
if (output_data.size() < end_index) {
output_data.resize(end_index);
}
int chunk_size = input_sample_rate / output_rate;
for (int i=0; i<samples_length; i+=channels_) {
Sample summary = ReSumSamples(&input.constData()[input_start + (i*chunk_size)], chunk_size * channels_, channels_);
memcpy(&output_data.data()[i + start_index],
summary.constData(),
summary.size() * sizeof(SamplePerChannel));
}
input_start = start_index;
input_length = samples_length;
}
void AudioVisualWaveform::OverwriteSamples(SampleBufferPtr samples, int sample_rate, const rational &start)
{
if (!channels_) {
qWarning() << "Failed to write samples - channel count is zero";
return;
}
// Old less optimized code. Keeping this around as a reference, but the below code is at least
// 10x faster so this shouldn't be used in production.
//
// int input_start, input_length;
// for (auto it=mipmapped_data_.begin(); it!=mipmapped_data_.end(); it++) {
// OverwriteSamplesFromBuffer(samples, sample_rate, start, it->first.toDouble(), it->second, input_start, input_length);
// }
// Process the largest mipmap directly for the samples
auto current_mipmap = mipmapped_data_.rbegin();
int input_start, input_length;
OverwriteSamplesFromBuffer(samples, sample_rate, start, current_mipmap->first.toDouble(), current_mipmap->second, input_start, input_length);
while (true) {
// For each smaller mipmap, we just process from the mipmap before it, making each one
// exponentially faster to create
auto previous_mipmap = current_mipmap;
current_mipmap++;
if (current_mipmap == mipmapped_data_.rend()) {
break;
}
OverwriteSamplesFromMipmap(previous_mipmap->second, previous_mipmap->first.toDouble(),
input_start, input_length, start, current_mipmap->first.toDouble(),
current_mipmap->second);
}
}
void AudioVisualWaveform::OverwriteSums(const AudioVisualWaveform &sums, const rational &dest, const rational& offset, const rational& length)
{
for (auto it=mipmapped_data_.begin(); it!=mipmapped_data_.end(); it++) {
rational rate = it->first;
Sample& our_arr = it->second;
const Sample& their_arr = sums.mipmapped_data_.at(rate);
double rate_dbl = rate.toDouble();
// Get our destination sample
int our_start_index = time_to_samples(dest, rate_dbl);
// Get our source sample
int their_start_index = time_to_samples(offset, rate_dbl);
// Determine how much we're copying
int copy_len = their_arr.size() - their_start_index;
if (!length.isNull()) {
copy_len = qMin(copy_len, time_to_samples(length, rate_dbl));
}
// Determine end index of our array
int end_index = our_start_index + copy_len;
if (our_arr.size() < end_index) {
our_arr.resize(end_index);
}
memcpy(reinterpret_cast<char*>(our_arr.data()) + our_start_index * sizeof(SamplePerChannel),
reinterpret_cast<const char*>(their_arr.constData()) + their_start_index * sizeof(SamplePerChannel),
copy_len * sizeof(SamplePerChannel));
}
}
void AudioVisualWaveform::Shift(const rational &from, const rational &to)
{
for (auto it=mipmapped_data_.begin(); it!=mipmapped_data_.end(); it++) {
rational rate = it->first;
double rate_dbl = rate.toDouble();
Sample& data = it->second;
int from_index = time_to_samples(from, rate_dbl);
int to_index = time_to_samples(to, rate_dbl);
if (from_index == to_index) {
return;
}
if (from_index > data.size()) {
return;
}
if (from_index > to_index) {
// Shifting backwards <-
int copy_sz = data.size() - from_index;
for (int i=0; i<copy_sz; i++) {
data.replace(to_index + i, data.at(from_index + i));
}
data.resize(data.size() - (from_index - to_index));
} else {
// Shifting forwards ->
int old_sz = data.size();
int distance = (to_index - from_index);
data.resize(data.size() + distance);
int copy_sz = old_sz - from_index;
for (int i=0; i<copy_sz; i++) {
data.replace(data.size() - i - 1, data.at(old_sz - i - 1));
}
memset(reinterpret_cast<char*>(&data[from_index]), 0, distance * sizeof(SamplePerChannel));
}
}
}
AudioVisualWaveform::Sample AudioVisualWaveform::GetSummaryFromTime(const rational &start, const rational &length) const
{
// Find mipmap that requries
auto using_mipmap = GetMipmapForScale(length.flipped().toDouble());
double rate_dbl = using_mipmap->first.toDouble();
int start_sample = time_to_samples(start, rate_dbl);
int sample_length = time_to_samples(length, rate_dbl);
return ReSumSamples(&using_mipmap->second.constData()[start_sample], sample_length, channels_);
}
AudioVisualWaveform::Sample AudioVisualWaveform::SumSamples(const float *samples, int nb_samples, int nb_channels)
{
return SumSamplesInternal(samples, nb_samples, nb_channels);
}
AudioVisualWaveform::Sample AudioVisualWaveform::SumSamples(SampleBufferPtr samples, int start_index, int length)
{
AudioVisualWaveform::Sample summed_samples(samples->audio_params().channel_count());
int end_index = start_index + length;
for (int i=start_index; i<end_index; i++) {
for (int channel=0; channel<samples->audio_params().channel_count(); channel++) {
ExpandMinMax<float>(summed_samples[channel], samples->data(channel)[i]);
}
}
return summed_samples;
}
AudioVisualWaveform::Sample AudioVisualWaveform::ReSumSamples(const SamplePerChannel* samples,
int nb_samples,
int nb_channels)
{
AudioVisualWaveform::Sample summed_samples(nb_channels);
for (int i=0;i<nb_samples;i+=nb_channels) {
for (int j=0;j<nb_channels;j++) {
const AudioVisualWaveform::SamplePerChannel& sample = samples[i + j];
if (sample.min < summed_samples[j].min) {
summed_samples[j].min = sample.min;
}
if (sample.max > summed_samples[j].max) {
summed_samples[j].max = sample.max;
}
}
}
return summed_samples;
}
void AudioVisualWaveform::DrawSample(QPainter *painter, const Sample& sample, int x, int y, int height, bool rectified)
{
if (sample.isEmpty()) {
return;
}
int channel_height = height / sample.size();
int channel_half_height = channel_height / 2;
for (int i=0;i<sample.size();i++) {
float max = qMin(sample.at(i).max, 1.0f);
float min = qMax(sample.at(i).min, -1.0f);
if (rectified) {
int channel_bottom = y + channel_height * (i + 1);
int diff = qRound((max - min) * channel_half_height);
painter->drawLine(x,
channel_bottom - diff,
x,
channel_bottom);
} else {
int channel_mid = y + channel_height * i + channel_half_height;
painter->drawLine(x,
channel_mid + qRound(min * static_cast<float>(channel_half_height)),
x,
channel_mid + qRound(max * static_cast<float>(channel_half_height)));
}
}
}
void AudioVisualWaveform::DrawWaveform(QPainter *painter, const QRect& rect, const double& scale, const AudioVisualWaveform &samples, const rational& start_time)
{
if (samples.mipmapped_data_.empty()) {
return;
}
auto using_mipmap = samples.GetMipmapForScale(scale);
rational rate = using_mipmap->first;
double rate_dbl = rate.toDouble();
const Sample& arr = using_mipmap->second;
int start_sample_index = samples.time_to_samples(start_time, rate_dbl);
if (start_sample_index >= arr.size()) {
return;
}
int next_sample_index = start_sample_index;
int sample_index;
Sample summary;
int summary_index = -1;
const QRect& viewport = painter->viewport();
QPoint top_left = painter->transform().map(viewport.topLeft());
int start = qMax(rect.x(), -top_left.x());
int end = qMin(rect.right(), -top_left.x() + viewport.width());
bool rectified = Config::Current()[QStringLiteral("RectifiedWaveforms")].toBool();
for (int i=start;i<end;i++) {
sample_index = next_sample_index;
if (sample_index == arr.size()) {
break;
}
next_sample_index = qMin(arr.size(),
start_sample_index + qFloor(rate_dbl * static_cast<double>(i - rect.x() + 1) / scale) * samples.channel_count());
if (summary_index != sample_index) {
summary = AudioVisualWaveform::ReSumSamples(&arr.at(sample_index),
qMax(samples.channel_count(), next_sample_index - sample_index),
samples.channel_count());
summary_index = sample_index;
}
DrawSample(painter, summary, i, rect.y(), rect.height(), rectified);
}
}
int AudioVisualWaveform::time_to_samples(const rational &time, double sample_rate) const
{
return time_to_samples(time.toDouble(), sample_rate);
}
int AudioVisualWaveform::time_to_samples(const double &time, double sample_rate) const
{
return qFloor(time * sample_rate) * channels_;
}
std::map<rational, AudioVisualWaveform::Sample>::const_iterator AudioVisualWaveform::GetMipmapForScale(double scale) const
{
// Find largest mipmap for this scale (or the largest if we don't find one sufficient)
auto using_mipmap = mipmapped_data_.cend();
using_mipmap--;
for (auto it=mipmapped_data_.cbegin(); it!=mipmapped_data_.cend(); it++) {
if (it->first.toDouble() >= scale) {
using_mipmap = it;
break;
}
}
return using_mipmap;
}
template<typename T>
AudioVisualWaveform::Sample AudioVisualWaveform::SumSamplesInternal(const T *samples, int nb_samples, int nb_channels)
{
AudioVisualWaveform::Sample summed_samples(nb_channels);
for (int i=0;i<nb_samples;i++) {
ExpandMinMax<T>(summed_samples[i%nb_channels], samples[i]);
}
return summed_samples;
}
template<typename T>
void AudioVisualWaveform::ExpandMinMax(AudioVisualWaveform::SamplePerChannel &sum, T value)
{
if (value < sum.min) {
sum.min = value;
}
if (value > sum.max) {
sum.max = value;
}
}
}