Chris@0
|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
Chris@0
|
2
|
Chris@0
|
3 /*
|
Chris@0
|
4 Sonic Visualiser
|
Chris@0
|
5 An audio file viewer and annotation editor.
|
Chris@0
|
6 Centre for Digital Music, Queen Mary, University of London.
|
Chris@0
|
7 This file copyright 2006 Chris Cannam.
|
Chris@0
|
8
|
Chris@0
|
9 This program is free software; you can redistribute it and/or
|
Chris@0
|
10 modify it under the terms of the GNU General Public License as
|
Chris@0
|
11 published by the Free Software Foundation; either version 2 of the
|
Chris@0
|
12 License, or (at your option) any later version. See the file
|
Chris@0
|
13 COPYING included with this distribution for more information.
|
Chris@0
|
14 */
|
Chris@0
|
15
|
Chris@0
|
16 #ifdef HAVE_JACK
|
Chris@0
|
17
|
Chris@0
|
18 #include "AudioJACKTarget.h"
|
Chris@0
|
19 #include "AudioCallbackPlaySource.h"
|
Chris@0
|
20
|
Chris@0
|
21 #include <iostream>
|
Chris@0
|
22 #include <cmath>
|
Chris@0
|
23
|
Chris@0
|
24 //#define DEBUG_AUDIO_JACK_TARGET 1
|
Chris@0
|
25
|
Chris@2
|
26 #ifdef BUILD_STATIC
|
Chris@2
|
27 #ifdef Q_OS_LINUX
|
Chris@2
|
28
|
Chris@2
|
29 // Some lunacy to enable JACK support in static builds. JACK isn't
|
Chris@2
|
30 // supposed to be linked statically, because it depends on a
|
Chris@2
|
31 // consistent shared memory layout between client library and daemon,
|
Chris@2
|
32 // so it's very fragile in the face of version mismatches.
|
Chris@2
|
33 //
|
Chris@2
|
34 // Therefore for static builds on Linux we avoid linking against JACK
|
Chris@2
|
35 // at all during the build, instead using dlopen and runtime symbol
|
Chris@2
|
36 // lookup to switch on JACK support at runtime. The following big
|
Chris@2
|
37 // mess (down to the #endifs) is the code that implements this.
|
Chris@2
|
38
|
Chris@2
|
39 static void *symbol(const char *name)
|
Chris@2
|
40 {
|
Chris@2
|
41 static bool attempted = false;
|
Chris@2
|
42 static void *library = 0;
|
Chris@2
|
43 static std::map<const char *, void *> symbols;
|
Chris@2
|
44 if (symbols.find(name) != symbols.end()) return symbols[name];
|
Chris@2
|
45 if (!library) {
|
Chris@2
|
46 if (!attempted) {
|
Chris@2
|
47 library = ::dlopen("libjack.so.1", RTLD_NOW);
|
Chris@2
|
48 if (!library) library = ::dlopen("libjack.so.0", RTLD_NOW);
|
Chris@2
|
49 if (!library) library = ::dlopen("libjack.so", RTLD_NOW);
|
Chris@2
|
50 if (!library) {
|
Chris@2
|
51 std::cerr << "WARNING: AudioJACKTarget: Failed to load JACK library: "
|
Chris@2
|
52 << ::dlerror() << " (tried .so, .so.0, .so.1)"
|
Chris@2
|
53 << std::endl;
|
Chris@2
|
54 }
|
Chris@2
|
55 attempted = true;
|
Chris@2
|
56 }
|
Chris@2
|
57 if (!library) return 0;
|
Chris@2
|
58 }
|
Chris@2
|
59 void *symbol = ::dlsym(library, name);
|
Chris@2
|
60 if (!symbol) {
|
Chris@2
|
61 std::cerr << "WARNING: AudioJACKTarget: Failed to locate symbol "
|
Chris@2
|
62 << name << ": " << ::dlerror() << std::endl;
|
Chris@2
|
63 }
|
Chris@2
|
64 symbols[name] = symbol;
|
Chris@2
|
65 return symbol;
|
Chris@2
|
66 }
|
Chris@2
|
67
|
Chris@2
|
68 static int dynamic_jack_set_process_callback(jack_client_t *client,
|
Chris@2
|
69 JackProcessCallback process_callback,
|
Chris@2
|
70 void *arg)
|
Chris@2
|
71 {
|
Chris@2
|
72 typedef int (*func)(jack_client_t *client,
|
Chris@2
|
73 JackProcessCallback process_callback,
|
Chris@2
|
74 void *arg);
|
Chris@2
|
75 void *s = symbol("jack_set_process_callback");
|
Chris@2
|
76 if (!s) return 1;
|
Chris@2
|
77 func f = (func)s;
|
Chris@2
|
78 return f(client, process_callback, arg);
|
Chris@2
|
79 }
|
Chris@2
|
80
|
Chris@41
|
81 static int dynamic_jack_set_xrun_callback(jack_client_t *client,
|
Chris@41
|
82 JackXRunCallback xrun_callback,
|
Chris@41
|
83 void *arg)
|
Chris@41
|
84 {
|
Chris@41
|
85 typedef int (*func)(jack_client_t *client,
|
Chris@41
|
86 JackXRunCallback xrun_callback,
|
Chris@41
|
87 void *arg);
|
Chris@41
|
88 void *s = symbol("jack_set_xrun_callback");
|
Chris@41
|
89 if (!s) return 1;
|
Chris@41
|
90 func f = (func)s;
|
Chris@41
|
91 return f(client, xrun_callback, arg);
|
Chris@41
|
92 }
|
Chris@41
|
93
|
Chris@2
|
94 static const char **dynamic_jack_get_ports(jack_client_t *client,
|
Chris@2
|
95 const char *port_name_pattern,
|
Chris@2
|
96 const char *type_name_pattern,
|
Chris@2
|
97 unsigned long flags)
|
Chris@2
|
98 {
|
Chris@2
|
99 typedef const char **(*func)(jack_client_t *client,
|
Chris@2
|
100 const char *port_name_pattern,
|
Chris@2
|
101 const char *type_name_pattern,
|
Chris@2
|
102 unsigned long flags);
|
Chris@2
|
103 void *s = symbol("jack_get_ports");
|
Chris@2
|
104 if (!s) return 0;
|
Chris@2
|
105 func f = (func)s;
|
Chris@2
|
106 return f(client, port_name_pattern, type_name_pattern, flags);
|
Chris@2
|
107 }
|
Chris@2
|
108
|
Chris@2
|
109 static jack_port_t *dynamic_jack_port_register(jack_client_t *client,
|
Chris@2
|
110 const char *port_name,
|
Chris@2
|
111 const char *port_type,
|
Chris@2
|
112 unsigned long flags,
|
Chris@2
|
113 unsigned long buffer_size)
|
Chris@2
|
114 {
|
Chris@2
|
115 typedef jack_port_t *(*func)(jack_client_t *client,
|
Chris@2
|
116 const char *port_name,
|
Chris@2
|
117 const char *port_type,
|
Chris@2
|
118 unsigned long flags,
|
Chris@2
|
119 unsigned long buffer_size);
|
Chris@2
|
120 void *s = symbol("jack_port_register");
|
Chris@2
|
121 if (!s) return 0;
|
Chris@2
|
122 func f = (func)s;
|
Chris@2
|
123 return f(client, port_name, port_type, flags, buffer_size);
|
Chris@2
|
124 }
|
Chris@2
|
125
|
Chris@2
|
126 static int dynamic_jack_connect(jack_client_t *client,
|
Chris@2
|
127 const char *source,
|
Chris@2
|
128 const char *dest)
|
Chris@2
|
129 {
|
Chris@2
|
130 typedef int (*func)(jack_client_t *client,
|
Chris@2
|
131 const char *source,
|
Chris@2
|
132 const char *dest);
|
Chris@2
|
133 void *s = symbol("jack_connect");
|
Chris@2
|
134 if (!s) return 1;
|
Chris@2
|
135 func f = (func)s;
|
Chris@2
|
136 return f(client, source, dest);
|
Chris@2
|
137 }
|
Chris@2
|
138
|
Chris@2
|
139 static void *dynamic_jack_port_get_buffer(jack_port_t *port,
|
Chris@2
|
140 jack_nframes_t sz)
|
Chris@2
|
141 {
|
Chris@2
|
142 typedef void *(*func)(jack_port_t *, jack_nframes_t);
|
Chris@2
|
143 void *s = symbol("jack_port_get_buffer");
|
Chris@2
|
144 if (!s) return 0;
|
Chris@2
|
145 func f = (func)s;
|
Chris@2
|
146 return f(port, sz);
|
Chris@2
|
147 }
|
Chris@2
|
148
|
Chris@2
|
149 static int dynamic_jack_port_unregister(jack_client_t *client,
|
Chris@2
|
150 jack_port_t *port)
|
Chris@2
|
151 {
|
Chris@2
|
152 typedef int(*func)(jack_client_t *, jack_port_t *);
|
Chris@2
|
153 void *s = symbol("jack_port_unregister");
|
Chris@2
|
154 if (!s) return 0;
|
Chris@2
|
155 func f = (func)s;
|
Chris@2
|
156 return f(client, port);
|
Chris@2
|
157 }
|
Chris@2
|
158
|
Chris@2
|
159 #define dynamic1(rv, name, argtype, failval) \
|
Chris@2
|
160 static rv dynamic_##name(argtype arg) { \
|
Chris@2
|
161 typedef rv (*func) (argtype); \
|
Chris@2
|
162 void *s = symbol(#name); \
|
Chris@2
|
163 if (!s) return failval; \
|
Chris@2
|
164 func f = (func) s; \
|
Chris@2
|
165 return f(arg); \
|
Chris@2
|
166 }
|
Chris@2
|
167
|
Chris@2
|
168 dynamic1(jack_client_t *, jack_client_new, const char *, 0);
|
Chris@2
|
169 dynamic1(jack_nframes_t, jack_get_buffer_size, jack_client_t *, 0);
|
Chris@2
|
170 dynamic1(jack_nframes_t, jack_get_sample_rate, jack_client_t *, 0);
|
Chris@2
|
171 dynamic1(int, jack_activate, jack_client_t *, 1);
|
Chris@2
|
172 dynamic1(int, jack_deactivate, jack_client_t *, 1);
|
Chris@2
|
173 dynamic1(int, jack_client_close, jack_client_t *, 1);
|
Chris@2
|
174 dynamic1(jack_nframes_t, jack_port_get_latency, jack_port_t *, 0);
|
Chris@2
|
175 dynamic1(const char *, jack_port_name, const jack_port_t *, 0);
|
Chris@2
|
176
|
Chris@2
|
177 #define jack_client_new dynamic_jack_client_new
|
Chris@2
|
178 #define jack_get_buffer_size dynamic_jack_get_buffer_size
|
Chris@2
|
179 #define jack_get_sample_rate dynamic_jack_get_sample_rate
|
Chris@2
|
180 #define jack_set_process_callback dynamic_jack_set_process_callback
|
Chris@41
|
181 #define jack_set_xrun_callback dynamic_jack_set_xrun_callback
|
Chris@2
|
182 #define jack_activate dynamic_jack_activate
|
Chris@2
|
183 #define jack_deactivate dynamic_jack_deactivate
|
Chris@2
|
184 #define jack_client_close dynamic_jack_client_close
|
Chris@2
|
185 #define jack_get_ports dynamic_jack_get_ports
|
Chris@2
|
186 #define jack_port_register dynamic_jack_port_register
|
Chris@2
|
187 #define jack_port_unregister dynamic_jack_port_unregister
|
Chris@2
|
188 #define jack_port_get_latency dynamic_jack_port_get_latency
|
Chris@2
|
189 #define jack_port_name dynamic_jack_port_name
|
Chris@2
|
190 #define jack_connect dynamic_jack_connect
|
Chris@2
|
191 #define jack_port_get_buffer dynamic_jack_port_get_buffer
|
Chris@2
|
192
|
Chris@2
|
193 #endif
|
Chris@2
|
194 #endif
|
Chris@2
|
195
|
Chris@0
|
196 AudioJACKTarget::AudioJACKTarget(AudioCallbackPlaySource *source) :
|
Chris@0
|
197 AudioCallbackPlayTarget(source),
|
Chris@0
|
198 m_client(0),
|
Chris@0
|
199 m_bufferSize(0),
|
Chris@0
|
200 m_sampleRate(0)
|
Chris@0
|
201 {
|
Chris@0
|
202 char name[20];
|
Chris@0
|
203 strcpy(name, "Sonic Visualiser");
|
Chris@0
|
204 m_client = jack_client_new(name);
|
Chris@0
|
205
|
Chris@0
|
206 if (!m_client) {
|
Chris@0
|
207 sprintf(name, "Sonic Visualiser (%d)", (int)getpid());
|
Chris@0
|
208 m_client = jack_client_new(name);
|
Chris@0
|
209 if (!m_client) {
|
Chris@0
|
210 std::cerr
|
Chris@0
|
211 << "ERROR: AudioJACKTarget: Failed to connect to JACK server"
|
Chris@0
|
212 << std::endl;
|
Chris@0
|
213 }
|
Chris@0
|
214 }
|
Chris@0
|
215
|
Chris@0
|
216 if (!m_client) return;
|
Chris@0
|
217
|
Chris@0
|
218 m_bufferSize = jack_get_buffer_size(m_client);
|
Chris@0
|
219 m_sampleRate = jack_get_sample_rate(m_client);
|
Chris@0
|
220
|
Chris@41
|
221 jack_set_xrun_callback(m_client, xrunStatic, this);
|
Chris@0
|
222 jack_set_process_callback(m_client, processStatic, this);
|
Chris@0
|
223
|
Chris@0
|
224 if (jack_activate(m_client)) {
|
Chris@0
|
225 std::cerr << "ERROR: AudioJACKTarget: Failed to activate JACK client"
|
Chris@0
|
226 << std::endl;
|
Chris@0
|
227 }
|
Chris@0
|
228
|
Chris@0
|
229 if (m_source) {
|
Chris@0
|
230 sourceModelReplaced();
|
Chris@0
|
231 }
|
Chris@0
|
232 }
|
Chris@0
|
233
|
Chris@0
|
234 AudioJACKTarget::~AudioJACKTarget()
|
Chris@0
|
235 {
|
Chris@0
|
236 if (m_client) {
|
Chris@0
|
237 jack_deactivate(m_client);
|
Chris@0
|
238 jack_client_close(m_client);
|
Chris@0
|
239 }
|
Chris@0
|
240 }
|
Chris@0
|
241
|
Chris@0
|
242 bool
|
Chris@0
|
243 AudioJACKTarget::isOK() const
|
Chris@0
|
244 {
|
Chris@0
|
245 return (m_client != 0);
|
Chris@0
|
246 }
|
Chris@0
|
247
|
Chris@0
|
248 int
|
Chris@0
|
249 AudioJACKTarget::processStatic(jack_nframes_t nframes, void *arg)
|
Chris@0
|
250 {
|
Chris@0
|
251 return ((AudioJACKTarget *)arg)->process(nframes);
|
Chris@0
|
252 }
|
Chris@0
|
253
|
Chris@41
|
254 int
|
Chris@41
|
255 AudioJACKTarget::xrunStatic(void *arg)
|
Chris@41
|
256 {
|
Chris@41
|
257 return ((AudioJACKTarget *)arg)->xrun();
|
Chris@41
|
258 }
|
Chris@41
|
259
|
Chris@0
|
260 void
|
Chris@0
|
261 AudioJACKTarget::sourceModelReplaced()
|
Chris@0
|
262 {
|
Chris@0
|
263 m_mutex.lock();
|
Chris@0
|
264
|
Chris@0
|
265 m_source->setTargetBlockSize(m_bufferSize);
|
Chris@0
|
266 m_source->setTargetSampleRate(m_sampleRate);
|
Chris@0
|
267
|
Chris@0
|
268 size_t channels = m_source->getSourceChannelCount();
|
Chris@0
|
269
|
Chris@0
|
270 // Because we offer pan, we always want at least 2 channels
|
Chris@0
|
271 if (channels < 2) channels = 2;
|
Chris@0
|
272
|
Chris@0
|
273 if (channels == m_outputs.size() || !m_client) {
|
Chris@0
|
274 m_mutex.unlock();
|
Chris@0
|
275 return;
|
Chris@0
|
276 }
|
Chris@0
|
277
|
Chris@0
|
278 const char **ports =
|
Chris@0
|
279 jack_get_ports(m_client, NULL, NULL,
|
Chris@0
|
280 JackPortIsPhysical | JackPortIsInput);
|
Chris@0
|
281 size_t physicalPortCount = 0;
|
Chris@0
|
282 while (ports[physicalPortCount]) ++physicalPortCount;
|
Chris@0
|
283
|
Chris@0
|
284 #ifdef DEBUG_AUDIO_JACK_TARGET
|
Chris@0
|
285 std::cerr << "AudioJACKTarget::sourceModelReplaced: have " << channels << " channels and " << physicalPortCount << " physical ports" << std::endl;
|
Chris@0
|
286 #endif
|
Chris@0
|
287
|
Chris@0
|
288 while (m_outputs.size() < channels) {
|
Chris@0
|
289
|
Chris@0
|
290 char name[20];
|
Chris@0
|
291 jack_port_t *port;
|
Chris@0
|
292
|
Chris@0
|
293 sprintf(name, "out %d", m_outputs.size() + 1);
|
Chris@0
|
294
|
Chris@0
|
295 port = jack_port_register(m_client,
|
Chris@0
|
296 name,
|
Chris@0
|
297 JACK_DEFAULT_AUDIO_TYPE,
|
Chris@0
|
298 JackPortIsOutput,
|
Chris@0
|
299 0);
|
Chris@0
|
300
|
Chris@0
|
301 if (!port) {
|
Chris@0
|
302 std::cerr
|
Chris@0
|
303 << "ERROR: AudioJACKTarget: Failed to create JACK output port "
|
Chris@0
|
304 << m_outputs.size() << std::endl;
|
Chris@0
|
305 } else {
|
Chris@0
|
306 m_source->setTargetPlayLatency(jack_port_get_latency(port));
|
Chris@0
|
307 }
|
Chris@0
|
308
|
Chris@0
|
309 if (m_outputs.size() < physicalPortCount) {
|
Chris@0
|
310 jack_connect(m_client, jack_port_name(port), ports[m_outputs.size()]);
|
Chris@0
|
311 }
|
Chris@0
|
312
|
Chris@0
|
313 m_outputs.push_back(port);
|
Chris@0
|
314 }
|
Chris@0
|
315
|
Chris@0
|
316 while (m_outputs.size() > channels) {
|
Chris@0
|
317 std::vector<jack_port_t *>::iterator itr = m_outputs.end();
|
Chris@0
|
318 --itr;
|
Chris@0
|
319 jack_port_t *port = *itr;
|
Chris@0
|
320 if (port) jack_port_unregister(m_client, port);
|
Chris@0
|
321 m_outputs.erase(itr);
|
Chris@0
|
322 }
|
Chris@0
|
323
|
Chris@0
|
324 m_mutex.unlock();
|
Chris@0
|
325 }
|
Chris@0
|
326
|
Chris@0
|
327 int
|
Chris@0
|
328 AudioJACKTarget::process(jack_nframes_t nframes)
|
Chris@0
|
329 {
|
Chris@0
|
330 if (!m_mutex.tryLock()) {
|
Chris@0
|
331 return 0;
|
Chris@0
|
332 }
|
Chris@0
|
333
|
Chris@0
|
334 if (m_outputs.empty()) {
|
Chris@0
|
335 m_mutex.unlock();
|
Chris@0
|
336 return 0;
|
Chris@0
|
337 }
|
Chris@0
|
338
|
Chris@0
|
339 #ifdef DEBUG_AUDIO_JACK_TARGET
|
Chris@0
|
340 std::cout << "AudioJACKTarget::process(" << nframes << "): have a source" << std::endl;
|
Chris@0
|
341 #endif
|
Chris@0
|
342
|
Chris@0
|
343 #ifdef DEBUG_AUDIO_JACK_TARGET
|
Chris@0
|
344 if (m_bufferSize != nframes) {
|
Chris@0
|
345 std::cerr << "WARNING: m_bufferSize != nframes (" << m_bufferSize << " != " << nframes << ")" << std::endl;
|
Chris@0
|
346 }
|
Chris@0
|
347 #endif
|
Chris@0
|
348
|
Chris@0
|
349 float **buffers = (float **)alloca(m_outputs.size() * sizeof(float *));
|
Chris@0
|
350
|
Chris@0
|
351 for (size_t ch = 0; ch < m_outputs.size(); ++ch) {
|
Chris@0
|
352 buffers[ch] = (float *)jack_port_get_buffer(m_outputs[ch], nframes);
|
Chris@0
|
353 }
|
Chris@0
|
354
|
Chris@106
|
355 size_t received = 0;
|
Chris@106
|
356
|
Chris@0
|
357 if (m_source) {
|
Chris@106
|
358 received = m_source->getSourceSamples(nframes, buffers);
|
Chris@106
|
359 }
|
Chris@106
|
360
|
Chris@106
|
361 for (size_t ch = 0; ch < m_outputs.size(); ++ch) {
|
Chris@106
|
362 for (size_t i = received; i < nframes; ++i) {
|
Chris@106
|
363 buffers[ch][i] = 0.0;
|
Chris@106
|
364 }
|
Chris@0
|
365 }
|
Chris@0
|
366
|
Chris@0
|
367 float peakLeft = 0.0, peakRight = 0.0;
|
Chris@0
|
368
|
Chris@0
|
369 for (size_t ch = 0; ch < m_outputs.size(); ++ch) {
|
Chris@0
|
370
|
Chris@0
|
371 float peak = 0.0;
|
Chris@0
|
372
|
Chris@0
|
373 for (size_t i = 0; i < nframes; ++i) {
|
Chris@0
|
374 buffers[ch][i] *= m_outputGain;
|
Chris@0
|
375 float sample = fabsf(buffers[ch][i]);
|
Chris@0
|
376 if (sample > peak) peak = sample;
|
Chris@0
|
377 }
|
Chris@0
|
378
|
Chris@0
|
379 if (ch == 0) peakLeft = peak;
|
Chris@0
|
380 if (ch > 0 || m_outputs.size() == 1) peakRight = peak;
|
Chris@0
|
381 }
|
Chris@0
|
382
|
Chris@0
|
383 if (m_source) {
|
Chris@0
|
384 m_source->setOutputLevels(peakLeft, peakRight);
|
Chris@0
|
385 }
|
Chris@0
|
386
|
Chris@0
|
387 m_mutex.unlock();
|
Chris@0
|
388 return 0;
|
Chris@0
|
389 }
|
Chris@0
|
390
|
Chris@41
|
391 int
|
Chris@41
|
392 AudioJACKTarget::xrun()
|
Chris@41
|
393 {
|
Chris@41
|
394 std::cerr << "AudioJACKTarget: xrun!" << std::endl;
|
Chris@42
|
395 if (m_source) m_source->audioProcessingOverload();
|
Chris@57
|
396 return 0;
|
Chris@41
|
397 }
|
Chris@0
|
398
|
Chris@0
|
399 #endif /* HAVE_JACK */
|
Chris@0
|
400
|