Zrythm v2.0.0-DEV
a highly automated and intuitive digital audio workstation
Loading...
Searching...
No Matches
transport.h
1// SPDX-FileCopyrightText: © 2018-2025 Alexandros Theodotou <alex@zrythm.org>
2// SPDX-License-Identifier: LicenseRef-ZrythmLicense
3
4#pragma once
5
6#include "dsp/atomic_position_qml_adapter.h"
7#include "dsp/itransport.h"
8#include "dsp/playhead_qml_adapter.h"
9#include "utils/icloneable.h"
10
11namespace zrythm::dsp
12{
13enum class PrerollCountBars
14{
15 PrerollNone,
16 PrerollOne,
17 PrerollTwo,
18 PrerollFour,
19};
20
21inline int
22preroll_count_bars_enum_to_int (PrerollCountBars bars)
23{
24 switch (bars)
25 {
26 case PrerollCountBars::PrerollNone:
27 return 0;
28 case PrerollCountBars::PrerollOne:
29 return 1;
30 case PrerollCountBars::PrerollTwo:
31 return 2;
32 case PrerollCountBars::PrerollFour:
33 return 4;
34 }
35 return -1;
36}
37
43class Transport : public QObject, public dsp::ITransport
44{
45 Q_OBJECT
46 QML_ELEMENT
47 Q_PROPERTY (
48 bool loopEnabled READ loopEnabled WRITE setLoopEnabled NOTIFY
49 loopEnabledChanged)
50 Q_PROPERTY (
51 bool recordEnabled READ recordEnabled WRITE setRecordEnabled NOTIFY
52 recordEnabledChanged)
53 Q_PROPERTY (
54 bool punchEnabled READ punchEnabled WRITE setPunchEnabled NOTIFY
55 punchEnabledChanged)
56 Q_PROPERTY (
57 PlayState playState READ getPlayState WRITE setPlayState NOTIFY
58 playStateChanged)
59 Q_PROPERTY (zrythm::dsp::PlayheadQmlWrapper * playhead READ playhead CONSTANT)
60 Q_PROPERTY (
61 zrythm::dsp::AtomicPositionQmlAdapter * cuePosition READ cuePosition CONSTANT)
62 Q_PROPERTY (
63 zrythm::dsp::AtomicPositionQmlAdapter * loopStartPosition READ
64 loopStartPosition CONSTANT)
65 Q_PROPERTY (
66 zrythm::dsp::AtomicPositionQmlAdapter * loopEndPosition READ loopEndPosition
67 CONSTANT)
68 Q_PROPERTY (
69 zrythm::dsp::AtomicPositionQmlAdapter * punchInPosition READ punchInPosition
70 CONSTANT)
71 Q_PROPERTY (
73 punchOutPosition CONSTANT)
74 QML_UNCREATABLE ("")
75
76
77
78 static constexpr auto REPEATED_BACKWARD_MS = au::milli (units::seconds) (240);
79
80public:
81 Q_ENUM (PlayState)
82
85 * gsettings.
86 */
87 enum class Display
88 {
89 BBT,
90 Time,
91 };
92 Q_ENUM (Display)
93
94
100 enum class RecordingMode
101 {
120
122
123 /**
124 * Events get put in new lanes each time recording starts/resumes (eg,
125 * when looping or entering/leaving the punch range).
126 */
127 Takes,
134 };
135 Q_ENUM (RecordingMode)
136
137public:
138 struct ConfigProvider
139 {
143 std::function<bool ()> return_to_cue_on_pause_;
149 std::function<int ()> metronome_countin_bars_;
150
156 std::function<int ()> recording_preroll_bars_;
157 };
158
160 {
161 public:
163 std::pair<units::sample_t, units::sample_t> loop_range,
164 std::pair<units::sample_t, units::sample_t> punch_range,
165 units::sample_t playhead_position,
168 PlayState play_state,
169 bool loop_enabled,
170 bool punch_enabled,
172 : loop_range_ (loop_range), punch_range_ (punch_range),
173 playhead_position_ (playhead_position),
174 recording_preroll_frames_remaining_ (recording_preroll_frames_remaining),
175 metronome_countin_frames_remaining_ (metronome_countin_frames_remaining),
176 play_state_ (play_state), loop_enabled_ (loop_enabled),
177 punch_enabled_ (punch_enabled), recording_enabled_ (recording_enabled)
179 }
180
181 std::pair<units::sample_t, units::sample_t>
182 get_loop_range_positions () const noexcept override
183 {
184 return loop_range_;
186 std::pair<units::sample_t, units::sample_t>
187 get_punch_range_positions () const noexcept override
188 {
189 return punch_range_;
191 PlayState get_play_state () const noexcept override { return play_state_; }
192 units::sample_t
193 get_playhead_position_in_audio_thread () const noexcept override
194 {
195 return playhead_position_;
196 }
197 bool loop_enabled () const noexcept override { return loop_enabled_; }
198
199 bool punch_enabled () const noexcept override { return punch_enabled_; }
200 bool recording_enabled () const noexcept override
201 {
202 return recording_enabled_;
203 }
204 units::sample_t
205 recording_preroll_frames_remaining () const noexcept override
206 {
207 return recording_preroll_frames_remaining_;
208 }
209 units::sample_t
210 metronome_countin_frames_remaining () const noexcept override
211 {
212 return metronome_countin_frames_remaining_;
213 }
214
215 void set_play_state (dsp::ITransport::PlayState play_state)
216 {
217 play_state_ = play_state;
218 }
219 void set_position (units::sample_t position)
220 {
221 playhead_position_ = position;
222 }
223 void consume_metronome_countin_samples (units::sample_t samples)
224 {
225 metronome_countin_frames_remaining_ -= samples;
226 }
227 void consume_recording_preroll_samples (units::sample_t samples)
228 {
229 recording_preroll_frames_remaining_ -= samples;
230 }
231
232 private:
233 std::pair<units::sample_t, units::sample_t> loop_range_;
234 std::pair<units::sample_t, units::sample_t> punch_range_;
235 units::sample_t playhead_position_;
236 units::sample_t recording_preroll_frames_remaining_;
237 units::sample_t metronome_countin_frames_remaining_;
238 PlayState play_state_;
239 bool loop_enabled_;
240 bool punch_enabled_;
241 bool recording_enabled_;
242 };
243
244 Transport (
245 const dsp::TempoMap &tempo_map,
246 ConfigProvider config_provider,
247 QObject * parent = nullptr);
248
249 // ==================================================================
250 // QML Interface
251 // ==================================================================
252
253 bool loopEnabled () const { return loop_enabled (); }
254 void setLoopEnabled (bool enabled);
255 Q_SIGNAL void loopEnabledChanged (bool enabled);
256
257 bool recordEnabled () const { return recording_enabled (); }
258 void setRecordEnabled (bool enabled);
259 Q_SIGNAL void recordEnabledChanged (bool enabled);
260
261 bool punchEnabled () const { return punch_enabled (); }
262 void setPunchEnabled (bool enabled);
263 Q_SIGNAL void punchEnabledChanged (bool enabled);
264
265 PlayState getPlayState () const;
266 void setPlayState (PlayState state);
267 Q_SIGNAL void playStateChanged (PlayState state);
268
269 dsp::PlayheadQmlWrapper * playhead () const
270 {
271 return playhead_adapter_.get ();
272 }
273 dsp::AtomicPositionQmlAdapter * cuePosition () const
274 {
275 return cue_position_adapter_.get ();
276 }
277 dsp::AtomicPositionQmlAdapter * loopStartPosition () const
278 {
279 return loop_start_position_adapter_.get ();
280 }
281 dsp::AtomicPositionQmlAdapter * loopEndPosition () const
283 return loop_end_position_adapter_.get ();
284 }
285 dsp::AtomicPositionQmlAdapter * punchInPosition () const
286 {
287 return punch_in_position_adapter_.get ();
288 }
289 dsp::AtomicPositionQmlAdapter * punchOutPosition () const
290 {
291 return punch_out_position_adapter_.get ();
292 }
293
296 */
297 Q_INVOKABLE void requestPause () [[clang::blocking]];
298
300
302 Q_INVOKABLE void requestRoll () [[clang::blocking]];
303
304 // ==================================================================
305
306 // ==================================================================
307 // ITransport Interface
308 // ==================================================================
310 units::sample_t
311 get_playhead_position_in_audio_thread () const noexcept override
312 {
313 return playhead_.position_during_processing_rounded ();
314 }
315
316 std::pair<units::sample_t, units::sample_t>
317 get_loop_range_positions () const noexcept override
318 {
319 return std::make_pair (
320 loop_start_position_.get_samples (), loop_end_position_.get_samples ());
321 }
322
323 std::pair<units::sample_t, units::sample_t>
324 get_punch_range_positions () const noexcept override
325 {
326 return std::make_pair (
327 punch_in_position_.get_samples (), punch_out_position_.get_samples ());
328 }
329
330 PlayState get_play_state () const noexcept override { return play_state_; }
331
332 bool loop_enabled () const noexcept override { return loop_; }
333 bool punch_enabled () const noexcept override { return punch_mode_; }
334 bool recording_enabled () const noexcept override { return recording_; }
335 units::sample_t recording_preroll_frames_remaining () const noexcept override
336 {
337 return recording_preroll_frames_remaining_;
338 }
339 units::sample_t metronome_countin_frames_remaining () const noexcept override
340 {
341 return countin_frames_remaining_;
342 }
343
344 // ==================================================================
346 Q_INVOKABLE bool isRolling () const
347 {
348 return play_state_ == PlayState::Rolling;
349 }
350
351 Q_INVOKABLE bool isPaused () const
352 {
353 return play_state_ == PlayState::Paused;
354 }
355
360 void add_to_playhead_in_audio_thread (units::sample_t nframes);
361
362 void set_play_state_rt_safe (PlayState state);
363
367 * This is only for moves other than while playing and for looping while
368 * playing.
369 *
370 * Should not be used during exporting.
371 *
372 * @param target_ticks Position to set to.
373 * @param set_cue_point Also set the cue point at this position.
374 */
375 void move_playhead (units::precise_tick_t target_ticks, bool set_cue_point);
376
383 bool prev,
384 RangeOf<units::precise_tick_t> auto &&extra_markers)
385 {
386 /* gather all markers */
387 std::vector<units::precise_tick_t> marker_ticks;
388 static_assert (__cpp_lib_containers_ranges >= 202202L);
389 marker_ticks.append_range (extra_markers);
390 marker_ticks.emplace_back (cue_position_.get_ticks ());
391 marker_ticks.emplace_back (loop_start_position_.get_ticks ());
392 marker_ticks.emplace_back (loop_end_position_.get_ticks ());
393 marker_ticks.emplace_back ();
394 std::ranges::sort (marker_ticks);
395
396 if (prev)
397 {
398 // Iterate backwards through marker_ticks with manual index.
399 // Equivalent to (but can't use enumerate yet on AppleClang):
400 // marker_ticks | std::views::enumerate | std::views::reverse
401 for (size_t i = 0; i < marker_ticks.size (); ++i)
402 {
403 const auto index = marker_ticks.size () - 1 - i;
404 const auto &marker_tick = marker_ticks[index];
405
406 if (marker_tick >= playhead_.position_ticks ())
407 continue;
408
409 if (
410 isRolling () && index > 0
411 && (playhead_.get_tempo_map ().tick_to_seconds (
412 playhead_.position_ticks ())
413 - playhead_.get_tempo_map ().tick_to_seconds (marker_tick))
414 < REPEATED_BACKWARD_MS)
415 {
416 continue;
417 }
418
419 move_playhead (marker_tick, true);
420 break;
421 }
422 }
423 else
424 {
425 for (auto &marker : marker_ticks)
426 {
427 if (marker > playhead_.position_ticks ())
428 {
429 move_playhead (marker, true);
430 break;
431 }
432 }
434 }
435
436 bool position_is_inside_punch_range (units::sample_t pos);
437
438 auto playhead_ticks_before_pause () const [[clang::blocking]]
439 {
440 return playhead_before_pause_;
441 }
442
444 * @brief For engine use only.
445 *
446 * @param samples Samples to consume.
447 */
448 void consume_metronome_countin_samples (units::sample_t samples)
449 {
450 assert (countin_frames_remaining_ >= samples);
451 countin_frames_remaining_ -= samples;
452 }
453
459 void consume_recording_preroll_samples (units::sample_t samples)
460 {
461 assert (recording_preroll_frames_remaining_ >= samples);
462 recording_preroll_frames_remaining_ -= samples;
463 }
464
465 auto get_snapshot () const
466 {
467 return TransportSnapshot{
473 get_play_state (),
474 loop_enabled (),
475 punch_enabled (),
477 };
478 }
479
480 friend void init_from (
481 Transport &obj,
482 const Transport &other,
483 utils::ObjectCloneType clone_type);
484
485private:
486 static constexpr auto kPlayheadKey = "playheadPosition"sv;
487 static constexpr auto kCuePosKey = "cuePosition"sv;
488 static constexpr auto kLoopStartPosKey = "loopStartPosition"sv;
489 static constexpr auto kLoopEndPosKey = "loopEndPosition"sv;
490 static constexpr auto kPunchInPosKey = "punchInPosition"sv;
491 static constexpr auto kPunchOutPosKey = "punchOutPosition"sv;
492 friend void to_json (nlohmann::json &j, const Transport &transport);
493 friend void from_json (const nlohmann::json &j, Transport &transport);
494
499 bool can_user_move_playhead () const;
500
501private:
503 dsp::Playhead playhead_;
504 utils::QObjectUniquePtr<dsp::PlayheadQmlWrapper> playhead_adapter_;
505
506 std::unique_ptr<dsp::AtomicPosition::TimeConversionFunctions>
507 time_conversion_funcs_;
508
510 dsp::AtomicPosition cue_position_;
511 utils::QObjectUniquePtr<dsp::AtomicPositionQmlAdapter> cue_position_adapter_;
512
514 dsp::AtomicPosition loop_start_position_;
515 utils::QObjectUniquePtr<dsp::AtomicPositionQmlAdapter>
516 loop_start_position_adapter_;
517
519 dsp::AtomicPosition loop_end_position_;
520 utils::QObjectUniquePtr<dsp::AtomicPositionQmlAdapter>
521 loop_end_position_adapter_;
522
524 dsp::AtomicPosition punch_in_position_;
525 utils::QObjectUniquePtr<dsp::AtomicPositionQmlAdapter>
526 punch_in_position_adapter_;
527
529 dsp::AtomicPosition punch_out_position_;
530 utils::QObjectUniquePtr<dsp::AtomicPositionQmlAdapter>
531 punch_out_position_adapter_;
532
534 bool loop_{ true };
535
537 bool punch_mode_{ false };
538
541 bool recording_{ false };
542
544 units::sample_t recording_preroll_frames_remaining_;
545
547 units::sample_t countin_frames_remaining_;
548
554 units::precise_tick_t playhead_before_pause_;
555
557 PlayState play_state_{ PlayState::Paused };
558
566 QTimer * property_notification_timer_ = nullptr;
567 std::atomic<bool> needs_property_notification_{ false };
568
569 ConfigProvider config_provider_;
570};
571}
Interface for transport.
Definition itransport.h:17
auto position_ticks() const
Get current playhead position in ticks (GUI thread only).
Definition playhead.h:173
units::sample_t get_playhead_position_in_audio_thread() const noexcept override
Get the playhead position.
Definition transport.h:178
std::pair< units::sample_t, units::sample_t > get_punch_range_positions() const noexcept override
Returns the punch recording range positions in samples.
Definition transport.h:172
units::sample_t metronome_countin_frames_remaining() const noexcept override
Frames remaining for metronome countin.
Definition transport.h:195
std::pair< units::sample_t, units::sample_t > get_loop_range_positions() const noexcept override
Returns the loop range positions in samples.
Definition transport.h:167
units::sample_t recording_preroll_frames_remaining() const noexcept override
Frames remaining to preroll (playing back some time earlier before actually recording/rolling).
Definition transport.h:190
bool recording_enabled() const noexcept override
Returns whether recording is enabled.
Definition transport.h:185
void goto_prev_or_next_marker(bool prev, RangeOf< units::precise_tick_t > auto &&extra_markers)
Moves the playhead to the previous or next marker.
Definition transport.h:367
Q_INVOKABLE void requestPause()
Request pause.
units::sample_t get_playhead_position_in_audio_thread() const noexcept override
Get the playhead position.
Definition transport.h:296
units::sample_t metronome_countin_frames_remaining() const noexcept override
Frames remaining for metronome countin.
Definition transport.h:324
void consume_metronome_countin_samples(units::sample_t samples)
For engine use only.
Definition transport.h:433
void move_playhead(units::precise_tick_t target_ticks, bool set_cue_point)
Moves playhead to given pos.
RecordingMode
Recording mode for MIDI and audio.
Definition transport.h:86
@ MergeEvents
Merge events in existing region.
Definition transport.h:106
@ Takes
Events get put in new lanes each time recording starts/resumes (eg, when looping or entering/leaving ...
Definition transport.h:112
@ OverwriteEvents
Overwrite events in first recorded region.
Definition transport.h:96
@ TakesMuted
Same as RECORDING_MODE_TAKES, except previous takes (since recording started) are muted.
Definition transport.h:118
std::pair< units::sample_t, units::sample_t > get_punch_range_positions() const noexcept override
Returns the punch recording range positions in samples.
Definition transport.h:309
void add_to_playhead_in_audio_thread(units::sample_t nframes)
Moves the playhead by the time corresponding to given samples, taking into account the loop end point...
Q_INVOKABLE void requestRoll()
Request playback.
void consume_recording_preroll_samples(units::sample_t samples)
For engine use only.
Definition transport.h:444
bool recording_enabled() const noexcept override
Returns whether recording is enabled.
Definition transport.h:319
std::pair< units::sample_t, units::sample_t > get_loop_range_positions() const noexcept override
Returns the loop range positions in samples.
Definition transport.h:302
Display
Corrseponts to "transport-display" in the gsettings.
Definition transport.h:73
units::sample_t recording_preroll_frames_remaining() const noexcept override
Frames remaining to preroll (playing back some time earlier before actually recording/rolling).
Definition transport.h:320
std::function< bool()> return_to_cue_on_pause_
Whether to return to cue position on pause.
Definition transport.h:128
std::function< int()> metronome_countin_bars_
Number of bars to count-in when requesting playback with metronome enabled.
Definition transport.h:134
std::function< int()> recording_preroll_bars_
Number of bars to pre-roll before recording.
Definition transport.h:141