From 46fb53ff24b6115f14c201d42ced6221fcc69297 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Wed, 14 Apr 2021 13:48:18 +1000 Subject: [PATCH] audiowaveformview: use new mipmapped waveform system Fixes #1427 --- app/widget/viewer/audiowaveformview.cpp | 191 ++++++------------------ app/widget/viewer/audiowaveformview.h | 41 +---- 2 files changed, 55 insertions(+), 177 deletions(-) diff --git a/app/widget/viewer/audiowaveformview.cpp b/app/widget/viewer/audiowaveformview.cpp index 3dbea3af2..20921b94b 100644 --- a/app/widget/viewer/audiowaveformview.cpp +++ b/app/widget/viewer/audiowaveformview.cpp @@ -30,21 +30,38 @@ namespace olive { +#define super SeekableWidget + AudioWaveformView::AudioWaveformView(QWidget *parent) : - SeekableWidget(parent), + super(parent), playback_(nullptr) { setAutoFillBackground(true); setBackgroundRole(QPalette::Base); +} - cached_waveform_.resize(QThread::idealThreadCount()); +AudioVisualWaveform GenerateWaveform(QIODevice* device, AudioParams params, TimeRange range) +{ + device->open(QFile::ReadOnly); + device->seek(params.time_to_bytes(range.in())); + + SampleBufferPtr samples = SampleBuffer::CreateFromPackedData(params, device->read(params.time_to_bytes(range.length()))); + AudioVisualWaveform waveform; + waveform.set_channel_count(params.channel_count()); + waveform.OverwriteSamples(samples, params.sample_rate()); + device->close(); + delete device; + return waveform; } void AudioWaveformView::SetViewer(AudioPlaybackCache *playback) { if (playback_) { - disconnect(playback_, &AudioPlaybackCache::Validated, this, &AudioWaveformView::ForceUpdateOfRange); - disconnect(playback_, &AudioPlaybackCache::ParametersChanged, this, &AudioWaveformView::BackendParamsChanged); + pool_.clear(); + pool_.waitForDone(); + + disconnect(playback_, &AudioPlaybackCache::Validated, this, &AudioWaveformView::RenderRange); + //disconnect(playback_, &AudioPlaybackCache::ParametersChanged, this, &AudioWaveformView::RenderRange); SetTimebase(0); } @@ -52,18 +69,20 @@ void AudioWaveformView::SetViewer(AudioPlaybackCache *playback) playback_ = playback; if (playback_) { - connect(playback_, &AudioPlaybackCache::Validated, this, &AudioWaveformView::ForceUpdateOfRange); - connect(playback_, &AudioPlaybackCache::ParametersChanged, this, &AudioWaveformView::BackendParamsChanged); + connect(playback_, &AudioPlaybackCache::Validated, this, &AudioWaveformView::RenderRange); + //connect(playback_, &AudioPlaybackCache::ParametersChanged, this, &AudioWaveformView::RenderRange); SetTimebase(playback_->GetParameters().sample_rate_as_time_base()); - } - ForceUpdate(); + waveform_.set_channel_count(playback_->GetParameters().channel_count()); + + RenderRange(TimeRange(0, playback_->GetLength())); + } } void AudioWaveformView::paintEvent(QPaintEvent *event) { - QWidget::paintEvent(event); + super::paintEvent(event); if (!playback_) { return; @@ -80,40 +99,9 @@ void AudioWaveformView::paintEvent(QPaintEvent *event) // Draw in/out points DrawTimelinePoints(&p); - CachedWaveformInfo wanted_info = {size(), GetScale(), GetScroll(), params}; - - for (int i=0; i(); - connect(cache.watcher, &QFutureWatcher::finished, this, &AudioWaveformView::BackgroundCacheFinished); - cache.watcher->setFuture(QtConcurrent::run(this, - &AudioWaveformView::DrawWaveform, - playback_->CreatePlaybackDevice(), - wanted_info, - slice_start, - slice_end)); - - } - } + // Draw waveform + p.setPen(QColor(64, 255, 160)); // FIXME: Hardcoded color + AudioVisualWaveform::DrawWaveform(&p, rect(), GetScale(), waveform_, SceneToTime(GetScroll())); // Draw playhead p.setPen(PLAYHEAD_COLOR); @@ -122,119 +110,38 @@ void AudioWaveformView::paintEvent(QPaintEvent *event) p.drawLine(playhead_x, 0, playhead_x, height()); } -QPixmap AudioWaveformView::DrawWaveform(QIODevice* fs, CachedWaveformInfo info, int slice_start, int slice_end) const +void AudioWaveformView::RenderRange(const TimeRange &range) { - QPixmap pixmap(slice_end - slice_start, info.size.height()); - pixmap.fill(Qt::transparent); + // Floor to second increments + int64_t start = qFloor(range.in().toDouble()); + int64_t end = qCeil(range.out().toDouble()); - bool rectified = Config::Current()[QStringLiteral("RectifiedWaveforms")].toBool(); + for (; start!=end; start++) { + TimeRange this_range(start, start+1); - if (fs->open(QFile::ReadOnly)) { + QFutureWatcher* watcher = new QFutureWatcher(); + connect(watcher, &QFutureWatcher::finished, this, &AudioWaveformView::BackgroundFinished); - QPainter wave_painter(&pixmap); - - // FIXME: Hardcoded color - wave_painter.setPen(QColor(64, 255, 160)); - - int drew = 0; - - fs->seek(info.params.samples_to_bytes(ScreenToUnitRounded(slice_start))); - - for (int x=slice_start; xatEnd(); x++) { - int samples_len = ScreenToUnitRounded(x+1) - ScreenToUnitRounded(x); - int max_read_size = info.params.samples_to_bytes(samples_len); - - QByteArray read_buffer = fs->read(max_read_size); - - // Detect whether we've reached EOF and recalculate sample count if so - if (read_buffer.size() < max_read_size) { - samples_len = info.params.bytes_to_samples(read_buffer.size()); - } - - QVector samples = AudioVisualWaveform::SumSamples(reinterpret_cast(read_buffer.constData()), - samples_len, - info.params.channel_count()); - - for (int i=0;iclose(); + jobs_.insert(this_range, watcher); + watcher->setFuture(QtConcurrent::run(&pool_, GenerateWaveform, playback_->CreatePlaybackDevice(), playback_->GetParameters(), this_range)); } - - delete fs; - - return pixmap; } -void AudioWaveformView::BackendParamsChanged() +void AudioWaveformView::BackgroundFinished() { - SetTimebase(playback_->GetParameters().sample_rate_as_time_base()); -} + QFutureWatcher* watcher = static_cast*>(sender()); -void AudioWaveformView::ForceUpdate() -{ - // Forces the cache to invalidate - for (int i=0; i= width()) { - return; - } - - int start_invalidate = qMax(0, in/cached_waveform_.size()); - int end_invalidate = qMin(cached_waveform_.size()-1, out/cached_waveform_.size()); - - for (int i=start_invalidate; i<=end_invalidate; i++) { - // Invalidate these - cached_waveform_[i].info.size = QSize(); - } - - update(); -} - -void AudioWaveformView::BackgroundCacheFinished() -{ - // Retrieve sender - QFutureWatcher* watcher = static_cast*>(sender()); - - // Determine index - int index = -1; - for (int i=0; iresult(); + waveform_.OverwriteSums(rendered, it.key().in()); + jobs_.erase(it); + update(); break; } } - if (index > -1) { - // Store generated pixmap - cached_waveform_[index].info = cached_waveform_[index].caching_info; - cached_waveform_[index].pixmap = watcher->result(); - cached_waveform_[index].watcher = nullptr; - - // Reset size - cached_waveform_[index].caching_info.size = QSize(); - - // Update with new pixmap - update(); - } - - // Clean up delete watcher; } diff --git a/app/widget/viewer/audiowaveformview.h b/app/widget/viewer/audiowaveformview.h index b8f1ebd49..097ff1e05 100644 --- a/app/widget/viewer/audiowaveformview.h +++ b/app/widget/viewer/audiowaveformview.h @@ -45,47 +45,18 @@ protected: virtual void paintEvent(QPaintEvent* event) override; private: - struct CachedWaveformInfo { - QSize size; - double scale; - int scroll; - AudioParams params; + void RenderRange(const TimeRange& range); - bool operator==(const CachedWaveformInfo& rhs) const - { - return size == rhs.size - && qFuzzyCompare(scale, rhs.scale) - && scroll == rhs.scroll - && params == rhs.params; - } - - bool operator!=(const CachedWaveformInfo& rhs) const - { - return !(*this == rhs); - } - }; - - struct ActiveCache { - QPixmap pixmap; - CachedWaveformInfo info; - CachedWaveformInfo caching_info; - QFutureWatcher* watcher = nullptr; - }; - - QPixmap DrawWaveform(QIODevice *fs, CachedWaveformInfo info, int slice_start, int slice_end) const; + QThreadPool pool_; AudioPlaybackCache *playback_; - QVector cached_waveform_; + AudioVisualWaveform waveform_; + + QHash*> jobs_; private slots: - void BackendParamsChanged(); - - void ForceUpdate(); - - void ForceUpdateOfRange(const TimeRange& range); - - void BackgroundCacheFinished(); + void BackgroundFinished(); };