File indexing completed on 2024-04-21 04:52:05

0001 /*
0002     SPDX-FileCopyrightText: 2011 Simon Andreas Eugster <simon.eu@gmail.com>
0003     This file is part of kdenlive. See www.kdenlive.org.
0004 
0005 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0006 */
0007 
0008 #include "scopemanager.h"
0009 #include "audioscopes/audiosignal.h"
0010 #include "audioscopes/audiospectrum.h"
0011 #include "audioscopes/spectrogram.h"
0012 #include "colorscopes/histogram.h"
0013 #include "colorscopes/rgbparade.h"
0014 #include "colorscopes/vectorscope.h"
0015 #include "colorscopes/waveform.h"
0016 #include "core.h"
0017 #include "definitions.h"
0018 #include "kdenlivesettings.h"
0019 #include "mainwindow.h"
0020 #include "monitor/monitormanager.h"
0021 
0022 #include "klocalizedstring.h"
0023 #include <QDockWidget>
0024 #include <QSignalMapper>
0025 
0026 //#define DEBUG_SM
0027 #ifdef DEBUG_SM
0028 #include <QDebug>
0029 #endif
0030 
0031 ScopeManager::ScopeManager(QObject *parent)
0032     : QObject(parent)
0033 
0034 {
0035     connect(pCore->monitorManager(), &MonitorManager::checkColorScopes, this, &ScopeManager::slotUpdateActiveRenderer);
0036     connect(pCore->monitorManager(), &MonitorManager::clearScopes, this, &ScopeManager::slotClearColorScopes);
0037     connect(pCore->monitorManager(), &MonitorManager::checkScopes, this, &ScopeManager::slotCheckActiveScopes);
0038 
0039     slotUpdateActiveRenderer();
0040 
0041     createScopes();
0042 }
0043 
0044 bool ScopeManager::addScope(AbstractAudioScopeWidget *audioScope, QDockWidget *audioScopeWidget)
0045 {
0046     bool added = false;
0047     int exists = 0;
0048     // Only add the scope if it does not exist yet in the list
0049     for (auto &m_audioScope : m_audioScopes) {
0050         if (m_audioScope.scope == audioScope) {
0051             exists = 1;
0052             break;
0053         }
0054     }
0055     if (exists == 0) {
0056 // Add scope to the list, set up signal/slot connections
0057 #ifdef DEBUG_SM
0058         qCDebug(KDENLIVE_LOG) << "Adding scope to scope manager: " << audioScope->widgetName();
0059 #endif
0060 
0061         AudioScopeData asd;
0062         asd.scope = audioScope;
0063         m_audioScopes.append(asd);
0064 
0065         connect(audioScope, &AbstractScopeWidget::requestAutoRefresh, this, &ScopeManager::slotCheckActiveScopes);
0066         if (audioScopeWidget != nullptr) {
0067             connect(audioScopeWidget, &QDockWidget::visibilityChanged, this, &ScopeManager::slotCheckActiveScopes);
0068             connect(audioScopeWidget, &QDockWidget::visibilityChanged, this, [this, audioScope]() { slotRequestFrame(QString(audioScope->widgetName())); });
0069         }
0070 
0071         added = true;
0072     }
0073     return added;
0074 }
0075 bool ScopeManager::addScope(AbstractGfxScopeWidget *colorScope, QDockWidget *colorScopeWidget)
0076 {
0077     bool added = false;
0078     int exists = 0;
0079     for (auto &m_colorScope : m_colorScopes) {
0080         if (m_colorScope.scope == colorScope) {
0081             exists = 1;
0082             break;
0083         }
0084     }
0085     if (exists == 0) {
0086 #ifdef DEBUG_SM
0087         qCDebug(KDENLIVE_LOG) << "Adding scope to scope manager: " << colorScope->widgetName();
0088 #endif
0089 
0090         GfxScopeData gsd;
0091         gsd.scope = colorScope;
0092         m_colorScopes.append(gsd);
0093 
0094         connect(colorScope, &AbstractScopeWidget::requestAutoRefresh, this, &ScopeManager::slotCheckActiveScopes);
0095         connect(colorScope, &AbstractGfxScopeWidget::signalFrameRequest, this, &ScopeManager::slotRequestFrame);
0096         connect(colorScope, &AbstractScopeWidget::signalScopeRenderingFinished, this, &ScopeManager::slotScopeReady);
0097         if (colorScopeWidget != nullptr) {
0098             connect(colorScopeWidget, &QDockWidget::visibilityChanged, this, &ScopeManager::slotCheckActiveScopes);
0099             connect(colorScopeWidget, &QDockWidget::visibilityChanged, this, [this, colorScope]() { slotRequestFrame(QString(colorScope->widgetName())); });
0100         }
0101 
0102         added = true;
0103     }
0104     return added;
0105 }
0106 
0107 void ScopeManager::slotDistributeAudio(const audioShortVector &sampleData, int freq, int num_channels, int num_samples)
0108 {
0109 #ifdef DEBUG_SM
0110     qCDebug(KDENLIVE_LOG) << "ScopeManager: Starting to distribute audio.";
0111 #endif
0112     for (auto &m_audioScope : m_audioScopes) {
0113         // Distribute audio to all scopes that are visible and want to be refreshed
0114         if (!m_audioScope.scope->visibleRegion().isEmpty()) {
0115             if (m_audioScope.scope->autoRefreshEnabled()) {
0116                 m_audioScope.scope->slotReceiveAudio(sampleData, freq, num_channels, num_samples);
0117 #ifdef DEBUG_SM
0118                 qCDebug(KDENLIVE_LOG) << "ScopeManager: Distributed audio to " << m_audioScopes[i].scope->widgetName();
0119 #endif
0120             }
0121         }
0122     }
0123 }
0124 void ScopeManager::slotDistributeFrame(const QImage &image)
0125 {
0126 #ifdef DEBUG_SM
0127     qCDebug(KDENLIVE_LOG) << "ScopeManager: Starting to distribute frame.";
0128 #endif
0129     for (auto &m_colorScope : m_colorScopes) {
0130         if (!m_colorScope.scope->visibleRegion().isEmpty()) {
0131             if (m_colorScope.scope->autoRefreshEnabled()) {
0132                 m_colorScope.scope->slotRenderZoneUpdated(image);
0133 #ifdef DEBUG_SM
0134                 qCDebug(KDENLIVE_LOG) << "ScopeManager: Distributed frame to " << m_colorScopes[i].scope->widgetName();
0135 #endif
0136             } else if (m_colorScope.singleFrameRequested) {
0137                 // Special case: Auto refresh is disabled, but user requested an update (e.g. by clicking).
0138                 // Force the scope to update.
0139                 m_colorScope.singleFrameRequested = false;
0140                 m_colorScope.scope->slotRenderZoneUpdated(image);
0141                 m_colorScope.scope->forceUpdateScope();
0142 #ifdef DEBUG_SM
0143                 qCDebug(KDENLIVE_LOG) << "ScopeManager: Distributed forced frame to " << m_colorScopes[i].scope->widgetName();
0144 #endif
0145             }
0146         }
0147     }
0148     // checkActiveColourScopes();
0149 }
0150 
0151 void ScopeManager::slotScopeReady()
0152 {
0153     if (m_lastConnectedRenderer) {
0154         Q_EMIT m_lastConnectedRenderer->scopesClear();
0155     }
0156 }
0157 
0158 void ScopeManager::slotRequestFrame(const QString &widgetName)
0159 {
0160 #ifdef DEBUG_SM
0161     qCDebug(KDENLIVE_LOG) << "ScopeManager: New frame was requested by " << widgetName;
0162 #endif
0163 
0164     // Search for the scope in the lists and tag it to trigger a forced update
0165     // in the distribution slots
0166     for (auto &m_colorScope : m_colorScopes) {
0167         if (m_colorScope.scope->widgetName() == widgetName) {
0168             m_colorScope.singleFrameRequested = true;
0169             break;
0170         }
0171     }
0172     for (auto &m_audioScope : m_audioScopes) {
0173         if (m_audioScope.scope->widgetName() == widgetName) {
0174             m_audioScope.singleFrameRequested = true;
0175             break;
0176         }
0177     }
0178     if (m_lastConnectedRenderer) {
0179         // TODO: trigger refresh?
0180         m_lastConnectedRenderer->refreshMonitorIfActive();
0181         // m_lastConnectedRenderer->sendFrameUpdate();
0182     }
0183 }
0184 
0185 void ScopeManager::slotClearColorScopes()
0186 {
0187     m_lastConnectedRenderer = nullptr;
0188 }
0189 
0190 void ScopeManager::slotUpdateActiveRenderer()
0191 {
0192     // Disconnect old connections
0193     if (m_lastConnectedRenderer != nullptr) {
0194 #ifdef DEBUG_SM
0195         qCDebug(KDENLIVE_LOG) << "Disconnected previous renderer: " << m_lastConnectedRenderer->id();
0196 #endif
0197         m_lastConnectedRenderer->disconnect(this);
0198     }
0199 
0200     m_lastConnectedRenderer = pCore->monitorManager()->activeMonitor();
0201 
0202     // Connect new renderer
0203     if (m_lastConnectedRenderer != nullptr) {
0204         connect(m_lastConnectedRenderer, &Monitor::frameUpdated, this, &ScopeManager::slotDistributeFrame, Qt::UniqueConnection);
0205         connect(m_lastConnectedRenderer, &Monitor::audioSamplesSignal, this, &ScopeManager::slotDistributeAudio, Qt::UniqueConnection);
0206 
0207 #ifdef DEBUG_SM
0208         qCDebug(KDENLIVE_LOG) << "Renderer connected to ScopeManager: " << m_lastConnectedRenderer->id();
0209 #endif
0210 
0211         if (imagesAcceptedByScopes()) {
0212 #ifdef DEBUG_SM
0213             qCDebug(KDENLIVE_LOG) << "Some scopes accept images, triggering frame update.";
0214 #endif
0215             m_lastConnectedRenderer->refreshMonitorIfActive();
0216         }
0217     }
0218 }
0219 
0220 void ScopeManager::slotCheckActiveScopes()
0221 {
0222 #ifdef DEBUG_SM
0223     qCDebug(KDENLIVE_LOG) << "Checking active scopes …";
0224 #endif
0225     // Leave a small delay to make sure that scope widget has been shown or hidden
0226     QTimer::singleShot(500, this, &ScopeManager::checkActiveAudioScopes);
0227     QTimer::singleShot(500, this, &ScopeManager::checkActiveColourScopes);
0228 }
0229 
0230 bool ScopeManager::audioAcceptedByScopes() const
0231 {
0232     bool accepted = pCore->audioMixerVisible;
0233     for (auto m_audioScope : m_audioScopes) {
0234         if (m_audioScope.scope->isVisible() && m_audioScope.scope->autoRefreshEnabled()) {
0235             accepted = true;
0236             break;
0237         }
0238     }
0239 #ifdef DEBUG_SM
0240     qCDebug(KDENLIVE_LOG) << "Any scope accepting audio? " << accepted;
0241 #endif
0242     return accepted;
0243 }
0244 bool ScopeManager::imagesAcceptedByScopes() const
0245 {
0246     bool accepted = false;
0247     for (auto m_colorScope : m_colorScopes) {
0248         if (!m_colorScope.scope->visibleRegion().isEmpty() && m_colorScope.scope->autoRefreshEnabled()) {
0249             accepted = true;
0250             break;
0251         }
0252     }
0253 #ifdef DEBUG_SM
0254     qCDebug(KDENLIVE_LOG) << "Any scope accepting images? " << accepted;
0255 #endif
0256     return accepted;
0257 }
0258 
0259 void ScopeManager::checkActiveAudioScopes()
0260 {
0261     bool audioStillRequested = audioAcceptedByScopes();
0262 
0263 #ifdef DEBUG_SM
0264     qCDebug(KDENLIVE_LOG) << "ScopeManager: New audio data still requested? " << audioStillRequested;
0265 #endif
0266     KdenliveSettings::setMonitor_audio(audioStillRequested);
0267     pCore->monitorManager()->slotUpdateAudioMonitoring();
0268 }
0269 
0270 void ScopeManager::checkActiveColourScopes()
0271 {
0272     bool imageStillRequested = imagesAcceptedByScopes();
0273 
0274 #ifdef DEBUG_SM
0275     qCDebug(KDENLIVE_LOG) << "ScopeManager: New frames still requested? " << imageStillRequested;
0276 #endif
0277 
0278     // Notify monitors whether frames are still required
0279     Monitor *monitor;
0280     monitor = static_cast<Monitor *>(pCore->monitorManager()->monitor(Kdenlive::ProjectMonitor));
0281     if (monitor != nullptr) {
0282         monitor->sendFrameForAnalysis(imageStillRequested);
0283     }
0284 
0285     monitor = static_cast<Monitor *>(pCore->monitorManager()->monitor(Kdenlive::ClipMonitor));
0286     if (monitor != nullptr) {
0287         monitor->sendFrameForAnalysis(imageStillRequested);
0288     }
0289 }
0290 
0291 void ScopeManager::createScopes()
0292 {
0293     createScopeDock(new Vectorscope(pCore->window()), i18n("Vectorscope"), QStringLiteral("vectorscope"));
0294     createScopeDock(new Waveform(pCore->window()), i18n("Waveform"), QStringLiteral("waveform"));
0295     createScopeDock(new RGBParade(pCore->window()), i18n("RGB Parade"), QStringLiteral("rgb_parade"));
0296     createScopeDock(new Histogram(pCore->window()), i18n("Histogram"), QStringLiteral("histogram"));
0297     // Deprecated scopes
0298     // createScopeDock(new Spectrogram(pCore->window()),   i18n("Spectrogram"));
0299     // createScopeDock(new AudioSignal(pCore->window()),   i18n("Audio Signal"));
0300     // createScopeDock(new AudioSpectrum(pCore->window()), i18n("AudioSpectrum"));
0301 }
0302 
0303 template <class T> void ScopeManager::createScopeDock(T *scopeWidget, const QString &title, const QString &name)
0304 {
0305     QDockWidget *dock = pCore->window()->addDock(title, name, scopeWidget);
0306     addScope(scopeWidget, dock);
0307 
0308     // close for initial layout
0309     // actual state will be restored by session management
0310     dock->close();
0311 }