1 // padthv1_jack.cpp
2 //
3 /****************************************************************************
4 Copyright (C) 2012-2021, rncbc aka Rui Nuno Capela. All rights reserved.
5
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License
8 as published by the Free Software Foundation; either version 2
9 of the License, or (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License along
17 with this program; if not, write to the Free Software Foundation, Inc.,
18 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20 *****************************************************************************/
21
22 #include "padthv1_jack.h"
23 #include "padthv1_config.h"
24 #include "padthv1_param.h"
25
26 #include "padthv1_programs.h"
27 #include "padthv1_controls.h"
28
29 #include <jack/midiport.h>
30
31 #include <cstdio>
32 #include <cstring>
33 #include <cmath>
34
35 #include <QCoreApplication>
36 #include <QDir>
37
38
39 #ifdef CONFIG_ALSA_MIDI
40
41 //-------------------------------------------------------------------------
42 // alsa input thread.
43
44 #include <QThread>
45
46
47 class padthv1_alsa_thread : public QThread
48 {
49 public:
50
padthv1_alsa_thread(padthv1_jack * sampl)51 padthv1_alsa_thread(padthv1_jack *sampl)
52 : QThread(), m_sampl(sampl), m_running(false) {}
53
~padthv1_alsa_thread()54 ~padthv1_alsa_thread()
55 { // fake sync and wait
56 if (m_running && isRunning()) do {
57 m_running = false;
58 }
59 while (!wait(100));
60 }
61
62 protected:
63
run()64 void run()
65 {
66 snd_seq_t *seq = m_sampl->alsa_seq();
67 if (seq == nullptr)
68 return;
69
70 m_running = true;
71
72 int nfds;
73 struct pollfd *pfds;
74
75 nfds = snd_seq_poll_descriptors_count(seq, POLLIN);
76 pfds = (struct pollfd *) alloca(nfds * sizeof(struct pollfd));
77 snd_seq_poll_descriptors(seq, pfds, nfds, POLLIN);
78
79 int poll_rc = 0;
80
81 while (m_running && poll_rc >= 0) {
82 poll_rc = ::poll(pfds, nfds, 200);
83 while (poll_rc > 0) {
84 snd_seq_event_t *ev = nullptr;
85 snd_seq_event_input(seq, &ev);
86 m_sampl->alsa_capture(ev);
87 // snd_seq_free_event(ev);
88 poll_rc = snd_seq_event_input_pending(seq, 0);
89 }
90 }
91
92 m_running = false;
93 }
94
95 private:
96
97 padthv1_jack *m_sampl;
98
99 volatile bool m_running;
100 };
101
102 #endif // CONFIG_ALSA_MIDI
103
104
105 //-------------------------------------------------------------------------
106 // JACK process callback.
107
108 static
padthv1_jack_process(jack_nframes_t nframes,void * arg)109 int padthv1_jack_process ( jack_nframes_t nframes, void *arg )
110 {
111 return static_cast<padthv1_jack *> (arg)->process(nframes);
112 }
113
114 //----------------------------------------------------------------------
115 // JACK buffer-size change callback.
116
padthv1_jack_buffer_size(jack_nframes_t nframes,void * arg)117 static int padthv1_jack_buffer_size ( jack_nframes_t nframes, void *arg )
118 {
119 static_cast<padthv1_jack *> (arg)->setBufferSize(nframes);
120
121 return 0;
122 }
123
124
125 //----------------------------------------------------------------------
126 // JACK on-shutdown callback.
127
padthv1_jack_on_shutdown(void * arg)128 static void padthv1_jack_on_shutdown ( void *arg )
129 {
130 static_cast<padthv1_jack *> (arg)->shutdown();
131 }
132
133
134 #ifdef CONFIG_JACK_SESSION
135
136 #include <jack/session.h>
137
138 #include <QDir>
139
140 //----------------------------------------------------------------------
141 // padthv1_jack_session_event -- JACK session event callabck
142 //
143
padthv1_jack_session_event(jack_session_event_t * pSessionEvent,void * pvArg)144 static void padthv1_jack_session_event (
145 jack_session_event_t *pSessionEvent, void *pvArg )
146 {
147 padthv1_jack *pSynth = static_cast<padthv1_jack *> (pvArg);
148
149 if (pSynth)
150 pSynth->sessionEvent(pSessionEvent);
151 }
152
153 #endif // CONFIG_JACK_SESSION
154
155
156 //-------------------------------------------------------------------------
157 // padthv1_jack - impl.
158 //
159
padthv1_jack(const char * client_name)160 padthv1_jack::padthv1_jack (const char *client_name) : padthv1(2)
161 {
162 m_client = nullptr;
163
164 m_activated = false;
165
166 m_audio_ins = nullptr;
167 m_audio_outs = nullptr;
168
169 m_ins = m_outs = nullptr;
170
171 ::memset(m_params, 0, padthv1::NUM_PARAMS * sizeof(float));
172
173 #ifdef CONFIG_JACK_MIDI
174 m_midi_in = nullptr;
175 #endif
176 #ifdef CONFIG_ALSA_MIDI
177 m_alsa_seq = nullptr;
178 // m_alsa_client = -1;
179 m_alsa_port = -1;
180 m_alsa_decoder = nullptr;
181 m_alsa_buffer = nullptr;
182 m_alsa_thread = nullptr;
183 #endif
184
185 padthv1::programs()->enabled(true);
186 padthv1::controls()->enabled(true);
187
188 open(client_name);
189 activate();
190 }
191
192
~padthv1_jack(void)193 padthv1_jack::~padthv1_jack (void)
194 {
195 deactivate();
196 close();
197 }
198
199
client(void) const200 jack_client_t *padthv1_jack::client (void) const
201 {
202 return m_client;
203 }
204
205
process(jack_nframes_t nframes)206 int padthv1_jack::process ( jack_nframes_t nframes )
207 {
208 if (!m_activated)
209 return 0;
210
211 const uint16_t nchannels = padthv1::channels();
212 float **ins = m_ins, **outs = m_outs;
213 for (uint16_t k = 0; k < nchannels; ++k) {
214 ins[k] = static_cast<float *> (
215 ::jack_port_get_buffer(m_audio_ins[k], nframes));
216 outs[k] = static_cast<float *> (
217 ::jack_port_get_buffer(m_audio_outs[k], nframes));
218 }
219
220 jack_position_t pos;
221 jack_transport_query(m_client, &pos);
222 if (pos.valid & JackPositionBBT) {
223 const float host_bpm = float(pos.beats_per_minute);
224 if (::fabsf(host_bpm - padthv1::tempo()) > 0.001f)
225 padthv1::setTempo(host_bpm);
226 }
227
228 uint32_t ndelta = 0;
229
230 #ifdef CONFIG_JACK_MIDI
231 void *midi_in = ::jack_port_get_buffer(m_midi_in, nframes);
232 if (midi_in) {
233 const uint32_t nevents = ::jack_midi_get_event_count(midi_in);
234 for (uint32_t n = 0; n < nevents; ++n) {
235 jack_midi_event_t event;
236 ::jack_midi_event_get(&event, midi_in, n);
237 if (event.time > ndelta) {
238 const uint32_t nread = event.time - ndelta;
239 if (nread > 0) {
240 padthv1::process(ins, outs, nread);
241 for (uint16_t k = 0; k < nchannels; ++k) {
242 ins[k] += nread;
243 outs[k] += nread;
244 }
245 }
246 ndelta = event.time;
247 }
248 padthv1::process_midi(event.buffer, event.size);
249 }
250 }
251 #endif
252 #ifdef CONFIG_ALSA_MIDI
253 const jack_nframes_t buffer_size = ::jack_get_buffer_size(m_client);
254 const jack_nframes_t frame_time = ::jack_last_frame_time(m_client);
255 uint8_t event_buffer[1024];
256 jack_midi_event_t event;
257 while (::jack_ringbuffer_peek(m_alsa_buffer,
258 (char *) &event, sizeof(event)) == sizeof(event)) {
259 if (event.time > frame_time)
260 break;
261 jack_nframes_t event_time = frame_time - event.time;
262 if (event_time > buffer_size)
263 event_time = 0;
264 else
265 event_time = buffer_size - event_time;
266 if (event_time > ndelta) {
267 const uint32_t nread = event_time - ndelta;
268 if (nread > 0) {
269 padthv1::process(ins, outs, nread);
270 for (uint16_t k = 0; k < nchannels; ++k) {
271 ins[k] += nread;
272 outs[k] += nread;
273 }
274 }
275 ndelta = event_time;
276 }
277 ::jack_ringbuffer_read_advance(m_alsa_buffer, sizeof(event));
278 ::jack_ringbuffer_read(m_alsa_buffer, (char *) event_buffer, event.size);
279 padthv1::process_midi(event_buffer, event.size);
280 }
281 #endif // CONFIG_ALSA_MIDI
282
283 if (nframes > ndelta)
284 padthv1::process(ins, outs, nframes - ndelta);
285
286 return 0;
287 }
288
289
290 #ifdef CONFIG_JACK_SESSION
291 #if defined(Q_CC_GNU) || defined(Q_CC_MINGW)
292 #pragma GCC diagnostic push
293 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
294 #endif
295 #endif
296
open(const char * client_name)297 void padthv1_jack::open ( const char *client_name )
298 {
299 // init param ports
300 for (uint32_t i = 0; i < padthv1::NUM_PARAMS; ++i) {
301 const padthv1::ParamIndex index = padthv1::ParamIndex(i);
302 m_params[i] = padthv1_param::paramDefaultValue(index);
303 padthv1::setParamPort(index, &m_params[i]);
304 }
305
306 // open client
307 m_client = ::jack_client_open(client_name, JackNullOption, nullptr);
308 if (m_client == nullptr)
309 return;
310
311 // set sample rate
312 padthv1::setSampleRate(float(jack_get_sample_rate(m_client)));
313 // padthv1::reset();
314
315 // register audio ports & buffers
316 uint16_t nchannels = padthv1::channels();
317
318 m_audio_ins = new jack_port_t * [nchannels];
319 m_audio_outs = new jack_port_t * [nchannels];
320
321 m_ins = new float * [nchannels];
322 m_outs = new float * [nchannels];
323
324 char port_name[32];
325 for (uint16_t k = 0; k < nchannels; ++k) {
326 ::snprintf(port_name, sizeof(port_name), "in_%d", k + 1);
327 m_audio_ins[k] = ::jack_port_register(m_client,
328 port_name, JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0);
329 m_ins[k] = nullptr;
330 ::snprintf(port_name, sizeof(port_name), "out_%d", k + 1);
331 m_audio_outs[k] = ::jack_port_register(m_client,
332 port_name, JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
333 m_outs[k] = nullptr;
334 }
335
336 // register midi port
337 #ifdef CONFIG_JACK_MIDI
338 m_midi_in = ::jack_port_register(m_client,
339 "in", JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0);
340 #endif
341 #ifdef CONFIG_ALSA_MIDI
342 m_alsa_seq = nullptr;
343 // m_alsa_client = -1;
344 m_alsa_port = -1;
345 m_alsa_decoder = nullptr;
346 m_alsa_buffer = nullptr;
347 m_alsa_thread = nullptr;
348 // open alsa sequencer client...
349 if (snd_seq_open(&m_alsa_seq, "hw", SND_SEQ_OPEN_INPUT, 0) >= 0) {
350 snd_seq_set_client_name(m_alsa_seq, client_name);
351 // m_alsa_client = snd_seq_client_id(m_alsa_seq);
352 m_alsa_port = snd_seq_create_simple_port(m_alsa_seq, "in",
353 SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE,
354 SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION);
355 snd_midi_event_new(1024, &m_alsa_decoder);
356 m_alsa_buffer = ::jack_ringbuffer_create(
357 1024 * (sizeof(jack_midi_event_t) + 4));
358 m_alsa_thread = new padthv1_alsa_thread(this);
359 m_alsa_thread->start(QThread::TimeCriticalPriority);
360 }
361 #endif // CONFIG_ALSA_MIDI
362
363 // setup any local, initial buffers...
364 padthv1::setBufferSize(::jack_get_buffer_size(m_client) << 2);
365
366 ::jack_set_buffer_size_callback(m_client,
367 padthv1_jack_buffer_size, this);
368
369 ::jack_on_shutdown(m_client,
370 padthv1_jack_on_shutdown, this);
371
372 // set process callbacks...
373 ::jack_set_process_callback(m_client,
374 padthv1_jack_process, this);
375
376 #ifdef CONFIG_JACK_SESSION
377 // JACK session event callback...
378 ::jack_set_session_callback(m_client,
379 padthv1_jack_session_event, this);
380 #endif
381 }
382
383 #ifdef CONFIG_JACK_SESSION
384 #if defined(Q_CC_GNU) || defined(Q_CC_MINGW)
385 #pragma GCC diagnostic pop
386 #endif
387 #endif
388
389
activate(void)390 void padthv1_jack::activate (void)
391 {
392 if (!m_activated) {
393 padthv1::reset();
394 if (m_client) {
395 ::jack_activate(m_client);
396 m_activated = true;
397 }
398 }
399 }
400
deactivate(void)401 void padthv1_jack::deactivate (void)
402 {
403 if (m_activated) {
404 if (m_client) {
405 m_activated = false;
406 ::jack_deactivate(m_client);
407 }
408 }
409 }
410
411
close(void)412 void padthv1_jack::close (void)
413 {
414 #ifdef CONFIG_ALSA_MIDI
415 // close alsa sequencer client...
416 if (m_alsa_seq) {
417 if (m_alsa_thread) {
418 delete m_alsa_thread;
419 m_alsa_thread = nullptr;
420 }
421 if (m_alsa_buffer) {
422 ::jack_ringbuffer_free(m_alsa_buffer);
423 m_alsa_buffer = nullptr;
424 }
425 if (m_alsa_decoder) {
426 snd_midi_event_free(m_alsa_decoder);
427 m_alsa_decoder = nullptr;
428 }
429 if (m_alsa_port >= 0) {
430 snd_seq_delete_simple_port(m_alsa_seq, m_alsa_port);
431 m_alsa_port = -1;
432 }
433 snd_seq_close(m_alsa_seq);
434 // m_alsa_client = -1;
435 m_alsa_seq = nullptr;
436 }
437 #endif
438
439 if (m_client == nullptr)
440 return;
441
442 #ifdef CONFIG_JACK_MIDI
443 // unregister midi port
444 if (m_midi_in) {
445 ::jack_port_unregister(m_client, m_midi_in);
446 m_midi_in = nullptr;
447 }
448 #endif
449
450 // unregister audio ports
451 const uint16_t nchannels = padthv1::channels();
452
453 for (uint16_t k = 0; k < nchannels; ++k) {
454 if (m_audio_outs && m_audio_outs[k]) {
455 ::jack_port_unregister(m_client, m_audio_outs[k]);
456 m_audio_outs[k] = nullptr;
457 }
458 if (m_outs && m_outs[k])
459 m_outs[k] = nullptr;
460 if (m_audio_ins && m_audio_ins[k]) {
461 ::jack_port_unregister(m_client, m_audio_ins[k]);
462 m_audio_ins[k] = nullptr;
463 }
464 if (m_ins && m_ins[k])
465 m_ins[k] = nullptr;
466 }
467
468 if (m_outs) {
469 delete [] m_outs;
470 m_outs = nullptr;
471 }
472 if (m_ins) {
473 delete [] m_ins;
474 m_ins = nullptr;
475 }
476
477 if (m_audio_outs) {
478 delete [] m_audio_outs;
479 m_audio_outs = nullptr;
480 }
481 if (m_audio_ins) {
482 delete [] m_audio_ins;
483 m_audio_ins = nullptr;
484 }
485
486 // close client
487 ::jack_client_close(m_client);
488 m_client = nullptr;
489 }
490
491
492 #ifdef CONFIG_ALSA_MIDI
493
494 // alsa sequencer client.
alsa_seq(void) const495 snd_seq_t *padthv1_jack::alsa_seq (void) const
496 {
497 return m_alsa_seq;
498 }
499
500 // alsa event capture.
alsa_capture(snd_seq_event_t * ev)501 void padthv1_jack::alsa_capture ( snd_seq_event_t *ev )
502 {
503 if (m_alsa_decoder == nullptr)
504 return;
505
506 if (ev == nullptr)
507 return;
508
509 // ignored events...
510 switch(ev->type) {
511 case SND_SEQ_EVENT_OSS:
512 case SND_SEQ_EVENT_CLIENT_START:
513 case SND_SEQ_EVENT_CLIENT_EXIT:
514 case SND_SEQ_EVENT_CLIENT_CHANGE:
515 case SND_SEQ_EVENT_PORT_START:
516 case SND_SEQ_EVENT_PORT_EXIT:
517 case SND_SEQ_EVENT_PORT_CHANGE:
518 case SND_SEQ_EVENT_PORT_SUBSCRIBED:
519 case SND_SEQ_EVENT_PORT_UNSUBSCRIBED:
520 case SND_SEQ_EVENT_USR0:
521 case SND_SEQ_EVENT_USR1:
522 case SND_SEQ_EVENT_USR2:
523 case SND_SEQ_EVENT_USR3:
524 case SND_SEQ_EVENT_USR4:
525 case SND_SEQ_EVENT_USR5:
526 case SND_SEQ_EVENT_USR6:
527 case SND_SEQ_EVENT_USR7:
528 case SND_SEQ_EVENT_USR8:
529 case SND_SEQ_EVENT_USR9:
530 case SND_SEQ_EVENT_BOUNCE:
531 case SND_SEQ_EVENT_USR_VAR0:
532 case SND_SEQ_EVENT_USR_VAR1:
533 case SND_SEQ_EVENT_USR_VAR2:
534 case SND_SEQ_EVENT_USR_VAR3:
535 case SND_SEQ_EVENT_USR_VAR4:
536 case SND_SEQ_EVENT_NONE:
537 return;
538 }
539
540 #ifdef CONFIG_DEBUG_0
541 // - show (input) event for debug purposes...
542 fprintf(stderr, "ALSA MIDI In: 0x%02x", ev->type);
543 if (ev->type == SND_SEQ_EVENT_SYSEX) {
544 fprintf(stderr, " SysEx {");
545 unsigned char *data = (unsigned char *) ev->data.ext.ptr;
546 for (unsigned int i = 0; i < ev->data.ext.len; ++i)
547 fprintf(stderr, " %02x", data[i]);
548 fprintf(stderr, " }\n");
549 } else {
550 for (unsigned int i = 0; i < sizeof(ev->data.raw8.d); ++i)
551 fprintf(stderr, " %3d", ev->data.raw8.d[i]);
552 fprintf(stderr, "\n");
553 }
554 #endif
555
556 const unsigned int nlimit = ::jack_ringbuffer_write_space(m_alsa_buffer);
557 if (nlimit > sizeof(jack_midi_event_t) + 4) {
558 unsigned char ev_buff[nlimit];
559 unsigned char *ev_data = &ev_buff[0] + sizeof(jack_midi_event_t);
560 const int ev_size = snd_midi_event_decode(m_alsa_decoder,
561 ev_data, nlimit - sizeof(jack_midi_event_t), ev);
562 if (ev_size > 0) {
563 jack_midi_event_t *ev_head = (jack_midi_event_t *) &ev_buff[0];
564 ev_head->time = ::jack_frame_time(m_client);
565 ev_head->size = ev_size;
566 ev_head->buffer = (jack_midi_data_t *) ev_data;
567 ::jack_ringbuffer_write(m_alsa_buffer,
568 (const char *) ev_buff, sizeof(jack_midi_event_t) + ev_size);
569 }
570 snd_midi_event_reset_decode(m_alsa_decoder);
571 }
572 }
573
574 #endif // CONFIG_ALSA_MIDI
575
576
577 #ifdef CONFIG_JACK_SESSION
578
579 #if defined(Q_CC_GNU) || defined(Q_CC_MINGW)
580 #pragma GCC diagnostic push
581 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
582 #endif
583
584 // JACK session event handler.
sessionEvent(void * pvSessionArg)585 void padthv1_jack::sessionEvent ( void *pvSessionArg )
586 {
587 jack_session_event_t *pJackSessionEvent
588 = (jack_session_event_t *) pvSessionArg;
589
590 #ifdef CONFIG_DEBUG
591 qDebug("padthv1_jack::sessionEvent()"
592 " type=%d client_uuid=\"%s\" session_dir=\"%s\"",
593 int(pJackSessionEvent->type),
594 pJackSessionEvent->client_uuid,
595 pJackSessionEvent->session_dir);
596 #endif
597
598 const bool bQuit = (pJackSessionEvent->type == JackSessionSaveAndQuit);
599
600 const QString sSessionDir
601 = QString::fromUtf8(pJackSessionEvent->session_dir);
602 const QString sSessionName
603 = QFileInfo(QFileInfo(sSessionDir).canonicalPath()).completeBaseName();
604 const QString sSessionFile = sSessionName + '.' + PADTHV1_TITLE;
605
606 QStringList args;
607 args << QCoreApplication::applicationFilePath();
608 args << QString("\"${SESSION_DIR}%1\"").arg(sSessionFile);
609
610 padthv1_param::savePreset(this,
611 QFileInfo(sSessionDir, sSessionFile).absoluteFilePath(), true);
612
613 const QByteArray aCmdLine = args.join(" ").toUtf8();
614 pJackSessionEvent->command_line = ::strdup(aCmdLine.constData());
615
616 ::jack_session_reply(m_client, pJackSessionEvent);
617 ::jack_session_event_free(pJackSessionEvent);
618
619 if (bQuit)
620 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
621 QCoreApplication::exit(0);
622 #else
623 QCoreApplication::quit();
624 #endif
625 }
626
627 #if defined(Q_CC_GNU) || defined(Q_CC_MINGW)
628 #pragma GCC diagnostic pop
629 #endif
630
631 #endif // CONFIG_JACK_SESSION
632
633
updatePreset(bool)634 void padthv1_jack::updatePreset ( bool /*bDirty*/ )
635 {
636 // nothing to do here...
637 }
638
639
updateParam(padthv1::ParamIndex)640 void padthv1_jack::updateParam ( padthv1::ParamIndex /*index*/ )
641 {
642 // nothing to do here...
643 }
644
645
updateParams(void)646 void padthv1_jack::updateParams (void)
647 {
648 // nothing to do here...
649 }
650
651
updateTuning(void)652 void padthv1_jack::updateTuning (void)
653 {
654 padthv1::resetTuning();
655 }
656
657
shutdown(void)658 void padthv1_jack::shutdown (void)
659 {
660 padthv1_jack_application *pApp = padthv1_jack_application::getInstance();
661 if (pApp)
662 pApp->shutdown();
663 }
664
665
shutdown_close(void)666 void padthv1_jack::shutdown_close (void)
667 {
668 m_activated = false;
669
670 if (m_client) {
671 ::jack_client_close(m_client);
672 m_client = nullptr;
673 }
674
675 close();
676 }
677
678
679 //-------------------------------------------------------------------------
680 // padthv1_jack_application -- Singleton application instance.
681 //
682
683 #include "padthv1widget_jack.h"
684
685 #include <QApplication>
686 #include <QTextStream>
687
688 #ifdef CONFIG_NSM
689 #include "padthv1_nsm.h"
690 #endif
691
692
693 #ifdef HAVE_SIGNAL_H
694
695 #include <QSocketNotifier>
696
697 #include <unistd.h>
698 #include <sys/types.h>
699 #include <sys/socket.h>
700 #include <signal.h>
701
702 // File descriptor for SIGTERM notifier.
703 static int g_fdSigterm[2] = { -1, -1 };
704
705 // Unix SIGTERM signal handler.
padthv1_sigterm_handler(int)706 static void padthv1_sigterm_handler ( int /*signo*/ )
707 {
708 char c = 1;
709
710 (void) (::write(g_fdSigterm[0], &c, sizeof(c)) > 0);
711 }
712
713 #endif // HAVE_SIGNAL_H
714
715
716 // Constructor.
padthv1_jack_application(int & argc,char ** argv)717 padthv1_jack_application::padthv1_jack_application ( int& argc, char **argv )
718 : QObject(nullptr), m_pApp(nullptr), m_bGui(true),
719 m_sClientName(PADTHV1_TITLE), m_pSynth(nullptr), m_pWidget(nullptr)
720 #ifdef CONFIG_NSM
721 , m_pNsmClient(nullptr)
722 #endif
723 {
724 #ifdef Q_WS_X11
725 m_bGui = (::getenv("DISPLAY") != 0);
726 #endif
727 for (int i = 1; i < argc; ++i) {
728 const QString& sArg
729 = QString::fromLocal8Bit(argv[i]);
730 if (sArg == "-g" || sArg == "--no-gui")
731 m_bGui = false;
732 }
733
734 if (m_bGui) {
735 #if defined(Q_OS_LINUX)
736 ::setenv("QT_QPA_PLATFORM", "xcb", 0);
737 #endif
738 #if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
739 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
740 QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
741 #endif
742 #endif
743 QApplication *pApp = new QApplication(argc, argv);
744 #if QT_VERSION >= QT_VERSION_CHECK(5, 1, 0)
745 pApp->setApplicationDisplayName(PADTHV1_TITLE);
746 // PADTHV1_TITLE " - " + QObject::tr(PADTHV1_SUBTITLE));
747 #endif
748 m_pApp = pApp;
749 } else {
750 m_pApp = new QCoreApplication(argc, argv);
751 }
752
753 #if QT_VERSION >= QT_VERSION_CHECK(5, 1, 0)
754 m_pApp->setApplicationName(PADTHV1_TITLE);
755 #endif
756
757 #ifdef HAVE_SIGNAL_H
758
759 // Set to ignore any fatal "Broken pipe" signals.
760 ::signal(SIGPIPE, SIG_IGN);
761
762 // Initialize file descriptors for SIGTERM socket notifier.
763 ::socketpair(AF_UNIX, SOCK_STREAM, 0, g_fdSigterm);
764 m_pSigtermNotifier
765 = new QSocketNotifier(g_fdSigterm[1], QSocketNotifier::Read, this);
766
767 QObject::connect(m_pSigtermNotifier,
768 SIGNAL(activated(int)),
769 SLOT(handle_sigterm()));
770
771 // Install SIGTERM signal handler.
772 struct sigaction sigterm;
773 sigterm.sa_handler = padthv1_sigterm_handler;
774 sigemptyset(&sigterm.sa_mask);
775 sigterm.sa_flags = 0;
776 sigterm.sa_flags |= SA_RESTART;
777 ::sigaction(SIGTERM, &sigterm, nullptr);
778 ::sigaction(SIGQUIT, &sigterm, nullptr);
779
780 // Ignore SIGHUP/SIGINT signals.
781 ::signal(SIGHUP, SIG_IGN);
782 ::signal(SIGINT, SIG_IGN);
783
784 #else
785
786 m_pSigtermNotifier = nullptr;
787
788 #endif // !HAVE_SIGNAL_H
789
790 // Pseudo-singleton instance.
791 g_pInstance = this;
792 }
793
794
795 // Destructor.
~padthv1_jack_application(void)796 padthv1_jack_application::~padthv1_jack_application (void)
797 {
798 g_pInstance = nullptr;
799
800 #ifdef HAVE_SIGNAL_H
801 if (m_pSigtermNotifier) delete m_pSigtermNotifier;
802 #endif
803 #ifdef CONFIG_NSM
804 if (m_pNsmClient) delete m_pNsmClient;
805 #endif
806 if (m_pWidget) delete m_pWidget;
807 if (m_pSynth) delete m_pSynth;
808 if (m_pApp) delete m_pApp;
809 }
810
811
812 // Argument parser method.
parse_args(void)813 bool padthv1_jack_application::parse_args (void)
814 {
815 QTextStream out(stderr);
816 const QStringList& args = m_pApp->arguments();
817 const int argc = args.count();
818
819 for (int i = 1; i < argc; ++i) {
820
821 QString sArg = args.at(i);
822
823 QString sVal;
824 const int iEqual = sArg.indexOf('=');
825 if (iEqual >= 0) {
826 sVal = sArg.right(sArg.length() - iEqual - 1);
827 sArg = sArg.left(iEqual);
828 }
829 else if (i < argc - 1) {
830 sVal = args.at(i + 1);
831 if (sVal.at(0) == '-')
832 sVal.clear();
833 }
834
835 if (sArg == "-n" || sArg == "--client-name") {
836 if (sVal.isNull()) {
837 out << QObject::tr("Option -n requires an argument (client-name).\n\n");
838 return false;
839 }
840 m_sClientName = sVal;
841 if (iEqual < 0)
842 ++i;
843 }
844 else
845 if (sArg == "-h" || sArg == "--help") {
846 out << QObject::tr(
847 "Usage: %1 [options] [preset-file]\n\n"
848 PADTHV1_TITLE " - " PADTHV1_SUBTITLE "\n\n"
849 "Options:\n\n"
850 " -g, --no-gui\n\tDisable the graphical user interface (GUI)\n\n"
851 " -n, --client-name=[label]\n\tSet the JACK client name (default: padthv1)\n\n"
852 " -h, --help\n\tShow help about command line options\n\n"
853 " -v, --version\n\tShow version information\n\n")
854 .arg(args.at(0));
855 return false;
856 }
857 else
858 if (sArg == "-v" || sArg == "-V" || sArg == "--version") {
859 out << QString("Qt: %1").arg(qVersion());
860 #if defined(QT_STATIC)
861 out << "-static";
862 #endif
863 out << '\n';
864 out << QString("%1: %2\n")
865 .arg(PADTHV1_TITLE)
866 .arg(CONFIG_BUILD_VERSION);
867 return false;
868 }
869 else {
870 // If we don't have one by now,
871 // this will be the startup preset file...
872 m_presets.append(sArg);
873 }
874 }
875
876 return true;
877 }
878
879
880 // Startup methods.
setup(void)881 bool padthv1_jack_application::setup (void)
882 {
883 if (m_pApp == nullptr)
884 return false;
885
886 if (!parse_args()) {
887 m_pApp->quit();
888 return false;
889 }
890
891 QObject::connect(this,
892 SIGNAL(shutdown_signal()),
893 SLOT(shutdown_slot()));
894
895 const QByteArray aClientName
896 = m_sClientName.toLocal8Bit();
897 const char *client_name
898 = aClientName.constData();
899
900 m_pSynth = new padthv1_jack(client_name);
901
902 if (m_bGui) {
903 m_pWidget = new padthv1widget_jack(m_pSynth);
904 // m_pWidget->show();
905 if (m_presets.isEmpty())
906 m_pWidget->initPreset();
907 else
908 m_pWidget->loadPreset(m_presets.first());
909 }
910 else
911 if (!m_presets.isEmpty())
912 padthv1_param::loadPreset(m_pSynth, m_presets.first());
913
914 #ifdef CONFIG_NSM
915 // Check whether to participate into a NSM session...
916 const QString& nsm_url
917 = QString::fromLatin1(::getenv("NSM_URL"));
918 if (!nsm_url.isEmpty()) {
919 m_pNsmClient = new padthv1_nsm(nsm_url);
920 QObject::connect(m_pNsmClient,
921 SIGNAL(open()),
922 SLOT(openSession()));
923 QObject::connect(m_pNsmClient,
924 SIGNAL(save()),
925 SLOT(saveSession()));
926 QObject::connect(m_pNsmClient,
927 SIGNAL(show()),
928 SLOT(showSession()));
929 QObject::connect(m_pNsmClient,
930 SIGNAL(hide()),
931 SLOT(hideSession()));
932 QString caps(":switch:dirty:");
933 if (m_bGui)
934 caps += "optional-gui:";
935 m_pNsmClient->announce(PADTHV1_TITLE, caps.toLocal8Bit().constData());
936 if (m_pWidget)
937 m_pWidget->setNsmClient(m_pNsmClient);
938 }
939 else
940 #endif // CONFIG_NSM
941 if (m_pWidget)
942 m_pWidget->show();
943
944 return true;
945 }
946
947
948 // Facade method.
exec(void)949 int padthv1_jack_application::exec (void)
950 {
951 return (setup() ? m_pApp->exec() : 1);
952 }
953
954
955 #ifdef CONFIG_NSM
956
openSession(void)957 void padthv1_jack_application::openSession (void)
958 {
959 if (m_pSynth == nullptr)
960 return;
961
962 if (m_pNsmClient == nullptr)
963 return;
964
965 if (!m_pNsmClient->is_active())
966 return;
967
968 #ifdef CONFIG_DEBUG
969 qDebug("padthv1_jack::openSession()");
970 #endif
971
972 m_pSynth->deactivate();
973 m_pSynth->close();
974
975 const QString& client_name = m_pNsmClient->client_name();
976 const QString& path_name = m_pNsmClient->path_name();
977 const QString& display_name = m_pNsmClient->display_name();
978
979 m_pSynth->open(client_name.toUtf8().constData());
980 m_pSynth->activate();
981
982 const QDir dir(path_name);
983 if (!dir.exists())
984 dir.mkpath(path_name);
985
986 QFileInfo fi(path_name, "session." PADTHV1_TITLE);
987 if (!fi.exists())
988 fi.setFile(path_name, display_name + '.' + PADTHV1_TITLE);
989 if (fi.exists()) {
990 const QString& sFilename = fi.absoluteFilePath();
991 if (m_pWidget) {
992 m_pWidget->loadPreset(sFilename);
993 } else {
994 padthv1_param::loadPreset(m_pSynth, sFilename);
995 }
996 }
997
998 m_pNsmClient->open_reply();
999 m_pNsmClient->dirty(false);
1000
1001 if (m_pWidget)
1002 m_pNsmClient->visible(m_pWidget->isVisible());
1003 }
1004
saveSession(void)1005 void padthv1_jack_application::saveSession (void)
1006 {
1007 if (m_pSynth == nullptr)
1008 return;
1009
1010 if (m_pNsmClient == nullptr)
1011 return;
1012
1013 if (!m_pNsmClient->is_active())
1014 return;
1015
1016 #ifdef CONFIG_DEBUG
1017 qDebug("padthv1_jack::saveSession()");
1018 #endif
1019
1020 // const QString& client_name = m_pNsmClient->client_name();
1021 const QString& path_name = m_pNsmClient->path_name();
1022 // const QString& display_name = m_pNsmClient->display_name();
1023 // const QFileInfo fi(path_name, display_name + '.' + PADTHV1_TITLE);
1024 const QFileInfo fi(path_name, "session." PADTHV1_TITLE);
1025
1026 padthv1_param::savePreset(m_pSynth, fi.absoluteFilePath(), true);
1027
1028 m_pNsmClient->save_reply();
1029 m_pNsmClient->dirty(false);
1030 }
1031
1032
showSession(void)1033 void padthv1_jack_application::showSession (void)
1034 {
1035 if (m_pNsmClient == nullptr)
1036 return;
1037
1038 if (!m_pNsmClient->is_active())
1039 return;
1040
1041 #ifdef CONFIG_DEBUG
1042 qDebug("padthv1_jack::showSession()");
1043 #endif
1044
1045 if (m_pWidget) {
1046 m_pWidget->show();
1047 m_pWidget->raise();
1048 m_pWidget->activateWindow();
1049 }
1050 }
1051
hideSession(void)1052 void padthv1_jack_application::hideSession (void)
1053 {
1054 if (m_pNsmClient == nullptr)
1055 return;
1056
1057 if (!m_pNsmClient->is_active())
1058 return;
1059
1060 #ifdef CONFIG_DEBUG
1061 qDebug("padthv1_jack::hideSession()");
1062 #endif
1063
1064 if (m_pWidget)
1065 m_pWidget->hide();
1066 }
1067
1068
1069 #endif // CONFIG_NSM
1070
1071
1072 #ifdef HAVE_SIGNAL_H
1073
1074 // SIGTERM signal handler.
handle_sigterm(void)1075 void padthv1_jack_application::handle_sigterm (void)
1076 {
1077 char c;
1078
1079 if (::read(g_fdSigterm[1], &c, sizeof(c)) > 0) {
1080 if (m_pApp && m_pWidget) {
1081 #ifdef CONFIG_NSM
1082 if (m_pNsmClient && m_pNsmClient->is_active())
1083 m_pWidget->updateDirtyPreset(false);
1084 #endif
1085 if (m_pWidget->queryClose())
1086 m_pApp->quit();
1087 }
1088 }
1089 }
1090
1091 #endif // HAVE_SIGNAL_H
1092
1093
1094 // JACK shutdown handlers.
shutdown(void)1095 void padthv1_jack_application::shutdown (void)
1096 {
1097 emit shutdown_signal();
1098 }
1099
1100
shutdown_slot(void)1101 void padthv1_jack_application::shutdown_slot (void)
1102 {
1103 bool bQuit = true;
1104
1105 if (m_pSynth)
1106 m_pSynth->shutdown_close();
1107
1108 if (m_pWidget)
1109 bQuit = m_pWidget->queryClose();
1110
1111 if (m_pApp && bQuit)
1112 m_pApp->quit();
1113 }
1114
1115
1116 // Pseudo-singleton instance.
1117 padthv1_jack_application *padthv1_jack_application::g_pInstance = nullptr;
1118
getInstance(void)1119 padthv1_jack_application *padthv1_jack_application::getInstance (void)
1120 {
1121 return g_pInstance;
1122 }
1123
1124
1125 //-------------------------------------------------------------------------
1126 // main
1127
main(int argc,char * argv[])1128 int main ( int argc, char *argv[] )
1129 {
1130 Q_INIT_RESOURCE(padthv1);
1131
1132 padthv1_jack_application app(argc, argv);
1133
1134 return app.exec();
1135 }
1136
1137
1138 // end of padthv1_jack.cpp
1139
1140