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

ossia / score / 37492781442

06 Oct 2026 04:04PM UTC coverage: 48.588% (+1.1%) from 47.45%
37492781442

push

github

jcelerier
execution: a port's address is rebound on the ossia port it has now

The rebinds on a port's exposed name and scriptable flag are queued, and a
queued call still runs after the port is bound again: one posted while a
shader's undo restored its ports ran after the second program change had
replaced the ossia inlets, and the audio thread unregistered a freed inlet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TcLJ4VuB2nzXbazQb9nyC1

14 of 14 new or added lines in 1 file covered. (100.0%)

4596 existing lines in 94 files now uncovered.

157183 of 323499 relevant lines covered (48.59%)

149381.01 hits per line

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

71.76
/src/plugins/score-plugin-engine/Execution/ExecutionController.cpp
1
#include "ExecutionController.hpp"
2

3
#include <LocalTree/LocalTreeDocumentPlugin.hpp>
4

5
#include <Explorer/DocumentPlugin/DeviceDocumentPlugin.hpp>
6
#include <Explorer/Explorer/DeviceExplorerModel.hpp>
7
#include <Explorer/Explorer/DeviceExplorerWidget.hpp>
8

9
#include <Scenario/Application/ScenarioActions.hpp>
10
#include <Scenario/Application/ScenarioApplicationPlugin.hpp>
11
#include <Scenario/Document/BaseScenario/BaseScenario.hpp>
12
#include <Scenario/Document/Interval/IntervalExecution.hpp>
13
#include <Scenario/Document/Interval/IntervalModel.hpp>
14
#include <Scenario/Document/ScenarioDocument/ScenarioDocumentModel.hpp>
15
#include <Scenario/Document/ScenarioDocument/ScenarioDocumentPresenter.hpp>
16
#include <Scenario/Execution/score2OSSIA.hpp>
17
#include <Scenario/Process/ScenarioExecution.hpp>
18
#include <Scenario/Process/ScenarioModel.hpp>
19

20
#include <Audio/AudioApplicationPlugin.hpp>
21
#include <Audio/Settings/Model.hpp>
22
#include <Execution/Clock/ClockFactory.hpp>
23
#include <Execution/DocumentPlugin.hpp>
24
#include <Execution/Settings/ExecutorModel.hpp>
25

26
#include <score/actions/ActionManager.hpp>
27
#include <score/model/ComponentUtils.hpp>
28
#include <score/tools/Bind.hpp>
29
#include <score/widgets/MessageBox.hpp>
30

31
#include <core/application/ApplicationSettings.hpp>
32
#include <core/document/Document.hpp>
33
#include <core/presenter/DocumentManager.hpp>
34

35
#include <ossia/dataflow/execution_state.hpp>
36
#include <ossia/editor/scenario/time_interval.hpp>
37
#include <ossia/editor/state/state.hpp>
38

39
#include <QGuiApplication>
40
#include <QMainWindow>
41

42
#include <Transport/DocumentPlugin.hpp>
43
#include <Transport/TransportInterface.hpp>
44

45
#include <wobjectimpl.h>
46
W_OBJECT_IMPL(Execution::ExecutionController)
3,316 ✔
47

48
/**
49
 * Execution state-machine explanation:
50
 *
51
 * * There are multiple sources for what controls the transport:
52
 * - From the GUI
53
 * - From the local tree or remote control plug-in
54
 * - From an external software (e.g. JACK transport)
55
 * - Automatically (e.g. --autoplay argument on command-line)
56
 *
57
 * * No matter the transport source, if there's a GUI its state must be consistent
58
 *   with what happens.
59
 *
60
 * Thus:
61
 * * The do_*** functions are requests
62
 * * The trigger_*** functions will perform the action and set the GUI state correctly
63
 * * The on_*** contain the actual implementation of the transport operation
64
 *
65
 * Notes on QAction:
66
 * * QAction::toggle(); -> will send toggled(true/false);
67
 * * QAction::trigger(); -> will send toggled(true/false); triggered(true/false);
68
 * * QAction::check(b); -> will send toggled(b);
69
 * * GUI: like QAction::trigger
70
 *
71
 * Thus, triggered can be used to differentiate between
72
 * * called from the GUI
73
 * * called from the software
74
 *
75
 * Example of call sequence:
76
 *
77
 * * Pressing the "global play button" (stop -> play):
78
 *   -> QAction::toggled(true)
79
 *     -> GUI changes (see TransportActions ctor)
80
 *   -> QAction::triggered(true)
81
 *     -> ApplicationPlugin::request_play_global(true)
82
 *     -> TransportInterface::requestPlay()
83
 *   -> the transport implementation eventually sends the "play()" signal
84
 *     -> &ApplicationPlugin::trigger_play_global
85
 *
86
 * * Pressing the "global play button" (play -> pause):
87
 *   -> QAction::toggled(false)
88
 *     -> GUI changes (see TransportActions ctor)
89
 *   -> QAction::triggered(false)
90
 *     -> ApplicationPlugin::request_play_global(false)
91
 *     -> TransportInterface::requestPause()
92
 *   -> the transport implementation eventually sends the "pause()" signal
93
 *     -> &ApplicationPlugin::trigger_pause()
94
 *
95
 * * Playing with "play from here":
96
 *   -> ApplicationPlugin::request_play(true, t)
97
 *   -> TransportInterface::requestTransport(t)
98
 *   -> TransportInterface::requestPlay
99
 *   -> The transport implementation eventually sends the "transport()" signal
100
 *     -> ??
101
 *   -> The transport implementation eventually sends the "play()" signal
102
 *     -> &ApplicationPlugin::trigger_play_global
103
 *
104
 * * Starting the transport from QJackCtl with a GUI
105
 *   -> the transport implementation eventually sends the "play()" signal
106
 *     -> &ApplicationPlugin::trigger_play_global
107
 *
108
 * * Starting the transport from QJackCtl without a GUI
109
 *   -> the transport implementation eventually sends the "play()" signal
110
 *     -> &ApplicationPlugin::trigger_play_global
111
 *
112
 * * Receiving a "/play" message to the local tree with a GUI
113
 *   -> ApplicationPlugin::request_play
114
 *   -> TransportInterface::requestPlay
115
 *   -> The transport implementation eventually sends the "play()" signal
116
 *     -> &ApplicationPlugin::trigger_play_global
117
 *
118
 * * Receiving a "/play" message to the local tree without a GUI
119
 */
120

121
namespace Execution
122
{
123
ExecutionController::ExecutionController(const score::GUIApplicationContext& ctx)
6,630 ✔
124
    : context{ctx}
3,315 ✔
125
    , m_scenario{ctx.guiApplicationPlugin<Scenario::ScenarioApplicationPlugin>()}
3,315 ✔
126
    , m_actions{m_scenario.transportActions()}
3,315 ✔
127
{
3,315 ✔
128
  if(ctx.applicationSettings.gui)
3,315 ✔
129
  {
130
    auto& acts = ctx.actions;
3,215 ✔
131
    using namespace Actions;
132
    connect(
3,215 ✔
133
        acts.action<Play>().action(), &QAction::triggered, this,
3,215 ✔
134
        &ExecutionController::request_play_local, Qt::QueuedConnection);
135

136
    connect(
3,215 ✔
137
        acts.action<PlayGlobal>().action(), &QAction::triggered, this,
3,215 ✔
138
        &ExecutionController::request_play_global, Qt::QueuedConnection);
139

140
    connect(
3,215 ✔
141
        acts.action<Stop>().action(), &QAction::triggered, this,
3,215 ✔
142
        &ExecutionController::request_stop, Qt::QueuedConnection);
143

144
    connect(
3,215 ✔
145
        acts.action<Reinitialize>().action(), &QAction::triggered, this,
3,215 ✔
146
        &ExecutionController::trigger_reinitialize, Qt::QueuedConnection);
147

148
    connect(
3,215 ✔
149
        &m_scenario.execution(), &Scenario::ScenarioExecution::playAtDate, this,
3,215 ✔
150
        qOverload<TimeVal>(&ExecutionController::request_play_from_here));
3,215 ✔
151
    connect(
3,215 ✔
152
        &m_scenario.execution(), &Scenario::ScenarioExecution::playIntervalAtDate,
3,215 ✔
153
        this, [this](Scenario::IntervalModel* itv, const TimeVal& t) {
3,215 ✔
UNCOV
154
      if(itv)
×
UNCOV
155
        request_play_from_here(*itv, t);
×
156
    });
×
157
    connect(
3,215 ✔
158
        &m_scenario.execution(), &Scenario::ScenarioExecution::beginScrub, this,
3,215 ✔
159
        &ExecutionController::request_begin_scrub);
160
    connect(
3,215 ✔
161
        &m_scenario.execution(), &Scenario::ScenarioExecution::scrub, this,
3,215 ✔
162
        &ExecutionController::request_scrub);
163
    connect(
3,215 ✔
164
        &m_scenario.execution(), &Scenario::ScenarioExecution::endScrub, this,
3,215 ✔
165
        &ExecutionController::request_end_scrub);
166
  }
3,215 ✔
UNCOV
167
}
×
168

169
ExecutionController::~ExecutionController()
3,314 ✔
170
{
3,314 ✔
171
  if(m_transport)
3,314 ✔
172
    m_transport->teardown();
3,314 ✔
173
}
3,314 ✔
174

175
TransportInterface& ExecutionController::transport() const noexcept
54 ✔
176
{
177
  return *m_transport;
54 ✔
178
}
179

180
void ExecutionController::request_play_global(bool b)
48 ✔
181
{
182
  if(b)
48 ✔
183
  {
184
    this->m_requestLocalPlay = false;
47 ✔
185
    m_transport->requestPlay();
47 ✔
186
  }
47 ✔
187
  else
188
  {
189
    m_transport->requestPause();
1 ✔
190
  }
191
}
48 ✔
192

193
void ExecutionController::request_play_local(bool b)
×
194
{
195
  if(b)
×
196
  {
UNCOV
197
    this->m_requestLocalPlay = true;
×
198
    m_transport->requestPlay();
×
199
  }
×
200
  else
201
  {
202
    m_transport->requestPause();
×
203
  }
204
}
×
205

206
void ExecutionController::request_play_interval(
3 ✔
207
    Scenario::IntervalModel& itv, exec_setup_fun setup, TimeVal t)
208
{
209
  m_intervalsToPlay.push_back({itv, std::move(setup), t});
3 ✔
210
  m_transport->requestPlay();
3 ✔
211
}
3 ✔
212

213
void ExecutionController::request_stop_interval(Scenario::IntervalModel& itv)
1 ✔
214
{
215
  stop_interval(itv);
1 ✔
216
}
1 ✔
217

218
void ExecutionController::request_stop()
4,426 ✔
219
{
220
  m_transport->requestStop();
4,426 ✔
221
}
4,426 ✔
222

223
void ExecutionController::trigger_play()
50 ✔
224
{
225
  if(!m_intervalsToPlay.empty())
50 ✔
226
  {
227
    m_actions.onPlayLocal();
3 ✔
228
    for(auto& to_play : m_intervalsToPlay)
6 ✔
229
      play_interval(to_play.interval, std::move(to_play.setup), to_play.t);
3 ✔
230
    m_intervalsToPlay.clear();
3 ✔
231
  }
3 ✔
232
  else if(this->m_requestLocalPlay)
47 ✔
233
  {
UNCOV
234
    m_actions.onPlayLocal();
×
UNCOV
235
    on_play_local(true);
×
UNCOV
236
  }
×
237
  else
238
  {
239
    m_actions.onPlayGlobal();
47 ✔
240
    on_play_global(true);
47 ✔
241
  }
242

243
  // Note: we set this here so that external "play" request are global
244
  // as it makes more sense:
245
  // local play is just for checking a sub-part of the score, but the external tools's times will
246
  // always be relative to the whole general score
247
  this->m_requestLocalPlay = false;
50 ✔
248
}
50 ✔
249

250
void ExecutionController::trigger_pause()
1 ✔
251
{
252
  m_actions.onPause();
1 ✔
253
  on_pause();
1 ✔
254
}
1 ✔
255

256
void ExecutionController::trigger_stop()
4,490 ✔
257
{
258
  m_actions.onStop();
4,490 ✔
259
  on_stop();
4,490 ✔
260

261
  // See note above
262
  this->m_requestLocalPlay = false;
4,490 ✔
263
}
4,490 ✔
264

265
void ExecutionController::trigger_reinitialize()
1 ✔
266
{
267
  m_actions.onStop();
1 ✔
268
  on_reinitialize();
1 ✔
269

270
  // See note above
271
  this->m_requestLocalPlay = false;
1 ✔
272
}
1 ✔
273

274
void ExecutionController::on_play_global(bool b)
47 ✔
275
{
276
  if(auto scenar = currentScenarioModel())
47 ✔
277
  {
278
    if(b)
47 ✔
279
    {
280
      play_interval(scenar->baseInterval(), {}, TimeVal::zero());
47 ✔
281
    }
47 ✔
282
    else
283
    {
UNCOV
284
      on_pause();
×
285
    }
286
  }
47 ✔
287
}
47 ✔
288

289
void ExecutionController::on_play_local(bool b, ::TimeVal t)
2 ✔
290
{
291
  if(auto scenar = currentScenarioPresenter();
2 ✔
292
     scenar && scenar->displayedElements.initialized())
2 ✔
293
  {
UNCOV
294
    if(b)
×
295
    {
UNCOV
296
      std::optional<TimeVal> startTime;
×
297
      auto& itv = scenar->displayedInterval();
×
298
      {
299
        if(itv.startMarker() != TimeVal::zero())
×
300
          startTime = itv.startMarker();
×
301
      }
302
      if(!startTime)
×
303
        if(t != TimeVal::zero())
×
UNCOV
304
          startTime = t;
×
305

306
      if(startTime && m_playing && m_clock)
×
307
      {
308
        if(m_clock->paused())
×
309
        {
UNCOV
310
          on_transport(*startTime);
×
311
        }
×
UNCOV
312
      }
×
313
      play_interval(scenar->displayedInterval(), {}, startTime ? *startTime : t);
×
UNCOV
314
    }
×
315
    else
316
    {
317
      on_pause();
×
318
    }
UNCOV
319
  }
×
320
  else if(auto scenar = currentScenarioModel(); scenar && b)
2 ✔
321
  {
322
    play_interval(scenar->baseInterval(), {}, t);
2 ✔
323
  }
2 ✔
324
  else
325
  {
326
    on_play_global(b);
×
327
  }
328
}
2 ✔
329

UNCOV
330
void ExecutionController::on_play_local(bool b)
×
331
{
UNCOV
332
  on_play_local(b, ::TimeVal::zero());
×
333
}
×
334

335
void ExecutionController::on_pause()
1 ✔
336
{
337
  ensure_audio_engine();
1 ✔
338

339
  if(m_clock)
1 ✔
340
  {
UNCOV
341
    m_clock->pause();
×
UNCOV
342
    m_paused = true;
×
343

344
    if(auto doc = currentDocument())
×
345
    {
346
      doc->context().execTimer.stop();
×
347

UNCOV
348
      if(auto transport_plug = doc->context().findPlugin<Transport::DocumentPlugin>())
×
349
        transport_plug->pause();
×
350
    }
×
351
  }
×
352
}
1 ✔
353

354
void ExecutionController::on_transport(TimeVal t)
3 ✔
355
{
356
  if(!m_clock)
3 ✔
357
    return;
2 ✔
358

359
  SCORE_ASSERT(m_clock->scenario);
1 ✔
360
  auto itv = m_clock->scenario->baseInterval().OSSIAInterval();
1 ✔
361
  if(!itv)
1 ✔
362
    return;
×
363

364
  auto& settings = context.settings<Execution::Settings::Model>();
1 ✔
365
  auto& ctx = m_clock->context;
1 ✔
366
  if(settings.getTransportValueCompilation())
1 ✔
367
  {
UNCOV
368
    auto execState = m_clock->context.execState;
×
UNCOV
369
    ctx.executionQueue.enqueue([execState, itv, time = m_clock->context.time(t)] {
×
370
      itv->offset(time);
×
UNCOV
371
      execState->commit();
×
372
    });
×
373
  }
×
374
  else
375
  {
376
    ctx.executionQueue.enqueue(
1 ✔
377
        [itv, time = m_clock->context.time(t)] { itv->transport(time); });
2 ✔
378
  }
379

380
  if(auto doc = currentDocument())
1 ✔
381
    if(auto transport_plug = doc->context().findPlugin<Transport::DocumentPlugin>())
2 ✔
382
      transport_plug->transport(t);
1 ✔
383
}
3 ✔
384

385
void ExecutionController::on_begin_scrub(TimeVal t)
×
386
{
387
  on_transport(t);
×
UNCOV
388
}
×
389

390
void ExecutionController::on_scrub(TimeVal t)
×
391
{
392
  on_transport(t);
×
UNCOV
393
}
×
394

UNCOV
395
void ExecutionController::on_end_scrub(TimeVal t)
×
396
{
UNCOV
397
  on_transport(t);
×
398
}
×
399

400
void ExecutionController::request_play_from_localtree(bool val)
×
401
{
UNCOV
402
  if(!m_playing && val)
×
403
  {
404
    // not playing, play requested
405
    request_play_local(val);
×
UNCOV
406
  }
×
UNCOV
407
  else if(m_playing)
×
408
  {
409
    if(m_paused == val)
×
410
    {
411
      // paused, play requested
412
      // or playing, pause requested
UNCOV
413
      request_play_local(val);
×
UNCOV
414
    }
×
UNCOV
415
  }
×
416
}
×
417

418
void ExecutionController::request_play_global_from_localtree(bool val)
×
419
{
UNCOV
420
  if(!m_playing && val)
×
421
  {
422
    // not playing, play requested
UNCOV
423
    request_play_global(val);
×
UNCOV
424
  }
×
UNCOV
425
  else if(m_playing)
×
426
  {
UNCOV
427
    if(m_paused == val)
×
428
    {
429
      // paused, play requested
430
      // or playing, pause requested
UNCOV
431
      request_play_global(val);
×
UNCOV
432
    }
×
UNCOV
433
  }
×
UNCOV
434
}
×
435

436
void ExecutionController::request_transport_from_localtree(TimeVal t)
2 ✔
437
{
438
  m_transport->requestTransport(t);
2 ✔
439
}
2 ✔
440

441
void ExecutionController::request_stop_from_localtree()
10 ✔
442
{
443
  trigger_stop();
10 ✔
444
}
10 ✔
445

446
void ExecutionController::request_reinitialize_from_localtree()
1 ✔
447
{
448
  trigger_reinitialize();
1 ✔
449
}
1 ✔
450

451
void ExecutionController::request_play_from_here(TimeVal t)
3 ✔
452
{
453
  if(m_clock)
3 ✔
454
  {
455
    m_transport->requestTransport(t);
1 ✔
456
  }
1 ✔
457
  else
458
  {
459
    on_play_local(true, t);
2 ✔
460

461
    // Syncs the transport buttons; actions only exist with a GUI.
462
    // FIXME this ends up calling play_interval again...
463
    if(this->context.applicationSettings.gui)
2 ✔
464
    {
UNCOV
465
      auto act = this->context.actions.action<Actions::Play>().action();
×
466
      act->trigger();
×
UNCOV
467
    }
×
468
  }
469
}
3 ✔
470

471
void ExecutionController::request_play_from_here(
4 ✔
472
    Scenario::IntervalModel& itv, TimeVal t)
473
{
474
  auto model = currentScenarioModel();
4 ✔
475
  const Scenario::IntervalModel* root = model ? &model->baseInterval() : nullptr;
4 ✔
476
  if(&itv == root)
4 ✔
UNCOV
477
    return request_play_from_here(t);
×
478

479
  if(!m_clock)
4 ✔
480
  {
481
    request_play_interval(itv, {}, t);
2 ✔
482
    return;
2 ✔
483
  }
484

485
  // The interval the clock plays; transport dates are relative to it.
486
  const Scenario::IntervalModel* played
2 ✔
487
      = m_clock->scenario ? &m_clock->scenario->baseInterval().scoreInterval() : root;
2 ✔
488
  if(&itv == played)
2 ✔
489
  {
UNCOV
490
    m_transport->requestTransport(t);
×
491
    return;
×
492
  }
493

494
  // The parent interval is running: restart only this interval at t.
495
  if(auto scenar = qobject_cast<Scenario::ProcessModel*>(itv.parent()))
2 ✔
496
  {
497
    auto parent = qobject_cast<Scenario::IntervalModel*>(scenar->parent());
2 ✔
498
    auto comp = score::findComponent<Execution::ScenarioComponentBase>(scenar->components());
2 ✔
499
    if(parent && parent->executing() && comp)
2 ✔
500
    {
501
      comp->playIntervalFrom(itv, t);
2 ✔
502
      return;
2 ✔
503
    }
UNCOV
504
  }
×
505

506
  // Otherwise transport the played interval to t, expressed in its time base.
UNCOV
507
  TimeVal date = t;
×
UNCOV
508
  const Scenario::IntervalModel* cur = &itv;
×
UNCOV
509
  while(cur && cur != played)
×
510
  {
UNCOV
511
    date += cur->date();
×
UNCOV
512
    cur = Scenario::closestParentInterval(cur->parent());
×
513
  }
UNCOV
514
  if(cur)
×
515
    m_transport->requestTransport(date);
×
516
}
4 ✔
517

UNCOV
518
void ExecutionController::request_begin_scrub(TimeVal t)
×
519
{
UNCOV
520
  if(m_clock)
×
521
  {
UNCOV
522
    m_transport->requestBeginScrub(t);
×
UNCOV
523
  }
×
524
  else
525
  {
UNCOV
526
    on_play_local(true, t);
×
527

528
    // FIXME this ends up calling play_interval again...
UNCOV
529
    if(this->context.applicationSettings.gui)
×
530
    {
531
      auto act = this->context.actions.action<Actions::Play>().action();
×
532
      act->trigger();
×
UNCOV
533
    }
×
534
  }
UNCOV
535
}
×
536

UNCOV
537
void ExecutionController::request_scrub(TimeVal t)
×
538
{
539
  if(m_clock)
×
540
  {
UNCOV
541
    m_transport->requestScrub(t);
×
542
  }
×
543
}
×
544

UNCOV
545
void ExecutionController::request_end_scrub(TimeVal t)
×
546
{
547
  if(m_clock)
×
548
  {
UNCOV
549
    m_transport->requestEndScrub(t);
×
UNCOV
550
  }
×
UNCOV
551
}
×
552

553
void ExecutionController::ensure_audio_engine()
54 ✔
554
{
555
  auto& audio_engine = this->context.guiApplicationPlugin<Audio::ApplicationPlugin>();
54 ✔
556
  if(!audio_engine.audio)
54 ✔
557
  {
558
    if(this->context.mainWindow)
×
559
    {
UNCOV
560
      score::warning(
×
UNCOV
561
          this->context.mainWindow, tr("Cannot play"),
×
UNCOV
562
          tr("Cannot start playback. It looks like the audio engine is not "
×
563
             "running.\n"
564
             "Check the audio settings in the software settings to ensure "
565
             "that a sound card "
566
             "is correctly configured.\n\n"
567
             "Check Settings > Audio > Device in particular. "
568
             "The power-on icon at the bottom of the transport toolbar will "
569
             "light up when the engine is running."));
UNCOV
570
      return;
×
571
    }
572
    else
573
    {
UNCOV
574
      qFatal("Cannot playback without an audio engine set up");
×
575
    }
576
  }
577
}
54 ✔
578

579
void ExecutionController::play_interval(
52 ✔
580
    Scenario::IntervalModel& cst, exec_setup_fun setup_fun, TimeVal t)
581
{
582
  auto doc = currentDocument();
52 ✔
583
  if(!doc)
52 ✔
584
    return;
×
585

586
  auto& ctx = doc->context();
52 ✔
587

588
  auto exec_plug = ctx.findPlugin<Execution::DocumentPlugin>();
52 ✔
589
  if(!exec_plug)
52 ✔
UNCOV
590
    return;
×
591

592
  ensure_audio_engine();
52 ✔
593

594
  if(m_playing)
52 ✔
595
  {
596
    SCORE_ASSERT(bool(m_clock));
1 ✔
597
    if(m_clock->paused())
1 ✔
598
    {
599
      m_clock->resume();
×
UNCOV
600
      m_paused = false;
×
601
    }
×
602
    else
603
    {
604
      // We are playing an interval while we are already executing.
605
      if(auto scenar = qobject_cast<Scenario::ProcessModel*>(cst.parent()))
1 ✔
606
      {
607
        auto exec_comp = score::findComponent<Execution::ScenarioComponentBase>(
1 ✔
608
            scenar->components());
1 ✔
609
        if(exec_comp)
1 ✔
610
        {
611
          exec_comp->playInterval(cst);
1 ✔
612
        }
1 ✔
613
        return;
1 ✔
614
      }
615
    }
616
  }
×
617
  else
618
  {
619
    // Here we stop the listening when we start playing the scenario.
620
    // Get all the selected nodes
621
    if(auto explorer = Explorer::try_deviceExplorerFromObject(*doc))
51 ✔
622
    {
623
      // Disable listening for everything
624
      if(explorer && !exec_plug->settings.getExecutionListening())
51 ✔
625
      {
626
        explorer->deviceModel().listening().stop();
×
UNCOV
627
      }
×
628

629
      if(this->context.applicationSettings.gui)
51 ✔
630
      {
631
        if(auto w = Explorer::findDeviceExplorerWidgetInstance(this->context))
9 ✔
632
        {
633
          w->setEditable(false);
9 ✔
634
        }
9 ✔
635
      }
9 ✔
636
    }
51 ✔
637

638
    // Ignores the top-level condition / triggers if any
639
    bool forcePlay{};
51 ✔
640
    if(this->context.applicationSettings.gui)
51 ✔
641
    {
642
      forcePlay = qApp->keyboardModifiers() & Qt::ControlModifier;
9 ✔
643
    }
9 ✔
644
    exec_plug->reload(forcePlay, cst);
51 ✔
645

646
    auto& c = exec_plug->context();
51 ✔
647
    m_clock = makeClock(c);
51 ✔
648

649
    if(setup_fun)
51 ✔
650
    {
651
      exec_plug->runAllCommands();
×
UNCOV
652
      SCORE_ASSERT(exec_plug->baseScenario());
×
653
      setup_fun(c, *exec_plug->baseScenario());
×
654
      exec_plug->runAllCommands();
×
655
    }
×
656

657
    m_clock->play(t);
51 ✔
658
    m_paused = false;
51 ✔
659
  }
660

661
  m_playing = true;
51 ✔
662
  ctx.execTimer.start();
51 ✔
663

664
  if(auto transport_plug = ctx.findPlugin<Transport::DocumentPlugin>())
51 ✔
665
    transport_plug->play();
51 ✔
666

667
  playStarted();
51 ✔
668
}
52 ✔
669

670
void ExecutionController::stop_interval(Scenario::IntervalModel& cst)
1 ✔
671
{
672
  auto doc = currentDocument();
1 ✔
673
  if(!doc)
1 ✔
UNCOV
674
    return;
×
675

676
  auto& ctx = doc->context();
1 ✔
677

678
  auto exec_plug = ctx.findPlugin<Execution::DocumentPlugin>();
1 ✔
679
  if(!exec_plug)
1 ✔
UNCOV
680
    return;
×
681

682
  ensure_audio_engine();
1 ✔
683

684
  if(m_playing)
1 ✔
685
  {
686
    // We are stopping an interval while we are already executing.
687
    if(auto scenar = qobject_cast<Scenario::ProcessModel*>(cst.parent()))
1 ✔
688
    {
689
      auto exec_comp
1 ✔
690
          = score::findComponent<Execution::ScenarioComponentBase>(scenar->components());
1 ✔
691
      if(exec_comp)
1 ✔
692
      {
693
        exec_comp->stopInterval(cst);
1 ✔
694
      }
1 ✔
695
    }
1 ✔
696
  }
1 ✔
697
}
1 ✔
698

699
TimeVal ExecutionController::execution_time() const
9,161 ✔
700
{
701
  if(m_clock)
9,161 ✔
702
  {
703
    SCORE_ASSERT(m_clock->scenario);
505 ✔
704
    auto& itv = m_clock->scenario->baseInterval().scoreInterval().duration;
505 ✔
705
    return TimeVal(itv.defaultDuration() * itv.playPercentage());
505 ✔
706
  }
707
  return TimeVal::zero();
8,656 ✔
708
}
9,161 ✔
709

710
void ExecutionController::on_record(::TimeVal t)
1 ✔
711
{
712
  // Playback can start while a recording waits for its first message.
713
  if(m_playing)
1 ✔
UNCOV
714
    return;
×
715

716
  // TODO have a on_exit handler to properly stop the scenario.
717
  if(auto scenar = currentScenarioModel())
1 ✔
718
  {
719
    if(auto exec_plug = scenar->context().findPlugin<Execution::DocumentPlugin>())
1 ✔
720
    {
721
      // Listening isn't stopped here.
722
      exec_plug->reload(false, scenar->baseInterval());
1 ✔
723
      m_clock = makeClock(exec_plug->context());
1 ✔
724
      m_clock->play(t);
1 ✔
725

726
      scenar->context().execTimer.start();
1 ✔
727
      m_playing = true;
1 ✔
728
      m_paused = false;
1 ✔
729

730
      if(auto transport_plug = scenar->context().findPlugin<Transport::DocumentPlugin>())
1 ✔
731
        transport_plug->play();
1 ✔
732
    }
1 ✔
733
  }
1 ✔
734
}
1 ✔
735

736
void ExecutionController::on_stop()
4,491 ✔
737
{
738
  LocalTree::DocumentPlugin* tree{};
4,491 ✔
739
  if(auto doc = currentDocument())
4,491 ✔
740
    tree = doc->context().findPlugin<LocalTree::DocumentPlugin>();
3,149 ✔
741
  if(tree)
4,491 ✔
742
    tree->beginRecall(LocalTree::Recall::Stop);
3,149 ✔
743

744
  bool wasplaying = m_playing;
4,491 ✔
745
  m_playing = false;
4,491 ✔
746
  m_paused = false;
4,491 ✔
747

748
  stop_clock();
4,491 ✔
749

750
  if(wasplaying)
4,491 ✔
751
    send_end_state();
52 ✔
752

753
  reset_after_stop();
4,491 ✔
754

755
  if(tree)
4,491 ✔
756
    tree->endRecall(LocalTree::Recall::Stop);
3,149 ✔
757
}
4,491 ✔
758

759
void ExecutionController::stop_clock()
4,491 ✔
760
{
761
  if(m_clock)
4,491 ✔
762
  {
763
    auto clock = std::move(m_clock);
52 ✔
764
    m_clock.reset();
52 ✔
765
    if(auto scenar = currentScenarioModel())
52 ✔
766
      if(auto exec_plug = scenar->context().findPlugin<Execution::DocumentPlugin>();
52 ✔
767
         bool(exec_plug))
104 ✔
768
      {
769
        try
770
        {
771
          clock->stop();
52 ✔
772
        }
52 ✔
773
        catch(...)
774
        {
UNCOV
775
          qDebug() << "Error while stopping the clock. There is likely an audio "
×
776
                      "hardware issue.";
UNCOV
777
        }
×
778
      }
52 ✔
779
  }
52 ✔
780
}
4,491 ✔
781

782
void ExecutionController::send_end_state()
52 ✔
783
{
784
  if(auto doc = currentDocument())
52 ✔
785
  {
786
    auto& ctx = doc->context();
52 ✔
787
    if(auto exec_plug = ctx.findPlugin<Execution::DocumentPlugin>())
52 ✔
788
    {
789
      exec_plug->playStopState();
52 ✔
790
    }
52 ✔
791
  }
52 ✔
792
}
52 ✔
793
void ExecutionController::reset_after_stop()
4,491 ✔
794
{
795
  if(auto doc = currentDocument())
4,491 ✔
796
  {
797
    auto& ctx = doc->context();
3,149 ✔
798
    ctx.execTimer.stop();
3,149 ✔
799

800
    if(auto transport_plug = ctx.findPlugin<Transport::DocumentPlugin>())
3,149 ✔
801
      transport_plug->stop();
3,149 ✔
802

803
    if(auto exec_plug = ctx.findPlugin<Execution::DocumentPlugin>())
3,149 ✔
804
    {
805
      exec_plug->clear();
3,149 ✔
806
    }
3,149 ✔
807
    else
808
    {
UNCOV
809
      return;
×
810
    }
811

812
    // If we can we resume listening
813
    if(!context.docManager.preparingNewDocument())
3,149 ✔
814
    {
815
      auto explorer = Explorer::try_deviceExplorerFromObject(*doc);
2,202 ✔
816
      if(explorer)
2,202 ✔
817
        explorer->deviceModel().listening().restore();
2,202 ✔
818
    }
2,202 ✔
819
  }
3,149 ✔
820

821
  auto scenar = currentScenarioModel();
4,491 ✔
822
  if(!scenar || scenar->closing())
4,491 ✔
823
    return;
1,906 ✔
824
  QTimer::singleShot(
2,585 ✔
825
      50, this,
826
      [this, itv = QPointer<Scenario::IntervalModel>{&scenar->baseInterval()}] {
5,124 ✔
827
    if(itv)
2,539 ✔
828
      reset_edition(*itv);
731 ✔
829
      });
2,539 ✔
830
}
4,491 ✔
831

832
void ExecutionController::reset_edition(Scenario::IntervalModel& base)
731 ✔
833
{
834
  // Reset edition for the device explorer
835
  if(context.applicationSettings.gui)
731 ✔
836
  {
837
    if(auto w = Explorer::findDeviceExplorerWidgetInstance(context))
721 ✔
838
    {
839
      w->setEditable(true);
568 ✔
840
    }
568 ✔
841
  }
721 ✔
842

843
  // FIXME uuuugh have an event in scenario instead (or better, move this
844
  // in process)
845
  base.reset();
731 ✔
846
  base.executionEvent(Scenario::IntervalExecutionEvent::Finished);
731 ✔
847
}
731 ✔
848

849
void ExecutionController::on_reinitialize()
1 ✔
850
{
851
  // TODO to be more precise, we should stop the execution, but keep
852
  // the execution_state alive until the last message is sent
853
  if(auto scenar = currentScenarioModel())
1 ✔
854
  {
855
    auto& ctx = scenar->context();
1 ✔
856
    auto& doc = ctx.document;
1 ✔
857
    auto exec_plug = ctx.findPlugin<Execution::DocumentPlugin>();
1 ✔
858
    if(!exec_plug)
1 ✔
UNCOV
859
      return;
×
860

861
    auto explorer = Explorer::try_deviceExplorerFromObject(doc);
1 ✔
862

863
    // Disable listening for everything
864
    if(explorer)
1 ✔
865
      if(!ctx.app.settings<Execution::Settings::Model>().getExecutionListening())
1 ✔
UNCOV
866
        explorer->deviceModel().listening().stop();
×
867

868
    // If we can we resume listening
869
    if(explorer)
1 ✔
870
      if(!context.docManager.preparingNewDocument())
2 ✔
871
        explorer->deviceModel().listening().restore();
1 ✔
872

873
    on_stop();
1 ✔
874

875
    LocalTree::StateRecall recall{ctx, *exec_plug};
1 ✔
876
    exec_plug->playStartState();
1 ✔
877
  }
1 ✔
878
}
1 ✔
879

880
void ExecutionController::init_transport()
3,315 ✔
881
{
882
  if(m_transport)
3,315 ✔
UNCOV
883
    m_transport->teardown();
×
884

885
  auto& s = context.settings<Execution::Settings::Model>();
3,315 ✔
886
  m_transport = s.getTransport();
3,315 ✔
887
  SCORE_ASSERT(m_transport);
3,315 ✔
888
  m_transport->setup();
3,315 ✔
889
  connect(
3,315 ✔
890
      m_transport, &Execution::TransportInterface::play, this,
3,315 ✔
891
      &ExecutionController::trigger_play);
892
  connect(
3,315 ✔
893
      m_transport, &Execution::TransportInterface::pause, this,
3,315 ✔
894
      &ExecutionController::trigger_pause);
895
  connect(
3,315 ✔
896
      m_transport, &Execution::TransportInterface::stop, this,
3,315 ✔
897
      &ExecutionController::trigger_stop);
898
  connect(
3,315 ✔
899
      m_transport, &Execution::TransportInterface::transport, this,
3,315 ✔
900
      &ExecutionController::on_transport);
901
  connect(
3,315 ✔
902
      m_transport, &Execution::TransportInterface::beginScrub, this,
3,315 ✔
903
      &ExecutionController::on_begin_scrub);
904
  connect(
3,315 ✔
905
      m_transport, &Execution::TransportInterface::scrub, this,
3,315 ✔
906
      &ExecutionController::on_scrub);
907
  connect(
3,315 ✔
908
      m_transport, &Execution::TransportInterface::endScrub, this,
3,315 ✔
909
      &ExecutionController::on_end_scrub);
910

911
  auto& audio_settings = this->context.settings<Audio::Settings::Model>();
3,315 ✔
912
  con(audio_settings, &Audio::Settings::Model::JackTransportChanged, this,
6,630 ✔
913
      &ExecutionController::init_transport, Qt::UniqueConnection);
3,315 ✔
914

915
  con(audio_settings, &Audio::Settings::Model::changed, this,
6,630 ✔
916
      &ExecutionController::on_audioSettingsChanged,
3,315 ✔
917
      Qt::ConnectionType(Qt::DirectConnection | Qt::UniqueConnection));
3,315 ✔
918
}
3,315 ✔
919

920
void ExecutionController::on_audioSettingsChanged()
1 ✔
921
{
922
  // The engine is about to be restarted, which tears the audio device tree
923
  // down under the graph. Stopping here is synchronous, hence done before the
924
  // audio plug-in's queued restart.
925
  if(m_playing)
1 ✔
UNCOV
926
    trigger_stop();
×
927
}
1 ✔
928

929
Scenario::ScenarioDocumentModel* ExecutionController::currentScenarioModel()
4,598 ✔
930
{
931
  if(auto doc = currentDocument())
4,598 ✔
932
  {
933
    return score::IDocument::try_modelDelegate<Scenario::ScenarioDocumentModel>(*doc);
3,256 ✔
934
  }
935
  return nullptr;
1,342 ✔
936
}
4,598 ✔
937

938
Scenario::ScenarioDocumentPresenter* ExecutionController::currentScenarioPresenter()
2 ✔
939
{
940
  if(auto doc = currentDocument())
2 ✔
941
  {
942
    return score::IDocument::try_presenterDelegate<Scenario::ScenarioDocumentPresenter>(
2 ✔
943
        *doc);
2 ✔
944
  }
UNCOV
945
  return nullptr;
×
946
}
2 ✔
947

948
score::Document* ExecutionController::currentDocument() const
13,688 ✔
949
{
950
  return context.documents.currentDocument();
13,688 ✔
951
}
952

953
std::unique_ptr<Execution::Clock>
954
ExecutionController::makeClock(const Execution::Context& ctx)
52 ✔
955
{
956
  auto& s = context.settings<Execution::Settings::Model>();
52 ✔
957
  auto clk = s.makeClock(ctx);
52 ✔
958
  SCORE_ASSERT(clk->scenario);
52 ✔
959
  auto& itv = clk->scenario->baseInterval().interval();
52 ✔
960
  con(itv, &IdentifiedObjectAbstract::identified_object_destroying, this,
104 ✔
961
      [this] { trigger_stop(); });
104 ✔
962
  return clk;
52 ✔
963
}
52 ✔
964

965
}
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