Zrythm v2.0.0-DEV
a highly automated and intuitive digital audio workstation
Loading...
Searching...
No Matches
port_backend.h
1// SPDX-FileCopyrightText: © 2024-2025 Alexandros Theodotou <alex@zrythm.org>
2// SPDX-License-Identifier: LicenseRef-ZrythmLicense
3
4#pragma once
5
6#include "zrythm-config.h"
7
8#include "dsp/midi_event.h"
9#include "dsp/port_identifier.h"
10#include "utils/concurrency.h"
11#include "utils/dsp.h"
12#include "utils/jack.h"
13#include "utils/monotonic_time_provider.h"
14
15#ifdef HAVE_JACK
16# include "weakjack/weak_libjack.h"
17#endif
18
20{
21public:
22 virtual ~PortBackend () = default;
23
29 {
30 nframes_t start_frame = 0;
31 nframes_t nframes = 0;
32 };
33
37 using MidiEventFilter = std::function<bool (const dsp::MidiEvent &)>;
38
39 using PortDesignationProvider = std::function<utils::Utf8String ()>;
40
46 virtual void sum_audio_data (float * buf, FrameRange range) { }
47
54 virtual void sum_midi_data (
55 dsp::MidiEvents &midi_events,
56 FrameRange range,
57 MidiEventFilter filter_func = [] (const auto &) { return true; })
58 {
59 }
60
64 virtual void send_data (const float * buf, FrameRange range) { }
65 virtual void send_data (const dsp::MidiEvents &midi_events, FrameRange range)
66 {
67 }
68
73 virtual void expose (
74 const dsp::PortIdentifier &id,
75 PortDesignationProvider designation_provider) = 0;
76
80 virtual void unexpose () = 0;
81
82 virtual void clear_backend_buffer (dsp::PortType type, nframes_t nframes) = 0;
83
84 virtual bool is_exposed () const = 0;
85};
86
87#ifdef HAVE_JACK
88class JackPortBackend : public PortBackend
89{
90public:
91 JackPortBackend (jack_client_t &client) : client_ (client) { }
92 ~JackPortBackend () override
93 {
94 {
95 if (is_exposed ())
96 {
97 unexpose ();
98 }
99 }
100 }
101
102 void sum_audio_data (float * buf, FrameRange range) override
103 {
104 const auto * in = reinterpret_cast<const float *> (
105 jack_port_get_buffer (port_, range.start_frame + range.nframes));
106
107 utils::float_ranges::add2 (
108 &buf[range.start_frame], &in[range.start_frame], range.nframes);
109 }
110
114 void copy_midi_event_vector_to_jack (
115 const dsp::MidiEventVector &src,
116 const nframes_t local_start_frames,
117 const nframes_t nframes,
118 void * buff) const
119 {
120 /*jack_midi_clear_buffer (buff);*/
121
122 src.foreach_event ([&] (const auto &ev) {
123 if (
124 ev.time_ < local_start_frames
125 || ev.time_ >= local_start_frames + nframes)
126 {
127 return;
128 }
129
130 std::array<jack_midi_data_t, 3> midi_data{};
131 std::copy_n (
132 midi_data.data (), ev.raw_buffer_sz_, (jack_midi_data_t *) buff);
133 jack_midi_event_write (
134 buff, ev.time_, midi_data.data (), ev.raw_buffer_sz_);
135# if 0
136 z_info (
137 "wrote MIDI event to JACK MIDI out at %d", ev.time);
138# endif
139 });
140 }
141
142 void sum_midi_data (
143 dsp::MidiEvents &midi_events,
144 FrameRange range,
145 MidiEventFilter filter_func) override
146 {
147 const auto start_frame = range.start_frame;
148 const auto nframes = range.nframes;
149 void * port_buf = jack_port_get_buffer (port_, nframes);
150 uint32_t num_events = jack_midi_get_event_count (port_buf);
151
152 jack_midi_event_t jack_ev;
153 for (unsigned i = 0; i < num_events; i++)
154 {
155 jack_midi_event_get (&jack_ev, port_buf, i);
156
157 if (jack_ev.time >= start_frame && jack_ev.time < start_frame + nframes)
158 {
159 if (
160 jack_ev.size == 3
161 && filter_func (dsp::MidiEvent{
162 jack_ev.buffer[0], jack_ev.buffer[1], jack_ev.buffer[2],
163 jack_ev.time }))
164 {
165 /*z_debug ("received event at {}", jack_ev.time);*/
167 jack_ev.time, jack_ev.buffer, (int) jack_ev.size);
168 }
169 else
170 {
171 /* different channel */
172 /*z_debug ("received event on different channel");*/
173 }
174 }
175 }
176 }
177
178 void send_data (const float * buf, FrameRange range) override
179 {
180 if (jack_port_connected (port_) <= 0)
181 {
182 return;
183 }
184
185 auto * out = reinterpret_cast<float *> (
186 jack_port_get_buffer (port_, range.start_frame + range.nframes));
187
188 utils::float_ranges::copy (
189 &out[range.start_frame], &buf[range.start_frame], range.nframes);
190 }
191 void send_data (const dsp::MidiEvents &midi_events, FrameRange range) override
192 {
193 if (jack_port_connected (port_) <= 0)
194 {
195 return;
196 }
197
198 void * buf = jack_port_get_buffer (port_, range.start_frame + range.nframes);
199 copy_midi_event_vector_to_jack (
200 midi_events.active_events_, range.start_frame, range.nframes, buf);
201 }
202
203 void expose (
204 const dsp::PortIdentifier &id,
205 PortDesignationProvider designation_provider) override
206 {
207 enum JackPortFlags flags
208 {
209 };
210 /* these are reversed */
211 if (id.is_input ())
212 flags = JackPortIsOutput;
213 else if (id.is_output ())
214 flags = JackPortIsInput;
215 else
216 {
217 z_return_if_reached ();
218 }
219
220 const char * type = get_jack_type (id.type_);
221 if (type == nullptr)
222 z_return_if_reached ();
223
224 auto label = designation_provider ();
225 z_info ("exposing port {} to JACK", label);
226 if (port_ == nullptr)
227 {
228 port_ = jack_port_register (&client_, label.c_str (), type, flags, 0);
229 z_return_if_fail (port_);
230 }
231 else
232 {
233 jack_port_rename (&client_, port_, label.c_str ());
234 }
235 }
236
237 void unexpose () override
238 {
239 if (!is_exposed ())
240 return;
241
242 int ret = jack_port_unregister (&client_, port_);
243 port_ = nullptr;
244 if (ret != 0)
245 {
246 auto jack_error = utils::jack::get_error_message ((jack_status_t) ret);
247 z_warning ("JACK port unregister error: {}", jack_error);
248 }
249 }
250
251 bool is_exposed () const override { return port_ != nullptr; }
252
253 void clear_backend_buffer (dsp::PortType type, nframes_t nframes) override
254 {
255 z_return_if_fail (port_ != nullptr);
256 void * buf = jack_port_get_buffer (port_, nframes);
257 z_return_if_fail (buf);
258 if (type == dsp::PortType::Audio)
259 {
260 auto * fbuf = static_cast<float *> (buf);
261 utils::float_ranges::fill (
262 &fbuf[0], utils::math::ALMOST_SILENCE, nframes);
263 }
264 else if (type == dsp::PortType::Event)
265 {
266 jack_midi_clear_buffer (buf);
267 }
268 }
269
270 jack_port_t * get_jack_port () const { return port_; }
271
275 static const char * get_jack_type (dsp::PortType type)
276 {
277 switch (type)
278 {
279 case dsp::PortType::Audio:
280 return JACK_DEFAULT_AUDIO_TYPE;
281 break;
282 case dsp::PortType::Event:
283 return JACK_DEFAULT_MIDI_TYPE;
284 break;
285 default:
286 return nullptr;
287 }
288 }
289
290private:
291 jack_port_t * port_{ nullptr };
292 jack_client_t &client_;
293};
294#endif
virtual void send_data(const float *buf, FrameRange range)
Sends the port data to the backend, after the port is processed.
virtual void unexpose()=0
Unexposes the port from the backend (if exposed).
virtual void sum_audio_data(float *buf, FrameRange range)
Sums the inputs coming in from the backend, before the port is processed.
virtual void sum_midi_data(dsp::MidiEvents &midi_events, FrameRange range, MidiEventFilter filter_func=[](const auto &) { return true;})
virtual void expose(const dsp::PortIdentifier &id, PortDesignationProvider designation_provider)=0
Exposes the port to the backend, if not already exposed.
std::function< bool(const dsp::MidiEvent &)> MidiEventFilter
Function used to filter MIDI events.
void add_event_from_buf(midi_time_t time, midi_byte_t *buf, int buf_size)
Parses a MidiEvent from a raw MIDI buffer.
Container for passing midi events through ports.
Definition midi_event.h:421
MidiEventVector active_events_
Events to use in this cycle.
Definition midi_event.h:433
Struct used to identify Ports in the project.
Lightweight UTF-8 string wrapper with safe conversions.
Definition string.h:39
uint32_t nframes_t
Frame count.
Definition types.h:55
constexpr float ALMOST_SILENCE
Tiny number to be used for denormaml prevention (-140dB).
Definition math.h:56
Frame range to operate on (on both the port's buffers and the backend's buffers).
Timed MIDI event.
Definition midi_event.h:28
Optimized DSP functions.