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