Loading...
Searching...
No Matches
ManualClock.hpp
1#pragma once
2
3#include <Execution/Clock/ClockFactory.hpp>
4#include <Execution/Clock/DataflowClock.hpp>
5#include <Execution/DocumentPlugin.hpp>
6
7#include <score/application/GUIApplicationContext.hpp>
8
9#include <ossia/audio/audio_engine.hpp>
10#include <ossia/editor/scenario/time_value.hpp>
11
12#include <QMainWindow>
13#include <QPointer>
14#include <QToolBar>
15
16#include <algorithm>
17#include <atomic>
18#include <chrono>
19#include <cmath>
20#include <thread>
21#include <vector>
22
23#include <verdigris>
24
25namespace Execution
26{
27namespace ManualClock
28{
29
33{
34 ossia::audio_engine::fun_type tick;
35 int64_t bufferSize{512};
36 double sampleRate{44100.};
37
38 std::vector<std::vector<float>> inputs;
39 std::vector<std::vector<float>> outputs;
40 std::vector<float*> inputPointers;
41 std::vector<float*> outputPointers;
42
43 std::atomic<int64_t> requested{};
44 std::atomic<int64_t> done{};
45
47 ossia::audio_engine::fun_type t, int64_t bs, double rate, int ins, int outs)
48 : tick{std::move(t)}
49 , bufferSize{std::max<int64_t>(bs, 1)}
50 , sampleRate{rate > 0. ? rate : 44100.}
51 , inputs(std::max(ins, 0), std::vector<float>(bufferSize))
52 , outputs(std::max(outs, 0), std::vector<float>(bufferSize))
53 {
54 for(auto& c : inputs)
55 inputPointers.push_back(c.data());
56 for(auto& c : outputs)
57 outputPointers.push_back(c.data());
58 }
59
60 int64_t samplesAt(double seconds) const noexcept
61 {
62 return int64_t(std::floor(seconds * sampleRate + 1e-6));
63 }
64
65 void run(const ossia::audio_tick_state& t)
66 {
67 for(int chan = 0; chan < t.n_out; chan++)
68 std::fill_n(t.outputs[chan], t.frames, 0.f);
69
70 using clk = std::chrono::steady_clock;
71 constexpr auto linger = std::chrono::milliseconds(5);
72 auto deadline = clk::now() + linger;
73 for(;;)
74 {
75 const int64_t target = requested.load(std::memory_order_acquire);
76 int64_t cur = done.load(std::memory_order_relaxed);
77 if(cur < target)
78 {
79 while(cur < target)
80 {
81 const int64_t n = std::min(target - cur, bufferSize);
82 ossia::audio_tick_state st{
83 inputPointers.data(),
84 outputPointers.data(),
85 int32_t(inputPointers.size()),
86 int32_t(outputPointers.size()),
87 uint64_t(n),
88 double(cur) / sampleRate};
89 tick(st);
90 cur += n;
91 }
92 done.store(cur, std::memory_order_release);
93 deadline = clk::now() + linger;
94 continue;
95 }
96 if(clk::now() >= deadline)
97 break;
98 std::this_thread::yield();
99 }
100 }
101
102 bool waitFor(int64_t target, const ossia::audio_engine& engine)
103 {
104 int64_t prev = requested.load(std::memory_order_relaxed);
105 if(target > prev)
106 requested.store(target, std::memory_order_release);
107
108 using clk = std::chrono::steady_clock;
109 const auto t0 = clk::now();
110 int spins = 0;
111 while(done.load(std::memory_order_acquire) < target)
112 {
113 if(!engine.running())
114 return false;
115 if(clk::now() - t0 > std::chrono::seconds(60))
116 return false;
117 if(spins++ < 1000)
118 std::this_thread::yield();
119 else
120 std::this_thread::sleep_for(std::chrono::microseconds(50));
121 }
122 return true;
123 }
124};
125
126class TimeWidget : public QToolBar
127{
128 W_OBJECT(TimeWidget)
129public:
130 TimeWidget(QWidget* parent = nullptr)
131 : QToolBar{parent}
132 {
133 for(int ms : {1, 10, 100, 1000})
134 {
135 auto act = addAction(QStringLiteral("+%1 ms").arg(ms));
136 connect(act, &QAction::triggered, this, [this, ms] { advance(ms); });
137 }
138 }
139
140 void advance(int milliseconds) W_SIGNAL(advance, milliseconds);
141};
142
143class Clock final : public Dataflow::Clock
144{
145public:
146 Clock(const Execution::Context& ctx)
147 : Dataflow::Clock{ctx, true}
148 {
149 }
150
151 ~Clock() override { delete m_widg.data(); }
152
153private:
154 void play_impl(const TimeVal& t) override
155 {
156 Dataflow::Clock::play_impl(t);
157
158 if(auto win = context.doc.app.mainWindow)
159 {
160 m_widg = new TimeWidget;
161 win->addToolBar(Qt::ToolBarArea::BottomToolBarArea, m_widg);
162 QObject::connect(m_widg, &TimeWidget::advance, m_widg, [this](int ms) {
163 stepTo(steppedSeconds() + ms / 1000.);
164 });
165 m_widg->show();
166 }
167 }
168
169 void stop_impl() override
170 {
171 delete m_widg.data();
172 Dataflow::Clock::stop_impl();
173 }
174
175 QPointer<TimeWidget> m_widg;
176};
177
179{
180 SCORE_CONCRETE("5e8d0f1b-752f-4e29-8c8c-ecd65bd69806")
181
182 QString prettyName() const override { return QObject::tr("Manual"); }
183
184 std::unique_ptr<Execution::Clock> make(const Execution::Context& ctx) override
185 {
186 return std::make_unique<Clock>(ctx);
187 }
188
190 makeTimeFunction(const score::DocumentContext& ctx) const override
191 {
192 return Dataflow::ClockFactory{}.makeTimeFunction(ctx);
193 }
194
195 Execution::reverse_time_function
196 makeReverseTimeFunction(const score::DocumentContext& ctx) const override
197 {
198 return Dataflow::ClockFactory{}.makeReverseTimeFunction(ctx);
199 }
200};
201}
202}
Definition DataflowClock.hpp:58
Definition DataflowClock.hpp:24
bool stepTo(double seconds) override
Definition DataflowClock.cpp:176
Definition ClockFactory.hpp:82
Definition ManualClock.hpp:179
Definition ManualClock.hpp:144
Definition ManualClock.hpp:127
Components used for the execution of a score.
Definition PortTap.cpp:24
smallfun::function< ossia::time_value(const TimeVal &)> time_function
Sets-up and manages the main execution clock.
Definition ExecutionContext.hpp:66
Definition ExecutionContext.hpp:208
Definition ManualClock.hpp:33
Definition TimeValue.hpp:21
Definition DocumentContext.hpp:18