OSSIA
Open Scenario System for Interactive Application
Loading...
Searching...
No Matches
jack_protocol.hpp
1#pragma once
2#include <ossia/detail/config.hpp>
3
4#if defined(OSSIA_ENABLE_JACK)
5#if __has_include(<jack/jack.h>) && !defined(__EMSCRIPTEN__)
6
7#include <ossia/audio/audio_engine.hpp>
9#include <ossia/detail/thread.hpp>
10
11#include <weak_libjack.h>
12#if defined(_WIN32)
13#include <TlHelp32.h>
14#endif
15
16#include <algorithm>
17#include <string_view>
18
19#define USE_WEAK_JACK 1
20#define NO_JACK_METADATA 1
21#define OSSIA_AUDIO_JACK 1
22
23namespace ossia
24{
25
26#if defined(_WIN32)
27inline bool has_jackd_process()
28{
29 auto plist = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
30 if(plist == INVALID_HANDLE_VALUE)
31 return false;
32
33 PROCESSENTRY32 entry;
34 entry.dwSize = sizeof(PROCESSENTRY32);
35
36 if(!Process32First(plist, &entry))
37 {
38 CloseHandle(plist);
39 return false;
40 }
41
42 do
43 {
44 using namespace std::literals;
45
46 const auto* name = entry.szExeFile;
47#if !defined(UNICODE)
48 if(name == std::string("jackd.exe"))
49 return true;
50#else
51 if(name == std::wstring(L"jackd.exe"))
52 return true;
53#endif
54 } while(Process32Next(plist, &entry));
55
56 CloseHandle(plist);
57 return false;
58}
59#endif
60
61struct jack_client
62{
63 jack_client(std::string name) noexcept
64 {
65 client = jack_client_open(name.c_str(), JackNoStartServer, nullptr);
66 }
67
68 ~jack_client()
69 {
70 if(client)
71 jack_client_close(client);
72 }
73 operator jack_client_t*() const noexcept { return client; }
74
75 jack_client_t* client{};
76};
77
78using transport_timebase_function = smallfun::function<void(int, jack_position_t&), 16>;
79using transport_sync_function
80 = smallfun::function<int(jack_transport_state_t, jack_position_t*), 16>;
81struct jack_settings
82{
83 std::vector<std::string> inputs;
84 std::vector<std::string> outputs;
85 bool autoconnect{};
86 transport_mode transport{};
87 transport_sync_function sync_function;
88 transport_timebase_function timebase_function;
89};
90
91class jack_engine final : public audio_engine
92{
93public:
94 jack_engine(
95 std::shared_ptr<jack_client> clt, int inputs, int outputs,
96 std::optional<jack_settings> settings = {})
97 : m_client{clt}
98 {
99 if(!m_client || !(*m_client))
100 {
101 std::cerr << "JACK server not running?" << std::endl;
102 throw std::runtime_error("Audio error: no JACK server");
103 }
104
105 jack_client_t* client = *m_client;
106 jack_set_process_callback(client, process, this);
107 jack_set_sample_rate_callback(
108 client, [](jack_nframes_t nframes, void* arg) -> int { return 0; }, this);
109 jack_on_shutdown(client, JackShutdownCallback{}, this);
110 jack_set_latency_callback(client, latency_callback, this);
111 for(int i = 0; i < inputs; i++)
112 {
113 std::string name;
114 if(settings)
115 name = settings->inputs[i];
116 else
117 name = "in_" + std::to_string(i + 1);
118
119 auto in = jack_port_register(
120 client, name.c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0);
121 if(!in)
122 {
123 jack_deactivate(client);
124 throw std::runtime_error("Audio error: cannot register JACK input");
125 }
126 input_ports.push_back(in);
127 }
128 for(int i = 0; i < outputs; i++)
129 {
130 std::string name;
131 if(settings)
132 name = settings->outputs[i];
133 else
134 name = "out_" + std::to_string(i + 1);
135
136 auto out = jack_port_register(
137 client, name.c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
138 if(!out)
139 {
140 jack_deactivate(client);
141 throw std::runtime_error("Audio error: cannot register JACK output");
142 }
143 output_ports.push_back(out);
144 }
145
146 int err = jack_activate(client);
147 this->effective_sample_rate = jack_get_sample_rate(client);
148 this->effective_buffer_size = jack_get_buffer_size(client);
149 this->effective_inputs = inputs;
150 this->effective_outputs = outputs;
151 if(err != 0)
152 {
153 jack_deactivate(client);
154 std::cerr << "JACK error: " << err << std::endl;
155 throw std::runtime_error("Audio error: JACK cannot activate");
156 }
157
158 if(settings ? settings->autoconnect : true)
159 {
160 {
161 auto ports = jack_get_ports(
162 client, nullptr, JACK_DEFAULT_AUDIO_TYPE,
163 JackPortIsPhysical | JackPortIsOutput);
164 if(ports)
165 {
166 for(std::size_t i = 0; i < input_ports.size(); i++)
167 {
168 if(!ports[i])
169 break;
170
171 jack_connect(client, ports[i], jack_port_name(input_ports[i]));
172 }
173
174 jack_free(ports);
175 }
176 }
177 {
178 auto ports = jack_get_ports(
179 client, nullptr, JACK_DEFAULT_AUDIO_TYPE,
180 JackPortIsPhysical | JackPortIsInput);
181 if(ports)
182 {
183 for(std::size_t i = 0; i < output_ports.size(); i++)
184 {
185 if(!ports[i])
186 break;
187
188 jack_connect(client, jack_port_name(output_ports[i]), ports[i]);
189 }
190
191 jack_free(ports);
192 }
193 }
194 }
195
196 // Once connected to the hardware, the ports have its latency. JACK
197 // recomputes it later, on its own thread, whenever the graph changes:
198 // latency_callback keeps the values current.
199 update_latencies();
200 ossia::logger().info(
201 "JACK: capture latency {} frames, playback latency {} frames",
202 this->effective_input_latency.load(), this->effective_output_latency.load());
203
204 if(settings && settings->transport != transport_mode::none)
205 {
206 transport = settings->transport;
207 if(settings->timebase_function.allocated())
208 {
209 if(transport == transport_mode::master)
210 {
211 this->timebase_function = std::move(settings->timebase_function);
212 jack_set_timebase_callback(
213 client, 0,
214 [](jack_transport_state_t state, jack_nframes_t nframes,
215 jack_position_t* pos, int new_pos, void* s) {
216 auto& self = (*(jack_engine*)s);
217 self.timebase_function(nframes, *pos);
218 },
219 this);
220 }
221 }
222
223 if(settings->sync_function.allocated())
224 {
225 this->sync_function = std::move(settings->sync_function);
226 jack_set_sync_callback(
227 client,
228 [](jack_transport_state_t st, jack_position_t* pos, void* s) -> int {
229 auto& self = (*(jack_engine*)s);
230 return self.sync_function(st, pos);
231 },
232 this);
233 }
234 }
235
236 activated = true;
237 }
238
239 ~jack_engine() override { stop(); }
240
241 bool running() const override
242 {
243 if(!m_client)
244 return false;
245 return activated;
246 }
247
248 void stop() override
249 {
250 audio_engine::stop();
251
252 if(m_client)
253 {
254 jack_client_t* client = *m_client;
255 activated = false;
256 jack_deactivate(client);
257 for(auto port : this->input_ports)
258 jack_port_unregister(client, port);
259 for(auto port : this->output_ports)
260 jack_port_unregister(client, port);
261 m_client.reset();
262 }
263 }
264
265private:
266 void update_latencies() noexcept
267 {
268 if(!input_ports.empty())
269 {
270 jack_latency_range_t range{};
271 jack_port_get_latency_range(input_ports[0], JackCaptureLatency, &range);
272 this->effective_input_latency.store(int(range.max), std::memory_order_relaxed);
273 }
274 if(!output_ports.empty())
275 {
276 jack_latency_range_t range{};
277 jack_port_get_latency_range(output_ports[0], JackPlaybackLatency, &range);
278 this->effective_output_latency.store(int(range.max), std::memory_order_relaxed);
279 }
280 }
281
282 // Called by JACK when the latencies of the graph change. A client with a
283 // latency callback has to propagate the latencies through itself: every
284 // input feeds every output without adding any, which is what JACK assumes
285 // for a client without one.
286 static void latency_callback(jack_latency_callback_mode_t mode, void* arg)
287 {
288 auto& self = *static_cast<jack_engine*>(arg);
289 const auto& from
290 = mode == JackCaptureLatency ? self.input_ports : self.output_ports;
291 const auto& to = mode == JackCaptureLatency ? self.output_ports : self.input_ports;
292
293 if(!from.empty())
294 {
295 jack_latency_range_t range{};
296 jack_port_get_latency_range(from[0], mode, &range);
297 for(std::size_t i = 1; i < from.size(); i++)
298 {
299 jack_latency_range_t r{};
300 jack_port_get_latency_range(from[i], mode, &r);
301 range.min = std::min(range.min, r.min);
302 range.max = std::max(range.max, r.max);
303 }
304 for(auto port : to)
305 jack_port_set_latency_range(port, mode, &range);
306 }
307
308 self.update_latencies();
309 }
310
311 static int
312 clear_buffers(jack_engine& self, jack_nframes_t nframes, std::size_t outputs)
313 {
314 for(std::size_t i = 0; i < outputs; i++)
315 {
316 auto chan = (jack_default_audio_sample_t*)jack_port_get_buffer(
317 self.output_ports[i], nframes);
318 for(std::size_t j = 0; j < nframes; j++)
319 chan[j] = 0.f;
320 }
321
322 return 0;
323 }
324
325 static int process(jack_nframes_t nframes, void* arg)
326 {
327 [[maybe_unused]]
328 static const thread_local auto _
329 = [] {
330 ossia::set_thread_name("ossia audio 0");
331 ossia::set_thread_pinned(thread_type::Audio, 0);
332 return 0;
333 }();
334
335 auto& self = *static_cast<jack_engine*>(arg);
336 self.tick_start();
337
338 const auto inputs = self.input_ports.size();
339 const auto outputs = self.output_ports.size();
340 if(self.stop_processing)
341 {
342 self.tick_clear();
343 return clear_buffers(self, nframes, outputs);
344 }
345
346 auto float_input = (float**)alloca(sizeof(float*) * inputs);
347 auto float_output = (float**)alloca(sizeof(float*) * outputs);
348 for(std::size_t i = 0; i < inputs; i++)
349 {
350 float_input[i] = (jack_default_audio_sample_t*)jack_port_get_buffer(
351 self.input_ports[i], nframes);
352 }
353 for(std::size_t i = 0; i < outputs; i++)
354 {
355 float_output[i] = (jack_default_audio_sample_t*)jack_port_get_buffer(
356 self.output_ports[i], nframes);
357 }
358
359 // Transport
360 jack_position_t pos{};
361 std::optional<transport_status> st;
362 std::optional<uint64_t> transport_frames;
363 auto transport_state = jack_transport_query(self.m_client->client, &pos);
364 if(self.transport != transport_mode::none)
365 {
366 switch(transport_state)
367 {
368 case JackTransportStopped:
369 st = transport_status::stopped;
370 break;
371 case JackTransportStarting:
372 default: // case JackTransportNetStarting: because not yet supported
373 // in Debian
374 st = transport_status::starting;
375 break;
376 case JackTransportRolling:
377 case JackTransportLooping:
378 st = transport_status::playing;
379 break;
380 }
381 transport_frames = jack_nframes_t(pos.frame);
382 }
383
384 // std::cerr << pos.beats_per_minute << std::endl;
385
386 // Actual execution
387 ossia::audio_tick_state ts{
388 float_input, float_output, (int)inputs, (int)outputs,
389 nframes, pos.usecs / 1e6, transport_frames, st};
390 self.audio_tick(ts);
391
392 self.tick_end();
393 return 0;
394 }
395
396 std::shared_ptr<jack_client> m_client{};
397 std::vector<jack_port_t*> input_ports;
398 std::vector<jack_port_t*> output_ports;
399
400 bool activated{};
401 transport_mode transport{};
402 transport_sync_function sync_function;
403 transport_timebase_function timebase_function;
404};
405}
406
407#endif
408#endif
Definition git_info.h:7
spdlog::logger & logger() noexcept
Where the errors will be logged. Default is stderr.
Definition context.cpp:120