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 "dsp/snap_grid.h"
10#include "utils/icloneable.h"
11
12namespace zrythm::dsp
13{
14enum class PrerollCountBars
15{
16 PrerollNone,
17 PrerollOne,
18 PrerollTwo,
19 PrerollFour,
20};
21
22inline int
23preroll_count_bars_enum_to_int (PrerollCountBars bars)
24{
25 switch (bars)
26 {
27 case PrerollCountBars::PrerollNone:
28 return 0;
29 case PrerollCountBars::PrerollOne:
30 return 1;
31 case PrerollCountBars::PrerollTwo:
32 return 2;
33 case PrerollCountBars::PrerollFour:
34 return 4;
35 }
36 return -1;
37}
38
44class Transport : public QObject, public dsp::ITransport
45{
46 Q_OBJECT
47 QML_ELEMENT
48 Q_PROPERTY (
49 bool loopEnabled READ loopEnabled WRITE setLoopEnabled NOTIFY
50 loopEnabledChanged)
51 Q_PROPERTY (
52 bool recordEnabled READ recordEnabled WRITE setRecordEnabled NOTIFY
53 recordEnabledChanged)
54 Q_PROPERTY (
55 bool punchEnabled READ punchEnabled WRITE setPunchEnabled NOTIFY
56 punchEnabledChanged)
57 Q_PROPERTY (
58 PlayState playState READ getPlayState WRITE setPlayState NOTIFY
59 playStateChanged)
60 Q_PROPERTY (zrythm::dsp::PlayheadQmlWrapper * playhead READ playhead CONSTANT)
61 Q_PROPERTY (
62 zrythm::dsp::AtomicPositionQmlAdapter * cuePosition READ cuePosition CONSTANT)
63 Q_PROPERTY (
64 zrythm::dsp::AtomicPositionQmlAdapter * loopStartPosition READ
65 loopStartPosition CONSTANT)
66 Q_PROPERTY (
67 zrythm::dsp::AtomicPositionQmlAdapter * loopEndPosition READ loopEndPosition
68 CONSTANT)
69 Q_PROPERTY (
70 zrythm::dsp::AtomicPositionQmlAdapter * punchInPosition READ punchInPosition
71 CONSTANT)
72 Q_PROPERTY (
74 punchOutPosition CONSTANT)
75 QML_UNCREATABLE ("")
76
77
78
79 static constexpr auto REPEATED_BACKWARD_MS = au::milli (units::seconds) (240);
80
81public:
82 Q_ENUM (PlayState)
83
86 * gsettings.
87 */
88 enum class Display
89 {
90 BBT,
91 Time,
92 };
93 Q_ENUM (Display)
94
95
101 enum class RecordingMode
102 {
121
123
124 /**
125 * Events get put in new lanes each time recording starts/resumes (eg,
126 * when looping or entering/leaving the punch range).
127 */
128 Takes,
135 };
136 Q_ENUM (RecordingMode)
137
138public:
139 struct ConfigProvider
140 {
144 std::function<bool ()> return_to_cue_on_pause_;
150 std::function<int ()> metronome_countin_bars_;
151
157 std::function<int ()> recording_preroll_bars_;
158 };
159
161 {
162 public:
164 std::pair<units::sample_t, units::sample_t> loop_range,
165 std::pair<units::sample_t, units::sample_t> punch_range,
166 units::sample_t playhead_position,
169 PlayState play_state,
170 bool loop_enabled,
171 bool punch_enabled,
173 : loop_range_ (loop_range), punch_range_ (punch_range),
174 playhead_position_ (playhead_position),
175 recording_preroll_frames_remaining_ (recording_preroll_frames_remaining),
176 metronome_countin_frames_remaining_ (metronome_countin_frames_remaining),
177 play_state_ (play_state), loop_enabled_ (loop_enabled),
178 punch_enabled_ (punch_enabled), recording_enabled_ (recording_enabled)
180 }
181
182 std::pair<units::sample_t, units::sample_t>
183 get_loop_range_positions () const noexcept override
184 {
185 return loop_range_;
187 std::pair<units::sample_t, units::sample_t>
188 get_punch_range_positions () const noexcept override
189 {
190 return punch_range_;
192 PlayState get_play_state () const noexcept override { return play_state_; }
193 units::sample_t
194 get_playhead_position_in_audio_thread () const noexcept override
195 {
196 return playhead_position_;
197 }
198 bool loop_enabled () const noexcept override { return loop_enabled_; }
199
200 bool punch_enabled () const noexcept override { return punch_enabled_; }
201 bool recording_enabled () const noexcept override
202 {
203 return recording_enabled_;
204 }
205 units::sample_t
206 recording_preroll_frames_remaining () const noexcept override
207 {
208 return recording_preroll_frames_remaining_;
209 }
210 units::sample_t
211 metronome_countin_frames_remaining () const noexcept override
212 {
213 return metronome_countin_frames_remaining_;
214 }
215
216 void set_play_state (dsp::ITransport::PlayState play_state)
217 {
218 play_state_ = play_state;
219 }
220 void set_position (units::sample_t position)
221 {
222 playhead_position_ = position;
223 }
224 void consume_metronome_countin_samples (units::sample_t samples)
225 {
226 metronome_countin_frames_remaining_ -= samples;
227 }
228 void consume_recording_preroll_samples (units::sample_t samples)
229 {
230 recording_preroll_frames_remaining_ -= samples;
231 }
232
233 private:
234 std::pair<units::sample_t, units::sample_t> loop_range_;
235 std::pair<units::sample_t, units::sample_t> punch_range_;
236 units::sample_t playhead_position_;
237 units::sample_t recording_preroll_frames_remaining_;
238 units::sample_t metronome_countin_frames_remaining_;
239 PlayState play_state_;
240 bool loop_enabled_;
241 bool punch_enabled_;
242 bool recording_enabled_;
243 };
244
245 Transport (
246 const dsp::TempoMap &tempo_map,
247 const dsp::SnapGrid &snap_grid,
248 ConfigProvider config_provider,
249 QObject * parent = nullptr);
250
251 // ==================================================================
252 // QML Interface
253 // ==================================================================
254
255 bool loopEnabled () const { return loop_enabled (); }
256 void setLoopEnabled (bool enabled);
257 Q_SIGNAL void loopEnabledChanged (bool enabled);
258
259 bool recordEnabled () const { return recording_enabled (); }
260 void setRecordEnabled (bool enabled);
261 Q_SIGNAL void recordEnabledChanged (bool enabled);
262
263 bool punchEnabled () const { return punch_enabled (); }
264 void setPunchEnabled (bool enabled);
265 Q_SIGNAL void punchEnabledChanged (bool enabled);
266
267 PlayState getPlayState () const;
268 void setPlayState (PlayState state);
269 Q_SIGNAL void playStateChanged (PlayState state);
270
271 dsp::PlayheadQmlWrapper * playhead () const
272 {
273 return playhead_adapter_.get ();
274 }
275 dsp::AtomicPositionQmlAdapter * cuePosition () const
276 {
277 return cue_position_adapter_.get ();
278 }
279 dsp::AtomicPositionQmlAdapter * loopStartPosition () const
280 {
281 return loop_start_position_adapter_.get ();
282 }
283 dsp::AtomicPositionQmlAdapter * loopEndPosition () const
284 {
285 return loop_end_position_adapter_.get ();
286 }
287 dsp::AtomicPositionQmlAdapter * punchInPosition () const
288 {
289 return punch_in_position_adapter_.get ();
290 }
291 dsp::AtomicPositionQmlAdapter * punchOutPosition () const
293 return punch_out_position_adapter_.get ();
294 }
295
296 Q_INVOKABLE void moveBackward () [[clang::blocking]];
297 Q_INVOKABLE void moveForward () [[clang::blocking]];
298
301 */
302 Q_INVOKABLE void requestPause () [[clang::blocking]];
303
305
307 Q_INVOKABLE void requestRoll () [[clang::blocking]];
308
309 // ==================================================================
310
311 // ==================================================================
312 // ITransport Interface
313 // ==================================================================
315 units::sample_t
316 get_playhead_position_in_audio_thread () const noexcept override
317 {
318 return playhead_.position_during_processing_rounded ();
319 }
320
321 std::pair<units::sample_t, units::sample_t>
322 get_loop_range_positions () const noexcept override
323 {
324 return std::make_pair (
325 loop_start_position_.get_samples (), loop_end_position_.get_samples ());
326 }
327
328 std::pair<units::sample_t, units::sample_t>
329 get_punch_range_positions () const noexcept override
330 {
331 return std::make_pair (
332 punch_in_position_.get_samples (), punch_out_position_.get_samples ());
333 }
334
335 PlayState get_play_state () const noexcept override { return play_state_; }
336
337 bool loop_enabled () const noexcept override { return loop_; }
338 bool punch_enabled () const noexcept override { return punch_mode_; }
339 bool recording_enabled () const noexcept override { return recording_; }
340 units::sample_t recording_preroll_frames_remaining () const noexcept override
341 {
342 return recording_preroll_frames_remaining_;
343 }
344 units::sample_t metronome_countin_frames_remaining () const noexcept override
345 {
346 return countin_frames_remaining_;
347 }
348
349 // ==================================================================
351 Q_INVOKABLE bool isRolling () const
352 {
353 return play_state_ == PlayState::Rolling;
354 }
355
356 Q_INVOKABLE bool isPaused () const
357 {
358 return play_state_ == PlayState::Paused;
359 }
360
365 void add_to_playhead_in_audio_thread (units::sample_t nframes);
366
367 void set_play_state_rt_safe (PlayState state);
368
372 * This is only for moves other than while playing and for looping while
373 * playing.
374 *
375 * Should not be used during exporting.
376 *
377 * @param target_ticks Position to set to.
378 * @param set_cue_point Also set the cue point at this position.
379 */
380 void move_playhead (units::precise_tick_t target_ticks, bool set_cue_point);
381
388 bool prev,
389 RangeOf<units::precise_tick_t> auto &&extra_markers)
390 {
391 /* gather all markers */
392 std::vector<units::precise_tick_t> marker_ticks;
393 static_assert (__cpp_lib_containers_ranges >= 202202L);
394 marker_ticks.append_range (extra_markers);
395 marker_ticks.emplace_back (cue_position_.get_ticks ());
396 marker_ticks.emplace_back (loop_start_position_.get_ticks ());
397 marker_ticks.emplace_back (loop_end_position_.get_ticks ());
398 marker_ticks.emplace_back ();
399 std::ranges::sort (marker_ticks);
400
401 if (prev)
402 {
403 // Iterate backwards through marker_ticks with manual index.
404 // Equivalent to (but can't use enumerate yet on AppleClang):
405 // marker_ticks | std::views::enumerate | std::views::reverse
406 for (size_t i = 0; i < marker_ticks.size (); ++i)
407 {
408 const auto index = marker_ticks.size () - 1 - i;
409 const auto &marker_tick = marker_ticks[index];
410
411 if (marker_tick >= playhead_.position_ticks ())
412 continue;
413
414 if (
415 isRolling () && index > 0
416 && (playhead_.get_tempo_map ().tick_to_seconds (
417 playhead_.position_ticks ())
418 - playhead_.get_tempo_map ().tick_to_seconds (marker_tick))
419 < REPEATED_BACKWARD_MS)
420 {
421 continue;
422 }
423
424 move_playhead (marker_tick, true);
425 break;
426 }
427 }
428 else
429 {
430 for (auto &marker : marker_ticks)
432 if (marker > playhead_.position_ticks ())
433 {
434 move_playhead (marker, true);
435 break;
436 }
437 }
438 }
439 }
440
446 void set_loop_range (
447 bool start,
448 units::precise_tick_t start_pos,
449 units::precise_tick_t pos,
450 bool snap);
451
452 bool position_is_inside_punch_range (units::sample_t pos);
453
454 auto playhead_ticks_before_pause () const [[clang::blocking]]
455 {
456 return playhead_before_pause_;
457 }
458
460 * @brief For engine use only.
461 *
462 * @param samples Samples to consume.
463 */
464 void consume_metronome_countin_samples (units::sample_t samples)
465 {
466 assert (countin_frames_remaining_ >= samples);
467 countin_frames_remaining_ -= samples;
468 }
469
475 void consume_recording_preroll_samples (units::sample_t samples)
476 {
477 assert (recording_preroll_frames_remaining_ >= samples);
478 recording_preroll_frames_remaining_ -= samples;
479 }
480
481 auto get_snapshot () const
482 {
483 return TransportSnapshot{
489 get_play_state (),
490 loop_enabled (),
491 punch_enabled (),
493 };
494 }
495
496 friend void init_from (
497 Transport &obj,
498 const Transport &other,
499 utils::ObjectCloneType clone_type);
500
501private:
502 static constexpr auto kPlayheadKey = "playhead"sv;
503 static constexpr auto kCuePosKey = "cuePos"sv;
504 static constexpr auto kLoopStartPosKey = "loopStartPos"sv;
505 static constexpr auto kLoopEndPosKey = "loopEndPos"sv;
506 static constexpr auto kPunchInPosKey = "punchInPos"sv;
507 static constexpr auto kPunchOutPosKey = "punchOutPos"sv;
508 static constexpr auto kPositionKey = "position"sv;
509 friend void to_json (nlohmann::json &j, const Transport &transport);
510 friend void from_json (const nlohmann::json &j, Transport &transport);
511
516 bool can_user_move_playhead () const;
517
518private:
520 dsp::Playhead playhead_;
521 utils::QObjectUniquePtr<dsp::PlayheadQmlWrapper> playhead_adapter_;
522
523 std::unique_ptr<dsp::AtomicPosition::TimeConversionFunctions>
524 time_conversion_funcs_;
525
527 dsp::AtomicPosition cue_position_;
528 utils::QObjectUniquePtr<dsp::AtomicPositionQmlAdapter> cue_position_adapter_;
529
531 dsp::AtomicPosition loop_start_position_;
532 utils::QObjectUniquePtr<dsp::AtomicPositionQmlAdapter>
533 loop_start_position_adapter_;
534
536 dsp::AtomicPosition loop_end_position_;
537 utils::QObjectUniquePtr<dsp::AtomicPositionQmlAdapter>
538 loop_end_position_adapter_;
539
541 dsp::AtomicPosition punch_in_position_;
542 utils::QObjectUniquePtr<dsp::AtomicPositionQmlAdapter>
543 punch_in_position_adapter_;
544
546 dsp::AtomicPosition punch_out_position_;
547 utils::QObjectUniquePtr<dsp::AtomicPositionQmlAdapter>
548 punch_out_position_adapter_;
549
551 bool loop_{ true };
552
554 bool punch_mode_{ false };
555
558 bool recording_{ false };
559
561 units::sample_t recording_preroll_frames_remaining_;
562
564 units::sample_t countin_frames_remaining_;
565
571 units::precise_tick_t playhead_before_pause_;
572
574 PlayState play_state_{ PlayState::Paused };
575
583 QTimer * property_notification_timer_ = nullptr;
584 std::atomic<bool> needs_property_notification_{ false };
585
586 ConfigProvider config_provider_;
587
588 const dsp::SnapGrid &snap_grid_;
589};
590}
Interface for transport.
Definition itransport.h:17
units::sample_t get_playhead_position_in_audio_thread() const noexcept override
Get the playhead position.
Definition transport.h:179
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:173
units::sample_t metronome_countin_frames_remaining() const noexcept override
Frames remaining for metronome countin.
Definition transport.h:196
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:168
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:191
bool recording_enabled() const noexcept override
Returns whether recording is enabled.
Definition transport.h:186
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:372
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:301
units::sample_t metronome_countin_frames_remaining() const noexcept override
Frames remaining for metronome countin.
Definition transport.h:329
void consume_metronome_countin_samples(units::sample_t samples)
For engine use only.
Definition transport.h:449
void set_loop_range(bool start, units::precise_tick_t start_pos, units::precise_tick_t pos, bool snap)
Set the loop range.
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:87
@ MergeEvents
Merge events in existing region.
Definition transport.h:107
@ Takes
Events get put in new lanes each time recording starts/resumes (eg, when looping or entering/leaving ...
Definition transport.h:113
@ OverwriteEvents
Overwrite events in first recorded region.
Definition transport.h:97
@ TakesMuted
Same as RECORDING_MODE_TAKES, except previous takes (since recording started) are muted.
Definition transport.h:119
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:314
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:460
bool recording_enabled() const noexcept override
Returns whether recording is enabled.
Definition transport.h:324
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:307
Display
Corrseponts to "transport-display" in the gsettings.
Definition transport.h:74
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:325
std::function< bool()> return_to_cue_on_pause_
Whether to return to cue position on pause.
Definition transport.h:129
std::function< int()> metronome_countin_bars_
Number of bars to count-in when requesting playback with metronome enabled.
Definition transport.h:135
std::function< int()> recording_preroll_bars_
Number of bars to pre-roll before recording.
Definition transport.h:142