1 /* -*- c-basic-offset: 4 -*-  vi:set ts=8 sts=4 sw=4: */
2 
3 /* trivial_sampler_qt_gui.cpp
4 
5    DSSI Soft Synth Interface
6    Constructed by Chris Cannam, Steve Harris and Sean Bolton
7 
8    A straightforward DSSI plugin sampler: Qt GUI.
9 
10    This example file is in the public domain.
11 */
12 
13 #include "trivial_sampler_qt_gui.h"
14 #include "trivial_sampler.h"
15 
16 #include <QApplication>
17 #include <QDesktopWidget>
18 #include <QPushButton>
19 #include <QTimer>
20 #include <QFileDialog>
21 #include <QMessageBox>
22 #include <QPixmap>
23 #include <QPainter>
24 #include <QGroupBox>
25 #include <QTextStream>
26 #include <cstdlib>
27 #include <iostream>
28 #include <unistd.h>
29 #include <math.h>
30 #include <sndfile.h>
31 
32 #include "dssi.h"
33 
34 #ifdef Q_WS_X11
35 #include <X11/Xlib.h>
36 #include <X11/Xutil.h>
37 #include <X11/Xatom.h>
38 #include <X11/SM/SMlib.h>
39 
handle_x11_error(Display * dpy,XErrorEvent * err)40 static int handle_x11_error(Display *dpy, XErrorEvent *err)
41 {
42     char errstr[256];
43     XGetErrorText(dpy, err->error_code, errstr, 256);
44     if (err->error_code != BadWindow) {
45 	std::cerr << "trivial_sampler_qt_gui: X Error: "
46 		  << errstr << " " << err->error_code
47 		  << "\nin major opcode:  " << err->request_code << std::endl;
48     }
49     return 0;
50 }
51 #endif
52 
53 using std::endl;
54 
55 lo_server osc_server = 0;
56 
57 static QTextStream cerr(stderr);
58 
59 #define NO_SAMPLE_TEXT "<none loaded>   "
60 
SamplerGUI(bool stereo,const char * host,const char * port,QByteArray controlPath,QByteArray midiPath,QByteArray configurePath,QByteArray exitingPath,QWidget * w)61 SamplerGUI::SamplerGUI(bool stereo, const char * host, const char * port,
62 		       QByteArray controlPath, QByteArray midiPath, QByteArray configurePath,
63 		       QByteArray exitingPath, QWidget *w) :
64     QFrame(w),
65     m_controlPath(controlPath),
66     m_midiPath(midiPath),
67     m_configurePath(configurePath),
68     m_exitingPath(exitingPath),
69     m_previewWidth(200),
70     m_previewHeight(40),
71     m_suppressHostUpdate(true),
72     m_hostRequestedQuit(false),
73     m_ready(false)
74 {
75     m_host = lo_address_new(host, port);
76 
77     QGridLayout *layout = new QGridLayout(this);
78 
79     QGroupBox *sampleBox = new QGroupBox("Sample", this);
80     layout->addWidget(sampleBox, 0, 0, 1, 2);
81 
82     QGridLayout *sampleLayout = new QGridLayout(sampleBox);
83 
84     sampleLayout->addWidget(new QLabel("File:  "), 0, 0);
85 
86     m_sampleFile = new QLabel(NO_SAMPLE_TEXT);
87     m_sampleFile->setFrameStyle(QFrame::Box | QFrame::Plain);
88     sampleLayout->addWidget(m_sampleFile, 0, 1, 1, 3);
89 
90     m_duration = new QLabel("0.00 sec");
91     sampleLayout->addWidget(m_duration, 2, 1, Qt::AlignLeft);
92     m_sampleRate = new QLabel;
93     sampleLayout->addWidget(m_sampleRate, 2, 2, Qt:: AlignCenter);
94     m_channels = new QLabel;
95     sampleLayout->addWidget(m_channels, 2, 3, Qt::AlignRight);
96 
97     QPixmap pmap(m_previewWidth, m_previewHeight);
98     pmap.fill();
99     m_preview = new QLabel;
100     m_preview->setFrameStyle(QFrame::Box | QFrame::Plain);
101     m_preview->setAlignment(Qt::AlignCenter);
102     m_preview->setPixmap(pmap);
103     sampleLayout->addWidget(m_preview, 1, 1, 1, 3);
104 
105     QPushButton *loadButton = new QPushButton(" ... ");
106     sampleLayout->addWidget(loadButton, 0, 5);
107     connect(loadButton, SIGNAL(pressed()), this, SLOT(fileSelect()));
108 
109     QPushButton *testButton = new QPushButton("Test");
110     connect(testButton, SIGNAL(pressed()), this, SLOT(test_press()));
111     connect(testButton, SIGNAL(released()), this, SLOT(test_release()));
112     sampleLayout->addWidget(testButton, 1, 5);
113 
114     if (stereo) {
115 	m_balanceLabel = new QLabel("Balance:  ");
116 	sampleLayout->addWidget(m_balanceLabel, 3, 0);
117     m_balance = new QSlider();
118     m_balance->setMinimum(-100);
119     m_balance->setMaximum(100);
120     m_balance->setPageStep(25);
121     m_balance->setValue(0);
122     m_balance->setOrientation(Qt::Horizontal);
123     m_balance->setTickPosition(QSlider::TicksBelow);
124 
125     sampleLayout->addWidget(m_balance, 3, 1, 1, 3);
126 
127 	connect(m_balance, SIGNAL(valueChanged(int)), this, SLOT(balanceChanged(int)));
128     } else {
129 	m_balance = 0;
130 	m_balanceLabel = 0;
131     }
132 
133     QGroupBox *tuneBox = new QGroupBox("Tuned playback");
134     layout->addWidget(tuneBox, 1, 0);
135 
136     QGridLayout *tuneLayout = new QGridLayout(tuneBox);
137 
138     m_retune = new QCheckBox("Enable");
139     m_retune->setChecked(true);
140     tuneLayout->addWidget(m_retune, 0, 0, Qt::AlignLeft);
141     connect(m_retune, SIGNAL(toggled(bool)), this, SLOT(retuneChanged(bool)));
142 
143     tuneLayout->addWidget(new QLabel("Base pitch: "), 1, 0);
144 
145     m_basePitch = new QSpinBox;
146     m_basePitch->setMinimum(0);
147     m_basePitch->setMaximum(120);
148     m_basePitch->setValue(60);
149     tuneLayout->addWidget(m_basePitch, 1, 1);
150     connect(m_basePitch, SIGNAL(valueChanged(int)), this, SLOT(basePitchChanged(int)));
151 
152     QGroupBox *noteOffBox = new QGroupBox("Note Off");
153     layout->addWidget(noteOffBox, 1, 1);
154 
155     QGridLayout *noteOffLayout = new QGridLayout(noteOffBox);
156 
157     m_sustain = new QCheckBox("Enable");
158     m_sustain->setChecked(true);
159     noteOffLayout->addWidget(m_sustain, 0, 0, Qt::AlignLeft);
160     connect(m_sustain, SIGNAL(toggled(bool)), this, SLOT(sustainChanged(bool)));
161 
162     noteOffLayout->addWidget(new QLabel("Release: "), 1, 0);
163 
164     m_release = new QSpinBox;
165     m_release->setMinimum(0);
166     m_release->setMaximum(int(Sampler_RELEASE_MAX * 1000));
167     m_release->setValue(0);
168     m_release->setSuffix("ms");
169     m_release->setSingleStep(10);
170     noteOffLayout->addWidget(m_release, 1, 1);
171     connect(m_release, SIGNAL(valueChanged(int)), this, SLOT(releaseChanged(int)));
172 
173     // cause some initial updates
174     retuneChanged     (m_retune    ->isChecked());
175     basePitchChanged  (m_basePitch ->value());
176     sustainChanged    (m_sustain   ->isChecked());
177     releaseChanged    (m_release   ->value());
178     if (stereo) {
179 	balanceChanged(m_balance   ->value());
180     }
181 
182     QTimer *myTimer = new QTimer(this);
183     connect(myTimer, SIGNAL(timeout()), this, SLOT(oscRecv()));
184     myTimer->setSingleShot(false);
185     myTimer->start(0);
186 
187     m_suppressHostUpdate = false;
188 }
189 
190 void
generatePreview(QString path)191 SamplerGUI::generatePreview(QString path)
192 {
193     SF_INFO info;
194     SNDFILE *file;
195     QPixmap pmap(m_previewWidth, m_previewHeight);
196     pmap.fill();
197 
198     info.format = 0;
199     file = sf_open(path.toLocal8Bit(), SFM_READ, &info);
200 
201     if (file && info.frames > 0) {
202 
203 	float binSize = (float)info.frames / m_previewWidth;
204 	float peak[2] = { 0.0f, 0.0f }, mean[2] = { 0.0f, 0.0f };
205 	float *frame = (float *)malloc(info.channels * sizeof(float));
206 	int bin = 0;
207 
208 	QPainter paint(&pmap);
209 
210 	for (size_t i = 0; i < info.frames; ++i) {
211 
212 	    sf_readf_float(file, frame, 1);
213 
214 	    if (fabs(frame[0]) > peak[0]) peak[0] = fabs(frame[0]);
215 	    mean[0] += fabs(frame[0]);
216 
217 	    if (info.channels > 1) {
218 		if (fabs(frame[1]) > peak[1]) peak[1] = fabs(frame[1]);
219 		mean[1] += fabs(frame[1]);
220 	    }
221 
222 	    if (i == size_t((bin + 1) * binSize)) {
223 
224 		float silent = 1.0 / float(m_previewHeight);
225 
226 		if (info.channels == 1) {
227 		    mean[1] = mean[0];
228 		    peak[1] = peak[0];
229 		}
230 
231 		mean[0] /= binSize;
232 		mean[1] /= binSize;
233 
234 		int m = m_previewHeight / 2;
235 
236 		paint.setPen(Qt::black);
237 		paint.drawLine(bin, m, bin, int(m - m * peak[0]));
238 		if (peak[0] > silent && peak[1] > silent) {
239 		    paint.drawLine(bin, m, bin, int(m + m * peak[1]));
240 		}
241 
242 		paint.setPen(Qt::gray);
243 		paint.drawLine(bin, m, bin, int(m - m * mean[0]));
244 		if (mean[0] > silent && mean[1] > silent) {
245 		    paint.drawLine(bin, m, bin, int(m + m * mean[1]));
246 		}
247 
248 		paint.setPen(Qt::black);
249 		paint.drawPoint(bin, int(m - m * peak[0]));
250 		if (peak[0] > silent && peak[1] > silent) {
251 		    paint.drawPoint(bin, int(m + m * peak[1]));
252 		}
253 
254 		mean[0] = mean[1] = 0.0f;
255 		peak[0] = peak[1] = 0.0f;
256 
257 		++bin;
258 	    }
259 	}
260 
261 	int duration = int(100.0 * float(info.frames) / float(info.samplerate));
262 	std::cout << "duration " << duration << std::endl;
263 	m_duration->setText(QString("%1.%2%3 sec")
264 			    .arg(duration / 100)
265 			    .arg((duration / 10) % 10)
266 			    .arg((duration % 10)));
267 	m_sampleRate->setText(QString("%1 Hz")
268 			      .arg(info.samplerate));
269 	m_channels->setText(info.channels > 1 ? (m_balance ? "stereo" : "stereo (to mix)") : "mono");
270 	if (m_balanceLabel) {
271 	    m_balanceLabel->setText(info.channels == 1 ? "Pan:  " : "Balance:  ");
272 	}
273 
274     } else {
275 	m_duration->setText("0.00 sec");
276 	m_sampleRate->setText("");
277 	m_channels->setText("");
278     }
279 
280     if (file) sf_close(file);
281 
282     m_preview->setPixmap(pmap);
283 
284 }
285 
286 void
setProjectDirectory(QString dir)287 SamplerGUI::setProjectDirectory(QString dir)
288 {
289     QFileInfo info(dir);
290     if (info.exists() && info.isDir() && info.isReadable()) {
291 	m_projectDir = dir;
292     }
293 }
294 
295 void
setSampleFile(QString file)296 SamplerGUI::setSampleFile(QString file)
297 {
298     m_suppressHostUpdate = true;
299     m_sampleFile->setText(QFileInfo(file).fileName());
300     m_file = file;
301     generatePreview(file);
302     m_suppressHostUpdate = false;
303 }
304 
305 void
setRetune(bool retune)306 SamplerGUI::setRetune(bool retune)
307 {
308     m_suppressHostUpdate = true;
309     m_retune->setChecked(retune);
310     m_basePitch->setEnabled(retune);
311     m_suppressHostUpdate = false;
312 }
313 
314 void
setBasePitch(int pitch)315 SamplerGUI::setBasePitch(int pitch)
316 {
317     m_suppressHostUpdate = true;
318     m_basePitch->setValue(pitch);
319     m_suppressHostUpdate = false;
320 }
321 
322 void
setSustain(bool sustain)323 SamplerGUI::setSustain(bool sustain)
324 {
325     m_suppressHostUpdate = true;
326     m_sustain->setChecked(sustain);
327     m_release->setEnabled(sustain);
328     m_suppressHostUpdate = false;
329 }
330 
331 void
setRelease(int ms)332 SamplerGUI::setRelease(int ms)
333 {
334     m_suppressHostUpdate = true;
335     m_release->setValue(ms);
336     m_suppressHostUpdate = false;
337 }
338 
339 void
setBalance(int balance)340 SamplerGUI::setBalance(int balance)
341 {
342     m_suppressHostUpdate = true;
343     if (m_balance) {
344 	m_balance->setValue(balance);
345     }
346     m_suppressHostUpdate = false;
347 }
348 
349 void
retuneChanged(bool retune)350 SamplerGUI::retuneChanged(bool retune)
351 {
352     if (!m_suppressHostUpdate) {
353 	lo_send(m_host, m_controlPath, "if", Sampler_RETUNE, retune ? 1.0 : 0.0);
354     }
355     m_basePitch->setEnabled(retune);
356 }
357 
358 void
basePitchChanged(int value)359 SamplerGUI::basePitchChanged(int value)
360 {
361     if (!m_suppressHostUpdate) {
362 	lo_send(m_host, m_controlPath, "if", Sampler_BASE_PITCH, (float)value);
363     }
364 }
365 
366 void
sustainChanged(bool on)367 SamplerGUI::sustainChanged(bool on)
368 {
369     if (!m_suppressHostUpdate) {
370 	lo_send(m_host, m_controlPath, "if", Sampler_SUSTAIN, on ? 0.0 : 1.0);
371     }
372     m_release->setEnabled(on);
373 }
374 
375 void
releaseChanged(int release)376 SamplerGUI::releaseChanged(int release)
377 {
378     if (!m_suppressHostUpdate) {
379 	float v = (float)release / 1000.0;
380 	if (v < Sampler_RELEASE_MIN) v = Sampler_RELEASE_MIN;
381 	lo_send(m_host, m_controlPath, "if", Sampler_RELEASE, v);
382     }
383 }
384 
385 void
balanceChanged(int balance)386 SamplerGUI::balanceChanged(int balance)
387 {
388     if (!m_suppressHostUpdate) {
389 	float v = (float)balance / 100.0;
390 	lo_send(m_host, m_controlPath, "if", Sampler_BALANCE, v);
391     }
392 }
393 
394 void
fileSelect()395 SamplerGUI::fileSelect()
396 {
397     QString orig = m_file;
398     if (orig.isEmpty()) {
399 	if (!m_projectDir.isEmpty()) {
400 	    orig = m_projectDir;
401 	} else {
402 	    orig = ".";
403 	}
404     }
405 
406     QString path = QFileDialog::getOpenFileName
407         (this, "Select an audio sample file", orig, "Audio files (*.wav *.aiff)");
408 
409     if (!path.isEmpty()) {
410 
411 	SF_INFO info;
412 	SNDFILE *file;
413 
414 	info.format = 0;
415 	file = sf_open(path.toLocal8Bit(), SFM_READ, &info);
416 
417 	if (!file) {
418 	    QMessageBox::warning
419 		(this, "Couldn't load audio file",
420 		 QString("Couldn't load audio sample file '%1'").arg(path),
421 		 QMessageBox::Ok, 0);
422 	    return;
423 	}
424 
425 	if (info.frames > Sampler_FRAMES_MAX) {
426 	    QMessageBox::warning
427 		(this, "Couldn't use audio file",
428 		 QString("Audio sample file '%1' is too large (%2 frames, maximum is %3)").arg(path).arg((int)info.frames).arg(Sampler_FRAMES_MAX),
429 		 QMessageBox::Ok, 0);
430 	    sf_close(file);
431 	    return;
432 	} else {
433 	    sf_close(file);
434 	    lo_send(m_host, m_configurePath, "ss", "load", path.toLocal8Bit().data());
435 	    setSampleFile(path);
436 	}
437     }
438 }
439 
440 void
test_press()441 SamplerGUI::test_press()
442 {
443     unsigned char noteon[4] = { 0x00, 0x90, 0x3C, 60 };
444 
445     lo_send(m_host, m_midiPath, "m", noteon);
446 }
447 
448 void
oscRecv()449 SamplerGUI::oscRecv()
450 {
451     if (osc_server) {
452 	lo_server_recv_noblock(osc_server, 1);
453     }
454 }
455 
456 void
test_release()457 SamplerGUI::test_release()
458 {
459     unsigned char noteoff[4] = { 0x00, 0x90, 0x3C, 0x00 };
460 
461     lo_send(m_host, m_midiPath, "m", noteoff);
462 }
463 
464 void
aboutToQuit()465 SamplerGUI::aboutToQuit()
466 {
467     if (!m_hostRequestedQuit) lo_send(m_host, m_exitingPath, "");
468 }
469 
~SamplerGUI()470 SamplerGUI::~SamplerGUI()
471 {
472     lo_address_free(m_host);
473 }
474 
475 
476 void
osc_error(int num,const char * msg,const char * path)477 osc_error(int num, const char *msg, const char *path)
478 {
479     cerr << "Error: liblo server error " << num
480 	 << " in path \"" << (path ? path : "(null)")
481 	 << "\": " << msg << endl;
482 }
483 
484 int
debug_handler(const char * path,const char * types,lo_arg ** argv,int argc,void * data,void * user_data)485 debug_handler(const char *path, const char *types, lo_arg **argv,
486 	      int argc, void *data, void *user_data)
487 {
488     int i;
489 
490     cerr << "Warning: unhandled OSC message in GUI:" << endl;
491 
492     for (i = 0; i < argc; ++i) {
493 	cerr << "arg " << i << ": type '" << types[i] << "': ";
494         lo_arg_pp((lo_type)types[i], argv[i]);
495 	cerr << endl;
496     }
497 
498     cerr << "(path is <" << path << ">)" << endl;
499     return 1;
500 }
501 
502 int
configure_handler(const char * path,const char * types,lo_arg ** argv,int argc,void * data,void * user_data)503 configure_handler(const char *path, const char *types, lo_arg **argv,
504 		  int argc, void *data, void *user_data)
505 {
506     SamplerGUI *gui = static_cast<SamplerGUI *>(user_data);
507     const char *key = (const char *)&argv[0]->s;
508     const char *value = (const char *)&argv[1]->s;
509 
510     if (!strcmp(key, "load")) {
511 	gui->setSampleFile(QString::fromLocal8Bit(value));
512     } else if (!strcmp(key, DSSI_PROJECT_DIRECTORY_KEY)) {
513 	gui->setProjectDirectory(QString::fromLocal8Bit(value));
514     }
515 
516     return 0;
517 }
518 
519 int
rate_handler(const char * path,const char * types,lo_arg ** argv,int argc,void * data,void * user_data)520 rate_handler(const char *path, const char *types, lo_arg **argv,
521 	     int argc, void *data, void *user_data)
522 {
523     return 0;
524 }
525 
526 int
show_handler(const char * path,const char * types,lo_arg ** argv,int argc,void * data,void * user_data)527 show_handler(const char *path, const char *types, lo_arg **argv,
528 	     int argc, void *data, void *user_data)
529 {
530     SamplerGUI *gui = static_cast<SamplerGUI *>(user_data);
531     while (!gui->ready()) sleep(1);
532     if (gui->isVisible()) gui->raise();
533     else {
534 	QRect geometry = gui->geometry();
535 	QPoint p(QApplication::desktop()->width()/2 - geometry.width()/2,
536 		 QApplication::desktop()->height()/2 - geometry.height()/2);
537 	gui->move(p);
538 	gui->show();
539     }
540 
541     return 0;
542 }
543 
544 int
hide_handler(const char * path,const char * types,lo_arg ** argv,int argc,void * data,void * user_data)545 hide_handler(const char *path, const char *types, lo_arg **argv,
546 	     int argc, void *data, void *user_data)
547 {
548     SamplerGUI *gui = static_cast<SamplerGUI *>(user_data);
549     gui->hide();
550     return 0;
551 }
552 
553 int
quit_handler(const char * path,const char * types,lo_arg ** argv,int argc,void * data,void * user_data)554 quit_handler(const char *path, const char *types, lo_arg **argv,
555 	     int argc, void *data, void *user_data)
556 {
557     SamplerGUI *gui = static_cast<SamplerGUI *>(user_data);
558     gui->setHostRequestedQuit(true);
559     qApp->quit();
560     return 0;
561 }
562 
563 int
control_handler(const char * path,const char * types,lo_arg ** argv,int argc,void * data,void * user_data)564 control_handler(const char *path, const char *types, lo_arg **argv,
565 		int argc, void *data, void *user_data)
566 {
567     SamplerGUI *gui = static_cast<SamplerGUI *>(user_data);
568 
569     if (argc < 2) {
570 	cerr << "Error: too few arguments to control_handler" << endl;
571 	return 1;
572     }
573 
574     const int port = argv[0]->i;
575     const float value = argv[1]->f;
576 
577     switch (port) {
578 
579     case Sampler_RETUNE:
580 	gui->setRetune(value < 0.001f ? false : true);
581 	break;
582 
583     case Sampler_BASE_PITCH:
584 	gui->setBasePitch((int)value);
585 	break;
586 
587     case Sampler_SUSTAIN:
588 	gui->setSustain(value < 0.001f ? true : false);
589 	break;
590 
591     case Sampler_RELEASE:
592 	gui->setRelease(value < (Sampler_RELEASE_MIN + 0.000001f) ?
593 			0 : (int)(value * 1000.0 + 0.5));
594 	break;
595 
596     case Sampler_BALANCE:
597 	gui->setBalance((int)(value * 100.0));
598 	break;
599 
600     default:
601 	cerr << "Warning: received request to set nonexistent port " << port << endl;
602     }
603 
604     return 0;
605 }
606 
607 int
main(int argc,char ** argv)608 main(int argc, char **argv)
609 {
610     cerr << "trivial_sampler_qt_gui starting..." << endl;
611 
612     QApplication application(argc, argv);
613 
614     if (application.argc() != 5) {
615 	cerr << "usage: "
616 	     << application.argv()[0]
617 	     << " <osc url>"
618 	     << " <plugin dllname>"
619 	     << " <plugin label>"
620 	     << " <user-friendly id>"
621 	     << endl;
622 	return 2;
623     }
624 
625 #ifdef Q_WS_X11
626     XSetErrorHandler(handle_x11_error);
627 #endif
628 
629     char *url = application.argv()[1];
630 
631     char *host = lo_url_get_hostname(url);
632     char *port = lo_url_get_port(url);
633     char *path = lo_url_get_path(url);
634 
635     char *label = application.argv()[3];
636     bool stereo = false;
637     if (QString(label).toLower() == QString(Sampler_Stereo_LABEL).toLower()) {
638 	stereo = true;
639     }
640 
641     SamplerGUI gui(stereo, host, port,
642 		   QByteArray(path) + "/control",
643 		   QByteArray(path) + "/midi",
644 		   QByteArray(path) + "/configure",
645 		   QByteArray(path) + "/exiting",
646 		   0);
647 
648     QByteArray myControlPath = QByteArray(path) + "/control";
649     QByteArray myConfigurePath = QByteArray(path) + "/configure";
650     QByteArray myRatePath = QByteArray(path) + "/sample-rate";
651     QByteArray myShowPath = QByteArray(path) + "/show";
652     QByteArray myHidePath = QByteArray(path) + "/hide";
653     QByteArray myQuitPath = QByteArray(path) + "/quit";
654 
655     osc_server = lo_server_new(NULL, osc_error);
656     lo_server_add_method(osc_server, myControlPath, "if", control_handler, &gui);
657     lo_server_add_method(osc_server, myConfigurePath, "ss", configure_handler, &gui);
658     lo_server_add_method(osc_server, myRatePath, "i", rate_handler, &gui);
659     lo_server_add_method(osc_server, myShowPath, "", show_handler, &gui);
660     lo_server_add_method(osc_server, myHidePath, "", hide_handler, &gui);
661     lo_server_add_method(osc_server, myQuitPath, "", quit_handler, &gui);
662     lo_server_add_method(osc_server, NULL, NULL, debug_handler, &gui);
663 
664     lo_address hostaddr = lo_address_new(host, port);
665     lo_send(hostaddr,
666 	    QByteArray(path) + "/update",
667 	    "s",
668 	    (QByteArray(lo_server_get_url(osc_server)) + QByteArray(path+1)).data());
669 
670     QObject::connect(&application, SIGNAL(aboutToQuit()), &gui, SLOT(aboutToQuit()));
671 
672     gui.setReady(true);
673     return application.exec();
674 }
675 
676