Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
20 changes: 13 additions & 7 deletions include/SampleThumbnail.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ namespace lmms::gui {
class LMMS_EXPORT SampleThumbnail
{
public:
enum Type { MONO, LEFT, RIGHT };

struct VisualizeParameters
{
QRect sampleRect; //!< A rectangle that covers the entire range of samples.
Expand All @@ -70,6 +72,8 @@ class LMMS_EXPORT SampleThumbnail
float sampleEnd = 1.0f; //!< Where the sample ends for drawing.

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

Type waveType = Type::MONO; //!< Determines which stereo channel is drawn
};

SampleThumbnail() = default;
Expand Down Expand Up @@ -104,20 +108,22 @@ class LMMS_EXPORT SampleThumbnail
};

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

Thumbnail zoomOut(float factor) const;

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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These operators no longer makes a lot of sense since the class is now holding 3 different arrays. Because they were unused anyway I've chosen to remove them.

const Peak& operator[](size_t index) const { return m_peaks[index]; }
Peak* mono() { return m_mono_peaks.data(); }
Peak* right() { return m_right_peaks.data(); }
Peak* left() { return m_left_peaks.data(); }

int width() const { return m_peaks.size(); }
int width() const { return m_mono_peaks.size(); }
double samplesPerPeak() const { return m_samplesPerPeak; }

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

Expand Down
3 changes: 3 additions & 0 deletions include/SetupDialog.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ private slots:
void toggleOpenLastProject(bool enabled);
void detachBehaviorChanged();
void loopMarkerModeChanged();
void stereoChannelsPolicyChanged();
void setLanguage(int lang);

// Performance settings widget.
Expand Down Expand Up @@ -153,6 +154,8 @@ private slots:
QComboBox* m_detachBehaviorComboBox;
QString m_loopMarkerMode;
QComboBox* m_loopMarkerComboBox;
QString m_stereoChannelsPolicy;
QComboBox* m_stereoChannelsComboBox;
QString m_autoScroll;
QComboBox* m_autoScrollComboBox;
QString m_lang;
Expand Down
106 changes: 79 additions & 27 deletions src/gui/SampleThumbnail.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,14 @@

#include "SampleThumbnail.h"

#include <algorithm>
#include <cassert>
#include <QFileInfo>
#include <QPainter>

#include "Sample.h"
#include "SampleBuffer.h"
#include "SampleFrame.h"

namespace {
constexpr auto MaxSampleThumbnailCacheSize = 32;
Expand All @@ -37,38 +41,60 @@ namespace {

namespace lmms::gui {

SampleThumbnail::Thumbnail::Thumbnail(std::vector<Peak> peaks, double samplesPerPeak)
: m_peaks(std::move(peaks))
SampleThumbnail::Thumbnail::Thumbnail(std::vector<Peak> right_peaks, std::vector<Peak> left_peaks, double samplesPerPeak)
: m_mono_peaks(right_peaks.size())
, m_right_peaks(std::move(right_peaks))
, m_left_peaks(std::move(left_peaks))
, m_samplesPerPeak(samplesPerPeak)
{
assert(m_right_peaks.size() == m_left_peaks.size() && "Stereo peaks arrays lengths don't match");

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This kind of precondition is bad, but Thumbnail is private in SampleThumbnail so it is unlikely this would cause any issue in the future.


for (size_t i = 0; i < m_right_peaks.size(); i++)
{
m_mono_peaks[i] = m_right_peaks[i] + m_left_peaks[i];
}
}

SampleThumbnail::Thumbnail::Thumbnail(const float* buffer, size_t size, size_t width)
: m_peaks(width)
, m_samplesPerPeak(std::max(static_cast<double>(size) / width, 1.0))
SampleThumbnail::Thumbnail::Thumbnail(const SampleBuffer* buffer, size_t width)
: m_mono_peaks(width)
, m_right_peaks(width)
, m_left_peaks(width)
, m_samplesPerPeak(std::max(static_cast<double>(buffer->size()) / width, 1.0))
{
for (auto peakIndex = std::size_t{0}; peakIndex < width; ++peakIndex)
{
const auto beginSample = buffer + static_cast<size_t>(std::floor(peakIndex * m_samplesPerPeak));
const auto endSample = buffer + static_cast<size_t>(std::ceil((peakIndex + 1) * m_samplesPerPeak));
const auto [min, max] = std::minmax_element(beginSample, endSample);
m_peaks[peakIndex] = Peak{*min, *max};
const auto beginSample = buffer->data() + static_cast<size_t>(std::floor(peakIndex * m_samplesPerPeak));
const auto endSample = buffer->data() + static_cast<size_t>(std::ceil((peakIndex + 1) * m_samplesPerPeak));
const auto [right_min, right_max] = std::minmax_element(beginSample, endSample,
[](const SampleFrame& a, const SampleFrame& b){ return a.right() < b.right();}
);
const auto [left_min, left_max] = std::minmax_element(beginSample, endSample,
[](const SampleFrame& a, const SampleFrame& b){ return a.left() < b.left();}
);
m_right_peaks[peakIndex] = Peak{right_min->right(), right_max->right()};
m_left_peaks[peakIndex] = Peak{left_min->left(), left_max->left()};
m_mono_peaks[peakIndex] = m_right_peaks[peakIndex] + m_left_peaks[peakIndex];
}
}

SampleThumbnail::Thumbnail SampleThumbnail::Thumbnail::zoomOut(float factor) const
{
assert(factor >= 1 && "Invalid zoom out factor");

auto peaks = std::vector<Peak>(m_peaks.size() / factor);
for (auto peakIndex = std::size_t{0}; peakIndex < peaks.size(); ++peakIndex)
auto right_peaks = std::vector<Peak>(m_right_peaks.size() / factor);
auto left_peaks = std::vector<Peak>(m_left_peaks.size() / factor);

for (auto peakIndex = std::size_t{0}; peakIndex < right_peaks.size(); ++peakIndex)
{
const auto beginAggregationAt = m_peaks.begin() + static_cast<size_t>(std::floor(peakIndex * factor));
const auto endAggregationAt = m_peaks.begin() + static_cast<size_t>(std::ceil((peakIndex + 1) * factor));
peaks[peakIndex] = std::accumulate(beginAggregationAt, endAggregationAt, Peak{});
}
const auto beginRightAggregationAt = m_right_peaks.begin() + static_cast<size_t>(std::floor(peakIndex * factor));
const auto endRightAggregationAt = m_right_peaks.begin() + static_cast<size_t>(std::ceil((peakIndex + 1) * factor));
right_peaks[peakIndex] = std::accumulate(beginRightAggregationAt, endRightAggregationAt, Peak{});

return Thumbnail{std::move(peaks), m_samplesPerPeak * factor};
const auto beginLeftAggregationAt = m_left_peaks.begin() + static_cast<size_t>(std::floor(peakIndex * factor));
const auto endLeftAggregationAt = m_left_peaks.begin() + static_cast<size_t>(std::ceil((peakIndex + 1) * factor));
left_peaks[peakIndex] = std::accumulate(beginLeftAggregationAt, endLeftAggregationAt, Peak{});
}
return Thumbnail{std::move(right_peaks), std::move(left_peaks), m_samplesPerPeak * factor};
}

SampleThumbnail::SampleThumbnail(const Sample& sample)
Expand All @@ -94,9 +120,7 @@ SampleThumbnail::SampleThumbnail(const Sample& sample)
s_sampleThumbnailCacheMap[std::move(entry)] = m_thumbnailCache;
}

const auto flatBuffer = m_buffer->data()->data();
const auto flatBufferSize = m_buffer->size() * DEFAULT_CHANNELS;
m_thumbnailCache->emplace_back(flatBuffer, flatBufferSize, flatBufferSize / AggregationPerZoomStep);
m_thumbnailCache->emplace_back(m_buffer.get(), m_buffer->size() / AggregationPerZoomStep);

while (m_thumbnailCache->back().width() >= AggregationPerZoomStep)
{
Expand Down Expand Up @@ -141,7 +165,11 @@ void SampleThumbnail::visualize(VisualizeParameters parameters, QPainter& painte
{
if (useOriginalBuffer && drawOriginalBuffer)
{
const auto value = m_buffer->data()->data()[i];
const auto value = (

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm unsure about this block and what its previous version was aiming for.
The conditions for it to be executed are very uncommon tho.

parameters.waveType == Type::RIGHT ? m_buffer->data()[i][0] :
parameters.waveType == Type::LEFT ? m_buffer->data()[i][1] :
(m_buffer->data()[i][0] + m_buffer->data()[i][1]) / 2
);
painter.drawPoint(x, renderRect.center().y() - value * yScale);
continue;
}
Expand All @@ -155,16 +183,40 @@ void SampleThumbnail::visualize(VisualizeParameters parameters, QPainter& painte

if (useOriginalBuffer)
{
const auto flatBuffer = m_buffer->data()->data();
const auto [min, max] = std::minmax_element(flatBuffer + beginIndex, flatBuffer + endIndex);
minPeak = *min;
maxPeak = *max;
const auto frameBuffer = m_buffer->data();

if (parameters.waveType == RIGHT)
{
const auto [min, max] = std::minmax_element(frameBuffer + beginIndex, frameBuffer + endIndex,
[](const SampleFrame& a, const SampleFrame& b){ return a.right() < b.right(); }
);
minPeak = min->right();
maxPeak = max->right();
}
else if (parameters.waveType == LEFT)
{
const auto [min, max] = std::minmax_element(frameBuffer + beginIndex, frameBuffer + endIndex,
[](const SampleFrame& a, const SampleFrame& b){ return a.left() < b.left(); }
);
minPeak = min->left();
maxPeak = max->left();
}
else
{
const auto flatBuffer = frameBuffer->data();
const auto [min, max] = std::minmax_element(flatBuffer + (2 * beginIndex), flatBuffer + (2 * 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{});
const Thumbnail::Peak* peaks = (
parameters.waveType == Type::RIGHT ? finerThumbnail->right() :
parameters.waveType == Type::LEFT ? finerThumbnail->left() :
finerThumbnail->mono()
);
const auto peak = std::accumulate(peaks + beginIndex, peaks + endIndex, Thumbnail::Peak{});
minPeak = peak.min;
maxPeak = peak.max;
}
Expand Down
31 changes: 25 additions & 6 deletions src/gui/clips/SampleClipView.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,20 @@

#include <QApplication>
#include <QMenu>
#include <QObject>
#include <QPainter>

#include "FileDialog.h"
#include "GuiApplication.h"
#include "AutomationEditor.h"
#include "ConfigManager.h"
#include "embed.h"
#include "FileDialog.h"
#include "GuiApplication.h"
#include "PathUtil.h"
#include "SampleClip.h"
#include "SampleThumbnail.h"
#include "Song.h"
#include "StringPairDrag.h"
#include "Track.h"
#include "TrackContainerView.h"
#include "TrackView.h"

Expand Down Expand Up @@ -206,153 +209,169 @@



void SampleClipView::paintEvent( QPaintEvent * pe )
{
QPainter painter( this );

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

setNeedsUpdate( false );

const auto trackViewWidth = getTrackView()->rect().width();

// Use the clip's height to avoid artifacts when rendering while something else is overlaying the clip.
const auto viewPortRect = QRect(0, 0, trackViewWidth * 2, rect().height());

m_paintPixmapXPosition = std::max(0, pe->rect().x() - trackViewWidth);

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

QPainter p( &m_paintPixmap );

bool muted = m_clip->getTrack()->isMuted() || m_clip->isMuted();
bool selected = isSelected();

QLinearGradient lingrad(0, 0, 0, height());
QColor c = painter.background().color();
if (muted) { c = c.darker(150); }
if (selected) { c = c.darker(150); }

lingrad.setColorAt( 1, c.darker( 300 ) );
lingrad.setColorAt( 0, c );

// paint a black rectangle under the clip to prevent glitches with transparent backgrounds
p.fillRect( rect(), QColor( 0, 0, 0 ) );

if( gradient() )
{
p.fillRect( rect(), lingrad );
}
else
{
p.fillRect( rect(), c );
}

auto clipColor = m_clip->color().value_or(m_clip->getTrack()->color().value_or(painter.pen().brush().color()));

p.setPen(clipColor);

if (muted)
{
QColor penColor = p.pen().brush().color();
penColor.setHsv(penColor.hsvHue(), penColor.hsvSaturation() / 4, penColor.value());
p.setPen(penColor.darker(250));
}
if (selected)
{
p.setPen(p.pen().brush().color().darker(150));
}

const int spacing = BORDER_WIDTH + 1;
const float ppb = fixedClips() ?
( parentWidget()->width() - 2 * BORDER_WIDTH )
/ (float) m_clip->length().getBar() :
pixelsPerBar();

float nom = Engine::getSong()->getTimeSigModel().getNumerator();
float den = Engine::getSong()->getTimeSigModel().getDenominator();
float ticksPerBar = DefaultTicksPerBar * nom / den;
float offsetStart = m_clip->startTimeOffset() / ticksPerBar * pixelsPerBar();
float sampleLength = m_clip->sampleLength() * ppb / ticksPerBar;

const auto& sample = m_clip->m_sample;

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

if (sample.sampleSize() > 0)
{
const auto param = SampleThumbnail::VisualizeParameters{
.sampleRect = QRect(sampleRextX, spacing, sampleLength, height() - spacing),
auto param = SampleThumbnail::VisualizeParameters{
.viewportRect = viewPortRect,
.amplification = sample.amplification(),
.reversed = sample.reversed()
};
const QString policy = ConfigManager::inst()->value("app", "stereochannelspolicy", "height");
if (policy == "never" || (policy == "height" && height() < DEFAULT_TRACK_HEIGHT * 2))
{
param.sampleRect = QRect(sampleRectX, spacing, sampleLength, height() - spacing);
param.waveType = SampleThumbnail::Type::MONO;
m_sampleThumbnail.visualize(param, p);
}
else
{
const int halfHeight = height() / 2;

m_sampleThumbnail.visualize(param, p);
param.sampleRect = QRect(sampleRectX, spacing, sampleLength, halfHeight - spacing);
param.waveType = SampleThumbnail::Type::LEFT;
m_sampleThumbnail.visualize(param, p);

param.sampleRect = QRect(sampleRectX, halfHeight + spacing, sampleLength, height() - spacing);
param.waveType = SampleThumbnail::Type::RIGHT;
m_sampleThumbnail.visualize(param, p);
}
}

QString name = PathUtil::cleanName(m_clip->m_sample.sampleFile());
paintTextLabel(name, p);

// disable antialiasing for borders, since its not needed
p.setRenderHint( QPainter::Antialiasing, false );

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

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

// draw the 'muted' pixmap only if the clip was manually muted
if( m_clip->isMuted() )
{
const int spacing = BORDER_WIDTH;
const int size = 14;
p.drawPixmap( spacing, height() - ( size + spacing ),
embed::getIconPixmap( "muted", size, size ) );
}

if ( m_marker )
{
p.setPen(markerColor());
p.drawLine(m_markerPos, rect().bottom(), m_markerPos, rect().top());
}
// recording sample tracks is not possible at the moment

/* if( m_clip->isRecord() )
{
p.setFont( pointSize<7>( p.font() ) );

p.setPen( textShadowColor() );
p.drawText( 10, p.fontMetrics().height()+1, "Rec" );
p.setPen( textColor() );
p.drawText( 9, p.fontMetrics().height(), "Rec" );

p.setBrush( QBrush( textColor() ) );
p.drawEllipse( 4, 5, 4, 4 );
}*/

p.end();

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




Check notice on line 374 in src/gui/clips/SampleClipView.cpp

View check run for this annotation

codefactor.io / CodeFactor

src/gui/clips/SampleClipView.cpp#L212-L374

Complex Method
void SampleClipView::reverseSample()
{
m_clip->m_sample.setReversed(!m_clip->m_sample.reversed());
Expand Down
21 changes: 21 additions & 0 deletions src/gui/modals/SetupDialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) :
"app", "openlastproject").toInt()),
m_detachBehavior{ConfigManager::inst()->value("ui", "detachbehavior", "show")},
m_loopMarkerMode{ConfigManager::inst()->value("app", "loopmarkermode", "dual")},
m_stereoChannelsPolicy{ConfigManager::inst()->value("app", "stereochannelspolicy", "height")},
m_autoScroll(ConfigManager::inst()->value("ui", "autoscroll", "stepped")),
m_lang(ConfigManager::inst()->value(
"app", "language")),
Expand Down Expand Up @@ -284,6 +285,19 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) :
guiGroupLayout->addWidget(new QLabel{tr("Loop edit mode"), guiGroupBox});
guiGroupLayout->addWidget(m_loopMarkerComboBox);

m_stereoChannelsComboBox = new QComboBox{guiGroupBox};

m_stereoChannelsComboBox->addItem(tr("Depending on track height"), "height");
m_stereoChannelsComboBox->addItem(tr("Always"), "always");
m_stereoChannelsComboBox->addItem(tr("Never"), "never");

m_stereoChannelsComboBox->setCurrentIndex(m_stereoChannelsComboBox->findData(m_stereoChannelsPolicy));
connect(m_stereoChannelsComboBox, qOverload<int>(&QComboBox::currentIndexChanged),
this, &SetupDialog::stereoChannelsPolicyChanged);

guiGroupLayout->addWidget(new QLabel{tr("Show samples stereo channels"), guiGroupBox});
guiGroupLayout->addWidget(m_stereoChannelsComboBox);

m_autoScrollComboBox = new QComboBox{guiGroupBox};
m_autoScrollComboBox->addItem(tr("Disabled"), TimeLineWidget::AutoScrollDisabledString);
m_autoScrollComboBox->addItem(tr("Stepped (Scroll once the playhead goes out of view)"), TimeLineWidget::AutoScrollSteppedString);
Expand Down Expand Up @@ -1009,6 +1023,7 @@ void SetupDialog::accept()
QString::number(m_openLastProject));
ConfigManager::inst()->setValue("ui", "detachbehavior", m_detachBehavior);
ConfigManager::inst()->setValue("app", "loopmarkermode", m_loopMarkerMode);
ConfigManager::inst()->setValue("app", "stereochannelspolicy", m_stereoChannelsPolicy);
ConfigManager::inst()->setValue("app", "language", m_lang);
ConfigManager::inst()->setValue("ui", "autoscroll", m_autoScroll);
ConfigManager::inst()->setValue("ui", "saveinterval",
Expand Down Expand Up @@ -1160,6 +1175,12 @@ void SetupDialog::loopMarkerModeChanged()
}


void SetupDialog::stereoChannelsPolicyChanged()
{
m_stereoChannelsPolicy = m_stereoChannelsComboBox->currentData().toString();
}


void SetupDialog::setLanguage(int lang)
{
m_lang = m_languages[lang];
Expand Down
Loading