1 /* -*- c-basic-offset: 4 -*-  vi:set ts=8 sts=4 sw=4: */
2 
3 /* less_trivial_synth_qt_gui.cpp
4 
5    DSSI Soft Synth Interface
6    Constructed by Chris Cannam and Steve Harris
7 
8    This is an example Qt GUI for an example DSSI synth plugin.
9 
10    This example file is in the public domain.
11 */
12 
13 #include "less_trivial_synth_qt_gui.h"
14 
15 #include <QApplication>
16 #include <QPushButton>
17 #include <QTextStream>
18 #include <QTimer>
19 #include <iostream>
20 #include <unistd.h>
21 
22 #ifdef Q_WS_X11
23 #include <X11/Xlib.h>
24 #include <X11/Xutil.h>
25 #include <X11/Xatom.h>
26 #include <X11/SM/SMlib.h>
27 
handle_x11_error(Display * dpy,XErrorEvent * err)28 static int handle_x11_error(Display *dpy, XErrorEvent *err)
29 {
30     char errstr[256];
31     XGetErrorText(dpy, err->error_code, errstr, 256);
32     if (err->error_code != BadWindow) {
33 	std::cerr << "less_trivial_synth_qt_gui: X Error: "
34 		  << errstr << " " << err->error_code
35 		  << "\nin major opcode:  " << err->request_code << std::endl;
36     }
37     return 0;
38 }
39 #endif
40 
41 using std::endl;
42 
43 #define LTS_PORT_FREQ    1
44 #define LTS_PORT_ATTACK  2
45 #define LTS_PORT_DECAY   3
46 #define LTS_PORT_SUSTAIN 4
47 #define LTS_PORT_RELEASE 5
48 #define LTS_PORT_TIMBRE  6
49 
50 lo_server osc_server = 0;
51 
52 static QTextStream cerr(stderr);
53 
SynthGUI(const char * host,const char * port,QByteArray controlPath,QByteArray midiPath,QByteArray programPath,QByteArray exitingPath,QWidget * w)54 SynthGUI::SynthGUI(const char * host, const char * port,
55 		   QByteArray controlPath, QByteArray midiPath, QByteArray programPath,
56 		   QByteArray exitingPath, QWidget *w) :
57     QFrame(w),
58     m_controlPath(controlPath),
59     m_midiPath(midiPath),
60     m_programPath(programPath),
61     m_exitingPath(exitingPath),
62     m_suppressHostUpdate(true),
63     m_hostRequestedQuit(false),
64     m_ready(false)
65 {
66     m_host = lo_address_new(host, port);
67 
68     QGridLayout *layout = new QGridLayout(this);
69 
70     m_tuning  = newQDial(100, 600, 10, 400); // (Hz - 400) * 10
71     m_attack  = newQDial(  1, 100,  1,  25); // s * 100
72     m_decay   = newQDial(  1, 100,  1,  25); // s * 100
73     m_sustain = newQDial(  0, 100,  1,  75); // %
74     m_release = newQDial(  1, 400, 10, 200); // s * 100
75     m_timbre  = newQDial(  1, 100,  1,  25); // s * 100
76 
77     m_tuningLabel  = new QLabel(this);
78     m_attackLabel  = new QLabel(this);
79     m_decayLabel   = new QLabel(this);
80     m_sustainLabel = new QLabel(this);
81     m_releaseLabel = new QLabel(this);
82     m_timbreLabel  = new QLabel(this);
83 
84     layout->addWidget(new QLabel("Pitch of A", this), 0, 0, Qt::AlignCenter);
85     layout->addWidget(new QLabel("Attack",     this), 0, 1, Qt::AlignCenter);
86     layout->addWidget(new QLabel("Decay",      this), 0, 2, Qt::AlignCenter);
87     layout->addWidget(new QLabel("Sustain",    this), 0, 3, Qt::AlignCenter);
88     layout->addWidget(new QLabel("Release",    this), 0, 4, Qt::AlignCenter);
89     layout->addWidget(new QLabel("Timbre",     this), 0, 5, Qt::AlignCenter);
90 
91     layout->addWidget(m_tuning,  1, 0);
92     layout->addWidget(m_attack,  1, 1);
93     layout->addWidget(m_decay,   1, 2);
94     layout->addWidget(m_sustain, 1, 3);
95     layout->addWidget(m_release, 1, 4);
96     layout->addWidget(m_timbre,  1, 5);
97 
98     layout->addWidget(m_tuningLabel,  2, 0, Qt::AlignCenter);
99     layout->addWidget(m_attackLabel,  2, 1, Qt::AlignCenter);
100     layout->addWidget(m_decayLabel,   2, 2, Qt::AlignCenter);
101     layout->addWidget(m_sustainLabel, 2, 3, Qt::AlignCenter);
102     layout->addWidget(m_releaseLabel, 2, 4, Qt::AlignCenter);
103     layout->addWidget(m_timbreLabel,  2, 5, Qt::AlignCenter);
104 
105     connect(m_tuning,  SIGNAL(valueChanged(int)), this, SLOT(tuningChanged(int)));
106     connect(m_attack,  SIGNAL(valueChanged(int)), this, SLOT(attackChanged(int)));
107     connect(m_decay,   SIGNAL(valueChanged(int)), this, SLOT(decayChanged(int)));
108     connect(m_sustain, SIGNAL(valueChanged(int)), this, SLOT(sustainChanged(int)));
109     connect(m_release, SIGNAL(valueChanged(int)), this, SLOT(releaseChanged(int)));
110     connect(m_timbre,  SIGNAL(valueChanged(int)), this, SLOT(timbreChanged(int)));
111 
112     // cause some initial updates
113     tuningChanged (m_tuning ->value());
114     attackChanged (m_attack ->value());
115     decayChanged  (m_decay  ->value());
116     sustainChanged(m_sustain->value());
117     releaseChanged(m_release->value());
118     timbreChanged (m_timbre ->value());
119 
120     QPushButton *testButton = new QPushButton("Test", this);
121     connect(testButton, SIGNAL(pressed()), this, SLOT(test_press()));
122     connect(testButton, SIGNAL(released()), this, SLOT(test_release()));
123     layout->addWidget(testButton, 2, 6, Qt::AlignCenter);
124 
125     QTimer *myTimer = new QTimer(this);
126     connect(myTimer, SIGNAL(timeout()), this, SLOT(oscRecv()));
127     myTimer->setSingleShot(false);
128     myTimer->start(0);
129 
130     m_suppressHostUpdate = false;
131 }
132 
133 
134 void
setTuning(float hz)135 SynthGUI::setTuning(float hz)
136 {
137     m_suppressHostUpdate = true;
138     m_tuning->setValue(int((hz - 400.0) * 10.0));
139     m_suppressHostUpdate = false;
140 }
141 
142 void
setAttack(float sec)143 SynthGUI::setAttack(float sec)
144 {
145     m_suppressHostUpdate = true;
146     m_attack->setValue(int(sec * 100));
147     m_suppressHostUpdate = false;
148 }
149 
150 void
setDecay(float sec)151 SynthGUI::setDecay(float sec)
152 {
153     m_suppressHostUpdate = true;
154     m_decay->setValue(int(sec * 100));
155     m_suppressHostUpdate = false;
156 }
157 
158 void
setSustain(float percent)159 SynthGUI::setSustain(float percent)
160 {
161     m_suppressHostUpdate = true;
162     m_sustain->setValue(int(percent));
163     m_suppressHostUpdate = false;
164 }
165 
166 void
setRelease(float sec)167 SynthGUI::setRelease(float sec)
168 {
169     m_suppressHostUpdate = true;
170     m_release->setValue(int(sec * 100));
171     m_suppressHostUpdate = false;
172 }
173 
174 void
setTimbre(float val)175 SynthGUI::setTimbre(float val)
176 {
177     m_suppressHostUpdate = true;
178     m_timbre->setValue(int(val * 100));
179     m_suppressHostUpdate = false;
180 }
181 
182 void
tuningChanged(int value)183 SynthGUI::tuningChanged(int value)
184 {
185     float hz = float(value) / 10.0 + 400.0;
186     m_tuningLabel->setText(QString("%1 Hz").arg(hz));
187 
188     if (!m_suppressHostUpdate) {
189 	cerr << "Sending to host: " << m_controlPath
190 	     << " port " << LTS_PORT_FREQ << " to " << hz << endl;
191 	lo_send(m_host, m_controlPath, "if", LTS_PORT_FREQ, hz);
192     }
193 }
194 
195 void
attackChanged(int value)196 SynthGUI::attackChanged(int value)
197 {
198     float sec = float(value) / 100.0;
199     m_attackLabel->setText(QString("%1 sec").arg(sec));
200 
201     if (!m_suppressHostUpdate) {
202 	lo_send(m_host, m_controlPath, "if", LTS_PORT_ATTACK, sec);
203     }
204 }
205 
206 void
decayChanged(int value)207 SynthGUI::decayChanged(int value)
208 {
209     float sec = float(value) / 100.0;
210     m_decayLabel->setText(QString("%1 sec").arg(sec));
211 
212     if (!m_suppressHostUpdate) {
213 	lo_send(m_host, m_controlPath, "if", LTS_PORT_DECAY, sec);
214     }
215 }
216 
217 void
sustainChanged(int value)218 SynthGUI::sustainChanged(int value)
219 {
220     m_sustainLabel->setText(QString("%1 %").arg(value));
221 
222     if (!m_suppressHostUpdate) {
223 	lo_send(m_host, m_controlPath, "if", LTS_PORT_SUSTAIN, float(value));
224     }
225 }
226 
227 void
releaseChanged(int value)228 SynthGUI::releaseChanged(int value)
229 {
230     float sec = float(value) / 100.0;
231     m_releaseLabel->setText(QString("%1 sec").arg(sec));
232 
233     if (!m_suppressHostUpdate) {
234 	lo_send(m_host, m_controlPath, "if", LTS_PORT_RELEASE, sec);
235     }
236 }
237 
238 void
timbreChanged(int value)239 SynthGUI::timbreChanged(int value)
240 {
241     float val = float(value) / 100.0;
242     m_timbreLabel->setText(QString("%1").arg(val));
243 
244     if (!m_suppressHostUpdate) {
245 	lo_send(m_host, m_controlPath, "if", LTS_PORT_TIMBRE, val);
246     }
247 }
248 
249 void
test_press()250 SynthGUI::test_press()
251 {
252     unsigned char noteon[4] = { 0x00, 0x90, 0x3C, 0x40 };
253 
254     lo_send(m_host, m_midiPath, "m", noteon);
255 }
256 
257 void
oscRecv()258 SynthGUI::oscRecv()
259 {
260     if (osc_server) {
261 	lo_server_recv_noblock(osc_server, 1);
262     }
263 }
264 
265 void
test_release()266 SynthGUI::test_release()
267 {
268     unsigned char noteoff[4] = { 0x00, 0x90, 0x3C, 0x00 };
269 
270     lo_send(m_host, m_midiPath, "m", noteoff);
271 }
272 
273 void
aboutToQuit()274 SynthGUI::aboutToQuit()
275 {
276     if (!m_hostRequestedQuit) lo_send(m_host, m_exitingPath, "");
277 }
278 
~SynthGUI()279 SynthGUI::~SynthGUI()
280 {
281     lo_address_free(m_host);
282 }
283 
284 
285 QDial *
newQDial(int minValue,int maxValue,int pageStep,int value)286 SynthGUI::newQDial( int minValue, int maxValue, int pageStep, int value )
287 {
288     QDial *dial = new QDial( this );
289     dial->setMinimum( minValue );
290     dial->setMaximum( maxValue );
291     dial->setPageStep( pageStep );
292     dial->setValue( value );
293     dial->setNotchesVisible(true);
294     return dial;
295 }
296 
297 
298 void
osc_error(int num,const char * msg,const char * path)299 osc_error(int num, const char *msg, const char *path)
300 {
301     cerr << "Error: liblo server error " << num
302 	 << " in path \"" << (path ? path : "(null)")
303 	 << "\": " << msg << endl;
304 }
305 
306 int
debug_handler(const char * path,const char * types,lo_arg ** argv,int argc,void * data,void * user_data)307 debug_handler(const char *path, const char *types, lo_arg **argv,
308 	      int argc, void *data, void *user_data)
309 {
310     int i;
311 
312     cerr << "Warning: unhandled OSC message in GUI:" << endl;
313 
314     for (i = 0; i < argc; ++i) {
315 	cerr << "arg " << i << ": type '" << types[i] << "': ";
316         lo_arg_pp((lo_type)types[i], argv[i]);
317 	cerr << endl;
318     }
319 
320     cerr << "(path is <" << path << ">)" << endl;
321     return 1;
322 }
323 
324 int
program_handler(const char * path,const char * types,lo_arg ** argv,int argc,void * data,void * user_data)325 program_handler(const char *path, const char *types, lo_arg **argv,
326 	       int argc, void *data, void *user_data)
327 {
328     cerr << "Program handler not yet implemented" << endl;
329     return 0;
330 }
331 
332 int
configure_handler(const char * path,const char * types,lo_arg ** argv,int argc,void * data,void * user_data)333 configure_handler(const char *path, const char *types, lo_arg **argv,
334 		  int argc, void *data, void *user_data)
335 {
336     return 0;
337 }
338 
339 int
rate_handler(const char * path,const char * types,lo_arg ** argv,int argc,void * data,void * user_data)340 rate_handler(const char *path, const char *types, lo_arg **argv,
341 	     int argc, void *data, void *user_data)
342 {
343     return 0; /* ignore it */
344 }
345 
346 int
show_handler(const char * path,const char * types,lo_arg ** argv,int argc,void * data,void * user_data)347 show_handler(const char *path, const char *types, lo_arg **argv,
348 	     int argc, void *data, void *user_data)
349 {
350     SynthGUI *gui = static_cast<SynthGUI *>(user_data);
351     while (!gui->ready()) sleep(1);
352     if (gui->isVisible()) gui->raise();
353     else gui->show();
354     return 0;
355 }
356 
357 int
hide_handler(const char * path,const char * types,lo_arg ** argv,int argc,void * data,void * user_data)358 hide_handler(const char *path, const char *types, lo_arg **argv,
359 	     int argc, void *data, void *user_data)
360 {
361     SynthGUI *gui = static_cast<SynthGUI *>(user_data);
362     gui->hide();
363     return 0;
364 }
365 
366 int
quit_handler(const char * path,const char * types,lo_arg ** argv,int argc,void * data,void * user_data)367 quit_handler(const char *path, const char *types, lo_arg **argv,
368 	     int argc, void *data, void *user_data)
369 {
370     SynthGUI *gui = static_cast<SynthGUI *>(user_data);
371     gui->setHostRequestedQuit(true);
372     qApp->quit();
373     return 0;
374 }
375 
376 int
control_handler(const char * path,const char * types,lo_arg ** argv,int argc,void * data,void * user_data)377 control_handler(const char *path, const char *types, lo_arg **argv,
378 		int argc, void *data, void *user_data)
379 {
380     SynthGUI *gui = static_cast<SynthGUI *>(user_data);
381 
382     if (argc < 2) {
383 	cerr << "Error: too few arguments to control_handler" << endl;
384 	return 1;
385     }
386 
387     const int port = argv[0]->i;
388     const float value = argv[1]->f;
389 
390     switch (port) {
391 
392     case LTS_PORT_FREQ:
393 	cerr << "gui setting frequency to " << value << endl;
394 	gui->setTuning(value);
395 	break;
396 
397     case LTS_PORT_ATTACK:
398 	cerr << "gui setting attack to " << value << endl;
399 	gui->setAttack(value);
400 	break;
401 
402     case LTS_PORT_DECAY:
403 	cerr << "gui setting decay to " << value << endl;
404 	gui->setDecay(value);
405 	break;
406 
407     case LTS_PORT_SUSTAIN:
408 	cerr << "gui setting sustain to " << value << endl;
409 	gui->setSustain(value);
410 	break;
411 
412     case LTS_PORT_RELEASE:
413 	cerr << "gui setting release to " << value << endl;
414 	gui->setRelease(value);
415 	break;
416 
417     case LTS_PORT_TIMBRE:
418 	cerr << "gui setting timbre to " << value << endl;
419 	gui->setTimbre(value);
420 	break;
421 
422     default:
423 	cerr << "Warning: received request to set nonexistent port " << port << endl;
424     }
425 
426     return 0;
427 }
428 
429 int
main(int argc,char ** argv)430 main(int argc, char **argv)
431 {
432     cerr << "less_trivial_synth_qt_gui starting..." << endl;
433 
434     QApplication application(argc, argv);
435 
436     if (application.argc() != 5) {
437 	cerr << "usage: "
438 	     << application.argv()[0]
439 	     << " <osc url>"
440 	     << " <plugin dllname>"
441 	     << " <plugin label>"
442 	     << " <user-friendly id>"
443 	     << endl;
444 	return 2;
445     }
446 
447 #ifdef Q_WS_X11
448     XSetErrorHandler(handle_x11_error);
449 #endif
450 
451     char *url = application.argv()[1];
452 
453     char *host = lo_url_get_hostname(url);
454     char *port = lo_url_get_port(url);
455     char *path = lo_url_get_path(url);
456 
457     SynthGUI gui(host, port,
458 		 QByteArray(path) + "/control",
459 		 QByteArray(path) + "/midi",
460 		 QByteArray(path) + "/program",
461 		 QByteArray(path) + "/exiting",
462 		 0);
463 
464     QByteArray myControlPath = QByteArray(path) + "/control";
465     QByteArray myProgramPath = QByteArray(path) + "/program";
466     QByteArray myConfigurePath = QByteArray(path) + "/configure";
467     QByteArray myRatePath = QByteArray(path) + "/sample-rate";
468     QByteArray myShowPath = QByteArray(path) + "/show";
469     QByteArray myHidePath = QByteArray(path) + "/hide";
470     QByteArray myQuitPath = QByteArray(path) + "/quit";
471 
472     osc_server = lo_server_new(NULL, osc_error);
473     lo_server_add_method(osc_server, myControlPath, "if", control_handler, &gui);
474     lo_server_add_method(osc_server, myProgramPath, "ii", program_handler, &gui);
475     lo_server_add_method(osc_server, myConfigurePath, "ss", configure_handler, &gui);
476     lo_server_add_method(osc_server, myRatePath, "i", rate_handler, &gui);
477     lo_server_add_method(osc_server, myShowPath, "", show_handler, &gui);
478     lo_server_add_method(osc_server, myHidePath, "", hide_handler, &gui);
479     lo_server_add_method(osc_server, myQuitPath, "", quit_handler, &gui);
480     lo_server_add_method(osc_server, NULL, NULL, debug_handler, &gui);
481 
482     lo_address hostaddr = lo_address_new(host, port);
483     lo_send(hostaddr,
484 	    QByteArray(path) + "/update",
485 	    "s",
486 	    (QByteArray(lo_server_get_url(osc_server))+QByteArray(path+1)).data());
487 
488     QObject::connect(&application, SIGNAL(aboutToQuit()), &gui, SLOT(aboutToQuit()));
489 
490     gui.setReady(true);
491     return application.exec();
492 }
493 
494