Preliminary support for audio stream effects (only swap and copy channel working)

Related to #382
parent f5b85e60
Pipeline #23842 passed with stage
in 9 minutes and 47 seconds
......@@ -41,8 +41,8 @@ BoolParamWidget::BoolParamWidget(std::shared_ptr<AssetParameterModel> model, QMo
slotRefresh();
// emit the signal of the base class when appropriate
connect(this->m_checkBox, &QCheckBox::stateChanged, [this](int) {
emit valueChanged(m_index, QString::number(m_checkBox->isChecked()), true); });
connect(this->m_checkBox, &QCheckBox::stateChanged, [this](int state) {
emit valueChanged(m_index, QString::number(state), true); });
}
void BoolParamWidget::slotShowComment(bool show)
......
......@@ -42,9 +42,9 @@ SwitchParamWidget::SwitchParamWidget(std::shared_ptr<AssetParameterModel> model,
slotRefresh();
// emit the signal of the base class when appropriate
connect(this->m_checkBox, &QCheckBox::stateChanged, [this](int) {
connect(this->m_checkBox, &QCheckBox::stateChanged, [this](int state) {
emit valueChanged(m_index,
(m_checkBox->isChecked() ? m_model->data(m_index, AssetParameterModel::MaxRole).toString()
(state == Qt::Checked ? m_model->data(m_index, AssetParameterModel::MaxRole).toString()
: m_model->data(m_index, AssetParameterModel::MinRole).toString()),
true);
});
......
......@@ -609,6 +609,17 @@ std::shared_ptr<Mlt::Producer> ProjectClip::getTimelineProducer(int trackId, int
m_audioProducers[trackId] = cloneProducer(true);
m_audioProducers[trackId]->set("set.test_audio", 0);
m_audioProducers[trackId]->set("set.test_image", 1);
if (m_streamEffects.contains(audioStream)) {
QStringList effects = m_streamEffects.value(audioStream);
for (const QString effect : effects) {
Mlt::Filter filt(*m_audioProducers[trackId]->profile(), effect.toUtf8().constData());
if (filt.is_valid()) {
// Add stream effect markup
filt.set("kdenlive:stream", 1);
m_audioProducers[trackId]->attach(filt);
}
}
}
if (audioStream > -1) {
m_audioProducers[trackId]->set("audio_index", audioStream);
}
......@@ -622,7 +633,7 @@ std::shared_ptr<Mlt::Producer> ProjectClip::getTimelineProducer(int trackId, int
}
if (state == PlaylistState::VideoOnly) {
// we return the video producer
// We need to get an audio producer, if none exists
// We need to get an video producer, if none exists
if (m_videoProducers.count(trackId) == 0) {
m_videoProducers[trackId] = cloneProducer(true);
m_videoProducers[trackId]->set("set.test_audio", 1);
......@@ -1593,3 +1604,112 @@ void ProjectClip::renameAudioStream(int id, QString name)
pCore->bin()->reloadMonitorStreamIfActive(clipId());
}
}
void ProjectClip::addAudioStreamEffect(int streamIndex, const QString effectName)
{
QString addedEffectName;
QMap <QString, QString> effectParams;
if (effectName.contains(QLatin1Char(' '))) {
// effect has parameters
QStringList params = effectName.split(QLatin1Char(' '));
addedEffectName = params.takeFirst();
for (const QString &p : params) {
QStringList paramValue = p.split(QLatin1Char('='));
if (paramValue.size() == 2) {
effectParams.insert(paramValue.at(0), paramValue.at(1));
}
}
} else {
addedEffectName = effectName;
}
QStringList effects;
if (m_streamEffects.contains(streamIndex)) {
QStringList readEffects = m_streamEffects.value(streamIndex);
// Remove effect if present (parameters might have changed
for (const QString effect : readEffects) {
if (effect == addedEffectName || effect.startsWith(addedEffectName + QStringLiteral(" "))) {
continue;
}
effects << effect;
}
effects << effectName;
} else {
effects = QStringList({effectName});
}
m_streamEffects.insert(streamIndex, effects);
setProducerProperty(QString("kdenlive:stream:%1").arg(streamIndex), effects.join(QLatin1Char('#')));
for (auto &p : m_audioProducers) {
int stream = p.first / 100;
if (stream == streamIndex) {
Mlt::Filter filt(*p.second->profile(), addedEffectName.toUtf8().constData());
if (filt.is_valid()) {
// Add stream effect markup
filt.set("kdenlive:stream", 1);
// Set parameters
QMapIterator<QString, QString> i(effectParams);
while (i.hasNext()) {
i.next();
filt.set(i.key().toUtf8().constData(), i.value().toUtf8().constData());
}
p.second->attach(filt);
}
}
}
}
void ProjectClip::removeAudioStreamEffect(int streamIndex, QString effectName)
{
QStringList effects;
if (effectName.contains(QLatin1Char(' '))) {
effectName = effectName.section(QLatin1Char(' '), 0, 0);
}
if (m_streamEffects.contains(streamIndex)) {
QStringList readEffects = m_streamEffects.value(streamIndex);
// Remove effect if present (parameters might have changed
for (const QString effect : readEffects) {
if (effect == effectName || effect.startsWith(effectName + QStringLiteral(" "))) {
continue;
}
effects << effect;
}
if (effects.isEmpty()) {
m_streamEffects.remove(streamIndex);
resetProducerProperty(QString("kdenlive:stream:%1").arg(streamIndex));
} else {
m_streamEffects.insert(streamIndex, effects);
setProducerProperty(QString("kdenlive:stream:%1").arg(streamIndex), effects.join(QLatin1Char('#')));
}
} else {
// No effects for this stream, this is not expected, abort
return;
}
for (auto &p : m_audioProducers) {
int stream = p.first / 100;
if (stream == streamIndex) {
int max = p.second->filter_count();
for (int i = 0; i < max; i++) {
std::shared_ptr<Mlt::Filter> fl(p.second->filter(i));
if (!fl->is_valid()) {
continue;
}
if (fl->get_int("kdenlive:stream") != 1) {
// This is not an audio stream effect
continue;
}
if (fl->get("mlt_service") == effectName) {
p.second->detach(*fl.get());
break;
}
}
}
}
}
QStringList ProjectClip::getAudioStreamEffect(int streamIndex) const
{
QStringList effects;
if (m_streamEffects.contains(streamIndex)) {
effects = m_streamEffects.value(streamIndex);
}
return effects;
}
......@@ -233,6 +233,12 @@ public:
/** @brief Rename an audio stream for this clip
*/
void renameAudioStream(int id, QString name) override;
/** @brief Add an audio effect on a specific audio stream for this clip. */
void addAudioStreamEffect(int streamIndex, const QString effectName) override;
/** @brief Remove an audio effect on a specific audio stream for this clip. */
void removeAudioStreamEffect(int streamIndex, QString effectName) override;
/** @brief Get the list of audio stream effects for a defined stream. */
QStringList getAudioStreamEffect(int streamIndex) const override;
protected:
friend class ClipModel;
......
......@@ -263,6 +263,13 @@ void ClipController::getInfoForProducer()
}
if (audioIndex > -1 || m_clipType == ClipType::Playlist) {
m_audioInfo = std::make_unique<AudioStreamInfo>(m_masterProducer, audioIndex);
// Load stream effects
for (int stream : m_audioInfo->streams().keys()) {
QString streamEffect = m_properties->get(QString("kdenlive:stream:%1").arg(stream).toUtf8().constData());
if (!streamEffect.isEmpty()) {
m_streamEffects.insert(stream, streamEffect.split(QChar('#')));
}
}
}
if (!m_hasLimitedDuration) {
......
......@@ -97,10 +97,16 @@ public:
/** @brief Returns this clip's producer. */
virtual std::shared_ptr<Mlt::Producer> thumbProducer() = 0;
/** @brief Rename an audio stream. */
virtual void renameAudioStream(int id, QString name) = 0;
/** @brief Add an audio effect on a specific audio stream for this clip. */
virtual void addAudioStreamEffect(int streamIndex, const QString effectName) = 0;
/** @brief Remove an audio effect on a specific audio stream for this clip. */
virtual void removeAudioStreamEffect(int streamIndex, const QString effectName) = 0;
virtual QStringList getAudioStreamEffect(int streamIndex) const = 0;
/** @brief Returns the clip's duration */
GenTime getPlaytime() const;
int getFramePlaytime() const;
......@@ -244,6 +250,7 @@ protected:
std::shared_ptr<MarkerListModel> m_markerModel;
bool m_hasAudio;
bool m_hasVideo;
QMap<int, QStringList> m_streamEffects;
/** @brief Store clip url temporarily while the clip controller has not been created. */
QString m_temporaryUrl;
std::shared_ptr<Mlt::Producer> m_thumbsProducer;
......
......@@ -65,6 +65,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <QToolBar>
#include <QUrl>
#include <QListWidgetItem>
#include <QButtonGroup>
#include <QVBoxLayout>
AnalysisTree::AnalysisTree(QWidget *parent)
......@@ -170,6 +171,7 @@ ClipPropertiesController::ClipPropertiesController(ClipController *controller, Q
, m_audioStream(nullptr)
, m_textEdit(nullptr)
, m_audioStreamsView(nullptr)
, m_activeAudioStreams(-1)
{
m_controller->mirrorOriginalProperties(m_sourceProperties);
setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
......@@ -251,7 +253,7 @@ ClipPropertiesController::ClipPropertiesController(ClipController *controller, Q
// Force properties
auto *vbox = new QVBoxLayout;
vbox->setSpacing(0);
// Force Audio properties
auto *audioVbox = new QVBoxLayout;
audioVbox->setSpacing(0);
......@@ -633,6 +635,7 @@ ClipPropertiesController::ClipPropertiesController(ClipController *controller, Q
while (i.hasNext()) {
i.next();
QListWidgetItem *item = new QListWidgetItem(i.value(), m_audioStreamsView);
// Store stream index
item->setData(Qt::UserRole, i.key());
// Store oringinal name
item->setData(Qt::UserRole + 1, i.value());
......@@ -642,6 +645,7 @@ ClipPropertiesController::ClipPropertiesController(ClipController *controller, Q
} else {
item->setCheckState(Qt::Unchecked);
}
updateStreamIcon(m_audioStreamsView->row(item), i.key());
}
if (audioStreamsInfo.count() > 1) {
QListWidgetItem *item = new QListWidgetItem(i18n("Merge all streams"), m_audioStreamsView);
......@@ -654,6 +658,22 @@ ClipPropertiesController::ClipPropertiesController(ClipController *controller, Q
item->setCheckState(Qt::Unchecked);
}
}
connect(m_audioStreamsView, &QListWidget::currentRowChanged, [this] (int row) {
if (row > -1) {
m_audioEffectGroup->setEnabled(true);
QListWidgetItem *item = m_audioStreamsView->item(row);
m_activeAudioStreams = item->data(Qt::UserRole).toInt();
QStringList effects = m_controller->getAudioStreamEffect(m_activeAudioStreams);
QSignalBlocker bk(m_swapChanels);
QSignalBlocker bk1(m_copyChannelGroup);
m_swapChanels->setChecked(effects.contains(QLatin1String("channelswap")));
m_copyChannel1->setChecked(effects.contains(QStringLiteral("channelcopy from=0 to=1")));
m_copyChannel2->setChecked(effects.contains(QStringLiteral("channelcopy from=1 to=0")));
} else {
m_activeAudioStreams = -1;
m_audioEffectGroup->setEnabled(false);
}
});
connect(m_audioStreamsView, &QListWidget::itemChanged, [this] (QListWidgetItem *item) {
if (!item) {
return;
......@@ -717,6 +737,76 @@ ClipPropertiesController::ClipPropertiesController(ClipController *controller, Q
emit updateClipProperties(m_id, m_originalProperties, properties);
m_originalProperties = properties;
});
// Audio effects
m_audioEffectGroup = new QGroupBox(this);
m_audioEffectGroup->setEnabled(false);
QVBoxLayout *vbox = new QVBoxLayout;
m_swapChanels = new QCheckBox(i18n("Swap Channels"), this);
connect(m_swapChanels, &QCheckBox::stateChanged, [this] (int state) {
if (m_activeAudioStreams == -1) {
// No stream selected, abort
return;
}
if (state == Qt::Checked) {
// Add swap channels effect
m_controller->addAudioStreamEffect(m_activeAudioStreams, QStringLiteral("channelswap"));
updateStreamIcon(m_audioStreamsView->currentRow(), m_activeAudioStreams);
} else {
// Remove swap channels effect
m_controller->removeAudioStreamEffect(m_activeAudioStreams, QStringLiteral("channelswap"));
updateStreamIcon(m_audioStreamsView->currentRow(), m_activeAudioStreams);
}
});
vbox->addWidget(m_swapChanels);
// Copy channel
QHBoxLayout *copyLay = new QHBoxLayout;
copyLay->addWidget(new QLabel(i18n("Copy Channel"), this));
m_copyChannel1 = new QCheckBox(i18n("1"), this);
m_copyChannel2 = new QCheckBox(i18n("2"), this);
m_copyChannelGroup = new QButtonGroup(this);
m_copyChannelGroup->addButton(m_copyChannel1);
m_copyChannelGroup->addButton(m_copyChannel2);
m_copyChannelGroup->setExclusive(false);
copyLay->addWidget(m_copyChannel1);
copyLay->addWidget(m_copyChannel2);
copyLay->addStretch(1);
vbox->addLayout(copyLay);
connect(m_copyChannelGroup, QOverload<QAbstractButton *, bool>::of(&QButtonGroup::buttonToggled), [this] (QAbstractButton *but, bool) {
if (but == m_copyChannel1) {
qDebug()<<"=== BUTTON 1 CLKD";
QSignalBlocker bk(m_copyChannelGroup);
m_copyChannel2->setChecked(false);
} else {
qDebug()<<"=== BUTTON 2 CLKD";
QSignalBlocker bk(m_copyChannelGroup);
m_copyChannel1->setChecked(false);
}
if (m_copyChannel1->isChecked()) {
m_controller->addAudioStreamEffect(m_activeAudioStreams, QStringLiteral("channelcopy from=0 to=1"));
updateStreamIcon(m_audioStreamsView->currentRow(), m_activeAudioStreams);
} else if (m_copyChannel2->isChecked()) {
m_controller->addAudioStreamEffect(m_activeAudioStreams, QStringLiteral("channelcopy from=1 to=0"));
updateStreamIcon(m_audioStreamsView->currentRow(), m_activeAudioStreams);
} else {
// Remove swap channels effect
m_controller->removeAudioStreamEffect(m_activeAudioStreams, QStringLiteral("channelcopy"));
updateStreamIcon(m_audioStreamsView->currentRow(), m_activeAudioStreams);
}
});
// Gain
QHBoxLayout *gainLay = new QHBoxLayout;
gainLay->addWidget(new QLabel(i18n("Gain"), this));
QSpinBox *spinGain = new QSpinBox(this);
spinGain->setRange(-10, 10);
spinGain->setSuffix(i18n("dB"));
gainLay->addWidget(spinGain);
gainLay->addStretch(1);
vbox->addLayout(gainLay);
// Normalize
vbox->addWidget(new QCheckBox(i18n("Normalize"), this));
vbox->addStretch(1);
m_audioEffectGroup->setLayout(vbox);
audioVbox->addWidget(m_audioEffectGroup);
// Audio sync
hlay = new QHBoxLayout;
......@@ -820,6 +910,15 @@ ClipPropertiesController::ClipPropertiesController(ClipController *controller, Q
ClipPropertiesController::~ClipPropertiesController() = default;
void ClipPropertiesController::updateStreamIcon(int row, int streamIndex)
{
QStringList effects = m_controller->getAudioStreamEffect(streamIndex);
QListWidgetItem *item = m_audioStreamsView->item(row);
if (item) {
item->setIcon(effects.isEmpty() ? QIcon() : QIcon::fromTheme(QStringLiteral("favorite")));
}
}
void ClipPropertiesController::updateTab(int ix)
{
KdenliveSettings::setProperties_panel_page(ix);
......
......@@ -36,6 +36,9 @@ class QTextEdit;
class QLabel;
class QComboBox;
class QListWidget;
class QGroupBox;
class QCheckBox;
class QButtonGroup;
class AnalysisTree : public QTreeWidget
{
......@@ -117,7 +120,16 @@ private:
AnalysisTree *m_analysisTree;
QTextEdit *m_textEdit;
QListWidget *m_audioStreamsView;
QGroupBox *m_audioEffectGroup;
QCheckBox *m_swapChanels;
QButtonGroup *m_copyChannelGroup;
QCheckBox *m_copyChannel1;
QCheckBox *m_copyChannel2;
/** @brief The selected audio stream. */
int m_activeAudioStreams;
void fillProperties();
/** @brief Add/remove icon beside audio stream to indicate effects. */
void updateStreamIcon(int row, int streamIndex);
signals:
void updateClipProperties(const QString &, const QMap<QString, QString> &, const QMap<QString, QString> &);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment