Skip to content

Commit 00dbedd

Browse files
committed
Put the chosen audio devices back after a reinitialize
- Reinitializing builds a new recorder and player, which come up on the system default. The chosen devices were only refreshed in the lists and never handed down again, so the boxes showed an input nothing was recording from until it was changed away and back - Startup does exactly that: one reinitialize right after the saved devices were applied
1 parent 4935c36 commit 00dbedd

6 files changed

Lines changed: 177 additions & 2 deletions

File tree

‎src/application/models/audio_settings_model.cpp‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@ AudioSettingsModel::AudioSettingsModel(AudioServiceS audioService, SettingsServi
3939
connect(m_audioService.get(), &AudioService::reinitialized, this, [this]() {
4040
refreshInputDevices();
4141
refreshOutputDevices();
42+
// Reinitializing builds a new recorder and a new player, and they come up on whatever the
43+
// system calls the default. The chosen devices have to be put back onto them, or the boxes go
44+
// on showing a choice that nothing is acting on -- which is what the application's own
45+
// startup does, one reinitialize after this model applied the saved devices.
46+
m_audioService->setInputDevice(m_selectedInputDeviceId);
47+
m_audioService->setOutputDevice(m_selectedOutputDeviceId);
4248
});
4349
}
4450

‎src/application/service/audio_service.hpp‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,9 @@ private slots:
9494
Q_INVOKABLE void setInputMeterActive(bool active);
9595

9696
Q_INVOKABLE QVariantList getInputDevices();
97-
Q_INVOKABLE void setInputDevice(int deviceId);
97+
Q_INVOKABLE virtual void setInputDevice(int deviceId);
9898
Q_INVOKABLE QVariantList getOutputDevices();
99-
Q_INVOKABLE void setOutputDevice(int deviceId);
99+
Q_INVOKABLE virtual void setOutputDevice(int deviceId);
100100

101101
Q_INVOKABLE QString latestRecordingFileName() const;
102102
Q_INVOKABLE bool isRecording() const;

‎src/unit_tests/CMakeLists.txt‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ add_subdirectory(application_service_test)
88
add_subdirectory(arpeggiator_test)
99
add_subdirectory(audio_analysis_service_test)
1010
add_subdirectory(audio_service_test)
11+
add_subdirectory(audio_settings_model_test)
1112
add_subdirectory(audio_file_io_test)
1213
add_subdirectory(audio_scope_test)
1314
add_subdirectory(auto_ducker_test)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${PROJECT_SOURCE_DIR}/src ${PROJECT_SOURCE_DIR}/src/contrib/SimpleLogger/src)
2+
set(NAME audio_settings_model_test)
3+
set(SRC
4+
${NAME}.cpp
5+
${NAME}.hpp)
6+
qt_add_executable(${NAME} ${SRC})
7+
set_target_properties(${NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${UNIT_TEST_BASE_DIR})
8+
add_test(${NAME} ${UNIT_TEST_BASE_DIR}/${NAME})
9+
target_link_libraries(${NAME} PRIVATE ApplicationLib Argengine_static CommonLib DomainLib InfraLib SimpleLogger_static ViewLib Qt${QT_VERSION_MAJOR}::Test Qt${QT_VERSION_MAJOR}::Gui SimpleLogger_static PkgConfig::RTMIDI PkgConfig::JACK PkgConfig::SNDFILE PkgConfig::RTAUDIO)
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
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 "audio_settings_model_test.hpp"
17+
18+
#include "../../application/models/audio_settings_model.hpp"
19+
#include "../../application/service/audio_service.hpp"
20+
#include "../../application/service/jack_service.hpp"
21+
#include "../../application/service/settings_service.hpp"
22+
#include "../../infra/audio/audio_engine.hpp"
23+
24+
#include <QSettings>
25+
#include <QTemporaryDir>
26+
#include <QTest>
27+
28+
#include <vector>
29+
30+
namespace noteahead {
31+
32+
namespace {
33+
34+
//! Settings of this test's own, since the model reads and writes the persisted device choice.
35+
QTemporaryDir & settingsDirectory()
36+
{
37+
static QTemporaryDir directory;
38+
return directory;
39+
}
40+
41+
//! An AudioService that records what it was told to use instead of opening anything.
42+
//!
43+
//! autoInitialize is off, so there is no worker and no stream: what is left is the choice being
44+
//! handed down, which is the whole subject here.
45+
class RecordingAudioService : public AudioService
46+
{
47+
public:
48+
RecordingAudioService(SettingsServiceS settingsService, AudioEngineS audioEngine)
49+
: AudioService { settingsService, std::make_shared<JackService>(settingsService, audioEngine), audioEngine, nullptr, false }
50+
{
51+
}
52+
53+
void setInputDevice(int deviceId) override
54+
{
55+
inputDeviceIds.push_back(deviceId);
56+
}
57+
58+
void setOutputDevice(int deviceId) override
59+
{
60+
outputDeviceIds.push_back(deviceId);
61+
}
62+
63+
//! Every device this service was told to use, in order.
64+
std::vector<int> inputDeviceIds;
65+
std::vector<int> outputDeviceIds;
66+
};
67+
68+
} // namespace
69+
70+
void AudioSettingsModelTest::initTestCase()
71+
{
72+
QCoreApplication::setOrganizationName("NoteaheadTest");
73+
QCoreApplication::setApplicationName("AudioSettingsModelTest");
74+
QVERIFY(settingsDirectory().isValid());
75+
QSettings::setDefaultFormat(QSettings::IniFormat);
76+
QSettings::setPath(QSettings::IniFormat, QSettings::UserScope, settingsDirectory().path());
77+
}
78+
79+
void AudioSettingsModelTest::test_construction_shouldApplyTheSavedDevices()
80+
{
81+
const auto settingsService = std::make_shared<SettingsService>();
82+
settingsService->setAudioInputDeviceId(7);
83+
settingsService->setAudioOutputDeviceId(9);
84+
85+
const auto audioService = std::make_shared<RecordingAudioService>(settingsService, std::make_shared<AudioEngine>());
86+
AudioSettingsModel model { audioService, settingsService };
87+
88+
QCOMPARE(model.selectedInputDeviceId(), 7);
89+
QCOMPARE(model.selectedOutputDeviceId(), 9);
90+
QCOMPARE(audioService->inputDeviceIds, std::vector<int> { 7 });
91+
QCOMPARE(audioService->outputDeviceIds, std::vector<int> { 9 });
92+
}
93+
94+
void AudioSettingsModelTest::test_reinitialized_shouldPutTheChosenDevicesBack()
95+
{
96+
// Reinitializing builds a new recorder and player, which come up on the system default. The
97+
// choice was only refreshed in the lists and never handed down again, so the boxes went on
98+
// showing a device nothing was recording from -- and the application reinitializes once at
99+
// startup, right after this model has applied the saved one. Changing the box and changing it back
100+
// was the only way to make it take.
101+
const auto settingsService = std::make_shared<SettingsService>();
102+
settingsService->setAudioInputDeviceId(7);
103+
settingsService->setAudioOutputDeviceId(9);
104+
105+
const auto audioService = std::make_shared<RecordingAudioService>(settingsService, std::make_shared<AudioEngine>());
106+
AudioSettingsModel model { audioService, settingsService };
107+
QCOMPARE(audioService->inputDeviceIds.size(), static_cast<size_t>(1));
108+
109+
emit audioService->reinitialized();
110+
111+
QCOMPARE(audioService->inputDeviceIds.size(), static_cast<size_t>(2));
112+
QCOMPARE(audioService->inputDeviceIds.back(), 7);
113+
QCOMPARE(audioService->outputDeviceIds.size(), static_cast<size_t>(2));
114+
QCOMPARE(audioService->outputDeviceIds.back(), 9);
115+
116+
// And what the user picked since, rather than what was saved at startup.
117+
model.setSelectedInputDeviceId(11);
118+
emit audioService->reinitialized();
119+
QCOMPARE(audioService->inputDeviceIds.back(), 11);
120+
}
121+
122+
} // namespace noteahead
123+
124+
QTEST_GUILESS_MAIN(noteahead::AudioSettingsModelTest)
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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+
#ifndef AUDIO_SETTINGS_MODEL_TEST_HPP
17+
#define AUDIO_SETTINGS_MODEL_TEST_HPP
18+
19+
#include <QObject>
20+
21+
namespace noteahead {
22+
23+
class AudioSettingsModelTest : public QObject
24+
{
25+
Q_OBJECT
26+
27+
private slots:
28+
void initTestCase();
29+
void test_construction_shouldApplyTheSavedDevices();
30+
void test_reinitialized_shouldPutTheChosenDevicesBack();
31+
};
32+
33+
} // namespace noteahead
34+
35+
#endif // AUDIO_SETTINGS_MODEL_TEST_HPP

0 commit comments

Comments
 (0)