Execution Namespace Reference

Components used for the execution of a score. More...

Detailed Description

Components used for the execution of a score.

Execution state-machine explanation:

  • There are multiple sources for what controls the transport:
  • From the GUI
  • From the local tree or remote control plug-in
  • From an external software (e.g. JACK transport)
  • Automatically (e.g. –autoplay argument on command-line)
  • No matter the transport source, if there's a GUI its state must be consistent with what happens.

Thus:

  • The do_*** functions are requests
  • The trigger_*** functions will perform the action and set the GUI state correctly
  • The on_*** contain the actual implementation of the transport operation

Notes on QAction:

  • QAction::toggle(); -> will send toggled(true/false);
  • QAction::trigger(); -> will send toggled(true/false); triggered(true/false);
  • QAction::check(b); -> will send toggled(b);
  • GUI: like QAction::trigger

Thus, triggered can be used to differentiate between

  • called from the GUI
  • called from the software

Example of call sequence:

  • Pressing the "global play button" (stop -> play): -> QAction::toggled(true) -> GUI changes (see TransportActions ctor) -> QAction::triggered(true) -> ApplicationPlugin::request_play_global(true) -> TransportInterface::requestPlay() -> the transport implementation eventually sends the "play()" signal -> &ApplicationPlugin::trigger_play_global
  • Pressing the "global play button" (play -> pause): -> QAction::toggled(false) -> GUI changes (see TransportActions ctor) -> QAction::triggered(false) -> ApplicationPlugin::request_play_global(false) -> TransportInterface::requestPause() -> the transport implementation eventually sends the "pause()" signal -> &ApplicationPlugin::trigger_pause()
  • Playing with "play from here": -> ApplicationPlugin::request_play(true, t) -> TransportInterface::requestTransport(t) -> TransportInterface::requestPlay -> The transport implementation eventually sends the "transport()" signal -> ?? -> The transport implementation eventually sends the "play()" signal -> &ApplicationPlugin::trigger_play_global
  • Starting the transport from QJackCtl with a GUI -> the transport implementation eventually sends the "play()" signal -> &ApplicationPlugin::trigger_play_global
  • Starting the transport from QJackCtl without a GUI -> the transport implementation eventually sends the "play()" signal -> &ApplicationPlugin::trigger_play_global
  • Receiving a "/play" message to the local tree with a GUI -> ApplicationPlugin::request_play -> TransportInterface::requestPlay -> The transport implementation eventually sends the "play()" signal -> &ApplicationPlugin::trigger_play_global
  • Receiving a "/play" message to the local tree without a GUI

This hierarchy of types is used to create the OSSIA structures for the execution behind score::Components.
Currently, all the execution structures are recreated from scratch when doing "play".
The classes inheriting from Execution::ProcessComponent are first created, then the Execution::IntervalComponent will ask them for their ossia::time_process which it gives to the matching ossia::time_interval.

Live modification during execution.

The execution engine allows live modification of scores. Since the execution happens in a different thread than edition, we have to be extremely careful however.
Instead of locking all the data structures of the OSSIA API with mutex, which may slow down the execution, we instead have a lock-free queue of edition commands.
Modifications are submitted from the component hierarchy :

A modification follows this pattern :

[ User modification in the GUI ]
|
v
[ Commands applied ]
|
v
[ Models modified and
modification signals emitted ]
|
v
[ Execution components
catches the signal ]
|
v
[ Command inserted into
|
v
[ The execution algorithm applies the
commands at the end of the current tick ]
Components used for the execution of a score.
Definition: ProcessComponent.cpp:12
ExecutionCommandQueue & executionQueue
Definition: ExecutionContext.hpp:91

For modification of values, for instance the duration of a Interval, this is easily visible. See for instance Execution::IntervalComponentBase 's constructor.
For creation and removal of objects, this should be handled automatically by the various ComponentHierarchy classes which take care of creating and removing the objects in the correct order. The Component classes just have to provide functions that will do the actual instantiation, and pre- & post- removal steps.

The actual "root" execution algorithm is given in Execution::DefaulClock::makeDefaultCallback

Execution Thread Safety

One must take care when modifying the Execution classes, since thins happen on two different threads.

The biggest problem is that the score structures could be created and deleted in a single tick. For instance when doing a complete undo - redo of the whole undo stack.
This means that anything send to the command queue must absolutely never access any of the score structures (for instance Scenario::IntervalModel, etc) directly : they have to be copied. Else, there will be crashes, someday.
In the flow graph shown before, everything up to and including "Command inserted into the execution queue" happens in the GUI thread, hence one can rely on everything "being here" at this point. However, in the actual commands, the only things safe to use are :

  • Copies of data : simple values, ints, etc, are safe.
  • Shared pointers : unlike most other places in score, the Execution components are not owned by their parents, but through shared pointers. This means that shall the component be removed, if the pointer was copied in the ExecutionCommand, there is no risk of crash. But one must take care of copying the actual shared_ptr and not just the this pointer for instance. Multiple classes inherit from std::enable_shared_from_this to allow a shared_from_this() function that gives back a shared_ptr to the this instance.

Classes

class  InvalidProcessException
 
class  ProcessComponent
 
struct  ProcessComponent_T
 
class  ProcessComponentFactory
 
class  ProcessComponentFactory_T
 
class  ProcessComponentFactoryList
 
class  ExecutionAction
 
class  ExecutionActionList
 
struct  Context
 
struct  SetupContext
 
struct  Transaction
 
class  PlayListeningHandler
 
class  PlayListeningHandlerFactory
 
struct  FinishCallback
 
class  BaseScenarioElement
 
class  Clock
 
class  ClockFactory
 
class  ClockFactoryList
 
class  DefaultClock
 
class  PlayContextMenu
 
struct  dfs_visitor_state
 
struct  dfs_visitor
 
struct  PlayFromIntervalScenarioPruner
 Sets the execution engine to play only the required parts. More...
 
struct  Queues
 
class  DocumentPlugin
 
class  ExecutionController
 
class  JackTransport
 
class  FaustEffectComponent
 
class  JitEffectComponent
 
class  MergerComponent
 
struct  MetronomeSounds
 
class  MetroComponent
 
class  SoundComponent
 
class  StepComponent
 
class  EventComponent
 
struct  interval_duration_data
 
class  IntervalComponentBase
 
class  IntervalComponent
 
struct  AddProcess
 
struct  RecomputePropagate
 
struct  ReconnectOutlets
 
struct  HandleNodeChange
 
class  StateComponentBase
 
class  StateComponent
 
struct  TimeSyncExecutionCallbacks
 
class  TimeSyncComponent
 
class  ScenarioComponentBase
 
struct  ScenarioComponent
 

Typedefs

using ExecutionCommand = smallfun::function< void(), 128, std::max((int) 8,(int) std::max(alignof(std::function< void()>), alignof(double))), smallfun::Methods::Move >
 
using GCCommand = smallfun::function< void(), 128+4 *8, std::max((int) 8,(int) std::max(alignof(std::function< void()>), alignof(double))), smallfun::Methods::Move >
 
using Component = score::GenericComponent< const Context >
 
using time_function = smallfun::function< ossia::time_value(const TimeVal &)>
 Sets-up and manages the main execution clock. More...
 
using reverse_time_function = smallfun::function< TimeVal(const ossia::time_value &)>
 
using ExecutionCommandQueue = ossia::spsc_queue< ExecutionCommand, 1024 >
 
using EditionCommandQueue = moodycamel::ConcurrentQueue< ExecutionCommand >
 
using GCCommandQueue = moodycamel::ConcurrentQueue< GCCommand >
 
using TransportInterface = Transport::TransportInterface
 
using exec_setup_fun = std::function< void(const Execution::Context &, Execution::BaseScenarioElement &)>
 
using tick_fun = ossia::audio_engine::fun_type
 
using FaustEffectComponentFactory = Execution::ProcessComponentFactory_T< FaustEffectComponent >
 
using JitEffectComponentFactory = Execution::ProcessComponentFactory_T< JitEffectComponent >
 
using MergerComponentFactory = ::Execution::ProcessComponentFactory_T< MergerComponent >
 
using MetroComponentFactory = ::Execution::ProcessComponentFactory_T< MetroComponent >
 
using SoundComponentFactory = ::Execution::ProcessComponentFactory_T< SoundComponent >
 
using StepComponentFactory = ::Execution::ProcessComponentFactory_T< StepComponent >
 
using ScenarioComponentHierarchy = HierarchicalScenarioComponent< ScenarioComponentBase, Scenario::ProcessModel, IntervalComponent, EventComponent, TimeSyncComponent, StateComponent, false >
 
using ScenarioComponentFactory = Execution::ProcessComponentFactory_T< ScenarioComponent >
 

Functions

ossia::net::node_base * findNode (const ossia::execution_state &st, const State::Address &addr)
 
std::optional< ossia::destination > makeDestination (const ossia::execution_state &devices, const State::AddressAccessor &addr)
 
template<typename T , typename Impl >
void set_destination_impl (const Context &plug, const State::AddressAccessor &address, const T &port, Impl &&append)
 
template<typename T >
constexpr auto gc (T &&t) noexcept
 
Audio::tick_fun makeExecutionTick (ossia::tick_setup_options opt, Execution::DocumentPlugin &plug, const std::shared_ptr< Execution::BaseScenarioElement > &scenar)
 
Audio::tick_fun makeBenchmarkTick (ossia::tick_setup_options opt, Execution::DocumentPlugin &plug, const std::shared_ptr< Execution::BaseScenarioElement > &scenar)
 
std::pair< std::optional< ossia::tempo_curve >, Scenario::TempoProcess * > tempoCurve (const Scenario::IntervalModel &itv, const Execution::Context &ctx)
 
ossia::time_signature_map timeSignatureMap (const Scenario::IntervalModel &itv, const Execution::Context &ctx)
 
auto propagatedOutlets (const Process::Outlets &outlets) noexcept
 
void connectPropagated (const ossia::node_ptr &process_node, const ossia::node_ptr &interval_node, ossia::graph_interface &g, const ossia::pod_vector< std::size_t > &propagated_outlets) noexcept
 
void updatePropagated (const ossia::node_ptr &process_node, const ossia::node_ptr &interval_node, ossia::graph_interface &g, std::size_t port_idx, bool is_propagated) noexcept
 
template<>
IntervalComponentScenarioComponentBase::make< IntervalComponent, Scenario::IntervalModel > (Scenario::IntervalModel &cst)
 
template<>
StateComponentScenarioComponentBase::make< StateComponent, Scenario::StateModel > (Scenario::StateModel &st)
 
template<>
EventComponentScenarioComponentBase::make< EventComponent, Scenario::EventModel > (Scenario::EventModel &ev)
 
template<>
TimeSyncComponentScenarioComponentBase::make< TimeSyncComponent, Scenario::TimeSyncModel > (Scenario::TimeSyncModel &tn)
 

Typedef Documentation

◆ time_function

typedef smallfun::function< ossia::time_value(const TimeVal &)> Execution::time_function

Sets-up and manages the main execution clock.

This class allows to create control mechanisms for the score execution clock.

The default implementation, DefaultClock, uses sleep() and threading to do the execution.

Other implementations could be used to synchronize to external clocks, such as the sound card clock, or the host clock if score is used as a plug-in.

The derived constructors should set-up the clock drive mode, speed, etc.