Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
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
1 change: 1 addition & 0 deletions include/SampleClipView.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ public slots:
SampleClip * m_clip;
SampleThumbnail m_sampleThumbnail;
QPixmap m_paintPixmap;
long m_paintPixmapXPosition;
bool splitClip( const TimePos pos ) override;
} ;

Expand Down
13 changes: 6 additions & 7 deletions include/SampleThumbnail.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,8 @@ class LMMS_EXPORT SampleThumbnail
{
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.
QRect viewportRect; //!< Specifies the location in `sampleRect` where the waveform will be drawn. Equals
//!< `sampleRect` when null.

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

Expand Down Expand Up @@ -95,8 +93,8 @@ class LMMS_EXPORT SampleThumbnail
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();
float min = std::numeric_limits<float>::infinity();
float max = -std::numeric_limits<float>::infinity();
};

Thumbnail() = default;
Expand All @@ -105,6 +103,7 @@ class LMMS_EXPORT SampleThumbnail

Thumbnail zoomOut(float factor) const;

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

Expand Down Expand Up @@ -134,7 +133,7 @@ class LMMS_EXPORT SampleThumbnail

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

std::shared_ptr<const SampleBuffer> m_buffer = SampleBuffer::emptyBuffer();
inline static std::unordered_map<SampleThumbnailEntry, std::shared_ptr<ThumbnailCache>, Hash> s_sampleThumbnailCacheMap;
};

Expand Down
78 changes: 50 additions & 28 deletions src/gui/SampleThumbnail.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
}

SampleThumbnail::SampleThumbnail(const Sample& sample)
: m_buffer(sample.buffer())
{
auto entry = SampleThumbnailEntry{sample.sampleFile(), QFileInfo{sample.sampleFile()}.lastModified()};
if (!entry.filePath.isEmpty())
Expand All @@ -91,11 +92,9 @@
s_sampleThumbnailCacheMap[std::move(entry)] = m_thumbnailCache;
}

if (!sample.buffer()) { throw std::runtime_error{"Cannot create a sample thumbnail with no buffer"}; }
if (sample.sampleSize() == 0) { return; }

const auto fullResolutionWidth = sample.sampleSize() * DEFAULT_CHANNELS;
m_thumbnailCache->emplace_back(&sample.buffer()->data()->left(), fullResolutionWidth, fullResolutionWidth);
const auto flatBuffer = m_buffer->data()->data();
const auto flatBufferSize = m_buffer->size() * DEFAULT_CHANNELS;
m_thumbnailCache->emplace_back(flatBuffer, flatBufferSize, flatBufferSize / AggregationPerZoomStep);

while (m_thumbnailCache->back().width() >= AggregationPerZoomStep)
{
Expand All @@ -104,54 +103,77 @@
}
}

void SampleThumbnail::visualize(VisualizeParameters parameters, QPainter& painter) const
{
const auto& sampleRect = parameters.sampleRect;
const auto& drawRect = parameters.drawRect.isNull() ? sampleRect : parameters.drawRect;
const auto& viewportRect = parameters.viewportRect.isNull() ? drawRect : parameters.viewportRect;
const auto& viewportRect = parameters.viewportRect.isNull() ? sampleRect : parameters.viewportRect;

const auto renderRect = sampleRect.intersected(drawRect).intersected(viewportRect);
const auto renderRect = sampleRect.intersected(viewportRect);
if (renderRect.isNull()) { return; }

const auto sampleRange = parameters.sampleEnd - parameters.sampleStart;
if (sampleRange <= 0 || sampleRange > 1) { return; }
if (sampleRange <= 0.0f || sampleRange > 1.0f) { return; }

const auto targetThumbnailWidth = static_cast<int>(static_cast<double>(sampleRect.width()) / sampleRange);
const auto targetThumbnailWidth = static_cast<int>(sampleRect.width() / sampleRange);
const auto finerThumbnail = std::find_if(m_thumbnailCache->rbegin(), m_thumbnailCache->rend(),
[&](const auto& thumbnail) { return thumbnail.width() >= targetThumbnailWidth; });

if (finerThumbnail == m_thumbnailCache->rend())
{
qDebug() << "Could not find closest finer thumbnail for a target width of" << targetThumbnailWidth;
return;
}
const auto useOriginalBuffer = finerThumbnail == m_thumbnailCache->rend();
const auto drawOriginalBuffer = static_cast<size_t>(targetThumbnailWidth) == m_buffer->size();

painter.save();
painter.setRenderHint(QPainter::Antialiasing, true);

const auto thumbnailBeginForward = std::max<int>(renderRect.x() - sampleRect.x(), static_cast<int>(parameters.sampleStart * targetThumbnailWidth));
const auto thumbnailEndForward = std::max<int>(renderRect.x() + renderRect.width() - sampleRect.x(), static_cast<int>(parameters.sampleEnd * targetThumbnailWidth));
const auto thumbnailBeginForward = std::max<int>(renderRect.x() - sampleRect.x(), parameters.sampleStart * targetThumbnailWidth);
const auto thumbnailEndForward = std::max<int>(renderRect.x() + renderRect.width() - sampleRect.x(), parameters.sampleEnd * targetThumbnailWidth);
const auto thumbnailBegin = parameters.reversed ? targetThumbnailWidth - thumbnailBeginForward - 1 : thumbnailBeginForward;
const auto thumbnailEnd = parameters.reversed ? targetThumbnailWidth - thumbnailEndForward : thumbnailEndForward;
const auto advanceThumbnailBy = parameters.reversed ? -1 : 1;

const auto finerThumbnailScaleFactor = static_cast<double>(finerThumbnail->width()) / targetThumbnailWidth;
const auto yScale = drawRect.height() / 2 * parameters.amplification;
const auto finerThumbnailWidth = useOriginalBuffer ? m_buffer->size() : finerThumbnail->width();
const auto finerThumbnailScaleFactor = static_cast<double>(finerThumbnailWidth) / targetThumbnailWidth;
const auto yScale = renderRect.height() / 2 * parameters.amplification;

for (auto x = renderRect.x(), i = thumbnailBegin; x < renderRect.x() + renderRect.width() && i != thumbnailEnd;
++x, i += advanceThumbnailBy)
++x, i += advanceThumbnailBy)
{
const auto beginAggregationAt = &(*finerThumbnail)[static_cast<int>(std::floor(i * finerThumbnailScaleFactor))];
const auto endAggregationAt = &(*finerThumbnail)[static_cast<int>(std::ceil((i + 1) * finerThumbnailScaleFactor))];
const auto peak = std::accumulate(beginAggregationAt, endAggregationAt, Thumbnail::Peak{});

const auto yMin = drawRect.center().y() - peak.min * yScale;
const auto yMax = drawRect.center().y() - peak.max * yScale;

painter.drawLine(x, yMin, x, yMax);
if (useOriginalBuffer && drawOriginalBuffer)
{
const auto value = m_buffer->data()->data()[i];
painter.drawPoint(x, renderRect.center().y() - value * yScale);
continue;
}
else
{
const auto beginIndex = static_cast<size_t>(std::floor(i * finerThumbnailScaleFactor));
const auto endIndex = static_cast<size_t>(std::ceil((i + 1) * finerThumbnailScaleFactor));

auto minPeak = 0.f;
auto maxPeak = 0.f;

if (useOriginalBuffer)
{
const auto flatBuffer = m_buffer->data()->data();
const auto [min, max] = std::minmax_element(flatBuffer + beginIndex, flatBuffer + endIndex);
minPeak = *min;
maxPeak = *max;
}
else
{
const auto beginAggregationAt = finerThumbnail->data() + beginIndex;
const auto endAggregationAt = finerThumbnail->data() + endIndex;
const auto peak = std::accumulate(beginAggregationAt, endAggregationAt, Thumbnail::Peak{});
minPeak = peak.min;
maxPeak = peak.max;
}

const auto yMin = renderRect.center().y() - minPeak * yScale;
const auto yMax = renderRect.center().y() - maxPeak * yScale;
painter.drawLine(x, yMin, x, yMax);
}
}

painter.restore();
}

Check notice on line 178 in src/gui/SampleThumbnail.cpp

View check run for this annotation

codefactor.io / CodeFactor

src/gui/SampleThumbnail.cpp#L106-L178

Complex Method
} // namespace lmms
2 changes: 2 additions & 0 deletions src/gui/clips/ClipView.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,8 @@ void ClipView::remove()
// as actually deleting the Clip with the deleteLater function. That being said, it shouldn't
// be possible to make a Clip without a Track (i.e., Clip::getTrack is never nullptr).
m_clip->deleteLater();

m_trackView->update();
}


Expand Down
31 changes: 21 additions & 10 deletions src/gui/clips/SampleClipView.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ namespace lmms::gui
SampleClipView::SampleClipView( SampleClip * _clip, TrackView * _tv ) :
ClipView( _clip, _tv ),
m_clip( _clip ),
m_paintPixmap()
m_paintPixmap(),
m_paintPixmapXPosition(0)
{
// update UI and tooltip
updateSample();
Expand Down Expand Up @@ -210,15 +211,20 @@ void SampleClipView::paintEvent( QPaintEvent * pe )

if( !needsUpdate() )
{
painter.drawPixmap( 0, 0, m_paintPixmap );
painter.drawPixmap(m_paintPixmapXPosition, 0, m_paintPixmap);
return;
}

setNeedsUpdate( false );

if (m_paintPixmap.isNull() || m_paintPixmap.size() != size())
// Use the clip's height to avoid artifacts when rendering while something else is overlaying the clip.
const auto viewPortRect = QRect(0, 0, pe->rect().width(), rect().height());

m_paintPixmapXPosition = pe->rect().x();

if (m_paintPixmap.isNull() || m_paintPixmap.size() != viewPortRect.size())
{
m_paintPixmap = QPixmap(size());
m_paintPixmap = QPixmap(viewPortRect.size());
}

QPainter p( &m_paintPixmap );
Expand Down Expand Up @@ -274,12 +280,14 @@ void SampleClipView::paintEvent( QPaintEvent * pe )
float sampleLength = m_clip->sampleLength() * ppb / ticksPerBar;

const auto& sample = m_clip->m_sample;

const auto sampleRextX = static_cast<int>(offsetStart) - m_paintPixmapXPosition;

if (sample.sampleSize() > 0)
{
const auto param = SampleThumbnail::VisualizeParameters{
.sampleRect = QRect(offsetStart, spacing, sampleLength, height() - spacing),
.drawRect = QRect(0, spacing, width(), height() - spacing),
.viewportRect = pe->rect(),
.sampleRect = QRect(sampleRextX, spacing, sampleLength, height() - spacing),
.viewportRect = viewPortRect,
.amplification = sample.amplification(),
.reversed = sample.reversed()
};
Expand All @@ -295,12 +303,15 @@ void SampleClipView::paintEvent( QPaintEvent * pe )

// inner border
p.setPen( c.lighter( 135 ) );
p.drawRect( 1, 1, rect().right() - BORDER_WIDTH,
p.drawRect(
-m_paintPixmapXPosition + 1,
1,
rect().right() - BORDER_WIDTH,
rect().bottom() - BORDER_WIDTH );

// outer border
p.setPen( c.darker( 200 ) );
p.drawRect( 0, 0, rect().right(), rect().bottom() );
p.drawRect( -m_paintPixmapXPosition, 0, rect().right(), rect().bottom() );

// draw the 'muted' pixmap only if the clip was manualy muted
if( m_clip->isMuted() )
Expand Down Expand Up @@ -332,7 +343,7 @@ void SampleClipView::paintEvent( QPaintEvent * pe )

p.end();

painter.drawPixmap( 0, 0, m_paintPixmap );
painter.drawPixmap(m_paintPixmapXPosition, 0, m_paintPixmap);
}


Expand Down
1 change: 1 addition & 0 deletions src/gui/editors/AutomationEditor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1219,6 +1219,7 @@ void AutomationEditor::paintEvent(QPaintEvent * pe )

const auto param = SampleThumbnail::VisualizeParameters{
.sampleRect = QRect(startPos, yOffset, sampleWidth, sampleHeight),
.viewportRect = pe->rect(),
.amplification = sample.amplification(),
.sampleStart = static_cast<float>(sample.startFrame()) / sample.sampleSize(),
.sampleEnd = static_cast<float>(sample.endFrame()) / sample.sampleSize(),
Expand Down
Loading