Zrythm v2.0.0-alpha.1
a highly automated and intuitive digital audio workstation
Loading...
Searching...
No Matches
timeline_data_provider.h
1// SPDX-FileCopyrightText: © 2025 Alexandros Theodotou <alex@zrythm.org>
2// SPDX-License-Identifier: LicenseRef-ZrythmLicense
3
4#pragma once
5
6#include "dsp/graph_node.h"
7#include "dsp/itransport.h"
8#include "dsp/midi_event_buffer.h"
9#include "dsp/timeline_data_cache.h"
10#include "structure/arrangement/clip_renderer.h"
11#include "utils/expandable_tick_range.h"
12#include "utils/qt.h"
13
14#include <farbot/RealtimeObject.hpp>
15
16namespace zrythm::structure::arrangement
17{
18
26class TimelineDataProvider : public QObject
27{
28 Q_OBJECT
29
30public:
31 using IntervalType = std::pair<units::sample_t, units::sample_t>;
32
33 explicit TimelineDataProvider (QObject * parent = nullptr) : QObject (parent)
34 {
35 }
36
37 ~TimelineDataProvider () override;
38
51 template <ClipObject ClipType>
53 this auto &self,
54 const dsp::TempoMap &tempo_map,
56 utils::ExpandableTickRange affected_range)
57 {
58 // Convert tick range to sample range
59 const auto sample_interval = [&affected_range, &tempo_map] ()
60 -> std::pair<units::sample_t, units::sample_t> {
61 if (!affected_range.is_full_content ())
62 {
63 const auto tick_range = affected_range.range ().value ();
64 return std::make_pair (
65 tempo_map.tick_to_samples_rounded (
66 dsp::TimelineTick{ units::ticks (tick_range.first) }),
67 tempo_map.tick_to_samples_rounded (
68 dsp::TimelineTick{ units::ticks (tick_range.second) }));
69 }
70 return std::make_pair (
71 units::samples (static_cast<int64_t> (0)),
72 units::samples (std::numeric_limits<int64_t>::max ()));
73 }();
74
75 // Remove existing caches at given interval (or all caches if no interval
76 // given)
77
78 if (affected_range.is_full_content ())
79 {
80 self.get_base_cache ()->clear ();
81 }
82 else
83 {
84 self.get_base_cache ()->remove_sequences_matching_interval (
85 sample_interval);
86 }
87
88 const auto clips_inside_interval_filter_func =
89 [&affected_range, sample_interval] (const auto &clip) {
90 if (affected_range.is_full_content ())
91 return true;
92
93 return clip->is_hit_by_range (sample_interval);
94 };
95
96 const auto cache_clip = [&] (const auto * r) {
97 // Skip muted clips
98 if (r->mute ()->muted ())
99 return;
100
101 if constexpr (
102 std::is_same_v<ClipType, arrangement::MidiClip>
103 || std::is_same_v<ClipType, arrangement::ChordClip>)
104 {
105 self.cache_midi_clip (*r, tempo_map);
106 }
107 else if constexpr (std::is_same_v<ClipType, arrangement::AudioClip>)
108 {
109 self.cache_audio_clip (*r);
110 }
111 else if constexpr (std::is_same_v<ClipType, arrangement::AutomationClip>)
112 {
113 self.cache_automation_clip (*r, tempo_map);
114 }
115 };
116
117 // Go through each clip and add a cache
118 std::ranges::for_each (
119 std::views::filter (clips, clips_inside_interval_filter_func), cache_clip);
120
121 // Finalize
122 self.get_base_cache ()->finalize_changes ();
123 }
124
125 virtual void clear_all_caches () = 0;
126 virtual void
127 remove_sequences_matching_interval_from_all_caches (IntervalType interval) = 0;
128
129 virtual const dsp::TimelineDataCache * get_base_cache () const = 0;
130
131protected:
132 virtual dsp::TimelineDataCache * get_base_cache () = 0;
134 dsp::ITransport::PlayState last_seen_transport_state_{
135 dsp::ITransport::PlayState::Paused
136 };
137
140};
141
148class MidiTimelineDataProvider : public TimelineDataProvider
149{
150 friend class TimelineDataProvider;
151
152public:
153 explicit MidiTimelineDataProvider (QObject * parent = nullptr);
154
155 const dsp::TimelineDataCache * get_base_cache () const override
156 {
157 return midi_cache_.get ();
158 }
159
164 const dsp::graph::ProcessBlockInfo &time_nfo,
165 dsp::ITransport::PlayState transport_state,
166 dsp::MidiEventBuffer &output_buffer) noexcept [[clang::nonblocking]];
167
168 void clear_all_caches () override;
169 void remove_sequences_matching_interval_from_all_caches (
170 IntervalType interval) override;
171
172 std::span<const dsp::SampleBasedMidiEvent> midi_events () const;
173
185 const dsp::TempoMap &tempo_map,
187 utils::ExpandableTickRange affected_range)
188 {
190 tempo_map, midi_clips, affected_range);
191 set_midi_events (midi_cache_->midi_events ());
192 }
193
205 const dsp::TempoMap &tempo_map,
207 utils::ExpandableTickRange affected_range)
208 {
210 tempo_map, chord_clips, affected_range);
211 set_midi_events (midi_cache_->midi_events ());
212 }
213
214protected:
215 dsp::TimelineDataCache * get_base_cache () override
216 {
217 return midi_cache_.get ();
218 }
219
220private:
224 template <typename MidiClipType>
225 void
226 cache_midi_clip (const MidiClipType &clip, const dsp::TempoMap &tempo_map)
227 {
228 // MIDI clip processing
229 juce::MidiMessageSequence clip_seq;
230
231 // Serialize clip (timings in timeline ticks)
233 clip_seq.addTimeToMessages (clip.position ()->ticks ());
234
235 // Convert JUCE sequence to native events with sample timestamps
236 std::vector<dsp::SampleBasedMidiEvent> native_events;
237 native_events.reserve (clip_seq.getNumEvents ());
238 for (const auto &event : clip_seq)
239 {
240 const auto sample_time = tempo_map.tick_to_samples_rounded (
241 dsp::TimelineTick{ units::ticks (event->message.getTimeStamp ()) });
242 const auto * raw = event->message.getRawData ();
243 const auto raw_size =
244 static_cast<size_t> (event->message.getRawDataSize ());
245 native_events.push_back (
247 std::span<const midi_byte_t>{ raw, raw_size }, sample_time));
248 }
249
250 // Add to cache
251 midi_cache_->add_midi_sequence (
252 std::make_pair (
253 tempo_map.tick_to_samples_rounded (clip.position ()->asTick ()),
254 clip.get_end_position_samples (true)),
255 native_events);
256 }
257
263 void set_midi_events (std::span<const dsp::SampleBasedMidiEvent> events);
264
265 utils::QObjectUniquePtr<dsp::MidiTimelineDataCache> midi_cache_;
266
267 farbot::RealtimeObject<
268 std::vector<dsp::SampleBasedMidiEvent>,
269 farbot::RealtimeObjectOptions::nonRealtimeMutatable>
270 active_midi_playback_sequence_;
271};
272
279class AudioTimelineDataProvider : public TimelineDataProvider
280{
281 friend class TimelineDataProvider;
282
283public:
284 explicit AudioTimelineDataProvider (QObject * parent = nullptr);
285
286 const dsp::TimelineDataCache * get_base_cache () const override
287 {
288 return audio_cache_.get ();
289 }
290
295 const dsp::graph::ProcessBlockInfo &time_nfo,
296 dsp::ITransport::PlayState transport_state,
297 std::span<float> output_left,
298 std::span<float> output_right) noexcept [[clang::nonblocking]];
299
300 void clear_all_caches () override;
301 void remove_sequences_matching_interval_from_all_caches (
302 IntervalType interval) override;
303
304 std::span<const dsp::AudioTimelineDataCache::AudioClipEntry>
305 audio_clips () const;
306
318 const dsp::TempoMap &tempo_map,
320 utils::ExpandableTickRange affected_range)
321 {
323 tempo_map, audio_clips, affected_range);
324 set_audio_clips (audio_cache_->audio_clips ());
325 }
326
327protected:
328 dsp::TimelineDataCache * get_base_cache () override
329 {
330 return audio_cache_.get ();
331 }
332
333private:
337 void cache_audio_clip (const arrangement::AudioClip &clip);
338
344 void set_audio_clips (
345 std::span<const dsp::AudioTimelineDataCache::AudioClipEntry> clips);
346
348
349 farbot::RealtimeObject<
350 std::vector<dsp::AudioTimelineDataCache::AudioClipEntry>,
351 farbot::RealtimeObjectOptions::nonRealtimeMutatable>
352 active_audio_clips_;
353};
354
361class AutomationTimelineDataProvider : public TimelineDataProvider
362{
363 friend class TimelineDataProvider;
364
365public:
366 explicit AutomationTimelineDataProvider (QObject * parent = nullptr);
367
368 const dsp::TimelineDataCache * get_base_cache () const override
369 {
370 return automation_cache_.get ();
371 }
372
377 const dsp::graph::ProcessBlockInfo &time_nfo,
378 dsp::ITransport::PlayState transport_state,
379 std::span<float> output_values) noexcept [[clang::nonblocking]];
380
387 std::optional<float>
388 get_automation_value_rt (units::sample_t sample_position) noexcept
389 [[clang::nonblocking]];
390
391 void clear_all_caches () override;
392 void remove_sequences_matching_interval_from_all_caches (
393 IntervalType interval) override;
394
395 std::span<const dsp::AutomationTimelineDataCache::AutomationCacheEntry>
396 automation_sequences () const;
397
409 const dsp::TempoMap &tempo_map,
411 utils::ExpandableTickRange affected_range)
412 {
414 tempo_map, automation_clips, affected_range);
415 set_automation_sequences (automation_cache_->automation_sequences ());
416 }
417
418protected:
419 dsp::TimelineDataCache * get_base_cache () override
420 {
421 return automation_cache_.get ();
422 }
423
424private:
430 static std::optional<float> evaluate_at_sample (
431 const std::vector<dsp::AutomationTimelineDataCache::AutomationCacheEntry>
432 &sequences,
433 units::sample_t sample_position) noexcept [[clang::nonblocking]];
434
438 void cache_automation_clip (
439 const arrangement::AutomationClip &clip,
440 const dsp::TempoMap &tempo_map);
441
447 void set_automation_sequences (
448 std::span<const dsp::AutomationTimelineDataCache::AutomationCacheEntry>
449 sequences);
450
452
453 farbot::RealtimeObject<
454 std::vector<dsp::AutomationTimelineDataCache::AutomationCacheEntry>,
455 farbot::RealtimeObjectOptions::nonRealtimeMutatable>
456 active_automation_sequences_;
457};
458
459} // namespace zrythm::structure::arrangement
Packed contiguous buffer for RT MIDI events.
Base class for timeline data caches.
A clip for playing back audio samples.
Definition audio_clip.h:24
void process_audio_events(const dsp::graph::ProcessBlockInfo &time_nfo, dsp::ITransport::PlayState transport_state, std::span< float > output_left, std::span< float > output_right) noexcept
Process audio events for the given time range.
void generate_audio_events(const dsp::TempoMap &tempo_map, utils::RangeOf< const arrangement::AudioClip * > auto audio_clips, utils::ExpandableTickRange affected_range)
Generate the audio event sequence to be used during realtime processing.
Represents an automation clip, which contains a collection of automation points.
std::optional< float > get_automation_value_rt(units::sample_t sample_position) noexcept
Get the automation value for a specific sample position.
void process_automation_events(const dsp::graph::ProcessBlockInfo &time_nfo, dsp::ITransport::PlayState transport_state, std::span< float > output_values) noexcept
Process automation events for the given time range.
void generate_automation_events(const dsp::TempoMap &tempo_map, utils::RangeOf< const arrangement::AutomationClip * > auto automation_clips, utils::ExpandableTickRange affected_range)
Generate the automation event sequence to be used during realtime processing.
static void serialize_to_sequence(const MidiClip &clip, juce::MidiMessageSequence &events, std::optional< TimelineRange > timeline_range_ticks=std::nullopt)
Serializes a MIDI clip to a MIDI message sequence.
void process_midi_events(const dsp::graph::ProcessBlockInfo &time_nfo, dsp::ITransport::PlayState transport_state, dsp::MidiEventBuffer &output_buffer) noexcept
Process MIDI events for the given time range.
void generate_midi_events(const dsp::TempoMap &tempo_map, utils::RangeOf< const arrangement::MidiClip * > auto midi_clips, utils::ExpandableTickRange affected_range)
Generate the MIDI event sequence to be used during realtime processing.
void generate_midi_events(const dsp::TempoMap &tempo_map, utils::RangeOf< const arrangement::ChordClip * > auto chord_clips, utils::ExpandableTickRange affected_range)
Generate the MIDI event sequence to be used during realtime processing.
void generate_events(this auto &self, const dsp::TempoMap &tempo_map, utils::RangeOf< const ClipType * > auto clips, utils::ExpandableTickRange affected_range)
Generate the event sequence to be used during realtime processing.
dsp::ITransport::PlayState last_seen_transport_state_
Last transport state we've seen.
units::sample_t next_expected_transport_position_
Next expected transport position (for detecting jumps).
bool is_full_content() const
Returns whether the range is the full content (ie, there is no range).
auto range() const -> std::optional< std::pair< double, double > >
Returns the range, or nullopt if the full content is covered.
A unique pointer for QObject objects that also works with QObject-based ownership.
Definition qt.h:36
void clip(std::span< float > buf, float minf, float maxf)
Clamp the buffer to min/max.
MidiEvent< TimeType > make_raw(std::span< const midi_byte_t > raw, TimeType time)
Creates a raw MIDI event from the given bytes.
Definition midi_event.h:298
Common struct to pass around during processing to avoid repeating the data in function arguments.
Definition graph_node.h:51