src/Regulator.cpp
src/Compressor.cpp
src/Limiter.cpp
+ src/Meter.cpp
src/Reverb.cpp
src/AudioTester.cpp
src/Patcher.cpp
=== "Fedora"
```bash
- dnf install meson qt5-qtbase-devel rtaudio-devel "pkgconfig(jack)" help2man
+ dnf install meson qt5-qtbase-devel qt5-qtquickcontrols2-devel qt5-qtsvg-devel qt5-qtnetworkauth-devel qt5-qtwebsockets-devel rtaudio-devel "pkgconfig(jack)" help2man python3-jinja2 python3-pyyaml
```
=== "Debian/Ubuntu"
+- Version: "1.6.4"
+ Date: 2022-09-16
+ Description:
+ - (added) Volume meters when connected to a Studio in VS mode
+ - (added) Validation of Linux desktop file in build steps
+ - (added) a copy invite link button to VS mode Studios
+ - (added) an advanced setting for buffer strategy in VS mode
+ - (updated) PLC workers now zero out the last good packet from stalled clients
+ - (updated) PLC distinguish stuck clients
+ - (updated) Fedora meson dependencies
+ - (fixed) UI is usable when Studio list in empty in VS mode
+ - (fixed) Crashing when unplugging a device while connected and using JACK
+ - (fixed) Launching from URL skipped setup
+ - (fixed) UI hanging when connecting to Studios in VS mode
+ - (fixed) "Refresh list" button disabling ui interaction in VS mode
+ - (fixed) Network stats not displaying on first connect after login (VS mode)
+ - (fixed) VS mode won't join studios when on warning or device setup screens
- Version: "1.6.3"
Date: 2022-08-23
Description:
Date: 2022-08-05
Description:
- (updated) Static Qt version for Linux builds
- - (upated) cleaner, easier to read VS settings
+ - (updated) cleaner, easier to read VS settings
- (updated) icons for 'Manage' and 'Settings' in VS mode
- (added) human-readable locations in VS mode
- (added) warning that cmake is not officially supported
--- /dev/null
+
+declare name "meter";
+declare version "1.0";
+declare author "Dominick Hing";
+declare license "MIT Style STK-4.2";
+declare description "VU Meter Faust Plugin for JackTrip";
+
+// Originally modified from https://github.com/grame-cncm/faust/blob/master-dev/examples/analysis/meter.dsp
+
+import("stdfaust.lib");
+
+process = peakMeter
+with {
+
+ round(n, x) = x
+ <: (ma.copysign(_, 1) : _ * (10 ^ n) <: int(_) , ma.frac : _, (_ >= 0.5) :> + : _ / (10 ^ n) ), _
+ : ma.copysign(_, _);
+ peakMeter = _ : max(ba.db2linear(-80), _) : ba.linear2db(_) : round(2, _);
+};
src/Limiter.h \
src/Regulator.h \
src/Reverb.h \
+ src/Meter.h \
src/AudioTester.h \
src/jacktrip_globals.h \
src/jacktrip_types.h \
src/compressordsp.h \
src/limiterdsp.h \
src/freeverbdsp.h \
+ src/meterdsp.h \
src/SslServer.h \
src/Auth.h
#(Removed JackTripThread.h JackTripWorkerMessages.h NetKS.h TestRingBuffer.h ThreadPoolTest.h)
src/gui/vsPinger.h \
src/gui/vsPing.h \
src/gui/vsUrlHandler.h \
+ src/gui/vsQmlClipboard.h \
src/JTApplication.h
}
!noupdater:!linux-g++:!linux-g++-64 {
src/Limiter.cpp \
src/Regulator.cpp \
src/Reverb.cpp \
+ src/Meter.cpp \
src/AudioTester.cpp \
src/jacktrip_globals.cpp \
src/JackTripWorker.cpp \
src = [ 'src/DataProtocol.cpp',
'src/JackTrip.cpp',
+ 'src/ProcessPlugin.cpp',
'src/AudioTester.cpp',
'src/jacktrip_globals.cpp',
'src/JackTripWorker.cpp',
'src/AudioInterface.cpp',
'src/Compressor.cpp',
'src/Limiter.cpp',
+ 'src/Meter.cpp',
'src/Reverb.cpp',
'src/main.cpp',
'src/SslServer.cpp',
moc_h = ['src/DataProtocol.h',
'src/JackTrip.h',
+ 'src/ProcessPlugin.h',
+ 'src/Meter.h',
'src/JackTripWorker.h',
'src/PacketHeader.h',
'src/Settings.h',
'src/gui/vsPinger.h',
'src/gui/vsPing.h',
'src/gui/vsUrlHandler.h',
+ 'src/gui/vsQmlClipboard.h',
'src/JTApplication.h'
]
qt5_dep = dependency('qt5', modules: ['Core', 'Gui', 'Network', 'Widgets', 'Quick', 'Qml', 'Svg', 'NetworkAuth', 'WebSockets'], include_type: 'system')
{
"app_name": "JackTrip",
"releases": [
+ {
+ "version": "1.6.4-rc.2",
+ "changelog": "Adding volume meters, bugfixes around Windows hanging, device disconnects, and PLC: https://github.com/jacktrip/jacktrip/releases/tag/v1.6.4-rc2",
+ "download": {
+ "date": "2022-09-08T00:00:00Z",
+ "url": "https://github.com/jacktrip/jacktrip/releases/download/v1.6.4-rc2/JackTrip-v1.6.4-rc2-macOS-x64-installer.pkg",
+ "downloadSize": 11554117,
+ "sha256": "c849c22583883027f94141b3878cebc85295c0a617640449d4f66862982f75c0"
+ }
+ },
+ {
+ "version": "1.6.4-rc.1",
+ "changelog": "Adding volume meters, bugfixes around Windows hanging, device disconnects, and PLC: https://github.com/jacktrip/jacktrip/releases/tag/v1.6.4-rc1",
+ "download": {
+ "date": "2022-09-01T00:00:00Z",
+ "url": "https://github.com/jacktrip/jacktrip/releases/download/v1.6.4-rc1/JackTrip-v1.6.4-rc1-macOS-x64-installer.pkg",
+ "downloadSize": 11548759,
+ "sha256": "6e8af76766b164e40e7a44842a14e640cb3c0fac7b9b7067f9c75048d3354496"
+ }
+ },
+ {
+ "version": "1.6.3",
+ "changelog": "Fixes around Linux desktop file and hub server mode: https://github.com/jacktrip/jacktrip/releases/tag/v1.6.3",
+ "download": {
+ "date": "2022-08-23T00:00:00Z",
+ "url": "https://github.com/jacktrip/jacktrip/releases/download/v1.6.3/JackTrip-v1.6.3-macOS-x64-installer.pkg",
+ "downloadSize": 11533023,
+ "sha256": "0b597c62544e9813949f859cc7b7c13bef893f6896f96b34a19889faf4855116"
+ }
+ },
{
"version": "1.6.2",
"changelog": "Ability to open app via URL schemes, displaying latency stats, and Virtual Studio device support. Learn more here: https://github.com/jacktrip/jacktrip/releases/tag/v1.6.2",
{
"app_name": "JackTrip",
"releases": [
+ {
+ "version": "1.6.4-rc.2",
+ "changelog": "Adding volume meters, bugfixes around Windows hanging, device disconnects, and PLC: https://github.com/jacktrip/jacktrip/releases/tag/v1.6.4-rc2",
+ "download": {
+ "date": "2022-09-08T00:00:00Z",
+ "url": "https://github.com/jacktrip/jacktrip/releases/download/v1.6.4-rc2/JackTrip-v1.6.4-rc2-Windows-x64-installer.msi",
+ "downloadSize": 43663360,
+ "sha256": "fc782f0f9547ff096eb99891745e5d80056090b05a179f0209bd7785b1b808a6"
+ }
+ },
+ {
+ "version": "1.6.4-rc.1",
+ "changelog": "Adding volume meters, bugfixes around Windows hanging, device disconnects, and PLC: https://github.com/jacktrip/jacktrip/releases/tag/v1.6.4-rc1",
+ "download": {
+ "date": "2022-09-01T00:00:00Z",
+ "url": "https://github.com/jacktrip/jacktrip/releases/download/v1.6.4-rc1/JackTrip-v1.6.4-rc1-Windows-x64-installer.msi",
+ "downloadSize": 43651072,
+ "sha256": "fbe0f883429119b4c878e0dff4bb2c79f2ac0d995b9c947f7eb8cc70fa59bc67"
+ }
+ },
+ {
+ "version": "1.6.3",
+ "changelog": "Fixes around Linux desktop file and hub server mode: https://github.com/jacktrip/jacktrip/releases/tag/v1.6.3",
+ "download": {
+ "date": "2022-08-23T00:00:00Z",
+ "url": "https://github.com/jacktrip/jacktrip/releases/download/v1.6.3/JackTrip-v1.6.3-Windows-x64-installer.msi",
+ "downloadSize": 43606016,
+ "sha256": "83a4def2a8c8fde24d147d39e70f60ee144e9425828f34eda2340c23ce72b1da"
+ }
+ },
{
"version": "1.6.2",
"changelog": "Ability to open app via URL schemes, displaying latency stats, and Virtual Studio device support. Learn more here: https://github.com/jacktrip/jacktrip/releases/tag/v1.6.2",
{
"app_name": "JackTrip",
"releases": [
+ {
+ "version": "1.6.3",
+ "changelog": "Fixes around Linux desktop file and hub server mode: https://github.com/jacktrip/jacktrip/releases/tag/v1.6.3",
+ "download": {
+ "date": "2022-08-23T00:00:00Z",
+ "url": "https://github.com/jacktrip/jacktrip/releases/download/v1.6.3/JackTrip-v1.6.3-macOS-x64-installer.pkg",
+ "downloadSize": 11533023,
+ "sha256": "0b597c62544e9813949f859cc7b7c13bef893f6896f96b34a19889faf4855116"
+ }
+ },
{
"version": "1.6.2",
"changelog": "Ability to open app via URL schemes, displaying latency stats, and Virtual Studio device support. Learn more here: https://github.com/jacktrip/jacktrip/releases/tag/v1.6.2",
{
"app_name": "JackTrip",
"releases": [
+ {
+ "version": "1.6.3",
+ "changelog": "Fixes around Linux desktop file and hub server mode: https://github.com/jacktrip/jacktrip/releases/tag/v1.6.3",
+ "download": {
+ "date": "2022-08-23T00:00:00Z",
+ "url": "https://github.com/jacktrip/jacktrip/releases/download/v1.6.3/JackTrip-v1.6.3-Windows-x64-installer.msi",
+ "downloadSize": 43606016,
+ "sha256": "83a4def2a8c8fde24d147d39e70f60ee144e9425828f34eda2340c23ce72b1da"
+ }
+ },
{
"version": "1.6.2",
"changelog": "Ability to open app via URL schemes, displaying latency stats, and Virtual Studio device support. Learn more here: https://github.com/jacktrip/jacktrip/releases/tag/v1.6.2",
std::cout << "Initializing Faust plugins (have " << nPlugins
<< ") at sampling rate " << mSampleRate << "\n";
for (ProcessPlugin* plugin : qAsConst(mProcessPluginsFromNetwork)) {
+ plugin->setOutgoingToNetwork(false);
+ plugin->updateNumChannels(mNumInChans, mNumOutChans);
plugin->init(mSampleRate);
}
for (ProcessPlugin* plugin : qAsConst(mProcessPluginsToNetwork)) {
+ plugin->setOutgoingToNetwork(true);
+ plugin->updateNumChannels(mNumInChans, mNumOutChans);
plugin->init(mSampleRate);
}
}
}
// Set function to call if Jack shuts down
- jack_on_shutdown(mClient, this->jackShutdown, 0);
+ jack_on_info_shutdown(mClient, this->jackShutdown, 0);
// Create input and output channels
createChannels();
}
//*******************************************************************************
-void JackAudioInterface::jackShutdown(void*)
+void JackAudioInterface::jackShutdown(jack_status_t code, const char* reason, void* arg)
{
- // std::cout << "The Jack Server was shut down!" << std::endl;
+ std::cout << reason << std::endl;
JackTrip::sJackStopped = true;
std::cout << "The Jack Server was shut down!" << std::endl;
- // throw std::runtime_error("The Jack Server was shut down!");
- // std::cout << "Exiting program..." << std::endl;
- // std::exit(1);
}
//*******************************************************************************
/// \brief Creates input and output channels in the Jack client
void createChannels();
/** \brief JACK calls this shutdown_callback if the server ever shuts down or
- * decides to disconnect the client.
+ * decides to disconnect the client and has a message to deliver.
*/
- static void jackShutdown(void*);
+ static void jackShutdown(jack_status_t code, const char* reason, void* arg);
/** \brief Set the process callback of the member function processCallback.
* This process will be called by the JACK server whenever there is work to be done.
*/
for (auto& i : mProcessPluginsFromNetwork) {
mAudioInterface->appendProcessPluginFromNetwork(i);
}
+
for (auto& i : mProcessPluginsToNetwork) {
mAudioInterface->appendProcessPluginToNetwork(i);
}
+
mAudioInterface->initPlugins(); // mSampleRate known now, which plugins require
mAudioInterface->startProcess(); // Tell JACK server we are ready for audio flow now
DataProtocol::packetHeaderTypeT mPacketHeaderType; ///< Packet Header Type
JackTrip::audiointerfaceModeT mAudiointerfaceMode;
- int mNumAudioChansIn; ///< Number of Audio Input Channels
- int mNumAudioChansOut; ///< Number of Audio Output Channels
+ int mNumAudioChansIn; ///< Number of Audio Input Channels
+ int mNumAudioChansOut; ///< Number of Audio Output Channels
+
#ifdef WAIR // WAIR
int mNumNetRevChans; ///< Number of Network Audio Channels (net comb filters)
#endif // endwhere
--- /dev/null
+//*****************************************************************
+/*
+ JackTrip: A System for High-Quality Audio Network Performance
+ over the Internet
+
+ Copyright (c) 2020 Julius Smith, Juan-Pablo Caceres, Chris Chafe.
+ SoundWIRE group at CCRMA, Stanford University.
+
+ Permission is hereby granted, free of charge, to any person
+ obtaining a copy of this software and associated documentation
+ files (the "Software"), to deal in the Software without
+ restriction, including without limitation the rights to use,
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following
+ conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ OTHER DEALINGS IN THE SOFTWARE.
+*/
+//*****************************************************************
+
+/**
+ * \file Meter.cpp
+ * \author Dominick Hing
+ * \date August 2022
+ * \license MIT
+ */
+
+#include "Meter.h"
+
+#include <QVector>
+
+#include "jacktrip_types.h"
+
+//*******************************************************************************
+void Meter::init(int samplingRate)
+{
+ ProcessPlugin::init(samplingRate);
+ if (samplingRate != fSamplingFreq) {
+ std::cerr << "Sampling rate not set by superclass!\n";
+ std::exit(1);
+ }
+
+ fs = float(fSamplingFreq);
+ for (int i = 0; i < mNumChannels; i++) {
+ meterP[i]->init(fs);
+ }
+
+ /* Set meter values to the default floor */
+ mValues.resize(mNumChannels);
+ QVector<float>::iterator it;
+ for (it = mValues.begin(); it != mValues.end(); ++it) {
+ *it = threshold;
+ }
+
+ /* Start timer */
+ int timeout_ms = 100;
+ connect(&mTimer, &QTimer::timeout, this, &Meter::onTick);
+ mTimer.setTimerType(Qt::PreciseTimer);
+ mTimer.setInterval(timeout_ms);
+ mTimer.setSingleShot(false);
+ mTimer.start();
+
+ inited = true;
+}
+
+//*******************************************************************************
+void Meter::compute(int nframes, float** inputs, float** /*_*/)
+{
+ // Note that the second parameter is unused. This is because all of the ProcessPlugins
+ // require the same function signature for the compute() function and is normally used
+ // for the faust plugin output. However, this plugin is not supposed to modify the
+ // signal itself like the other plugins (e.g. Limiter) do, so we don't want to write
+ // to this buffer. We just need to report the VU meter output
+
+ if (not inited) {
+ std::cerr << "*** Meter " << this << ": init never called! Doing it now.\n";
+ if (fSamplingFreq <= 0) {
+ fSamplingFreq = 48000;
+ std::cout << "Meter " << this
+ << ": *** HAD TO GUESS the sampling rate (chose 48000 Hz) ***\n";
+ }
+ init(fSamplingFreq);
+ }
+
+ QVector<float> meterBuf(nframes);
+ float* meterBufPtr = meterBuf.data();
+ float** output = &meterBufPtr;
+ for (int i = 0; i < mNumChannels; i++) {
+ /* Run the signal through Faust */
+ meterP[i]->compute(nframes, &inputs[i], output);
+
+ /* Use the existing value of mValues[i] as
+ the threshold - this will be reset to the default floor of -80dB
+ on each timeout */
+ float max = mValues[i];
+ QVector<float>::iterator it;
+ for (it = meterBuf.begin(); it != meterBuf.end(); ++it) {
+ if (*it > max) {
+ max = *it;
+ }
+ }
+
+ /* Update mValues */
+ mValues[i] = max;
+ }
+
+ /* Set processed audio flag */
+ hasProcessedAudio = true;
+}
+
+//*******************************************************************************
+void Meter::updateNumChannels(int nChansIn, int nChansOut)
+{
+ if (outgoingPluginToNetwork) {
+ mNumChannels = nChansIn;
+ } else {
+ mNumChannels = nChansOut;
+ }
+
+ mValues.resize(mNumChannels);
+ QVector<float>::iterator it;
+ for (it = mValues.begin(); it != mValues.end(); ++it) {
+ *it = threshold;
+ }
+}
+
+//*******************************************************************************
+void Meter::onTick()
+{
+ if (hasProcessedAudio) {
+ /* Send the measurements to whatever other component requests it */
+ emit onComputedVolumeMeasurements(mValues);
+
+ /* Set meter values to the default floor */
+ QVector<float>::iterator it;
+ for (it = mValues.begin(); it != mValues.end(); ++it) {
+ *it = threshold;
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+//*****************************************************************
+/*
+ JackTrip: A System for High-Quality Audio Network Performance
+ over the Internet
+
+ Copyright (c) 2020 Julius Smith, Juan-Pablo Caceres, Chris Chafe.
+ SoundWIRE group at CCRMA, Stanford University.
+
+ Permission is hereby granted, free of charge, to any person
+ obtaining a copy of this software and associated documentation
+ files (the "Software"), to deal in the Software without
+ restriction, including without limitation the rights to use,
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following
+ conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ OTHER DEALINGS IN THE SOFTWARE.
+*/
+//*****************************************************************
+
+/**
+ * \file Meter.h
+ * \author Dominick Hing
+ * \date August 2022
+ * \license MIT
+ */
+
+#ifndef __METER_H__
+#define __METER_H__
+
+#include <QObject>
+#include <QTimer>
+#include <QVector>
+#include <iostream>
+#include <vector>
+
+#include "ProcessPlugin.h"
+#include "meterdsp.h"
+
+/** \brief The Meter class measures the live audio loudness level
+ */
+class Meter : public ProcessPlugin
+{
+ Q_OBJECT;
+
+ public:
+ /// \brief The class constructor sets the number of channels to measure
+ Meter(int numchans, bool verboseFlag = false) : mNumChannels(numchans)
+ {
+ setVerbose(verboseFlag);
+ for (int i = 0; i < mNumChannels; i++) {
+ meterP.push_back(new meterdsp);
+ // meterUIP.push_back(new APIUI);
+ // meterP[i]->buildUserInterface(meterUIP[i]);
+ }
+ }
+
+ /// \brief The class destructor
+ virtual ~Meter()
+ {
+ for (int i = 0; i < mNumChannels; i++) {
+ delete meterP[i];
+ }
+ meterP.clear();
+ }
+
+ void init(int samplingRate) override;
+ int getNumInputs() override { return (mNumChannels); }
+ int getNumOutputs() override { return (mNumChannels); }
+ void compute(int nframes, float** inputs, float** outputs) override;
+ const char* getName() const override { return "VU Meter"; };
+
+ void updateNumChannels(int nChansIn, int nChansOut) override;
+
+ private:
+ float fs;
+ int mNumChannels;
+ float threshold = -80.0;
+ std::vector<meterdsp*> meterP;
+ bool hasProcessedAudio = false;
+
+ QTimer mTimer;
+ QVector<float> mValues;
+
+ private slots:
+ void onTick();
+
+ signals:
+ void onComputedVolumeMeasurements(QVector<float> values);
+};
+
+#endif
\ No newline at end of file
#ifndef __PROCESSPLUGIN_H__
#define __PROCESSPLUGIN_H__
+#include <QObject>
#include <QThread>
/** \brief Interface for the process plugins to add to the JACK callback process in
* methods except init, which is optional for processing that are sampling rate dependent
* or that need specific initialization.
*/
-class ProcessPlugin
+class ProcessPlugin : public QObject
{
+ Q_OBJECT;
+
public:
/// \brief The Class Constructor
ProcessPlugin(){};
virtual bool getInited() { return inited; }
virtual void setVerbose(bool v) { verbose = v; }
+ virtual void setOutgoingToNetwork(bool toNetwork)
+ {
+ outgoingPluginToNetwork = toNetwork;
+ }
+
/// \brief Compute process
virtual void compute(int nframes, float** inputs, float** outputs) = 0;
+ /**
+ * @brief This function may optionally be used by plugins. This is useful
+ * if the number of audio channels in the parent audio interface has changed
+ * after the plugin instance was instantiated, to tell the plugin to modify its
+ * functionality.
+ */
+ virtual void updateNumChannels(int /*nChansIn*/, int /*nChansOut*/) { return; };
+
protected:
- int fSamplingFreq; ///< Faust Data member, Sampling Rate
- bool inited = false;
- bool verbose = false;
+ int fSamplingFreq; //< Faust Data member, Sampling Rate
+ bool inited = false;
+ bool verbose = false;
+ bool outgoingPluginToNetwork = false; //< Tells the plugin if it processes audio
+ // going into or out of the network
};
#endif
goto PACKETOK;
}
}
+ // make this a global value? -- same threshold as
+ // UdpDataProtocol::printUdpWaitedTooLong
+ double wait_time = 30; // msec
+ if ((mLastSeqNumOut == mLastSeqNumIn)
+ && ((now - mIncomingTiming[mLastSeqNumOut]) > wait_time)) {
+ // std::cout << (mIncomingTiming[mLastSeqNumOut] - now)
+ // << "mLastSeqNumIn: " << mLastSeqNumIn <<
+ // "\tmLastSeqNumOut: " << mLastSeqNumOut << std::endl;
+ goto ZERO_OUTPUT;
+ } // "good underrun", not a stuck client
+ // std::cout << "within window -- mLastSeqNumIn: " <<
+ // mLastSeqNumIn <<
+ // "\tmLastSeqNumOut: " << mLastSeqNumOut << std::endl;
goto UNDERRUN;
}
// be accessible
mRtAudio->openStream(&out_params, &in_params, RTAUDIO_FLOAT32, sampleRate,
&bufferFrames, &RtAudioInterface::wrapperRtAudioCallback,
- this, &options);
+ this, &options, &RtAudioInterface::RtAudioErrorCallback);
setBufferSize(bufferFrames);
} catch (RtAudioError& e) {
std::cout << '\n' << e.getMessage() << '\n' << std::endl;
outputBuffer, inputBuffer, nFrames, streamTime, status);
}
+//*******************************************************************************
+void RtAudioInterface::RtAudioErrorCallback(RtAudioError::Type type,
+ const std::string& errorText)
+{
+ if ((type != RtAudioError::WARNING) && (type != RtAudioError::DEBUG_WARNING)) {
+ std::cout << '\n' << errorText << '\n' << std::endl;
+ throw std::runtime_error(errorText);
+ }
+}
+
//*******************************************************************************
int RtAudioInterface::startProcess() const
{
static int wrapperRtAudioCallback(void* outputBuffer, void* inputBuffer,
unsigned int nFrames, double streamTime,
RtAudioStreamStatus status, void* userData);
+ static void RtAudioErrorCallback(RtAudioError::Type type,
+ const std::string& errorText);
void printDeviceInfo(unsigned int deviceId);
int mNumInChans; ///< Number of Input Channels
property int buttonHeight: 25
property int buttonWidth: 103
property int extraSettingsButtonWidth: 16
+ property int emptyListMessageWidth: 450
+ property int createMessageTopMargin: 16
+ property int createButtonTopMargin: 48
+ property int fontBig: 28
property int fontMedium: 11
property int scrollY: 0
if (currentIndex == -1) {
currentIndex = studioListView.indexAt(16 * virtualstudio.uiScale, studioListView.contentY + (16 * virtualstudio.uiScale));
}
- virtualstudio.refreshStudios(currentIndex)
+ virtualstudio.refreshStudios(currentIndex, true)
}
Rectangle {
}
}
- Component {
- id: sectionHeading
- Rectangle {
- color: "transparent"
- height: 72 * virtualstudio.uiScale; x: 16 * virtualstudio.uiScale; width: ListView.view.width - (2 * x)
- // required property string section: section (for 5.15)
- Text {
- id: sectionText
- //anchors.bottom: parent.bottom
- y: 12 * virtualstudio.uiScale
- // text: parent.section (for 5.15)
- text: section
- font { family: "Poppins"; pixelSize: 28 * virtualstudio.fontScale * virtualstudio.uiScale; weight: Font.Bold }
- color: textColour
- }
- Button {
- id: createButton
- background: Rectangle {
- radius: 6 * virtualstudio.uiScale
- color: createButton.down ? "#E7E8E8" : "#F2F3F3"
- border.width: 1
- border.color: createButton.down ? "#B0B5B5" : "#EAEBEB"
- layer.enabled: createButton.hovered && !createButton.down
- layer.effect: DropShadow {
- horizontalOffset: 1 * virtualstudio.uiScale
- verticalOffset: 1 * virtualstudio.uiScale
- radius: 8.0 * virtualstudio.uiScale
- samples: 17
- color: "#80A1A1A1"
- }
- }
- onClicked: { virtualstudio.createStudio(); }
- anchors.right: filterButton.left
- anchors.rightMargin: 16
- anchors.verticalCenter: sectionText.verticalCenter
- width: 150 * virtualstudio.uiScale; height: 30 * virtualstudio.uiScale
- Text {
- text: "Create a Studio"
- font.family: "Poppins"
- font.pixelSize: 11 * virtualstudio.fontScale * virtualstudio.uiScale
- font.weight: Font.Bold
- color: "#DB0A0A"
- anchors.horizontalCenter: parent.horizontalCenter
- anchors.verticalCenter: parent.verticalCenter
- }
- visible: section == virtualstudio.logoSection ? true : false
- }
- Button {
- id: filterButton
- background: Rectangle {
- radius: 6 * virtualstudio.uiScale
- color: filterButton.down ? "#E7E8E8" : "#F2F3F3"
- border.width: 1
- border.color: filterButton.down ? "#B0B5B5" : "#EAEBEB"
- layer.enabled: filterButton.hovered && !filterButton.down
- layer.effect: DropShadow {
- horizontalOffset: 1 * virtualstudio.uiScale
- verticalOffset: 1 * virtualstudio.uiScale
- radius: 8.0 * virtualstudio.uiScale
- samples: 17
- color: "#80A1A1A1"
- }
- }
- onClicked: { filterMenu.open(); }
- anchors.right: parent.right
- anchors.verticalCenter: sectionText.verticalCenter
- width: 150 * virtualstudio.uiScale; height: 30 * virtualstudio.uiScale
- Text {
- text: "Filter Studios"
- font.family: "Poppins"
- font.pixelSize: 11 * virtualstudio.fontScale * virtualstudio.uiScale
- anchors.horizontalCenter: parent.horizontalCenter
- anchors.verticalCenter: parent.verticalCenter
- }
- visible: section == virtualstudio.logoSection ? true : false
-
- Popup {
- id: filterMenu
- y: Math.round(parent.height + 8)
- rightMargin: 16 * virtualstudio.uiScale
- width: 210 * virtualstudio.uiScale; height: 64 * virtualstudio.uiScale
- modal: false
- focus: false
- closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
- background: Rectangle {
- radius: 6 * virtualstudio.uiScale
- color: "#F6F8F8"
- border.width: 1
- border.color: "#34979797"
- layer.enabled: true
- layer.effect: DropShadow {
- horizontalOffset: 1 * virtualstudio.uiScale
- verticalOffset: 1 * virtualstudio.uiScale
- radius: 8.0 * virtualstudio.uiScale
- samples: 17
- color: "#80A1A1A1"
- }
- }
- contentItem: Column {
- anchors.fill: parent
- CheckBox {
- id: inactiveCheckbox
- text: qsTr("Show my inactive Studios")
- checkState: virtualstudio.showInactive ? Qt.Checked : Qt.Unchecked
- onClicked: { virtualstudio.showInactive = inactiveCheckbox.checkState == Qt.Checked;
- refreshing = true;
- refresh();
- }
- indicator: Rectangle {
- implicitWidth: 16 * virtualstudio.uiScale
- implicitHeight: 16 * virtualstudio.uiScale
- x: inactiveCheckbox.leftPadding
- y: parent.height / 2 - height / 2
- radius: 3 * virtualstudio.uiScale
- border.color: inactiveCheckbox.down ? "#007AFF" : "#0062cc"
-
- Rectangle {
- width: 10 * virtualstudio.uiScale
- height: 10 * virtualstudio.uiScale
- x: 3 * virtualstudio.uiScale
- y: 3 * virtualstudio.uiScale
- radius: 2 * virtualstudio.uiScale
- color: inactiveCheckbox.down ? "#007AFF" : "#0062cc"
- visible: inactiveCheckbox.checked
- }
- }
- contentItem: Text {
- text: inactiveCheckbox.text
- font.family: "Poppins"
- font.pixelSize: 10 * virtualstudio.fontScale * virtualstudio.uiScale
- anchors.horizontalCenter: parent.horizontalCenter
- anchors.verticalCenter: parent.verticalCenter
- leftPadding: inactiveCheckbox.indicator.width + inactiveCheckbox.spacing
- }
- }
- CheckBox {
- id: selfHostedCheckbox
- text: qsTr("Show self-hosted Studios")
- checkState: virtualstudio.showSelfHosted ? Qt.Checked : Qt.Unchecked
- onClicked: { virtualstudio.showSelfHosted = selfHostedCheckbox.checkState == Qt.Checked;
- refreshing = true;
- refresh();
- }
- indicator: Rectangle {
- implicitWidth: 16 * virtualstudio.uiScale
- implicitHeight: 16 * virtualstudio.uiScale
- x: selfHostedCheckbox.leftPadding
- y: parent.height / 2 - height / 2
- radius: 3 * virtualstudio.uiScale
- border.color: selfHostedCheckbox.down ? "#007AFF" : "#0062CC"
-
- Rectangle {
- width: 10 * virtualstudio.uiScale
- height: 10 * virtualstudio.uiScale
- x: 3 * virtualstudio.uiScale
- y: 3 * virtualstudio.uiScale
- radius: 2 * virtualstudio.uiScale
- color: selfHostedCheckbox.down ? "#007AFF" : "#0062CC"
- visible: selfHostedCheckbox.checked
- }
- }
- contentItem: Text {
- text: selfHostedCheckbox.text
- font.family: "Poppins"
- font.pixelSize: 10 * virtualstudio.fontScale * virtualstudio.uiScale
- anchors.horizontalCenter: parent.horizontalCenter
- anchors.verticalCenter: parent.verticalCenter
- leftPadding: selfHostedCheckbox.indicator.width + selfHostedCheckbox.spacing
- }
- }
- }
- }
- }
- }
- }
-
Component {
id: footer
Rectangle {
manageable: isManageable
available: canConnect
connected: false
+ studioId: id ? id : ""
+ inviteKeyString: inviteKey ? inviteKey : ""
}
- section {property: "type"; criteria: ViewSection.FullString; delegate: sectionHeading }
+ section {property: "type"; criteria: ViewSection.FullString; delegate: SectionHeading {} }
+
+ // Show sectionHeading if there are no Studios in list
+ SectionHeading {
+ id: emptyListSectionHeading
+ listIsEmpty: true
+ visible: parent.count == 0 && !virtualstudio.showCreateStudio
+ }
+
+ Text {
+ id: emptyListMessage
+ visible: parent.count == 0 && !virtualstudio.showCreateStudio
+ text: "No studios found that match your filter criteria"
+ font { family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale }
+ color: textColour
+ width: emptyListMessageWidth
+ wrapMode: Text.Wrap
+ horizontalAlignment: Text.AlignHCenter
+ anchors.horizontalCenter: emptyListSectionHeading.horizontalCenter
+ anchors.verticalCenter: parent.verticalCenter
+ }
+
+ Rectangle {
+ id: newUserEmptyList
+ anchors.fill: parent
+ color: "transparent"
+ visible: parent.count == 0 && virtualstudio.showCreateStudio
+
+ Rectangle {
+ color: "transparent"
+ width: emptyListMessageWidth
+ height: createButton.height + createStudioMessage.height + welcomeMessage.height + createButtonTopMargin + createMessageTopMargin
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.verticalCenter: parent.verticalCenter
+
+ Text {
+ id: welcomeMessage
+ text: "Welcome"
+ font { family: "Poppins"; pixelSize: fontBig * virtualstudio.fontScale * virtualstudio.uiScale; weight: Font.Bold }
+ color: textColour
+ width: emptyListMessageWidth
+ wrapMode: Text.Wrap
+ horizontalAlignment: Text.AlignHCenter
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.top: parent.top
+ }
+
+ Text {
+ id: createStudioMessage
+ text: "JackTrip works by connecting your computer's audio to a Virtual Studio. Create your first Studio to get started!"
+ font { family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale }
+ color: textColour
+ width: emptyListMessageWidth
+ wrapMode: Text.Wrap
+ horizontalAlignment: Text.AlignHCenter
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.top: welcomeMessage.bottom
+ anchors.topMargin: createMessageTopMargin
+ }
+
+ Button {
+ id: createButton
+ background: Rectangle {
+ radius: 6 * virtualstudio.uiScale
+ color: createButton.down ? "#E7E8E8" : "#F2F3F3"
+ border.width: 1
+ border.color: createButton.down ? "#B0B5B5" : "#EAEBEB"
+ layer.enabled: createButton.hovered && !createButton.down
+ layer.effect: DropShadow {
+ horizontalOffset: 1 * virtualstudio.uiScale
+ verticalOffset: 1 * virtualstudio.uiScale
+ radius: 8.0 * virtualstudio.uiScale
+ samples: 17
+ color: "#80A1A1A1"
+ }
+ }
+ onClicked: { virtualstudio.createStudio(); }
+ anchors.top: createStudioMessage.bottom
+ anchors.topMargin: createButtonTopMargin
+ anchors.horizontalCenter: createStudioMessage.horizontalCenter
+ width: 210 * virtualstudio.uiScale; height: 45 * virtualstudio.uiScale
+ Text {
+ text: "Create a Studio"
+ font.family: "Poppins"
+ font.pixelSize: 18 * virtualstudio.fontScale * virtualstudio.uiScale
+ font.weight: Font.Bold
+ color: "#DB0A0A"
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.verticalCenter: parent.verticalCenter
+ }
+ }
+ }
+ }
// Disable momentum scroll
MouseArea {
property bool connecting: false
- property int leftMargin: 16
+ property int leftHeaderMargin: 16
property int fontBig: 28
- property int fontMedium: 18
- property int fontSmall: 11
+ property int fontMedium: 12
+ property int fontSmall: 10
+ property int fontTiny: 8
- property int smallTextPadding: 8
+ property int bodyMargin: 60
property string textColour: virtualstudio.darkMode ? "#FAFBFB" : "#0F0D0D"
+ property string meterColor: virtualstudio.darkMode ? "gray" : "#E0E0E0"
property real imageLightnessValue: virtualstudio.darkMode ? 1.0 : 0.0
+ property string meterGreen: "#61C554"
+ property string meterYellow: "#F5BF4F"
+ property string meterRed: "#F21B1B"
+
+ function getNetworkStatsText (networkStats) {
+ let minRtt = networkStats.minRtt;
+ let maxRtt = networkStats.maxRtt;
+ let avgRtt = networkStats.avgRtt;
+
+ let texts = ["Measuring stats ...", ""];
+
+ if (!minRtt || !maxRtt) {
+ return texts;
+ }
+
+ texts[0] = "<b>" + minRtt + " ms - " + maxRtt + " ms</b>, avg " + avgRtt + " ms round-trip time";
+
+ let quality = "poor";
+ if (avgRtt <= 25) {
+
+ if (maxRtt <= 30) {
+ quality = "excellent";
+ } else {
+ quality = "good";
+ }
+
+ } else if (avgRtt <= 30) {
+ quality = "good";
+ } else if (avgRtt <= 35) {
+ quality = "fair";
+ }
+
+ texts[1] = "Your connection quality is <b>" + quality + "</b>."
+ return texts;
+ }
+
Image {
x: parent.width - (49 * virtualstudio.uiScale); y: 16 * virtualstudio.uiScale
width: 32 * virtualstudio.uiScale; height: 59 * virtualstudio.uiScale
Text {
id: heading
text: virtualstudio.connectionState
- x: leftMargin * virtualstudio.uiScale; y: 34 * virtualstudio.uiScale
+ x: leftHeaderMargin * virtualstudio.uiScale; y: 34 * virtualstudio.uiScale
font { family: "Poppins"; weight: Font.Bold; pixelSize: fontBig * virtualstudio.fontScale * virtualstudio.uiScale }
color: textColour
}
Studio {
- x: parent.leftMargin * virtualstudio.uiScale; y: 96 * virtualstudio.uiScale
+ x: leftHeaderMargin * virtualstudio.uiScale; y: 96 * virtualstudio.uiScale
width: parent.width - (2 * x)
connected: true
serverLocation: virtualstudio.currentStudio >= 0 && virtualstudio.regions[serverModel[virtualstudio.currentStudio].location] ? "in " + virtualstudio.regions[serverModel[virtualstudio.currentStudio].location].label : ""
publicStudio: virtualstudio.currentStudio >= 0 ? serverModel[virtualstudio.currentStudio].isPublic : false
manageable: virtualstudio.currentStudio >= 0 ? serverModel[virtualstudio.currentStudio].isManageable : false
available: virtualstudio.currentStudio >= 0 ? serverModel[virtualstudio.currentStudio].canConnect : false
- }
-
- Image {
- id: mic
- source: "mic.svg"
- x: 80 * virtualstudio.uiScale; y: 250 * virtualstudio.uiScale
- width: 18 * virtualstudio.uiScale; height: 28 * virtualstudio.uiScale
+ studioId: virtualstudio.currentStudio >= 0 ? serverModel[virtualstudio.currentStudio].id : ""
+ inviteKeyString: virtualstudio.currentStudio >= 0 ? serverModel[virtualstudio.currentStudio].inviteKey : ""
}
- Colorize {
- anchors.fill: mic
- source: mic
- hue: 0
- saturation: 0
- lightness: imageLightnessValue
- }
-
- Image {
- id: headphones
- source: "headphones.svg"
- anchors.horizontalCenter: mic.horizontalCenter
- y: 329 * virtualstudio.uiScale
- width: 24 * virtualstudio.uiScale; height: 26 * virtualstudio.uiScale
- }
+ Item {
+ id: inputDevice
+ x: bodyMargin * virtualstudio.uiScale; y: 250 * virtualstudio.uiScale
+ width: Math.min(parent.width / 2, 320 * virtualstudio.uiScale) - x
+ height: 100 * virtualstudio.uiScale
+ clip: true
+
+ Image {
+ id: mic
+ source: "mic.svg"
+ x: 0; y: 0
+ width: 18 * virtualstudio.uiScale; height: 28 * virtualstudio.uiScale
+ }
- Image {
- id: network
- source: "network.svg"
- anchors.horizontalCenter: mic.horizontalCenter
- y: 408 * virtualstudio.uiScale
- width: 28 * virtualstudio.uiScale; height: 28 * virtualstudio.uiScale
- }
+ Colorize {
+ anchors.fill: mic
+ source: mic
+ hue: 0
+ saturation: 0
+ lightness: imageLightnessValue
+ }
- Colorize {
- anchors.fill: headphones
- source: headphones
- hue: 0
- saturation: 0
- lightness: imageLightnessValue
- }
+ Text {
+ id: inputDeviceHeader
+ x: 64 * virtualstudio.uiScale
+ width: parent.width - 64 * virtualstudio.uiScale
+ text: "<b>Input Device</b>"
+ font {family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale }
+ anchors.verticalCenter: mic.verticalCenter
+ color: textColour
+ elide: Text.ElideRight
+ }
- Colorize {
- anchors.fill: network
- source: network
- hue: 0
- saturation: 0
- lightness: imageLightnessValue
- }
-
- Text {
- id: inputDeviceHeader
- x: 120 * virtualstudio.uiScale
- text: virtualstudio.audioBackend == "JACK" ?
- virtualstudio.audioBackend : inputComboModel[virtualstudio.inputDevice]
- font {family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale }
- anchors.verticalCenter: mic.verticalCenter
- color: textColour
- }
-
- Text {
- id: outputDeviceHeader
- x: 120 * virtualstudio.uiScale
- text: virtualstudio.audioBackend == "JACK" ?
- virtualstudio.audioBackend : outputComboModel[virtualstudio.outputDevice]
- font {family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale }
- anchors.verticalCenter: headphones.verticalCenter
- color: textColour
+ Text {
+ id: inputDeviceName
+ width: parent.width - 100 * virtualstudio.uiScale
+ anchors.top: inputDeviceHeader.bottom
+ anchors.left: inputDeviceHeader.left
+ text: virtualstudio.audioBackend == "JACK" ?
+ virtualstudio.audioBackend : inputComboModel[virtualstudio.inputDevice]
+ font {family: "Poppins"; pixelSize: fontTiny * virtualstudio.fontScale * virtualstudio.uiScale }
+ color: textColour
+ elide: Text.ElideRight
+ }
}
- Text {
- id: networkStatsHeader
- x: 120 * virtualstudio.uiScale
- text: "Network"
- font {family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale }
- anchors.verticalCenter: network.verticalCenter
- color: textColour
- }
+ Item {
+ id: outputDevice
+ x: bodyMargin * virtualstudio.uiScale; y: 330 * virtualstudio.uiScale
+ width: Math.min(parent.width / 2, 320 * virtualstudio.uiScale) - x
+ height: 100 * virtualstudio.uiScale
+ clip: true
+
+ Image {
+ id: headphones
+ source: "headphones.svg"
+ x: 0; y: 0
+ width: 28 * virtualstudio.uiScale; height: 28 * virtualstudio.uiScale
+ }
- function getNetworkStatsText (networkStats) {
- let minRtt = networkStats.minRtt;
- let maxRtt = networkStats.maxRtt;
- let avgRtt = networkStats.avgRtt;
+ Colorize {
+ anchors.fill: headphones
+ source: headphones
+ hue: 0
+ saturation: 0
+ lightness: imageLightnessValue
+ }
- let texts = ["Measuring stats ...", "", ""];
+ Text {
+ id: outputDeviceHeader
+ x: 64 * virtualstudio.uiScale
+ width: parent.width - 64 * virtualstudio.uiScale
+ text: "<b>Output Device</b>"
+ font {family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale }
+ anchors.verticalCenter: headphones.verticalCenter
+ color: textColour
+ elide: Text.ElideRight
+ }
- if (!minRtt || !maxRtt) {
- return texts;
+ Text {
+ id: outputDeviceName
+ width: parent.width - 100 * virtualstudio.uiScale
+ anchors.top: outputDeviceHeader.bottom
+ anchors.left: outputDeviceHeader.left
+ text: virtualstudio.audioBackend == "JACK" ?
+ virtualstudio.audioBackend : outputComboModel[virtualstudio.outputDevice]
+ font {family: "Poppins"; pixelSize: fontTiny * virtualstudio.fontScale * virtualstudio.uiScale }
+ color: textColour
+ elide: Text.ElideRight
}
+ }
- texts[0] = "<b>" + minRtt + " ms - " + maxRtt + " ms</b>, avg " + avgRtt + " ms round-trip time";
+ Meter {
+ id: inputDeviceMeters
+ x: inputDevice.x + inputDevice.width; y: 250 * virtualstudio.uiScale
+ width: parent.width - inputDevice.width - 2 * bodyMargin * virtualstudio.uiScale
+ height: 100 * virtualstudio.uiScale
+ model: inputMeterModel
+ clipped: inputClipped
+ }
- let quality = "poor";
- if (avgRtt <= 25) {
+ Meter {
+ id: outputDeviceMeters
+ x: outputDevice.x + outputDevice.width; y: 330 * virtualstudio.uiScale
+ width: parent.width - inputDevice.width - 2 * bodyMargin * virtualstudio.uiScale
+ height: 100 * virtualstudio.uiScale
+ model: outputMeterModel
+ clipped: outputClipped
+ }
- if (maxRtt <= 30) {
- quality = "excellent";
- } else {
- quality = "good";
- }
+ Item {
+ id: networkStatsHeader
+ x: bodyMargin * virtualstudio.uiScale; y: 410 * virtualstudio.uiScale
+ width: Math.min(parent.width / 2, 320 * virtualstudio.uiScale) - x
+ height: 128 * virtualstudio.uiScale
+
+ Image {
+ id: network
+ source: "network.svg"
+ x: 0; y: 0
+ width: 28 * virtualstudio.uiScale; height: 28 * virtualstudio.uiScale
+ }
- } else if (avgRtt <= 30) {
- quality = "good";
- } else if (avgRtt <= 35) {
- quality = "fair";
+ Colorize {
+ anchors.fill: network
+ source: network
+ hue: 0
+ saturation: 0
+ lightness: imageLightnessValue
}
- texts[1] = "Your connection quality is <b>" + quality + "</b>."
- return texts;
+ Text {
+ id: networkStatsHeaderText
+ text: "<b>Network</b>"
+ font {family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale }
+ x: 64 * virtualstudio.uiScale
+ anchors.verticalCenter: network.verticalCenter
+ color: textColour
+ }
}
- Text {
- id: netstat0
- text: getNetworkStatsText(virtualstudio.networkStats)[0]
- font {family: "Poppins"; pixelSize: fontSmall * virtualstudio.fontScale * virtualstudio.uiScale }
- topPadding: smallTextPadding
- anchors.left: inputDeviceHeader.left
- anchors.top: networkStatsHeader.bottom
- color: textColour
- }
+ Item {
+ id: networkStatsText
+ x: networkStatsHeader.x + networkStatsHeader.width; y: 410 * virtualstudio.uiScale
+ width: parent.width - networkStatsHeader.width - 2 * bodyMargin * virtualstudio.uiScale
+ height: 128 * virtualstudio.uiScale
+
+ Text {
+ id: netstat0
+ x: 0; y: 0
+ text: getNetworkStatsText(virtualstudio.networkStats)[0]
+ font {family: "Poppins"; pixelSize: fontTiny * virtualstudio.fontScale * virtualstudio.uiScale }
+ color: textColour
+ }
- Text {
- id: netstat1
- text: getNetworkStatsText(virtualstudio.networkStats)[1]
- font {family: "Poppins"; pixelSize: fontSmall * virtualstudio.fontScale * virtualstudio.uiScale }
- topPadding: smallTextPadding
- anchors.left: inputDeviceHeader.left
- anchors.top: netstat0.bottom
- color: textColour
+ Text {
+ id: netstat1
+ x: 0
+ text: getNetworkStatsText(virtualstudio.networkStats)[1]
+ font {family: "Poppins"; pixelSize: fontTiny * virtualstudio.fontScale * virtualstudio.uiScale }
+ topPadding: 8 * virtualstudio.uiScale
+ anchors.top: netstat0.bottom
+ color: textColour
+ }
}
}
--- /dev/null
+import QtQuick 2.12
+import QtQuick.Controls 2.12
+import QtGraphicalEffects 1.12
+
+Item {
+ required property var model
+ property int bins: 15
+
+ property int innerMargin: 2 * virtualstudio.uiScale
+ property int clipWidth: 10 * virtualstudio.uiScale
+ required property bool clipped
+
+ property string meterColor: virtualstudio.darkMode ? "#5B5858" : "#D3D4D4"
+
+ property string meterGreen: "#61C554"
+ property string meterYellow: "#F5BF4F"
+ property string meterRed: "#F21B1B"
+
+ function getBoxColor (idx, level) {
+
+ // Case where the meter should be filled
+ if (level > (idx / bins)) {
+ let fillColor = meterGreen;
+ if (idx > 8 && idx <= 11) {
+ fillColor = meterYellow;
+ } else if (idx > 11) {
+ fillColor = meterRed;
+ }
+ return fillColor;
+
+ // Case where the meter should not be filled
+ } else {
+ return meterColor
+ }
+ }
+
+ ListView {
+ id: meters
+ x: 0; y: 0
+ width: parent.width - clipWidth
+ height: parent.height
+ model: parent.model
+
+ delegate: Item {
+ x: 0;
+ width: parent.width
+ height: 14 * virtualstudio.uiScale
+ required property var modelData
+
+ property int boxHeight: 10 * virtualstudio.uiScale
+ property int boxWidth: (width / bins) - innerMargin
+ property int boxRadius: 4 * virtualstudio.uiScale
+
+ Rectangle {
+ id: box0
+ x: 0;
+ y: 0;
+ width: boxWidth
+ height: boxHeight
+ color: getBoxColor(0, parent.modelData.level)
+ radius: boxRadius
+ }
+
+ Rectangle {
+ id: box1
+ x: boxWidth + innerMargin;
+ y: 0;
+ width: boxWidth
+ height: boxHeight
+ color: getBoxColor(1, parent.modelData.level)
+ radius: boxRadius
+ }
+
+ Rectangle {
+ id: box2
+ x: (boxWidth) * 2 + innerMargin * 2;
+ y: 0;
+ width: boxWidth
+ height: boxHeight
+ color: getBoxColor(2, parent.modelData.level)
+ radius: boxRadius
+ }
+
+ Rectangle {
+ id: box3
+ x: (boxWidth) * 3 + innerMargin * 3;
+ y: 0;
+ width: boxWidth
+ height: boxHeight
+ color: getBoxColor(3, parent.modelData.level)
+ radius: boxRadius
+ }
+
+ Rectangle {
+ id: box4
+ x: (boxWidth) * 4 + innerMargin * 4;
+ y: 0;
+ width: boxWidth
+ height: boxHeight
+ color: getBoxColor(4, parent.modelData.level)
+ radius: boxRadius
+ }
+
+ Rectangle {
+ id: box5
+ x: (boxWidth) * 5 + innerMargin * 5;
+ y: 0;
+ width: boxWidth
+ height: boxHeight
+ color: getBoxColor(5, parent.modelData.level)
+ radius: boxRadius
+ }
+
+ Rectangle {
+ id: box6
+ x: (boxWidth) * 6 + innerMargin * 6;
+ y: 0;
+ width: boxWidth
+ height: boxHeight
+ color: getBoxColor(6, parent.modelData.level)
+ radius: boxRadius
+ }
+
+ Rectangle {
+ id: box7
+ x: (boxWidth) * 7 + innerMargin * 7;
+ y: 0;
+ width: boxWidth
+ height: boxHeight
+ color: getBoxColor(7, parent.modelData.level)
+ radius: boxRadius
+ }
+
+ Rectangle {
+ id: box8
+ x: (boxWidth) * 8 + innerMargin * 8;
+ y: 0;
+ width: boxWidth
+ height: boxHeight
+ color: getBoxColor(8, parent.modelData.level)
+ radius: boxRadius
+ }
+
+ Rectangle {
+ id: box9
+ x: (boxWidth) * 9 + innerMargin * 9;
+ y: 0;
+ width: boxWidth
+ height: boxHeight
+ color: getBoxColor(9, parent.modelData.level)
+ radius: boxRadius
+ }
+
+ Rectangle {
+ id: box10
+ x: (boxWidth) * 10 + innerMargin * 10;
+ y: 0;
+ width: boxWidth
+ height: boxHeight
+ color: getBoxColor(10, parent.modelData.level)
+ radius: boxRadius
+ }
+
+ Rectangle {
+ id: box11
+ x: (boxWidth) * 11 + innerMargin * 11;
+ y: 0;
+ width: boxWidth
+ height: boxHeight
+ color: getBoxColor(11, parent.modelData.level)
+ radius: boxRadius
+ }
+
+ Rectangle {
+ id: box12
+ x: (boxWidth) * 12 + innerMargin * 12;
+ y: 0;
+ width: boxWidth
+ height: boxHeight
+ color: getBoxColor(12, parent.modelData.level)
+ radius: boxRadius
+ }
+
+ Rectangle {
+ id: box13
+ x: (boxWidth) * 13 + innerMargin * 13;
+ y: 0;
+ width: boxWidth
+ height: boxHeight
+ color: getBoxColor(13, parent.modelData.level)
+ radius: boxRadius
+ }
+
+ Rectangle {
+ id: box14
+ x: (boxWidth) * 14 + innerMargin * 14;
+ y: 0;
+ width: boxWidth
+ height: boxHeight
+ color: getBoxColor(14, parent.modelData.level)
+ radius: boxRadius
+ }
+ }
+ }
+
+ Rectangle {
+ id: clipIndicator
+ x: meters.x + meters.width; y: 0
+ width: Math.min(clipWidth, ((parent.width - clipWidth) / bins) - innerMargin)
+ height: 24 * virtualstudio.uiScale
+ radius: 4 * virtualstudio.uiScale
+ color: clipped ? meterRed : meterColor
+ }
+}
\ No newline at end of file
--- /dev/null
+import QtQuick 2.12
+import QtQuick.Controls 2.12
+import QtGraphicalEffects 1.12
+
+Rectangle {
+ property bool listIsEmpty: false
+ // required property string section: section (for 5.15)
+ color: "transparent"
+ height: 72 * virtualstudio.uiScale
+ x: 16 * virtualstudio.uiScale
+ y: listIsEmpty ? 16 * virtualstudio.uiScale : 0
+ width: listIsEmpty ? parent.width - (2 * x) : ListView.view.width - (2 * x)
+ Text {
+ id: sectionText
+ //anchors.bottom: parent.bottom
+ y: 12 * virtualstudio.uiScale
+ // text: parent.section (for 5.15)
+ width: parent.width - 332 * virtualstudio.uiScale
+ fontSizeMode: Text.HorizontalFit
+ text: listIsEmpty ? "No Studios" : section
+ font { family: "Poppins"; pixelSize: 28 * virtualstudio.fontScale * virtualstudio.uiScale; weight: Font.Bold }
+ color: textColour
+ verticalAlignment: Text.AlignBottom
+ }
+ Button {
+ id: createButton
+ background: Rectangle {
+ radius: 6 * virtualstudio.uiScale
+ color: createButton.down ? "#E7E8E8" : "#F2F3F3"
+ border.width: 1
+ border.color: createButton.down ? "#B0B5B5" : "#EAEBEB"
+ layer.enabled: createButton.hovered && !createButton.down
+ layer.effect: DropShadow {
+ horizontalOffset: 1 * virtualstudio.uiScale
+ verticalOffset: 1 * virtualstudio.uiScale
+ radius: 8.0 * virtualstudio.uiScale
+ samples: 17
+ color: "#80A1A1A1"
+ }
+ }
+ onClicked: { virtualstudio.createStudio(); }
+ anchors.right: filterButton.left
+ anchors.rightMargin: 16
+ anchors.verticalCenter: sectionText.verticalCenter
+ width: 150 * virtualstudio.uiScale; height: 30 * virtualstudio.uiScale
+ Text {
+ text: "Create a Studio"
+ font.family: "Poppins"
+ font.pixelSize: 11 * virtualstudio.fontScale * virtualstudio.uiScale
+ font.weight: Font.Bold
+ color: "#DB0A0A"
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.verticalCenter: parent.verticalCenter
+ }
+ visible: listIsEmpty ? true : (section == virtualstudio.logoSection ? true : false)
+ }
+ Button {
+ id: filterButton
+ background: Rectangle {
+ radius: 6 * virtualstudio.uiScale
+ color: filterButton.down ? "#E7E8E8" : "#F2F3F3"
+ border.width: 1
+ border.color: filterButton.down ? "#B0B5B5" : "#EAEBEB"
+ layer.enabled: filterButton.hovered && !filterButton.down
+ layer.effect: DropShadow {
+ horizontalOffset: 1 * virtualstudio.uiScale
+ verticalOffset: 1 * virtualstudio.uiScale
+ radius: 8.0 * virtualstudio.uiScale
+ samples: 17
+ color: "#80A1A1A1"
+ }
+ }
+ onClicked: { filterMenu.open(); }
+ anchors.right: parent.right
+ anchors.verticalCenter: sectionText.verticalCenter
+ width: 150 * virtualstudio.uiScale; height: 30 * virtualstudio.uiScale
+ Text {
+ text: "Filter Studios"
+ font.family: "Poppins"
+ font.pixelSize: 11 * virtualstudio.fontScale * virtualstudio.uiScale
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.verticalCenter: parent.verticalCenter
+ }
+ visible: listIsEmpty ? true : (section == virtualstudio.logoSection ? true : false)
+
+ Popup {
+ id: filterMenu
+ y: Math.round(parent.height + 8)
+ rightMargin: 16 * virtualstudio.uiScale
+ width: 210 * virtualstudio.uiScale; height: 64 * virtualstudio.uiScale
+ modal: false
+ focus: false
+ closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
+ background: Rectangle {
+ radius: 6 * virtualstudio.uiScale
+ color: "#F6F8F8"
+ border.width: 1
+ border.color: "#34979797"
+ layer.enabled: true
+ layer.effect: DropShadow {
+ horizontalOffset: 1 * virtualstudio.uiScale
+ verticalOffset: 1 * virtualstudio.uiScale
+ radius: 8.0 * virtualstudio.uiScale
+ samples: 17
+ color: "#80A1A1A1"
+ }
+ }
+ contentItem: Column {
+ anchors.fill: parent
+ CheckBox {
+ id: inactiveCheckbox
+ text: qsTr("Show my inactive Studios")
+ checkState: virtualstudio.showInactive ? Qt.Checked : Qt.Unchecked
+ onClicked: { virtualstudio.showInactive = inactiveCheckbox.checkState == Qt.Checked;
+ refreshing = true;
+ refresh();
+ }
+ indicator: Rectangle {
+ implicitWidth: 16 * virtualstudio.uiScale
+ implicitHeight: 16 * virtualstudio.uiScale
+ x: inactiveCheckbox.leftPadding
+ y: parent.height / 2 - height / 2
+ radius: 3 * virtualstudio.uiScale
+ border.color: inactiveCheckbox.down ? "#007AFF" : "#0062cc"
+
+ Rectangle {
+ width: 10 * virtualstudio.uiScale
+ height: 10 * virtualstudio.uiScale
+ x: 3 * virtualstudio.uiScale
+ y: 3 * virtualstudio.uiScale
+ radius: 2 * virtualstudio.uiScale
+ color: inactiveCheckbox.down ? "#007AFF" : "#0062cc"
+ visible: inactiveCheckbox.checked
+ }
+ }
+ contentItem: Text {
+ text: inactiveCheckbox.text
+ font.family: "Poppins"
+ font.pixelSize: 10 * virtualstudio.fontScale * virtualstudio.uiScale
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.verticalCenter: parent.verticalCenter
+ leftPadding: inactiveCheckbox.indicator.width + inactiveCheckbox.spacing
+ }
+ }
+ CheckBox {
+ id: selfHostedCheckbox
+ text: qsTr("Show self-hosted Studios")
+ checkState: virtualstudio.showSelfHosted ? Qt.Checked : Qt.Unchecked
+ onClicked: { virtualstudio.showSelfHosted = selfHostedCheckbox.checkState == Qt.Checked;
+ refreshing = true;
+ refresh();
+ }
+ indicator: Rectangle {
+ implicitWidth: 16 * virtualstudio.uiScale
+ implicitHeight: 16 * virtualstudio.uiScale
+ x: selfHostedCheckbox.leftPadding
+ y: parent.height / 2 - height / 2
+ radius: 3 * virtualstudio.uiScale
+ border.color: selfHostedCheckbox.down ? "#007AFF" : "#0062CC"
+
+ Rectangle {
+ width: 10 * virtualstudio.uiScale
+ height: 10 * virtualstudio.uiScale
+ x: 3 * virtualstudio.uiScale
+ y: 3 * virtualstudio.uiScale
+ radius: 2 * virtualstudio.uiScale
+ color: selfHostedCheckbox.down ? "#007AFF" : "#0062CC"
+ visible: selfHostedCheckbox.checked
+ }
+ }
+ contentItem: Text {
+ text: selfHostedCheckbox.text
+ font.family: "Poppins"
+ font.pixelSize: 10 * virtualstudio.fontScale * virtualstudio.uiScale
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.verticalCenter: parent.verticalCenter
+ leftPadding: selfHostedCheckbox.indicator.width + selfHostedCheckbox.spacing
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
color: appearanceBtn.down ? buttonPressedColour : (appearanceBtn.hovered || settingsGroupView == "Appearance" ? buttonHoverColour : backgroundColour)
}
}
+ Button {
+ id: advancedBtn
+ text: "Advanced"
+ width: parent.width
+ contentItem: Label {
+ text: advancedBtn.text
+ font { family: "Poppins"; pixelSize: fontSmall * virtualstudio.fontScale * virtualstudio.uiScale }
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ color: textColour
+ }
+ background: Rectangle {
+ width: parent.width
+ color: advancedBtn.down ? buttonPressedColour : (advancedBtn.hovered || settingsGroupView == "Advanced" ? buttonHoverColour : backgroundColour)
+ }
+ }
Button {
id: profileBtn
text: "Profile"
}
Button {
- id: modeButton
+ id: darkButton
background: Rectangle {
radius: 6 * virtualstudio.uiScale
- color: modeButton.down ? buttonPressedColour : (modeButton.hovered ? buttonHoverColour : buttonColour)
+ color: darkButton.down ? buttonPressedColour : (darkButton.hovered ? buttonHoverColour : buttonColour)
border.width: 1
- border.color: modeButton.down ? buttonPressedStroke : (modeButton.hovered ? buttonHoverStroke : buttonStroke)
+ border.color: darkButton.down ? buttonPressedStroke : (darkButton.hovered ? buttonHoverStroke : buttonStroke)
}
- onClicked: { window.state = "login"; virtualstudio.toStandard(); }
- x: parent.width - (232 * virtualstudio.uiScale); y: scaleSlider.y + (56 * virtualstudio.uiScale)
+ onClicked: { virtualstudio.darkMode = !virtualstudio.darkMode; }
+ x: parent.width - (232 * virtualstudio.uiScale); y: modeButton.y + (56 * virtualstudio.uiScale)
width: 216 * virtualstudio.uiScale; height: 30 * virtualstudio.uiScale
Text {
- text: virtualstudio.psiBuild ? "Switch to Standard Mode" : "Switch to Classic Mode"
+ text: virtualstudio.darkMode ? "Switch to Light Mode" : "Switch to Dark Mode"
font { family: "Poppins"; pixelSize: fontSmall * virtualstudio.fontScale * virtualstudio.uiScale }
anchors { horizontalCenter: parent.horizontalCenter; verticalCenter: parent.verticalCenter }
color: textColour
}
Text {
- anchors.verticalCenter: modeButton.verticalCenter
+ anchors.verticalCenter: darkButton.verticalCenter
x: leftMargin * virtualstudio.uiScale
- text: "Display Mode"
+ text: "Color Theme"
font { family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale }
color: textColour
}
-
+ }
+
+ Rectangle {
+ id: advancedSettingsView
+ width: 0.8 * parent.width
+ height: parent.height - header.height
+ x: 0.2 * window.width
+ y: header.height
+ color: backgroundColour
+ visible: settingsGroupView == "Advanced"
+
Button {
- id: darkButton
+ id: modeButton
background: Rectangle {
radius: 6 * virtualstudio.uiScale
- color: darkButton.down ? buttonPressedColour : (darkButton.hovered ? buttonHoverColour : buttonColour)
+ color: modeButton.down ? buttonPressedColour : (modeButton.hovered ? buttonHoverColour : buttonColour)
border.width: 1
- border.color: darkButton.down ? buttonPressedStroke : (darkButton.hovered ? buttonHoverStroke : buttonStroke)
+ border.color: modeButton.down ? buttonPressedStroke : (modeButton.hovered ? buttonHoverStroke : buttonStroke)
}
- onClicked: { virtualstudio.darkMode = !virtualstudio.darkMode; }
- x: parent.width - (232 * virtualstudio.uiScale); y: modeButton.y + (56 * virtualstudio.uiScale)
+ onClicked: { window.state = "login"; virtualstudio.toStandard(); }
+ x: 234 * virtualstudio.uiScale; y: 100 * virtualstudio.uiScale
width: 216 * virtualstudio.uiScale; height: 30 * virtualstudio.uiScale
Text {
- text: virtualstudio.darkMode ? "Switch to Light Mode" : "Switch to Dark Mode"
+ text: virtualstudio.psiBuild ? "Switch to Standard Mode" : "Switch to Classic Mode"
font { family: "Poppins"; pixelSize: fontSmall * virtualstudio.fontScale * virtualstudio.uiScale }
anchors { horizontalCenter: parent.horizontalCenter; verticalCenter: parent.verticalCenter }
color: textColour
}
Text {
- anchors.verticalCenter: darkButton.verticalCenter
+ anchors.verticalCenter: modeButton.verticalCenter
x: leftMargin * virtualstudio.uiScale
- text: "Color Theme"
+ text: "Display Mode"
font { family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale }
color: textColour
}
ComboBox {
id: updateChannelCombo
- x: 234 * virtualstudio.uiScale; y: darkButton.y + (56 * virtualstudio.uiScale)
+ x: 234 * virtualstudio.uiScale; y: modeButton.y + (48 * virtualstudio.uiScale)
width: parent.width - x - (16 * virtualstudio.uiScale); height: 36 * virtualstudio.uiScale
model: updateChannelComboModel
currentIndex: virtualstudio.updateChannel == "stable" ? 0 : 1
color: textColour
visible: !virtualstudio.noUpdater
}
+
+ ComboBox {
+ id: bufferStrategyCombo
+ x: updateChannelCombo.x; y: updateChannelCombo.y + (48 * virtualstudio.uiScale)
+ width: updateChannelCombo.width; height: updateChannelCombo.height
+ model: bufferStrategyComboModel
+ currentIndex: virtualstudio.bufferStrategy
+ onActivated: { virtualstudio.bufferStrategy = currentIndex }
+ font.family: "Poppins"
+ visible: virtualstudio.audioBackend != "JACK"
+ }
+
+ Text {
+ anchors.verticalCenter: bufferStrategyCombo.verticalCenter
+ x: 48 * virtualstudio.uiScale
+ text: "Buffer Strategy"
+ font { family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale }
+ visible: virtualstudio.audioBackend != "JACK"
+ color: textColour
+ }
}
Rectangle {
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtGraphicalEffects 1.12
+import VS 1.0
Rectangle {
width: 664; height: 83 * virtualstudio.uiScale
property string serverLocation: "Germany - Berlin"
property string flagImage: "flags/DE.svg"
property string studioName: "Test Studio"
+ property string studioId: ""
+ property string inviteKeyString: ""
property bool publicStudio: false
property bool manageable: false
property bool available: true
property bool connected: false
+ property bool inviteCopied: false
property int leftMargin: 81
property int topMargin: 13
+ property int bottomToolTipMargin: 8
+ property int rightToolTipMargin: 4
property real fontBig: 18
property real fontMedium: 11
property string backgroundColour: virtualstudio.darkMode ? "#494646" : "#F4F6F6"
property string textColour: virtualstudio.darkMode ? "#FAFBFB" : "#0F0D0D"
- property string shadowColour: virtualstudio.darkMode ? "40000000" : "#80A1A1A1"
+ property string shadowColour: virtualstudio.darkMode ? "#40000000" : "#80A1A1A1"
+ property string toolTipBackgroundColour: inviteCopied ? "#57B147" : (virtualstudio.darkMode ? "#323232" : "#F3F3F3")
+ property string toolTipTextColour: inviteCopied ? "#FAFBFB" : textColour
property string joinColour: virtualstudio.darkMode ? (connected ? "#FCB6B6" : "#E2EBE0") : (connected ? "#FCB6B6" : "#C4F4BE")
property string joinHoverColour: virtualstudio.darkMode ? (connected ? "#D49696" : "#BAC7B8") : (connected ? "#E3A4A4" : "#B0DCAB")
property string joinPressedColour: virtualstudio.darkMode ? (connected ? "#F2AEAE" : "#D8E2D6") : (connected ? "#EFADAD" : "#BAE8B5")
property string managePressedColour: virtualstudio.darkMode ? "#E4E5E5" : "#EAEBEB"
property string manageStroke: virtualstudio.darkMode ? "#8B8D8D" : "#949494"
+ Clipboard {
+ id: clipboard
+ }
+
Rectangle {
id: shadow
anchors.fill: parent
Text {
x: leftMargin * virtualstudio.uiScale; y: 11 * virtualstudio.uiScale;
- width: manageable ? parent.width - (233 * virtualstudio.uiScale) : parent.width - (156 * virtualstudio.uiScale)
+ width: manageable ? parent.width - (310 * virtualstudio.uiScale) : parent.width - (233 * virtualstudio.uiScale)
text: studioName
+ fontSizeMode: Text.HorizontalFit
font { family: "Poppins"; weight: Font.Bold; pixelSize: fontBig * virtualstudio.fontScale * virtualstudio.uiScale }
elide: Text.ElideRight
+ verticalAlignment: Text.AlignVCenter
color: textColour
}
Button {
id: joinButton
- x: manageable ? parent.width - (142 * virtualstudio.uiScale) : parent.width - (65 * virtualstudio.uiScale)
+ x: manageable ? parent.width - (219 * virtualstudio.uiScale) : parent.width - (142 * virtualstudio.uiScale)
y: topMargin * virtualstudio.uiScale; width: 40 * virtualstudio.uiScale; height: width
background: Rectangle {
radius: width / 2
visible: connected || canConnect || canStart
color: textColour
}
+
+ Button {
+ id: inviteButton
+ x: manageable ? parent.width - (142 * virtualstudio.uiScale) : parent.width - (65 * virtualstudio.uiScale)
+ y: topMargin * virtualstudio.uiScale; width: 40 * virtualstudio.uiScale; height: width
+ background: Rectangle {
+ radius: width / 2
+ color: inviteButton.down ? managePressedColour : (inviteButton.hovered ? manageHoverColour : manageColour)
+ border.width: inviteButton.down ? 1 : 0
+ border.color: manageStroke
+ }
+ Timer {
+ id: copiedResetTimer
+ interval: 2000; running: false; repeat: false
+ onTriggered: inviteCopied = false;
+ }
+ onClicked: {
+ inviteCopied = true;
+ if (!inviteKeyString) {
+ clipboard.setText(qsTr("https://app.jacktrip.org/studios/" + studioId + "?invited=true"));
+ } else {
+ clipboard.setText(qsTr("https://app.jacktrip.org/studios/" + studioId + "?invited=" + inviteKeyString));
+ }
+ copiedResetTimer.restart()
+ }
+ visible: true
+ Image {
+ width: 20 * virtualstudio.uiScale; height: width
+ anchors { verticalCenter: parent.verticalCenter; horizontalCenter: parent.horizontalCenter }
+ source: "share.svg"
+ }
+ ToolTip {
+ parent: inviteButton
+ visible: inviteButton.hovered || inviteCopied
+ bottomPadding: bottomToolTipMargin * virtualstudio.uiScale
+ rightPadding: rightToolTipMargin * virtualstudio.uiScale
+ delay: 100
+ contentItem: Rectangle {
+ color: toolTipBackgroundColour
+ radius: 3
+ anchors.fill: parent
+ anchors.bottomMargin: bottomToolTipMargin * virtualstudio.uiScale
+ anchors.rightMargin: rightToolTipMargin * virtualstudio.uiScale
+ layer.enabled: true
+ layer.effect: DropShadow {
+ horizontalOffset: 1 * virtualstudio.uiScale
+ verticalOffset: 1 * virtualstudio.uiScale
+ radius: 10.0 * virtualstudio.uiScale
+ samples: 21
+ color: shadowColour
+ }
+
+ Text {
+ anchors.centerIn: parent
+ font { family: "Poppins"; pixelSize: fontSmall * virtualstudio.fontScale * virtualstudio.uiScale}
+ text: inviteCopied ? qsTr("📋 Copied invitation link to Clipboard") : qsTr("Copy invite link for Studio")
+ color: toolTipTextColour
+ }
+ }
+ background: Rectangle {
+ color: "transparent"
+ }
+ }
+ }
+
+ Text {
+ anchors.horizontalCenter: inviteButton.horizontalCenter
+ y: 56 * virtualstudio.uiScale
+ text: "Invite"
+ font { family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale }
+ visible: true
+ color: textColour
+ }
Button {
id: manageButton
<file>Studio.qml</file>
<file>Browse.qml</file>
<file>Settings.qml</file>
+ <file>Meter.qml</file>
<file>Connected.qml</file>
<file>Failed.qml</file>
<file>Setup.qml</file>
+ <file>SectionHeading.qml</file>
<file>logo.svg</file>
<file>wedge.svg</file>
<file>wedge_inactive.svg</file>
<file>join.svg</file>
<file>leave.svg</file>
<file>manage.svg</file>
+ <file>share.svg</file>
<file>cog.svg</file>
<file>mic.svg</file>
<file>ethernet.png</file>
--- /dev/null
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M18.8377 23.1914C17.9686 23.1914 17.2242 22.8816 16.6047 22.2621C15.9852 21.6426 15.6755 20.8983 15.6755 20.0291C15.6755 19.8997 15.6893 19.7471 15.7171 19.5714C15.7448 19.3957 15.7864 19.2339 15.8419 19.086L7.43689 14.2039C7.1595 14.5183 6.81738 14.7725 6.41054 14.9667C6.0037 15.1609 5.58761 15.258 5.16227 15.258C4.29311 15.258 3.54877 14.9482 2.92927 14.3287C2.30976 13.7092 2 12.9649 2 12.0957C2 11.208 2.30976 10.4591 2.92927 9.84882C3.54877 9.23856 4.29311 8.93343 5.16227 8.93343C5.58761 8.93343 5.99445 9.01664 6.3828 9.18308C6.77115 9.34951 7.12252 9.58992 7.43689 9.9043L15.8419 5.07767C15.7864 4.94822 15.7448 4.80028 15.7171 4.63384C15.6893 4.46741 15.6755 4.31022 15.6755 4.16227C15.6755 3.27462 15.9852 2.52566 16.6047 1.9154C17.2242 1.30513 17.9686 1 18.8377 1C19.7254 1 20.4743 1.30513 21.0846 1.9154C21.6949 2.52566 22 3.27462 22 4.16227C22 5.03144 21.6949 5.77577 21.0846 6.39529C20.4743 7.01479 19.7254 7.32455 18.8377 7.32455C18.4124 7.32455 18.0009 7.2552 17.6033 7.11651C17.2057 6.97781 16.8682 6.75127 16.5908 6.43689L8.18585 11.0971C8.22284 11.245 8.2552 11.4161 8.28294 11.6103C8.31068 11.8044 8.32455 11.9663 8.32455 12.0957C8.32455 12.2252 8.31068 12.3638 8.28294 12.5118C8.2552 12.6597 8.22284 12.8077 8.18585 12.9556L16.5908 17.7268C16.8682 17.4679 17.1919 17.2598 17.5617 17.1026C17.9316 16.9454 18.3569 16.8669 18.8377 16.8669C19.7254 16.8669 20.4743 17.172 21.0846 17.7822C21.6949 18.3925 22 19.1415 22 20.0291C22 20.8983 21.6949 21.6426 21.0846 22.2621C20.4743 22.8816 19.7254 23.1914 18.8377 23.1914Z" fill="#000000"/>
+</svg>
// Set our font scaling to convert points to pixels
m_fontScale = 4.0 / 3.0;
-#ifdef RT_AUDIO
+ // Initialize timers needed for clip indicators
+ m_inputClipTimer.setTimerType(Qt::PreciseTimer);
+ m_inputClipTimer.setSingleShot(true);
+ m_inputClipTimer.setInterval(3000);
+ m_outputClipTimer.setTimerType(Qt::PreciseTimer);
+ m_outputClipTimer.setSingleShot(true);
+ m_outputClipTimer.setInterval(3000);
+
+ m_inputClipTimer.callOnTimeout([&]() {
+ m_view.engine()->rootContext()->setContextProperty(QStringLiteral("inputClipped"),
+ QVariant::fromValue(false));
+ });
+
+ m_outputClipTimer.callOnTimeout([&]() {
+ m_view.engine()->rootContext()->setContextProperty(
+ QStringLiteral("outputClipped"), QVariant::fromValue(false));
+ });
+
settings.beginGroup(QStringLiteral("Audio"));
- m_useRtAudio = settings.value(QStringLiteral("Backend"), 0).toInt() == 1;
- m_inputDevice = settings.value(QStringLiteral("InputDevice"), "").toString();
- m_outputDevice = settings.value(QStringLiteral("OutputDevice"), "").toString();
- m_bufferSize = settings.value(QStringLiteral("BufferSize"), 128).toInt();
- settings.endGroup();
+#ifdef RT_AUDIO
+ m_useRtAudio = settings.value(QStringLiteral("Backend"), 0).toInt() == 1;
+ m_inputDevice = settings.value(QStringLiteral("InputDevice"), "").toString();
+ m_outputDevice = settings.value(QStringLiteral("OutputDevice"), "").toString();
+ m_bufferSize = settings.value(QStringLiteral("BufferSize"), 128).toInt();
m_previousBuffer = m_bufferSize;
refreshDevices();
m_previousInput = m_inputDevice;
QStringLiteral("outputComboModel"),
QVariant::fromValue(QStringList(QLatin1String(""))));
#endif
+ m_bufferStrategy = settings.value(QStringLiteral("BufferStrategy"), 0).toInt();
+ settings.endGroup();
#ifdef USE_WEAK_JACK
// Check if Jack is available
m_view.engine()->rootContext()->setContextProperty(
QStringLiteral("bufferComboModel"), QVariant::fromValue(m_bufferOptions));
+ m_view.engine()->rootContext()->setContextProperty(
+ QStringLiteral("bufferStrategyComboModel"),
+ QVariant::fromValue(m_bufferStrategyOptions));
m_view.engine()->rootContext()->setContextProperty(
QStringLiteral("updateChannelComboModel"),
QVariant::fromValue(m_updateChannelOptions));
this);
m_view.engine()->rootContext()->setContextProperty(QStringLiteral("serverModel"),
QVariant::fromValue(m_servers));
+
+ m_view.engine()->rootContext()->setContextProperty(
+ QStringLiteral("inputMeterModel"), QVariant::fromValue(QVector<float>()));
+ m_view.engine()->rootContext()->setContextProperty(
+ QStringLiteral("outputMeterModel"), QVariant::fromValue(QVector<float>()));
+ m_view.engine()->rootContext()->setContextProperty(QStringLiteral("inputClipped"),
+ QVariant::fromValue(false));
+ m_view.engine()->rootContext()->setContextProperty(QStringLiteral("outputClipped"),
+ QVariant::fromValue(false));
+
m_view.engine()->rootContext()->setContextProperty(
QStringLiteral("backendComboModel"),
QVariant::fromValue(QStringList()
sendHeartbeat();
});
- // Connect joinStudio callbacks
- connect(this, &VirtualStudio::studioToJoinChanged, this, &VirtualStudio::joinStudio);
// QueuedConnection since refreshFinished is sometimes signaled from a network reply
// thread
connect(this, &VirtualStudio::refreshFinished, this, &VirtualStudio::joinStudio,
#endif
}
+int VirtualStudio::bufferStrategy()
+{
+ return m_bufferStrategy;
+}
+
+void VirtualStudio::setBufferStrategy(int index)
+{
+ m_bufferStrategy =
+ index >= 0 ? index
+ : m_bufferStrategyOptions.indexOf(QStringLiteral("Minimal Latency"));
+ QSettings settings;
+ settings.beginGroup(QStringLiteral("Audio"));
+ settings.setValue(QStringLiteral("BufferStrategy"), m_bufferStrategy);
+ settings.endGroup();
+}
+
int VirtualStudio::currentStudio()
{
return m_currentStudio;
settings.endGroup();
}
+bool VirtualStudio::showCreateStudio()
+{
+ return m_showCreateStudio;
+}
+
+void VirtualStudio::setShowCreateStudio(bool createStudio)
+{
+ m_showCreateStudio = createStudio;
+ emit showCreateStudioChanged();
+}
+
bool VirtualStudio::showDeviceSetup()
{
return m_showDeviceSetup;
// device setup view proceeds warning view
// if device setup is shown, do not immediately join
if (!m_showDeviceSetup) {
+ // We're done waiting to be on the browse page
+ m_shouldJoin = true;
joinStudio();
}
}
void VirtualStudio::setStudioToJoin(const QUrl& url)
{
m_studioToJoin = url;
- emit studioToJoinChanged();
}
bool VirtualStudio::noUpdater()
return m_failedMessage;
}
+bool VirtualStudio::shouldJoin()
+{
+ return m_shouldJoin;
+}
+
+void VirtualStudio::setShouldJoin(bool join)
+{
+ m_shouldJoin = join;
+}
+
void VirtualStudio::joinStudio()
{
if (!m_authenticated || m_studioToJoin.isEmpty() || m_servers.isEmpty()) {
// getServerList emits refreshFinished which
// will come back to this function.
if (m_authenticated && !m_studioToJoin.isEmpty() && m_servers.isEmpty()) {
- getServerList(true);
+ getServerList(true, true);
}
return;
}
+ if (!m_shouldJoin) {
+ // Not time to join yet.
+ // Waiting until joinStudio is called and m_shouldJoin is true.
+ return;
+ }
+
QString scheme = m_studioToJoin.scheme();
QString path = m_studioToJoin.path();
QString url = m_studioToJoin.toString();
emit hasRefreshTokenChanged();
}
-void VirtualStudio::refreshStudios(int index)
+void VirtualStudio::refreshStudios(int index, bool signalRefresh)
{
- getServerList(false, index);
+ getServerList(false, signalRefresh, index);
}
void VirtualStudio::refreshDevices()
// this function is called after the device setup view
// which can display upon opening the app from join link
if (!m_studioToJoin.isEmpty()) {
+ // We're done waiting to be on the browse page
+ m_shouldJoin = true;
joinStudio();
}
}
}
m_jackTripRunning = true;
- m_connectionState = QStringLiteral("Connecting...");
+ m_connectionState = QStringLiteral("Preparing audio...");
emit connectionStateChanged();
VsServerInfo* studioInfo = static_cast<VsServerInfo*>(m_servers.at(m_currentStudio));
try {
buffer_size = m_bufferSize;
}
#endif
- JackTrip* jackTrip =
- m_device->initJackTrip(m_useRtAudio, input, output, buffer_size, studioInfo);
+ JackTrip* jackTrip = m_device->initJackTrip(
+ m_useRtAudio, input, output, buffer_size, m_bufferStrategy, studioInfo);
QObject::connect(jackTrip, &JackTrip::signalProcessesStopped, this,
&VirtualStudio::processFinished, Qt::QueuedConnection);
&VirtualStudio::receivedConnectionFromPeer,
Qt::QueuedConnection);
+ Meter* m_outputMeter = new Meter(jackTrip->getNumOutputChannels());
+ jackTrip->appendProcessPluginFromNetwork(m_outputMeter);
+ connect(m_outputMeter, &Meter::onComputedVolumeMeasurements, this,
+ &VirtualStudio::updatedOutputVuMeasurements);
+
+ Meter* m_inputMeter = new Meter(jackTrip->getNumInputChannels());
+ jackTrip->appendProcessPluginToNetwork(m_inputMeter);
+ connect(m_inputMeter, &Meter::onComputedVolumeMeasurements, this,
+ &VirtualStudio::updatedInputVuMeasurements);
+
+ m_connectionState = QStringLiteral("Connecting...");
+ emit connectionStateChanged();
+#ifdef RT_AUDIO
+ if (m_useRtAudio) {
+ // This is a hack. RtAudio::openStream blocks the UI thread.
+ // But I am not comfortable changing how all of JackTrip consumes
+ // RtAudio to fix a VS mode bug.
+ delay(805);
+ }
+#endif
m_device->startJackTrip();
+
+ m_view.engine()->rootContext()->setContextProperty(
+ QStringLiteral("inputMeterModel"),
+ QVariant::fromValue(QVector<float>(jackTrip->getNumInputChannels())));
+
+ m_view.engine()->rootContext()->setContextProperty(
+ QStringLiteral("outputMeterModel"),
+ QVariant::fromValue(QVector<float>(jackTrip->getNumOutputChannels())));
+
m_device->startPinger(studioInfo);
} catch (const std::exception& e) {
// Let the user know what our exception was.
if (!m_studioToJoin.isEmpty()) {
// FTUX shows warnings and device setup views
// if any of these enabled, do not immediately join
- if (!m_showWarnings && !m_showDeviceSetup) {
+ if (!m_showDeviceSetup) {
+ // Don't need to set m_shouldJoin because it's default true
joinStudio();
}
}
return;
}
+void VirtualStudio::updatedInputVuMeasurements(const QVector<float> valuesInDecibels)
+{
+ QJsonArray uiValues;
+ bool detectedClip = false;
+
+ // Always output 2 meter readings to the UI
+ for (int i = 0; i < 2; i++) {
+ // Determine decibel reading
+ float dB = m_meterMin;
+ if (i < valuesInDecibels.size()) {
+ dB = std::max(m_meterMin, valuesInDecibels[i]);
+ }
+
+ // Produce a normalized value from 0 to 1
+ float meter = (dB - m_meterMin) / (m_meterMax - m_meterMin);
+
+ QJsonObject object{{QStringLiteral("dB"), dB}, {QStringLiteral("level"), meter}};
+ uiValues.push_back(object);
+
+ // Signal a clip if we haven't done so already
+ if (dB >= -0.05 && !detectedClip) {
+ m_inputClipTimer.start();
+ m_view.engine()->rootContext()->setContextProperty(
+ QStringLiteral("inputClipped"), QVariant::fromValue(true));
+ detectedClip = true;
+ }
+ }
+
+ m_view.engine()->rootContext()->setContextProperty(QStringLiteral("inputMeterModel"),
+ QVariant::fromValue(uiValues));
+}
+
+void VirtualStudio::updatedOutputVuMeasurements(const QVector<float> valuesInDecibels)
+{
+ QJsonArray uiValues;
+ bool detectedClip = false;
+
+ // Always output 2 meter readings to the UI
+ for (int i = 0; i < 2; i++) {
+ // Determine decibel reading
+ float dB = m_meterMin;
+ if (i < valuesInDecibels.size()) {
+ dB = std::max(m_meterMin, valuesInDecibels[i]);
+ }
+
+ // Produce a normalized value from 0 to 1
+ float meter = (dB - m_meterMin) / (m_meterMax - m_meterMin);
+
+ QJsonObject object{{QStringLiteral("dB"), dB}, {QStringLiteral("level"), meter}};
+ uiValues.push_back(object);
+
+ // Signal a clip if we haven't done so already
+ if (dB >= -0.05 && !detectedClip) {
+ m_outputClipTimer.start();
+ m_view.engine()->rootContext()->setContextProperty(
+ QStringLiteral("outputClipped"), QVariant::fromValue(true));
+ detectedClip = true;
+ }
+ }
+
+ m_view.engine()->rootContext()->setContextProperty(QStringLiteral("outputMeterModel"),
+ QVariant::fromValue(uiValues));
+}
+
void VirtualStudio::setupAuthenticator()
{
if (m_authenticator.isNull()) {
void VirtualStudio::sendHeartbeat()
{
- if (m_device != nullptr && m_connectionState != "Connecting...") {
+ if (m_device != nullptr && m_connectionState != "Connecting..."
+ && m_connectionState != "Preparing audio...") {
m_device->sendHeartbeat();
}
}
-void VirtualStudio::getServerList(bool firstLoad, int index)
+void VirtualStudio::getServerList(bool firstLoad, bool signalRefresh, int index)
{
{
QMutexLocker locker(&m_refreshMutex);
QNetworkReply* reply =
m_authenticator->get(QStringLiteral("https://app.jacktrip.org/api/servers"));
- connect(reply, &QNetworkReply::finished, this, [&, reply, topServerId, firstLoad]() {
- if (reply->error() != QNetworkReply::NoError) {
- std::cout << "Error: " << reply->errorString().toStdString() << std::endl;
- emit authFailed();
- reply->deleteLater();
- return;
- }
+ connect(
+ reply, &QNetworkReply::finished, this,
+ [&, reply, topServerId, firstLoad, signalRefresh]() {
+ if (reply->error() != QNetworkReply::NoError) {
+ std::cout << "Error: " << reply->errorString().toStdString() << std::endl;
+ emit authFailed();
+ reply->deleteLater();
+ return;
+ }
- QByteArray response = reply->readAll();
- QJsonDocument serverList = QJsonDocument::fromJson(response);
- if (!serverList.isArray()) {
- std::cout << "Error: Not an array" << std::endl;
- QMutexLocker locker(&m_refreshMutex);
- m_refreshInProgress = false;
- emit authFailed();
- reply->deleteLater();
- return;
- }
- QJsonArray servers = serverList.array();
- // Divide our servers by category initially so that they're easier to sort
- QList<QObject*> yourServers;
- QList<QObject*> subServers;
- QList<QObject*> pubServers;
-
- for (int i = 0; i < servers.count(); i++) {
- if (servers.at(i)[QStringLiteral("type")].toString().contains(
- QStringLiteral("JackTrip"))) {
- VsServerInfo* serverInfo = new VsServerInfo(this);
- serverInfo->setIsManageable(
- servers.at(i)[QStringLiteral("admin")].toBool());
- QString status = servers.at(i)[QStringLiteral("status")].toString();
- bool activeStudio = status == QLatin1String("Ready");
- bool hostedStudio = servers.at(i)[QStringLiteral("managed")].toBool();
- // Only iterate through servers that we want to show
- if (!m_showSelfHosted && !hostedStudio) {
- continue;
- }
- if (!m_showInactive && !activeStudio) {
- continue;
- }
- if (activeStudio || (serverInfo->isManageable() && m_showInactive)) {
- serverInfo->setName(servers.at(i)[QStringLiteral("name")].toString());
- serverInfo->setHost(
- servers.at(i)[QStringLiteral("serverHost")].toString());
- serverInfo->setStatus(
- servers.at(i)[QStringLiteral("status")].toString());
- serverInfo->setPort(
- servers.at(i)[QStringLiteral("serverPort")].toInt());
- serverInfo->setIsPublic(
- servers.at(i)[QStringLiteral("public")].toBool());
- serverInfo->setRegion(
- servers.at(i)[QStringLiteral("region")].toString());
- serverInfo->setPeriod(
- servers.at(i)[QStringLiteral("period")].toInt());
- serverInfo->setSampleRate(
- servers.at(i)[QStringLiteral("sampleRate")].toInt());
- serverInfo->setQueueBuffer(
- servers.at(i)[QStringLiteral("queueBuffer")].toInt());
- serverInfo->setBannerURL(
- servers.at(i)[QStringLiteral("bannerURL")].toString());
- serverInfo->setId(servers.at(i)[QStringLiteral("id")].toString());
- serverInfo->setSessionId(
- servers.at(i)[QStringLiteral("sessionId")].toString());
- if (servers.at(i)[QStringLiteral("owner")].toBool()) {
- yourServers.append(serverInfo);
- serverInfo->setSection(VsServerInfo::YOUR_STUDIOS);
- } else if (m_subscribedServers.contains(serverInfo->id())) {
- subServers.append(serverInfo);
- serverInfo->setSection(VsServerInfo::SUBSCRIBED_STUDIOS);
- } else {
- pubServers.append(serverInfo);
+ QByteArray response = reply->readAll();
+ QJsonDocument serverList = QJsonDocument::fromJson(response);
+ if (!serverList.isArray()) {
+ std::cout << "Error: Not an array" << std::endl;
+ QMutexLocker locker(&m_refreshMutex);
+ m_refreshInProgress = false;
+ emit authFailed();
+ reply->deleteLater();
+ return;
+ }
+ QJsonArray servers = serverList.array();
+ // Divide our servers by category initially so that they're easier to sort
+ QList<QObject*> yourServers;
+ QList<QObject*> subServers;
+ QList<QObject*> pubServers;
+ int skippedStudios = 0;
+
+ for (int i = 0; i < servers.count(); i++) {
+ if (servers.at(i)[QStringLiteral("type")].toString().contains(
+ QStringLiteral("JackTrip"))) {
+ VsServerInfo* serverInfo = new VsServerInfo(this);
+ serverInfo->setIsManageable(
+ servers.at(i)[QStringLiteral("admin")].toBool());
+ QString status = servers.at(i)[QStringLiteral("status")].toString();
+ bool activeStudio = status == QLatin1String("Ready");
+ bool hostedStudio = servers.at(i)[QStringLiteral("managed")].toBool();
+ // Only iterate through servers that we want to show
+ if (!m_showSelfHosted && !hostedStudio) {
+ if (activeStudio || (serverInfo->isManageable())) {
+ skippedStudios++;
+ }
+ continue;
+ }
+ if (!m_showInactive && !activeStudio) {
+ if (serverInfo->isManageable()) {
+ skippedStudios++;
+ }
+ continue;
+ }
+ if (activeStudio || (serverInfo->isManageable() && m_showInactive)) {
+ serverInfo->setName(
+ servers.at(i)[QStringLiteral("name")].toString());
+ serverInfo->setHost(
+ servers.at(i)[QStringLiteral("serverHost")].toString());
+ serverInfo->setStatus(
+ servers.at(i)[QStringLiteral("status")].toString());
+ serverInfo->setPort(
+ servers.at(i)[QStringLiteral("serverPort")].toInt());
+ serverInfo->setIsPublic(
+ servers.at(i)[QStringLiteral("public")].toBool());
+ serverInfo->setRegion(
+ servers.at(i)[QStringLiteral("region")].toString());
+ serverInfo->setPeriod(
+ servers.at(i)[QStringLiteral("period")].toInt());
+ serverInfo->setSampleRate(
+ servers.at(i)[QStringLiteral("sampleRate")].toInt());
+ serverInfo->setQueueBuffer(
+ servers.at(i)[QStringLiteral("queueBuffer")].toInt());
+ serverInfo->setBannerURL(
+ servers.at(i)[QStringLiteral("bannerURL")].toString());
+ serverInfo->setId(servers.at(i)[QStringLiteral("id")].toString());
+ serverInfo->setSessionId(
+ servers.at(i)[QStringLiteral("sessionId")].toString());
+ serverInfo->setInviteKey(
+ servers.at(i)[QStringLiteral("inviteKey")].toString());
+ if (servers.at(i)[QStringLiteral("owner")].toBool()) {
+ yourServers.append(serverInfo);
+ serverInfo->setSection(VsServerInfo::YOUR_STUDIOS);
+ } else if (m_subscribedServers.contains(serverInfo->id())) {
+ subServers.append(serverInfo);
+ serverInfo->setSection(VsServerInfo::SUBSCRIBED_STUDIOS);
+ } else {
+ pubServers.append(serverInfo);
+ }
}
}
}
- }
- std::sort(yourServers.begin(), yourServers.end(),
- [](QObject* first, QObject* second) {
- return static_cast<VsServerInfo*>(first)->name()
- < static_cast<VsServerInfo*>(second)->name();
- });
- std::sort(subServers.begin(), subServers.end(),
- [](QObject* first, QObject* second) {
- return static_cast<VsServerInfo*>(first)->name()
- < static_cast<VsServerInfo*>(second)->name();
- });
- std::sort(pubServers.begin(), pubServers.end(),
- [](QObject* first, QObject* second) {
- return static_cast<VsServerInfo*>(first)->name()
- < static_cast<VsServerInfo*>(second)->name();
- });
-
- // If we don't have any owned servers, move the JackTrip logo to an appropriate
- // section header.
- if (yourServers.isEmpty()) {
- if (subServers.isEmpty()) {
- m_logoSection = QStringLiteral("Public Studios");
+ std::sort(yourServers.begin(), yourServers.end(),
+ [](QObject* first, QObject* second) {
+ return static_cast<VsServerInfo*>(first)->name()
+ < static_cast<VsServerInfo*>(second)->name();
+ });
+ std::sort(subServers.begin(), subServers.end(),
+ [](QObject* first, QObject* second) {
+ return static_cast<VsServerInfo*>(first)->name()
+ < static_cast<VsServerInfo*>(second)->name();
+ });
+ std::sort(pubServers.begin(), pubServers.end(),
+ [](QObject* first, QObject* second) {
+ return static_cast<VsServerInfo*>(first)->name()
+ < static_cast<VsServerInfo*>(second)->name();
+ });
+
+ // If we don't have any owned servers, move the JackTrip logo to an
+ // appropriate section header.
+ if (yourServers.isEmpty()) {
+ if (subServers.isEmpty()) {
+ m_logoSection = QStringLiteral("Public Studios");
+
+ if (pubServers.isEmpty() && skippedStudios == 0) {
+ // This is a new user
+ setShowCreateStudio(true);
+ } else {
+ // This is not a new user.
+ // Set to false in case the studio created since refreshing.
+ setShowCreateStudio(false);
+ }
+ } else {
+ m_logoSection = QStringLiteral("Subscribed Studios");
+ }
+ emit logoSectionChanged();
} else {
- m_logoSection = QStringLiteral("Subscribed Studios");
+ m_logoSection = QStringLiteral("Your Studios");
+ emit logoSectionChanged();
}
- emit logoSectionChanged();
- } else {
- m_logoSection = QStringLiteral("Your Studios");
- emit logoSectionChanged();
- }
- QMutexLocker locker(&m_refreshMutex);
- // Check that we haven't tried connecting to a server between the
- // request going out and the response.
- if (!m_allowRefresh) {
- m_refreshInProgress = false;
- return;
- }
- m_servers.clear();
- m_servers.append(yourServers);
- m_servers.append(subServers);
- m_servers.append(pubServers);
- m_view.engine()->rootContext()->setContextProperty(
- QStringLiteral("serverModel"), QVariant::fromValue(m_servers));
- int index = -1;
- if (!topServerId.isEmpty()) {
- for (int i = 0; i < m_servers.count(); i++) {
- if (static_cast<VsServerInfo*>(m_servers.at(i))->id() == topServerId) {
- index = i;
- break;
+ QMutexLocker locker(&m_refreshMutex);
+ // Check that we haven't tried connecting to a server between the
+ // request going out and the response.
+ if (!m_allowRefresh) {
+ m_refreshInProgress = false;
+ return;
+ }
+ m_servers.clear();
+ m_servers.append(yourServers);
+ m_servers.append(subServers);
+ m_servers.append(pubServers);
+ m_view.engine()->rootContext()->setContextProperty(
+ QStringLiteral("serverModel"), QVariant::fromValue(m_servers));
+ int index = -1;
+ if (!topServerId.isEmpty()) {
+ for (int i = 0; i < m_servers.count(); i++) {
+ if (static_cast<VsServerInfo*>(m_servers.at(i))->id()
+ == topServerId) {
+ index = i;
+ break;
+ }
}
}
- }
- if (firstLoad) {
- emit authSucceeded();
- emit refreshFinished(index);
- m_refreshTimer.setInterval(10000);
- m_refreshTimer.start();
- m_heartbeatTimer.setInterval(5000);
- m_heartbeatTimer.start();
- } else {
- emit refreshFinished(index);
- }
+ if (firstLoad) {
+ emit authSucceeded();
+ m_refreshTimer.setInterval(10000);
+ m_refreshTimer.start();
+ m_heartbeatTimer.setInterval(5000);
+ m_heartbeatTimer.start();
+ }
- m_refreshInProgress = false;
+ if (signalRefresh) {
+ emit refreshFinished(index);
+ }
- reply->deleteLater();
- });
+ m_refreshInProgress = false;
+
+ reply->deleteLater();
+ });
}
void VirtualStudio::getUserId()
m_subscribedServers.append(
subscriptions.at(i)[QStringLiteral("serverId")].toString());
}
- getServerList(true);
+ getServerList(true, false);
reply->deleteLater();
});
}
delete m_servers.at(i);
}
+ delete m_inputMeter;
+ delete m_outputMeter;
+
QDesktopServices::unsetUrlHandler("jacktrip");
}
#ifndef VIRTUALSTUDIO_H
#define VIRTUALSTUDIO_H
+#include <QEventLoop>
#include <QList>
#include <QMutex>
#include <QScopedPointer>
#include <QSharedPointer>
#include <QTimer>
+#include <QVector>
#include <QtNetworkAuth>
#include "../JackTrip.h"
+#include "../Meter.h"
#include "vsDevice.h"
#include "vsQuickView.h"
#include "vsServerInfo.h"
outputDeviceChanged)
Q_PROPERTY(
int bufferSize READ bufferSize WRITE setBufferSize NOTIFY bufferSizeChanged)
+ Q_PROPERTY(int bufferStrategy READ bufferStrategy WRITE setBufferStrategy NOTIFY
+ bufferStrategyChanged)
Q_PROPERTY(int currentStudio READ currentStudio NOTIFY currentStudioChanged)
Q_PROPERTY(QJsonObject regions READ regions NOTIFY regionsChanged)
Q_PROPERTY(QJsonObject userMetadata READ userMetadata NOTIFY userMetadataChanged)
showInactiveChanged)
Q_PROPERTY(bool showSelfHosted READ showSelfHosted WRITE setShowSelfHosted NOTIFY
showSelfHostedChanged)
+ Q_PROPERTY(bool showCreateStudio READ showCreateStudio WRITE setShowCreateStudio
+ NOTIFY showCreateStudioChanged)
Q_PROPERTY(QString connectionState READ connectionState NOTIFY connectionStateChanged)
Q_PROPERTY(QJsonObject networkStats READ networkStats NOTIFY networkStatsChanged)
+
Q_PROPERTY(QString updateChannel READ updateChannel WRITE setUpdateChannel NOTIFY
updateChannelChanged)
Q_PROPERTY(float fontScale READ fontScale CONSTANT)
Q_PROPERTY(bool noUpdater READ noUpdater CONSTANT)
Q_PROPERTY(bool psiBuild READ psiBuild CONSTANT)
Q_PROPERTY(QString failedMessage READ failedMessage NOTIFY failedMessageChanged)
+ Q_PROPERTY(
+ bool shouldJoin READ shouldJoin WRITE setShouldJoin NOTIFY shouldJoinChanged)
public:
explicit VirtualStudio(bool firstRun = false, QObject* parent = nullptr);
void setOutputDevice(int device);
int bufferSize();
void setBufferSize(int index);
+ int bufferStrategy();
+ void setBufferStrategy(int index);
int currentStudio();
QJsonObject regions();
QJsonObject userMetadata();
QString connectionState();
QJsonObject networkStats();
+ QVector<float> inputMeterLevels();
+ QVector<float> outputMeterLevels();
QString updateChannel();
void setUpdateChannel(const QString& channel);
bool showInactive();
void setShowInactive(bool inactive);
bool showSelfHosted();
void setShowSelfHosted(bool selfHosted);
+ bool showCreateStudio();
+ void setShowCreateStudio(bool createStudio);
float fontScale();
float uiScale();
void setUiScale(float scale);
bool noUpdater();
bool psiBuild();
QString failedMessage();
+ bool shouldJoin();
+ void setShouldJoin(bool join);
public slots:
void toStandard();
void toVirtualStudio();
void login();
void logout();
- void refreshStudios(int index);
+ void refreshStudios(int index, bool signalRefresh = false);
void refreshDevices();
void revertSettings();
void applySettings();
void inputDeviceChanged();
void outputDeviceChanged();
void bufferSizeChanged();
+ void bufferStrategyChanged();
void currentStudioChanged();
void regionsChanged();
void userMetadataChanged();
void showInactiveChanged();
void showSelfHostedChanged();
+ void showCreateStudioChanged();
void connectionStateChanged();
void networkStatsChanged();
void updateChannelChanged();
void uiScaleChanged();
void newScale();
void darkModeChanged();
- void studioToJoinChanged();
void signalExit();
void periodicRefresh();
void failedMessageChanged();
+ void shouldJoinChanged();
private slots:
void slotAuthSucceded();
void launchBrowser(const QUrl& url);
void joinStudio();
void updatedStats(const QJsonObject& stats);
+ void updatedInputVuMeasurements(const QVector<float> valuesInDecibels);
+ void updatedOutputVuMeasurements(const QVector<float> valuesInDecibels);
private:
void setupAuthenticator();
void sendHeartbeat();
- void getServerList(bool firstLoad = false, int index = -1);
+ void getServerList(bool firstLoad = false, bool signalRefresh = false,
+ int index = -1);
void getUserId();
void getSubscriptions();
void getRegions();
bool m_showFirstRun = false;
bool m_checkSsl = true;
+ bool m_shouldJoin = true;
QString m_updateChannel;
QString m_refreshToken;
QString m_userId;
bool m_isExiting = false;
bool m_showInactive = false;
bool m_showSelfHosted = false;
+ bool m_showCreateStudio = false;
bool m_showDeviceSetup = true;
bool m_showWarnings = true;
float m_fontScale = 1;
float m_uiScale;
float m_previousUiScale;
+ int m_bufferStrategy = 0;
bool m_darkMode = false;
QString m_failedMessage = "";
QUrl m_studioToJoin;
bool m_authenticated = false;
+ Meter* m_inputMeter;
+ Meter* m_outputMeter;
+ QTimer m_inputClipTimer;
+ QTimer m_outputClipTimer;
+
+ float m_meterMax = 0.0;
+ float m_meterMin = -64.0;
+
#ifdef RT_AUDIO
QStringList m_inputDeviceList;
QStringList m_outputDeviceList;
QString m_previousOutput;
quint16 m_previousBuffer;
bool m_previousUseRtAudio = false;
+ inline void delay(int millisecondsWait)
+ {
+ QEventLoop loop;
+ QTimer t;
+ t.connect(&t, &QTimer::timeout, &loop, &QEventLoop::quit);
+ t.start(millisecondsWait);
+ loop.exec();
+ }
#endif
- QStringList m_bufferOptions = {"16", "32", "64", "128", "256", "512", "1024"};
- QStringList m_updateChannelOptions = {"Stable", "Edge"};
+ QStringList m_bufferOptions = {"16", "32", "64", "128", "256", "512", "1024"};
+ QStringList m_bufferStrategyOptions = {"Minimal Latency", "Stable Latency",
+ "Loss Concealment"};
+ QStringList m_updateChannelOptions = {"Stable", "Edge"};
#ifdef __APPLE__
NoNap m_noNap;
target: virtualstudio
onAuthSucceeded: {
if (virtualstudio.showDeviceSetup) {
+ virtualstudio.shouldJoin = false;
window.state = "setup";
} else {
+ virtualstudio.shouldJoin = true;
window.state = "browse";
}
}
m_apiSecret = settings.value(QStringLiteral("ApiSecret"), "").toString();
m_appUUID = settings.value(QStringLiteral("AppUUID"), "").toString();
m_appID = settings.value(QStringLiteral("AppID"), "").toString();
-
- sendHeartbeat();
}
// registerApp idempotently registers an emulated device belonging to the current user
reply->deleteLater();
return;
}
+ } else if (m_apiPrefix != "" && m_apiSecret != "") {
+ sendHeartbeat();
}
QSettings settings;
[[maybe_unused]] std::string input,
[[maybe_unused]] std::string output,
[[maybe_unused]] int bufferSize,
+ [[maybe_unused]] int bufferStrategy,
VsServerInfo* studioInfo)
{
m_jackTrip.reset(new JackTrip(JackTrip::CLIENTTOPINGSERVER, JackTrip::UDP, 2, 2,
}
#endif
m_jackTrip->setRemoteClientName(m_appID);
- m_jackTrip->setBufferStrategy(1);
+ // increment m_bufferStrategy by 1 for array-index mapping
+ m_jackTrip->setBufferStrategy(bufferStrategy + 1);
m_jackTrip->setBufferQueueLength(-500);
m_jackTrip->setPeerAddress(studioInfo->host());
m_jackTrip->setPeerPorts(studioInfo->port());
settings.beginGroup(QStringLiteral("VirtualStudio"));
settings.setValue(QStringLiteral("AppID"), m_appID);
settings.endGroup();
+
+ sendHeartbeat();
}
reply->deleteLater();
void sendHeartbeat();
void setServerId(QString studioID);
JackTrip* initJackTrip(bool useRtAudio, std::string input, std::string output,
- int bufferSize, VsServerInfo* studioInfo);
+ int bufferSize, int bufferStrategy, VsServerInfo* studioInfo);
void startJackTrip();
void stopJackTrip();
void reconcileAgentConfig(QJsonDocument newState);
--- /dev/null
+#ifndef VSQMLCLIPBOARD_H
+#define VSQMLCLIPBOARD_H
+
+#include <QApplication>
+#include <QClipboard>
+#include <QObject>
+
+class VsQmlClipboard : public QObject
+{
+ Q_OBJECT
+ public:
+ explicit VsQmlClipboard(QObject* parent = 0) : QObject(parent)
+ {
+ clipboard = QApplication::clipboard();
+ }
+
+ Q_INVOKABLE void setText(QString text)
+ {
+ clipboard->setText(text, QClipboard::Clipboard);
+ }
+
+ private:
+ QClipboard* clipboard;
+};
+
+#endif // VSQMLCLIPBOARD_H
\ No newline at end of file
m_sessionId = sessionId;
}
+QString VsServerInfo::inviteKey()
+{
+ return m_inviteKey;
+}
+
+void VsServerInfo::setInviteKey(const QString& inviteKey)
+{
+ m_inviteKey = inviteKey;
+}
+
VsServerInfo::~VsServerInfo() = default;
Q_PROPERTY(quint32 sampleRate READ sampleRate CONSTANT)
Q_PROPERTY(quint16 queueBuffer READ queueBuffer CONSTANT)
Q_PROPERTY(QString status READ status CONSTANT)
+ Q_PROPERTY(QString id READ id CONSTANT)
+ Q_PROPERTY(QString inviteKey READ inviteKey CONSTANT)
public:
enum serverSectionT { YOUR_STUDIOS, SUBSCRIBED_STUDIOS, PUBLIC_STUDIOS };
void setSessionId(const QString& sessionId);
QString status();
void setStatus(const QString& status);
+ QString inviteKey();
+ void setInviteKey(const QString& inviteKey);
signals:
void canConnectChanged();
QString m_id;
QString m_sessionId;
QString m_status;
+ QString m_inviteKey;
/* Remaining JSON fields
"loopback": true,
"createdAt": "2021-09-07T17:15:38Z",
"expiresAt": "2021-09-07T17:15:38Z",
"updatedAt": "2021-09-07T17:15:38Z"
+ "inviteKey": "invitestring",
*/
};
#include "AudioInterface.h"
-constexpr const char* const gVersion = "1.6.3"; ///< JackTrip version
+constexpr const char* const gVersion = "1.6.4"; ///< JackTrip version
//*******************************************************************************
/// \name Default Values
#include "JTApplication.h"
#include "gui/virtualstudio.h"
+#include "gui/vsQmlClipboard.h"
#include "gui/vsUrlHandler.h"
#endif
}
#ifndef NO_VS
+ // Register clipboard Qml type
+ qmlRegisterType<VsQmlClipboard>("VS", 1, 0, "Clipboard");
+
// Parse command line for deep link
QCommandLineOption deeplinkOption(QStringList() << QStringLiteral("deeplink"));
deeplinkOption.setValueName(QStringLiteral("deeplink"));
--- /dev/null
+/* ------------------------------------------------------------
+author: "Dominick Hing"
+license: "MIT Style STK-4.2"
+name: "meter"
+version: "1.0"
+Code generated with Faust 2.40.0 (https://faust.grame.fr)
+Compilation options: -a faust2header.cpp -lang cpp -i -inpl -cn meterdsp -es 1 -mcd 16
+-single -ftz 0
+------------------------------------------------------------ */
+
+#ifndef __METERDSP_H__
+#define __METERDSP_H__
+
+// NOTE: ANY INCLUDE-GUARD HERE MUST BE DERIVED FROM THE CLASS NAME
+//
+// faust2header.cpp - FAUST Architecture File
+// This is a simple variation of matlabplot.cpp in the Faust distribution
+// aimed at creating a simple C++ header file (.h) containing a Faust DSP.
+// See the Makefile for how to use it.
+
+/************************** BEGIN dsp.h ********************************
+ FAUST Architecture File
+ Copyright (C) 2003-2022 GRAME, Centre National de Creation Musicale
+ ---------------------------------------------------------------------
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+ EXCEPTION : As a special exception, you may create a larger work
+ that contains this FAUST architecture section and distribute
+ that work under terms of your choice, so long as this FAUST
+ architecture section is not modified.
+ ************************************************************************/
+
+#ifndef __dsp__
+#define __dsp__
+
+#include <string>
+#include <vector>
+
+#ifndef FAUSTFLOAT
+#define FAUSTFLOAT float
+#endif
+
+struct UI;
+struct Meta;
+
+/**
+ * DSP memory manager.
+ */
+
+struct dsp_memory_manager {
+ virtual ~dsp_memory_manager() {}
+
+ /**
+ * Inform the Memory Manager with the number of expected memory zones.
+ * @param count - the number of expected memory zones
+ */
+ virtual void begin(size_t /*count*/) {}
+
+ /**
+ * Give the Memory Manager information on a given memory zone.
+ * @param size - the size in bytes of the memory zone
+ * @param reads - the number of Read access to the zone used to compute one frame
+ * @param writes - the number of Write access to the zone used to compute one frame
+ */
+ virtual void info(size_t /*size*/, size_t /*reads*/, size_t /*writes*/) {}
+
+ /**
+ * Inform the Memory Manager that all memory zones have been described,
+ * to possibly start a 'compute the best allocation strategy' step.
+ */
+ virtual void end() {}
+
+ /**
+ * Allocate a memory zone.
+ * @param size - the memory zone size in bytes
+ */
+ virtual void* allocate(size_t size) = 0;
+
+ /**
+ * Destroy a memory zone.
+ * @param ptr - the memory zone pointer to be deallocated
+ */
+ virtual void destroy(void* ptr) = 0;
+};
+
+/**
+ * Signal processor definition.
+ */
+
+class dsp
+{
+ public:
+ dsp() {}
+ virtual ~dsp() {}
+
+ /* Return instance number of audio inputs */
+ virtual int getNumInputs() = 0;
+
+ /* Return instance number of audio outputs */
+ virtual int getNumOutputs() = 0;
+
+ /**
+ * Trigger the ui_interface parameter with instance specific calls
+ * to 'openTabBox', 'addButton', 'addVerticalSlider'... in order to build the UI.
+ *
+ * @param ui_interface - the user interface builder
+ */
+ virtual void buildUserInterface(UI* ui_interface) = 0;
+
+ /* Return the sample rate currently used by the instance */
+ virtual int getSampleRate() = 0;
+
+ /**
+ * Global init, calls the following methods:
+ * - static class 'classInit': static tables initialization
+ * - 'instanceInit': constants and instance state initialization
+ *
+ * @param sample_rate - the sampling rate in Hz
+ */
+ virtual void init(int sample_rate) = 0;
+
+ /**
+ * Init instance state
+ *
+ * @param sample_rate - the sampling rate in Hz
+ */
+ virtual void instanceInit(int sample_rate) = 0;
+
+ /**
+ * Init instance constant state
+ *
+ * @param sample_rate - the sampling rate in Hz
+ */
+ virtual void instanceConstants(int sample_rate) = 0;
+
+ /* Init default control parameters values */
+ virtual void instanceResetUserInterface() = 0;
+
+ /* Init instance state (like delay lines...) but keep the control parameter values */
+ virtual void instanceClear() = 0;
+
+ /**
+ * Return a clone of the instance.
+ *
+ * @return a copy of the instance on success, otherwise a null pointer.
+ */
+ virtual dsp* clone() = 0;
+
+ /**
+ * Trigger the Meta* parameter with instance specific calls to 'declare' (key, value)
+ * metadata.
+ *
+ * @param m - the Meta* meta user
+ */
+ virtual void metadata(Meta* m) = 0;
+
+ /**
+ * DSP instance computation, to be called with successive in/out audio buffers.
+ *
+ * @param count - the number of frames to compute
+ * @param inputs - the input audio buffers as an array of non-interleaved FAUSTFLOAT
+ * samples (eiher float, double or quad)
+ * @param outputs - the output audio buffers as an array of non-interleaved FAUSTFLOAT
+ * samples (eiher float, double or quad)
+ *
+ */
+ virtual void compute(int count, FAUSTFLOAT** inputs, FAUSTFLOAT** outputs) = 0;
+
+ /**
+ * DSP instance computation: alternative method to be used by subclasses.
+ *
+ * @param date_usec - the timestamp in microsec given by audio driver.
+ * @param count - the number of frames to compute
+ * @param inputs - the input audio buffers as an array of non-interleaved FAUSTFLOAT
+ * samples (either float, double or quad)
+ * @param outputs - the output audio buffers as an array of non-interleaved FAUSTFLOAT
+ * samples (either float, double or quad)
+ *
+ */
+ virtual void compute(double /*date_usec*/, int count, FAUSTFLOAT** inputs,
+ FAUSTFLOAT** outputs)
+ {
+ compute(count, inputs, outputs);
+ }
+};
+
+/**
+ * Generic DSP decorator.
+ */
+
+class decorator_dsp : public dsp
+{
+ protected:
+ dsp* fDSP;
+
+ public:
+ decorator_dsp(dsp* dsp = nullptr) : fDSP(dsp) {}
+ virtual ~decorator_dsp() { delete fDSP; }
+
+ virtual int getNumInputs() { return fDSP->getNumInputs(); }
+ virtual int getNumOutputs() { return fDSP->getNumOutputs(); }
+ virtual void buildUserInterface(UI* ui_interface)
+ {
+ fDSP->buildUserInterface(ui_interface);
+ }
+ virtual int getSampleRate() { return fDSP->getSampleRate(); }
+ virtual void init(int sample_rate) { fDSP->init(sample_rate); }
+ virtual void instanceInit(int sample_rate) { fDSP->instanceInit(sample_rate); }
+ virtual void instanceConstants(int sample_rate)
+ {
+ fDSP->instanceConstants(sample_rate);
+ }
+ virtual void instanceResetUserInterface() { fDSP->instanceResetUserInterface(); }
+ virtual void instanceClear() { fDSP->instanceClear(); }
+ virtual decorator_dsp* clone() { return new decorator_dsp(fDSP->clone()); }
+ virtual void metadata(Meta* m) { fDSP->metadata(m); }
+ // Beware: subclasses usually have to overload the two 'compute' methods
+ virtual void compute(int count, FAUSTFLOAT** inputs, FAUSTFLOAT** outputs)
+ {
+ fDSP->compute(count, inputs, outputs);
+ }
+ virtual void compute(double date_usec, int count, FAUSTFLOAT** inputs,
+ FAUSTFLOAT** outputs)
+ {
+ fDSP->compute(date_usec, count, inputs, outputs);
+ }
+};
+
+/**
+ * DSP factory class, used with LLVM and Interpreter backends
+ * to create DSP instances from a compiled DSP program.
+ */
+
+class dsp_factory
+{
+ protected:
+ // So that to force sub-classes to use deleteDSPFactory(dsp_factory* factory);
+ virtual ~dsp_factory() {}
+
+ public:
+ virtual std::string getName() = 0;
+ virtual std::string getSHAKey() = 0;
+ virtual std::string getDSPCode() = 0;
+ virtual std::string getCompileOptions() = 0;
+ virtual std::vector<std::string> getLibraryList() = 0;
+ virtual std::vector<std::string> getIncludePathnames() = 0;
+
+ virtual dsp* createDSPInstance() = 0;
+
+ virtual void setMemoryManager(dsp_memory_manager* manager) = 0;
+ virtual dsp_memory_manager* getMemoryManager() = 0;
+};
+
+// Denormal handling
+
+#if defined(__SSE__)
+#include <xmmintrin.h>
+#endif
+
+class ScopedNoDenormals
+{
+ private:
+ intptr_t fpsr;
+
+ void setFpStatusRegister(intptr_t fpsr_aux) noexcept
+ {
+#if defined(__arm64__) || defined(__aarch64__)
+ asm volatile("msr fpcr, %0" : : "ri"(fpsr_aux));
+#elif defined(__SSE__)
+ _mm_setcsr(static_cast<uint32_t>(fpsr_aux));
+#endif
+ }
+
+ void getFpStatusRegister() noexcept
+ {
+#if defined(__arm64__) || defined(__aarch64__)
+ asm volatile("mrs %0, fpcr" : "=r"(fpsr));
+#elif defined(__SSE__)
+ fpsr = static_cast<intptr_t>(_mm_getcsr());
+#endif
+ }
+
+ public:
+ ScopedNoDenormals() noexcept
+ {
+#if defined(__arm64__) || defined(__aarch64__)
+ intptr_t mask = (1 << 24 /* FZ */);
+#else
+#if defined(__SSE__)
+#if defined(__SSE2__)
+ intptr_t mask = 0x8040;
+#else
+ intptr_t mask = 0x8000;
+#endif
+#else
+ intptr_t mask = 0x0000;
+#endif
+#endif
+ getFpStatusRegister();
+ setFpStatusRegister(fpsr | mask);
+ }
+
+ ~ScopedNoDenormals() noexcept { setFpStatusRegister(fpsr); }
+};
+
+#define AVOIDDENORMALS ScopedNoDenormals();
+
+#endif
+
+/************************** END dsp.h **************************/
+/************************** BEGIN APIUI.h *****************************
+FAUST Architecture File
+Copyright (C) 2003-2022 GRAME, Centre National de Creation Musicale
+---------------------------------------------------------------------
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+EXCEPTION : As a special exception, you may create a larger work
+that contains this FAUST architecture section and distribute
+that work under terms of your choice, so long as this FAUST
+architecture section is not modified.
+************************************************************************/
+
+#ifndef API_UI_H
+#define API_UI_H
+
+#include <stdio.h>
+
+#include <map>
+#include <sstream>
+#include <string>
+#include <vector>
+
+/************************** BEGIN meta.h *******************************
+ FAUST Architecture File
+ Copyright (C) 2003-2022 GRAME, Centre National de Creation Musicale
+ ---------------------------------------------------------------------
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+ EXCEPTION : As a special exception, you may create a larger work
+ that contains this FAUST architecture section and distribute
+ that work under terms of your choice, so long as this FAUST
+ architecture section is not modified.
+ ************************************************************************/
+
+#ifndef __meta__
+#define __meta__
+
+/**
+ The base class of Meta handler to be used in dsp::metadata(Meta* m) method to retrieve
+ (key, value) metadata.
+ */
+struct Meta {
+ virtual ~Meta() {}
+ virtual void declare(const char* key, const char* value) = 0;
+};
+
+#endif
+/************************** END meta.h **************************/
+/************************** BEGIN UI.h *****************************
+ FAUST Architecture File
+ Copyright (C) 2003-2022 GRAME, Centre National de Creation Musicale
+ ---------------------------------------------------------------------
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+ EXCEPTION : As a special exception, you may create a larger work
+ that contains this FAUST architecture section and distribute
+ that work under terms of your choice, so long as this FAUST
+ architecture section is not modified.
+ ********************************************************************/
+
+#ifndef __UI_H__
+#define __UI_H__
+
+#ifndef FAUSTFLOAT
+#define FAUSTFLOAT float
+#endif
+
+/*******************************************************************************
+ * UI : Faust DSP User Interface
+ * User Interface as expected by the buildUserInterface() method of a DSP.
+ * This abstract class contains only the method that the Faust compiler can
+ * generate to describe a DSP user interface.
+ ******************************************************************************/
+
+struct Soundfile;
+
+template<typename REAL>
+struct UIReal {
+ UIReal() {}
+ virtual ~UIReal() {}
+
+ // -- widget's layouts
+
+ virtual void openTabBox(const char* label) = 0;
+ virtual void openHorizontalBox(const char* label) = 0;
+ virtual void openVerticalBox(const char* label) = 0;
+ virtual void closeBox() = 0;
+
+ // -- active widgets
+
+ virtual void addButton(const char* label, REAL* zone) = 0;
+ virtual void addCheckButton(const char* label, REAL* zone) = 0;
+ virtual void addVerticalSlider(const char* label, REAL* zone, REAL init, REAL min,
+ REAL max, REAL step) = 0;
+ virtual void addHorizontalSlider(const char* label, REAL* zone, REAL init, REAL min,
+ REAL max, REAL step) = 0;
+ virtual void addNumEntry(const char* label, REAL* zone, REAL init, REAL min, REAL max,
+ REAL step) = 0;
+
+ // -- passive widgets
+
+ virtual void addHorizontalBargraph(const char* label, REAL* zone, REAL min,
+ REAL max) = 0;
+ virtual void addVerticalBargraph(const char* label, REAL* zone, REAL min,
+ REAL max) = 0;
+
+ // -- soundfiles
+
+ virtual void addSoundfile(const char* label, const char* filename,
+ Soundfile** sf_zone) = 0;
+
+ // -- metadata declarations
+
+ virtual void declare(REAL* /*zone*/, const char* /*key*/, const char* /*val*/) {}
+
+ // To be used by LLVM client
+ virtual int sizeOfFAUSTFLOAT() { return sizeof(FAUSTFLOAT); }
+};
+
+struct UI : public UIReal<FAUSTFLOAT> {
+ UI() {}
+ virtual ~UI() {}
+};
+
+#endif
+/************************** END UI.h **************************/
+/************************** BEGIN PathBuilder.h **************************
+ FAUST Architecture File
+ Copyright (C) 2003-2022 GRAME, Centre National de Creation Musicale
+ ---------------------------------------------------------------------
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+ EXCEPTION : As a special exception, you may create a larger work
+ that contains this FAUST architecture section and distribute
+ that work under terms of your choice, so long as this FAUST
+ architecture section is not modified.
+ ************************************************************************/
+
+#ifndef FAUST_PATHBUILDER_H
+#define FAUST_PATHBUILDER_H
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+/*******************************************************************************
+ * PathBuilder : Faust User Interface
+ * Helper class to build complete hierarchical path for UI items.
+ ******************************************************************************/
+
+class PathBuilder
+{
+ protected:
+ std::vector<std::string> fControlsLevel;
+
+ public:
+ PathBuilder() {}
+ virtual ~PathBuilder() {}
+
+ std::string replaceCharList(std::string str, const std::vector<char>& ch1, char ch2)
+ {
+ std::vector<char>::const_iterator beg = ch1.begin();
+ std::vector<char>::const_iterator end = ch1.end();
+ for (size_t i = 0; i < str.length(); ++i) {
+ if (std::find(beg, end, str[i]) != end) {
+ str[i] = ch2;
+ }
+ }
+ return str;
+ }
+
+ std::string buildPath(const std::string& label)
+ {
+ std::string res = "/";
+ for (size_t i = 0; i < fControlsLevel.size(); i++) {
+ res += fControlsLevel[i];
+ res += "/";
+ }
+ res += label;
+ std::vector<char> rep = {' ', '#', '*', ',', '/', '?',
+ '[', ']', '{', '}', '(', ')'};
+ replaceCharList(res, rep, '_');
+ return res;
+ }
+
+ void pushLabel(const std::string& label) { fControlsLevel.push_back(label); }
+ void popLabel() { fControlsLevel.pop_back(); }
+};
+
+#endif // FAUST_PATHBUILDER_H
+/************************** END PathBuilder.h **************************/
+/************************** BEGIN ValueConverter.h ********************
+ FAUST Architecture File
+ Copyright (C) 2003-2022 GRAME, Centre National de Creation Musicale
+ ---------------------------------------------------------------------
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+ EXCEPTION : As a special exception, you may create a larger work
+ that contains this FAUST architecture section and distribute
+ that work under terms of your choice, so long as this FAUST
+ architecture section is not modified.
+ ********************************************************************/
+
+#ifndef __ValueConverter__
+#define __ValueConverter__
+
+/***************************************************************************************
+ ValueConverter.h
+ (GRAME, Copyright 2015-2019)
+
+ Set of conversion objects used to map user interface values (for example a gui slider
+ delivering values between 0 and 1) to faust values (for example a vslider between
+ 20 and 20000) using a log scale.
+
+ -- Utilities
+
+ Range(lo,hi) : clip a value x between lo and hi
+ Interpolator(lo,hi,v1,v2) : Maps a value x between lo and hi to a value y between v1 and
+v2 Interpolator3pt(lo,mi,hi,v1,vm,v2) : Map values between lo mid hi to values between v1
+vm v2
+
+ -- Value Converters
+
+ ValueConverter::ui2faust(x)
+ ValueConverter::faust2ui(x)
+
+ -- ValueConverters used for sliders depending of the scale
+
+ LinearValueConverter(umin, umax, fmin, fmax)
+ LinearValueConverter2(lo, mi, hi, v1, vm, v2) using 2 segments
+ LogValueConverter(umin, umax, fmin, fmax)
+ ExpValueConverter(umin, umax, fmin, fmax)
+
+ -- ValueConverters used for accelerometers based on 3 points
+
+ AccUpConverter(amin, amid, amax, fmin, fmid, fmax) -- curve 0
+ AccDownConverter(amin, amid, amax, fmin, fmid, fmax) -- curve 1
+ AccUpDownConverter(amin, amid, amax, fmin, fmid, fmax) -- curve 2
+ AccDownUpConverter(amin, amid, amax, fmin, fmid, fmax) -- curve 3
+
+ -- lists of ZoneControl are used to implement accelerometers metadata for each axes
+
+ ZoneControl(zone, valueConverter) : a zone with an accelerometer data converter
+
+ -- ZoneReader are used to implement screencolor metadata
+
+ ZoneReader(zone, valueConverter) : a zone with a data converter
+
+****************************************************************************************/
+
+#include <assert.h>
+#include <float.h>
+
+#include <algorithm> // std::max
+#include <cmath>
+#include <vector>
+
+//--------------------------------------------------------------------------------------
+// Interpolator(lo,hi,v1,v2)
+// Maps a value x between lo and hi to a value y between v1 and v2
+// y = v1 + (x-lo)/(hi-lo)*(v2-v1)
+// y = v1 + (x-lo) * coef with coef = (v2-v1)/(hi-lo)
+// y = v1 + x*coef - lo*coef
+// y = v1 - lo*coef + x*coef
+// y = offset + x*coef with offset = v1 - lo*coef
+//--------------------------------------------------------------------------------------
+class Interpolator
+{
+ private:
+ //--------------------------------------------------------------------------------------
+ // Range(lo,hi) clip a value between lo and hi
+ //--------------------------------------------------------------------------------------
+ struct Range {
+ double fLo;
+ double fHi;
+
+ Range(double x, double y)
+ : fLo(std::min<double>(x, y)), fHi(std::max<double>(x, y))
+ {
+ }
+ double operator()(double x) { return (x < fLo) ? fLo : (x > fHi) ? fHi : x; }
+ };
+
+ Range fRange;
+ double fCoef;
+ double fOffset;
+
+ public:
+ Interpolator(double lo, double hi, double v1, double v2) : fRange(lo, hi)
+ {
+ if (hi != lo) {
+ // regular case
+ fCoef = (v2 - v1) / (hi - lo);
+ fOffset = v1 - lo * fCoef;
+ } else {
+ // degenerate case, avoids division by zero
+ fCoef = 0;
+ fOffset = (v1 + v2) / 2;
+ }
+ }
+ double operator()(double v)
+ {
+ double x = fRange(v);
+ return fOffset + x * fCoef;
+ }
+
+ void getLowHigh(double& amin, double& amax)
+ {
+ amin = fRange.fLo;
+ amax = fRange.fHi;
+ }
+};
+
+//--------------------------------------------------------------------------------------
+// Interpolator3pt(lo,mi,hi,v1,vm,v2)
+// Map values between lo mid hi to values between v1 vm v2
+//--------------------------------------------------------------------------------------
+class Interpolator3pt
+{
+ private:
+ Interpolator fSegment1;
+ Interpolator fSegment2;
+ double fMid;
+
+ public:
+ Interpolator3pt(double lo, double mi, double hi, double v1, double vm, double v2)
+ : fSegment1(lo, mi, v1, vm), fSegment2(mi, hi, vm, v2), fMid(mi)
+ {
+ }
+ double operator()(double x) { return (x < fMid) ? fSegment1(x) : fSegment2(x); }
+
+ void getMappingValues(double& amin, double& amid, double& amax)
+ {
+ fSegment1.getLowHigh(amin, amid);
+ fSegment2.getLowHigh(amid, amax);
+ }
+};
+
+//--------------------------------------------------------------------------------------
+// Abstract ValueConverter class. Converts values between UI and Faust representations
+//--------------------------------------------------------------------------------------
+class ValueConverter // Identity by default
+{
+ public:
+ virtual ~ValueConverter() {}
+ virtual double ui2faust(double x) { return x; };
+ virtual double faust2ui(double x) { return x; };
+};
+
+//--------------------------------------------------------------------------------------
+// A converter than can be updated
+//--------------------------------------------------------------------------------------
+
+class UpdatableValueConverter : public ValueConverter
+{
+ protected:
+ bool fActive;
+
+ public:
+ UpdatableValueConverter() : fActive(true) {}
+ virtual ~UpdatableValueConverter() {}
+
+ virtual void setMappingValues(double amin, double amid, double amax, double min,
+ double init, double max) = 0;
+ virtual void getMappingValues(double& amin, double& amid, double& amax) = 0;
+
+ void setActive(bool on_off) { fActive = on_off; }
+ bool getActive() { return fActive; }
+};
+
+//--------------------------------------------------------------------------------------
+// Linear conversion between ui and Faust values
+//--------------------------------------------------------------------------------------
+class LinearValueConverter : public ValueConverter
+{
+ private:
+ Interpolator fUI2F;
+ Interpolator fF2UI;
+
+ public:
+ LinearValueConverter(double umin, double umax, double fmin, double fmax)
+ : fUI2F(umin, umax, fmin, fmax), fF2UI(fmin, fmax, umin, umax)
+ {
+ }
+
+ LinearValueConverter() : fUI2F(0., 0., 0., 0.), fF2UI(0., 0., 0., 0.) {}
+ virtual double ui2faust(double x) { return fUI2F(x); }
+ virtual double faust2ui(double x) { return fF2UI(x); }
+};
+
+//--------------------------------------------------------------------------------------
+// Two segments linear conversion between ui and Faust values
+//--------------------------------------------------------------------------------------
+class LinearValueConverter2 : public UpdatableValueConverter
+{
+ private:
+ Interpolator3pt fUI2F;
+ Interpolator3pt fF2UI;
+
+ public:
+ LinearValueConverter2(double amin, double amid, double amax, double min, double init,
+ double max)
+ : fUI2F(amin, amid, amax, min, init, max), fF2UI(min, init, max, amin, amid, amax)
+ {
+ }
+
+ LinearValueConverter2() : fUI2F(0., 0., 0., 0., 0., 0.), fF2UI(0., 0., 0., 0., 0., 0.)
+ {
+ }
+
+ virtual double ui2faust(double x) { return fUI2F(x); }
+ virtual double faust2ui(double x) { return fF2UI(x); }
+
+ virtual void setMappingValues(double amin, double amid, double amax, double min,
+ double init, double max)
+ {
+ fUI2F = Interpolator3pt(amin, amid, amax, min, init, max);
+ fF2UI = Interpolator3pt(min, init, max, amin, amid, amax);
+ }
+
+ virtual void getMappingValues(double& amin, double& amid, double& amax)
+ {
+ fUI2F.getMappingValues(amin, amid, amax);
+ }
+};
+
+//--------------------------------------------------------------------------------------
+// Logarithmic conversion between ui and Faust values
+//--------------------------------------------------------------------------------------
+class LogValueConverter : public LinearValueConverter
+{
+ public:
+ LogValueConverter(double umin, double umax, double fmin, double fmax)
+ : LinearValueConverter(umin, umax, std::log(std::max<double>(DBL_MIN, fmin)),
+ std::log(std::max<double>(DBL_MIN, fmax)))
+ {
+ }
+
+ virtual double ui2faust(double x)
+ {
+ return std::exp(LinearValueConverter::ui2faust(x));
+ }
+ virtual double faust2ui(double x)
+ {
+ return LinearValueConverter::faust2ui(std::log(std::max<double>(x, DBL_MIN)));
+ }
+};
+
+//--------------------------------------------------------------------------------------
+// Exponential conversion between ui and Faust values
+//--------------------------------------------------------------------------------------
+class ExpValueConverter : public LinearValueConverter
+{
+ public:
+ ExpValueConverter(double umin, double umax, double fmin, double fmax)
+ : LinearValueConverter(umin, umax, std::min<double>(DBL_MAX, std::exp(fmin)),
+ std::min<double>(DBL_MAX, std::exp(fmax)))
+ {
+ }
+
+ virtual double ui2faust(double x)
+ {
+ return std::log(LinearValueConverter::ui2faust(x));
+ }
+ virtual double faust2ui(double x)
+ {
+ return LinearValueConverter::faust2ui(std::min<double>(DBL_MAX, std::exp(x)));
+ }
+};
+
+//--------------------------------------------------------------------------------------
+// Convert accelerometer or gyroscope values to Faust values
+// Using an Up curve (curve 0)
+//--------------------------------------------------------------------------------------
+class AccUpConverter : public UpdatableValueConverter
+{
+ private:
+ Interpolator3pt fA2F;
+ Interpolator3pt fF2A;
+
+ public:
+ AccUpConverter(double amin, double amid, double amax, double fmin, double fmid,
+ double fmax)
+ : fA2F(amin, amid, amax, fmin, fmid, fmax)
+ , fF2A(fmin, fmid, fmax, amin, amid, amax)
+ {
+ }
+
+ virtual double ui2faust(double x) { return fA2F(x); }
+ virtual double faust2ui(double x) { return fF2A(x); }
+
+ virtual void setMappingValues(double amin, double amid, double amax, double fmin,
+ double fmid, double fmax)
+ {
+ //__android_log_print(ANDROID_LOG_ERROR, "Faust", "AccUpConverter update %f %f %f
+ //%f %f %f", amin,amid,amax,fmin,fmid,fmax);
+ fA2F = Interpolator3pt(amin, amid, amax, fmin, fmid, fmax);
+ fF2A = Interpolator3pt(fmin, fmid, fmax, amin, amid, amax);
+ }
+
+ virtual void getMappingValues(double& amin, double& amid, double& amax)
+ {
+ fA2F.getMappingValues(amin, amid, amax);
+ }
+};
+
+//--------------------------------------------------------------------------------------
+// Convert accelerometer or gyroscope values to Faust values
+// Using a Down curve (curve 1)
+//--------------------------------------------------------------------------------------
+class AccDownConverter : public UpdatableValueConverter
+{
+ private:
+ Interpolator3pt fA2F;
+ Interpolator3pt fF2A;
+
+ public:
+ AccDownConverter(double amin, double amid, double amax, double fmin, double fmid,
+ double fmax)
+ : fA2F(amin, amid, amax, fmax, fmid, fmin)
+ , fF2A(fmin, fmid, fmax, amax, amid, amin)
+ {
+ }
+
+ virtual double ui2faust(double x) { return fA2F(x); }
+ virtual double faust2ui(double x) { return fF2A(x); }
+
+ virtual void setMappingValues(double amin, double amid, double amax, double fmin,
+ double fmid, double fmax)
+ {
+ //__android_log_print(ANDROID_LOG_ERROR, "Faust", "AccDownConverter update %f %f
+ //%f %f %f %f", amin,amid,amax,fmin,fmid,fmax);
+ fA2F = Interpolator3pt(amin, amid, amax, fmax, fmid, fmin);
+ fF2A = Interpolator3pt(fmin, fmid, fmax, amax, amid, amin);
+ }
+
+ virtual void getMappingValues(double& amin, double& amid, double& amax)
+ {
+ fA2F.getMappingValues(amin, amid, amax);
+ }
+};
+
+//--------------------------------------------------------------------------------------
+// Convert accelerometer or gyroscope values to Faust values
+// Using an Up-Down curve (curve 2)
+//--------------------------------------------------------------------------------------
+class AccUpDownConverter : public UpdatableValueConverter
+{
+ private:
+ Interpolator3pt fA2F;
+ Interpolator fF2A;
+
+ public:
+ AccUpDownConverter(double amin, double amid, double amax, double fmin,
+ double /*fmid*/, double fmax)
+ : fA2F(amin, amid, amax, fmin, fmax, fmin)
+ , fF2A(fmin, fmax, amin,
+ amax) // Special, pseudo inverse of a non monotonic function
+ {
+ }
+
+ virtual double ui2faust(double x) { return fA2F(x); }
+ virtual double faust2ui(double x) { return fF2A(x); }
+
+ virtual void setMappingValues(double amin, double amid, double amax, double fmin,
+ double /*fmid*/, double fmax)
+ {
+ //__android_log_print(ANDROID_LOG_ERROR, "Faust", "AccUpDownConverter update %f %f
+ //%f %f %f %f", amin,amid,amax,fmin,fmid,fmax);
+ fA2F = Interpolator3pt(amin, amid, amax, fmin, fmax, fmin);
+ fF2A = Interpolator(fmin, fmax, amin, amax);
+ }
+
+ virtual void getMappingValues(double& amin, double& amid, double& amax)
+ {
+ fA2F.getMappingValues(amin, amid, amax);
+ }
+};
+
+//--------------------------------------------------------------------------------------
+// Convert accelerometer or gyroscope values to Faust values
+// Using a Down-Up curve (curve 3)
+//--------------------------------------------------------------------------------------
+class AccDownUpConverter : public UpdatableValueConverter
+{
+ private:
+ Interpolator3pt fA2F;
+ Interpolator fF2A;
+
+ public:
+ AccDownUpConverter(double amin, double amid, double amax, double fmin,
+ double /*fmid*/, double fmax)
+ : fA2F(amin, amid, amax, fmax, fmin, fmax)
+ , fF2A(fmin, fmax, amin,
+ amax) // Special, pseudo inverse of a non monotonic function
+ {
+ }
+
+ virtual double ui2faust(double x) { return fA2F(x); }
+ virtual double faust2ui(double x) { return fF2A(x); }
+
+ virtual void setMappingValues(double amin, double amid, double amax, double fmin,
+ double /*fmid*/, double fmax)
+ {
+ //__android_log_print(ANDROID_LOG_ERROR, "Faust", "AccDownUpConverter update %f %f
+ //%f %f %f %f", amin,amid,amax,fmin,fmid,fmax);
+ fA2F = Interpolator3pt(amin, amid, amax, fmax, fmin, fmax);
+ fF2A = Interpolator(fmin, fmax, amin, amax);
+ }
+
+ virtual void getMappingValues(double& amin, double& amid, double& amax)
+ {
+ fA2F.getMappingValues(amin, amid, amax);
+ }
+};
+
+//--------------------------------------------------------------------------------------
+// Base class for ZoneControl
+//--------------------------------------------------------------------------------------
+class ZoneControl
+{
+ protected:
+ FAUSTFLOAT* fZone;
+
+ public:
+ ZoneControl(FAUSTFLOAT* zone) : fZone(zone) {}
+ virtual ~ZoneControl() {}
+
+ virtual void update(double /*v*/) const {}
+
+ virtual void setMappingValues(int /*curve*/, double /*amin*/, double /*amid*/,
+ double /*amax*/, double /*min*/, double /*init*/,
+ double /*max*/)
+ {
+ }
+ virtual void getMappingValues(double& /*amin*/, double& /*amid*/, double& /*amax*/) {}
+
+ FAUSTFLOAT* getZone() { return fZone; }
+
+ virtual void setActive(bool /*on_off*/) {}
+ virtual bool getActive() { return false; }
+
+ virtual int getCurve() { return -1; }
+};
+
+//--------------------------------------------------------------------------------------
+// Useful to implement accelerometers metadata as a list of ZoneControl for each axes
+//--------------------------------------------------------------------------------------
+class ConverterZoneControl : public ZoneControl
+{
+ protected:
+ ValueConverter* fValueConverter;
+
+ public:
+ ConverterZoneControl(FAUSTFLOAT* zone, ValueConverter* converter)
+ : ZoneControl(zone), fValueConverter(converter)
+ {
+ }
+ virtual ~ConverterZoneControl()
+ {
+ delete fValueConverter;
+ } // Assuming fValueConverter is not kept elsewhere...
+
+ virtual void update(double v) const
+ {
+ *fZone = FAUSTFLOAT(fValueConverter->ui2faust(v));
+ }
+
+ ValueConverter* getConverter() { return fValueConverter; }
+};
+
+//--------------------------------------------------------------------------------------
+// Association of a zone and a four value converter, each one for each possible curve.
+// Useful to implement accelerometers metadata as a list of ZoneControl for each axes
+//--------------------------------------------------------------------------------------
+class CurveZoneControl : public ZoneControl
+{
+ private:
+ std::vector<UpdatableValueConverter*> fValueConverters;
+ int fCurve;
+
+ public:
+ CurveZoneControl(FAUSTFLOAT* zone, int curve, double amin, double amid, double amax,
+ double min, double init, double max)
+ : ZoneControl(zone), fCurve(0)
+ {
+ assert(curve >= 0 && curve <= 3);
+ fValueConverters.push_back(new AccUpConverter(amin, amid, amax, min, init, max));
+ fValueConverters.push_back(
+ new AccDownConverter(amin, amid, amax, min, init, max));
+ fValueConverters.push_back(
+ new AccUpDownConverter(amin, amid, amax, min, init, max));
+ fValueConverters.push_back(
+ new AccDownUpConverter(amin, amid, amax, min, init, max));
+ fCurve = curve;
+ }
+ virtual ~CurveZoneControl()
+ {
+ for (const auto& it : fValueConverters) {
+ delete it;
+ }
+ }
+ void update(double v) const
+ {
+ if (fValueConverters[fCurve]->getActive())
+ *fZone = FAUSTFLOAT(fValueConverters[fCurve]->ui2faust(v));
+ }
+
+ void setMappingValues(int curve, double amin, double amid, double amax, double min,
+ double init, double max)
+ {
+ fValueConverters[curve]->setMappingValues(amin, amid, amax, min, init, max);
+ fCurve = curve;
+ }
+
+ void getMappingValues(double& amin, double& amid, double& amax)
+ {
+ fValueConverters[fCurve]->getMappingValues(amin, amid, amax);
+ }
+
+ void setActive(bool on_off)
+ {
+ for (const auto& it : fValueConverters) {
+ it->setActive(on_off);
+ }
+ }
+
+ int getCurve() { return fCurve; }
+};
+
+class ZoneReader
+{
+ private:
+ FAUSTFLOAT* fZone;
+ Interpolator fInterpolator;
+
+ public:
+ ZoneReader(FAUSTFLOAT* zone, double lo, double hi)
+ : fZone(zone), fInterpolator(lo, hi, 0, 255)
+ {
+ }
+
+ virtual ~ZoneReader() {}
+
+ int getValue() { return (fZone != nullptr) ? int(fInterpolator(*fZone)) : 127; }
+};
+
+#endif
+/************************** END ValueConverter.h **************************/
+
+typedef unsigned int uint;
+
+class APIUI
+ : public PathBuilder
+ , public Meta
+ , public UI
+{
+ public:
+ enum ItemType {
+ kButton = 0,
+ kCheckButton,
+ kVSlider,
+ kHSlider,
+ kNumEntry,
+ kHBargraph,
+ kVBargraph
+ };
+ enum Type { kAcc = 0, kGyr = 1, kNoType };
+
+ protected:
+ enum Mapping { kLin = 0, kLog = 1, kExp = 2 };
+
+ struct Item {
+ std::string fPath;
+ std::string fLabel;
+ ValueConverter* fConversion;
+ FAUSTFLOAT* fZone;
+ FAUSTFLOAT fInit;
+ FAUSTFLOAT fMin;
+ FAUSTFLOAT fMax;
+ FAUSTFLOAT fStep;
+ ItemType fItemType;
+ };
+ std::vector<Item> fItems;
+
+ std::vector<std::map<std::string, std::string> > fMetaData;
+ std::vector<ZoneControl*> fAcc[3];
+ std::vector<ZoneControl*> fGyr[3];
+
+ // Screen color control
+ // "...[screencolor:red]..." etc.
+ bool fHasScreenControl; // true if control screen color metadata
+ ZoneReader* fRedReader;
+ ZoneReader* fGreenReader;
+ ZoneReader* fBlueReader;
+
+ // Current values controlled by metadata
+ std::string fCurrentUnit;
+ int fCurrentScale;
+ std::string fCurrentAcc;
+ std::string fCurrentGyr;
+ std::string fCurrentColor;
+ std::string fCurrentTooltip;
+ std::map<std::string, std::string> fCurrentMetadata;
+
+ // Add a generic parameter
+ virtual void addParameter(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT init,
+ FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT step,
+ ItemType type)
+ {
+ std::string path = buildPath(label);
+
+ // handle scale metadata
+ ValueConverter* converter = nullptr;
+ switch (fCurrentScale) {
+ case kLin:
+ converter = new LinearValueConverter(0, 1, min, max);
+ break;
+ case kLog:
+ converter = new LogValueConverter(0, 1, min, max);
+ break;
+ case kExp:
+ converter = new ExpValueConverter(0, 1, min, max);
+ break;
+ }
+ fCurrentScale = kLin;
+
+ fItems.push_back({path, label, converter, zone, init, min, max, step, type});
+
+ if (fCurrentAcc.size() > 0 && fCurrentGyr.size() > 0) {
+ fprintf(
+ stderr,
+ "warning : 'acc' and 'gyr' metadata used for the same %s parameter !!\n",
+ label);
+ }
+
+ // handle acc metadata "...[acc : <axe> <curve> <amin> <amid> <amax>]..."
+ if (fCurrentAcc.size() > 0) {
+ std::istringstream iss(fCurrentAcc);
+ int axe, curve;
+ double amin, amid, amax;
+ iss >> axe >> curve >> amin >> amid >> amax;
+
+ if ((0 <= axe) && (axe < 3) && (0 <= curve) && (curve < 4) && (amin < amax)
+ && (amin <= amid) && (amid <= amax)) {
+ fAcc[axe].push_back(
+ new CurveZoneControl(zone, curve, amin, amid, amax, min, init, max));
+ } else {
+ fprintf(stderr, "incorrect acc metadata : %s \n", fCurrentAcc.c_str());
+ }
+ fCurrentAcc = "";
+ }
+
+ // handle gyr metadata "...[gyr : <axe> <curve> <amin> <amid> <amax>]..."
+ if (fCurrentGyr.size() > 0) {
+ std::istringstream iss(fCurrentGyr);
+ int axe, curve;
+ double amin, amid, amax;
+ iss >> axe >> curve >> amin >> amid >> amax;
+
+ if ((0 <= axe) && (axe < 3) && (0 <= curve) && (curve < 4) && (amin < amax)
+ && (amin <= amid) && (amid <= amax)) {
+ fGyr[axe].push_back(
+ new CurveZoneControl(zone, curve, amin, amid, amax, min, init, max));
+ } else {
+ fprintf(stderr, "incorrect gyr metadata : %s \n", fCurrentGyr.c_str());
+ }
+ fCurrentGyr = "";
+ }
+
+ // handle screencolor metadata "...[screencolor:red|green|blue|white]..."
+ if (fCurrentColor.size() > 0) {
+ if ((fCurrentColor == "red") && (fRedReader == nullptr)) {
+ fRedReader = new ZoneReader(zone, min, max);
+ fHasScreenControl = true;
+ } else if ((fCurrentColor == "green") && (fGreenReader == nullptr)) {
+ fGreenReader = new ZoneReader(zone, min, max);
+ fHasScreenControl = true;
+ } else if ((fCurrentColor == "blue") && (fBlueReader == nullptr)) {
+ fBlueReader = new ZoneReader(zone, min, max);
+ fHasScreenControl = true;
+ } else if ((fCurrentColor == "white") && (fRedReader == nullptr)
+ && (fGreenReader == nullptr) && (fBlueReader == nullptr)) {
+ fRedReader = new ZoneReader(zone, min, max);
+ fGreenReader = new ZoneReader(zone, min, max);
+ fBlueReader = new ZoneReader(zone, min, max);
+ fHasScreenControl = true;
+ } else {
+ fprintf(stderr, "incorrect screencolor metadata : %s \n",
+ fCurrentColor.c_str());
+ }
+ }
+ fCurrentColor = "";
+
+ fMetaData.push_back(fCurrentMetadata);
+ fCurrentMetadata.clear();
+ }
+
+ int getZoneIndex(std::vector<ZoneControl*>* table, int p, int val)
+ {
+ FAUSTFLOAT* zone = fItems[uint(p)].fZone;
+ for (size_t i = 0; i < table[val].size(); i++) {
+ if (zone == table[val][i]->getZone())
+ return int(i);
+ }
+ return -1;
+ }
+
+ void setConverter(std::vector<ZoneControl*>* table, int p, int val, int curve,
+ double amin, double amid, double amax)
+ {
+ int id1 = getZoneIndex(table, p, 0);
+ int id2 = getZoneIndex(table, p, 1);
+ int id3 = getZoneIndex(table, p, 2);
+
+ // Deactivates everywhere..
+ if (id1 != -1)
+ table[0][uint(id1)]->setActive(false);
+ if (id2 != -1)
+ table[1][uint(id2)]->setActive(false);
+ if (id3 != -1)
+ table[2][uint(id3)]->setActive(false);
+
+ if (val == -1) { // Means: no more mapping...
+ // So stay all deactivated...
+ } else {
+ int id4 = getZoneIndex(table, p, val);
+ if (id4 != -1) {
+ // Reactivate the one we edit...
+ table[val][uint(id4)]->setMappingValues(
+ curve, amin, amid, amax, fItems[uint(p)].fMin, fItems[uint(p)].fInit,
+ fItems[uint(p)].fMax);
+ table[val][uint(id4)]->setActive(true);
+ } else {
+ // Allocate a new CurveZoneControl which is 'active' by default
+ FAUSTFLOAT* zone = fItems[uint(p)].fZone;
+ table[val].push_back(new CurveZoneControl(
+ zone, curve, amin, amid, amax, fItems[uint(p)].fMin,
+ fItems[uint(p)].fInit, fItems[uint(p)].fMax));
+ }
+ }
+ }
+
+ void getConverter(std::vector<ZoneControl*>* table, int p, int& val, int& curve,
+ double& amin, double& amid, double& amax)
+ {
+ int id1 = getZoneIndex(table, p, 0);
+ int id2 = getZoneIndex(table, p, 1);
+ int id3 = getZoneIndex(table, p, 2);
+
+ if (id1 != -1) {
+ val = 0;
+ curve = table[val][uint(id1)]->getCurve();
+ table[val][uint(id1)]->getMappingValues(amin, amid, amax);
+ } else if (id2 != -1) {
+ val = 1;
+ curve = table[val][uint(id2)]->getCurve();
+ table[val][uint(id2)]->getMappingValues(amin, amid, amax);
+ } else if (id3 != -1) {
+ val = 2;
+ curve = table[val][uint(id3)]->getCurve();
+ table[val][uint(id3)]->getMappingValues(amin, amid, amax);
+ } else {
+ val = -1; // No mapping
+ curve = 0;
+ amin = -100.;
+ amid = 0.;
+ amax = 100.;
+ }
+ }
+
+ public:
+ APIUI()
+ : fHasScreenControl(false)
+ , fRedReader(nullptr)
+ , fGreenReader(nullptr)
+ , fBlueReader(nullptr)
+ , fCurrentScale(kLin)
+ {
+ }
+
+ virtual ~APIUI()
+ {
+ for (const auto& it : fItems)
+ delete it.fConversion;
+ for (int i = 0; i < 3; i++) {
+ for (const auto& it : fAcc[i])
+ delete it;
+ for (const auto& it : fGyr[i])
+ delete it;
+ }
+ delete fRedReader;
+ delete fGreenReader;
+ delete fBlueReader;
+ }
+
+ // -- widget's layouts
+
+ virtual void openTabBox(const char* label) { pushLabel(label); }
+ virtual void openHorizontalBox(const char* label) { pushLabel(label); }
+ virtual void openVerticalBox(const char* label) { pushLabel(label); }
+ virtual void closeBox() { popLabel(); }
+
+ // -- active widgets
+
+ virtual void addButton(const char* label, FAUSTFLOAT* zone)
+ {
+ addParameter(label, zone, 0, 0, 1, 1, kButton);
+ }
+
+ virtual void addCheckButton(const char* label, FAUSTFLOAT* zone)
+ {
+ addParameter(label, zone, 0, 0, 1, 1, kCheckButton);
+ }
+
+ virtual void addVerticalSlider(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT init,
+ FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT step)
+ {
+ addParameter(label, zone, init, min, max, step, kVSlider);
+ }
+
+ virtual void addHorizontalSlider(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT init,
+ FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT step)
+ {
+ addParameter(label, zone, init, min, max, step, kHSlider);
+ }
+
+ virtual void addNumEntry(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT init,
+ FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT step)
+ {
+ addParameter(label, zone, init, min, max, step, kNumEntry);
+ }
+
+ // -- passive widgets
+
+ virtual void addHorizontalBargraph(const char* label, FAUSTFLOAT* zone,
+ FAUSTFLOAT min, FAUSTFLOAT max)
+ {
+ addParameter(label, zone, min, min, max, (max - min) / 1000.0f, kHBargraph);
+ }
+
+ virtual void addVerticalBargraph(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT min,
+ FAUSTFLOAT max)
+ {
+ addParameter(label, zone, min, min, max, (max - min) / 1000.0f, kVBargraph);
+ }
+
+ // -- soundfiles
+
+ virtual void addSoundfile(const char* /*label*/, const char* /*filename*/,
+ Soundfile** /*sf_zone*/)
+ {
+ }
+
+ // -- metadata declarations
+
+ virtual void declare(FAUSTFLOAT* /*zone*/, const char* key, const char* val)
+ {
+ // Keep metadata
+ fCurrentMetadata[key] = val;
+
+ if (strcmp(key, "scale") == 0) {
+ if (strcmp(val, "log") == 0) {
+ fCurrentScale = kLog;
+ } else if (strcmp(val, "exp") == 0) {
+ fCurrentScale = kExp;
+ } else {
+ fCurrentScale = kLin;
+ }
+ } else if (strcmp(key, "unit") == 0) {
+ fCurrentUnit = val;
+ } else if (strcmp(key, "acc") == 0) {
+ fCurrentAcc = val;
+ } else if (strcmp(key, "gyr") == 0) {
+ fCurrentGyr = val;
+ } else if (strcmp(key, "screencolor") == 0) {
+ fCurrentColor = val; // val = "red", "green", "blue" or "white"
+ } else if (strcmp(key, "tooltip") == 0) {
+ fCurrentTooltip = val;
+ }
+ }
+
+ virtual void declare(const char* /*key*/, const char* /*val*/) {}
+
+ //-------------------------------------------------------------------------------
+ // Simple API part
+ //-------------------------------------------------------------------------------
+ int getParamsCount() { return int(fItems.size()); }
+
+ int getParamIndex(const char* path)
+ {
+ auto it1 = find_if(fItems.begin(), fItems.end(), [=](const Item& it) {
+ return it.fPath == std::string(path);
+ });
+ if (it1 != fItems.end()) {
+ return int(it1 - fItems.begin());
+ }
+
+ auto it2 = find_if(fItems.begin(), fItems.end(), [=](const Item& it) {
+ return it.fLabel == std::string(path);
+ });
+ if (it2 != fItems.end()) {
+ return int(it2 - fItems.begin());
+ }
+
+ return -1;
+ }
+ const char* getParamAddress(int p) { return fItems[uint(p)].fPath.c_str(); }
+ const char* getParamLabel(int p) { return fItems[uint(p)].fLabel.c_str(); }
+ std::map<const char*, const char*> getMetadata(int p)
+ {
+ std::map<const char*, const char*> res;
+ std::map<std::string, std::string> metadata = fMetaData[uint(p)];
+ for (const auto& it : metadata) {
+ res[it.first.c_str()] = it.second.c_str();
+ }
+ return res;
+ }
+
+ const char* getMetadata(int p, const char* key)
+ {
+ return (fMetaData[uint(p)].find(key) != fMetaData[uint(p)].end())
+ ? fMetaData[uint(p)][key].c_str()
+ : "";
+ }
+ FAUSTFLOAT getParamMin(int p) { return fItems[uint(p)].fMin; }
+ FAUSTFLOAT getParamMax(int p) { return fItems[uint(p)].fMax; }
+ FAUSTFLOAT getParamStep(int p) { return fItems[uint(p)].fStep; }
+ FAUSTFLOAT getParamInit(int p) { return fItems[uint(p)].fInit; }
+
+ FAUSTFLOAT* getParamZone(int p) { return fItems[uint(p)].fZone; }
+
+ FAUSTFLOAT getParamValue(int p) { return *fItems[uint(p)].fZone; }
+ FAUSTFLOAT getParamValue(const char* path)
+ {
+ int index = getParamIndex(path);
+ return (index >= 0) ? getParamValue(index) : FAUSTFLOAT(0);
+ }
+
+ void setParamValue(int p, FAUSTFLOAT v) { *fItems[uint(p)].fZone = v; }
+ void setParamValue(const char* path, FAUSTFLOAT v)
+ {
+ int index = getParamIndex(path);
+ if (index >= 0) {
+ setParamValue(index, v);
+ } else {
+ fprintf(stderr, "setParamValue : '%s' not found\n",
+ (path == nullptr ? "NULL" : path));
+ }
+ }
+
+ double getParamRatio(int p)
+ {
+ return fItems[uint(p)].fConversion->faust2ui(*fItems[uint(p)].fZone);
+ }
+ void setParamRatio(int p, double r)
+ {
+ *fItems[uint(p)].fZone = FAUSTFLOAT(fItems[uint(p)].fConversion->ui2faust(r));
+ }
+
+ double value2ratio(int p, double r)
+ {
+ return fItems[uint(p)].fConversion->faust2ui(r);
+ }
+ double ratio2value(int p, double r)
+ {
+ return fItems[uint(p)].fConversion->ui2faust(r);
+ }
+
+ /**
+ * Return the control type (kAcc, kGyr, or -1) for a given parameter.
+ *
+ * @param p - the UI parameter index
+ *
+ * @return the type
+ */
+ Type getParamType(int p)
+ {
+ if (p >= 0) {
+ if (getZoneIndex(fAcc, p, 0) != -1 || getZoneIndex(fAcc, p, 1) != -1
+ || getZoneIndex(fAcc, p, 2) != -1) {
+ return kAcc;
+ } else if (getZoneIndex(fGyr, p, 0) != -1 || getZoneIndex(fGyr, p, 1) != -1
+ || getZoneIndex(fGyr, p, 2) != -1) {
+ return kGyr;
+ }
+ }
+ return kNoType;
+ }
+
+ /**
+ * Return the Item type (kButton = 0, kCheckButton, kVSlider, kHSlider, kNumEntry,
+ * kHBargraph, kVBargraph) for a given parameter.
+ *
+ * @param p - the UI parameter index
+ *
+ * @return the Item type
+ */
+ ItemType getParamItemType(int p) { return fItems[uint(p)].fItemType; }
+
+ /**
+ * Set a new value coming from an accelerometer, propagate it to all relevant
+ * FAUSTFLOAT* zones.
+ *
+ * @param acc - 0 for X accelerometer, 1 for Y accelerometer, 2 for Z accelerometer
+ * @param value - the new value
+ *
+ */
+ void propagateAcc(int acc, double value)
+ {
+ for (size_t i = 0; i < fAcc[acc].size(); i++) {
+ fAcc[acc][i]->update(value);
+ }
+ }
+
+ /**
+ * Used to edit accelerometer curves and mapping. Set curve and related mapping for a
+ * given UI parameter.
+ *
+ * @param p - the UI parameter index
+ * @param acc - 0 for X accelerometer, 1 for Y accelerometer, 2 for Z accelerometer
+ * (-1 means "no mapping")
+ * @param curve - between 0 and 3
+ * @param amin - mapping 'min' point
+ * @param amid - mapping 'middle' point
+ * @param amax - mapping 'max' point
+ *
+ */
+ void setAccConverter(int p, int acc, int curve, double amin, double amid, double amax)
+ {
+ setConverter(fAcc, p, acc, curve, amin, amid, amax);
+ }
+
+ /**
+ * Used to edit gyroscope curves and mapping. Set curve and related mapping for a
+ * given UI parameter.
+ *
+ * @param p - the UI parameter index
+ * @param acc - 0 for X gyroscope, 1 for Y gyroscope, 2 for Z gyroscope (-1 means "no
+ * mapping")
+ * @param curve - between 0 and 3
+ * @param amin - mapping 'min' point
+ * @param amid - mapping 'middle' point
+ * @param amax - mapping 'max' point
+ *
+ */
+ void setGyrConverter(int p, int gyr, int curve, double amin, double amid, double amax)
+ {
+ setConverter(fGyr, p, gyr, curve, amin, amid, amax);
+ }
+
+ /**
+ * Used to edit accelerometer curves and mapping. Get curve and related mapping for a
+ * given UI parameter.
+ *
+ * @param p - the UI parameter index
+ * @param acc - the acc value to be retrieved (-1 means "no mapping")
+ * @param curve - the curve value to be retrieved
+ * @param amin - the amin value to be retrieved
+ * @param amid - the amid value to be retrieved
+ * @param amax - the amax value to be retrieved
+ *
+ */
+ void getAccConverter(int p, int& acc, int& curve, double& amin, double& amid,
+ double& amax)
+ {
+ getConverter(fAcc, p, acc, curve, amin, amid, amax);
+ }
+
+ /**
+ * Used to edit gyroscope curves and mapping. Get curve and related mapping for a
+ * given UI parameter.
+ *
+ * @param p - the UI parameter index
+ * @param gyr - the gyr value to be retrieved (-1 means "no mapping")
+ * @param curve - the curve value to be retrieved
+ * @param amin - the amin value to be retrieved
+ * @param amid - the amid value to be retrieved
+ * @param amax - the amax value to be retrieved
+ *
+ */
+ void getGyrConverter(int p, int& gyr, int& curve, double& amin, double& amid,
+ double& amax)
+ {
+ getConverter(fGyr, p, gyr, curve, amin, amid, amax);
+ }
+
+ /**
+ * Set a new value coming from an gyroscope, propagate it to all relevant FAUSTFLOAT*
+ * zones.
+ *
+ * @param gyr - 0 for X gyroscope, 1 for Y gyroscope, 2 for Z gyroscope
+ * @param value - the new value
+ *
+ */
+ void propagateGyr(int gyr, double value)
+ {
+ for (size_t i = 0; i < fGyr[gyr].size(); i++) {
+ fGyr[gyr][i]->update(value);
+ }
+ }
+
+ /**
+ * Get the number of FAUSTFLOAT* zones controlled with the accelerometer.
+ *
+ * @param acc - 0 for X accelerometer, 1 for Y accelerometer, 2 for Z accelerometer
+ * @return the number of zones
+ *
+ */
+ int getAccCount(int acc) { return (acc >= 0 && acc < 3) ? int(fAcc[acc].size()) : 0; }
+
+ /**
+ * Get the number of FAUSTFLOAT* zones controlled with the gyroscope.
+ *
+ * @param gyr - 0 for X gyroscope, 1 for Y gyroscope, 2 for Z gyroscope
+ * @param the number of zones
+ *
+ */
+ int getGyrCount(int gyr) { return (gyr >= 0 && gyr < 3) ? int(fGyr[gyr].size()) : 0; }
+
+ // getScreenColor() : -1 means no screen color control (no screencolor metadata found)
+ // otherwise return 0x00RRGGBB a ready to use color
+ int getScreenColor()
+ {
+ if (fHasScreenControl) {
+ int r = (fRedReader) ? fRedReader->getValue() : 0;
+ int g = (fGreenReader) ? fGreenReader->getValue() : 0;
+ int b = (fBlueReader) ? fBlueReader->getValue() : 0;
+ return (r << 16) | (g << 8) | b;
+ } else {
+ return -1;
+ }
+ }
+};
+
+#endif
+/************************** END APIUI.h **************************/
+
+// NOTE: "faust -scn name" changes the last line above to
+// #include <faust/name/name.h>
+
+//----------------------------------------------------------------------------
+// FAUST Generated Code
+//----------------------------------------------------------------------------
+
+#ifndef FAUSTFLOAT
+#define FAUSTFLOAT float
+#endif
+
+/* link with : "" */
+#include <math.h>
+
+#include <algorithm>
+#include <cmath>
+#include <cstdint>
+
+#ifndef FAUSTCLASS
+#define FAUSTCLASS meterdsp
+#endif
+
+#ifdef __APPLE__
+#define exp10f __exp10f
+#define exp10 __exp10
+#endif
+
+#if defined(_WIN32)
+#define RESTRICT __restrict
+#else
+#define RESTRICT __restrict__
+#endif
+
+class meterdsp : public dsp
+{
+ private:
+ int fSampleRate;
+
+ public:
+ void metadata(Meta* m)
+ {
+ m->declare("author", "Dominick Hing");
+ m->declare("basics.lib/name", "Faust Basic Element Library");
+ m->declare("basics.lib/version", "0.5");
+ m->declare("compile_options",
+ "-a faust2header.cpp -lang cpp -i -inpl -cn meterdsp -es 1 -mcd 16 "
+ "-single -ftz 0");
+ m->declare("description", "VU Meter Faust Plugin for JackTrip");
+ m->declare("filename", "meterdsp.dsp");
+ m->declare("license", "MIT Style STK-4.2");
+ m->declare("maths.lib/author", "GRAME");
+ m->declare("maths.lib/copyright", "GRAME");
+ m->declare("maths.lib/license", "LGPL with exception");
+ m->declare("maths.lib/name", "Faust Math Library");
+ m->declare("maths.lib/version", "2.5");
+ m->declare("name", "meter");
+ m->declare("version", "1.0");
+ }
+
+ virtual int getNumInputs() { return 1; }
+ virtual int getNumOutputs() { return 1; }
+
+ static void classInit(int /*sample_rate*/) {}
+
+ virtual void instanceConstants(int sample_rate) { fSampleRate = sample_rate; }
+
+ virtual void instanceResetUserInterface() {}
+
+ virtual void instanceClear() {}
+
+ virtual void init(int sample_rate)
+ {
+ classInit(sample_rate);
+ instanceInit(sample_rate);
+ }
+ virtual void instanceInit(int sample_rate)
+ {
+ instanceConstants(sample_rate);
+ instanceResetUserInterface();
+ instanceClear();
+ }
+
+ virtual meterdsp* clone() { return new meterdsp(); }
+
+ virtual int getSampleRate() { return fSampleRate; }
+
+ virtual void buildUserInterface(UI* ui_interface)
+ {
+ ui_interface->openVerticalBox("meter");
+ ui_interface->closeBox();
+ }
+
+ virtual void compute(int count, FAUSTFLOAT** inputs, FAUSTFLOAT** outputs)
+ {
+ FAUSTFLOAT* input0 = inputs[0];
+ FAUSTFLOAT* output0 = outputs[0];
+ for (int i0 = 0; i0 < count; i0 = i0 + 1) {
+ float fTemp0 = float(input0[i0]);
+ float fTemp1 =
+ 20.0f
+ * std::log10(std::max<float>(1.17549435e-38f,
+ std::max<float>(9.99999975e-05f, fTemp0)));
+ float fTemp2 = 100.0f * float(copysignf(float(fTemp1), 1.0f));
+ output0[i0] = FAUSTFLOAT(float(copysignf(
+ float(0.00999999978f
+ * float(int(fTemp2) + (fTemp2 - std::floor(fTemp2) >= 0.5f))),
+ float(fTemp1))));
+ }
+ }
+};
+
+#endif