• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ossia / score / 30826105710

03 Aug 2026 03:08PM UTC coverage: 15.264%. Remained the same
30826105710

Pull #1975

github

web-flow
Merge a8c35e79e into 192676568
Pull Request #1975: audio: add a native ASIO backend on Windows

1 of 15 new or added lines in 3 files covered. (6.67%)

30464 of 199583 relevant lines covered (15.26%)

1075.27 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

70.06
/src/plugins/score-plugin-audio/Audio/AudioApplicationPlugin.cpp
1
#include "AudioApplicationPlugin.hpp"
2

3
#include <Explorer/DocumentPlugin/DeviceDocumentPlugin.hpp>
4

5
#include <Scenario/Application/ScenarioActions.hpp>
6

7
#include <Audio/AudioDevice.hpp>
8
#include <Audio/AudioInterface.hpp>
9
#include <Audio/AudioTick.hpp>
10
#include <Audio/Settings/Model.hpp>
11

12
#include <score/actions/ActionManager.hpp>
13
#include <score/tools/Bind.hpp>
14
#include <score/widgets/ControlWidgets.hpp>
15
#include <score/widgets/HelpInteraction.hpp>
16
#include <score/widgets/MessageBox.hpp>
17
#include <score/widgets/SetIcons.hpp>
18

19
#include <core/application/ApplicationSettings.hpp>
20
#include <core/document/Document.hpp>
21
#include <core/presenter/DocumentManager.hpp>
22

23
#include <ossia/audio/audio_engine.hpp>
24
#include <ossia/audio/audio_protocol.hpp>
25

26
#include <QTimer>
27
#include <QToolBar>
28

29
SCORE_DECLARE_ACTION(RestartAudio, "Restart Audio", Common, QKeySequence::UnknownKey)
117✔
30
namespace Audio
31
{
32
ApplicationPlugin::ApplicationPlugin(const score::GUIApplicationContext& ctx)
39✔
33
    : score::GUIApplicationPlugin{ctx}
39✔
34
{
39✔
35
  startTimer(50);
39✔
36
}
×
37

38
void ApplicationPlugin::initialize()
39✔
39
{
40
  auto& set = context.settings<Audio::Settings::Model>();
39✔
41

42
  // First validate the current audio settings
43

44
  auto& engines = score::GUIAppContext().interfaces<Audio::AudioFactoryList>();
39✔
45

46
  if(auto dev = engines.get(set.getDriver()))
39✔
47
  {
48
    dev->initialize(set, this->context);
39✔
49
  }
39✔
50

51
  con(set, &Audio::Settings::Model::changed, this, &ApplicationPlugin::restart_engine,
78✔
52
      Qt::QueuedConnection);
39✔
53
}
39✔
54

55
ApplicationPlugin::~ApplicationPlugin() { }
78✔
56

57
void ApplicationPlugin::on_closeDocument(score::Document& old)
20✔
58
{
59
#if defined(__EMSCRIPTEN__)
60
  // The WebAudio worklet is a page-lifetime singleton that cannot be torn down
61
  // and recreated, so destroying the engine on close would leave playback dead
62
  // until a full page reload (e.g. after File > New). Keep the engine alive and
63
  // just park it on the document-independent pause tick; the next document
64
  // rebinds to it via restart_engine().
65
  if(audio)
66
    audio->set_tick(Audio::makePauseTick(this->context));
67
#else
68
  stop_engine();
20✔
69
#endif
70
}
20✔
71

72
void ApplicationPlugin::on_documentChanged(
39✔
73
    score::Document* olddoc, score::Document* newdoc)
74
{
75
  if(newdoc)
39✔
76
    restart_engine();
20✔
77
}
39✔
78

79
void ApplicationPlugin::timerEvent(QTimerEvent*)
54✔
80
{
81
  if(audio)
54✔
82
    audio->gc();
14✔
83

84
  for(auto it = previous_audio.begin(); it != previous_audio.end();)
74✔
85
  {
86
    auto& engine = **it;
20✔
87
    engine.gc();
20✔
88
    if(engine.stop_received)
20✔
89
    {
90
      it = previous_audio.erase(it);
20✔
91
    }
20✔
92
    else
93
    {
94
      ++it;
×
95
    }
96
  }
97
}
54✔
98

99
score::GUIElements ApplicationPlugin::makeGUIElements()
39✔
100
{
101
  GUIElements e;
39✔
102

103
  auto& toolbars = e.toolbars;
39✔
104

105
  // The toolbar with the volume control
106
  m_audioEngineAct = new QAction{tr("Restart Audio"), this};
39✔
107
  m_audioEngineAct->setCheckable(true);
39✔
108
  m_audioEngineAct->setChecked(bool(audio));
39✔
109
  score::setHelp(m_audioEngineAct, "Restart the audio engine");
39✔
110

111
  setIcons(
39✔
112
      m_audioEngineAct, QStringLiteral(":/icons/engine_on.png"),
39✔
113
      QStringLiteral(":/icons/engine_off.png"),
39✔
114
      QStringLiteral(":/icons/engine_disabled.png"), false);
39✔
115
  {
116
    auto bar = new QToolBar(tr("Volume"));
39✔
117
    auto sl = new score::VolumeSlider{bar};
39✔
118
    sl->setFixedSize(100, 20);
39✔
119
    sl->setValue(0.5);
39✔
120
    score::setHelp(sl, "Change the master volume");
39✔
121
    bar->addWidget(sl);
39✔
122
    bar->addAction(m_audioEngineAct);
39✔
123
    connect(sl, &score::VolumeSlider::valueChanged, this, [this](double v) {
39✔
124
      if(!this->audio)
×
125
        return;
×
126

127
      auto doc = context.currentDocument();
×
128
      if(!doc)
×
129
        return;
×
130
      auto dev = (Dataflow::AudioDevice*)doc->plugin<Explorer::DeviceDocumentPlugin>()
×
131
                     .list()
×
132
                     .audioDevice();
×
133
      if(!dev)
×
134
        return;
×
135
      auto p = dev->getProtocol();
×
136
      if(!p)
×
137
        return;
×
138

139
      auto root = ossia::net::find_node(dev->getDevice()->get_root_node(), "/out/main");
×
140
      if(root)
×
141
      {
142
        if(auto p = root->get_parameter())
×
143
        {
144
          auto audio_p = static_cast<ossia::audio_parameter*>(p);
×
145
          audio_p->push_value(v);
×
146
        }
×
147
      }
×
148
    });
×
149

150
    toolbars.emplace_back(
78✔
151
        bar, StringKey<score::Toolbar>("Audio"), Qt::BottomToolBarArea, 400);
39✔
152
  }
153

154
  e.actions.container.reserve(2);
39✔
155
  e.actions.add<Actions::RestartAudio>(m_audioEngineAct);
39✔
156

157
  connect(
39✔
158
      m_audioEngineAct, &QAction::triggered, this, &ApplicationPlugin::restart_engine);
39✔
159

160
  return e;
39✔
161
}
39✔
162

163
void ApplicationPlugin::restart_engine()
20✔
164
try
165
{
166
  if(m_updating_audio)
20✔
167
    return;
×
168

169
#if defined(__EMSCRIPTEN__)
170
  if(!audio)
171
  {
172
    // Defer the first start to a clean stack depth: the WebAudio backend blocks
173
    // (emscripten_sleep / ASYNCIFY) waiting for the AudioWorklet, which deadlocks
174
    // if run synchronously while nested inside document loading.
175
    QTimer::singleShot(0, this, [this] { start_engine(); });
176
  }
177
  else
178
  {
179
    // The worklet is a page-lifetime singleton and cannot be recreated, so a
180
    // new/changed document rebinds its audio device to the running engine rather
181
    // than tearing it down.
182
    QTimer::singleShot(0, this, [this] {
183
      if(auto doc = this->currentDocument())
184
      {
185
        auto dev = (Dataflow::AudioDevice*)doc->context()
186
                       .plugin<Explorer::DeviceDocumentPlugin>()
187
                       .list()
188
                       .audioDevice();
189
        if(dev)
190
          dev->reconnect();
191
        if(audio)
192
          audio->set_tick(Audio::makePauseTick(this->context));
193
      }
194
    });
195
  }
196
#else
197
  stop_engine();
20✔
198
  start_engine();
20✔
199
#endif
200
}
20✔
201
catch(std::exception& e)
202
{
203
  score::warning(
×
204
      context.documentTabWidget, tr("Audio Error"),
×
205
      tr("Warning: error while restarting the audio engine:\n%1").arg(e.what()));
×
206
}
×
207
catch(...)
208
{
209
  score::warning(
×
210
      context.documentTabWidget, tr("Audio Error"),
×
211
      tr("Warning: audio engine stuck. "
×
212
         "Operation aborted. "
213
         "Check the audio settings."));
214
}
20✔
215

NEW
216
void ApplicationPlugin::with_engine_stopped(std::function<void()> f)
×
217
{
NEW
218
  const bool was_running = bool(audio);
×
NEW
219
  if(was_running)
×
NEW
220
    stop_engine();
×
221

222
  try
223
  {
NEW
224
    if(f)
×
NEW
225
      f();
×
NEW
226
  }
×
227
  catch(...)
228
  {
229
    // Deliberately swallowed: leaving audio down because the callback misbehaved
230
    // would be worse than whatever it failed at, so fall through and restart.
NEW
231
  }
×
232

NEW
233
  if(was_running)
×
NEW
234
    start_engine();
×
NEW
235
}
×
236

237
void ApplicationPlugin::stop_engine()
40✔
238
{
239
  if(audio)
40✔
240
  {
241
    for(auto d : context.docManager.documents())
42✔
242
    {
243
      auto dev = (Dataflow::AudioDevice*)d->context()
44✔
244
                     .plugin<Explorer::DeviceDocumentPlugin>()
22✔
245
                     .list()
22✔
246
                     .audioDevice();
22✔
247
      if(dev)
22✔
248
      {
249
        if(auto proto = dev->getProtocol())
22✔
250
        {
251
          proto->stop();
20✔
252
        }
20✔
253
      }
22✔
254
    }
255

256
    audio->stop();
20✔
257
    previous_audio.push_back(std::move(audio));
20✔
258
    audio.reset();
20✔
259
  }
20✔
260
}
40✔
261

262
void ApplicationPlugin::start_engine()
20✔
263
{
264
  if(auto doc = this->currentDocument())
20✔
265
  {
266
    auto dev = (Dataflow::AudioDevice*)doc->context()
40✔
267
                   .plugin<Explorer::DeviceDocumentPlugin>()
20✔
268
                   .list()
20✔
269
                   .audioDevice();
20✔
270
    if(!dev)
20✔
271
      return;
×
272

273
    auto& set = this->context.settings<Audio::Settings::Model>();
20✔
274
    auto& engines = score::GUIAppContext().interfaces<Audio::AudioFactoryList>();
20✔
275

276
    if(auto dev = engines.get(set.getDriver()))
20✔
277
    {
278
      try
279
      {
280
        SCORE_ASSERT(!audio);
20✔
281
        audio = dev->make_engine(set, this->context);
20✔
282
        if(!audio)
20✔
283
          throw std::runtime_error{""};
×
284

285
        m_updating_audio = true;
20✔
286
        auto bs = audio->effective_buffer_size;
20✔
287
        if(bs <= 0)
20✔
288
          bs = set.getBufferSize();
20✔
289
        if(bs <= 0)
20✔
290
          bs = 512;
×
291
        auto rate = audio->effective_sample_rate;
20✔
292
        if(rate <= 0)
20✔
293
          rate = set.getRate();
20✔
294
        if(rate <= 0)
20✔
295
          rate = 44100;
×
296
        set.setBufferSize(bs);
20✔
297
        set.setRate(rate);
20✔
298
        m_updating_audio = false;
20✔
299
      }
20✔
300
      catch(...)
301
      {
302
        score::warning(
×
303
            nullptr, tr("Audio error"),
×
304
            tr("The desired audio settings could not be applied.\nPlease "
×
305
               "change "
306
               "them."));
307
      }
×
308
    }
20✔
309

310
    if(m_audioEngineAct)
20✔
311
      m_audioEngineAct->setChecked(bool(audio));
20✔
312

313
    dev->reconnect();
20✔
314
    if(audio)
20✔
315
    {
316
      audio->set_tick(Audio::makePauseTick(this->context));
20✔
317
    }
20✔
318
  }
20✔
319
}
20✔
320

321
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc