diff --git a/src/context/applets/analyzer/plugin/AnalyzerBase.cpp b/src/context/applets/analyzer/plugin/AnalyzerBase.cpp index 4d13ce55ce..6050cd118c 100644 --- a/src/context/applets/analyzer/plugin/AnalyzerBase.cpp +++ b/src/context/applets/analyzer/plugin/AnalyzerBase.cpp @@ -1,239 +1,243 @@ /**************************************************************************************** * Copyright (c) 2003 Max Howell * * Copyright (c) 2009 Martin Sandsmark * * Copyright (c) 2013 Mark Kretschmann * * Copyright (c) 2017 Malte Veerman * * * * 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. If not, see . * ****************************************************************************************/ #include "AnalyzerBase.h" #include "AnalyzerWorker.h" #include "core/support/Amarok.h" #include "core/support/Debug.h" #include "EngineController.h" #include "MainWindow.h" #include #ifdef Q_WS_X11 #include #endif #include #include // INSTRUCTIONS // 1. Reimplement QQuickFramebufferObject::createRenderer(). // 2. Reimplement Analyzer::Base::createWorker(). // 3. Set your preferred scope width with setScopeSize(). Analyzer::Base::Base( QQuickItem *parent ) : QQuickFramebufferObject( parent ) , m_sampleRate( 44100 ) , m_scopeSize( 0 ) , m_worker( Q_NULLPTR ) { DEBUG_BLOCK qRegisterMetaType("WindowFunction"); m_minFreq = config().readEntry( "minFreq", 50.0 ); m_maxFreq = config().readEntry( "maxFreq", 15000.0 ); connect( The::engineController(), &EngineController::trackChanged, this, &Base::refreshSampleRate ); connect( The::engineController(), &EngineController::trackMetadataChanged, this, &Base::refreshSampleRate ); #ifdef Q_WS_X11 connect( KWindowSystem::self(), &KWindowSystem::currentDesktopChanged, this, &Base::currentDesktopChanged ); #endif QTimer::singleShot( 0, this, &Base::connectSignals ); } Analyzer::Base::~Base() { DEBUG_BLOCK - m_worker->deleteLater(); - m_worker = Q_NULLPTR; + if( m_worker ) + { + m_worker->deleteLater(); + m_worker = nullptr; + } + m_workerThread.quit(); m_workerThread.wait(); } void Analyzer::Base::connectSignals() { DEBUG_BLOCK if( !m_worker ) { m_worker = createWorker(); m_worker->setSampleSize( sampleSize() ); m_worker->setScopeSize( m_scopeSize ); m_worker->setWindowFunction( windowFunction() ); m_worker->moveToThread( &m_workerThread ); m_workerThread.start(); connect( this, &Base::calculateExpFactorNeeded, m_worker, &Worker::calculateExpFactor ); connect( this, &Base::windowFunctionChanged, m_worker, &Worker::setWindowFunction ); connect( this, &Base::sampleSizeChanged, m_worker, &Worker::setSampleSize ); connect( this, &Base::scopeSizeChanged, m_worker, &Worker::setScopeSize ); connect( The::engineController(), &EngineController::playbackStateChanged, m_worker, &Worker::playbackStateChanged ); connect( The::engineController(), &EngineController::audioDataReady, m_worker, &Worker::receiveData, Qt::DirectConnection ); setSampleSize( config().readEntry( "sampleSize", 4096 ) ); setWindowFunction( (WindowFunction) config().readEntry( "windowFunction", (int)Hann ) ); emit calculateExpFactorNeeded( m_minFreq, m_maxFreq, m_sampleRate); } } void Analyzer::Base::disconnectSignals() { DEBUG_BLOCK if( m_worker ) disconnect( The::engineController(), &EngineController::audioDataReady, m_worker, &Worker::receiveData ); } void Analyzer::Base::currentDesktopChanged() { // Optimization for X11/Linux desktops: // Don't update the analyzer if Amarok is not on the active virtual desktop. if( The::mainWindow()->isOnCurrentDesktop() ) connectSignals(); else disconnectSignals(); } void Analyzer::Base::refreshSampleRate() { const auto currentTrack = The::engineController()->currentTrack(); int sampleRate = currentTrack ? currentTrack->sampleRate() : 44100; if( m_sampleRate == sampleRate ) return; m_sampleRate = sampleRate; emit calculateExpFactorNeeded( m_minFreq, m_maxFreq, m_sampleRate ); } KConfigGroup Analyzer::Base::config() const { return Amarok::config( QStringLiteral( "Context" ) ).group( "Analyzer" ); } void Analyzer::Base::setScopeSize( int scopeSize ) { if( scopeSize <= 0 ) { debug() << "Scope size must be greater than zero"; return; } if( m_scopeSize == scopeSize ) return; m_scopeSize = scopeSize; emit scopeSizeChanged( scopeSize ); emit calculateExpFactorNeeded( m_minFreq, m_maxFreq, m_sampleRate ); } void Analyzer::Base::setMaxFreq( qreal maxFreq ) { DEBUG_BLOCK debug() << "Set maximum frequency to:" << maxFreq; if( m_maxFreq == maxFreq || maxFreq < 0.0 ) return; config().writeEntry( "maxFreq", maxFreq ); m_maxFreq = maxFreq; emit maxFreqChanged(); emit calculateExpFactorNeeded( m_minFreq, m_maxFreq, m_sampleRate ); } void Analyzer::Base::setMinFreq( qreal minFreq ) { DEBUG_BLOCK debug() << "Set minimum frequency to:" << minFreq; if( m_minFreq == minFreq || minFreq <= 0.0 ) return; config().writeEntry( "minFreq", minFreq ); m_minFreq = minFreq; emit minFreqChanged(); emit calculateExpFactorNeeded( m_minFreq, m_maxFreq, m_sampleRate ); } Analyzer::Base::WindowFunction Analyzer::Base::windowFunction() const { return (WindowFunction)config().readEntry( "windowFunction", (int)Hann ); } void Analyzer::Base::setWindowFunction( WindowFunction windowFunction ) { DEBUG_BLOCK debug() << "Set window function to:" << windowFunction; config().writeEntry( "windowFunction", (int)windowFunction ); emit windowFunctionChanged( windowFunction ); } int Analyzer::Base::sampleSize() const { return config().readEntry( "sampleSize", 2048 ); } void Analyzer::Base::setSampleSize( uint sampleSize ) { DEBUG_BLOCK debug() << "Set sample size to:" << sampleSize; if( sampleSize < (int) EngineController::DATAOUTPUT_DATA_SIZE ) { warning() << "Sample size must be at least" << EngineController::DATAOUTPUT_DATA_SIZE; sampleSize = EngineController::DATAOUTPUT_DATA_SIZE; } config().writeEntry( "sampleSize", sampleSize ); emit sampleSizeChanged( sampleSize ); emit calculateExpFactorNeeded( m_minFreq, m_maxFreq, m_sampleRate ); } const Analyzer::Worker * Analyzer::Base::worker() const { return const_cast( m_worker ); } diff --git a/src/context/applets/analyzer/plugin/BlockAnalyzer.cpp b/src/context/applets/analyzer/plugin/BlockAnalyzer.cpp index aaf3732f1c..74ed6efe16 100644 --- a/src/context/applets/analyzer/plugin/BlockAnalyzer.cpp +++ b/src/context/applets/analyzer/plugin/BlockAnalyzer.cpp @@ -1,236 +1,236 @@ /**************************************************************************************** * Copyright (c) 2003-2005 Max Howell * * Copyright (c) 2005-2013 Mark Kretschmann * * * * 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 Pulic License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #include "BlockAnalyzer.h" #include "AnalyzerWorker.h" #include "BlockRenderer.h" #include "BlockWorker.h" - #include "PaletteHandler.h" +#include "core/support/Debug.h" #include #include #include #include #include BlockAnalyzer::BlockAnalyzer( QQuickItem *parent ) : Analyzer::Base( parent ) , m_columns( 0 ) //int , m_rows( 0 ) //int , m_fadeBarsPixmaps( FADE_SIZE ) //vector { setTextureFollowsItemSize( true ); setObjectName( "Blocky" ); m_columnWidth = config().readEntry( "columnWidth", 4 ); m_fallSpeed = (FallSpeed) config().readEntry( "fallSpeed", (int) Medium ); m_showFadebars = config().readEntry( "showFadebars", true ); paletteChange( The::paletteHandler()->palette() ); connect( The::paletteHandler(), &PaletteHandler::newPalette, this, &BlockAnalyzer::paletteChange ); connect( this, &QQuickItem::windowChanged, this, &BlockAnalyzer::newWindow ); } QQuickFramebufferObject::Renderer* BlockAnalyzer::createRenderer() const { return new BlockRenderer; } Analyzer::Worker * BlockAnalyzer::createWorker() const { auto worker = new BlockWorker( m_rows, m_columns, m_step, m_showFadebars ); if( window() ) worker->setRefreshRate( window()->screen()->refreshRate() ); connect( worker, &BlockWorker::finished, this, &QQuickFramebufferObject::update, Qt::QueuedConnection ); connect( this, &BlockAnalyzer::stepChanged, worker, &BlockWorker::setStep, Qt::QueuedConnection ); connect( this, &BlockAnalyzer::rowsChanged, worker, &BlockWorker::setRows, Qt::QueuedConnection ); connect( this, &BlockAnalyzer::columnsChanged, worker, &BlockWorker::setColumns, Qt::QueuedConnection ); connect( this, &BlockAnalyzer::refreshRateChanged, worker, &BlockWorker::setRefreshRate, Qt::QueuedConnection ); connect( this, &BlockAnalyzer::showFadebarsChanged, worker, &BlockWorker::setShowFadebars, Qt::QueuedConnection ); return worker; } void BlockAnalyzer::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) { QQuickFramebufferObject::geometryChanged( newGeometry, oldGeometry ); if( !newGeometry.isValid() ) return; const int oldRows = m_rows; // Rounded up so that the last column/line is covered if partially visible m_columns = std::lround( std::ceil( (double)newGeometry.width() / ( m_columnWidth + 1 ) ) ); emit columnsChanged( m_columns ); m_rows = std::ceil( (double)newGeometry.height() / ( BLOCK_HEIGHT + 1 ) ); emit rowsChanged( m_rows ); setScopeSize( m_columns ); if( m_rows != oldRows ) { m_barPixmap = QPixmap( m_columnWidth, m_rows * ( BLOCK_HEIGHT + 1 ) ); determineStep(); paletteChange( The::paletteHandler()->palette() ); } else drawBackground( The::paletteHandler()->palette() ); } void BlockAnalyzer::determineStep() { // falltime is dependent on rowcount due to our digital resolution (ie we have boxes/blocks of pixels) const qreal fallTime = 1.0 / pow( 1.5, m_fallSpeed ); // time to fall from top to bottom m_step = qreal( m_rows ) / fallTime; // the amount of rows to fall per second emit stepChanged( m_step ); } void BlockAnalyzer::paletteChange( const QPalette& palette ) //virtual { const QColor bg = palette.color( QPalette::Active, QPalette::Base ); const QColor abg = palette.color( QPalette::Active, QPalette::AlternateBase ); const QColor highlight = palette.color( QPalette::Active, QPalette::Highlight ); m_topBarPixmap = QPixmap( m_columnWidth, BLOCK_HEIGHT ); m_topBarPixmap.fill( highlight ); m_barPixmap.fill( QColor( ( highlight.red() + bg.red() ) / 2, ( highlight.green() + bg.green() ) / 2, ( highlight.blue() + bg.blue() ) / 2 ) ); QPainter p( &m_barPixmap ); int h, s, v; palette.color( QPalette::Active, QPalette::Dark ).getHsv( &h, &s, &v ); const QColor fade = QColor::fromHsv( h + 30, s, v ); const double dr = fade.red() - abg.red(); const double dg = fade.green() - abg.green(); const double db = fade.blue() - abg.blue(); const int r = abg.red(), g = abg.green(), b = abg.blue(); // Precalculate all fade-bar pixmaps for( int y = 0; y < FADE_SIZE; ++y ) { m_fadeBarsPixmaps[y] = QPixmap( m_columnWidth, m_rows * ( BLOCK_HEIGHT + 1 ) ); m_fadeBarsPixmaps[y].fill( palette.color( QPalette::Active, QPalette::Base ) ); const double Y = 1.0 - ( log10( ( FADE_SIZE ) - y ) / log10( ( FADE_SIZE ) ) ); QPainter f( &m_fadeBarsPixmaps[y] ); for( int z = 0; z < m_rows; ++z ) f.fillRect( 0, z * ( BLOCK_HEIGHT + 1 ), m_columnWidth, BLOCK_HEIGHT, QColor( r + int( dr * Y ), g + int( dg * Y ), b + int( db * Y ) ) ); } m_pixmapsChanged = true; drawBackground( palette ); } void BlockAnalyzer::drawBackground( const QPalette &palette ) { const QColor bg = palette.color( QPalette::Active, QPalette::Base ); const QColor abg = palette.color( QPalette::Active, QPalette::AlternateBase ); // background gets stretched if it is too big m_backgroundPixmap = QPixmap( width(), height() ); m_backgroundPixmap.fill( bg ); QPainter p( &m_backgroundPixmap ); for( int x = 0; x < m_columns; ++x ) for( int y = 0; y < m_rows; ++y ) p.fillRect( x * ( m_columnWidth + 1 ), y * ( BLOCK_HEIGHT + 1 ), m_columnWidth, BLOCK_HEIGHT, abg ); m_pixmapsChanged = true; update(); } void BlockAnalyzer::setFallSpeed( FallSpeed fallSpeed ) { DEBUG_BLOCK debug() << "Fall speed set to:" << fallSpeed; if( m_fallSpeed == fallSpeed ) return; m_fallSpeed = fallSpeed; config().writeEntry( "fallSpeed", (int) m_fallSpeed ); emit fallSpeedChanged(); determineStep(); } void BlockAnalyzer::setColumnWidth( int columnWidth ) { DEBUG_BLOCK debug() << "Column width set to:" << columnWidth; if( columnWidth < 1 ) { warning() << "Column width can not be smaller than one!"; columnWidth = 1; } if( m_columnWidth == columnWidth ) return; m_columnWidth = columnWidth; config().writeEntry( "columnWidth", m_columnWidth ); emit columnWidthChanged(); m_columns = std::lround( std::ceil( (double)width() / ( m_columnWidth + 1 ) ) ); emit columnsChanged( m_columns ); setScopeSize( m_columns ); m_barPixmap = QPixmap( m_columnWidth, m_rows * ( BLOCK_HEIGHT + 1 ) ); paletteChange( The::paletteHandler()->palette() ); } void BlockAnalyzer::setShowFadebars( bool showFadebars ) { DEBUG_BLOCK debug() << "Show fadebars:" << showFadebars; if( m_showFadebars == showFadebars ) return; m_showFadebars = showFadebars; emit showFadebarsChanged( m_showFadebars ); } void BlockAnalyzer::newWindow( QQuickWindow* window ) { if( window ) emit refreshRateChanged( window->screen()->refreshRate() ); } diff --git a/src/context/applets/analyzer/plugin/BlockRenderer.h b/src/context/applets/analyzer/plugin/BlockRenderer.h index ad25393a71..9e37aca2ab 100644 --- a/src/context/applets/analyzer/plugin/BlockRenderer.h +++ b/src/context/applets/analyzer/plugin/BlockRenderer.h @@ -1,127 +1,129 @@ /* * Copyright (c) 2003-2005 Max Howell * Copyright (c) 2005-2013 Mark Kretschmann * Copyright 2017 Malte Veerman * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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. If not, see . * */ #ifndef BLOCKRENDERER_H #define BLOCKRENDERER_H #include "BlockAnalyzer.h" #include "BlockWorker.h" -#include "core/support/Debug.h" #include #include #include +#include #include -#include -#include + class BlockRenderer : public QQuickFramebufferObject::Renderer { public: static const int BLOCK_HEIGHT = BlockAnalyzer::BLOCK_HEIGHT; - BlockRenderer() {} + BlockRenderer() : m_worker( nullptr ) {} protected: QOpenGLFramebufferObject* createFramebufferObject(const QSize &size) override { QOpenGLFramebufferObject* fo = new QOpenGLFramebufferObject(size); fo->setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil); return fo; } void render() override { + // Synchronize worker data + if (!m_worker) + return; + + m_worker->m_mutex.lock(); + const QVector store = m_worker->m_store; + const QVector > fadebars = m_worker->m_fadebars; + m_worker->m_mutex.unlock(); + QOpenGLPaintDevice d; d.setSize(framebufferObject()->size()); QPainter p(&d); // Draw the background p.drawPixmap(QRect(QPoint(0, 0), framebufferObject()->size()), m_backgroundPixmap); - for( uint x = 0; x < (uint)m_store.size(); ++x ) + for(uint x = 0; x < (uint)store.size(); ++x) { // Draw fade bars - for( const auto &fadebar : m_fadebars.at(x) ) + for(const auto &fadebar : qAsConst(fadebars.at(x))) { - if( fadebar.intensity > 0 ) + if(fadebar.intensity > 0) { const uint offset = fadebar.intensity; const int fadeHeight = fadebar.y * (BLOCK_HEIGHT + 1); - if( fadeHeight > 0 ) - p.drawPixmap(x * ( m_columnWidth + 1 ), 0, m_fadeBarsPixmaps.value(offset), 0, 0, m_columnWidth, fadeHeight); + if(fadeHeight > 0) + p.drawPixmap(x * (m_columnWidth + 1), 0, m_fadeBarsPixmaps.value(offset), 0, 0, m_columnWidth, fadeHeight); } } // Draw bars - const int height = m_store.at(x) * (BLOCK_HEIGHT + 1); + const int height = store.at(x) * (BLOCK_HEIGHT + 1); if (height > 0) p.drawPixmap(x * (m_columnWidth + 1), 0, m_barPixmap, 0, 0, m_columnWidth, height); // Draw top bar p.drawPixmap(x * (m_columnWidth + 1), height + BLOCK_HEIGHT - 1, m_topBarPixmap); } } void synchronize(QQuickFramebufferObject *item) override { auto analyzer = qobject_cast(item); if (!analyzer) return; m_rows = analyzer->m_rows; m_columnWidth = analyzer->m_columnWidth; - auto worker = qobject_cast(analyzer->worker()); - if (worker) - { - worker->m_mutex.lock(); - m_store = worker->m_store; - m_fadebars = worker->m_fadebars; - worker->m_mutex.unlock(); - } + if (!m_worker) + m_worker = qobject_cast(analyzer->worker()); if (analyzer->m_pixmapsChanged) { m_barPixmap = analyzer->m_barPixmap; m_topBarPixmap = analyzer->m_topBarPixmap; m_backgroundPixmap = analyzer->m_backgroundPixmap; m_fadeBarsPixmaps = analyzer->m_fadeBarsPixmaps; analyzer->m_pixmapsChanged = false; } } private: - QVector m_store; - QVector > m_fadebars; + QPointer m_worker; + int m_rows; int m_columnWidth; QPixmap m_barPixmap; QPixmap m_topBarPixmap; QPixmap m_backgroundPixmap; QVector m_fadeBarsPixmaps; }; #endif //BLOCKRENDERER_H diff --git a/src/context/applets/analyzer/plugin/BlockWorker.cpp b/src/context/applets/analyzer/plugin/BlockWorker.cpp index d8523ea3db..bcc3323848 100644 --- a/src/context/applets/analyzer/plugin/BlockWorker.cpp +++ b/src/context/applets/analyzer/plugin/BlockWorker.cpp @@ -1,129 +1,129 @@ /* * Copyright 2017 Malte Veerman * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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. If not, see . * */ #include "BlockWorker.h" #include "BlockAnalyzer.h" #include "core/support/Debug.h" BlockWorker::BlockWorker( int rows, int columns, qreal step, bool showFadebars ) : m_step( step ) , m_rows( rows ) , m_columns( columns ) , m_refreshTime( 16 ) , m_showFadebars( showFadebars ) { m_yscale.resize( m_rows ); const double PRO = 1; //PRO allows us to restrict the range somewhat for( int z = 0; z < m_rows; ++z ) m_yscale[z] = 1 - log10( m_rows - z ) / log10( m_rows + PRO ); m_store.resize( columns ); m_fadebars.resize( columns ); m_lastUpdate.start(); } void BlockWorker::setRows( int rows ) { if( m_rows == rows ) return; m_mutex.lock(); m_rows = rows; m_yscale.resize( m_rows + 1 ); const double PRO = 1; //PRO allows us to restrict the range somewhat for( int z = 0; z < m_rows; ++z ) m_yscale[z] = 1 - log10( m_rows - z ) / log10( m_rows + PRO ); m_mutex.unlock(); } -void BlockWorker::setColumns(int columns) +void BlockWorker::setColumns( int columns ) { if( m_columns == columns ) return; m_columns = columns; } void BlockWorker::analyze() { int timeElapsed = m_lastUpdate.elapsed(); // only analyze if screen is fast enough if( timeElapsed < m_refreshTime - 1 ) QThread::currentThread()->msleep( m_refreshTime - timeElapsed - 1 ); const auto scopeData = scope(); const int scopeSize = scopeData.size(); timeElapsed = m_lastUpdate.restart(); const qreal step = m_step * timeElapsed / 1000.0; const qreal fadeStep = (qreal)timeElapsed / 20.0; - // block m_store and m_fadebars - QMutexLocker locker(&m_mutex); + // lock m_store and m_fadebars + QMutexLocker locker( &m_mutex ); m_store.resize( scopeSize ); m_fadebars.resize( scopeSize ); for( int x = 0; x < scopeSize; ++x ) { // determine y int y = 0; while( y < m_yscale.size() && scopeData.at(x) > m_yscale.at(y) ) y++; auto &fadebars = m_fadebars[x]; auto &store = m_store[x]; // remove obscured fadebars - while( !fadebars.isEmpty() && fadebars.last().y >= y ) + while( !fadebars.isEmpty() && fadebars.last().y <= y ) fadebars.removeLast(); // remove completely faded fadebars while( !fadebars.isEmpty() && fadebars.first().intensity <= fadeStep ) fadebars.removeFirst(); // fade the rest for( auto &fadebar : fadebars ) fadebar.intensity -= fadeStep; if( ( double )y < store ) { // add new fadebar at old column height if( m_showFadebars ) fadebars << Fadebar( store, BlockAnalyzer::FADE_SIZE ); store = qMax( store - step, double( y ) ); } else store = y; } emit finished(); }