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