Zrythm v2.0.0-alpha.1
a highly automated and intuitive digital audio workstation
Loading...
Searching...
No Matches
recording_coordinator.h
1// SPDX-FileCopyrightText: © 2026 Alexandros Theodotou <alex@zrythm.org>
2// SPDX-License-Identifier: LicenseRef-ZrythmLicense
3
4#pragma once
5
6#include <atomic>
7#include <chrono>
8#include <memory>
9#include <unordered_map>
10#include <variant>
11#include <vector>
12
13#include "controllers/recording_session.h"
14#include "structure/tracks/track_fwd.h"
15
16#include <QObject>
17
18namespace zrythm::controllers
19{
20
47class RecordingCoordinator : public QObject
48{
49 Q_OBJECT
50
51public:
52 static constexpr auto kDrainInterval = std::chrono::milliseconds (100);
53
54 explicit RecordingCoordinator (QObject * parent = nullptr);
55 ~RecordingCoordinator () override;
56
57 Q_DISABLE_COPY_MOVE (RecordingCoordinator)
58
59 enum class SessionType : std::uint8_t
60 {
61 Audio,
62 Midi,
63 };
64
65 using SessionHandle =
66 std::variant<std::monostate, AudioRecordingSession *, MidiRecordingSession *>;
67
74 void arm_track (
75 structure::tracks::TrackUuid track_id,
76 units::sample_u32_t max_block_length,
77 SessionType type) [[clang::blocking]];
78
84 void disarm_track (structure::tracks::TrackUuid track_id) [[clang::blocking]];
85
91 [[nodiscard]] bool has_session (structure::tracks::TrackUuid track_id) const;
92
100
115 Q_SLOT void finalizeAllSessions ();
116
123 [[nodiscard]] SessionHandle
124 session_for_track (structure::tracks::TrackUuid track_id) const noexcept
125 [[clang::nonblocking]];
126
135 void prepare_for_processing (units::sample_u32_t block_length);
136
137Q_SIGNALS:
138
146 structure::tracks::TrackUuid track_id,
147 std::vector<RecordingAudioPacket> packets);
148
156 structure::tracks::TrackUuid track_id,
157 std::vector<RecordingMidiPacket> packets);
158
169
170private:
171 struct RtSnapshot
172 {
173 std::unordered_map<structure::tracks::TrackUuid, AudioRecordingSession *>
174 audio;
175 std::unordered_map<structure::tracks::TrackUuid, MidiRecordingSession *> midi;
176 };
177
178 using AudioDrainResult = std::vector<
179 std::pair<structure::tracks::TrackUuid, std::vector<RecordingAudioPacket>>>;
180
181 using MidiDrainResult = std::vector<
182 std::pair<structure::tracks::TrackUuid, std::vector<RecordingMidiPacket>>>;
183
184 void publish_snapshot ();
185
186 bool drain_pending_deletion (
187 AudioDrainResult &audio_ready,
188 MidiDrainResult &midi_ready);
189
190 // TODO: Replace this raw atomic pointer with a proper multi-reader
191 // realtime-safe container. The current approach works but is ugly:
192 // publish_snapshot() heap-allocates a new RtSnapshot, atomically swaps the
193 // pointer, and defers the old RtSnapshot's deletion to
194 // pending_snapshot_deletion_. Those deferred snapshots are freed in
195 // process_pending(), which runs at least kDrainInterval (100 ms) after
196 // the last publish — long after any RT thread has finished reading the
197 // old snapshot. The initial snapshot (created in the constructor) and
198 // any remaining pending snapshots are freed in the destructor.
199 std::atomic<RtSnapshot *> rt_snapshot_{ nullptr };
200
201 // Two-phase deferred deletion for retired snapshots:
202 // 1. publish_snapshot() pushes the old snapshot into
203 // pending_snapshot_deletion_.
204 // 2. process_pending() swaps pending_snapshot_deletion_ into
205 // old_snapshots_ (destroying what was previously there) and emits
206 // signals. Each retired snapshot therefore survives at least two
207 // drain intervals (~200 ms), guaranteeing every RT reader has moved
208 // on to a newer snapshot before the old one is freed.
209 std::vector<std::unique_ptr<RtSnapshot>> pending_snapshot_deletion_;
210 std::vector<std::unique_ptr<RtSnapshot>> old_snapshots_;
211
212 struct Impl;
213 std::unique_ptr<Impl> impl_;
214};
215
216}
void arm_track(structure::tracks::TrackUuid track_id, units::sample_u32_t max_block_length, SessionType type)
Arms a track for recording.
void disarm_track(structure::tracks::TrackUuid track_id)
Disarms a track.
bool has_session(structure::tracks::TrackUuid track_id) const
Returns whether a session exists for the given track.
Q_SLOT void finalizeAllSessions()
Finalizes the current recording take and resets sessions for reuse.
void prepare_for_processing(units::sample_u32_t block_length)
Prepares all active sessions for processing at the given block length.
SessionHandle session_for_track(structure::tracks::TrackUuid track_id) const noexcept
Gets the session for a track (for RT callback access).
void recordingSessionEnded()
Emitted when a recording take has been finalized.
void process_pending()
Drains all active sessions and processes pending deletions.
void audioDataReady(structure::tracks::TrackUuid track_id, std::vector< RecordingAudioPacket > packets)
Emitted when recorded audio data has been drained from a session.
void midiDataReady(structure::tracks::TrackUuid track_id, std::vector< RecordingMidiPacket > packets)
Emitted when recorded MIDI data has been drained from a session.