Skip to content

Commit dc6f6a3

Browse files
committed
Show the assigned track names in the device rack dialog
1 parent 084a786 commit dc6f6a3

10 files changed

Lines changed: 373 additions & 59 deletions

File tree

src/application/application.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ Application::Application(int & argc, char ** argv)
9797
, m_deviceRack { std::make_unique<DeviceRack>(m_deviceService) }
9898
, m_samplerController { std::make_shared<SamplerController>(std::make_shared<SamplerDevice>("Default Sampler")) }
9999
, m_synthController { std::make_shared<SynthController>(std::make_shared<SynthDevice>("Default Synth")) }
100-
, m_deviceRackController { std::make_shared<DeviceRackController>(m_deviceService, m_samplerController, m_synthController) }
100+
, m_deviceRackController { std::make_shared<DeviceRackController>(m_deviceService, m_samplerController, m_synthController, m_editorService) }
101101
, m_jackService { std::make_shared<JackService>(m_settingsService, m_audioEngine) }
102102
, m_audioService { std::make_shared<AudioService>(m_settingsService, m_jackService, m_audioEngine) }
103103
, m_eventSelectionModel { std::make_shared<EventSelectionModel>() }

src/application/service/device_rack_controller.cpp

Lines changed: 72 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,28 +14,93 @@
1414
// along with Noteahead. If not, see <http://www.gnu.org/licenses/>.
1515

1616
#include "device_rack_controller.hpp"
17+
18+
#include "../../common/constants.hpp"
19+
#include "../../domain/devices/sampler_device.hpp"
20+
#include "../../domain/devices/synth_device.hpp"
1721
#include "device_service.hpp"
22+
#include "editor_service.hpp"
1823
#include "sampler_controller.hpp"
1924
#include "synth_controller.hpp"
20-
#include "../../domain/devices/sampler_device.hpp"
21-
#include "../../domain/devices/synth_device.hpp"
2225

2326
namespace noteahead {
2427

25-
DeviceRackController::DeviceRackController(DeviceServiceS deviceService, SamplerControllerS samplerController, SynthControllerS synthController, QObject * parent)
26-
: QObject { parent }
28+
DeviceRackController::DeviceRackController(DeviceServiceS deviceService, SamplerControllerS samplerController, SynthControllerS synthController, EditorServiceS editorService, QObject * parent)
29+
: QAbstractListModel { parent }
2730
, m_deviceService { std::move(deviceService) }
2831
, m_samplerController { std::move(samplerController) }
2932
, m_synthController { std::move(synthController) }
33+
, m_editorService { std::move(editorService) }
3034
{
31-
connect(m_deviceService.get(), &DeviceService::dataChanged, this, &DeviceRackController::devicesChanged);
35+
if (m_deviceService) {
36+
m_devices = m_deviceService->internalDeviceNamesQt();
37+
connect(m_deviceService.get(), &DeviceService::dataChanged, this, &DeviceRackController::refresh);
38+
}
39+
if (m_editorService) {
40+
connect(m_editorService.get(), &EditorService::songChanged, this, &DeviceRackController::refresh);
41+
}
3242
}
3343

3444
DeviceRackController::~DeviceRackController() = default;
3545

36-
QStringList DeviceRackController::devices() const
46+
int DeviceRackController::rowCount(const QModelIndex & parent) const
3747
{
38-
return m_deviceService->internalDeviceNamesQt();
48+
if (parent.isValid()) {
49+
return 0;
50+
}
51+
return m_devices.size();
52+
}
53+
54+
QVariant DeviceRackController::data(const QModelIndex & index, int role) const
55+
{
56+
if (!index.isValid()) {
57+
return {};
58+
}
59+
60+
const auto row = index.row();
61+
if (row < 0 || row >= m_devices.size()) {
62+
return {};
63+
}
64+
65+
const auto & name = m_devices.at(row);
66+
67+
switch (static_cast<DataRole>(role)) {
68+
case DataRole::Name:
69+
return name;
70+
case DataRole::TrackNames:
71+
return trackNames(name);
72+
}
73+
74+
return {};
75+
}
76+
77+
QHash<int, QByteArray> DeviceRackController::roleNames() const
78+
{
79+
static const QHash<int, QByteArray> roles {
80+
{ static_cast<int>(DataRole::Name), "name" },
81+
{ static_cast<int>(DataRole::TrackNames), "trackNames" }
82+
};
83+
return roles;
84+
}
85+
86+
void DeviceRackController::refresh()
87+
{
88+
beginResetModel();
89+
if (m_deviceService) {
90+
m_devices = m_deviceService->internalDeviceNamesQt();
91+
}
92+
endResetModel();
93+
}
94+
95+
QString DeviceRackController::trackNames(const QString & deviceName) const
96+
{
97+
QStringList trackNames;
98+
for (const auto index : m_editorService->trackIndices()) {
99+
if (const auto portName = m_editorService->instrumentPortName(index); portName == deviceName) {
100+
trackNames << m_editorService->trackName(index);
101+
}
102+
}
103+
return trackNames.join(", ");
39104
}
40105

41106
void DeviceRackController::openDevice(const QString & name)

src/application/service/device_rack_controller.hpp

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,42 +16,59 @@
1616
#ifndef DEVICE_RACK_CONTROLLER_HPP
1717
#define DEVICE_RACK_CONTROLLER_HPP
1818

19+
#include <QAbstractListModel>
1920
#include <QObject>
2021
#include <QStringList>
22+
2123
#include <memory>
2224

2325
namespace noteahead {
2426

2527
class DeviceService;
28+
class EditorService;
2629
class SamplerController;
2730
class SynthController;
2831

29-
class DeviceRackController : public QObject
32+
class DeviceRackController : public QAbstractListModel
3033
{
3134
Q_OBJECT
32-
Q_PROPERTY(QStringList devices READ devices NOTIFY devicesChanged)
3335

3436
public:
37+
enum class DataRole
38+
{
39+
Name = Qt::UserRole + 1,
40+
TrackNames
41+
};
42+
Q_ENUM(DataRole)
43+
3544
using DeviceServiceS = std::shared_ptr<DeviceService>;
3645
using SamplerControllerS = std::shared_ptr<SamplerController>;
3746
using SynthControllerS = std::shared_ptr<SynthController>;
47+
using EditorServiceS = std::shared_ptr<EditorService>;
3848

39-
explicit DeviceRackController(DeviceServiceS deviceService, SamplerControllerS samplerController, SynthControllerS synthController, QObject * parent = nullptr);
49+
explicit DeviceRackController(DeviceServiceS deviceService, SamplerControllerS samplerController, SynthControllerS synthController, EditorServiceS editorService, QObject * parent = nullptr);
4050
~DeviceRackController() override;
4151

42-
QStringList devices() const;
52+
int rowCount(const QModelIndex & parent = QModelIndex()) const override;
53+
QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override;
54+
QHash<int, QByteArray> roleNames() const override;
4355

56+
Q_INVOKABLE void refresh();
4457
Q_INVOKABLE void openDevice(const QString & name);
4558

4659
signals:
47-
void devicesChanged();
4860
void samplerDialogRequested();
4961
void synthDialogRequested();
5062

5163
private:
64+
QString trackNames(const QString & deviceName) const;
65+
5266
DeviceServiceS m_deviceService;
5367
SamplerControllerS m_samplerController;
5468
SynthControllerS m_synthController;
69+
EditorServiceS m_editorService;
70+
71+
QStringList m_devices;
5572
};
5673

5774
} // namespace noteahead

src/application/service/device_service.hpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,12 @@ class DeviceService : public QObject
5454
void processMidiAllNotesOff();
5555

5656
using InternalDeviceNames = std::vector<std::string>;
57-
InternalDeviceNames internalDeviceNames() const;
57+
virtual InternalDeviceNames internalDeviceNames() const;
5858

59-
Q_INVOKABLE QStringList internalDeviceNamesQt() const;
59+
Q_INVOKABLE virtual QStringList internalDeviceNamesQt() const;
6060

61-
Q_INVOKABLE QStringList categories() const;
62-
Q_INVOKABLE QStringList devicesByCategory(const QString & category) const;
61+
Q_INVOKABLE virtual QStringList categories() const;
62+
Q_INVOKABLE virtual QStringList devicesByCategory(const QString & category) const;
6363

6464
void setSynthUserPresets(const UserPresets & presets);
6565
UserPresets synthUserPresets() const;

src/unit_tests/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ add_subdirectory(application_service_test)
33
add_subdirectory(arpeggiator_test)
44
add_subdirectory(audio_file_io_test)
55
add_subdirectory(automation_service_test)
6+
add_subdirectory(device_rack_controller_test)
67
add_subdirectory(editor_service_test)
78
add_subdirectory(editor_service_undo_test)
89
add_subdirectory(keyboard_service_test)
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
2+
3+
set(NAME device_rack_controller_test)
4+
5+
set(SRC
6+
${NAME}.cpp
7+
${NAME}.hpp
8+
../../application/command/automation_command.cpp
9+
../../application/command/composite_command.cpp
10+
../../application/command/note_edit_command.cpp
11+
../../application/command/undo_stack.cpp
12+
../../application/instrument_request.cpp
13+
../../application/models/sampler/sampler_pad_model.cpp
14+
../../application/note_converter.cpp
15+
../../application/position.cpp
16+
../../application/service/automation_service.cpp
17+
../../application/service/copy_manager.cpp
18+
../../application/service/device_rack_controller.cpp
19+
../../application/service/device_service.cpp
20+
../../application/service/editor_service.cpp
21+
../../application/service/mixer_service.cpp
22+
../../application/service/property_service.cpp
23+
../../application/service/random_service.cpp
24+
../../application/service/sampler_controller.cpp
25+
../../application/service/selection_service.cpp
26+
../../application/service/settings_service.cpp
27+
../../application/service/side_chain_service.cpp
28+
../../application/service/synth_controller.cpp
29+
../../common/constants.cpp
30+
../../common/utils.cpp
31+
../../common/waveform_generator.cpp
32+
../../domain/arpeggiator.cpp
33+
../../domain/automation.cpp
34+
../../domain/automation_location.cpp
35+
../../domain/column.cpp
36+
../../domain/column_settings.cpp
37+
../../domain/devices/delay_effect.cpp
38+
../../domain/devices/device.cpp
39+
../../domain/devices/effect.cpp
40+
../../domain/devices/high_pass_filter_effect.cpp
41+
../../domain/devices/low_pass_filter_effect.cpp
42+
../../domain/devices/panning_effect.cpp
43+
../../domain/devices/sampler_device.cpp
44+
../../domain/devices/synth_device.cpp
45+
../../domain/devices/synth_presets.cpp
46+
../../domain/devices/volume_effect.cpp
47+
../../domain/dsp/adsr_envelope.cpp
48+
../../domain/dsp/cascaded_svf.cpp
49+
../../domain/dsp/dsp_component.cpp
50+
../../domain/dsp/lfo.cpp
51+
../../domain/dsp/multi_engine.cpp
52+
../../domain/dsp/oversampler.cpp
53+
../../domain/dsp/polyblep_oscillator.cpp
54+
../../domain/event.cpp
55+
../../domain/event_data.cpp
56+
../../domain/instrument.cpp
57+
../../domain/instrument_settings.cpp
58+
../../domain/interpolator.cpp
59+
../../domain/line.cpp
60+
../../domain/line_event.cpp
61+
../../domain/midi_address.cpp
62+
../../domain/midi_cc_automation.cpp
63+
../../domain/midi_cc_data.cpp
64+
../../domain/midi_cc_setting.cpp
65+
../../domain/midi_note_data.cpp
66+
../../domain/mixer_unit.cpp
67+
../../domain/note_data.cpp
68+
../../domain/note_data_manipulator.cpp
69+
../../domain/parameter.cpp
70+
../../domain/parameter_container.cpp
71+
../../domain/pattern.cpp
72+
../../domain/pitch_bend_automation.cpp
73+
../../domain/pitch_bend_data.cpp
74+
../../domain/play_order.cpp
75+
../../domain/song.cpp
76+
../../domain/track.cpp
77+
../../infra/audio/audio_engine.cpp
78+
../../infra/audio/backend/sndfile_reader.cpp
79+
../../infra/midi/midi_cc_mapping.cpp
80+
../../infra/settings.cpp
81+
)
82+
83+
qt_add_executable(${NAME} ${SRC})
84+
85+
set_target_properties(${NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${UNIT_TEST_BASE_DIR})
86+
87+
add_test(${NAME} ${UNIT_TEST_BASE_DIR}/${NAME})
88+
89+
target_link_libraries(${NAME} PRIVATE
90+
Qt${QT_VERSION_MAJOR}::Gui
91+
Qt${QT_VERSION_MAJOR}::Test
92+
SimpleLogger
93+
${SNDFILE_LIBRARIES}
94+
)
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// This file is part of Noteahead.
2+
// Copyright (C) 2026 Jussi Lind <jussi.lind@iki.fi>
3+
//
4+
// Noteahead is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
// Noteahead is distributed in the hope that it will be useful,
9+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
// GNU General Public License for more details.
12+
//
13+
// You should have received a copy of the GNU General Public License
14+
// along with Noteahead. If not, see <http://www.gnu.org/licenses/>.
15+
16+
#include "device_rack_controller_test.hpp"
17+
18+
#include "../../application/service/device_rack_controller.hpp"
19+
#include "../../application/service/device_service.hpp"
20+
#include "../../application/service/editor_service.hpp"
21+
#include "../../application/service/sampler_controller.hpp"
22+
#include "../../application/service/synth_controller.hpp"
23+
#include "../../common/constants.hpp"
24+
25+
#include <QSignalSpy>
26+
#include <QTest>
27+
28+
namespace noteahead {
29+
30+
class MockDeviceService : public DeviceService
31+
{
32+
public:
33+
MockDeviceService() : DeviceService(nullptr) {}
34+
QStringList internalDeviceNamesQt() const override { return m_names; }
35+
void setMockNames(const QStringList & names) { m_names = names; }
36+
private:
37+
QStringList m_names;
38+
};
39+
40+
class MockEditorService : public EditorService
41+
{
42+
public:
43+
std::vector<quint64> trackIndices() const override { return m_indices; }
44+
void setMockIndices(const std::vector<quint64> & indices) { m_indices = indices; }
45+
46+
QString trackName(quint64 trackIndex) const override { return m_names.at(trackIndex); }
47+
void setMockTrackName(quint64 trackIndex, const QString & name) { m_names[trackIndex] = name; }
48+
49+
QString instrumentPortName(quint64 trackIndex) const override { return m_ports.at(trackIndex); }
50+
void setMockInstrumentPortName(quint64 trackIndex, const QString & port) { m_ports[trackIndex] = port; }
51+
52+
private:
53+
std::vector<quint64> m_indices;
54+
std::map<quint64, QString> m_names;
55+
std::map<quint64, QString> m_ports;
56+
};
57+
58+
void DeviceRackControllerTest::test_devices()
59+
{
60+
const auto deviceService = std::make_shared<MockDeviceService>();
61+
const auto names = QStringList { "Sampler 1", "Synth 1" };
62+
deviceService->setMockNames(names);
63+
64+
DeviceRackController controller(deviceService, nullptr, nullptr, nullptr);
65+
QCOMPARE(controller.rowCount(), 2);
66+
QCOMPARE(controller.data(controller.index(0), static_cast<int>(DeviceRackController::DataRole::Name)).toString(), names.at(0));
67+
QCOMPARE(controller.data(controller.index(1), static_cast<int>(DeviceRackController::DataRole::Name)).toString(), names.at(1));
68+
}
69+
70+
void DeviceRackControllerTest::test_trackNames()
71+
{
72+
const auto deviceService = std::make_shared<MockDeviceService>();
73+
deviceService->setMockNames({ "Sampler 1", "Synth 1" });
74+
75+
const auto editorService = std::make_shared<MockEditorService>();
76+
editorService->setMockIndices({ 0, 1, 2, 3 });
77+
editorService->setMockTrackName(0, "Track 1");
78+
editorService->setMockInstrumentPortName(0, "Sampler 1");
79+
editorService->setMockTrackName(1, "Track 2");
80+
editorService->setMockInstrumentPortName(1, "Synth 1");
81+
editorService->setMockTrackName(2, "Track 3");
82+
editorService->setMockInstrumentPortName(2, "Sampler 1");
83+
editorService->setMockTrackName(3, "Track 4");
84+
editorService->setMockInstrumentPortName(3, "Synth 1");
85+
86+
DeviceRackController controller(deviceService, nullptr, nullptr, editorService);
87+
88+
QCOMPARE(controller.data(controller.index(0), static_cast<int>(DeviceRackController::DataRole::TrackNames)).toString(), QString("Track 1, Track 3"));
89+
QCOMPARE(controller.data(controller.index(1), static_cast<int>(DeviceRackController::DataRole::TrackNames)).toString(), QString("Track 2, Track 4"));
90+
}
91+
92+
void DeviceRackControllerTest::test_openDevice()
93+
{
94+
// This test would require more complex mocking of DeviceService to return real Device objects
95+
// or further mocking of Sampler/Synth controllers.
96+
// For now, let's focus on the requested trackNames functionality.
97+
}
98+
99+
} // namespace noteahead
100+
101+
QTEST_GUILESS_MAIN(noteahead::DeviceRackControllerTest)

0 commit comments

Comments
 (0)