Zrythm v2.0.0-DEV
a highly automated and intuitive digital audio workstation
Loading...
Searching...
No Matches
audio_sample_processor.h
1// SPDX-FileCopyrightText: © 2025 Alexandros Theodotou <alex@zrythm.org>
2// SPDX-License-Identifier: LicenseRef-ZrythmLicense
3
4#pragma once
5
6#include <source_location>
7
8#include "dsp/processor_base.h"
9#include "utils/dsp.h"
10#include "utils/types.h"
11
12#include <boost/container/static_vector.hpp>
13
14namespace zrythm::dsp
15{
16
17class AudioSampleProcessor : public dsp::ProcessorBase
18{
19public:
20 AudioSampleProcessor (
22 : dsp::ProcessorBase (dependencies)
23 {
24
25 auto out_ref = dependencies.port_registry_.create_object<dsp::AudioPort> (
26 u8"Stereo Out", PortFlow::Output, AudioPort::BusLayout::Stereo, 2);
27 out_ref.get_object_as<dsp::AudioPort> ()->set_symbol (u8"stereo_out");
28 add_output_port (out_ref);
29 set_name (u8"Audio Sample Processor");
30 }
31
37 struct PlayableSampleSingleChannel
38 {
39 using UnmutableSampleSpan = std::span<const float>;
40 PlayableSampleSingleChannel (
41 UnmutableSampleSpan buf,
42 channels_t channel_index,
43 float volume,
44 nframes_t start_offset,
45 std::source_location source_location)
46 : buf_ (buf), channel_index_ (channel_index), volume_ (volume),
47 start_offset_ (start_offset), source_location_ (source_location)
48 {
49 }
51 UnmutableSampleSpan buf_;
52
57
60
63 float volume_ = 1.0f;
64
68
69 // For debugging purposes
70 std::source_location source_location_;
71 };
72
73 using QueueSingleChannelSampleCallback =
74 std::function<void (PlayableSampleSingleChannel)>;
75
85 {
86 if (sample.buf_.empty ()) [[unlikely]]
87 {
88 // ignore empty samples
89 return;
90 }
91
92 samples_to_play_.emplace_back (sample);
93 if (queue_sample_cb_.has_value ())
94 {
95 std::invoke (queue_sample_cb_.value (), sample);
96 }
97 }
98
99 void set_queue_sample_callback (QueueSingleChannelSampleCallback cb)
100 {
101 queue_sample_cb_ = std::move (cb);
102 }
103
104 auto get_output_audio_port_non_rt () const
105 {
106 return get_output_ports ()[0].get_object_as<dsp::AudioPort> ();
107 }
108
109 auto get_output_audio_port_rt () const { return audio_out_; }
110
112 EngineProcessTimeInfo time_nfo,
113 const dsp::ITransport &transport) noexcept override
114 {
115 const auto cycle_offset = time_nfo.local_offset_;
116 const auto nframes = time_nfo.nframes_;
117
118 auto * out_port = get_output_audio_port_rt ();
119
120 // Clear output
121 out_port->clear_buffer (cycle_offset, nframes);
122
123 // Process the samples in the queue
124 for (auto it = samples_to_play_.begin (); it != samples_to_play_.end ();)
125 {
126 auto &sp = *it;
127
128 // If sample starts after this cycle, update offset and skip processing
129 if (sp.start_offset_ >= nframes)
130 {
131 sp.start_offset_ -= nframes;
132 ++it;
133 continue;
134 }
135
136 const auto process_samples =
137 [&] (nframes_t fader_buf_offset, nframes_t len) {
138 if (sp.channel_index_ < 2) [[likely]]
139 {
140 out_port->buffers ()->addFrom (
141 sp.channel_index_, static_cast<int> (fader_buf_offset),
142 &sp.buf_[sp.offset_], static_cast<int> (len), sp.volume_);
143 }
144 sp.offset_ += len;
145 };
146
147 // If sample is already playing
148 if (sp.offset_ > 0)
149 {
150 const auto max_frames =
151 std::min ((nframes_t) (sp.buf_.size () - sp.offset_), nframes);
152 process_samples (cycle_offset, max_frames);
153 }
154 // If we can start playback in this cycle
155 else if (sp.start_offset_ >= cycle_offset)
156 {
157 const auto max_frames = std::min (
158 (nframes_t) sp.buf_.size (),
159 (cycle_offset + nframes) - sp.start_offset_);
160 process_samples (sp.start_offset_, max_frames);
161 }
162
163 // If the sample is finished playing, remove it
164 if (sp.offset_ >= (unsigned_frame_t) sp.buf_.size ())
165 {
166 it = samples_to_play_.erase (it);
167 }
168 else
169 {
170 ++it;
171 }
172 }
173 }
174
175 void custom_prepare_for_processing (
176 sample_rate_t sample_rate,
177 nframes_t max_block_length) override
178 {
179 samples_to_play_.clear ();
180 audio_out_ = get_output_ports ()[0].get_object_as<dsp::AudioPort> ();
181 }
182
183 void custom_release_resources () override
184 {
185 samples_to_play_.clear ();
186 audio_out_ = nullptr;
187 }
188
189private:
195 boost::container::static_vector<PlayableSampleSingleChannel, 128>
196 samples_to_play_;
197
203 std::optional<QueueSingleChannelSampleCallback> queue_sample_cb_;
204
205 AudioPort * audio_out_{};
206};
207} // namespace zrythm::dsp
Audio port specifics.
Definition audio_port.h:25
void custom_process_block(EngineProcessTimeInfo time_nfo, const dsp::ITransport &transport) noexcept override
Custom processor logic after processing all owned parameters.
void add_sample_to_process(PlayableSampleSingleChannel sample)
Adds a sample to the queue.
Interface for transport.
Definition itransport.h:17
A base class for processors in the DSP graph.
void set_name(const utils::Utf8String &name)
Set a custom name to be used in the DSP graph.
uint_fast64_t unsigned_frame_t
Unsigned type for frame index.
Definition types.h:81
uint32_t sample_rate_t
Sample rate.
Definition types.h:61
uint32_t nframes_t
Frame count.
Definition types.h:58
uint_fast8_t channels_t
Number of channels.
Definition types.h:67
Common struct to pass around during processing to avoid repeating the data in function arguments.
Definition types.h:136
A sample playback handle to be used by the engine.
UnmutableSampleSpan buf_
Samples to play for a single channel.
nframes_t start_offset_
Offset relative to the current processing cycle to start playing the sample.
unsigned_frame_t offset_
The current offset in the buffer.
channels_t channel_index_
Channel index to play this sample at.
float volume_
The volume to play the sample at (ratio from 0.0 to 2.0, where 1.0 is the normal volume).
Optimized DSP functions.