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