Skip to content

Commit 910c9fe

Browse files
committed
Add experimental effects units
1 parent 86a5023 commit 910c9fe

51 files changed

Lines changed: 3140 additions & 1 deletion

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/plugins/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,9 @@ endfunction()
166166

167167
add_subdirectory(coreplugin)
168168
add_subdirectory(audio)
169+
add_subdirectory(effectsunitmanager)
170+
add_subdirectory(gaineffectsunit)
171+
add_subdirectory(channelmappereffectsunit)
169172
add_subdirectory(synth)
170173
add_subdirectory(welcomewizard)
171174
add_subdirectory(maintenance)
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# SPDX-FileCopyrightText: Team OpenVPI
2+
# SPDX-License-Identifier: LGPL-3.0-or-later
3+
4+
project(channelmappereffectsunit
5+
VERSION ${APPLICATION_VERSION}
6+
DESCRIPTION ${CURRENT_PLUGIN_DESC}
7+
)
8+
9+
diffscope_add_builtin_plugin(${PROJECT_NAME}
10+
NAME org.diffscope.channelmappereffectsunit
11+
DISPLAY_NAME "Channel Mapper Effects Unit"
12+
MACRO_PREFIX CHANNEL_MAPPER_EFFECTS_UNIT
13+
QML_MODULE_NAME ChannelMapperEffectsUnit
14+
NO_ACTION
15+
QT_LINKS
16+
Core
17+
Qml
18+
Quick
19+
LINKS
20+
ExtensionSystem::ExtensionSystem
21+
ChorusKit::AppCore
22+
QAKCore
23+
effectsunitmanager
24+
talcs::Core
25+
svscraft::Core
26+
svscraft::Quick
27+
INCLUDE_PRIVATE
28+
internal/**
29+
)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
version = 1
2+
3+
[[annotations]]
4+
path = ["res/translations/**", "plugin.json.in"]
5+
SPDX-FileCopyrightText = "Team OpenVPI"
6+
SPDX-License-Identifier = "LGPL-3.0-or-later"
Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
// SPDX-FileCopyrightText: Team OpenVPI
2+
// SPDX-License-Identifier: LGPL-3.0-or-later
3+
4+
#include "ChannelMapperEffectsUnit.h"
5+
6+
#include <algorithm>
7+
#include <atomic>
8+
#include <cmath>
9+
#include <memory>
10+
11+
#include <QJsonObject>
12+
#include <QQmlComponent>
13+
#include <QQuickItem>
14+
#include <QVariant>
15+
16+
#include <CoreApi/runtimeinterface.h>
17+
18+
#include <TalcsCore/AudioSource.h>
19+
20+
namespace ChannelMapperEffectsUnit::Internal {
21+
22+
namespace {
23+
24+
constexpr double minimumMixPercent = -100.0;
25+
constexpr double maximumMixPercent = 100.0;
26+
27+
double normalizedMixPercent(const QJsonValue &value) {
28+
if (!value.isDouble() || !std::isfinite(value.toDouble())) {
29+
return 0.0;
30+
}
31+
return std::clamp(value.toDouble(), minimumMixPercent, maximumMixPercent);
32+
}
33+
34+
bool mixValuesEqual(double left, double right) {
35+
return qFuzzyIsNull(left - right);
36+
}
37+
38+
float mixCoefficient(double percent) {
39+
return static_cast<float>(percent * 0.01);
40+
}
41+
42+
}
43+
44+
class ChannelMapperProcessor final : public talcs::AudioSource {
45+
public:
46+
void setMixCoefficients(
47+
double leftLeftPercent,
48+
double leftRightPercent,
49+
double rightLeftPercent,
50+
double rightRightPercent) {
51+
m_leftLeft.store(mixCoefficient(leftLeftPercent), std::memory_order_relaxed);
52+
m_leftRight.store(mixCoefficient(leftRightPercent), std::memory_order_relaxed);
53+
m_rightLeft.store(mixCoefficient(rightLeftPercent), std::memory_order_relaxed);
54+
m_rightRight.store(mixCoefficient(rightRightPercent), std::memory_order_relaxed);
55+
}
56+
57+
protected:
58+
qint64 processReading(const talcs::AudioSourceReadData &readData) override {
59+
const int channelCount = readData.buffer->channelCount();
60+
if (channelCount == 0) {
61+
return readData.length;
62+
}
63+
const float leftLeft = m_leftLeft.load(std::memory_order_relaxed);
64+
const float leftRight = m_leftRight.load(std::memory_order_relaxed);
65+
const float rightLeft = m_rightLeft.load(std::memory_order_relaxed);
66+
const float rightRight = m_rightRight.load(std::memory_order_relaxed);
67+
const bool hasRightInput = channelCount > 1;
68+
for (qint64 position = readData.startPos;
69+
position < readData.startPos + readData.length; ++position) {
70+
const float leftInput = readData.buffer->sample(0, position);
71+
const float rightInput = hasRightInput
72+
? readData.buffer->sample(1, position)
73+
: 0.0f;
74+
readData.buffer->setSample(0, position, leftInput * leftLeft + rightInput * leftRight);
75+
if (hasRightInput) {
76+
readData.buffer->setSample(1, position, leftInput * rightLeft + rightInput * rightRight);
77+
}
78+
}
79+
return readData.length;
80+
}
81+
82+
private:
83+
std::atomic<float> m_leftLeft{1.0f};
84+
std::atomic<float> m_leftRight{0.0f};
85+
std::atomic<float> m_rightLeft{0.0f};
86+
std::atomic<float> m_rightRight{1.0f};
87+
};
88+
89+
ChannelMapperEffectsUnit::ChannelMapperEffectsUnit(QQmlComponent *editorComponent, QObject *parent)
90+
: EffectsUnit(parent) {
91+
auto processor = std::make_unique<ChannelMapperProcessor>();
92+
m_processor = processor.get();
93+
setProcessor(std::move(processor));
94+
updateProcessor();
95+
96+
auto object = editorComponent->createWithInitialProperties({
97+
{QStringLiteral("effectsUnit"), QVariant::fromValue(this)},
98+
}, editorComponent->creationContext());
99+
if (!object) {
100+
qFatal() << editorComponent->errorString();
101+
}
102+
auto editor = qobject_cast<QQuickItem *>(object);
103+
if (!editor) {
104+
delete object;
105+
qFatal("ChannelMapperEditor must create a QQuickItem");
106+
}
107+
setEditor(editor);
108+
}
109+
110+
ChannelMapperEffectsUnit::~ChannelMapperEffectsUnit() = default;
111+
112+
double ChannelMapperEffectsUnit::leftLeftMixPercent() const {
113+
return m_leftLeftMixPercent;
114+
}
115+
116+
double ChannelMapperEffectsUnit::leftRightMixPercent() const {
117+
return m_leftRightMixPercent;
118+
}
119+
120+
double ChannelMapperEffectsUnit::rightLeftMixPercent() const {
121+
return m_rightLeftMixPercent;
122+
}
123+
124+
double ChannelMapperEffectsUnit::rightRightMixPercent() const {
125+
return m_rightRightMixPercent;
126+
}
127+
128+
QJsonValue ChannelMapperEffectsUnit::getState() const {
129+
return QJsonObject{
130+
{QStringLiteral("leftLeftMixPercent"), m_committedLeftLeftMixPercent},
131+
{QStringLiteral("leftRightMixPercent"), m_committedLeftRightMixPercent},
132+
{QStringLiteral("rightLeftMixPercent"), m_committedRightLeftMixPercent},
133+
{QStringLiteral("rightRightMixPercent"), m_committedRightRightMixPercent},
134+
};
135+
}
136+
137+
void ChannelMapperEffectsUnit::setState(const QJsonValue &state) {
138+
const auto object = state.isObject() ? state.toObject() : QJsonObject{};
139+
const double leftLeft = normalizedMixPercent(object.value(QStringLiteral("leftLeftMixPercent")));
140+
const double leftRight = normalizedMixPercent(object.value(QStringLiteral("leftRightMixPercent")));
141+
const double rightLeft = normalizedMixPercent(object.value(QStringLiteral("rightLeftMixPercent")));
142+
const double rightRight = normalizedMixPercent(object.value(QStringLiteral("rightRightMixPercent")));
143+
const bool stateChanged = !mixValuesEqual(m_committedLeftLeftMixPercent, leftLeft)
144+
|| !mixValuesEqual(m_committedLeftRightMixPercent, leftRight)
145+
|| !mixValuesEqual(m_committedRightLeftMixPercent, rightLeft)
146+
|| !mixValuesEqual(m_committedRightRightMixPercent, rightRight);
147+
const bool leftLeftChanged = !mixValuesEqual(m_leftLeftMixPercent, leftLeft);
148+
const bool leftRightChanged = !mixValuesEqual(m_leftRightMixPercent, leftRight);
149+
const bool rightLeftChanged = !mixValuesEqual(m_rightLeftMixPercent, rightLeft);
150+
const bool rightRightChanged = !mixValuesEqual(m_rightRightMixPercent, rightRight);
151+
152+
m_committedLeftLeftMixPercent = leftLeft;
153+
m_committedLeftRightMixPercent = leftRight;
154+
m_committedRightLeftMixPercent = rightLeft;
155+
m_committedRightRightMixPercent = rightRight;
156+
m_leftLeftMixPercent = leftLeft;
157+
m_leftRightMixPercent = leftRight;
158+
m_rightLeftMixPercent = rightLeft;
159+
m_rightRightMixPercent = rightRight;
160+
updateProcessor();
161+
162+
if (leftLeftChanged) {
163+
Q_EMIT leftLeftMixPercentChanged();
164+
}
165+
if (leftRightChanged) {
166+
Q_EMIT leftRightMixPercentChanged();
167+
}
168+
if (rightLeftChanged) {
169+
Q_EMIT rightLeftMixPercentChanged();
170+
}
171+
if (rightRightChanged) {
172+
Q_EMIT rightRightMixPercentChanged();
173+
}
174+
if (stateChanged) {
175+
Q_EMIT updated();
176+
}
177+
}
178+
179+
void ChannelMapperEffectsUnit::previewLeftLeftMixPercent(double value) {
180+
if (previewMix(m_leftLeftMixPercent, value)) {
181+
Q_EMIT leftLeftMixPercentChanged();
182+
}
183+
}
184+
185+
void ChannelMapperEffectsUnit::previewLeftRightMixPercent(double value) {
186+
if (previewMix(m_leftRightMixPercent, value)) {
187+
Q_EMIT leftRightMixPercentChanged();
188+
}
189+
}
190+
191+
void ChannelMapperEffectsUnit::previewRightLeftMixPercent(double value) {
192+
if (previewMix(m_rightLeftMixPercent, value)) {
193+
Q_EMIT rightLeftMixPercentChanged();
194+
}
195+
}
196+
197+
void ChannelMapperEffectsUnit::previewRightRightMixPercent(double value) {
198+
if (previewMix(m_rightRightMixPercent, value)) {
199+
Q_EMIT rightRightMixPercentChanged();
200+
}
201+
}
202+
203+
void ChannelMapperEffectsUnit::commitPreview() {
204+
if (mixValuesEqual(m_committedLeftLeftMixPercent, m_leftLeftMixPercent)
205+
&& mixValuesEqual(m_committedLeftRightMixPercent, m_leftRightMixPercent)
206+
&& mixValuesEqual(m_committedRightLeftMixPercent, m_rightLeftMixPercent)
207+
&& mixValuesEqual(m_committedRightRightMixPercent, m_rightRightMixPercent)) {
208+
return;
209+
}
210+
m_committedLeftLeftMixPercent = m_leftLeftMixPercent;
211+
m_committedLeftRightMixPercent = m_leftRightMixPercent;
212+
m_committedRightLeftMixPercent = m_rightLeftMixPercent;
213+
m_committedRightRightMixPercent = m_rightRightMixPercent;
214+
Q_EMIT updated();
215+
}
216+
217+
void ChannelMapperEffectsUnit::setLeftLeftMixPercent(double value) {
218+
previewLeftLeftMixPercent(value);
219+
commitPreview();
220+
}
221+
222+
void ChannelMapperEffectsUnit::setLeftRightMixPercent(double value) {
223+
previewLeftRightMixPercent(value);
224+
commitPreview();
225+
}
226+
227+
void ChannelMapperEffectsUnit::setRightLeftMixPercent(double value) {
228+
previewRightLeftMixPercent(value);
229+
commitPreview();
230+
}
231+
232+
void ChannelMapperEffectsUnit::setRightRightMixPercent(double value) {
233+
previewRightRightMixPercent(value);
234+
commitPreview();
235+
}
236+
237+
bool ChannelMapperEffectsUnit::previewMix(double &member, double value) {
238+
value = std::clamp(value, minimumMixPercent, maximumMixPercent);
239+
if (mixValuesEqual(member, value)) {
240+
return false;
241+
}
242+
member = value;
243+
updateProcessor();
244+
return true;
245+
}
246+
247+
void ChannelMapperEffectsUnit::updateProcessor() {
248+
m_processor->setMixCoefficients(m_leftLeftMixPercent, m_leftRightMixPercent,
249+
m_rightLeftMixPercent, m_rightRightMixPercent);
250+
}
251+
252+
ChannelMapperEffectsUnitClass::ChannelMapperEffectsUnitClass(QObject *parent)
253+
: EffectsUnitClass(tr("Channel Mapper"), parent),
254+
m_editorComponent(new QQmlComponent(Core::RuntimeInterface::qmlEngine(),
255+
QStringLiteral("DiffScope.ChannelMapperEffectsUnit"),
256+
QStringLiteral("ChannelMapperEditor"), this)) {
257+
if (m_editorComponent->isError()) {
258+
qFatal() << m_editorComponent->errorString();
259+
}
260+
}
261+
262+
ChannelMapperEffectsUnitClass::~ChannelMapperEffectsUnitClass() = default;
263+
264+
EffectsUnitManager::EffectsUnit *ChannelMapperEffectsUnitClass::create(QObject *parent) const {
265+
return new ChannelMapperEffectsUnit(m_editorComponent, parent);
266+
}
267+
268+
}
269+
270+
#include "moc_ChannelMapperEffectsUnit.cpp"

0 commit comments

Comments
 (0)