Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve waveform rendering performance #7366

Open
wants to merge 80 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
80 commits
Select commit Hold shift + click to select a range
5ad7845
Experimental sample thumbnail
khoidauminh Apr 23, 2024
795ad7f
Rename some classes and type aliases. Make some type declarations exp…
khoidauminh Apr 25, 2024
140006b
Use a combination of audioFile name and shared_ptrs to track samples;…
khoidauminh Apr 25, 2024
14d1e91
That weird line at the end of the sample is now gone
khoidauminh Apr 25, 2024
71284b0
Small changes to the code; Add comments
khoidauminh Apr 26, 2024
4553283
Add missing word to comment; Fix typo
khoidauminh Apr 26, 2024
7a6e2aa
Track `SharedSampleThumbnailList`s instead
khoidauminh May 2, 2024
f281961
Major refactor; implement thumbnailing for SlicerT, AFP and Automatio…
khoidauminh Jul 3, 2024
e03dacd
Code clean up, renames and documenting
khoidauminh Jul 3, 2024
4a1689e
Merge branch 'LMMS:master' into sample-thumbnail
khoidauminh Jul 4, 2024
82c51e0
Add the namespace lmms comments
khoidauminh Jul 4, 2024
35589f9
More code updates and documentation
khoidauminh Jul 4, 2024
92b9c15
Fix error in comment
khoidauminh Jul 4, 2024
fa24cfe
Comment out `qDebug`s
khoidauminh Jul 5, 2024
101199e
Fix formatting
khoidauminh Jul 5, 2024
b903d9f
Use alternative initialization of `SampleThumbnailVisualizeParameters`
khoidauminh Jul 5, 2024
7a393ef
Remove commented code
khoidauminh Jul 12, 2024
4e39c16
Fix style and simplify code
sakertooth Sep 10, 2024
9880d83
Use auto
sakertooth Sep 10, 2024
69cc6ba
Draw lines using floating point
sakertooth Sep 10, 2024
d7f89ac
Merge the classes into one nested class
sakertooth Sep 10, 2024
9605dec
Fix comparison of different signedness
sakertooth Sep 10, 2024
7f3d43f
Include memory header
sakertooth Sep 10, 2024
3c41384
Fix a logic error when selecting samples; Rename a const
khoidauminh Sep 10, 2024
5f70d59
Fix more issues with signedness
sakertooth Sep 10, 2024
4f9f353
Fix sample drawing error in `visualizeOriginal`
khoidauminh Sep 11, 2024
12bc9cc
Only render regions in view of the sample
khoidauminh Sep 11, 2024
0a210ee
Allow partial repaints of sample clips
khoidauminh Sep 11, 2024
d6a3638
Remove unused variable
khoidauminh Sep 11, 2024
305baee
Limit most of the painting to the visible region
khoidauminh Sep 11, 2024
9392493
Revert back to using rect() in some places
khoidauminh Sep 11, 2024
4bc9dae
Partial rendering for AutomationEditor
khoidauminh Sep 11, 2024
5cd947b
Don't redraw painted regions; allowHighResolution; remove `visualizeO…
khoidauminh Sep 12, 2024
daf45cc
Add s_sampleThumbnailCacheMap back for testing convenience
khoidauminh Sep 12, 2024
2c2202f
Minor change to the way `thumbnailSizeDivisor` is calculated
khoidauminh Sep 12, 2024
0317e80
Extend update region by an amount
khoidauminh Sep 12, 2024
bfb5367
forgot about this
khoidauminh Sep 12, 2024
547e513
Merge branch 'LMMS:master' into sample-thumbnail
khoidauminh Sep 12, 2024
834e861
Adapt to master; Redesign VisualizeParameters; Don't rely entirely on…
khoidauminh Sep 13, 2024
cfa35e6
Merge branch 'master' into sample-thumbnail
khoidauminh Sep 18, 2024
0b8315c
Merge branch 'LMMS:master' into sample-thumbnail
khoidauminh Sep 25, 2024
240696d
Don't try to preserve painted regions
khoidauminh Sep 25, 2024
b6cf4e3
Allow for a bit more thumbnails; Fix incorrect rendering when vertica…
khoidauminh Sep 26, 2024
ab3f0d1
Merge branch 'master' into sample-thumbnail
khoidauminh Oct 3, 2024
58a98a4
Fix missing include statement
khoidauminh Oct 3, 2024
b9bd512
Merge branch 'master' into sample-thumbnail
khoidauminh Nov 11, 2024
38cd04d
Remove the unused variable
khoidauminh Nov 11, 2024
5414e34
Merge branch 'LMMS:master' into sample-thumbnail
khoidauminh Nov 22, 2024
75ba820
Merge branch 'master' into sample-thumbnail
khoidauminh Dec 26, 2024
186ca61
Code optimization; Remove RMS member from Bit; Rename viewRect to dra…
khoidauminh Dec 28, 2024
309bc36
Merge branch 'master' into sample-thumbnail
khoidauminh Dec 28, 2024
7a53536
More code optimizations
khoidauminh Dec 28, 2024
5df559a
Fix formatting
sakertooth Dec 28, 2024
26545b0
Use begin instead of cbegin
sakertooth Dec 28, 2024
f522e1a
Improve generation of thumbnails
sakertooth Jan 1, 2025
55c55c8
Improve expressiveness of the draw code
sakertooth Jan 1, 2025
2476ce1
Add support for reversing
sakertooth Jan 1, 2025
3bc7f3b
Fix drawing code
sakertooth Jan 2, 2025
2a81387
Fix draw code (part 2)
sakertooth Jan 3, 2025
70a01b8
Apply more fixes and simplifications
sakertooth Jan 3, 2025
fea47be
Undo some out of scope changes
sakertooth Jan 3, 2025
ffe4915
Remove SampleWaveform
sakertooth Jan 3, 2025
a9f9194
Improve documentation
sakertooth Jan 3, 2025
6f1becb
Use size_t for some counters
sakertooth Jan 3, 2025
b6eeccb
Change width parameter to be size_t
sakertooth Jan 3, 2025
5a0ad5e
Remove temporary aggregated peak variable
sakertooth Jan 3, 2025
3dd6217
Bump up AggregationPerZoomStep to 10
sakertooth Jan 3, 2025
ae44632
Zoom out only requested range of thumbnail instead of clipping it sep…
sakertooth Jan 3, 2025
dd5dac1
Rename targetSampleWidth to targetThumbnailWidth
sakertooth Jan 3, 2025
3579edb
Handle reversing for AFP; Iterate in reverse instead of reversing the…
khoidauminh Jan 4, 2025
5c9039d
Change names to be more accurate
sakertooth Jan 4, 2025
7d5ba17
Improve implementation of sample thumbnail cache map
sakertooth Jan 4, 2025
5378902
Move AggregationPerZoomStep back down to 2, do not cap smallest thumb…
sakertooth Jan 4, 2025
5952123
Simplify sample thumbnail cache handling in constructor
sakertooth Jan 5, 2025
313af1e
Call drawLines instead of drawLine in a loop
sakertooth Jan 5, 2025
141a20d
Bump up AggregationPerZoomStep to 10 again
sakertooth Jan 5, 2025
f07897c
Fix off-by-one error when constructing Thumbnail from buffer
sakertooth Jan 6, 2025
60400f1
Fix crash when viewport is not in bounds
sakertooth Jan 7, 2025
57d17b6
Apply performance improvements
sakertooth Jan 7, 2025
c8cb378
Apply minor changes
sakertooth Jan 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions include/AutomationEditor.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
#include "SampleClip.h"
#include "TimePos.h"
#include "lmms_basics.h"
#include "SampleThumbnail.h"

class QPainter;
class QPixmap;
Expand Down Expand Up @@ -290,6 +291,8 @@ protected slots:
QColor m_ghostNoteColor;
QColor m_detuningNoteColor;
QColor m_ghostSampleColor;

SampleThumbnail m_sampleThumbnail;

friend class AutomationEditorWindow;

Expand Down
3 changes: 3 additions & 0 deletions include/SampleClipView.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@

#include "ClipView.h"

#include "SampleThumbnail.h"

namespace lmms
{

Expand Down Expand Up @@ -63,6 +65,7 @@ public slots:

private:
SampleClip * m_clip;
SampleThumbnail m_sampleThumbnail;
QPixmap m_paintPixmap;
bool splitClip( const TimePos pos ) override;
} ;
Expand Down
153 changes: 153 additions & 0 deletions include/SampleThumbnail.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/*
* SampleThumbnail.h
*
* Copyright (c) 2024 Khoi Dau <[email protected]>
* Copyright (c) 2024 Sotonye Atemie <[email protected]>
*
* This file is part of LMMS - https://lmms.io
*
* 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 (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/

#ifndef LMMS_SAMPLE_THUMBNAIL_H
#define LMMS_SAMPLE_THUMBNAIL_H

#include <QDateTime>
#include <QPainter>
#include <QRect>
#include <memory>

#include "Sample.h"
#include "lmms_export.h"

namespace lmms {

/**
Allows for visualizing sample data.

On construction, thumbnails will be generated
at logarathmic intervals of downsampling. Those cached thumbnails will then be further downsampled on the fly and
transformed in various ways to create the desired waveform.

Given that we are dealing with far less data to generate
the visualization however (i.e., we are not reading from original sample data when drawing), this provides a
significant performance boost that wouldn't be possible otherwise.
*/
class LMMS_EXPORT SampleThumbnail
{
public:
//! Maximum number of cached sample thumbnails.
//! This cache only allows for caching sample thumbnails that have an associated file path.
static constexpr auto MaxSampleThumbnailCacheSize = 32;

//! The number of samples to aggregate per zoom step when building the thumbnail cache.
static constexpr auto AggregationPerZoomStep = 10;

struct VisualizeParameters
{
QRect sampleRect; //!< A rectangle that covers the entire range of samples.

QRect drawRect; //!< Specifies the location in `sampleRect` where the waveform will be drawn. Equals
//!< `sampleRect` when null.

QRect viewportRect; //!< Clips `drawRect`. Equals `drawRect` when null.

float amplification = 1.0f; //!< The amount of amplification to apply to the waveform.

float sampleStart = 0.0f; //!< Where the sample begins for drawing.

float sampleEnd = 1.0f; //!< Where the sample ends for drawing.

bool reversed = false; //!< Determines if the waveform is drawn in reverse or not.
};

SampleThumbnail() = default;
SampleThumbnail(const Sample& sample);
sakertooth marked this conversation as resolved.
Show resolved Hide resolved
void visualize(VisualizeParameters parameters, QPainter& painter) const;

private:
class Thumbnail
{
public:
struct Peak
{
Peak() = default;

Peak(float min, float max)
: min(min)
, max(max)
{
}

Peak(const SampleFrame& frame)
: min(std::min(frame.left(), frame.right()))
, max(std::max(frame.left(), frame.right()))
{
}

Peak operator+(const Peak& other) const { return Peak(std::min(min, other.min), std::max(max, other.max)); }
Peak operator+(const SampleFrame& frame) const { return *this + Peak{frame}; }

float min = std::numeric_limits<float>::max();
float max = std::numeric_limits<float>::min();
};

Thumbnail() = default;
Thumbnail(std::vector<Peak> peaks, double samplesPerPeak);
Thumbnail(const float* buffer, size_t size, size_t width);

Thumbnail zoomOut(float factor) const;

Peak& operator[](size_t index) { return m_peaks[index]; }
const Peak& operator[](size_t index) const { return m_peaks[index]; }

Peak* peaks() { return m_peaks.data(); }
const Peak* peaks() const { return m_peaks.data(); }

int width() const { return m_peaks.size(); }
sakertooth marked this conversation as resolved.
Show resolved Hide resolved
double samplesPerPeak() const { return m_samplesPerPeak; }

private:
std::vector<Peak> m_peaks;
double m_samplesPerPeak = 0.0;
};

struct SampleThumbnailEntry
{
QString filePath;
QDateTime lastModified;

friend bool operator==(const SampleThumbnailEntry& first, const SampleThumbnailEntry& second)
{
return first.filePath == second.filePath && first.lastModified == second.lastModified;
}
};

struct Hash
{
std::size_t operator()(const SampleThumbnailEntry& entry) const noexcept { return qHash(entry.filePath); }
};

using ThumbnailCache = std::vector<Thumbnail>;
std::shared_ptr<ThumbnailCache> m_thumbnailCache = std::make_shared<ThumbnailCache>();

inline static std::unordered_map<SampleThumbnailEntry, std::shared_ptr<ThumbnailCache>, Hash> s_sampleThumbnailCacheMap;
};

} // namespace lmms

#endif // LMMS_SAMPLE_THUMBNAIL_H
49 changes: 0 additions & 49 deletions include/SampleWaveform.h

This file was deleted.

22 changes: 13 additions & 9 deletions plugins/AudioFileProcessor/AudioFileProcessorWaveView.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,16 @@

#include "AudioFileProcessorWaveView.h"

#include "Sample.h"
#include "ConfigManager.h"
#include "SampleThumbnail.h"
#include "FontHelper.h"
#include "SampleWaveform.h"

#include <QPainter>
#include <QMouseEvent>

#include <algorithm>


namespace lmms
{

Expand Down Expand Up @@ -81,7 +81,8 @@ AudioFileProcessorWaveView::AudioFileProcessorWaveView(QWidget* parent, int w, i
m_isDragging(false),
m_reversed(false),
m_framesPlayed(0),
m_animation(ConfigManager::inst()->value("ui", "animateafp").toInt())
m_animation(ConfigManager::inst()->value("ui", "animateafp").toInt()),
m_sampleThumbnail(*buf)
{
setFixedSize(w, h);
setMouseTracking(true);
Expand Down Expand Up @@ -338,13 +339,16 @@ void AudioFileProcessorWaveView::updateGraph()
m_graph.fill(Qt::transparent);
QPainter p(&m_graph);
p.setPen(QColor(255, 255, 255));

const auto dataOffset = m_reversed ? m_sample->sampleSize() - m_to : m_from;

const auto rect = QRect{0, 0, m_graph.width(), m_graph.height()};
const auto waveform = SampleWaveform::Parameters{
m_sample->data() + dataOffset, static_cast<size_t>(range()), m_sample->amplification(), m_sample->reversed()};
SampleWaveform::visualize(waveform, p, rect);
m_sampleThumbnail = SampleThumbnail{*m_sample};

auto param = SampleThumbnail::VisualizeParameters{};
param.amplification = m_sample->amplification();
param.reversed = m_sample->reversed();
param.sampleStart = static_cast<float>(m_from) / m_sample->sampleSize();
param.sampleEnd = static_cast<float>(m_to) / m_sample->sampleSize();
param.sampleRect = m_graph.rect();
m_sampleThumbnail.visualize(param, p);
}

void AudioFileProcessorWaveView::zoom(const bool out)
Expand Down
2 changes: 2 additions & 0 deletions plugins/AudioFileProcessor/AudioFileProcessorWaveView.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@


#include "Knob.h"
#include "SampleThumbnail.h"


namespace lmms
Expand Down Expand Up @@ -144,6 +145,7 @@ public slots:
bool m_reversed;
f_cnt_t m_framesPlayed;
bool m_animation;
SampleThumbnail m_sampleThumbnail;

friend class AudioFileProcessorView;

Expand Down
33 changes: 23 additions & 10 deletions plugins/SlicerT/SlicerTWaveform.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
#include <QBitmap>
#include <qpainterpath.h>

#include "SampleWaveform.h"
#include "SampleThumbnail.h"
#include "SlicerT.h"
#include "SlicerTView.h"
#include "embed.h"
Expand Down Expand Up @@ -115,10 +115,17 @@ void SlicerTWaveform::drawSeekerWaveform()
brush.setPen(s_waveformColor);

const auto& sample = m_slicerTParent->m_originalSample;
const auto waveform
= SampleWaveform::Parameters{sample.data(), sample.sampleSize(), sample.amplification(), sample.reversed()};
const auto rect = QRect(0, 0, m_seekerWaveform.width(), m_seekerWaveform.height());
SampleWaveform::visualize(waveform, brush, rect);

m_sampleThumbnail = SampleThumbnail{sample};

auto param = SampleThumbnail::VisualizeParameters{};
param.amplification = sample.amplification();
param.reversed = sample.reversed();
param.sampleRect = m_seekerWaveform.rect();
param.sampleStart = static_cast<float>(sample.startFrame()) / sample.sampleSize();
param.sampleEnd = static_cast<float>(sample.endFrame()) / sample.sampleSize();
m_sampleThumbnail.visualize(param, brush);


// increase brightness in inner color
QBitmap innerMask = m_seekerWaveform.createMaskFromColor(s_waveformMaskColor, Qt::MaskMode::MaskOutColor);
Expand Down Expand Up @@ -171,13 +178,19 @@ void SlicerTWaveform::drawEditorWaveform()
size_t endFrame = m_seekerEnd * m_slicerTParent->m_originalSample.sampleSize();

brush.setPen(s_waveformColor);
float zoomOffset = (m_editorHeight - m_zoomLevel * m_editorHeight) / 2;
long zoomOffset = (m_editorHeight - m_zoomLevel * m_editorHeight) / 2;
sakertooth marked this conversation as resolved.
Show resolved Hide resolved

const auto& sample = m_slicerTParent->m_originalSample;
const auto waveform = SampleWaveform::Parameters{
sample.data() + startFrame, endFrame - startFrame, sample.amplification(), sample.reversed()};
const auto rect = QRect(0, zoomOffset, m_editorWidth, m_zoomLevel * m_editorHeight);
SampleWaveform::visualize(waveform, brush, rect);

m_sampleThumbnail = SampleThumbnail{sample};

auto param = SampleThumbnail::VisualizeParameters{};
param.amplification = sample.amplification();
param.reversed = sample.reversed();
param.sampleStart = static_cast<float>(startFrame) / sample.sampleSize();
param.sampleEnd = static_cast<float>(endFrame) / sample.sampleSize();
param.sampleRect = QRect(0, zoomOffset, m_editorWidth, static_cast<long>(m_zoomLevel * m_editorHeight));
m_sampleThumbnail.visualize(param, brush);

// increase brightness in inner color
QBitmap innerMask = m_editorWaveform.createMaskFromColor(s_waveformMaskColor, Qt::MaskMode::MaskOutColor);
Expand Down
4 changes: 4 additions & 0 deletions plugins/SlicerT/SlicerTWaveform.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
#include <QMouseEvent>
#include <QPainter>

#include "SampleThumbnail.h"

namespace lmms {

class SlicerT;
Expand Down Expand Up @@ -108,6 +110,8 @@ public slots:
QPixmap m_editorWaveform;
QPixmap m_sliceEditor;
QPixmap m_emptySampleIcon;

SampleThumbnail m_sampleThumbnail;

SlicerT* m_slicerTParent;

Expand Down
2 changes: 1 addition & 1 deletion src/gui/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ SET(LMMS_SRCS
gui/RowTableView.cpp
gui/SampleLoader.cpp
gui/SampleTrackWindow.cpp
gui/SampleWaveform.cpp
gui/SampleThumbnail.cpp
gui/SendButtonIndicator.cpp
gui/SideBar.cpp
gui/SideBarWidget.cpp
Expand Down
Loading
Loading