1 /*
2 SuperCollider Qt IDE
3 Copyright (c) 2012 Jakob Leben & Tim Blechmann
4 http://www.audiosynth.com
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (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
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21 #include <QDebug>
22 #include <QWidgetAction>
23
24 #include "sc_server.hpp"
25 #include "sc_process.hpp"
26 #include "main.hpp"
27 #include "../widgets/util/volume_widget.hpp"
28
29 #include <yaml-cpp/yaml.h>
30
31 #include <sstream>
32 #include <iomanip>
33 #include <boost/chrono/chrono_io.hpp>
34 #include <osc/OscReceivedElements.h>
35 #include <osc/OscOutboundPacketStream.h>
36
37 using namespace std;
38 using namespace boost::chrono;
39
40 namespace ScIDE {
41
ScServer(ScProcess * scLang,Settings::Manager * settings,QObject * parent)42 ScServer::ScServer(ScProcess* scLang, Settings::Manager* settings, QObject* parent):
43 QObject(parent),
44 mLang(scLang),
45 mPort(0),
46 mIsRecording(false) {
47 createActions(settings);
48
49 mUdpSocket = new QUdpSocket(this);
50 startTimer(333);
51
52 connect(scLang, SIGNAL(stateChanged(QProcess::ProcessState)), this,
53 SLOT(onScLangStateChanged(QProcess::ProcessState)));
54 connect(scLang, SIGNAL(response(QString, QString)), this, SLOT(onScLangReponse(QString, QString)));
55 connect(mUdpSocket, SIGNAL(readyRead()), this, SLOT(onServerDataArrived()));
56 }
57
createActions(Settings::Manager * settings)58 void ScServer::createActions(Settings::Manager* settings) {
59 const QString synthServerCategory(tr("Sound Synthesis Server"));
60 QAction* action;
61 QWidgetAction* widgetAction;
62
63 mActions[ToggleRunning] = action = new QAction(tr("Boot or quit default server"), this);
64 // the default QAction::TextHeuristicRole incorrectly detects a quit role on macOS
65 action->setMenuRole(QAction::NoRole);
66 connect(action, SIGNAL(triggered()), this, SLOT(toggleRunning()));
67 // settings->addAction( action, "synth-server-toggle-running", synthServerCategory);
68
69 mActions[Boot] = action = new QAction(QIcon::fromTheme("system-run"), tr("&Boot Server"), this);
70 action->setShortcut(tr("Ctrl+B", "Boot default server"));
71 connect(action, SIGNAL(triggered()), this, SLOT(boot()));
72 settings->addAction(action, "synth-server-boot", synthServerCategory);
73
74 mActions[Quit] = action = new QAction(QIcon::fromTheme("system-shutdown"), tr("&Quit Server"), this);
75 connect(action, SIGNAL(triggered()), this, SLOT(quit()));
76 settings->addAction(action, "synth-server-quit", synthServerCategory);
77
78 mActions[KillAll] = action = new QAction(QIcon::fromTheme("system-killall"), tr("&Kill All Servers"), this);
79 connect(action, SIGNAL(triggered()), this, SLOT(killAll()));
80 settings->addAction(action, "synth-server-killall", synthServerCategory);
81
82 mActions[Reboot] = action = new QAction(QIcon::fromTheme("system-reboot"), tr("&Reboot Server"), this);
83 connect(action, SIGNAL(triggered()), this, SLOT(reboot()));
84 settings->addAction(action, "synth-server-reboot", synthServerCategory);
85
86 mActions[ShowMeters] = action = new QAction(tr("Show Server Meter"), this);
87 action->setShortcut(tr("Ctrl+M", "Show server meter"));
88 connect(action, SIGNAL(triggered()), this, SLOT(showMeters()));
89 settings->addAction(action, "synth-server-meter", synthServerCategory);
90
91 mActions[ShowScope] = action = new QAction(tr("Show Scope"), this);
92 action->setShortcut(tr("Ctrl+Shift+M", "Show scope"));
93 connect(action, SIGNAL(triggered()), this, SLOT(showScope()));
94 settings->addAction(action, "synth-server-scope", synthServerCategory);
95
96 mActions[ShowFreqScope] = action = new QAction(tr("Show Freqscope"), this);
97 action->setShortcut(tr("Ctrl+Alt+M", "Show freqscope"));
98 connect(action, SIGNAL(triggered()), this, SLOT(showFreqScope()));
99 settings->addAction(action, "synth-server-freqscope", synthServerCategory);
100
101 mActions[DumpNodeTree] = action = new QAction(tr("Dump Node Tree"), this);
102 action->setShortcut(tr("Ctrl+T", "Dump node tree"));
103 connect(action, SIGNAL(triggered()), this, SLOT(dumpNodeTree()));
104 settings->addAction(action, "synth-server-dump-nodes", synthServerCategory);
105
106 mActions[DumpNodeTreeWithControls] = action = new QAction(tr("Dump Node Tree with Controls"), this);
107 action->setShortcut(tr("Ctrl+Shift+T", "Dump node tree with controls"));
108 connect(action, SIGNAL(triggered()), this, SLOT(dumpNodeTreeWithControls()));
109 settings->addAction(action, "synth-server-dump-nodes-with-controls", synthServerCategory);
110
111 mActions[PlotTree] = action = new QAction(tr("Show Node Tree"), this);
112 action->setShortcut(tr("Ctrl+Alt+T", "Show node tree"));
113 connect(action, SIGNAL(triggered()), this, SLOT(plotTree()));
114 settings->addAction(action, "synth-server-plot-tree", synthServerCategory);
115
116 mActions[DumpOSC] = action = new QAction(tr("Server Dump OSC"), this);
117 action->setCheckable(true);
118 connect(action, SIGNAL(triggered(bool)), this, SLOT(sendDumpingOSC(bool)));
119 settings->addAction(action, "synth-server-dumpOSC", synthServerCategory);
120
121 mActions[Mute] = action = new QAction(tr("Mute"), this);
122 action->setShortcut(tr("Ctrl+Alt+End", "Mute sound output."));
123 action->setCheckable(true);
124 connect(action, SIGNAL(triggered(bool)), this, SLOT(sendMuted(bool)));
125 connect(action, SIGNAL(toggled(bool)), this, SIGNAL(mutedChanged(bool)));
126 settings->addAction(action, "synth-server-mute", synthServerCategory);
127
128 mVolumeWidget = new VolumeWidget;
129
130 mActions[Volume] = widgetAction = new QWidgetAction(this);
131 widgetAction->setDefaultWidget(mVolumeWidget);
132
133 connect(mVolumeWidget, &VolumeWidget::volumeChangeRequested, [this](float newValue) { setVolume(newValue); });
134 connect(this, SIGNAL(volumeChanged(float)), mVolumeWidget, SLOT(setVolume(float)));
135 connect(this, SIGNAL(volumeRangeChanged(float, float)), mVolumeWidget, SLOT(setVolumeRange(float, float)));
136 emit volumeChanged(mVolume);
137 emit volumeRangeChanged(mVolumeMin, mVolumeMax);
138
139 mActions[VolumeUp] = action = new QAction(tr("Increase Volume"), this);
140 action->setShortcut(tr("Ctrl+Alt+PgUp", "Increase volume"));
141 connect(action, SIGNAL(triggered()), this, SLOT(increaseVolume()));
142 settings->addAction(action, "synth-server-volume-up", synthServerCategory);
143
144 mActions[VolumeDown] = action = new QAction(tr("Decrease Volume"), this);
145 action->setShortcut(tr("Ctrl+Alt+PgDown", "Decrease volume"));
146 connect(action, SIGNAL(triggered()), this, SLOT(decreaseVolume()));
147 settings->addAction(action, "synth-server-volume-down", synthServerCategory);
148
149 mActions[VolumeRestore] = action = new QAction(tr("Restore Volume to 0 dB"), this);
150 action->setShortcut(tr("Ctrl+Alt+Home", "Restore volume"));
151 connect(action, SIGNAL(triggered()), this, SLOT(restoreVolume()));
152 settings->addAction(action, "synth-server-volume-restore", synthServerCategory);
153
154 mActions[Record] = action = new QAction(tr("Recording"), this);
155 action->setCheckable(true);
156 connect(action, SIGNAL(triggered(bool)), this, SLOT(sendRecording(bool)));
157 connect(action, SIGNAL(toggled(bool)), this, SIGNAL(recordingChanged(bool)));
158 settings->addAction(action, "synth-server-record", synthServerCategory);
159
160 mActions[PauseRecord] = action = new QAction(tr("Pause Recording"), this);
161 action->setCheckable(true);
162 connect(action, SIGNAL(triggered(bool)), this, SLOT(pauseRecording(bool)));
163 connect(action, SIGNAL(toggled(bool)), this, SIGNAL(pauseChanged(bool)));
164 settings->addAction(action, "synth-server-pause-recording", synthServerCategory);
165
166
167 connect(mActions[Boot], SIGNAL(changed()), this, SLOT(updateToggleRunningAction()));
168 connect(mActions[Quit], SIGNAL(changed()), this, SLOT(updateToggleRunningAction()));
169
170 updateToggleRunningAction();
171 updateRecordingAction();
172 updateEnabledActions();
173 }
174
updateToggleRunningAction()175 void ScServer::updateToggleRunningAction() {
176 QAction* targetAction = isRunning() ? mActions[Quit] : mActions[Boot];
177 mActions[ToggleRunning]->setText(targetAction->text());
178 mActions[ToggleRunning]->setIcon(targetAction->icon());
179 mActions[ToggleRunning]->setShortcut(targetAction->shortcut());
180 }
181
boot()182 void ScServer::boot() {
183 if (isRunning())
184 return;
185
186 mLang->evaluateCode("ScIDE.defaultServer.boot", true);
187 }
188
quit()189 void ScServer::quit() {
190 if (!isRunning())
191 return;
192
193 mLang->evaluateCode("ScIDE.defaultServer.quit", true);
194 }
195
killAll()196 void ScServer::killAll() { mLang->evaluateCode("Server.killAll", true); }
197
reboot()198 void ScServer::reboot() { mLang->evaluateCode("ScIDE.defaultServer.reboot", true); }
199
toggleRunning()200 void ScServer::toggleRunning() {
201 if (isRunning())
202 quit();
203 else
204 boot();
205 }
206
showMeters()207 void ScServer::showMeters() { mLang->evaluateCode("ScIDE.defaultServer.meter", true); }
208
showScope()209 void ScServer::showScope() {
210 mLang->evaluateCode("ScIDE.defaultServer.scope(ScIDE.defaultServer.options.numOutputBusChannels)", true);
211 }
212
showFreqScope()213 void ScServer::showFreqScope() { mLang->evaluateCode("ScIDE.defaultServer.freqscope", true); }
214
dumpNodeTree()215 void ScServer::dumpNodeTree() { queryAllNodes(false); }
216
dumpNodeTreeWithControls()217 void ScServer::dumpNodeTreeWithControls() { queryAllNodes(true); }
218
queryAllNodes(bool dumpControls)219 void ScServer::queryAllNodes(bool dumpControls) {
220 QString arg = dumpControls ? QStringLiteral("true") : QStringLiteral("false");
221
222 mLang->evaluateCode(QStringLiteral("ScIDE.defaultServer.queryAllNodes(%1)").arg(arg), true);
223 }
224
plotTree()225 void ScServer::plotTree() { mLang->evaluateCode("ScIDE.defaultServer.plotTree", true); }
226
isMuted() const227 bool ScServer::isMuted() const { return mActions[Mute]->isChecked(); }
228
setMuted(bool muted)229 void ScServer::setMuted(bool muted) {
230 mActions[Mute]->setChecked(muted);
231 sendMuted(muted);
232 }
233
isDumpingOSC() const234 bool ScServer::isDumpingOSC() const { return mActions[DumpOSC]->isChecked(); }
235
setDumpingOSC(bool dumping)236 void ScServer::setDumpingOSC(bool dumping) {
237 mActions[DumpOSC]->setChecked(dumping);
238 sendDumpingOSC(dumping);
239 }
240
volume() const241 float ScServer::volume() const { return mVolume; }
242
setVolume(float volume)243 void ScServer::setVolume(float volume) {
244 volume = qBound(mVolumeMin, volume, mVolumeMax);
245
246 if (volume != mVolume) {
247 mVolume = volume;
248 sendVolume(volume);
249 emit volumeChanged(volume);
250 }
251 }
252
253
setVolumeRange(float min,float max)254 void ScServer::setVolumeRange(float min, float max) {
255 mVolumeMin = min;
256 mVolumeMax = max;
257 emit volumeRangeChanged(min, max);
258 }
259
increaseVolume()260 void ScServer::increaseVolume() { changeVolume(+1.5); }
261
decreaseVolume()262 void ScServer::decreaseVolume() { changeVolume(-1.5); }
263
changeVolume(float difference)264 void ScServer::changeVolume(float difference) { setVolume(volume() + difference); }
265
restoreVolume()266 void ScServer::restoreVolume() {
267 setVolume(0.0f);
268 unmute();
269 }
270
sendMuted(bool muted)271 void ScServer::sendMuted(bool muted) {
272 static const QString muteCommand("ScIDE.defaultServer.mute");
273 static const QString unmuteCommand("ScIDE.defaultServer.unmute");
274
275 mLang->evaluateCode(muted ? muteCommand : unmuteCommand, true);
276 }
277
sendDumpingOSC(bool dumping)278 void ScServer::sendDumpingOSC(bool dumping) {
279 static const QString dumpCommand("ScIDE.defaultServer.dumpOSC(true)");
280 static const QString stopDumpCommand("ScIDE.defaultServer.dumpOSC(false)");
281
282 mLang->evaluateCode(dumping ? dumpCommand : stopDumpCommand, true);
283 }
284
sendVolume(float volume)285 void ScServer::sendVolume(float volume) {
286 mLang->evaluateCode(QStringLiteral("ScIDE.setServerVolume(%1)").arg(volume), true);
287 }
288
isRecording() const289 bool ScServer::isRecording() const { return mIsRecording; }
isPaused() const290 bool ScServer::isPaused() const { return mIsRecordingPaused; }
291
292
setRecording(bool doRecord)293 void ScServer::setRecording(bool doRecord) {
294 if (!isRunning())
295 return;
296
297 mIsRecording = doRecord;
298 mIsRecordingPaused = false;
299 updateRecordingAction();
300 }
301
pauseRecording(bool flag)302 void ScServer::pauseRecording(bool flag) {
303 if (mIsRecordingPaused != flag) {
304 mIsRecordingPaused = flag;
305 if (flag) {
306 mLang->evaluateCode(QStringLiteral("ScIDE.defaultServer.pauseRecording"), true);
307 } else {
308 if (mIsRecording) {
309 setRecording(true);
310 sendRecording(true);
311 }
312 }
313 updateRecordingAction();
314 }
315 }
316
317
sendRecording(bool doRecord)318 void ScServer::sendRecording(bool doRecord) {
319 static const QString startRecordingCommand("ScIDE.defaultServer.record");
320 static const QString stopRecordingCommand("ScIDE.defaultServer.stopRecording");
321 setRecording(doRecord);
322 mLang->evaluateCode(doRecord ? startRecordingCommand : stopRecordingCommand);
323 }
324
325
updateRecordingAction()326 void ScServer::updateRecordingAction() {
327 if (isRecording()) {
328 int s = mRecordingSeconds % 60;
329 int m = mRecordingSeconds / 60 % 60;
330 int h = mRecordingSeconds / 3600;
331 ostringstream msg;
332 msg << "Recording: ";
333 msg << setw(2) << setfill('0') << h << ':';
334 msg << setw(2) << setfill('0') << m << ':';
335 msg << setw(2) << setfill('0') << s;
336 mActions[Record]->setText(msg.str().c_str());
337 } else {
338 mRecordingSeconds = 0;
339 mActions[Record]->setText("Start Recording");
340 mIsRecordingPaused = false;
341 }
342 mActions[Record]->setChecked(isRecording());
343 mActions[PauseRecord]->setChecked(mIsRecordingPaused);
344 }
345
onScLangStateChanged(QProcess::ProcessState)346 void ScServer::onScLangStateChanged(QProcess::ProcessState) { updateEnabledActions(); }
347
onScLangReponse(const QString & selector,const QString & data)348 void ScServer::onScLangReponse(const QString& selector, const QString& data) {
349 static QString defaultServerRunningChangedSelector("defaultServerRunningChanged");
350 static QString mutedSelector("serverMuted");
351 static QString unmutedSelector("serverUnmuted");
352 static QString ampSelector("serverAmp");
353 static QString ampRangeSelector("serverAmpRange");
354 static QString startDumpOSCSelector("dumpOSCStarted");
355 static QString stopDumpOSCSelector("dumpOSCStopped");
356 static QString startRecordingSelector("recordingStarted");
357 static QString pauseRecordingSelector("recordingPaused");
358 static QString stopRecordingSelector("recordingStopped");
359 static QString recordingDurationSelector("recordingDuration");
360
361 if (selector == defaultServerRunningChangedSelector)
362 handleRuningStateChangedMsg(data);
363 else if (selector == mutedSelector) {
364 mActions[Mute]->setChecked(true);
365 } else if (selector == unmutedSelector) {
366 mActions[Mute]->setChecked(false);
367 } else if (selector == startDumpOSCSelector) {
368 mActions[DumpOSC]->setChecked(true);
369 } else if (selector == stopDumpOSCSelector) {
370 mActions[DumpOSC]->setChecked(false);
371 } else if (selector == recordingDurationSelector) {
372 bool ok;
373 float duration = data.mid(1, data.size() - 2).toFloat(&ok);
374 if (ok) {
375 mRecordingSeconds = (int)duration;
376 updateRecordingAction();
377 }
378 } else if (selector == startRecordingSelector) {
379 setRecording(true);
380 } else if (selector == startRecordingSelector) {
381 setRecording(true);
382 } else if (selector == pauseRecordingSelector) {
383 pauseRecording(true);
384 } else if (selector == stopRecordingSelector) {
385 setRecording(false);
386 } else if (selector == ampSelector) {
387 bool ok;
388 float volume = data.mid(1, data.size() - 2).toFloat(&ok);
389 if (ok) {
390 mVolume = volume;
391 emit volumeChanged(volume);
392 }
393 } else if (selector == ampRangeSelector) {
394 bool ok;
395 QStringList dataList = data.mid(1, data.size() - 2).split(',');
396 if (dataList.size() < 2)
397 return;
398 float min = dataList[0].toFloat(&ok);
399 if (!ok)
400 return;
401 float max = dataList[1].toFloat(&ok);
402 if (!ok)
403 return;
404 mVolumeMin = min;
405 mVolumeMax = max;
406 setVolumeRange(min, max);
407 }
408 }
409
handleRuningStateChangedMsg(const QString & data)410 void ScServer::handleRuningStateChangedMsg(const QString& data) {
411 bool serverRunningState = false;
412 bool serverUnresponsive = false;
413 std::string hostName;
414 int port = -1;
415
416 try {
417 const YAML::Node doc = YAML::Load(data.toStdString());
418 if (doc) {
419 assert(doc.IsSequence());
420
421 serverRunningState = doc[0].as<bool>();
422 hostName = doc[1].as<std::string>();
423 port = doc[2].as<int>();
424 serverUnresponsive = doc[3].as<bool>();
425 }
426 } catch (...) {
427 return; // LATER: report error?
428 }
429
430 QString qstrHostName(hostName.c_str());
431
432 onRunningStateChanged(serverRunningState, qstrHostName, port);
433
434 emit runningStateChanged(serverRunningState, qstrHostName, port, serverUnresponsive);
435 }
436
timerEvent(QTimerEvent * event)437 void ScServer::timerEvent(QTimerEvent* event) {
438 if (mPort) {
439 char buffer[512];
440 osc::OutboundPacketStream stream(buffer, 512);
441 stream << osc::BeginMessage("/status");
442 stream << osc::MessageTerminator();
443
444 qint64 sentSize = mUdpSocket->write(stream.Data(), stream.Size());
445 if (sentSize == -1)
446 qCritical() << "Failed to send server status request:" << mUdpSocket->errorString();
447 }
448 }
449
onRunningStateChanged(bool running,QString const & hostName,int port)450 void ScServer::onRunningStateChanged(bool running, QString const& hostName, int port) {
451 if (running) {
452 mServerAddress = QHostAddress(hostName);
453 mPort = port;
454 mUdpSocket->connectToHost(mServerAddress, mPort);
455 } else {
456 mServerAddress.clear();
457 mPort = 0;
458 mIsRecording = false;
459 mUdpSocket->disconnectFromHost();
460 }
461
462 updateToggleRunningAction();
463 updateRecordingAction();
464 updateEnabledActions();
465 }
466
onServerDataArrived()467 void ScServer::onServerDataArrived() {
468 while (mUdpSocket->hasPendingDatagrams()) {
469 size_t datagramSize = mUdpSocket->pendingDatagramSize();
470 QByteArray array(datagramSize, 0);
471 qint64 readSize = mUdpSocket->readDatagram(array.data(), datagramSize);
472 if (readSize == -1)
473 continue;
474
475 processOscPacket(osc::ReceivedPacket(array.data(), datagramSize));
476 }
477 }
478
processOscMessage(const osc::ReceivedMessage & message)479 void ScServer::processOscMessage(const osc::ReceivedMessage& message) {
480 if (strcmp(message.AddressPattern(), "/status.reply") == 0) {
481 processServerStatusMessage(message);
482 }
483 }
484
processServerStatusMessage(const osc::ReceivedMessage & message)485 void ScServer::processServerStatusMessage(const osc::ReceivedMessage& message) {
486 if (!isRunning())
487 return;
488
489 osc::int32 unused;
490 osc::int32 ugenCount;
491 osc::int32 synthCount;
492 osc::int32 groupCount;
493 osc::int32 defCount;
494 float avgCPU;
495 float peakCPU;
496
497 auto args = message.ArgumentStream();
498
499 try {
500 args >> unused >> ugenCount >> synthCount >> groupCount >> defCount >> avgCPU >> peakCPU;
501 } catch (osc::MissingArgumentException) {
502 qCritical("Misformatted server status message.");
503 return;
504 }
505
506 emit updateServerStatus(ugenCount, synthCount, groupCount, defCount, avgCPU, peakCPU);
507 }
508
updateEnabledActions()509 void ScServer::updateEnabledActions() {
510 bool langRunning = mLang->state() == QProcess::Running;
511 bool langAndServerRunning = langRunning && isRunning();
512
513 mActions[ToggleRunning]->setEnabled(langRunning);
514 mActions[KillAll]->setEnabled(langRunning);
515 mActions[Reboot]->setEnabled(langRunning);
516 mActions[ShowMeters]->setEnabled(langRunning);
517 mActions[ShowScope]->setEnabled(langRunning);
518 mActions[ShowFreqScope]->setEnabled(langRunning);
519 mActions[DumpNodeTree]->setEnabled(langAndServerRunning);
520 mActions[DumpNodeTreeWithControls]->setEnabled(langAndServerRunning);
521 mActions[PlotTree]->setEnabled(langAndServerRunning);
522 mActions[Mute]->setEnabled(langAndServerRunning);
523 mActions[VolumeUp]->setEnabled(langAndServerRunning);
524 mActions[VolumeDown]->setEnabled(langAndServerRunning);
525 mActions[Volume]->setEnabled(langAndServerRunning);
526 mActions[VolumeRestore]->setEnabled(langAndServerRunning);
527 mActions[Record]->setEnabled(langAndServerRunning);
528 mActions[PauseRecord]->setEnabled(langAndServerRunning);
529 mActions[DumpOSC]->setEnabled(langAndServerRunning);
530 }
531
532 }
533