OSSIA
Open Scenario System for Interactive Application
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 
21 namespace ossia
22 {
23 
24 #if defined(_WIN32)
25 inline 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 
59 struct 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 
76 using transport_timebase_function = smallfun::function<void(int, jack_position_t&), 16>;
77 using transport_sync_function
78  = smallfun::function<int(jack_transport_state_t, jack_position_t*), 16>;
79 struct 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 
89 class jack_engine final : public audio_engine
90 {
91 public:
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
229  {
230  stop();
231 
232  if(m_client)
233  {
234  jack_client_t* client = *m_client;
235  jack_deactivate(client);
236  for(auto port : this->input_ports)
237  jack_port_unregister(client, port);
238  for(auto port : this->output_ports)
239  jack_port_unregister(client, port);
240  }
241  }
242 
243  bool running() const override
244  {
245  if(!m_client)
246  return false;
247  return activated;
248  }
249 
250 private:
251  static int
252  clear_buffers(jack_engine& self, jack_nframes_t nframes, std::size_t outputs)
253  {
254  for(std::size_t i = 0; i < outputs; i++)
255  {
256  auto chan = (jack_default_audio_sample_t*)jack_port_get_buffer(
257  self.output_ports[i], nframes);
258  for(std::size_t j = 0; j < nframes; j++)
259  chan[j] = 0.f;
260  }
261 
262  return 0;
263  }
264 
265  static int process(jack_nframes_t nframes, void* arg)
266  {
267  static const thread_local auto _ = [] {
268  ossia::set_thread_name("ossia audio 0");
269  ossia::set_thread_pinned(thread_type::Audio, 0);
270  return 0;
271  }();
272 
273  auto& self = *static_cast<jack_engine*>(arg);
274  self.tick_start();
275 
276  const auto inputs = self.input_ports.size();
277  const auto outputs = self.output_ports.size();
278  if(self.stop_processing)
279  {
280  self.tick_clear();
281  return clear_buffers(self, nframes, outputs);
282  }
283 
284  auto float_input = (float**)alloca(sizeof(float*) * inputs);
285  auto float_output = (float**)alloca(sizeof(float*) * outputs);
286  for(std::size_t i = 0; i < inputs; i++)
287  {
288  float_input[i] = (jack_default_audio_sample_t*)jack_port_get_buffer(
289  self.input_ports[i], nframes);
290  }
291  for(std::size_t i = 0; i < outputs; i++)
292  {
293  float_output[i] = (jack_default_audio_sample_t*)jack_port_get_buffer(
294  self.output_ports[i], nframes);
295  }
296 
297  // Transport
298  jack_position_t pos{};
299  std::optional<transport_status> st;
300  std::optional<uint64_t> transport_frames;
301  auto transport_state = jack_transport_query(self.m_client->client, &pos);
302  if(self.transport != transport_mode::none)
303  {
304  switch(transport_state)
305  {
306  case JackTransportStopped:
307  st = transport_status::stopped;
308  break;
309  case JackTransportStarting:
310  default: // case JackTransportNetStarting: because not yet supported
311  // in Debian
312  st = transport_status::starting;
313  break;
314  case JackTransportRolling:
315  case JackTransportLooping:
316  st = transport_status::playing;
317  break;
318  }
319  transport_frames = jack_nframes_t(pos.frame);
320  }
321 
322  // std::cerr << pos.beats_per_minute << std::endl;
323 
324  // Actual execution
325  ossia::audio_tick_state ts{
326  float_input, float_output, (int)inputs, (int)outputs,
327  nframes, pos.usecs / 1e6, transport_frames, st};
328  self.audio_tick(ts);
329 
330  self.tick_end();
331  return 0;
332  }
333 
334  std::shared_ptr<jack_client> m_client{};
335  std::vector<jack_port_t*> input_ports;
336  std::vector<jack_port_t*> output_ports;
337 
338  bool activated{};
339  transport_mode transport{};
340  transport_sync_function sync_function;
341  transport_timebase_function timebase_function;
342 };
343 }
344 
345 #endif
346 #endif
Definition: git_info.h:7