diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index cd07f21300..a9f4c8eed7 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2648,6 +2648,8 @@ void Application::displayOverlay() { audioMeterY, Menu::getInstance()->isOptionChecked(MenuOption::Mirror)); + _audio.renderScope(_glWidget->width(), _glWidget->height()); + glBegin(GL_QUADS); if (isClipping) { glColor3f(1, 0, 0); diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index 698a8db725..1e85e77883 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -92,7 +92,14 @@ Audio::Audio(int16_t initialJitterBufferSamples, QObject* parent) : _processSpatialAudio(false), _spatialAudioStart(0), _spatialAudioFinish(0), - _spatialAudioRingBuffer(NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL, true) // random access mode + _spatialAudioRingBuffer(NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL, true), // random access mode + _scopeEnabled(false), + _scopeEnabledPause(false), + _scopeInputOffset(0), + _scopeOutputOffset(0), + _scopeInput(SAMPLES_PER_SCOPE_H * sizeof(int16_t), 0), + _scopeOutputLeft(SAMPLES_PER_SCOPE_H * sizeof(int16_t), 0), + _scopeOutputRight(SAMPLES_PER_SCOPE_H * sizeof(int16_t), 0) { // clear the array of locally injected samples memset(_localProceduralSamples, 0, NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL); @@ -575,6 +582,14 @@ void Audio::handleAudioInput() { processProceduralAudio(monoAudioSamples, NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL); } + if (_scopeEnabled && !_scopeEnabledPause) { + unsigned int numMonoAudioChannels = 1; + unsigned int monoAudioChannel = 0; + addBufferToScope(_scopeInput, _scopeInputOffset, monoAudioSamples, monoAudioChannel, numMonoAudioChannels); + _scopeInputOffset += NETWORK_SAMPLES_PER_FRAME; + _scopeInputOffset %= SAMPLES_PER_SCOPE_H; + } + NodeList* nodeList = NodeList::getInstance(); SharedNodePointer audioMixer = nodeList->soloNodeOfType(NodeType::AudioMixer); @@ -813,6 +828,30 @@ void Audio::processReceivedAudio(const QByteArray& audioByteArray) { if (_outputDevice) { _outputDevice->write(outputBuffer); } + + if (_scopeEnabled && !_scopeEnabledPause) { + unsigned int numAudioChannels = _desiredOutputFormat.channelCount(); + int16_t* samples = ringBufferSamples; + for (int numSamples = numNetworkOutputSamples / numAudioChannels; numSamples > 0; numSamples -= NETWORK_SAMPLES_PER_FRAME) { + + unsigned int audioChannel = 0; + addBufferToScope( + _scopeOutputLeft, + _scopeOutputOffset, + samples, audioChannel, numAudioChannels); + + audioChannel = 1; + addBufferToScope( + _scopeOutputRight, + _scopeOutputOffset, + samples, audioChannel, numAudioChannels); + + _scopeOutputOffset += NETWORK_SAMPLES_PER_FRAME; + _scopeOutputOffset %= SAMPLES_PER_SCOPE_H; + samples += NETWORK_SAMPLES_PER_FRAME * numAudioChannels; + } + } + delete[] ringBufferSamples; } } @@ -1019,6 +1058,139 @@ void Audio::renderToolBox(int x, int y, bool boxed) { glDisable(GL_TEXTURE_2D); } +void Audio::toggleScopePause() { + _scopeEnabledPause = !_scopeEnabledPause; +} + +void Audio::toggleScope() { + _scopeEnabled = !_scopeEnabled; + if (_scopeEnabled) { + static const int width = SAMPLES_PER_SCOPE_H; + _scopeInputOffset = 0; + _scopeOutputOffset = 0; + memset(_scopeInput.data(), 0, width * sizeof(int16_t)); + memset(_scopeOutputLeft.data(), 0, width * sizeof(int16_t)); + memset(_scopeOutputRight.data(), 0, width * sizeof(int16_t)); + } +} + +void Audio::addBufferToScope( + QByteArray& byteArray, unsigned int frameOffset, const int16_t* src, unsigned int srcChannel, unsigned int srcNumChannels) { + + int16_t* tgt = (int16_t*)byteArray.data() + frameOffset; + src += srcChannel; + + for (int i = NETWORK_SAMPLES_PER_FRAME; --i >= 0; src += srcNumChannels) { + float sample = (float)*src; + float multiplier = (float)MULTIPLIER_SCOPE_V / logf(2.0f); + int16_t value; + + if (sample > 0) { + value = (int16_t)(multiplier * logf(sample)); + } else if (sample < 0) { + value = (int16_t)(-multiplier * logf(-sample)); + } else { + value = 0; + } + + *tgt++ = value; + //*tgt++ = sample / 0x7fff * (SAMPLES_PER_SCOPE_V / 2); + } +} + +void Audio::renderScope(int width, int height) { + + if (!_scopeEnabled) + return; + + static const unsigned int backgroundColor = 0x33333399; + static const unsigned int gridColor = 0x44444499; + static const unsigned int inputColor = 0x44aa4499; + static const unsigned int outputLeftColor = 0xaa444499; + static const unsigned int outputRightColor = 0x4444aa99; + static const int gridRows = 2; + static const int gridCols = 5; + + int x = (width - SAMPLES_PER_SCOPE_H) / 2; + int y = (height - SAMPLES_PER_SCOPE_V) / 2; + int w = SAMPLES_PER_SCOPE_H; + int h = SAMPLES_PER_SCOPE_V; + + renderBackground(backgroundColor, x, y, w, h); + renderGrid(gridColor, x, y, w, h, gridRows, gridCols); + renderLineStrip(inputColor, x, y, w, _scopeInputOffset, _scopeInput); + renderLineStrip(outputLeftColor, x, y, w, _scopeOutputOffset, _scopeOutputLeft); + renderLineStrip(outputRightColor, x, y, w, _scopeOutputOffset, _scopeOutputRight); +} + +void Audio::renderBackground(unsigned int rgba, int x, int y, int width, int height) { + glBegin(GL_QUADS); + glColor4f(((rgba >> 24) & 0xff) / 255.0f, + ((rgba >> 16) & 0xff) / 255.0f, + ((rgba >> 8) & 0xff) / 255.0f, + (rgba & 0xff) / 255.0f); + glVertex2i(x, y); + glVertex2i(x + width, y); + glVertex2i(x + width, y + height); + glVertex2i(x , y + height); + glEnd(); + glColor4f(1, 1, 1, 1); +} + +void Audio::renderGrid(unsigned int rgba, int x, int y, int width, int height, int rows, int cols) { + glBegin(GL_LINES); + glColor4f(((rgba >> 24) & 0xff) / 255.0f, + ((rgba >> 16) & 0xff) / 255.0f, + ((rgba >> 8) & 0xff) / 255.0f, + (rgba & 0xff) / 255.0f); + + int dx = width / cols; + int dy = height / rows; + int tx = x; + int ty = y; + + // Draw horizontal grid lines + for (int i = rows + 1; --i >= 0; ) { + glVertex2i(x, ty); + glVertex2i(x + width, ty); + ty += dy; + } + // Draw vertical grid lines + for (int i = cols + 1; --i >= 0; ) { + glVertex2i(tx, y); + glVertex2i(tx, y + height); + tx += dx; + } + glEnd(); + glColor4f(1, 1, 1, 1); +} + +void Audio::renderLineStrip(unsigned int rgba, int x, int y, int n, int offset, const QByteArray& byteArray) { + + glColor4f(((rgba >> 24) & 0xff) / 255.0f, + ((rgba >> 16) & 0xff) / 255.0f, + ((rgba >> 8) & 0xff) / 255.0f, + (rgba & 0xff) / 255.0f); + + glBegin(GL_LINE_STRIP); + + int16_t sample; + int16_t* samples = ((int16_t*) byteArray.data()) + offset; + y += SAMPLES_PER_SCOPE_V / 2; + for (int i = n - offset; --i >= 0; ) { + sample = *samples++; + glVertex2i(x++, y - sample); + } + samples = (int16_t*) byteArray.data(); + for (int i = offset; --i >= 0; ) { + sample = *samples++; + glVertex2i(x++, y - sample); + } + glEnd(); + glColor4f(1, 1, 1, 1); +} + + bool Audio::switchInputToAudioDevice(const QAudioDeviceInfo& inputDeviceInfo) { bool supportedFormat = false; diff --git a/interface/src/Audio.h b/interface/src/Audio.h index 96def43dd2..4d4501c93d 100644 --- a/interface/src/Audio.h +++ b/interface/src/Audio.h @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -67,6 +68,7 @@ public: bool mousePressEvent(int x, int y); void renderToolBox(int x, int y, bool boxed); + void renderScope(int width, int height); int getNetworkSampleRate() { return SAMPLE_RATE; } int getNetworkBufferLengthSamplesPerChannel() { return NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL; } @@ -83,6 +85,8 @@ public slots: void toggleMute(); void toggleAudioNoiseReduction(); void toggleToneInjection(); + void toggleScope(); + void toggleScopePause(); void toggleAudioSpatialProcessing(); virtual void handleAudioByteArray(const QByteArray& audioByteArray); @@ -196,6 +200,28 @@ private: int calculateNumberOfFrameSamples(int numBytes); float calculateDeviceToNetworkInputRatio(int numBytes); + // Audio scope methods for data acquisition + void addBufferToScope(QByteArray& byteArray, unsigned int frameOffset, const int16_t* src, unsigned int srcChannel, unsigned int srcNumChannels); + + // Audio scope methods for rendering + void renderBackground(unsigned int rgba, int x, int y, int width, int height); + void renderGrid(unsigned int rgba, int x, int y, int width, int height, int rows, int cols); + void renderLineStrip(unsigned int rgba, int x, int y, int n, int offset, const QByteArray& byteArray); + + // Audio scope data + static const unsigned int NETWORK_SAMPLES_PER_FRAME = NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL; + static const unsigned int FRAMES_PER_SCOPE = 5; + static const unsigned int SAMPLES_PER_SCOPE_H = FRAMES_PER_SCOPE * NETWORK_SAMPLES_PER_FRAME; + static const unsigned int MULTIPLIER_SCOPE_V = 20; + static const unsigned int SAMPLES_PER_SCOPE_V = 2 * 15 * MULTIPLIER_SCOPE_V; + bool _scopeEnabled; + bool _scopeEnabledPause; + int _scopeInputOffset; + int _scopeOutputOffset; + QByteArray _scopeInput; + QByteArray _scopeOutputLeft; + QByteArray _scopeOutputRight; + }; diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 7cdd72afd5..2e4abaa17d 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -259,6 +259,9 @@ Menu::Menu() : addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Bandwidth, 0, true); addActionToQMenuAndActionHash(viewMenu, MenuOption::BandwidthDetails, 0, this, SLOT(bandwidthDetails())); addActionToQMenuAndActionHash(viewMenu, MenuOption::OctreeStats, 0, this, SLOT(octreeStatsDetails())); + addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::AudioScope, 0, false, + appInstance->getAudio(), + SLOT(toggleScope())); QMenu* developerMenu = addMenu("Developer"); @@ -385,6 +388,11 @@ Menu::Menu() : false, appInstance->getAudio(), SLOT(toggleToneInjection())); + addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::AudioScopePause, + Qt::CTRL | Qt::Key_P, + false, + appInstance->getAudio(), + SLOT(toggleScopePause())); QMenu* spatialAudioMenu = audioDebugMenu->addMenu("Spatial Audio"); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 597ae4a74b..ddcd0571f8 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -256,8 +256,9 @@ namespace MenuOption { const QString AmbientOcclusion = "Ambient Occlusion"; const QString Atmosphere = "Atmosphere"; const QString AudioNoiseReduction = "Audio Noise Reduction"; + const QString AudioScope = "Audio Scope"; + const QString AudioScopePause = "Pause Audio Scope"; const QString AudioToneInjection = "Inject Test Tone"; - const QString AudioSpatialProcessing = "Audio Spatial Processing"; const QString AudioSpatialProcessingHeadOriented = "Head Oriented"; const QString AudioSpatialProcessingIncludeOriginal = "Includes Network Original"; diff --git a/interface/src/ui/InfoView.cpp b/interface/src/ui/InfoView.cpp index 06de3fdf2e..99a4f8cb06 100644 --- a/interface/src/ui/InfoView.cpp +++ b/interface/src/ui/InfoView.cpp @@ -28,8 +28,8 @@ InfoView::InfoView(bool forced) : QString absPath = QFileInfo(Application::resourcesPath() + "html/interface-welcome-allsvg.html").absoluteFilePath(); QUrl url = QUrl::fromLocalFile(absPath); - load(url); - connect(this, SIGNAL(loadFinished(bool)), this, SLOT(loaded(bool))); +// load(url); +// connect(this, SIGNAL(loadFinished(bool)), this, SLOT(loaded(bool))); } void InfoView::showFirstTime() {