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

ossia / score / 35899133125

23 Sep 2026 05:57PM UTC coverage: 41.488% (+1.2%) from 40.337%
35899133125

push

github

jcelerier
3rdparty: follow the merged libossia

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

123236 of 297041 relevant lines covered (41.49%)

113626.05 hits per line

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

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

3
#include <Explorer/DocumentPlugin/DeviceDocumentPlugin.hpp>
4
#include <Explorer/Explorer/DeviceExplorerModel.hpp>
5
#include <Explorer/Explorer/DeviceExplorerWidget.hpp>
6

7
#include <Scenario/Application/ScenarioActions.hpp>
8
#include <Scenario/Application/ScenarioApplicationPlugin.hpp>
9
#include <Scenario/Document/BaseScenario/BaseScenario.hpp>
10
#include <Scenario/Document/Interval/IntervalExecution.hpp>
11
#include <Scenario/Document/ScenarioDocument/ScenarioDocumentModel.hpp>
12
#include <Scenario/Document/ScenarioDocument/ScenarioDocumentPresenter.hpp>
13
#include <Scenario/Execution/score2OSSIA.hpp>
14
#include <Scenario/Process/ScenarioExecution.hpp>
15

16
#include <Audio/AudioApplicationPlugin.hpp>
17
#include <Audio/Settings/Model.hpp>
18
#include <Execution/Clock/ClockFactory.hpp>
19
#include <Execution/DocumentPlugin.hpp>
20
#include <Execution/Settings/ExecutorModel.hpp>
21

22
#include <score/actions/ActionManager.hpp>
23
#include <score/model/ComponentUtils.hpp>
24
#include <score/tools/Bind.hpp>
25
#include <score/widgets/MessageBox.hpp>
26

27
#include <core/application/ApplicationSettings.hpp>
28
#include <core/document/Document.hpp>
29
#include <core/presenter/DocumentManager.hpp>
30

31
#include <ossia/dataflow/execution_state.hpp>
32
#include <ossia/editor/scenario/time_interval.hpp>
33
#include <ossia/editor/state/state.hpp>
34

35
#include <QGuiApplication>
36
#include <QMainWindow>
37

38
#include <Transport/DocumentPlugin.hpp>
39
#include <Transport/TransportInterface.hpp>
40

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

114
namespace Execution
115
{
116
ExecutionController::ExecutionController(const score::GUIApplicationContext& ctx)
3,522✔
117
    : context{ctx}
1,761✔
118
    , m_scenario{ctx.guiApplicationPlugin<Scenario::ScenarioApplicationPlugin>()}
1,761✔
119
    , m_actions{m_scenario.transportActions()}
1,761✔
120
{
1,761✔
121
  if(ctx.applicationSettings.gui)
1,761✔
122
  {
123
    auto& acts = ctx.actions;
1,700✔
124
    using namespace Actions;
125
    connect(
1,700✔
126
        acts.action<Play>().action(), &QAction::triggered, this,
1,700✔
127
        &ExecutionController::request_play_local, Qt::QueuedConnection);
128

129
    connect(
1,700✔
130
        acts.action<PlayGlobal>().action(), &QAction::triggered, this,
1,700✔
131
        &ExecutionController::request_play_global, Qt::QueuedConnection);
132

133
    connect(
1,700✔
134
        acts.action<Stop>().action(), &QAction::triggered, this,
1,700✔
135
        &ExecutionController::request_stop, Qt::QueuedConnection);
136

137
    connect(
1,700✔
138
        acts.action<Reinitialize>().action(), &QAction::triggered, this,
1,700✔
139
        &ExecutionController::trigger_reinitialize, Qt::QueuedConnection);
140

141
    connect(
1,700✔
142
        &m_scenario.execution(), &Scenario::ScenarioExecution::playAtDate, this,
1,700✔
143
        &ExecutionController::request_play_from_here);
144
    connect(
1,700✔
145
        &m_scenario.execution(), &Scenario::ScenarioExecution::beginScrub, this,
1,700✔
146
        &ExecutionController::request_begin_scrub);
147
    connect(
1,700✔
148
        &m_scenario.execution(), &Scenario::ScenarioExecution::scrub, this,
1,700✔
149
        &ExecutionController::request_scrub);
150
    connect(
1,700✔
151
        &m_scenario.execution(), &Scenario::ScenarioExecution::endScrub, this,
1,700✔
152
        &ExecutionController::request_end_scrub);
153
  }
1,700✔
154
}
×
155

156
ExecutionController::~ExecutionController()
1,759✔
157
{
1,759✔
158
  if(m_transport)
1,759✔
159
    m_transport->teardown();
1,759✔
160
}
1,759✔
161

162
TransportInterface& ExecutionController::transport() const noexcept
24✔
163
{
164
  return *m_transport;
24✔
165
}
166

167
void ExecutionController::request_play_global(bool b)
23✔
168
{
169
  if(b)
23✔
170
  {
171
    this->m_requestLocalPlay = false;
22✔
172
    m_transport->requestPlay();
22✔
173
  }
22✔
174
  else
175
  {
176
    m_transport->requestPause();
1✔
177
  }
178
}
23✔
179

180
void ExecutionController::request_play_local(bool b)
×
181
{
182
  if(b)
×
183
  {
184
    this->m_requestLocalPlay = true;
×
185
    m_transport->requestPlay();
×
186
  }
×
187
  else
188
  {
189
    m_transport->requestPause();
×
190
  }
191
}
×
192

193
void ExecutionController::request_play_interval(
×
194
    Scenario::IntervalModel& itv, exec_setup_fun setup, TimeVal t)
195
{
196
  m_intervalsToPlay.push_back({itv, std::move(setup), t});
×
197
  m_transport->requestPlay();
×
198
}
×
199

200
void ExecutionController::request_stop_interval(Scenario::IntervalModel& itv)
×
201
{
202
  stop_interval(itv);
×
203
}
×
204

205
void ExecutionController::request_stop()
1,839✔
206
{
207
  m_transport->requestStop();
1,839✔
208
}
1,839✔
209

210
void ExecutionController::trigger_play()
22✔
211
{
212
  if(!m_intervalsToPlay.empty())
22✔
213
  {
214
    m_actions.onPlayLocal();
×
215
    for(auto& to_play : m_intervalsToPlay)
×
216
      play_interval(to_play.interval, std::move(to_play.setup), to_play.t);
×
217
    m_intervalsToPlay.clear();
×
218
  }
×
219
  else if(this->m_requestLocalPlay)
22✔
220
  {
221
    m_actions.onPlayLocal();
×
222
    on_play_local(true);
×
223
  }
×
224
  else
225
  {
226
    m_actions.onPlayGlobal();
22✔
227
    on_play_global(true);
22✔
228
  }
229

230
  // Note: we set this here so that external "play" request are global
231
  // as it makes more sense:
232
  // local play is just for checking a sub-part of the score, but the external tools's times will
233
  // always be relative to the whole general score
234
  this->m_requestLocalPlay = false;
22✔
235
}
22✔
236

237
void ExecutionController::trigger_pause()
1✔
238
{
239
  m_actions.onPause();
1✔
240
  on_pause();
1✔
241
}
1✔
242

243
void ExecutionController::trigger_stop()
1,858✔
244
{
245
  m_actions.onStop();
1,858✔
246
  on_stop();
1,858✔
247

248
  // See note above
249
  this->m_requestLocalPlay = false;
1,858✔
250
}
1,858✔
251

252
void ExecutionController::trigger_reinitialize()
1✔
253
{
254
  m_actions.onStop();
1✔
255
  on_reinitialize();
1✔
256

257
  // See note above
258
  this->m_requestLocalPlay = false;
1✔
259
}
1✔
260

261
void ExecutionController::on_play_global(bool b)
22✔
262
{
263
  if(auto scenar = currentScenarioModel())
22✔
264
  {
265
    if(b)
22✔
266
    {
267
      play_interval(scenar->baseInterval(), {}, TimeVal::zero());
22✔
268
    }
22✔
269
    else
270
    {
271
      on_pause();
×
272
    }
273
  }
22✔
274
}
22✔
275

276
void ExecutionController::on_play_local(bool b, ::TimeVal t)
×
277
{
278
  if(auto scenar = currentScenarioPresenter();
×
279
     scenar && scenar->displayedElements.initialized())
×
280
  {
281
    if(b)
×
282
    {
283
      std::optional<TimeVal> startTime;
×
284
      auto& itv = scenar->displayedInterval();
×
285
      {
286
        if(itv.startMarker() != TimeVal::zero())
×
287
          startTime = itv.startMarker();
×
288
      }
289
      if(!startTime)
×
290
        if(t != TimeVal::zero())
×
291
          startTime = t;
×
292

293
      if(startTime && m_playing && m_clock)
×
294
      {
295
        if(m_clock->paused())
×
296
        {
297
          on_transport(*startTime);
×
298
        }
×
299
      }
×
300
      play_interval(scenar->displayedInterval(), {}, startTime ? *startTime : t);
×
301
    }
×
302
    else
303
    {
304
      on_pause();
×
305
    }
306
  }
×
307
  else
308
  {
309
    on_play_global(b);
×
310
  }
311
}
×
312

313
void ExecutionController::on_play_local(bool b)
×
314
{
315
  on_play_local(b, ::TimeVal::zero());
×
316
}
×
317

318
void ExecutionController::on_pause()
1✔
319
{
320
  ensure_audio_engine();
1✔
321

322
  if(m_clock)
1✔
323
  {
324
    m_clock->pause();
×
325
    m_paused = true;
×
326

327
    if(auto doc = currentDocument())
×
328
    {
329
      doc->context().execTimer.stop();
×
330

331
      if(auto transport_plug = doc->context().findPlugin<Transport::DocumentPlugin>())
×
332
        transport_plug->pause();
×
333
    }
×
334
  }
×
335
}
1✔
336

337
void ExecutionController::on_transport(TimeVal t)
2✔
338
{
339
  if(!m_clock)
2✔
340
    return;
2✔
341

342
  SCORE_ASSERT(m_clock->scenario);
×
343
  auto itv = m_clock->scenario->baseInterval().OSSIAInterval();
×
344
  if(!itv)
×
345
    return;
×
346

347
  auto& settings = context.settings<Execution::Settings::Model>();
×
348
  auto& ctx = m_clock->context;
×
349
  if(settings.getTransportValueCompilation())
×
350
  {
351
    auto execState = m_clock->context.execState;
×
352
    ctx.executionQueue.enqueue([execState, itv, time = m_clock->context.time(t)] {
×
353
      itv->offset(time);
×
354
      execState->commit();
×
355
    });
×
356
  }
×
357
  else
358
  {
359
    ctx.executionQueue.enqueue(
×
360
        [itv, time = m_clock->context.time(t)] { itv->transport(time); });
×
361
  }
362

363
  if(auto doc = currentDocument())
×
364
    if(auto transport_plug = doc->context().findPlugin<Transport::DocumentPlugin>())
×
365
      transport_plug->transport(t);
×
366
}
2✔
367

368
void ExecutionController::on_begin_scrub(TimeVal t)
×
369
{
370
  on_transport(t);
×
371
}
×
372

373
void ExecutionController::on_scrub(TimeVal t)
×
374
{
375
  on_transport(t);
×
376
}
×
377

378
void ExecutionController::on_end_scrub(TimeVal t)
×
379
{
380
  on_transport(t);
×
381
}
×
382

383
void ExecutionController::request_play_from_localtree(bool val)
×
384
{
385
  if(!m_playing && val)
×
386
  {
387
    // not playing, play requested
388
    request_play_local(val);
×
389
  }
×
390
  else if(m_playing)
×
391
  {
392
    if(m_paused == val)
×
393
    {
394
      // paused, play requested
395
      // or playing, pause requested
396
      request_play_local(val);
×
397
    }
×
398
  }
×
399
}
×
400

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

419
void ExecutionController::request_transport_from_localtree(TimeVal t)
2✔
420
{
421
  m_transport->requestTransport(t);
2✔
422
}
2✔
423

424
void ExecutionController::request_stop_from_localtree()
10✔
425
{
426
  trigger_stop();
10✔
427
}
10✔
428

429
void ExecutionController::request_reinitialize_from_localtree()
1✔
430
{
431
  trigger_reinitialize();
1✔
432
}
1✔
433

434
void ExecutionController::request_play_from_here(TimeVal t)
×
435
{
436
  if(m_clock)
×
437
  {
438
    m_transport->requestTransport(t);
×
439
  }
×
440
  else
441
  {
442
    on_play_local(true, t);
×
443

444
    // FIXME this ends up calling play_interval again...
445
    auto act = this->context.actions.action<Actions::Play>().action();
×
446
    act->trigger();
×
447
  }
448
}
×
449

450
void ExecutionController::request_begin_scrub(TimeVal t)
×
451
{
452
  if(m_clock)
×
453
  {
454
    m_transport->requestBeginScrub(t);
×
455
  }
×
456
  else
457
  {
458
    on_play_local(true, t);
×
459

460
    // FIXME this ends up calling play_interval again...
461
    auto act = this->context.actions.action<Actions::Play>().action();
×
462
    act->trigger();
×
463
  }
464
}
×
465

466
void ExecutionController::request_scrub(TimeVal t)
×
467
{
468
  if(m_clock)
×
469
  {
470
    m_transport->requestScrub(t);
×
471
  }
×
472
}
×
473

474
void ExecutionController::request_end_scrub(TimeVal t)
×
475
{
476
  if(m_clock)
×
477
  {
478
    m_transport->requestEndScrub(t);
×
479
  }
×
480
}
×
481

482
void ExecutionController::ensure_audio_engine()
23✔
483
{
484
  auto& audio_engine = this->context.guiApplicationPlugin<Audio::ApplicationPlugin>();
23✔
485
  if(!audio_engine.audio)
23✔
486
  {
487
    if(this->context.mainWindow)
×
488
    {
489
      score::warning(
×
490
          this->context.mainWindow, tr("Cannot play"),
×
491
          tr("Cannot start playback. It looks like the audio engine is not "
×
492
             "running.\n"
493
             "Check the audio settings in the software settings to ensure "
494
             "that a sound card "
495
             "is correctly configured.\n\n"
496
             "Check Settings > Audio > Device in particular. "
497
             "The power-on icon at the bottom of the transport toolbar will "
498
             "light up when the engine is running."));
499
      return;
×
500
    }
501
    else
502
    {
503
      qFatal("Cannot playback without an audio engine set up");
×
504
    }
505
  }
506
}
23✔
507

508
void ExecutionController::play_interval(
22✔
509
    Scenario::IntervalModel& cst, exec_setup_fun setup_fun, TimeVal t)
510
{
511
  auto doc = currentDocument();
22✔
512
  if(!doc)
22✔
513
    return;
×
514

515
  auto& ctx = doc->context();
22✔
516

517
  auto exec_plug = ctx.findPlugin<Execution::DocumentPlugin>();
22✔
518
  if(!exec_plug)
22✔
519
    return;
×
520

521
  ensure_audio_engine();
22✔
522

523
  if(m_playing)
22✔
524
  {
525
    SCORE_ASSERT(bool(m_clock));
×
526
    if(m_clock->paused())
×
527
    {
528
      m_clock->resume();
×
529
      m_paused = false;
×
530
    }
×
531
    else
532
    {
533
      // We are playing an interval while we are already executing.
534
      if(auto scenar = qobject_cast<Scenario::ProcessModel*>(cst.parent()))
×
535
      {
536
        auto exec_comp = score::findComponent<Execution::ScenarioComponentBase>(
×
537
            scenar->components());
×
538
        if(exec_comp)
×
539
        {
540
          exec_comp->playInterval(cst);
×
541
        }
×
542
        return;
×
543
      }
544
    }
545
  }
×
546
  else
547
  {
548
    // Here we stop the listening when we start playing the scenario.
549
    // Get all the selected nodes
550
    if(auto explorer = Explorer::try_deviceExplorerFromObject(*doc))
22✔
551
    {
552
      // Disable listening for everything
553
      if(explorer && !exec_plug->settings.getExecutionListening())
22✔
554
      {
555
        explorer->deviceModel().listening().stop();
×
556
      }
×
557

558
      if(this->context.applicationSettings.gui)
22✔
559
      {
560
        if(auto w = Explorer::findDeviceExplorerWidgetInstance(this->context))
8✔
561
        {
562
          w->setEditable(false);
8✔
563
        }
8✔
564
      }
8✔
565
    }
22✔
566

567
    // Ignores the top-level condition / triggers if any
568
    bool forcePlay{};
22✔
569
    if(this->context.applicationSettings.gui)
22✔
570
    {
571
      forcePlay = qApp->keyboardModifiers() & Qt::ControlModifier;
8✔
572
    }
8✔
573
    exec_plug->reload(forcePlay, cst);
22✔
574

575
    auto& c = exec_plug->context();
22✔
576
    m_clock = makeClock(c);
22✔
577

578
    if(setup_fun)
22✔
579
    {
580
      exec_plug->runAllCommands();
×
581
      SCORE_ASSERT(exec_plug->baseScenario());
×
582
      setup_fun(c, *exec_plug->baseScenario());
×
583
      exec_plug->runAllCommands();
×
584
    }
×
585

586
    m_clock->play(t);
22✔
587
    m_paused = false;
22✔
588
  }
589

590
  m_playing = true;
22✔
591
  ctx.execTimer.start();
22✔
592

593
  if(auto transport_plug = ctx.findPlugin<Transport::DocumentPlugin>())
22✔
594
    transport_plug->play();
22✔
595
}
22✔
596

597
void ExecutionController::stop_interval(Scenario::IntervalModel& cst)
×
598
{
599
  auto doc = currentDocument();
×
600
  if(!doc)
×
601
    return;
×
602

603
  auto& ctx = doc->context();
×
604

605
  auto exec_plug = ctx.findPlugin<Execution::DocumentPlugin>();
×
606
  if(!exec_plug)
×
607
    return;
×
608

609
  ensure_audio_engine();
×
610

611
  if(m_playing)
×
612
  {
613
    // We are stopping an interval while we are already executing.
614
    if(auto scenar = qobject_cast<Scenario::ProcessModel*>(cst.parent()))
×
615
    {
616
      auto exec_comp
×
617
          = score::findComponent<Execution::ScenarioComponentBase>(scenar->components());
×
618
      if(exec_comp)
×
619
      {
620
        exec_comp->stopInterval(cst);
×
621
      }
×
622
    }
×
623
  }
×
624
}
×
625

626
TimeVal ExecutionController::execution_time() const
5,778✔
627
{
628
  if(m_clock)
5,778✔
629
  {
630
    SCORE_ASSERT(m_clock->scenario);
500✔
631
    auto& itv = m_clock->scenario->baseInterval().scoreInterval().duration;
500✔
632
    return TimeVal(itv.defaultDuration() * itv.playPercentage());
500✔
633
  }
634
  return TimeVal::zero();
5,278✔
635
}
5,778✔
636

637
void ExecutionController::on_record(::TimeVal t)
×
638
{
639
  SCORE_ASSERT(!m_playing);
×
640

641
  // TODO have a on_exit handler to properly stop the scenario.
642
  if(auto scenar = currentScenarioModel())
×
643
  {
644
    if(auto exec_plug = scenar->context().findPlugin<Execution::DocumentPlugin>())
×
645
    {
646
      // Listening isn't stopped here.
647
      exec_plug->reload(false, scenar->baseInterval());
×
648
      m_clock = makeClock(exec_plug->context());
×
649
      m_clock->play(t);
×
650

651
      scenar->context().execTimer.start();
×
652
      m_playing = true;
×
653
      m_paused = false;
×
654

655
      if(auto transport_plug = scenar->context().findPlugin<Transport::DocumentPlugin>())
×
656
        transport_plug->play();
×
657
    }
×
658
  }
×
659
}
×
660

661
void ExecutionController::on_stop()
1,859✔
662
{
663
  bool wasplaying = m_playing;
1,859✔
664
  m_playing = false;
1,859✔
665
  m_paused = false;
1,859✔
666

667
  stop_clock();
1,859✔
668

669
  if(wasplaying)
1,859✔
670
    send_end_state();
11✔
671

672
  reset_after_stop();
1,859✔
673
}
1,859✔
674

675
void ExecutionController::stop_clock()
1,859✔
676
{
677
  if(m_clock)
1,859✔
678
  {
679
    auto clock = std::move(m_clock);
11✔
680
    m_clock.reset();
11✔
681
    if(auto scenar = currentScenarioModel())
11✔
682
      if(auto exec_plug = scenar->context().findPlugin<Execution::DocumentPlugin>();
11✔
683
         bool(exec_plug))
22✔
684
      {
685
        try
686
        {
687
          clock->stop();
11✔
688
        }
11✔
689
        catch(...)
690
        {
691
          qDebug() << "Error while stopping the clock. There is likely an audio "
×
692
                      "hardware issue.";
693
        }
×
694
      }
11✔
695
  }
11✔
696
}
1,859✔
697

698
void ExecutionController::send_end_state()
11✔
699
{
700
  if(auto doc = currentDocument())
11✔
701
  {
702
    auto& ctx = doc->context();
11✔
703
    if(auto exec_plug = ctx.findPlugin<Execution::DocumentPlugin>())
11✔
704
    {
705
      exec_plug->playStopState();
11✔
706
    }
11✔
707
  }
11✔
708
}
11✔
709
void ExecutionController::reset_after_stop()
1,859✔
710
{
711
  if(auto doc = currentDocument())
1,859✔
712
  {
713
    auto& ctx = doc->context();
1,287✔
714
    ctx.execTimer.stop();
1,287✔
715

716
    if(auto transport_plug = ctx.findPlugin<Transport::DocumentPlugin>())
1,287✔
717
      transport_plug->stop();
1,287✔
718

719
    if(auto exec_plug = ctx.findPlugin<Execution::DocumentPlugin>())
1,287✔
720
    {
721
      exec_plug->clear();
1,287✔
722
    }
1,287✔
723
    else
724
    {
725
      return;
×
726
    }
727

728
    // If we can we resume listening
729
    if(!context.docManager.preparingNewDocument())
1,287✔
730
    {
731
      auto explorer = Explorer::try_deviceExplorerFromObject(*doc);
910✔
732
      if(explorer)
910✔
733
        explorer->deviceModel().listening().restore();
910✔
734
    }
910✔
735
  }
1,287✔
736

737
  auto scenar = currentScenarioModel();
1,859✔
738
  if(!scenar || scenar->closing())
1,859✔
739
    return;
785✔
740
  QTimer::singleShot(
1,074✔
741
      50, this,
742
      [this, itv = QPointer<Scenario::IntervalModel>{&scenar->baseInterval()}] {
2,142✔
743
    if(itv)
1,068✔
744
      reset_edition(*itv);
416✔
745
      });
1,068✔
746
}
1,859✔
747

748
void ExecutionController::reset_edition(Scenario::IntervalModel& base)
416✔
749
{
750
  // Reset edition for the device explorer
751
  if(context.applicationSettings.gui)
416✔
752
  {
753
    if(auto w = Explorer::findDeviceExplorerWidgetInstance(context))
406✔
754
    {
755
      w->setEditable(true);
317✔
756
    }
317✔
757
  }
406✔
758

759
  // FIXME uuuugh have an event in scenario instead (or better, move this
760
  // in process)
761
  base.reset();
416✔
762
  base.executionEvent(Scenario::IntervalExecutionEvent::Finished);
416✔
763
}
416✔
764

765
void ExecutionController::on_reinitialize()
1✔
766
{
767
  // TODO to be more precise, we should stop the execution, but keep
768
  // the execution_state alive until the last message is sent
769
  if(auto scenar = currentScenarioModel())
1✔
770
  {
771
    auto& ctx = scenar->context();
1✔
772
    auto& doc = ctx.document;
1✔
773
    auto exec_plug = ctx.findPlugin<Execution::DocumentPlugin>();
1✔
774
    if(!exec_plug)
1✔
775
      return;
×
776

777
    auto explorer = Explorer::try_deviceExplorerFromObject(doc);
1✔
778

779
    // Disable listening for everything
780
    if(explorer)
1✔
781
      if(!ctx.app.settings<Execution::Settings::Model>().getExecutionListening())
1✔
782
        explorer->deviceModel().listening().stop();
×
783

784
    // If we can we resume listening
785
    if(explorer)
1✔
786
      if(!context.docManager.preparingNewDocument())
2✔
787
        explorer->deviceModel().listening().restore();
1✔
788

789
    on_stop();
1✔
790

791
    exec_plug->playStartState();
1✔
792
  }
1✔
793
}
1✔
794

795
void ExecutionController::init_transport()
1,761✔
796
{
797
  if(m_transport)
1,761✔
798
    m_transport->teardown();
×
799

800
  auto& s = context.settings<Execution::Settings::Model>();
1,761✔
801
  m_transport = s.getTransport();
1,761✔
802
  SCORE_ASSERT(m_transport);
1,761✔
803
  m_transport->setup();
1,761✔
804
  connect(
1,761✔
805
      m_transport, &Execution::TransportInterface::play, this,
1,761✔
806
      &ExecutionController::trigger_play);
807
  connect(
1,761✔
808
      m_transport, &Execution::TransportInterface::pause, this,
1,761✔
809
      &ExecutionController::trigger_pause);
810
  connect(
1,761✔
811
      m_transport, &Execution::TransportInterface::stop, this,
1,761✔
812
      &ExecutionController::trigger_stop);
813
  connect(
1,761✔
814
      m_transport, &Execution::TransportInterface::transport, this,
1,761✔
815
      &ExecutionController::on_transport);
816
  connect(
1,761✔
817
      m_transport, &Execution::TransportInterface::beginScrub, this,
1,761✔
818
      &ExecutionController::on_begin_scrub);
819
  connect(
1,761✔
820
      m_transport, &Execution::TransportInterface::scrub, this,
1,761✔
821
      &ExecutionController::on_scrub);
822
  connect(
1,761✔
823
      m_transport, &Execution::TransportInterface::endScrub, this,
1,761✔
824
      &ExecutionController::on_end_scrub);
825

826
  auto& audio_settings = this->context.settings<Audio::Settings::Model>();
1,761✔
827
  con(audio_settings, &Audio::Settings::Model::JackTransportChanged, this,
3,522✔
828
      &ExecutionController::init_transport, Qt::UniqueConnection);
1,761✔
829

830
  con(audio_settings, &Audio::Settings::Model::changed, this,
3,522✔
831
      &ExecutionController::on_audioSettingsChanged,
1,761✔
832
      Qt::ConnectionType(Qt::DirectConnection | Qt::UniqueConnection));
1,761✔
833
}
1,761✔
834

835
void ExecutionController::on_audioSettingsChanged()
1✔
836
{
837
  // The engine is about to be restarted, which tears the audio device tree
838
  // down under the graph. Stopping here is synchronous, hence done before the
839
  // audio plug-in's queued restart.
840
  if(m_playing)
1✔
841
    trigger_stop();
×
842
}
1✔
843

844
Scenario::ScenarioDocumentModel* ExecutionController::currentScenarioModel()
1,893✔
845
{
846
  if(auto doc = currentDocument())
1,893✔
847
  {
848
    return score::IDocument::try_modelDelegate<Scenario::ScenarioDocumentModel>(*doc);
1,321✔
849
  }
850
  return nullptr;
572✔
851
}
1,893✔
852

853
Scenario::ScenarioDocumentPresenter* ExecutionController::currentScenarioPresenter()
×
854
{
855
  if(auto doc = currentDocument())
×
856
  {
857
    return score::IDocument::try_presenterDelegate<Scenario::ScenarioDocumentPresenter>(
×
858
        *doc);
×
859
  }
860
  return nullptr;
×
861
}
×
862

863
score::Document* ExecutionController::currentDocument() const
3,785✔
864
{
865
  return context.documents.currentDocument();
3,785✔
866
}
867

868
std::unique_ptr<Execution::Clock>
869
ExecutionController::makeClock(const Execution::Context& ctx)
22✔
870
{
871
  auto& s = context.settings<Execution::Settings::Model>();
22✔
872
  auto clk = s.makeClock(ctx);
22✔
873
  SCORE_ASSERT(clk->scenario);
22✔
874
  auto& itv = clk->scenario->baseInterval().interval();
22✔
875
  con(itv, &IdentifiedObjectAbstract::identified_object_destroying, this,
44✔
876
      [this] { trigger_stop(); });
29✔
877
  return clk;
22✔
878
}
22✔
879

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