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

ossia / score / 30754311710

02 Aug 2026 03:25PM UTC coverage: 15.222% (-0.04%) from 15.263%
30754311710

Pull #2163

github

web-flow
Merge 8b7bf0aad into e99b6a5da
Pull Request #2163: Fix backwards playback for audio plug-ins (VST, VST3, LV2, JSFX)

0 of 57 new or added lines in 5 files covered. (0.0%)

343 existing lines in 16 files now uncovered.

30381 of 199582 relevant lines covered (15.22%)

1075.26 hits per line

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

23.28
/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)
78✔
117
    : context{ctx}
39✔
118
    , m_scenario{ctx.guiApplicationPlugin<Scenario::ScenarioApplicationPlugin>()}
39✔
119
    , m_actions{m_scenario.transportActions()}
39✔
120
{
39✔
121
  if(ctx.applicationSettings.gui)
39✔
122
  {
123
    auto& acts = ctx.actions;
39✔
124
    using namespace Actions;
125
    connect(
39✔
126
        acts.action<Play>().action(), &QAction::triggered, this,
39✔
127
        &ExecutionController::request_play_local, Qt::QueuedConnection);
128

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

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

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

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

156
ExecutionController::~ExecutionController()
39✔
157
{
39✔
158
  m_transport->teardown();
39✔
159
}
39✔
160

161
TransportInterface& ExecutionController::transport() const noexcept
×
162
{
163
  return *m_transport;
×
164
}
165

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

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

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

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

204
void ExecutionController::request_stop()
53✔
205
{
206
  m_transport->requestStop();
53✔
207
}
53✔
208

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

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

236
void ExecutionController::trigger_pause()
×
237
{
238
  m_actions.onPause();
×
239
  on_pause();
×
240
}
×
241

242
void ExecutionController::trigger_stop()
53✔
243
{
244
  m_actions.onStop();
53✔
245
  on_stop();
53✔
246

247
  // See note above
248
  this->m_requestLocalPlay = false;
53✔
249
}
53✔
250

251
void ExecutionController::trigger_reinitialize()
×
252
{
253
  m_actions.onStop();
×
254
  on_reinitialize();
×
255

256
  // See note above
257
  this->m_requestLocalPlay = false;
×
258
}
×
259

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

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

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

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

316
void ExecutionController::on_pause()
×
317
{
318
  ensure_audio_engine();
×
319

320
  if(m_clock)
×
321
  {
322
    m_clock->pause();
×
323
    m_paused = true;
×
324

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

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

335
void ExecutionController::on_transport(TimeVal t)
×
336
{
337
  if(!m_clock)
×
338
    return;
×
339

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

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

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

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

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

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

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

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

417
void ExecutionController::request_transport_from_localtree(TimeVal t)
×
418
{
419
  m_transport->requestTransport(t);
×
420
}
×
421

422
void ExecutionController::request_stop_from_localtree()
×
423
{
424
  trigger_stop();
×
425
}
×
426

427
void ExecutionController::request_reinitialize_from_localtree()
×
428
{
429
  trigger_reinitialize();
×
430
}
×
431

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

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

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

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

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

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

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

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

513
  auto& ctx = doc->context();
×
514

515
  auto exec_plug = ctx.findPlugin<Execution::DocumentPlugin>();
×
516
  if(!exec_plug)
×
517
    return;
×
518

519
  ensure_audio_engine();
×
520

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

556
      if(this->context.applicationSettings.gui)
×
557
      {
558
        if(auto w = Explorer::findDeviceExplorerWidgetInstance(this->context))
×
559
        {
560
          w->setEditable(false);
×
561
        }
×
562
      }
×
563
    }
×
564

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

573
    auto& c = exec_plug->context();
×
574
    m_clock = makeClock(c);
×
575

576
    if(setup_fun)
×
577
    {
578
      exec_plug->runAllCommands();
×
579
      SCORE_ASSERT(exec_plug->baseScenario());
×
580
      setup_fun(c, *exec_plug->baseScenario());
×
581
      exec_plug->runAllCommands();
×
582
    }
×
583

584
    m_clock->play(t);
×
585
    m_paused = false;
×
586
  }
587

588
  m_playing = true;
×
589
  ctx.execTimer.start();
×
590

591
  if(auto transport_plug = ctx.findPlugin<Transport::DocumentPlugin>())
×
592
    transport_plug->play();
×
593
}
×
594

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

601
  auto& ctx = doc->context();
×
602

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

607
  ensure_audio_engine();
×
608

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

624
TimeVal ExecutionController::execution_time() const
46✔
625
{
626
  if(m_clock)
46✔
627
  {
628
    SCORE_ASSERT(m_clock->scenario);
×
629
    auto& itv = m_clock->scenario->baseInterval().scoreInterval().duration;
×
630
    return TimeVal(itv.defaultDuration() * itv.playPercentage());
×
631
  }
632
  return TimeVal::zero();
46✔
633
}
46✔
634

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

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

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

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

659
void ExecutionController::on_stop()
53✔
660
{
661
  bool wasplaying = m_playing;
53✔
662
  m_playing = false;
53✔
663
  m_paused = false;
53✔
664

665
  stop_clock();
53✔
666

667
  if(wasplaying)
53✔
668
    send_end_state();
×
669

670
  reset_after_stop();
53✔
671
}
53✔
672

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

696
void ExecutionController::send_end_state()
×
697
{
698
  if(auto doc = currentDocument())
×
699
  {
700
    auto& ctx = doc->context();
×
701
    if(auto exec_plug = ctx.findPlugin<Execution::DocumentPlugin>())
×
702
    {
703
      exec_plug->playStopState();
×
704
    }
×
705
  }
×
706
}
×
707
void ExecutionController::reset_after_stop()
53✔
708
{
709
  if(auto doc = currentDocument())
53✔
710
  {
711
    auto& ctx = doc->context();
33✔
712
    ctx.execTimer.stop();
33✔
713

714
    if(auto transport_plug = ctx.findPlugin<Transport::DocumentPlugin>())
33✔
715
      transport_plug->stop();
33✔
716

717
    if(auto exec_plug = ctx.findPlugin<Execution::DocumentPlugin>())
33✔
718
    {
719
      exec_plug->clear();
33✔
720
    }
33✔
721
    else
722
    {
723
      return;
×
724
    }
725

726
    // If we can we resume listening
727
    if(!context.docManager.preparingNewDocument())
33✔
728
    {
729
      auto explorer = Explorer::try_deviceExplorerFromObject(*doc);
25✔
730
      if(explorer)
25✔
731
        explorer->deviceModel().listening().restore();
25✔
732
    }
25✔
733
  }
33✔
734

735
  auto scenar = currentScenarioModel();
53✔
736
  if(!scenar || scenar->closing())
53✔
737
    return;
25✔
738
  QTimer::singleShot(
28✔
739
      50, this,
740
      [this, itv = QPointer<Scenario::IntervalModel>{&scenar->baseInterval()}] {
56✔
741
    if(itv)
28✔
UNCOV
742
      reset_edition();
×
743
      });
28✔
744
}
53✔
745

UNCOV
746
void ExecutionController::reset_edition()
×
747
{
748
  // Reset edition for the device explorer
UNCOV
749
  if(context.applicationSettings.gui)
×
750
  {
UNCOV
751
    if(auto w = Explorer::findDeviceExplorerWidgetInstance(context))
×
752
    {
UNCOV
753
      w->setEditable(true);
×
UNCOV
754
    }
×
UNCOV
755
  }
×
756

757
  // FIXME uuuugh have an event in scenario instead (or better, move this
758
  // in process)
UNCOV
759
  auto scenar = currentScenarioModel();
×
UNCOV
760
  if(!scenar)
×
761
    return;
×
UNCOV
762
  scenar->baseInterval().reset();
×
UNCOV
763
  scenar->baseInterval().executionEvent(Scenario::IntervalExecutionEvent::Finished);
×
UNCOV
764
}
×
765

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

778
    auto explorer = Explorer::try_deviceExplorerFromObject(doc);
×
779

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

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

790
    on_stop();
×
791

792
    exec_plug->playStartState();
×
793
  }
×
794
}
×
795

796
void ExecutionController::init_transport()
39✔
797
{
798
  if(m_transport)
39✔
799
    m_transport->teardown();
×
800

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

827
  auto& audio_settings = this->context.settings<Audio::Settings::Model>();
39✔
828
  con(audio_settings, &Audio::Settings::Model::JackTransportChanged, this,
78✔
829
      &ExecutionController::init_transport, Qt::UniqueConnection);
39✔
830
}
39✔
831

832
Scenario::ScenarioDocumentModel* ExecutionController::currentScenarioModel()
53✔
833
{
834
  if(auto doc = currentDocument())
53✔
835
  {
836
    return score::IDocument::try_modelDelegate<Scenario::ScenarioDocumentModel>(*doc);
33✔
837
  }
838
  return nullptr;
20✔
839
}
53✔
840

841
Scenario::ScenarioDocumentPresenter* ExecutionController::currentScenarioPresenter()
×
842
{
843
  if(auto doc = currentDocument())
×
844
  {
845
    return score::IDocument::try_presenterDelegate<Scenario::ScenarioDocumentPresenter>(
×
846
        *doc);
×
847
  }
848
  return nullptr;
×
849
}
×
850

851
score::Document* ExecutionController::currentDocument() const
106✔
852
{
853
  return context.documents.currentDocument();
106✔
854
}
855

856
std::unique_ptr<Execution::Clock>
857
ExecutionController::makeClock(const Execution::Context& ctx)
×
858
{
859
  auto& s = context.settings<Execution::Settings::Model>();
×
860
  auto clk = s.makeClock(ctx);
×
861
  SCORE_ASSERT(clk->scenario);
×
862
  auto& itv = clk->scenario->baseInterval().interval();
×
863
  con(itv, &IdentifiedObjectAbstract::identified_object_destroying, this,
×
864
      [this] { trigger_stop(); });
×
865
  return clk;
×
866
}
×
867

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