From f281961d8815f1814d748b4ec77fdd3547f64428 Mon Sep 17 00:00:00 2001 From: khoidauminh Date: Wed, 3 Jul 2024 15:58:21 +0700 Subject: [PATCH] Major refactor; implement thumbnailing for SlicerT, AFP and Automation editor --- include/AutomationEditor.h | 3 + include/SampleClipView.h | 2 +- include/SampleThumbnail.h | 59 ++- .../AudioFileProcessorWaveView.cpp | 33 +- .../AudioFileProcessorWaveView.h | 2 + plugins/SlicerT/SlicerTWaveform.cpp | 47 ++- plugins/SlicerT/SlicerTWaveform.h | 3 + src/gui/SampleThumbnail.cpp | 393 +++++++++++------- src/gui/clips/SampleClipView.cpp | 21 +- src/gui/editors/AutomationEditor.cpp | 26 +- 10 files changed, 387 insertions(+), 202 deletions(-) diff --git a/include/AutomationEditor.h b/include/AutomationEditor.h index 1110e8e4c9e..9dfb60569a3 100644 --- a/include/AutomationEditor.h +++ b/include/AutomationEditor.h @@ -38,6 +38,7 @@ #include "SampleClip.h" #include "TimePos.h" #include "lmms_basics.h" +#include "SampleThumbnail.h" class QPainter; class QPixmap; @@ -288,6 +289,8 @@ protected slots: QColor m_ghostNoteColor; QColor m_detuningNoteColor; QColor m_ghostSampleColor; + + SampleThumbnailListManager m_thumbnaillist; friend class AutomationEditorWindow; diff --git a/include/SampleClipView.h b/include/SampleClipView.h index 517c4c60720..8389fb85a8d 100644 --- a/include/SampleClipView.h +++ b/include/SampleClipView.h @@ -65,7 +65,7 @@ public slots: private: SampleClip * m_clip; - SampleThumbnailListManager thumbnaillist; + SampleThumbnailListManager m_thumbnaillist; QPixmap m_paintPixmap; bool splitClip( const TimePos pos ) override; } ; diff --git a/include/SampleThumbnail.h b/include/SampleThumbnail.h index 990fcb3db21..8e143ea37b8 100644 --- a/include/SampleThumbnail.h +++ b/include/SampleThumbnail.h @@ -25,9 +25,9 @@ #ifndef LMMS_SAMPLE_THUMBNAIL_H #define LMMS_SAMPLE_THUMBNAIL_H -constexpr unsigned long long MIN_THUMBNAIL_SIZE = 32; +constexpr unsigned long long MIN_THUMBNAIL_SIZE = 1; constexpr unsigned long long MAX_THUMBNAIL_SIZE = 32768; -constexpr unsigned long long THUMBNAIL_SIZE_DIVISOR = 2; +constexpr unsigned long long THUMBNAIL_SIZE_DIVISOR = 32; #include #include @@ -44,9 +44,18 @@ namespace lmms { struct SampleThumbnailVisualizeParameters { - float amplification; - bool reversed; - float pixelsTillSampleEnd; + const Sample* originalSample = nullptr; + + const float amplification; + const bool reversed; + + const float sampleStartPercent = 0.0; + const float sampleEndPercent = 1.0; + + const long x; + const long y; + const long width; + const long height; }; struct SampleThumbnailBit @@ -58,38 +67,48 @@ struct SampleThumbnailBit SampleThumbnailBit(); - SampleThumbnailBit(const sampleFrame& sample); - - ~SampleThumbnailBit(); - - void merge(const SampleThumbnailBit& other); + SampleThumbnailBit(const SampleFrame&); - void mergeFrame(const sampleFrame& sample); + void merge(const SampleThumbnailBit&); - SampleThumbnailBit linear(const SampleThumbnailBit& other, float t) const; + void mergeFrame(const SampleFrame&); }; using SampleThumbnail = std::vector; using SampleThumbnailList = std::vector; -using SharedSampleThumbnailList = std::shared_ptr< SampleThumbnailList >; - -static std::map SAMPLE_THUMBNAIL_MAP; +using SharedSampleThumbnailList = std::shared_ptr; -class LMMS_EXPORT SampleThumbnailListManager +class LMMS_EXPORT SampleThumbnailListManager { private: SharedSampleThumbnailList list; + static std::map SAMPLE_THUMBNAIL_MAP; + +protected: + static void draw( + QPainter& painter, const SampleThumbnailBit& bit, + int lineX, int centerY, float scalingFactor, + QColor color, QColor rmsColor + ); + + static SampleThumbnail generate( + const size_t thumbnailsize, + const SampleFrame* sampleBuffer, + const SampleFrame* sampleBufferEnd + ); public: SampleThumbnailListManager(); - SampleThumbnailListManager(const Sample& inputSample); + SampleThumbnailListManager(const Sample&); - bool selectFromGlobalThumbnailMap(const Sample& inputSample); + bool selectFromGlobalThumbnailMap(const Sample&); void cleanUpGlobalThumbnailMap(); - - void visualize(SampleThumbnailVisualizeParameters parameters, QPainter& painter, const QRect& rect); + + void visualize(const SampleThumbnailVisualizeParameters&, QPainter&) const; + static void visualize_original(const SampleThumbnailVisualizeParameters&, QPainter&); + }; } diff --git a/plugins/AudioFileProcessor/AudioFileProcessorWaveView.cpp b/plugins/AudioFileProcessor/AudioFileProcessorWaveView.cpp index 1742ee3a7cd..caaa634f185 100644 --- a/plugins/AudioFileProcessor/AudioFileProcessorWaveView.cpp +++ b/plugins/AudioFileProcessor/AudioFileProcessorWaveView.cpp @@ -26,7 +26,7 @@ #include "ConfigManager.h" #include "gui_templates.h" -#include "SampleWaveform.h" +#include "SampleThumbnail.h" #include #include @@ -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_thumbnaillist(SampleThumbnailListManager(*buf)) { setFixedSize(w, h); setMouseTracking(true); @@ -333,10 +334,30 @@ void AudioFileProcessorWaveView::updateGraph() QPainter p(&m_graph); p.setPen(QColor(255, 255, 255)); - const auto rect = QRect{0, 0, m_graph.width(), m_graph.height()}; - const auto waveform = SampleWaveform::Parameters{ - m_sample->data() + m_from, static_cast(range()), m_sample->amplification(), m_sample->reversed()}; - SampleWaveform::visualize(waveform, p, rect); + //const auto rect = QRect{0, 0, m_graph.width(), m_graph.height()}; + //const auto waveform = SampleWaveform::Parameters{ + //m_sample->data() + m_from, static_cast(range()), m_sample->amplification(), m_sample->reversed()}; + //SampleWaveform::visualize(waveform, p, rect); + + // Performance hit is neglectable + m_thumbnaillist = SampleThumbnailListManager(*m_sample); + + const auto parameters = SampleThumbnailVisualizeParameters{ + .originalSample = m_sample, + + .amplification = m_sample->amplification(), + .reversed = m_sample->reversed(), + + .sampleStartPercent = static_cast(m_from) / m_sample->sampleSize(), + .sampleEndPercent = static_cast(m_to ) / m_sample->sampleSize(), + + .x = 0, + .y = 0, + .width = m_graph.width(), + .height = m_graph.height() + }; + + m_thumbnaillist.visualize(parameters, p); } void AudioFileProcessorWaveView::zoom(const bool out) diff --git a/plugins/AudioFileProcessor/AudioFileProcessorWaveView.h b/plugins/AudioFileProcessor/AudioFileProcessorWaveView.h index f40b69d121f..2e770444379 100644 --- a/plugins/AudioFileProcessor/AudioFileProcessorWaveView.h +++ b/plugins/AudioFileProcessor/AudioFileProcessorWaveView.h @@ -27,6 +27,7 @@ #include "Knob.h" +#include "SampleThumbnail.h" namespace lmms @@ -142,6 +143,7 @@ public slots: bool m_reversed; f_cnt_t m_framesPlayed; bool m_animation; + SampleThumbnailListManager m_thumbnaillist; friend class AudioFileProcessorView; diff --git a/plugins/SlicerT/SlicerTWaveform.cpp b/plugins/SlicerT/SlicerTWaveform.cpp index 3793ed2f159..5ab1fabee9b 100644 --- a/plugins/SlicerT/SlicerTWaveform.cpp +++ b/plugins/SlicerT/SlicerTWaveform.cpp @@ -26,7 +26,7 @@ #include -#include "SampleWaveform.h" +#include "SampleThumbnail.h" #include "SlicerT.h" #include "SlicerTView.h" #include "embed.h" @@ -90,9 +90,21 @@ 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); + //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_thumbnaillist = SampleThumbnailListManager(sample); + const auto parameters = SampleThumbnailVisualizeParameters{ + .amplification = sample.amplification(), + .reversed = sample.reversed(), + + .x = 0, + .y = 0, + .width = m_seekerWaveform.width(), + .height = m_seekerWaveform.height() + }; + m_thumbnaillist.visualize(parameters, brush); // increase brightness in inner color QBitmap innerMask = m_seekerWaveform.createMaskFromColor(s_waveformMaskColor, Qt::MaskMode::MaskOutColor); @@ -141,15 +153,32 @@ void SlicerTWaveform::drawEditorWaveform() QPainter brush(&m_editorWaveform); size_t startFrame = m_seekerStart * m_slicerTParent->m_originalSample.sampleSize(); - size_t endFrame = m_seekerEnd * m_slicerTParent->m_originalSample.sampleSize(); + 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; 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); + //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_thumbnaillist = SampleThumbnailListManager(sample); + const auto parameters = SampleThumbnailVisualizeParameters{ + .originalSample = &sample, + + .amplification = sample.amplification(), + .reversed = sample.reversed(), + + .sampleStartPercent = static_cast(startFrame) / sample.sampleSize(), + .sampleEndPercent = static_cast( endFrame) / sample.sampleSize(), + + .x = 0, + .y = zoomOffset, + .width = m_editorWidth, + .height = static_cast(m_zoomLevel * m_editorHeight) + }; + m_thumbnaillist.visualize(parameters, brush); // increase brightness in inner color QBitmap innerMask = m_editorWaveform.createMaskFromColor(s_waveformMaskColor, Qt::MaskMode::MaskOutColor); diff --git a/plugins/SlicerT/SlicerTWaveform.h b/plugins/SlicerT/SlicerTWaveform.h index 6478e7f8684..28086869610 100644 --- a/plugins/SlicerT/SlicerTWaveform.h +++ b/plugins/SlicerT/SlicerTWaveform.h @@ -34,6 +34,7 @@ #include "Instrument.h" #include "SampleBuffer.h" +#include "SampleThumbnail.h" namespace lmms { @@ -108,6 +109,8 @@ public slots: QPixmap m_editorWaveform; QPixmap m_sliceEditor; QPixmap m_emptySampleIcon; + + SampleThumbnailListManager m_thumbnaillist; SlicerT* m_slicerTParent; diff --git a/src/gui/SampleThumbnail.cpp b/src/gui/SampleThumbnail.cpp index e3021d83517..2bcad07d287 100644 --- a/src/gui/SampleThumbnail.cpp +++ b/src/gui/SampleThumbnail.cpp @@ -27,27 +27,29 @@ namespace lmms { -//~ SampleThumbnailBit::SampleThumbnailBit(float max, float min): - //~ max(max), - //~ min(min) -//~ {} +std::map + SampleThumbnailListManager::SAMPLE_THUMBNAIL_MAP{}; -SampleThumbnailBit::SampleThumbnailBit(const sampleFrame& frame): +SampleThumbnailBit::SampleThumbnailBit(const SampleFrame& frame): maxRMS(-0.0), minRMS( 0.0) { - if (frame[0] >= frame[1]) - { - this->max = frame[0]; - this->min = frame[1]; - } - else + const float l = frame.left(); + const float r = frame.right(); + + if (l > r) + { + this->max = l; + this->min = r; + } + else { - this->max = frame[1]; - this->min = frame[0]; + this->max = r; + this->min = l; } } + SampleThumbnailBit::SampleThumbnailBit(): max(-100.0), min( 100.0), @@ -55,9 +57,6 @@ SampleThumbnailBit::SampleThumbnailBit(): minRMS( 0.0) {} -SampleThumbnailBit::~SampleThumbnailBit() -{} - void SampleThumbnailBit::merge(const SampleThumbnailBit& other) { this->min = std::min(this->min, other.min); @@ -70,77 +69,57 @@ void SampleThumbnailBit::merge(const SampleThumbnailBit& other) this->minRMS = std::clamp(this->minRMS, this->min, this->max); } -void SampleThumbnailBit::mergeFrame(const sampleFrame& frame) +void SampleThumbnailBit::mergeFrame(const SampleFrame& frame) { - const auto newLine = SampleThumbnailBit(frame); - - this->merge(newLine); -} - -SampleThumbnailBit SampleThumbnailBit::linear(const SampleThumbnailBit& other, float t) const -{ - float t2 = 1.0 - t; - - SampleThumbnailBit o; - - o.max = this->max * t2 + other.max * t; - o.min = this->min * t2 + other.min * t; - - o.maxRMS = this->maxRMS * t2 + other.maxRMS * t; - o.minRMS = this->minRMS * t2 + other.minRMS * t; - - return o; + const auto other = SampleThumbnailBit(frame); + this->min = std::min(this->min, other.min); + this->max = std::max(this->max, other.max); } SampleThumbnailListManager::SampleThumbnailListManager() { - this->list = std::make_shared< SampleThumbnailList >( SampleThumbnailList() ); + this->list = nullptr; } bool SampleThumbnailListManager::selectFromGlobalThumbnailMap( const Sample& inputSample ) { - const auto samplePtr = inputSample.buffer(); - const QString name = inputSample.sampleFile(); + const auto samplePtr = inputSample.buffer(); + const QString name = inputSample.sampleFile(); const auto end = SAMPLE_THUMBNAIL_MAP.end(); - auto list = SAMPLE_THUMBNAIL_MAP.begin(); - - while (list != end && list->first != name) list++; + auto list = SAMPLE_THUMBNAIL_MAP.find(name); - if (list == end) - { - this->list = std::make_shared< SampleThumbnailList >( SampleThumbnailList() ); - - qDebug("Generating thumbnails for file: %s", qUtf8Printable(name)); - - SAMPLE_THUMBNAIL_MAP.insert( - std::pair - ( - name, - this->list - ) - ); - - return false; - } - else + if (list != end) { - this->list = list->second; - + this->list = list->second; return true; } + + this->list = std::make_shared(SampleThumbnailList()); + + qDebug("Generating thumbnails for file: %s", qUtf8Printable(name)); + + SAMPLE_THUMBNAIL_MAP.insert( + std::pair + ( + name, + this->list + ) + ); + + return false; } void SampleThumbnailListManager::cleanUpGlobalThumbnailMap() { auto map = SAMPLE_THUMBNAIL_MAP.begin(); while (map != SAMPLE_THUMBNAIL_MAP.end()) - { + { // All instances of SampleThumbnailListManager are destroyed, // a.k.a sample goes out of use - if (map->second.use_count() == 1) + if (map->second.use_count() == 1) { qDebug("Deleting an orphaned thumbnaillist..."); SAMPLE_THUMBNAIL_MAP.erase(map); @@ -154,6 +133,49 @@ void SampleThumbnailListManager::cleanUpGlobalThumbnailMap() qDebug("Now holding %lu thumbnaillists", SAMPLE_THUMBNAIL_MAP.size()); } +SampleThumbnail SampleThumbnailListManager::generate( + const size_t thumbnailSize, + const SampleFrame* sampleBuffer, + const SampleFrame* sampleBufferEnd +) { + const size_t sampleSize = sampleBufferEnd - sampleBuffer; + + const size_t sampleChunk = (sampleSize + thumbnailSize) / thumbnailSize; + + SampleThumbnail thumbnail(thumbnailSize, SampleThumbnailBit()); + + for (size_t tIndex = 0; tIndex < thumbnailSize; tIndex++) + { + size_t sampleIndex = tIndex * sampleSize / thumbnailSize; + + const size_t sampleChunkBound = std::min( + sampleIndex + sampleChunk, + sampleSize + ); + + auto& bit = thumbnail[tIndex]; + + float rms = 0.0; + + while (sampleIndex < sampleChunkBound) + { + const auto& frame = sampleBuffer[sampleIndex]; + bit.mergeFrame(frame); + const float ave = frame.average(); + rms += ave * ave; + + sampleIndex++; + } + + rms = std::sqrt(rms / sampleChunk); + + bit.maxRMS = std::clamp( rms, bit.min, bit.max); + bit.minRMS = std::clamp(-rms, bit.min, bit.max); + } + + return thumbnail; +} + SampleThumbnailListManager::SampleThumbnailListManager(const Sample& inputSample) { if (selectFromGlobalThumbnailMap(inputSample)) { return; } @@ -163,9 +185,10 @@ SampleThumbnailListManager::SampleThumbnailListManager(const Sample& inputSample const auto sampleBufferSize = inputSample.sampleSize(); const auto& buffer = inputSample.data(); + // For very small samples, use the sample size. const size_t firstThumbnailSize = - std::max( - std::min(sampleBufferSize, 1), + std::min( + std::max(sampleBufferSize, 1), MAX_THUMBNAIL_SIZE ); @@ -174,60 +197,33 @@ SampleThumbnailListManager::SampleThumbnailListManager(const Sample& inputSample auto& thumbnaillist = *this->list; - SampleThumbnail firstThumbnail = std::vector(firstThumbnailSize, lmms::SampleThumbnailBit()); - - size_t sampleFrameIndex = 0; - - float rms = 0.0; - - // Prepare the first thumbnail with the largest size. - for (; sampleFrameIndex < sampleBufferSize; sampleFrameIndex++) - { - size_t sampleThumbnailIndex = - sampleFrameIndex * - firstThumbnailSize / - sampleBufferSize - ; - - const auto& frame = buffer[sampleFrameIndex]; - - auto& bit = firstThumbnail[sampleThumbnailIndex]; - - rms = rms*0.85 + std::abs(bit.max + bit.min)*0.1; - - bit.maxRMS = std::clamp( rms, bit.min, bit.max); - bit.minRMS = std::clamp(-rms, bit.min, bit.max); - - bit.mergeFrame(frame); - } + SampleThumbnail firstThumbnail = generate( + firstThumbnailSize, + buffer, + buffer + sampleBufferSize + ); thumbnaillist.push_back(firstThumbnail); - // Generate the remaining thumbnails using the first one, each one - // is half the size of the previous. + // Generate the remaining thumbnails using the first one, each one's + // size is the size of the previous one divided by THUMBNAIL_SIZE_DIVISOR for ( size_t thumbnailSize = firstThumbnailSize / THUMBNAIL_SIZE_DIVISOR; thumbnailSize >= MIN_THUMBNAIL_SIZE; thumbnailSize /= THUMBNAIL_SIZE_DIVISOR - ) - { + ) { const auto& biggerThumbnail = thumbnaillist.back(); const auto biggerThumbnailSize = biggerThumbnail.size(); size_t bitIndex = 0; SampleThumbnail thumbnail = std::vector(thumbnailSize, lmms::SampleThumbnailBit()); - qDebug("Size %lu", thumbnail.size()); + qDebug("Generating for size %lu", thumbnail.size()); for (const auto& biggerBit: biggerThumbnail) { auto& bit = thumbnail[bitIndex * thumbnailSize / biggerThumbnailSize]; - - //~ rms = rms*0.5 + std::abs(bit.max + bit.min)*0.5; - - //~ bit.maxRMS = std::clamp( rms, bit.min, bit.max); - //~ bit.minRMS = std::clamp(-rms, bit.min, bit.max); - + bit.merge(biggerBit); ++bitIndex; @@ -237,79 +233,182 @@ SampleThumbnailListManager::SampleThumbnailListManager(const Sample& inputSample } } -void SampleThumbnailListManager::visualize( - SampleThumbnailVisualizeParameters parameters, - QPainter& painter, - const QRect& rect +void SampleThumbnailListManager::draw( + QPainter& painter, const SampleThumbnailBit& bit, + int lineX, int centerY, float scalingFactor, + QColor color, QColor rmsColor ) { - const int x = rect.x(); - // Prevent unnecessary out of bounds drawing. - const int absXOr0 = (x < 0) ? ( -x ) : ( 0 ); + const int lengthY1 = bit.max * scalingFactor; + const int lengthY2 = bit.min * scalingFactor; - const int height = rect.height(); - const int width = rect.width(); - const int centerY = rect.center().y(); + const int lineY1 = centerY - lengthY1; + const int lineY2 = centerY - lengthY2; - const auto color = painter.pen().color(); - const auto rmsColor = color.lighter(123); - //qDebug("offset_end %d", parameters.offset_end); + const int rmsLineY1 = centerY - bit.maxRMS * scalingFactor; + const int rmsLineY2 = centerY - bit.minRMS * scalingFactor; + + painter.drawLine(lineX, lineY1, lineX, lineY2); + painter.setPen(rmsColor); + painter.drawLine(lineX, rmsLineY1, lineX, rmsLineY2); + painter.setPen(color); +} + + +void SampleThumbnailListManager::visualize( + const SampleThumbnailVisualizeParameters& parameters, + QPainter& painter +) const { + + const float sampleViewPercent = parameters.sampleEndPercent - parameters.sampleStartPercent; + + // We specify that the existence of the original sample + // means we may need the sample to be drawn + // with the highest quaility possible. + // + // For AFP and SlicerT where the sample isn't drawn a whole lot + // of times and waveform is required to be crisp. + // + // However when the sample is small enough, we can still use thumbnails. + if (parameters.originalSample) + { + const float sampleSize = static_cast(parameters.originalSample->sampleSize()); + const long sampleViewSize = static_cast(sampleSize * sampleViewPercent); - const int halfHeight = height / 2; + if (sampleViewSize / parameters.width < 882) + { + visualize_original(parameters, painter); + return; + } + } + + const long x = parameters.x; + // If the clip extends to the left past the start of the sample, + // start starting at the start of the sample and skip the blank space. + const long absXOr0 = (x < 0) ? -x : 0; + + const long height = parameters.height; + const long halfHeight = height / 2; + const long width = parameters.width; + const long centerY = parameters.y + halfHeight; + + const auto color = painter.pen().color(); + const auto rmsColor = color.lighter(123); + const float scalingFactor = halfHeight * parameters.amplification - // static_cast(parameters.numChannels) ; - long last = this->list->size()-1; - long setIndex = 0; - while (setIndex < last && (*this->list)[setIndex].size() > width) + auto list = this->list->end()-1; + const auto begin = this->list->begin(); + + const long widthSelect = static_cast(1.0f * width / sampleViewPercent); + //qDebug("%ld", widthSelect); + + while (list != begin && list->size() < widthSelect) { - setIndex++; + list--; } - const auto& thumbnail = (*this->list)[setIndex]; - - //~ qDebug("Using thumbnail of size: %lu", thumbnail.size()); + const auto& thumbnail = *list; - const float thumbnailSize = static_cast(thumbnail.size()); - const long tS = thumbnail.size()-1; - //~ const float xThumbnail = (x - parameters.offset_start) * thumbnailSize / width; + const long thumbnailSize = thumbnail.size(); + const long thumbnailLastSample = std::max(parameters.sampleEndPercent*thumbnailSize, 1) - 1; + + //qDebug("Using thumbnail of size %ld", thumbnailSize); - const long bound = std::min(width, parameters.pixelsTillSampleEnd); + const long tStart = static_cast(parameters.sampleStartPercent * thumbnailSize); + + const long thumbnailViewSize = thumbnailLastSample + 1 - tStart; - const float ratio = thumbnailSize / static_cast(width); + const long tLast = std::min(thumbnailLastSample, thumbnailSize-1); + long tIndex = 0; + + const long tChunk = (thumbnailSize + width) / width; - long tI = 0; // Don't draw out of bounds. long pixelIndex = absXOr0; - do + + do { - tI = static_cast(pixelIndex * ratio); + tIndex = tStart + pixelIndex * thumbnailViewSize / width; - const auto thumbnailBit = thumbnail[ - (parameters.reversed) ? ( tS - tI ) : ( tI ) - ]; + auto thumbnailBit = SampleThumbnailBit(); - const int lengthY1 = thumbnailBit.max * scalingFactor; - const int lengthY2 = thumbnailBit.min * scalingFactor; + const long tChunkBound = tIndex + tChunk; - const int lineY1 = centerY - lengthY1; - const int lineY2 = centerY - lengthY2; - const int lineX = pixelIndex + x; - painter.drawLine(lineX, lineY1, lineX, lineY2); + while (tIndex < tChunkBound) + { + thumbnailBit.merge(thumbnail[ + parameters.reversed ? tLast - tIndex : tIndex + ]); + tIndex += 1; + } - const int rmsLineY1 = centerY - thumbnailBit.maxRMS * scalingFactor; - const int rmsLineY2 = centerY - thumbnailBit.minRMS * scalingFactor; - - painter.setPen(rmsColor); - painter.drawLine(lineX, rmsLineY1, lineX, rmsLineY2); - painter.setPen(color); + draw( + painter, thumbnailBit, + pixelIndex + x, centerY, scalingFactor, + color, rmsColor + ); pixelIndex++; + } + while (pixelIndex <= width && tIndex < tLast); + +} + +// Method is made public to be easily accessed when +// one needs to quickly draw a sample without a thumbnail. +// +// As performant as the original SampleWaveform code. +void SampleThumbnailListManager::visualize_original( + const SampleThumbnailVisualizeParameters& parameters, + QPainter& painter +) { + const long x = parameters.x; + + // If the clip extends to the left past the start of the + // sample, start drawing at the start of the sample and + // skip the blank space. + const int absXOr0 = (x < 0) ? -x : 0; + + const long height = parameters.height; + const long halfHeight = height / 2; + const long width = parameters.width; + const long centerY = parameters.y + halfHeight; + + const float scalingFactor = + halfHeight * parameters.amplification + ; + + const auto color = painter.pen().color(); + const auto rmsColor = color.lighter(123); + + const auto originalSampleBuffer = parameters.originalSample->data(); + const long originalSampleSize = parameters.originalSample->sampleSize(); - } while (pixelIndex <= bound && tI < tS); + const long sampleStartFrame = static_cast(parameters.sampleStartPercent * originalSampleSize); + const long sampleEndFrame = std::min( + parameters.sampleEndPercent * originalSampleSize, + originalSampleSize + ); + const auto thumbnail = generate( + width, + originalSampleBuffer + sampleStartFrame, + originalSampleBuffer + sampleEndFrame + ); + + for (long pixelIndex = absXOr0; pixelIndex < width; pixelIndex++) + { + draw( + painter, + thumbnail[pixelIndex], + pixelIndex + x, centerY, scalingFactor, + color, rmsColor + ); + } } + } diff --git a/src/gui/clips/SampleClipView.cpp b/src/gui/clips/SampleClipView.cpp index a176b830795..aaf44acd1cc 100644 --- a/src/gui/clips/SampleClipView.cpp +++ b/src/gui/clips/SampleClipView.cpp @@ -35,7 +35,6 @@ #include "SampleClip.h" #include "SampleLoader.h" #include "SampleThumbnail.h" -#include "SampleWaveform.h" #include "Song.h" #include "StringPairDrag.h" @@ -46,7 +45,7 @@ namespace lmms::gui SampleClipView::SampleClipView( SampleClip * _clip, TrackView * _tv ) : ClipView( _clip, _tv ), m_clip( _clip ), - thumbnaillist( SampleThumbnailListManager() ), + m_thumbnaillist( SampleThumbnailListManager() ), m_paintPixmap() { // update UI and tooltip @@ -64,7 +63,7 @@ void SampleClipView::updateSample() { update(); - thumbnaillist = SampleThumbnailListManager(m_clip->m_sample); + m_thumbnaillist = SampleThumbnailListManager(m_clip->m_sample); // set tooltip to filename so that user can see what sample this // sample-clip contains @@ -276,13 +275,6 @@ void SampleClipView::paintEvent( QPaintEvent * pe ) float offset_start = m_clip->startTimeOffset() / ticksPerBar * pixelsPerBar(); float offset_end = m_clip->sampleLength() * ppb / ticksPerBar; float length = m_clip->length() * ppb / ticksPerBar; - QRect r = QRect( - offset_start, - spacing, - qMax( static_cast( offset_end ), 1 ), - rect().bottom() - 2 * spacing - ); - // qDebug("%d %d", (int) offset_start, (int) length); const auto& sample = m_clip->m_sample; @@ -295,11 +287,14 @@ void SampleClipView::paintEvent( QPaintEvent * pe ) const auto parameters = SampleThumbnailVisualizeParameters{ .amplification = sample.amplification(), .reversed = sample.reversed(), - .pixelsTillSampleEnd = length - offset_start + + .x = static_cast(offset_start), + .y = spacing, + .width = std::max(static_cast(offset_end), 1), + .height = rect().bottom() - 2 * spacing }; - - thumbnaillist.visualize(parameters, p, r); + m_thumbnaillist.visualize(parameters, p); QString name = PathUtil::cleanName(m_clip->m_sample.sampleFile()); paintTextLabel(name, p); diff --git a/src/gui/editors/AutomationEditor.cpp b/src/gui/editors/AutomationEditor.cpp index a7ca0727972..9d909fb6ded 100644 --- a/src/gui/editors/AutomationEditor.cpp +++ b/src/gui/editors/AutomationEditor.cpp @@ -39,7 +39,7 @@ #include #include "SampleClip.h" -#include "SampleWaveform.h" +#include "SampleThumbnail.h" #ifndef __USE_XOPEN #define __USE_XOPEN @@ -107,7 +107,8 @@ AutomationEditor::AutomationEditor() : m_scaleColor(Qt::SolidPattern), m_crossColor(0, 0, 0), m_backgroundShade(0, 0, 0), - m_ghostNoteColor(0, 0, 0) + m_ghostNoteColor(0, 0, 0), + m_thumbnaillist(SampleThumbnailListManager()) { connect( this, SIGNAL(currentClipChanged()), this, SLOT(updateAfterClipChange()), @@ -1025,6 +1026,7 @@ void AutomationEditor::setGhostSample(SampleClip* newGhostSample) // Expects a pointer to a Sample buffer or nullptr. m_ghostSample = newGhostSample; m_renderSample = true; + m_thumbnaillist = SampleThumbnailListManager(newGhostSample->sample()); } void AutomationEditor::paintEvent(QPaintEvent * pe ) @@ -1219,10 +1221,22 @@ void AutomationEditor::paintEvent(QPaintEvent * pe ) p.setPen(m_ghostSampleColor); const auto& sample = m_ghostSample->sample(); - const auto waveform = SampleWaveform::Parameters{ - sample.data(), sample.sampleSize(), sample.amplification(), sample.reversed()}; - const auto rect = QRect(startPos, yOffset, sampleWidth, sampleHeight); - SampleWaveform::visualize(waveform, p, rect); + //const auto waveform = SampleWaveform::Parameters{ + //sample.data(), sample.sampleSize(), sample.amplification(), sample.reversed()}; + //const auto rect = QRect(startPos, yOffset, sampleWidth, sampleHeight); + //SampleWaveform::visualize(waveform, p, rect); + + const auto parameters = SampleThumbnailVisualizeParameters{ + .amplification = sample.amplification(), + .reversed = sample.reversed(), + + .x = startPos, + .y = yOffset, + .width = sampleWidth, + .height = sampleHeight + }; + + m_thumbnaillist.visualize(parameters, p); } // draw ghost notes