File indexing completed on 2025-01-05 04:25:40

0001 /****************************************************************************************
0002  * Copyright (c) 2003 Max Howell <max.howell@methylblue.com>                            *
0003  * Copyright (c) 2009 Martin Sandsmark <martin.sandsmark@kde.org>                       *
0004  * Copyright (c) 2013 Mark Kretschmann <kretschmann@kde.org>                            *
0005  * Copyright (c) 2017 Malte Veerman <malte.veerman@gmail.com>                           *
0006  *                                                                                      *
0007  * This program is free software; you can redistribute it and/or modify it under        *
0008  * the terms of the GNU General Public License as published by the Free Software        *
0009  * Foundation; either version 2 of the License, or (at your option) any later           *
0010  * version.                                                                             *
0011  *                                                                                      *
0012  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
0013  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
0014  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
0015  *                                                                                      *
0016  * You should have received a copy of the GNU General Public License along with         *
0017  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
0018  ****************************************************************************************/
0019 
0020 #include "AnalyzerBase.h"
0021 #include "AnalyzerWorker.h"
0022 
0023 #include "core/support/Amarok.h"
0024 #include "core/support/Debug.h"
0025 #include "EngineController.h"
0026 #include "MainWindow.h"
0027 
0028 #include <cmath>
0029 
0030 #ifdef Q_WS_X11
0031 #include <KWindowSystem>
0032 #endif
0033 
0034 #include <QQuickWindow>
0035 #include <QTimer>
0036 
0037 
0038 // INSTRUCTIONS
0039 // 1. Reimplement QQuickFramebufferObject::createRenderer().
0040 // 2. Reimplement Analyzer::Base::createWorker().
0041 // 3. Set your preferred scope width with setScopeSize().
0042 
0043 
0044 Analyzer::Base::Base( QQuickItem *parent )
0045     : QQuickFramebufferObject( parent )
0046     , m_sampleRate( 44100 )
0047     , m_scopeSize( 0 )
0048     , m_worker( nullptr )
0049 {
0050     DEBUG_BLOCK
0051 
0052     qRegisterMetaType<WindowFunction>("WindowFunction");
0053 
0054     m_minFreq = config().readEntry( "minFreq", 50.0 );
0055     m_maxFreq = config().readEntry( "maxFreq", 15000.0 );
0056 
0057     connect( The::engineController(), &EngineController::trackChanged, this, &Base::refreshSampleRate );
0058     connect( The::engineController(), &EngineController::trackMetadataChanged, this, &Base::refreshSampleRate );
0059 
0060 #ifdef Q_WS_X11
0061     connect( KWindowSystem::self(), &KWindowSystem::currentDesktopChanged, this, &Base::currentDesktopChanged );
0062 #endif
0063 
0064     QTimer::singleShot( 0, this, &Base::connectSignals );
0065 }
0066 
0067 Analyzer::Base::~Base()
0068 {
0069     DEBUG_BLOCK
0070 
0071     if( m_worker )
0072     {
0073         m_worker->deleteLater();
0074         m_worker = nullptr;
0075     }
0076 
0077     m_workerThread.quit();
0078     m_workerThread.wait();
0079 }
0080 
0081 void
0082 Analyzer::Base::connectSignals()
0083 {
0084     DEBUG_BLOCK
0085 
0086     if( !m_worker )
0087     {
0088         m_worker = createWorker();
0089         m_worker->setSampleSize( sampleSize() );
0090         m_worker->setScopeSize( m_scopeSize );
0091         m_worker->setWindowFunction( windowFunction() );
0092         m_worker->moveToThread( &m_workerThread );
0093         m_workerThread.start();
0094 
0095         connect( this, &Base::calculateExpFactorNeeded, m_worker, &Worker::calculateExpFactor );
0096         connect( this, &Base::windowFunctionChanged, m_worker, &Worker::setWindowFunction );
0097         connect( this, &Base::sampleSizeChanged, m_worker, &Worker::setSampleSize );
0098         connect( this, &Base::scopeSizeChanged, m_worker, &Worker::setScopeSize );
0099         connect( The::engineController(), &EngineController::playbackStateChanged, m_worker, &Worker::playbackStateChanged );
0100         connect( The::engineController(), &EngineController::audioDataReady, m_worker, &Worker::receiveData, Qt::DirectConnection );
0101 
0102         setSampleSize( config().readEntry( "sampleSize", 4096 ) );
0103         setWindowFunction( (WindowFunction) config().readEntry( "windowFunction", (int)Hann ) );
0104         Q_EMIT calculateExpFactorNeeded( m_minFreq, m_maxFreq, m_sampleRate);
0105     }
0106 }
0107 
0108 void
0109 Analyzer::Base::disconnectSignals()
0110 {
0111     DEBUG_BLOCK
0112 
0113     if( m_worker )
0114         disconnect( The::engineController(), &EngineController::audioDataReady, m_worker, &Worker::receiveData );
0115 }
0116 
0117 void
0118 Analyzer::Base::currentDesktopChanged()
0119 {
0120     // Optimization for X11/Linux desktops:
0121     // Don't update the analyzer if Amarok is not on the active virtual desktop.
0122 
0123     if( The::mainWindow()->isOnCurrentDesktop() )
0124         connectSignals();
0125     else
0126         disconnectSignals();
0127 }
0128 
0129 void
0130 Analyzer::Base::refreshSampleRate()
0131 {
0132     const auto currentTrack = The::engineController()->currentTrack();
0133     int sampleRate = currentTrack ? currentTrack->sampleRate() : 44100;
0134 
0135     if( m_sampleRate == sampleRate )
0136         return;
0137 
0138     m_sampleRate = sampleRate;
0139 
0140     Q_EMIT calculateExpFactorNeeded( m_minFreq, m_maxFreq, m_sampleRate );
0141 }
0142 
0143 KConfigGroup
0144 Analyzer::Base::config() const
0145 {
0146     return Amarok::config( QStringLiteral( "Context" ) ).group( "Analyzer" );
0147 }
0148 
0149 void
0150 Analyzer::Base::setScopeSize( int scopeSize )
0151 {
0152     if( scopeSize <= 0 )
0153     {
0154         debug() << "Scope size must be greater than zero";
0155         return;
0156     }
0157 
0158     if( m_scopeSize == scopeSize )
0159         return;
0160 
0161     m_scopeSize = scopeSize;
0162     Q_EMIT scopeSizeChanged( scopeSize );
0163     Q_EMIT calculateExpFactorNeeded( m_minFreq, m_maxFreq, m_sampleRate );
0164 }
0165 
0166 void
0167 Analyzer::Base::setMaxFreq( qreal maxFreq )
0168 {
0169     DEBUG_BLOCK
0170 
0171     debug() << "Set maximum frequency to:" << maxFreq;
0172 
0173     if( m_maxFreq == maxFreq || maxFreq < 0.0 )
0174         return;
0175 
0176     config().writeEntry( "maxFreq", maxFreq );
0177     m_maxFreq = maxFreq;
0178     Q_EMIT maxFreqChanged();
0179     Q_EMIT calculateExpFactorNeeded( m_minFreq, m_maxFreq, m_sampleRate );
0180 }
0181 
0182 void
0183 Analyzer::Base::setMinFreq( qreal minFreq )
0184 {
0185     DEBUG_BLOCK
0186 
0187     debug() << "Set minimum frequency to:" << minFreq;
0188 
0189     if( m_minFreq == minFreq || minFreq <= 0.0 )
0190         return;
0191 
0192     config().writeEntry( "minFreq", minFreq );
0193     m_minFreq = minFreq;
0194     Q_EMIT minFreqChanged();
0195     Q_EMIT calculateExpFactorNeeded( m_minFreq, m_maxFreq, m_sampleRate );
0196 }
0197 
0198 Analyzer::Base::WindowFunction
0199 Analyzer::Base::windowFunction() const
0200 {
0201     return (WindowFunction)config().readEntry( "windowFunction", (int)Hann );
0202 }
0203 
0204 void
0205 Analyzer::Base::setWindowFunction( WindowFunction windowFunction )
0206 {
0207     DEBUG_BLOCK
0208 
0209     debug() << "Set window function to:" << windowFunction;
0210 
0211     config().writeEntry( "windowFunction", (int)windowFunction );
0212     Q_EMIT windowFunctionChanged( windowFunction );
0213 }
0214 
0215 int Analyzer::Base::sampleSize() const
0216 {
0217     return config().readEntry( "sampleSize", 2048 );
0218 }
0219 
0220 void
0221 Analyzer::Base::setSampleSize( uint sampleSize )
0222 {
0223     DEBUG_BLOCK
0224 
0225     debug() << "Set sample size to:" << sampleSize;
0226 
0227     if( sampleSize < (int) EngineController::DATAOUTPUT_DATA_SIZE )
0228     {
0229         warning() << "Sample size must be at least" << EngineController::DATAOUTPUT_DATA_SIZE;
0230         sampleSize = EngineController::DATAOUTPUT_DATA_SIZE;
0231     }
0232 
0233     config().writeEntry( "sampleSize", sampleSize );
0234     Q_EMIT sampleSizeChanged( sampleSize );
0235     Q_EMIT calculateExpFactorNeeded( m_minFreq, m_maxFreq, m_sampleRate );
0236 }
0237 
0238 const Analyzer::Worker *
0239 Analyzer::Base::worker() const
0240 {
0241     return const_cast<const Worker*>( m_worker );
0242 }
0243