Skip to content

Commit 1e53f4c

Browse files
committed
Implement user presets
- Currently works in the context of a song
1 parent 37f4ec5 commit 1e53f4c

31 files changed

Lines changed: 542 additions & 66 deletions

File tree

src/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,7 @@ set(QML_SOURCE_FILES
319319
${QML_BASE_DIR}/Dialogs/SettingsDialog_ThemeSettings.qml
320320
${QML_BASE_DIR}/Dialogs/ShortcutsDialog.qml
321321
${QML_BASE_DIR}/Dialogs/SideChainTargetDelegate.qml
322+
${QML_BASE_DIR}/Dialogs/StringInputDialog.qml
322323
${QML_BASE_DIR}/Dialogs/TrackSettingsDialog.qml
323324
${QML_BASE_DIR}/Dialogs/TrackSettingsDialog_InstrumentSettings.qml
324325
${QML_BASE_DIR}/Dialogs/TrackSettingsDialog_MidiCcSettings.qml

src/application/application.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,9 @@ void Application::connectDeviceService()
353353
connect(m_editorService.get(), &EditorService::devicesDeserializationRequested, m_deviceService.get(), &DeviceService::deserializeFromXml);
354354
connect(m_editorService.get(), &EditorService::projectPathChanged, m_deviceService.get(), &DeviceService::setProjectPath);
355355

356+
m_synthController->setDeviceService(m_deviceService);
357+
connect(m_deviceService.get(), &DeviceService::synthUserPresetsChanged, m_synthController.get(), &SynthController::setUserPresets);
358+
356359
connect(m_deviceService.get(), &DeviceService::dataChanged, this, [this]() {
357360
m_editorService->setIsModified(true);
358361
});

src/application/service/device_service.cpp

Lines changed: 122 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,18 @@
1212
//
1313
// You should have received a copy of the GNU General Public License
1414
// along with Noteahead. If not, see <http://www.gnu.org/licenses/>.
15-
1615
#include "device_service.hpp"
1716

1817
#include "../../common/constants.hpp"
18+
#include "../../common/utils.hpp"
1919
#include "../../domain/devices/sampler_device.hpp"
2020
#include "../../domain/devices/synth_device.hpp"
2121
#include "../../infra/audio/audio_engine.hpp"
2222

23-
#include <QStringList>
2423
#include <QXmlStreamReader>
2524
#include <QXmlStreamWriter>
25+
#include <set>
26+
#include <QXmlStreamWriter>
2627

2728
#include <set>
2829

@@ -32,6 +33,9 @@ DeviceService::DeviceService(AudioEngineS audioEngine, QObject * parent)
3233
: QObject { parent }
3334
, m_audioEngine { std::move(audioEngine) }
3435
{
36+
for (int i = 0; i < 128; ++i) {
37+
m_synthUserPresets[i] = SynthPresets::initPreset();
38+
}
3539
}
3640

3741
DeviceService::~DeviceService() = default;
@@ -78,6 +82,13 @@ void DeviceService::processMidiCc(const QString & portName, uint8_t controller,
7882
}
7983
}
8084

85+
void DeviceService::processMidiProgramChange(const QString & portName, uint8_t program, uint8_t channel)
86+
{
87+
if (const auto dev = device(portName.toStdString()); dev) {
88+
dev->processMidiProgramChange(program, channel);
89+
}
90+
}
91+
8192
void DeviceService::processMidiAllNotesOff(const QString & portName)
8293
{
8394
if (const auto dev = device(portName.toStdString()); dev) {
@@ -139,6 +150,32 @@ QStringList DeviceService::devicesByCategory(const QString & category) const
139150
return devices;
140151
}
141152

153+
void DeviceService::setSynthUserPresets(const UserPresets & presets)
154+
{
155+
m_synthUserPresets = presets;
156+
for (const auto & name : internalDeviceNames()) {
157+
if (const auto synth = std::dynamic_pointer_cast<SynthDevice>(device(name))) {
158+
synth->setUserPresets(m_synthUserPresets);
159+
}
160+
}
161+
if (const auto synth = std::dynamic_pointer_cast<SynthDevice>(device(Constants::synthDeviceName().toStdString()))) {
162+
synth->setUserPresets(m_synthUserPresets);
163+
}
164+
emit synthUserPresetsChanged(m_synthUserPresets);
165+
}
166+
167+
UserPresets DeviceService::synthUserPresets() const
168+
{
169+
return m_synthUserPresets;
170+
}
171+
172+
void DeviceService::saveSynthUserPreset(int index, const SynthPreset & preset)
173+
{
174+
m_synthUserPresets[index] = preset;
175+
setSynthUserPresets(m_synthUserPresets);
176+
emit dataChanged();
177+
}
178+
142179
void DeviceService::setProjectPath(const std::string & projectPath)
143180
{
144181
for (const auto & name : internalDeviceNames()) {
@@ -156,30 +193,107 @@ void DeviceService::serializeToXml(QXmlStreamWriter & writer) const
156193
dev->serializeToXml(writer);
157194
}
158195
}
159-
if (const auto synth = std::dynamic_pointer_cast<SynthDevice>(device(Constants::synthDeviceName().toStdString())); synth) {
196+
if (const auto synth = std::dynamic_pointer_cast<SynthDevice>(device(Constants::synthDeviceName().toStdString()))) {
160197
synth->serializeToXml(writer);
161198
}
199+
200+
if (!m_synthUserPresets.empty()) {
201+
std::shared_ptr<SynthDevice> synth;
202+
for (const auto & name : internalDeviceNames()) {
203+
if (auto dev = std::dynamic_pointer_cast<SynthDevice>(device(name))) {
204+
synth = dev;
205+
break;
206+
}
207+
}
208+
const auto typeId = synth ? QString::fromStdString(synth->typeId()) : "";
209+
210+
writer.writeStartElement(Constants::NahdXml::xmlKeyUserPresets());
211+
if (!typeId.isEmpty()) {
212+
writer.writeAttribute(Constants::NahdXml::xmlKeyTypeId(), typeId);
213+
}
214+
215+
for (auto && [index, preset] : m_synthUserPresets) {
216+
if (!preset.parameters.empty()) {
217+
writer.writeStartElement(Constants::NahdXml::xmlKeyPreset());
218+
writer.writeAttribute(Constants::NahdXml::xmlKeyIndex(), QString::number(index));
219+
writer.writeAttribute(Constants::NahdXml::xmlKeyName(), QString::fromStdString(preset.name));
220+
for (auto && [paramName, value] : preset.parameters) {
221+
writer.writeStartElement(Constants::NahdXml::xmlKeyParameter());
222+
writer.writeAttribute(Constants::NahdXml::xmlKeyName(), QString::fromStdString(paramName));
223+
if (synth) {
224+
if (auto p = synth->parameter(paramName); p) {
225+
writer.writeAttribute(Constants::NahdXml::xmlKeyValue(), QString::number(Parameter::internalToXmlValue(value, p->get().xmlMin(), p->get().xmlMax())));
226+
writer.writeAttribute(Constants::NahdXml::xmlKeyMin(), QString::number(p->get().xmlMin()));
227+
writer.writeAttribute(Constants::NahdXml::xmlKeyMax(), QString::number(p->get().xmlMax()));
228+
writer.writeAttribute(Constants::NahdXml::xmlKeyDefault(), QString::number(p->get().xmlDefault()));
229+
writer.writeAttribute(Constants::NahdXml::xmlKeyScale(), QString::number(p->get().xmlScale()));
230+
} else {
231+
writer.writeAttribute(Constants::NahdXml::xmlKeyValue(), QString::number(static_cast<double>(value)));
232+
}
233+
} else {
234+
writer.writeAttribute(Constants::NahdXml::xmlKeyValue(), QString::number(static_cast<double>(value)));
235+
}
236+
writer.writeEndElement(); // Parameter
237+
}
238+
writer.writeEndElement(); // Preset
239+
}
240+
}
241+
writer.writeEndElement(); // SynthUserPresets
242+
}
243+
162244
writer.writeEndElement(); // Devices
163245
}
164246

165247
void DeviceService::deserializeFromXml(QXmlStreamReader & reader)
166248
{
167-
const auto samplersCategory = Constants::NahdXml::xmlValueSamplers();
168-
169249
while (reader.readNextStartElement()) {
170250
const auto name = reader.name();
171251
if (name == Constants::NahdXml::xmlKeyDevice()) {
172252
const auto category = reader.attributes().value(Constants::NahdXml::xmlKeyCategory()).toString();
173253
const auto deviceName = reader.attributes().value(Constants::NahdXml::xmlKeyName()).toString().toStdString();
174-
if (auto dev = device(deviceName)) {
254+
if (const auto dev = device(deviceName)) {
175255
dev->deserializeFromXml(reader);
176256
} else {
177257
reader.skipCurrentElement();
178258
}
179-
} else if (reader.isStartElement() && reader.name() == Constants::NahdXml::xmlKeySynth()) {
180-
if (const auto synth = std::dynamic_pointer_cast<SynthDevice>(device(Constants::synthDeviceName().toStdString())); synth) {
259+
} else if (reader.name() == Constants::NahdXml::xmlKeySynth()) {
260+
if (const auto synth = std::dynamic_pointer_cast<SynthDevice>(device(Constants::synthDeviceName().toStdString()))) {
181261
synth->deserializeFromXml(reader);
182262
}
263+
} else if (reader.name() == Constants::NahdXml::xmlKeyUserPresets()) {
264+
const auto typeId = Utils::Xml::readStringAttribute(reader, Constants::NahdXml::xmlKeyTypeId(), false);
265+
266+
while (reader.readNextStartElement()) {
267+
if (reader.name() == Constants::NahdXml::xmlKeyPreset()) {
268+
const auto index = Utils::Xml::readUIntAttribute(reader, Constants::NahdXml::xmlKeyIndex()).value_or(0);
269+
const auto presetName = Utils::Xml::readStringAttribute(reader, Constants::NahdXml::xmlKeyName()).value_or("Init");
270+
SynthPreset preset { presetName.toStdString(), {} };
271+
272+
while (reader.readNextStartElement()) {
273+
if (reader.name() == Constants::NahdXml::xmlKeyParameter()) {
274+
const auto paramName = Utils::Xml::readStringAttribute(reader, Constants::NahdXml::xmlKeyName()).value_or("").toStdString();
275+
if (!paramName.empty()) {
276+
const auto xmlMin = reader.attributes().value(Constants::NahdXml::xmlKeyMin());
277+
const auto xmlMax = reader.attributes().value(Constants::NahdXml::xmlKeyMax());
278+
if (!xmlMin.isNull() && !xmlMax.isNull()) {
279+
const auto xmlValue = reader.attributes().value(Constants::NahdXml::xmlKeyValue()).toInt();
280+
preset.parameters[paramName] = Parameter::xmlValueToInternal(xmlValue, xmlMin.toInt(), xmlMax.toInt());
281+
} else {
282+
const auto paramValue = Utils::Xml::readDoubleAttribute(reader, Constants::NahdXml::xmlKeyValue()).value_or(0.0);
283+
preset.parameters[paramName] = static_cast<float>(paramValue);
284+
}
285+
}
286+
reader.skipCurrentElement();
287+
} else {
288+
reader.skipCurrentElement();
289+
}
290+
}
291+
m_synthUserPresets[index] = std::move(preset);
292+
} else {
293+
reader.skipCurrentElement();
294+
}
295+
}
296+
setSynthUserPresets(m_synthUserPresets);
183297
} else {
184298
reader.skipCurrentElement();
185299
}

src/application/service/device_service.hpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#define DEVICE_SERVICE_HPP
1818

1919
#include "../../domain/devices/device.hpp"
20+
#include "../../domain/devices/synth_presets.hpp"
2021

2122
#include <QObject>
2223
#include <QStringList>
@@ -48,6 +49,7 @@ class DeviceService : public QObject
4849
void processMidiNoteOn(const QString & portName, uint8_t note, uint8_t velocity);
4950
void processMidiNoteOff(const QString & portName, uint8_t note);
5051
void processMidiCc(const QString & portName, uint8_t controller, uint8_t value, uint8_t channel);
52+
void processMidiProgramChange(const QString & portName, uint8_t program, uint8_t channel);
5153
void processMidiAllNotesOff(const QString & portName);
5254
void processMidiAllNotesOff();
5355

@@ -59,16 +61,22 @@ class DeviceService : public QObject
5961
Q_INVOKABLE QStringList categories() const;
6062
Q_INVOKABLE QStringList devicesByCategory(const QString & category) const;
6163

64+
void setSynthUserPresets(const UserPresets & presets);
65+
UserPresets synthUserPresets() const;
66+
void saveSynthUserPreset(int index, const SynthPreset & preset);
67+
6268
void setProjectPath(const std::string & projectPath);
6369

6470
void serializeToXml(QXmlStreamWriter & writer) const;
6571
void deserializeFromXml(QXmlStreamReader & reader);
6672

6773
signals:
6874
void dataChanged();
75+
void synthUserPresetsChanged(const UserPresets & presets);
6976

7077
private:
7178
AudioEngineS m_audioEngine;
79+
UserPresets m_synthUserPresets;
7280
};
7381

7482
} // namespace noteahead

src/application/service/editor_service.cpp

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,25 @@
1313
// You should have received a copy of the GNU General Public License
1414
// along with Noteahead. If not, see <http://www.gnu.org/licenses/>.
1515

16-
#include "../../domain/pattern.hpp"
1716
#include "editor_service.hpp"
1817

19-
#include "../../infra/settings.hpp"
2018
#include "../../common/constants.hpp"
19+
#include "../../common/utils.hpp"
2120
#include "../../contrib/SimpleLogger/src/simple_logger.hpp"
21+
#include "../../domain/column_settings.hpp"
22+
#include "../../domain/devices/device.hpp"
23+
#include "../../domain/instrument.hpp"
24+
#include "../../domain/instrument_settings.hpp"
25+
#include "../../domain/line.hpp"
2226
#include "../../domain/note_data.hpp"
2327
#include "../../domain/note_data_manipulator.hpp"
28+
#include "../../domain/pattern.hpp"
2429
#include "../../domain/song.hpp"
25-
#include "../command/note_edit_command.hpp"
30+
#include "../../domain/track.hpp"
31+
#include "../../infra/settings.hpp"
2632
#include "../command/automation_command.hpp"
2733
#include "../command/composite_command.hpp"
34+
#include "../command/note_edit_command.hpp"
2835
#include "../instrument_request.hpp"
2936
#include "../note_converter.hpp"
3037
#include "audio_service.hpp"
@@ -1864,7 +1871,7 @@ void EditorService::requestSelectionPaste()
18641871
const auto & interpolation = automation.interpolation();
18651872
for (size_t line = interpolation.line0; line <= interpolation.line1; ++line) {
18661873
const auto existing = m_automationService->midiCcAutomationsByLine(location.pattern(), location.track(), location.column(), line);
1867-
for (auto && ex : existing) {
1874+
for (const auto & ex : existing) {
18681875
if (std::ranges::find(midiCcDeletions, ex) == midiCcDeletions.end()) {
18691876
midiCcDeletions.push_back(ex);
18701877
}
@@ -1877,7 +1884,7 @@ void EditorService::requestSelectionPaste()
18771884
const auto & interpolation = automation.interpolation();
18781885
for (size_t line = interpolation.line0; line <= interpolation.line1; ++line) {
18791886
const auto existing = m_automationService->pitchBendAutomationsByLine(location.pattern(), location.track(), location.column(), line);
1880-
for (auto && ex : existing) {
1887+
for (const auto & ex : existing) {
18811888
if (std::ranges::find(pitchBendDeletions, ex) == pitchBendDeletions.end()) {
18821889
pitchBendDeletions.push_back(ex);
18831890
}

src/application/service/editor_service.hpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ class EditorService : public QObject
173173
virtual Q_INVOKABLE bool isTrackVisible(quint64 track) const;
174174
virtual Q_INVOKABLE bool isModified() const;
175175
virtual Q_INVOKABLE void resetModified();
176+
virtual void setIsModified(bool isModified);
176177

177178
virtual Q_INVOKABLE Position position() const;
178179
virtual Q_INVOKABLE quint64 positionBarLine() const;
@@ -296,8 +297,6 @@ class EditorService : public QObject
296297
virtual ColumnSettingsS columnSettings(quint64 trackIndex, quint64 columnIndex) const;
297298
virtual void setColumnSettings(quint64 trackIndex, quint64 columnIndex, ColumnSettingsS settings);
298299

299-
virtual void setIsModified(bool isModified);
300-
301300
virtual Q_INVOKABLE bool canUndo() const;
302301
virtual Q_INVOKABLE bool canRedo() const;
303302
virtual Q_INVOKABLE void undo();

0 commit comments

Comments
 (0)