Skip to content

Commit c7a00a4

Browse files
committed
Implement an insert effect system
1 parent e30d818 commit c7a00a4

40 files changed

Lines changed: 720 additions & 131 deletions

src/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,7 @@ set(QML_SOURCE_FILES
329329
${QML_BASE_DIR}/Dialogs/AddMidiCcSettingDialog.qml
330330
${QML_BASE_DIR}/Dialogs/AddPitchBendAutomationDialog.qml
331331
${QML_BASE_DIR}/Dialogs/DeviceGalleryDialog.qml
332+
${QML_BASE_DIR}/Dialogs/DeviceInsertEffectsDialog.qml
332333
${QML_BASE_DIR}/Dialogs/DeviceRackDialog.qml
333334
${QML_BASE_DIR}/Dialogs/ColumnSettingsDialog.qml
334335
${QML_BASE_DIR}/Dialogs/ColumnSettingsDialog_InstrumentSettings.qml

src/application/service/device_service.cpp

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -204,11 +204,17 @@ void DeviceService::serializeToXml(QXmlStreamWriter & writer) const
204204
}
205205

206206
writer.writeStartElement(Constants::NahdXml::xmlKeyMasterEffects());
207-
m_audioEngine->effectRack().serializeEffectsToXml(writer);
208207

209-
for (int deviceSlot = 0; deviceSlot < static_cast<int>(Constants::deviceRackSize()); ++deviceSlot) {
208+
writer.writeStartElement(Constants::NahdXml::xmlKeyInsertEffects());
209+
m_audioEngine->insertEffectRack().serializeEffectsToXml(writer);
210+
writer.writeEndElement();
211+
212+
writer.writeStartElement(Constants::NahdXml::xmlKeySendEffects());
213+
m_audioEngine->sendEffectRack().serializeEffectsToXml(writer);
214+
215+
for (int deviceSlot = 0; deviceSlot < static_cast<int>(Constants::deviceRackSize()); deviceSlot++) {
210216
if (const auto dev = m_audioEngine->device(deviceSlot)) {
211-
for (int effectSlot = 0; effectSlot < static_cast<int>(Constants::effectRackSize()); ++effectSlot) {
217+
for (int effectSlot = 0; effectSlot < static_cast<int>(Constants::effectRackSize()); effectSlot++) {
212218
const float send = dev->reverbSend(effectSlot);
213219
if (send > 0.0001f) {
214220
writer.writeStartElement(Constants::NahdXml::xmlKeySend());
@@ -220,6 +226,7 @@ void DeviceService::serializeToXml(QXmlStreamWriter & writer) const
220226
}
221227
}
222228
}
229+
writer.writeEndElement(); // SendEffects
223230
writer.writeEndElement(); // MasterEffects
224231

225232
if (!m_synthUserPresets.empty()) {
@@ -330,9 +337,31 @@ void DeviceService::deserializeFromXml(QXmlStreamReader & reader)
330337
// Handled via generic Device element if present in slot
331338
} else if (reader.name() == Constants::NahdXml::xmlKeyMasterEffects()) {
332339
while (reader.readNextStartElement()) {
333-
if (reader.name() == Constants::NahdXml::xmlKeyEffect()) {
334-
m_audioEngine->effectRack().deserializeEffect(reader);
340+
if (reader.name() == Constants::NahdXml::xmlKeyInsertEffects()) {
341+
m_audioEngine->insertEffectRack().deserializeEffectsFromXml(reader);
342+
} else if (reader.name() == Constants::NahdXml::xmlKeySendEffects()) {
343+
while (reader.readNextStartElement()) {
344+
if (reader.name() == Constants::NahdXml::xmlKeyEffect()) {
345+
m_audioEngine->sendEffectRack().deserializeEffect(reader);
346+
} else if (reader.name() == Constants::NahdXml::xmlKeySend()) {
347+
const auto deviceSlot = Utils::Xml::readIntAttribute(reader, Constants::NahdXml::xmlKeyDeviceSlot(), false);
348+
const auto effectSlot = Utils::Xml::readIntAttribute(reader, Constants::NahdXml::xmlKeyEffectSlot(), false);
349+
const auto value = Utils::Xml::readDoubleAttribute(reader, Constants::NahdXml::xmlKeyValue(), false);
350+
if (deviceSlot.has_value() && effectSlot.has_value() && value.has_value()) {
351+
if (const auto dev = m_audioEngine->device(static_cast<size_t>(deviceSlot.value()))) {
352+
dev->setReverbSend(static_cast<size_t>(effectSlot.value()), static_cast<float>(value.value()));
353+
}
354+
}
355+
reader.skipCurrentElement();
356+
} else {
357+
reader.skipCurrentElement();
358+
}
359+
}
360+
} else if (reader.name() == Constants::NahdXml::xmlKeyEffect()) {
361+
// Backward compatibility: effects directly under MasterEffects
362+
m_audioEngine->sendEffectRack().deserializeEffect(reader);
335363
} else if (reader.name() == Constants::NahdXml::xmlKeySend()) {
364+
// Backward compatibility: sends directly under MasterEffects
336365
const auto deviceSlot = Utils::Xml::readIntAttribute(reader, Constants::NahdXml::xmlKeyDeviceSlot(), false);
337366
const auto effectSlot = Utils::Xml::readIntAttribute(reader, Constants::NahdXml::xmlKeyEffectSlot(), false);
338367
const auto value = Utils::Xml::readDoubleAttribute(reader, Constants::NahdXml::xmlKeyValue(), false);
@@ -428,9 +457,14 @@ void DeviceService::reset()
428457
emit dataChanged();
429458
}
430459

431-
EffectRack & DeviceService::effectRack()
460+
EffectRack & DeviceService::sendEffectRack()
461+
{
462+
return m_audioEngine->sendEffectRack();
463+
}
464+
465+
EffectRack & DeviceService::insertEffectRack()
432466
{
433-
return m_audioEngine->effectRack();
467+
return m_audioEngine->insertEffectRack();
434468
}
435469

436470
} // namespace noteahead

src/application/service/device_service.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,8 @@ class DeviceService : public QObject
7575

7676
void reset();
7777

78-
EffectRack & effectRack();
78+
EffectRack & sendEffectRack();
79+
EffectRack & insertEffectRack();
7980

8081
signals:
8182
void dataChanged();

src/application/service/effect_rack_controller.cpp

Lines changed: 101 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -44,35 +44,90 @@ EffectRackController::EffectRackController(DeviceServiceS deviceService, EditorS
4444

4545
int EffectRackController::effectCount() const
4646
{
47-
return static_cast<int>(m_deviceService->effectRack().effectCount());
47+
if (const auto rack = currentRack()) {
48+
return static_cast<int>(rack->get().effectCount());
49+
}
50+
return 0;
4851
}
4952

5053
int EffectRackController::revision() const
5154
{
5255
return m_revision;
5356
}
5457

58+
QString EffectRackController::targetDeviceName() const
59+
{
60+
return m_targetDeviceName;
61+
}
62+
63+
void EffectRackController::setTargetDeviceName(const QString & name)
64+
{
65+
if (m_targetDeviceName != name) {
66+
m_targetDeviceName = name;
67+
emit targetDeviceNameChanged();
68+
m_revision++;
69+
emit revisionChanged();
70+
emit effectCountChanged();
71+
}
72+
}
73+
74+
bool EffectRackController::isInsertRack() const
75+
{
76+
return m_isInsertRack;
77+
}
78+
79+
void EffectRackController::setIsInsertRack(bool isInsert)
80+
{
81+
if (m_isInsertRack != isInsert) {
82+
m_isInsertRack = isInsert;
83+
emit isInsertRackChanged();
84+
m_revision++;
85+
emit revisionChanged();
86+
emit effectCountChanged();
87+
}
88+
}
89+
90+
std::optional<std::reference_wrapper<EffectRack>> EffectRackController::currentRack() const
91+
{
92+
if (m_targetDeviceName.isEmpty()) {
93+
if (m_isInsertRack) {
94+
return std::ref(m_deviceService->insertEffectRack());
95+
} else {
96+
return std::ref(m_deviceService->sendEffectRack());
97+
}
98+
} else {
99+
if (const auto device = m_deviceService->device(m_targetDeviceName.toStdString())) {
100+
return std::ref(device->insertEffectRack());
101+
}
102+
}
103+
return std::nullopt;
104+
}
105+
55106
float EffectRackController::parameterValue(int effectIndex, const QString & paramName) const
56107
{
57-
if (const auto effect = m_deviceService->effectRack().effect(static_cast<size_t>(effectIndex))) {
58-
if (const auto parameter = effect->parameter(paramName.toStdString()); parameter) {
59-
return parameter->get().value();
108+
if (const auto rack = currentRack()) {
109+
if (const auto effect = rack->get().effect(static_cast<size_t>(effectIndex))) {
110+
if (const auto parameter = effect->parameter(paramName.toStdString()); parameter) {
111+
return parameter->get().value();
112+
}
60113
}
61114
}
62115
return 0.0f;
63116
}
64117

65118
void EffectRackController::setParameterValue(int effectIndex, const QString & paramName, float value)
66119
{
67-
if (const auto effect = m_deviceService->effectRack().effect(static_cast<size_t>(effectIndex))) {
68-
if (const auto parameter = effect->parameter(paramName.toStdString()); parameter) {
69-
if (parameter->get().update(value)) {
70-
effect->sync();
120+
if (const auto rack = currentRack()) {
121+
if (const auto effect = rack->get().effect(static_cast<size_t>(effectIndex))) {
122+
if (const auto parameter = effect->parameter(paramName.toStdString()); parameter) {
123+
if (parameter->get().update(value)) {
124+
effect->sync();
71125

72-
m_editorService->setIsModified(true);
73-
m_revision++;
74-
emit revisionChanged();
75-
emit parameterChanged(effectIndex, paramName);
126+
m_editorService->setIsModified(true);
127+
m_revision++;
128+
emit revisionChanged();
129+
emit parameterChanged(effectIndex, paramName);
130+
}
76131
}
77132
}
78133
}
@@ -97,19 +152,23 @@ void EffectRackController::setEffect(int slotIndex, const QString & typeId)
97152
}
98153

99154
if (effect) {
100-
m_deviceService->effectRack().setEffect(static_cast<size_t>(slotIndex), std::move(effect));
101-
m_editorService->setIsModified(true);
102-
m_revision++;
103-
emit revisionChanged();
155+
if (const auto rack = currentRack()) {
156+
rack->get().setEffect(static_cast<size_t>(slotIndex), std::move(effect));
157+
m_editorService->setIsModified(true);
158+
m_revision++;
159+
emit revisionChanged();
160+
}
104161
}
105162
}
106163

107164
void EffectRackController::clearEffect(int slotIndex)
108165
{
109-
m_deviceService->effectRack().setEffect(static_cast<size_t>(slotIndex), nullptr);
110-
m_editorService->setIsModified(true);
111-
m_revision++;
112-
emit revisionChanged();
166+
if (const auto rack = currentRack()) {
167+
rack->get().setEffect(static_cast<size_t>(slotIndex), nullptr);
168+
m_editorService->setIsModified(true);
169+
m_revision++;
170+
emit revisionChanged();
171+
}
113172
}
114173
QVariantList EffectRackController::availableEffects() const
115174
{
@@ -123,31 +182,30 @@ QVariantList EffectRackController::availableEffects() const
123182
};
124183

125184
addEffect("Reverb", ReverbEffect::typeIdString());
126-
addEffect("Delay", DelayEffect::typeIdString());
127-
addEffect("High Pass Filter", HighPassFilterEffect::typeIdString());
128-
addEffect("Low Pass Filter", LowPassFilterEffect::typeIdString());
129-
addEffect("Panning", PanningEffect::typeIdString());
130-
addEffect("Volume", VolumeEffect::typeIdString());
131185

132186
return list;
133187
}
134188

135189
QStringList EffectRackController::parameterNames(int effectIndex) const
136190
{
137-
if (const auto effect = m_deviceService->effectRack().effect(static_cast<size_t>(effectIndex))) {
138-
QStringList names;
139-
for (const auto & name : effect->parameterNames()) {
140-
names.append(QString::fromStdString(name));
191+
if (const auto rack = currentRack()) {
192+
if (const auto effect = rack->get().effect(static_cast<size_t>(effectIndex))) {
193+
QStringList names;
194+
for (const auto & name : effect->parameterNames()) {
195+
names.append(QString::fromStdString(name));
196+
}
197+
return names;
141198
}
142-
return names;
143199
}
144200
return {};
145201
}
146202

147203
QString EffectRackController::effectType(int effectIndex) const
148204
{
149-
if (const auto effect = m_deviceService->effectRack().effect(static_cast<size_t>(effectIndex))) {
150-
return QString::fromStdString(effect->type());
205+
if (const auto rack = currentRack()) {
206+
if (const auto effect = rack->get().effect(static_cast<size_t>(effectIndex))) {
207+
return QString::fromStdString(effect->type());
208+
}
151209
}
152210
return "";
153211
}
@@ -193,15 +251,17 @@ QStringList EffectRackController::reverbPresets() const
193251

194252
void EffectRackController::applyReverbPreset(int effectIndex, int presetIndex)
195253
{
196-
if (const auto effect = m_deviceService->effectRack().effect(static_cast<size_t>(effectIndex))) {
197-
if (const auto reverb = std::dynamic_pointer_cast<ReverbEffect>(effect)) {
198-
const auto presetNames = ReverbEffect::presetNames();
199-
if (presetIndex >= 0 && presetIndex < static_cast<int>(presetNames.size())) {
200-
reverb->applyPreset(ReverbEffect::stringToPreset(presetNames[presetIndex]));
201-
m_editorService->setIsModified(true);
202-
m_revision++;
203-
emit revisionChanged();
204-
emit parameterChanged(effectIndex, ""); // Notify all parameters changed
254+
if (const auto rack = currentRack()) {
255+
if (const auto effect = rack->get().effect(static_cast<size_t>(effectIndex))) {
256+
if (const auto reverb = std::dynamic_pointer_cast<ReverbEffect>(effect)) {
257+
const auto presetNames = ReverbEffect::presetNames();
258+
if (presetIndex >= 0 && presetIndex < static_cast<int>(presetNames.size())) {
259+
reverb->applyPreset(ReverbEffect::stringToPreset(presetNames[presetIndex]));
260+
m_editorService->setIsModified(true);
261+
m_revision++;
262+
emit revisionChanged();
263+
emit parameterChanged(effectIndex, ""); // Notify all parameters changed
264+
}
205265
}
206266
}
207267
}

src/application/service/effect_rack_controller.hpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@
2121

2222
#include <QObject>
2323

24+
#include <functional>
2425
#include <memory>
26+
#include <optional>
2527

2628
namespace noteahead {
2729

@@ -30,6 +32,8 @@ class EffectRackController : public QObject
3032
Q_OBJECT
3133
Q_PROPERTY(int effectCount READ effectCount NOTIFY effectCountChanged)
3234
Q_PROPERTY(int revision READ revision NOTIFY revisionChanged)
35+
Q_PROPERTY(QString targetDeviceName READ targetDeviceName WRITE setTargetDeviceName NOTIFY targetDeviceNameChanged)
36+
Q_PROPERTY(bool isInsertRack READ isInsertRack WRITE setIsInsertRack NOTIFY isInsertRackChanged)
3337

3438
public:
3539
using DeviceServiceS = std::shared_ptr<DeviceService>;
@@ -39,6 +43,12 @@ class EffectRackController : public QObject
3943
int effectCount() const;
4044
int revision() const;
4145

46+
QString targetDeviceName() const;
47+
void setTargetDeviceName(const QString & name);
48+
49+
bool isInsertRack() const;
50+
void setIsInsertRack(bool isInsert);
51+
4252
Q_INVOKABLE float parameterValue(int effectIndex, const QString & paramName) const;
4353
Q_INVOKABLE void setParameterValue(int effectIndex, const QString & paramName, float value);
4454

@@ -65,12 +75,18 @@ class EffectRackController : public QObject
6575
signals:
6676
void effectCountChanged();
6777
void revisionChanged();
78+
void targetDeviceNameChanged();
79+
void isInsertRackChanged();
6880
void parameterChanged(int effectIndex, const QString & paramName);
6981

7082
private:
83+
std::optional<std::reference_wrapper<EffectRack>> currentRack() const;
84+
7185
DeviceServiceS m_deviceService;
7286
EditorServiceS m_editorService;
7387
int m_revision = 0;
88+
QString m_targetDeviceName;
89+
bool m_isInsertRack = false;
7490
};
7591

7692
} // namespace noteahead

src/application/service/jack_service.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ int JackService::processCallback(jack_nframes_t frameCount, void * arg)
314314
std::fill(self->m_engineInterleavedBuffer.begin(), self->m_engineInterleavedBuffer.begin() + totalSamples, 0.0f);
315315
}
316316

317-
AudioContext audioContext { self->m_engineInterleavedBuffer.data(), frameCount, self->sampleRate() };
317+
AudioContext audioContext { std::span(self->m_engineInterleavedBuffer.data(), frameCount * 2), frameCount, self->sampleRate() };
318318
self->m_audioEngine->process(audioContext);
319319

320320
auto outL = static_cast<jack_default_audio_sample_t *>(jack_port_get_buffer(self->m_outputPortL, frameCount));

src/application/service/render_worker.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ void RenderWorker::render(const QString & fileName, const noteahead::RenderWorke
9797
}
9898

9999
std::fill(audioBuffer.begin(), audioBuffer.begin() + framesToProcess * 2, 0.0f);
100-
AudioContext audioContext { audioBuffer.data(), framesToProcess, sampleRate };
100+
AudioContext audioContext { std::span(audioBuffer.data(), static_cast<size_t>(framesToProcess) * 2), framesToProcess, sampleRate };
101101
m_audioEngine->process(audioContext);
102102

103103
if (!recorder.push(audioBuffer.data(), framesToProcess * 2)) {
@@ -120,7 +120,7 @@ void RenderWorker::render(const QString & fileName, const noteahead::RenderWorke
120120
audioBuffer.resize(finalFrames * 2);
121121
}
122122
std::fill(audioBuffer.begin(), audioBuffer.begin() + finalFrames * 2, 0.0f);
123-
AudioContext audioContext { audioBuffer.data(), finalFrames, sampleRate };
123+
AudioContext audioContext { std::span(audioBuffer.data(), static_cast<size_t>(finalFrames) * 2), finalFrames, sampleRate };
124124
m_audioEngine->process(audioContext);
125125
while (!recorder.push(audioBuffer.data(), finalFrames * 2)) {
126126
std::this_thread::sleep_for(std::chrono::milliseconds { 1 });

0 commit comments

Comments
 (0)