diff --git a/src/audiomixer/mixerwidget.cpp b/src/audiomixer/mixerwidget.cpp index 08ff50b52..d8d6b3830 100644 --- a/src/audiomixer/mixerwidget.cpp +++ b/src/audiomixer/mixerwidget.cpp @@ -1,488 +1,489 @@ /*************************************************************************** * Copyright (C) 2019 by Jean-Baptiste Mardelle * * This file is part of Kdenlive. See www.kdenlive.org. * * * * 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 "mixerwidget.hpp" #include "mlt++/MltFilter.h" #include "mlt++/MltTractor.h" #include "mlt++/MltEvent.h" #include "mlt++/MltProfile.h" #include "core.h" #include "kdenlivesettings.h" #include "mixermanager.hpp" #include "audiolevelwidget.hpp" #include "capture/mediacapture.h" #include #include #include #include #include #include #include #include #include #include +#include #include #include static inline double IEC_Scale(double dB) { dB = log10(dB) * 20.0; double fScale = 1.0f; if (dB < -70.0f) fScale = 0.0f; else if (dB < -60.0f) fScale = (dB + 70.0f) * 0.0025f; else if (dB < -50.0f) fScale = (dB + 60.0f) * 0.005f + 0.025f; else if (dB < -40.0) fScale = (dB + 50.0f) * 0.0075f + 0.075f; else if (dB < -30.0f) fScale = (dB + 40.0f) * 0.015f + 0.15f; else if (dB < -20.0f) fScale = (dB + 30.0f) * 0.02f + 0.3f; else if (dB < -0.001f || dB > 0.001f) /* if (dB < 0.0f) */ fScale = (dB + 20.0f) * 0.025f + 0.5f; return fScale; } static inline int fromDB(double level) { int value = 60; if (level > 0.) { // increase volume value = 100 - ((pow(10, 1. - level/24) - 1) / .225); } else if (level < 0.) { value = (10 - pow(10, 1. - level/-50)) / -0.11395 + 59; } return value; } void MixerWidget::property_changed( mlt_service , MixerWidget *widget, char *name ) { if (widget && !strcmp(name, "_position")) { mlt_properties filter_props = MLT_FILTER_PROPERTIES( widget->m_monitorFilter->get_filter()); int pos = mlt_properties_get_int(filter_props, "_position"); if (!widget->m_levels.contains(pos)) { widget->m_levels[pos] = {IEC_Scale(mlt_properties_get_double(filter_props, "_audio_level.0")), IEC_Scale(mlt_properties_get_double(filter_props, "_audio_level.1"))}; if (widget->m_levels.size() > widget->m_maxLevels) { widget->m_levels.erase(widget->m_levels.begin()); } } } } MixerWidget::MixerWidget(int tid, std::shared_ptr service, const QString &trackTag, MixerManager *parent) : QWidget(parent) , m_manager(parent) , m_tid(tid) , m_levelFilter(nullptr) , m_monitorFilter(nullptr) , m_balanceFilter(nullptr) , m_maxLevels(qMax(30, (int)(service->get_fps() * 1.5))) , m_solo(nullptr) , m_record(nullptr) , m_collapse(nullptr) , m_lastVolume(0) , m_listener(nullptr) , m_recording(false) { buildUI(service.get(), trackTag); } MixerWidget::MixerWidget(int tid, Mlt::Tractor *service, const QString &trackTag, MixerManager *parent) : QWidget(parent) , m_manager(parent) , m_tid(tid) , m_levelFilter(nullptr) , m_monitorFilter(nullptr) , m_balanceFilter(nullptr) , m_maxLevels(qMax(30, (int)(service->get_fps() * 1.5))) , m_solo(nullptr) , m_record(nullptr) , m_collapse(nullptr) , m_lastVolume(0) , m_listener(nullptr) , m_recording(false) { buildUI(service, trackTag); } MixerWidget::~MixerWidget() { if (m_listener) { delete m_listener; } } void MixerWidget::buildUI(Mlt::Tractor *service, const QString &trackTag) { setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); // Build audio meter widget m_audioMeterWidget.reset(new AudioLevelWidget(width(), this)); // intialize for stereo display m_audioMeterWidget->setAudioValues({-100, -100}); // Build volume widget m_volumeSlider = new QSlider(Qt::Vertical, this); m_volumeSlider->setRange(0, 100); m_volumeSlider->setValue(60); m_volumeSlider->setToolTip(i18n("Volume")); m_volumeSpin = new QDoubleSpinBox(this); m_volumeSpin->setRange(-50, 24); m_volumeSpin->setSuffix(i18n("dB")); m_volumeSpin->setFrame(false); connect(m_volumeSpin, static_cast(&QDoubleSpinBox::valueChanged), [&](double val) { m_volumeSlider->setValue(fromDB(val)); }); m_balanceDial = new QDial(this); m_balanceDial->setRange(-50, 50); m_balanceDial->setValue(0); m_balanceSpin = new QSpinBox(this); m_balanceSpin->setRange(-50, 50); m_balanceSpin->setValue(0); m_balanceSpin->setFrame(false); m_balanceSpin->setToolTip(i18n("Balance")); // Check if we already have build-in filters for this tractor int max = service->filter_count(); for (int i = 0; i < max; i++) { std::shared_ptr fl(service->filter(i)); if (!fl->is_valid()) { continue; } const QString filterService = fl->get("mlt_service"); if (filterService == QLatin1String("audiolevel")) { m_monitorFilter = fl; } else if (filterService == QLatin1String("volume")) { m_levelFilter = fl; int volume = m_levelFilter->get_int("level"); m_volumeSpin->setValue(volume); m_volumeSlider->setValue(fromDB(volume)); } else if (filterService == QLatin1String("panner")) { m_balanceFilter = fl; int val = m_balanceFilter->get_double("start") * 100 - 50; m_balanceSpin->setValue(val); m_balanceDial->setValue(val); } } // Build default filters if not found if (m_levelFilter == nullptr) { m_levelFilter.reset(new Mlt::Filter(service->get_profile(), "volume")); if (m_levelFilter->is_valid()) { m_levelFilter->set("internal_added", 237); m_levelFilter->set("disable", 1); service->attach(*m_levelFilter.get()); } } if (m_balanceFilter == nullptr) { m_balanceFilter.reset(new Mlt::Filter(service->get_profile(), "panner")); if (m_balanceFilter->is_valid()) { m_balanceFilter->set("internal_added", 237); m_balanceFilter->set("start", 0.5); m_balanceFilter->set("disable", 1); service->attach(*m_balanceFilter.get()); } } // Monitoring should be appended last so that other effects are reflected in audio monitor if (m_monitorFilter == nullptr) { m_monitorFilter.reset(new Mlt::Filter(service->get_profile(), "audiolevel")); if (m_monitorFilter->is_valid()) { m_monitorFilter->set("iec_scale", 0); service->attach(*m_monitorFilter.get()); } } m_trackLabel = new QLabel(trackTag, this); m_trackLabel->setAutoFillBackground(true); m_trackLabel->setAlignment(Qt::AlignHCenter); m_trackLabel->setFrameStyle(QFrame::Panel | QFrame::Sunken); m_muteAction = new KDualAction(i18n("Mute track"), i18n("Unmute track"), this); m_muteAction->setActiveIcon(QIcon::fromTheme(QStringLiteral("kdenlive-hide-audio"))); m_muteAction->setInactiveIcon(QIcon::fromTheme(QStringLiteral("kdenlive-show-audio"))); connect(m_balanceDial, &QDial::valueChanged, m_balanceSpin, &QSpinBox::setValue); connect(m_muteAction, &KDualAction::activeChangedByUser, [&](bool active) { if (m_tid == -1) { // Muting master, special case if (m_levelFilter) { if (active) { m_lastVolume = m_levelFilter->get_int("level"); m_levelFilter->set("level", -1000); } else { m_levelFilter->set("level", m_lastVolume); } } } else { emit muteTrack(m_tid, !active); reset(); } updateLabel(); }); QToolButton *mute = new QToolButton(this); mute->setDefaultAction(m_muteAction); mute->setAutoRaise(true); // Setup default width QFontMetrics fm(font()); setFixedWidth(3 * mute->sizeHint().width()); if (m_tid > -1) { // No solo / rec button on master m_solo = new QToolButton(this); m_solo->setCheckable(true); m_solo->setIcon(QIcon::fromTheme("headphones")); m_solo->setToolTip(i18n("Solo mode")); m_solo->setAutoRaise(true); connect(m_solo, &QToolButton::toggled, [&](bool toggled) { emit toggleSolo(m_tid, toggled); updateLabel(); }); m_record = new QToolButton(this); m_record->setIcon(QIcon::fromTheme("media-record")); m_record->setToolTip(i18n("Record")); m_record->setCheckable(true); m_record->setAutoRaise(true); connect(m_record, &QToolButton::clicked, [&]() { m_manager->recordAudio(m_tid); }); } else { m_collapse = new QToolButton(this); m_collapse->setIcon(KdenliveSettings::mixerCollapse() ? QIcon::fromTheme("arrow-left") : QIcon::fromTheme("arrow-right")); m_collapse->setToolTip(i18n("Show Channels")); m_collapse->setCheckable(true); m_collapse->setAutoRaise(true); m_collapse->setChecked(KdenliveSettings::mixerCollapse() ); connect(m_collapse, &QToolButton::clicked, [&]() { KdenliveSettings::setMixerCollapse(m_collapse->isChecked()); m_collapse->setIcon(m_collapse->isChecked() ? QIcon::fromTheme("arrow-left") : QIcon::fromTheme("arrow-right")); m_manager->collapseMixers(); }); } connect(m_volumeSlider, &QSlider::valueChanged, [&](int value) { QSignalBlocker bk(m_volumeSpin); if (m_recording) { m_volumeSpin->setValue(value); KdenliveSettings::setAudiocapturevolume(value); //TODO update capture volume } else if (m_levelFilter != nullptr) { double dbValue = 0; if (value > 60) { // increase volume dbValue = 24 * (1 - log10((100 - value)*0.225 + 1)); } else if (value < 60) { dbValue = -50 * (1 - log10(10 - (value - 59)*(-0.11395))); } m_volumeSpin->setValue(dbValue); m_levelFilter->set("level", dbValue); m_levelFilter->set("disable", value == 60 ? 1 : 0); m_levels.clear(); m_manager->purgeCache(); } }); connect(m_balanceSpin, static_cast(&QSpinBox::valueChanged), [&](int value) { QSignalBlocker bk(m_balanceDial); m_balanceDial->setValue(value); if (m_balanceFilter != nullptr) { m_balanceFilter->set("start", (value + 50) / 100.); m_balanceFilter->set("disable", value == 0 ? 1 : 0); m_levels.clear(); m_manager->purgeCache(); } }); QVBoxLayout *lay = new QVBoxLayout; setContentsMargins(0, 0, 0, 0); lay->setMargin(0); lay->addWidget(m_trackLabel); QHBoxLayout *buttonslay = new QHBoxLayout; buttonslay->setSpacing(0); buttonslay->setContentsMargins(0, 0, 0, 0); if (m_collapse) { buttonslay->addWidget(m_collapse); } buttonslay->addWidget(mute); if (m_solo) { buttonslay->addWidget(m_solo); } if (m_record) { buttonslay->addWidget(m_record); } lay->addLayout(buttonslay); lay->addWidget(m_balanceDial); lay->addWidget(m_balanceSpin); QHBoxLayout *hlay = new QHBoxLayout; hlay->addWidget(m_audioMeterWidget.get()); hlay->addWidget(m_volumeSlider); lay->addLayout(hlay); lay->addWidget(m_volumeSpin); lay->setStretch(4, 10); setLayout(lay); if (service->get_int("hide") > 1) { setMute(true); } } void MixerWidget::mousePressEvent(QMouseEvent *event) { if(event->button() == Qt::RightButton) { QWidget *child = childAt(event->pos()); if (child == m_balanceDial) { m_balanceSpin->setValue(0); } else if (child == m_volumeSlider) { m_volumeSlider->setValue(60); } } else { QWidget::mousePressEvent(event); } } void MixerWidget::setMute(bool mute) { m_muteAction->setActive(mute); m_volumeSlider->setEnabled(!mute); m_volumeSpin->setEnabled(!mute); m_audioMeterWidget->setEnabled(!mute); m_balanceSpin->setEnabled(!mute); m_balanceDial->setEnabled(!mute); updateLabel(); } void MixerWidget::updateLabel() { if (m_recording) { QPalette pal = m_trackLabel->palette(); pal.setColor(QPalette::Window, Qt::red); m_trackLabel->setPalette(pal); } else if (m_muteAction->isActive()) { QPalette pal = m_trackLabel->palette(); pal.setColor(QPalette::Window, QColor("#ff8c00")); m_trackLabel->setPalette(pal); } else if (m_solo && m_solo->isChecked()) { QPalette pal = m_trackLabel->palette(); pal.setColor(QPalette::Window, Qt::darkGreen); m_trackLabel->setPalette(pal); } else { QPalette pal = palette(); m_trackLabel->setPalette(pal); } } void MixerWidget::updateAudioLevel(int pos) { QMutexLocker lk(&m_storeMutex); if (m_levels.contains(pos)) { m_audioMeterWidget->setAudioValues({m_levels.value(pos).first, m_levels.value(pos).second}); //m_levels.remove(pos); } else { m_audioMeterWidget->setAudioValues({-100, -100}); } } void MixerWidget::reset() { QMutexLocker lk(&m_storeMutex); m_levels.clear(); m_audioMeterWidget->setAudioValues({-100, -100}); } void MixerWidget::clear() { QMutexLocker lk(&m_storeMutex); m_levels.clear(); } bool MixerWidget::isMute() const { return m_muteAction->isActive(); } void MixerWidget::unSolo() { if (m_solo) { QSignalBlocker bl(m_solo); m_solo->setChecked(false); } } void MixerWidget::gotRecLevels(QVectorlevels) { switch (levels.size()) { case 0: m_audioMeterWidget->setAudioValues({-100, -100}); break; case 1: m_audioMeterWidget->setAudioValues({IEC_Scale(levels[0]), -100}); break; default: m_audioMeterWidget->setAudioValues({IEC_Scale(levels[0]), IEC_Scale(levels[1])}); break; } } void MixerWidget::setRecordState(bool recording) { m_recording = recording; m_record->setChecked(m_recording); QSignalBlocker bk(m_volumeSpin); QSignalBlocker bk2(m_volumeSlider); if (m_recording) { connect(pCore->getAudioDevice(), &MediaCapture::audioLevels, this, &MixerWidget::gotRecLevels); m_balanceDial->setEnabled(false); m_balanceSpin->setEnabled(false); m_volumeSpin->setRange(0, 100); m_volumeSpin->setSuffix(QStringLiteral("%")); m_volumeSpin->setValue(KdenliveSettings::audiocapturevolume()); m_volumeSlider->setValue(KdenliveSettings::audiocapturevolume()); } else { m_balanceDial->setEnabled(true); m_balanceSpin->setEnabled(true); int level = m_levelFilter->get_int("level"); disconnect(pCore->getAudioDevice(), &MediaCapture::audioLevels, this, &MixerWidget::gotRecLevels); m_volumeSpin->setRange(-100, 60); m_volumeSpin->setSuffix(i18n("dB")); m_volumeSpin->setValue(level); m_volumeSlider->setValue(fromDB(level)); } updateLabel(); } void MixerWidget::connectMixer(bool doConnect) { if (doConnect) { if (m_listener == nullptr) { m_listener = m_monitorFilter->listen("property-changed", this, (mlt_listener)property_changed); } } else { delete m_listener; m_listener = nullptr; } } diff --git a/src/doc/kdenlivedoc.h b/src/doc/kdenlivedoc.h index 7b5630808..64cd7b49e 100644 --- a/src/doc/kdenlivedoc.h +++ b/src/doc/kdenlivedoc.h @@ -1,252 +1,253 @@ /*************************************************************************** * Copyright (C) 2007 by Jean-Baptiste Mardelle (jb@kdenlive.org) * * * * 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, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * ***************************************************************************/ /*! \class KdenliveDoc \brief Represents a kdenlive project file Instances of KdeliveDoc classes are created by void MainWindow::newFile(bool showProjectSettings, bool force) */ #ifndef KDENLIVEDOC_H #define KDENLIVEDOC_H +#include #include #include #include #include #include #include #include "definitions.h" #include "gentime.h" #include "timecode.h" class MainWindow; class TrackInfo; class ProjectClip; class MarkerListModel; class Render; class ProfileParam; class QUndoGroup; class QUndoCommand; class DocUndoStack; namespace Mlt { class Profile; } class KdenliveDoc : public QObject { Q_OBJECT public: KdenliveDoc(const QUrl &url, QString projectFolder, QUndoGroup *undoGroup, const QString &profileName, const QMap &properties, const QMap &metadata, const QPoint &tracks, bool *openBackup, MainWindow *parent = nullptr); ~KdenliveDoc() override; friend class LoadJob; /** @brief Get current document's producer. */ const QByteArray getProjectXml(); double fps() const; int width() const; int height() const; QUrl url() const; KAutoSaveFile *m_autosave; Timecode timecode() const; std::shared_ptr commandStack(); int getFramePos(const QString &duration); /** @brief Get a list of all clip ids that are inside a folder. */ QStringList getBinFolderClipIds(const QString &folderId) const; const QString description() const; void setUrl(const QUrl &url); /** @brief Defines whether the document needs to be saved. */ bool isModified() const; /** @brief Returns the project folder, used to store project temporary files. */ QString projectTempFolder() const; /** @brief Returns the folder used to store project data files (titles, etc). */ QString projectDataFolder() const; void setZoom(int horizontal, int vertical = -1); QPoint zoom() const; double dar() const; /** @brief Returns the project file xml. */ QDomDocument xmlSceneList(const QString &scene); /** @brief Saves the project file xml to a file. */ bool saveSceneList(const QString &path, const QString &scene); /** @brief Saves only the MLT xml to a file for preview rendering. */ void saveMltPlaylist(const QString &fileName); void cacheImage(const QString &fileId, const QImage &img) const; void setProjectFolder(const QUrl &url); void setZone(int start, int end); QPoint zone() const; /** @brief Returns target tracks (video, audio). */ QPair targetTracks() const; void setDocumentProperty(const QString &name, const QString &value); virtual const QString getDocumentProperty(const QString &name, const QString &defaultValue = QString()) const; /** @brief Gets the list of renderer properties saved into the document. */ QMap getRenderProperties() const; /** @brief Read the display ratio from an xml project file. */ static double getDisplayRatio(const QString &path); /** @brief Backup the project file */ void backupLastSavedVersion(const QString &path); /** @brief Returns the document metadata (author, copyright, ...) */ const QMap metadata() const; /** @brief Set the document metadata (author, copyright, ...) */ void setMetadata(const QMap &meta); /** @brief Get all document properties that need to be saved */ QMap documentProperties(); bool useProxy() const; bool useExternalProxy() const; bool autoGenerateProxy(int width) const; bool autoGenerateImageProxy(int width) const; /** @brief Saves effects embedded in project file. */ void saveCustomEffects(const QDomNodeList &customeffects); void resetProfile(); /** @brief Returns true if the profile file has changed. */ bool profileChanged(const QString &profile) const; /** @brief Get an action from main actioncollection. */ QAction *getAction(const QString &name); /** @brief Add an action to main actioncollection. */ void doAddAction(const QString &name, QAction *a, const QKeySequence &shortcut); void invalidatePreviews(QList chunks); void previewProgress(int p); /** @brief Select most appropriate rendering profile for timeline preview based on fps / size. */ void selectPreviewProfile(); void displayMessage(const QString &text, MessageType type = DefaultMessage, int timeOut = 0); /** @brief Get a cache directory for this project. */ QDir getCacheDir(CacheType type, bool *ok) const; /** @brief Create standard cache dirs for the project */ void initCacheDirs(); /** @brief Get a list of all proxy hash used in this project */ QStringList getProxyHashList(); /** @brief Move project data files to new url */ void moveProjectData(const QString &src, const QString &dest); /** @brief Returns a pointer to the guide model */ std::shared_ptr getGuideModel() const; // TODO REFAC: delete */ Render *renderer(); /** @brief Returns MLT's root (base path) */ const QString documentRoot() const; /** @brief Returns true if timeline preview settings changed*/ bool updatePreviewSettings(const QString &profile); /** @brief Returns the recommended proxy profile parameters */ QString getAutoProxyProfile(); /** @brief Returns the number of clips in this project (useful to show loading progress) */ int clipsCount() const; private: QUrl m_url; QDomDocument m_document; int m_clipsCount; /** @brief MLT's root (base path) that is stripped from urls in saved xml */ QString m_documentRoot; Timecode m_timecode; std::shared_ptr m_commandStack; QString m_searchFolder; /** @brief Tells whether the current document has been changed after being saved. */ bool m_modified; /** @brief The default recommended proxy extension */ QString m_proxyExtension; /** @brief The default recommended proxy params */ QString m_proxyParams; /** @brief Tells whether the current document was modified by Kdenlive on opening, and a backup should be created on save. */ enum DOCSTATUS { CleanProject, ModifiedProject, UpgradedProject }; DOCSTATUS m_documentOpenStatus; /** @brief The project folder, used to store project files (titles, effects...). */ QString m_projectFolder; QList m_undoChunks; QMap m_documentProperties; QMap m_documentMetadata; std::shared_ptr m_guideModel; QString searchFileRecursively(const QDir &dir, const QString &matchSize, const QString &matchHash) const; /** @brief Creates a new project. */ QDomDocument createEmptyDocument(int videotracks, int audiotracks); QDomDocument createEmptyDocument(const QList &tracks, int audiotracks); /** @brief Updates the project folder location entry in the kdenlive file dialogs to point to the current project folder. */ void updateProjectFolderPlacesEntry(); /** @brief Only keep some backup files, delete some */ void cleanupBackupFiles(); /** @brief Load document properties from the xml file */ void loadDocumentProperties(); /** @brief update document properties to reflect a change in the current profile */ void updateProjectProfile(bool reloadProducers = false); /** @brief initialize proxy settings based on hw status */ void initProxySettings(); public slots: void slotCreateTextTemplateClip(const QString &group, const QString &groupId, QUrl path); /** @brief Sets the document as modified or up to date. * @description If crash recovery is turned on, a timer calls KdenliveDoc::slotAutoSave() \n * Emits docModified connected to MainWindow::slotUpdateDocumentState \n * @param mod (optional) true if the document has to be saved */ void setModified(bool mod = true); void slotProxyCurrentItem(bool doProxy, QList> clipList = QList>(), bool force = false, QUndoCommand *masterCommand = nullptr); /** @brief Saves the current project at the autosave location. * @description The autosave files are in ~/.kde/data/stalefiles/kdenlive/ */ void slotAutoSave(const QString &scene); /** @brief Groups were changed, save to MLT. */ void groupsChanged(const QString &groups); private slots: void slotModified(); void switchProfile(std::unique_ptr &profile, const QString &id, const QDomElement &xml); void slotSwitchProfile(const QString &profile_path); /** @brief Check if we did a new action invalidating more recent undo items. */ void checkPreviewStack(); /** @brief Guides were changed, save to MLT. */ void guidesChanged(); signals: void resetProjectList(); /** @brief Informs that the document status has been changed. * * If the document has been modified, it's called with true as an argument. */ void docModified(bool); void selectLastAddedClip(const QString &); /** @brief When creating a backup file, also save a thumbnail of current timeline */ void saveTimelinePreview(const QString &path); /** @brief Trigger the autosave timer start */ void startAutoSave(); /** @brief Current doc created effects, reload list */ void reloadEffects(const QStringList &paths); /** @brief Fps was changed, update timeline (changed = 1 means no change) */ void updateFps(double changed); /** @brief If a command is pushed when we are in the middle of undo stack, invalidate further undo history */ void removeInvalidUndo(int ix); /** @brief Update compositing info */ void updateCompositionMode(int); }; #endif