Skip to content

Commit f39f3c1

Browse files
committed
audio: implement an ASIO-based audio backend. Fixes #1422
1 parent 4439173 commit f39f3c1

5 files changed

Lines changed: 225 additions & 1 deletion

File tree

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
#pragma once
2+
#include <ossia/detail/config.hpp>
3+
#include <Audio/AudioInterface.hpp>
4+
#include <Audio/Settings/Model.hpp>
5+
#include <Audio/Settings/View.hpp>
6+
7+
#include <score/command/Dispatchers/SettingsCommandDispatcher.hpp>
8+
#include <score/tools/Bind.hpp>
9+
#include <score/widgets/SignalUtils.hpp>
10+
11+
#include <QComboBox>
12+
#include <QFormLayout>
13+
#include <QPushButton>
14+
#include <QLabel>
15+
16+
#if defined(OSSIA_ENABLE_ASIO)
17+
#include <ossia/audio/asio_protocol.hpp>
18+
19+
extern AsioDrivers* asioDrivers;
20+
namespace Audio
21+
{
22+
struct ASIOCard
23+
{
24+
QString name;
25+
int driver_index{-1};
26+
int inputChan{};
27+
int outputChan{};
28+
};
29+
30+
class NativeASIOFactory final
31+
: public QObject
32+
, public AudioFactory
33+
{
34+
SCORE_CONCRETE("2d21a3aa-f108-4e05-a3a0-8fd6b150bda5")
35+
public:
36+
std::vector<ASIOCard> devices;
37+
38+
NativeASIOFactory() { rescan(); }
39+
~NativeASIOFactory() override { }
40+
41+
bool available() const noexcept override { return !devices.empty(); }
42+
43+
void
44+
initialize(Audio::Settings::Model& set, const score::ApplicationContext& ctx) override
45+
{
46+
rescan();
47+
48+
if(!set.getCardOut().isEmpty())
49+
{
50+
// Check that the saved device still exists
51+
auto it = std::find_if(
52+
devices.begin(), devices.end(),
53+
[&](const ASIOCard& d) { return d.name == set.getCardOut(); });
54+
if(it != devices.end())
55+
return;
56+
}
57+
58+
// Auto-select first device
59+
if(devices.size() > 1)
60+
{
61+
auto& dev = devices[1]; // [0] is "No device"
62+
set.setCardIn(dev.name);
63+
set.setCardOut(dev.name);
64+
set.setDefaultIn(dev.inputChan);
65+
set.setDefaultOut(dev.outputChan);
66+
}
67+
}
68+
69+
void rescan()
70+
{
71+
devices.clear();
72+
devices.push_back(ASIOCard{QObject::tr("No device"), -1, 0, 0});
73+
74+
try
75+
{
76+
auto cards = ossia::asio_engine::enumerate_drivers();
77+
for(auto& card : cards)
78+
{
79+
// To get channel counts, we must briefly load each driver
80+
int ins = 0, outs = 0;
81+
if(loadAsioDriver(const_cast<char*>(card.name.c_str())))
82+
{
83+
ASIODriverInfo info{};
84+
info.asioVersion = 2;
85+
if(ASIOInit(&info) == ASE_OK)
86+
{
87+
long numIn = 0, numOut = 0;
88+
ASIOGetChannels(&numIn, &numOut);
89+
ins = (int)numIn;
90+
outs = (int)numOut;
91+
ASIOExit();
92+
}
93+
if(asioDrivers)
94+
asioDrivers->removeCurrentDriver();
95+
}
96+
97+
devices.push_back(
98+
ASIOCard{QString::fromStdString(card.name), card.driver_index, ins, outs});
99+
}
100+
}
101+
catch(...)
102+
{
103+
}
104+
}
105+
106+
QString prettyName() const override { return QObject::tr("ASIOSDK"); }
107+
108+
std::shared_ptr<ossia::audio_engine> make_engine(
109+
const Audio::Settings::Model& set, const score::ApplicationContext& ctx) override
110+
{
111+
return std::make_shared<ossia::asio_engine>(
112+
set.getCardOut().toStdString(), set.getDefaultIn(), set.getDefaultOut(),
113+
set.getRate(), set.getBufferSize());
114+
}
115+
116+
void setCard(QComboBox* combo, QString val)
117+
{
118+
for(int i = 0; i < combo->count(); i++)
119+
{
120+
if(combo->itemData(i).toString() == val)
121+
{
122+
combo->setCurrentIndex(i);
123+
return;
124+
}
125+
}
126+
}
127+
128+
QWidget* make_settings(
129+
Audio::Settings::Model& m, Audio::Settings::View& v,
130+
score::SettingsCommandDispatcher& m_disp, QWidget* parent) override
131+
{
132+
auto w = new QWidget{parent};
133+
auto lay = new QFormLayout{w};
134+
135+
auto card_list = new QComboBox{w};
136+
auto show_ui = new QPushButton{tr("Show Control Panel"), w};
137+
138+
// Populate device list
139+
for(std::size_t i = 0; i < devices.size(); i++)
140+
{
141+
auto& card = devices[i];
142+
card_list->addItem(card.name, card.name);
143+
}
144+
145+
using Model = Audio::Settings::Model;
146+
147+
{
148+
lay->addRow(QObject::tr("Device"), card_list);
149+
150+
auto update_dev = [=, &m, &m_disp](const ASIOCard& dev) {
151+
if(dev.name != m.getCardOut())
152+
{
153+
m_disp.submitDeferredCommand<Audio::Settings::SetModelCardIn>(m, dev.name);
154+
m_disp.submitDeferredCommand<Audio::Settings::SetModelCardOut>(m, dev.name);
155+
m_disp.submitDeferredCommand<Audio::Settings::SetModelDefaultIn>(
156+
m, dev.inputChan);
157+
m_disp.submitDeferredCommand<Audio::Settings::SetModelDefaultOut>(
158+
m, dev.outputChan);
159+
}
160+
};
161+
162+
QObject::connect(
163+
card_list, SignalUtils::QComboBox_currentIndexChanged_int(), &v, [=](int i) {
164+
if(i >= 0 && i < (int)devices.size())
165+
{
166+
update_dev(devices[i]);
167+
}
168+
});
169+
170+
if(m.getCardOut().isEmpty())
171+
{
172+
if(devices.size() > 1)
173+
{
174+
update_dev(devices[1]);
175+
}
176+
}
177+
else
178+
{
179+
setCard(card_list, m.getCardOut());
180+
}
181+
}
182+
183+
{
184+
lay->addWidget(show_ui);
185+
connect(show_ui, &QPushButton::clicked, this, [=] {
186+
int idx = card_list->currentIndex();
187+
if(idx > 0 && idx < (int)devices.size())
188+
{
189+
ossia::asio_engine::open_control_panel(devices[idx].name.toStdString());
190+
}
191+
});
192+
}
193+
194+
addBufferSizeWidget(*w, m, v);
195+
addSampleRateWidget(*w, m, v);
196+
197+
con(m, &Model::changed, w, [=, &m] { setCard(card_list, m.getCardOut()); });
198+
return w;
199+
}
200+
};
201+
202+
}
203+
204+
#endif

src/plugins/score-plugin-audio/Audio/Settings/Model.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ void Model::initDriver(Audio::AudioFactory::ConcreteKey val)
9292
uid = "3533ee88-9a8d-486c-b20b-6c966cf4eaa0";
9393
else if(env == "alsa_miniaudio")
9494
uid = "e0c533da-a1f4-4795-90b5-a805cdfcb79f";
95+
else if(env == "asio")
96+
uid = "2d21a3aa-f108-4e05-a3a0-8fd6b150bda5";
9597

9698
if(!uid.isEmpty())
9799
{

src/plugins/score-plugin-audio/CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ set(HDRS
1313
"${CMAKE_CURRENT_SOURCE_DIR}/Audio/ALSAInterface.hpp"
1414
"${CMAKE_CURRENT_SOURCE_DIR}/Audio/ALSAMiniAudioInterface.hpp"
1515
"${CMAKE_CURRENT_SOURCE_DIR}/Audio/ALSAPortAudioInterface.hpp"
16+
"${CMAKE_CURRENT_SOURCE_DIR}/Audio/ASIOInterface.hpp"
1617
"${CMAKE_CURRENT_SOURCE_DIR}/Audio/ASIOPortAudioInterface.hpp"
1718
"${CMAKE_CURRENT_SOURCE_DIR}/Audio/CoreAudioInterface.hpp"
1819
"${CMAKE_CURRENT_SOURCE_DIR}/Audio/CoreAudioPortAudioInterface.hpp"
@@ -89,6 +90,11 @@ if(OSSIA_ENABLE_PIPEWIRE)
8990
list(APPEND SCORE_FEATURES_LIST pipewire)
9091
endif()
9192

93+
if(OSSIA_ENABLE_ASIO AND TARGET asio::sdk)
94+
target_link_libraries(${PROJECT_NAME} PRIVATE $<BUILD_INTERFACE:asio::sdk>)
95+
list(APPEND SCORE_FEATURES_LIST asio)
96+
endif()
97+
9298
if(OSSIA_ENABLE_SDL)
9399
target_link_libraries(${PROJECT_NAME} PRIVATE $<BUILD_INTERFACE:ossia::sdl2>)
94100
list(APPEND SCORE_FEATURES_LIST sdl)

src/plugins/score-plugin-audio/score_plugin_audio.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <Audio/ALSAInterface.hpp>
77
#include <Audio/ALSAMiniAudioInterface.hpp>
88
#include <Audio/ALSAPortAudioInterface.hpp>
9+
#include <Audio/ASIOInterface.hpp>
910
#include <Audio/ASIOPortAudioInterface.hpp>
1011
#include <Audio/AudioApplicationPlugin.hpp>
1112
#include <Audio/AudioDevice.hpp>
@@ -151,6 +152,13 @@ std::vector<score::InterfaceBase*> score_plugin_audio::factories(
151152
#if defined(__linux__) && defined(OSSIA_AUDIO_MINIAUDIO)
152153
add_factories<FW<Audio::AudioFactory, Audio::ALSAMiniAudioFactory>>(vec, ctx, key);
153154
return vec;
155+
#endif
156+
}
157+
else if(forced_backend == "asio")
158+
{
159+
#if defined(OSSIA_AUDIO_ASIO)
160+
add_factories<FW<Audio::AudioFactory, Audio::NativeASIOFactory>>(vec, ctx, key);
161+
return vec;
154162
#endif
155163
}
156164
else if(forced_backend == "dummy")
@@ -174,6 +182,10 @@ std::vector<score::InterfaceBase*> score_plugin_audio::factories(
174182
,
175183
Audio::ALSAFactory
176184
#endif
185+
#if defined(OSSIA_AUDIO_ASIO)
186+
,
187+
Audio::NativeASIOFactory
188+
#endif
177189
#if defined(OSSIA_AUDIO_PORTAUDIO)
178190
#if __has_include(<pa_asio.h>)
179191
,

0 commit comments

Comments
 (0)