diff --git a/app/widget/timelinewidget/view/timelineviewblockitem.cpp b/app/widget/timelinewidget/view/timelineviewblockitem.cpp index a0792c6a9..c70c979da 100644 --- a/app/widget/timelinewidget/view/timelineviewblockitem.cpp +++ b/app/widget/timelinewidget/view/timelineviewblockitem.cpp @@ -32,7 +32,7 @@ #include "common/qtutils.h" #include "config/config.h" #include "node/block/transition/transition.h" -#include "widget/viewer/waveformview.h" +#include "widget/viewer/audiowaveformview.h" TimelineViewBlockItem::TimelineViewBlockItem(Block *block, QGraphicsItem* parent) : TimelineViewRect(parent), @@ -93,7 +93,7 @@ void TimelineViewBlockItem::paint(QPainter *painter, const QStyleOptionGraphicsI QByteArray w = wave_file.readAll(); // FIXME: Hardcoded channel count - WaveformView::DrawWaveform(painter, + AudioWaveformView::DrawWaveform(painter, rect().toRect(), this->GetScale(), reinterpret_cast(w.constData()), diff --git a/app/widget/viewer/CMakeLists.txt b/app/widget/viewer/CMakeLists.txt index 6d99220dc..16693f4de 100644 --- a/app/widget/viewer/CMakeLists.txt +++ b/app/widget/viewer/CMakeLists.txt @@ -16,6 +16,8 @@ set(OLIVE_SOURCES ${OLIVE_SOURCES} + widget/viewer/audiowaveformview.h + widget/viewer/audiowaveformview.cpp widget/viewer/footageviewer.h widget/viewer/footageviewer.cpp widget/viewer/viewer.h @@ -24,7 +26,5 @@ set(OLIVE_SOURCES widget/viewer/viewerglwidget.cpp widget/viewer/viewersizer.h widget/viewer/viewersizer.cpp - widget/viewer/waveformview.h - widget/viewer/waveformview.cpp PARENT_SCOPE ) diff --git a/app/widget/viewer/audiowaveformview.cpp b/app/widget/viewer/audiowaveformview.cpp new file mode 100644 index 000000000..dd7ac643b --- /dev/null +++ b/app/widget/viewer/audiowaveformview.cpp @@ -0,0 +1,189 @@ +#include "audiowaveformview.h" + +#include +#include +#include + +#include "common/clamp.h" +#include "config/config.h" + +AudioWaveformView::AudioWaveformView(QWidget *parent) : + SeekableWidget(parent), + backend_(nullptr) +{ + setAutoFillBackground(true); + setBackgroundRole(QPalette::Base); +} + +void AudioWaveformView::SetBackend(AudioRenderBackend *backend) +{ + if (backend_) { + disconnect(backend_, &AudioRenderBackend::QueueComplete, this, static_cast(&AudioWaveformView::update)); + disconnect(backend_, &AudioRenderBackend::ParamsChanged, this, &AudioWaveformView::BackendParamsChanged); + + SetTimebase(0); + } + + backend_ = backend; + + if (backend_) { + connect(backend_, &AudioRenderBackend::QueueComplete, this, static_cast(&AudioWaveformView::update)); + connect(backend_, &AudioRenderBackend::ParamsChanged, this, &AudioWaveformView::BackendParamsChanged); + + SetTimebase(backend_->params().time_base()); + } + + update(); +} + +void AudioWaveformView::DrawWaveform(QPainter *painter, const QRect& rect, const double& scale, const SampleSummer::Sum* samples, int nb_samples, int channels) +{ + int sample_index, next_sample_index = 0; + + QVector summary; + int summary_index = -1; + + int channel_height = rect.height() / channels; + int channel_half_height = channel_height / 2; + + for (int i=0;i(SampleSummer::kSumSampleRate) * static_cast(i+1) / scale) * channels); + + if (summary_index != sample_index) { + summary = SampleSummer::ReSumSamples(&samples[sample_index], + qMax(channels, next_sample_index - sample_index), + channels); + summary_index = sample_index; + } + + int line_x = i + rect.x(); + + for (int j=0;jdrawLine(line_x, + channel_bottom - diff, + line_x, + channel_bottom); + } else{ + int channel_mid = rect.y() + channel_height * j + channel_half_height; + + painter->drawLine(line_x, + channel_mid + clamp(qRound(summary.at(j).min * channel_half_height), -channel_half_height, channel_half_height), + line_x, + channel_mid + clamp(qRound(summary.at(j).max * channel_half_height), -channel_half_height, channel_half_height)); + } + } + } +} + +void AudioWaveformView::paintEvent(QPaintEvent *event) +{ + QWidget::paintEvent(event); + + if (!backend_ || backend_->CachePathName().isEmpty() || !backend_->params().is_valid()) { + return; + } + + const AudioRenderingParams& params = backend_->params(); + + if (cached_size_ != size() + || cached_scale_ != GetScale() + || cached_scroll_ != GetScroll()) { + + cached_waveform_ = QPixmap(size()); + cached_waveform_.fill(Qt::transparent); + + QFile fs(backend_->CachePathName()); + + if (fs.open(QFile::ReadOnly)) { + + QPainter wave_painter(&cached_waveform_); + + // FIXME: Hardcoded color + wave_painter.setPen(Qt::green); + + int channel_height = height() / params.channel_count(); + int channel_half_height = channel_height / 2; + + int drew = 0; + + fs.seek(params.samples_to_bytes(ScreenToUnitRounded(0))); + + for (int x=0; x samples = SampleSummer::SumSamples(reinterpret_cast(read_buffer.constData()), + samples_len, + params.channel_count()); + + for (int i=0;i(channel_half_height), + x, + channel_mid + samples.at(i).max * static_cast(channel_half_height)); + } + + drew++; + } + } + + cached_size_ = size(); + cached_scale_ = GetScale(); + cached_scroll_ = GetScroll(); + + fs.close(); + + } + } + + QPainter p(this); + + // Draw in/out points + DrawTimelinePoints(&p); + + // Draw cached waveform pixmap + p.drawPixmap(0, 0, cached_waveform_); + + // Draw playhead + p.setPen(GetPlayheadColor()); + + int playhead_x = UnitToScreen(GetTime()); + p.drawLine(playhead_x, 0, playhead_x, height()); +} + +void AudioWaveformView::BackendParamsChanged() +{ + SetTimebase(backend_->params().time_base()); +} diff --git a/app/widget/viewer/waveformview.h b/app/widget/viewer/audiowaveformview.h similarity index 78% rename from app/widget/viewer/waveformview.h rename to app/widget/viewer/audiowaveformview.h index 1e92e2d1d..283670c1d 100644 --- a/app/widget/viewer/waveformview.h +++ b/app/widget/viewer/audiowaveformview.h @@ -8,11 +8,11 @@ #include "render/backend/audiorenderbackend.h" #include "widget/timeruler/seekablewidget.h" -class WaveformView : public SeekableWidget +class AudioWaveformView : public SeekableWidget { Q_OBJECT public: - WaveformView(QWidget* parent = nullptr); + AudioWaveformView(QWidget* parent = nullptr); //void SetData(const QString& file, const AudioRenderingParams& params); @@ -26,6 +26,11 @@ protected: private: AudioRenderBackend* backend_; + QPixmap cached_waveform_; + QSize cached_size_; + double cached_scale_; + int cached_scroll_; + private slots: void BackendParamsChanged(); diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 4ff69b2d2..d2408630b 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -61,7 +61,7 @@ ViewerWidget::ViewerWidget(QWidget *parent) : sizer_->SetWidget(gl_widget_); // Create waveform view when audio is connected and video isn't - waveform_view_ = new WaveformView(); + waveform_view_ = new AudioWaveformView(); stack_->addWidget(waveform_view_); // Create time ruler @@ -70,7 +70,7 @@ ViewerWidget::ViewerWidget(QWidget *parent) : // Create scrollbar layout->addWidget(scrollbar()); connect(scrollbar(), &QScrollBar::valueChanged, ruler(), &TimeRuler::SetScroll); - connect(scrollbar(), &QScrollBar::valueChanged, waveform_view_, &WaveformView::SetScroll); + connect(scrollbar(), &QScrollBar::valueChanged, waveform_view_, &AudioWaveformView::SetScroll); // Create lower controls controls_ = new PlaybackControls(); @@ -96,7 +96,7 @@ ViewerWidget::ViewerWidget(QWidget *parent) : audio_renderer_ = new AudioBackend(this); waveform_view_->SetBackend(audio_renderer_); - connect(waveform_view_, &WaveformView::TimeChanged, this, &ViewerWidget::SetTimeAndSignal); + connect(waveform_view_, &AudioWaveformView::TimeChanged, this, &ViewerWidget::SetTimeAndSignal); connect(PixelFormat::instance(), &PixelFormat::FormatChanged, this, &ViewerWidget::UpdateRendererParameters); @@ -306,8 +306,8 @@ void ViewerWidget::PushScrubbedAudio() QIODevice* audio_src = audio_renderer_->GetAudioPullDevice(); if (audio_src && audio_src->open(QFile::ReadOnly)) { - // Try to get one "frame" of audio - int size_of_sample = audio_renderer_->params().time_to_bytes(timebase()); + // FIXME: Hardcoded scrubbing interval (20ms) + int size_of_sample = audio_renderer_->params().time_to_bytes(rational(20, 1000)); // Push audio audio_src->seek(audio_renderer_->params().time_to_bytes(GetTime())); diff --git a/app/widget/viewer/viewer.h b/app/widget/viewer/viewer.h index 88aab38b7..4c3c642dd 100644 --- a/app/widget/viewer/viewer.h +++ b/app/widget/viewer/viewer.h @@ -28,6 +28,7 @@ #include #include +#include "audiowaveformview.h" #include "common/rational.h" #include "node/output/viewer/viewer.h" #include "render/backend/opengl/openglbackend.h" @@ -35,7 +36,6 @@ #include "render/backend/audio/audiobackend.h" #include "viewerglwidget.h" #include "viewersizer.h" -#include "waveformview.h" #include "widget/playbackcontrols/playbackcontrols.h" #include "widget/timebased/timebased.h" @@ -159,7 +159,7 @@ private: bool time_changed_from_timer_; - WaveformView* waveform_view_; + AudioWaveformView* waveform_view_; private slots: void PlaybackTimerUpdate(); diff --git a/app/widget/viewer/waveformview.cpp b/app/widget/viewer/waveformview.cpp deleted file mode 100644 index ec695a4c1..000000000 --- a/app/widget/viewer/waveformview.cpp +++ /dev/null @@ -1,171 +0,0 @@ -#include "waveformview.h" - -#include -#include -#include - -#include "common/clamp.h" -#include "config/config.h" - -WaveformView::WaveformView(QWidget *parent) : - SeekableWidget(parent), - backend_(nullptr) -{ - setAutoFillBackground(true); - setBackgroundRole(QPalette::Base); -} - -void WaveformView::SetBackend(AudioRenderBackend *backend) -{ - if (backend_) { - disconnect(backend_, &AudioRenderBackend::QueueComplete, this, static_cast(&WaveformView::update)); - disconnect(backend_, &AudioRenderBackend::ParamsChanged, this, &WaveformView::BackendParamsChanged); - - SetTimebase(0); - } - - backend_ = backend; - - if (backend_) { - connect(backend_, &AudioRenderBackend::QueueComplete, this, static_cast(&WaveformView::update)); - connect(backend_, &AudioRenderBackend::ParamsChanged, this, &WaveformView::BackendParamsChanged); - - SetTimebase(backend_->params().time_base()); - } - - update(); -} - -void WaveformView::DrawWaveform(QPainter *painter, const QRect& rect, const double& scale, const SampleSummer::Sum* samples, int nb_samples, int channels) -{ - int sample_index, next_sample_index = 0; - - QVector summary; - int summary_index = -1; - - int channel_height = rect.height() / channels; - int channel_half_height = channel_height / 2; - - for (int i=0;i(SampleSummer::kSumSampleRate) * static_cast(i+1) / scale) * channels); - - if (summary_index != sample_index) { - summary = SampleSummer::ReSumSamples(&samples[sample_index], - qMax(channels, next_sample_index - sample_index), - channels); - summary_index = sample_index; - } - - int line_x = i + rect.x(); - - for (int j=0;jdrawLine(line_x, - channel_bottom - diff, - line_x, - channel_bottom); - } else{ - int channel_mid = rect.y() + channel_height * j + channel_half_height; - - painter->drawLine(line_x, - channel_mid + clamp(qRound(summary.at(j).min * channel_half_height), -channel_half_height, channel_half_height), - line_x, - channel_mid + clamp(qRound(summary.at(j).max * channel_half_height), -channel_half_height, channel_half_height)); - } - } - } -} - -void WaveformView::paintEvent(QPaintEvent *event) -{ - QWidget::paintEvent(event); - - if (!backend_ || backend_->CachePathName().isEmpty() || !backend_->params().is_valid()) { - return; - } - - const AudioRenderingParams& params = backend_->params(); - - QFile fs(backend_->CachePathName()); - - if (fs.open(QFile::ReadOnly)) { - - QPainter p(this); - - DrawTimelinePoints(&p); - - // FIXME: Hardcoded color - p.setPen(Qt::green); - - int channel_height = height() / params.channel_count(); - int channel_half_height = channel_height / 2; - - int drew = 0; - - fs.seek(params.samples_to_bytes(ScreenToUnitRounded(0))); - - for (int x=0; x samples = SampleSummer::SumSamples(reinterpret_cast(read_buffer.constData()), - samples_len, - params.channel_count()); - - for (int i=0;i(channel_half_height), - x, - channel_mid + samples.at(i).max * static_cast(channel_half_height)); - } - - drew++; - } - } - - fs.close(); - - // Draw playhead - p.setPen(GetPlayheadColor()); - - int playhead_x = UnitToScreen(GetTime()); - p.drawLine(playhead_x, 0, playhead_x, height()); - - } -} - -void WaveformView::BackendParamsChanged() -{ - SetTimebase(rational(1, backend_->params().sample_rate())); -}