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>
8#include <ossia/detail/thread.hpp>
9
10#include <weak_libjack.h>
11#if defined(_WIN32)
12#include <TlHelp32.h>
13#endif
14
15#include <string_view>
16
17#define USE_WEAK_JACK 1
18#define NO_JACK_METADATA 1
19#define OSSIA_AUDIO_JACK 1
20
21namespace ossia
22{
23
24#if defined(_WIN32)
25inline bool has_jackd_process()
26{
27 auto plist = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
28 if(plist == INVALID_HANDLE_VALUE)
29 return false;
30
31 PROCESSENTRY32 entry;
32 entry.dwSize = sizeof(PROCESSENTRY32);
33
34 if(!Process32First(plist, &entry))
35 {
36 CloseHandle(plist);
37 return false;
38 }
39
40 do
41 {
42 using namespace std::literals;
43
44 const auto* name = entry.szExeFile;
45#if !defined(UNICODE)
46 if(name == std::string("jackd.exe"))
47 return true;
48#else
49 if(name == std::wstring(L"jackd.exe"))
50 return true;
51#endif
52 } while(Process32Next(plist, &entry));
53
54 CloseHandle(plist);
55 return false;
56}
57#endif
58
59struct jack_client
60{
61 jack_client(std::string name) noexcept
62 {
63 client = jack_client_open(name.c_str(), JackNoStartServer, nullptr);
64 }
65
66 ~jack_client()
67 {
68 if(client)
69 jack_client_close(client);
70 }
71 operator jack_client_t*() const noexcept { return client; }
72
73 jack_client_t* client{};
74};
75
76using transport_timebase_function = smallfun::function<void(int, jack_position_t&), 16>;
77using transport_sync_function
78 = smallfun::function<int(jack_transport_state_t, jack_position_t*), 16>;
79struct jack_settings
80{
81 std::vector<std::string> inputs;
82 std::vector<std::string> outputs;
83 bool autoconnect{};
84 transport_mode transport{};
85 transport_sync_function sync_function;
86 transport_timebase_function timebase_function;
87};
88
89class jack_engine final : public audio_engine
90{
91public:
92 jack_engine(
93 std::shared_ptr<jack_client> clt, int inputs, int outputs,
94 std::optional<jack_settings> settings = {})
95 : m_client{clt}
96 {
97 if(!m_client || !(*m_client))
98 {
99 std::cerr << "JACK server not running?" << std::endl;
100 throw std::runtime_error("Audio error: no JACK server");
101 }
102
103 jack_client_t* client = *m_client;
104 jack_set_process_callback(client, process, this);
105 jack_set_sample_rate_callback(
106 client, [](jack_nframes_t nframes, void* arg) -> int { return 0; }, this);
107 jack_on_shutdown(client, JackShutdownCallback{}, this);
108 for(int i = 0; i < inputs; i++)
109 {
110 std::string name;
111 if(settings)
112 name = settings->inputs[i];
113 else
114 name = "in_" + std::to_string(i + 1);
115
116 auto in = jack_port_register(
117 client, name.c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0);
118 if(!in)
119 {
120 jack_deactivate(client);
121 throw std::runtime_error("Audio error: cannot register JACK input");
122 }
123 input_ports.push_back(in);
124 }
125 for(int i = 0; i < outputs; i++)
126 {
127 std::string name;
128 if(settings)
129 name = settings->outputs[i];
130 else
131 name = "out_" + std::to_string(i + 1);
132
133 auto out = jack_port_register(
134 client, name.c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
135 if(!out)
136 {
137 jack_deactivate(client);
138 throw std::runtime_error("Audio error: cannot register JACK output");
139 }
140 output_ports.push_back(out);
141 }
142
143 int err = jack_activate(client);
144 this->effective_sample_rate = jack_get_sample_rate(client);
145 this->effective_buffer_size = jack_get_buffer_size(client);
146 this->effective_inputs = inputs;
147 this->effective_outputs = outputs;
148 if(err != 0)
149 {
150 jack_deactivate(client);
151 std::cerr << "JACK error: " << err << std::endl;
152 throw std::runtime_error("Audio error: JACK cannot activate");
153 }
154
155 if(settings ? settings->autoconnect : true)
156 {
157 {
158 auto ports = jack_get_ports(
159 client, nullptr, JACK_DEFAULT_AUDIO_TYPE,
160 JackPortIsPhysical | JackPortIsOutput);
161 if(ports)
162 {
163 for(std::size_t i = 0; i < input_ports.size(); i++)
164 {
165 if(!ports[i])
166 break;
167
168 jack_connect(client, ports[i], jack_port_name(input_ports[i]));
169 }
170
171 jack_free(ports);
172 }
173 }
174 {
175 auto ports = jack_get_ports(
176 client, nullptr, JACK_DEFAULT_AUDIO_TYPE,
177 JackPortIsPhysical | JackPortIsInput);
178 if(ports)
179 {
180 for(std::size_t i = 0; i < output_ports.size(); i++)
181 {
182 if(!ports[i])
183 break;
184
185 jack_connect(client, jack_port_name(output_ports[i]), ports[i]);
186 }
187
188 jack_free(ports);
189 }
190 }
191 }
192
193 if(settings && settings->transport != transport_mode::none)
194 {
195 transport = settings->transport;
196 if(settings->timebase_function.allocated())
197 {
198 if(transport == transport_mode::master)
199 {
200 this->timebase_function = std::move(settings->timebase_function);
201 jack_set_timebase_callback(
202 client, 0,
203 [](jack_transport_state_t state, jack_nframes_t nframes,
204 jack_position_t* pos, int new_pos, void* s) {
205 auto& self = (*(jack_engine*)s);
206 self.timebase_function(nframes, *pos);
207 },
208 this);
209 }
210 }
211
212 if(settings->sync_function.allocated())
213 {
214 this->sync_function = std::move(settings->sync_function);
215 jack_set_sync_callback(
216 client,
217 [](jack_transport_state_t st, jack_position_t* pos, void* s) -> int {
218 auto& self = (*(jack_engine*)s);
219 return self.sync_function(st, pos);
220 },
221 this);
222 }
223 }
224
225 activated = true;
226 }
227
228 ~jack_engine() override { stop(); }
229
230 bool running() const override
231 {
232 if(!m_client)
233 return false;
234 return activated;
235 }
236
237 void stop() override
238 {
239 audio_engine::stop();
240
241 if(m_client)
242 {
243 jack_client_t* client = *m_client;
244 activated = false;
245 jack_deactivate(client);
246 for(auto port : this->input_ports)
247 jack_port_unregister(client, port);
248 for(auto port : this->output_ports)
249 jack_port_unregister(client, port);
250 m_client.reset();
251 }
252 }
253
254private:
255 static int
256 clear_buffers(jack_engine& self, jack_nframes_t nframes, std::size_t outputs)
257 {
258 for(std::size_t i = 0; i < outputs; i++)
259 {
260 auto chan = (jack_default_audio_sample_t*)jack_port_get_buffer(
261 self.output_ports[i], nframes);
262 for(std::size_t j = 0; j < nframes; j++)
263 chan[j] = 0.f;
264 }
265
266 return 0;
267 }
268
269 static int process(jack_nframes_t nframes, void* arg)
270 {
271 [[maybe_unused]]
272 static const thread_local auto _
273 = [] {
274 ossia::set_thread_name("ossia audio 0");
275 ossia::set_thread_pinned(thread_type::Audio, 0);
276 return 0;
277 }();
278
279 auto& self = *static_cast<jack_engine*>(arg);
280 self.tick_start();
281
282 const auto inputs = self.input_ports.size();
283 const auto outputs = self.output_ports.size();
284 if(self.stop_processing)
285 {
286 self.tick_clear();
287 return clear_buffers(self, nframes, outputs);
288 }
289
290 auto float_input = (float**)alloca(sizeof(float*) * inputs);
291 auto float_output = (float**)alloca(sizeof(float*) * outputs);
292 for(std::size_t i = 0; i < inputs; i++)
293 {
294 float_input[i] = (jack_default_audio_sample_t*)jack_port_get_buffer(
295 self.input_ports[i], nframes);
296 }
297 for(std::size_t i = 0; i < outputs; i++)
298 {
299 float_output[i] = (jack_default_audio_sample_t*)jack_port_get_buffer(
300 self.output_ports[i], nframes);
301 }
302
303 // Transport
304 jack_position_t pos{};
305 std::optional<transport_status> st;
306 std::optional<uint64_t> transport_frames;
307 auto transport_state = jack_transport_query(self.m_client->client, &pos);
308 if(self.transport != transport_mode::none)
309 {
310 switch(transport_state)
311 {
312 case JackTransportStopped:
313 st = transport_status::stopped;
314 break;
315 case JackTransportStarting:
316 default: // case JackTransportNetStarting: because not yet supported
317 // in Debian
318 st = transport_status::starting;
319 break;
320 case JackTransportRolling:
321 case JackTransportLooping:
322 st = transport_status::playing;
323 break;
324 }
325 transport_frames = jack_nframes_t(pos.frame);
326 }
327
328 // std::cerr << pos.beats_per_minute << std::endl;
329
330 // Actual execution
331 ossia::audio_tick_state ts{
332 float_input, float_output, (int)inputs, (int)outputs,
333 nframes, pos.usecs / 1e6, transport_frames, st};
334 self.audio_tick(ts);
335
336 self.tick_end();
337 return 0;
338 }
339
340 std::shared_ptr<jack_client> m_client{};
341 std::vector<jack_port_t*> input_ports;
342 std::vector<jack_port_t*> output_ports;
343
344 bool activated{};
345 transport_mode transport{};
346 transport_sync_function sync_function;
347 transport_timebase_function timebase_function;
348};
349}
350
351#endif
352#endif
Definition git_info.h:7