diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 4755d9137a..b66226e1a5 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -47,6 +47,7 @@ Agent::Agent(const QByteArray& packet) : _scriptEngine.getEntityScriptingInterface()->setPacketSender(&_entityEditSender); DependencyManager::set(); + DependencyManager::set(); } void Agent::readPendingDatagrams() { diff --git a/assignment-client/src/AssignmentClient.cpp b/assignment-client/src/AssignmentClient.cpp index 80f3cbab5e..bf67d4d597 100644 --- a/assignment-client/src/AssignmentClient.cpp +++ b/assignment-client/src/AssignmentClient.cpp @@ -136,7 +136,6 @@ AssignmentClient::AssignmentClient(int &argc, char **argv) : // Create Singleton objects on main thread NetworkAccessManager::getInstance(); - auto soundCache = DependencyManager::get(); } void AssignmentClient::sendAssignmentRequest() { diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index b862cd0c78..e865ab0035 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -479,10 +479,20 @@ void AudioMixer::sendAudioEnvironmentPacket(SharedNodePointer node) { for (int i = 0; i < _zoneReverbSettings.size(); ++i) { AudioMixerClientData* data = static_cast(node->getLinkedData()); glm::vec3 streamPosition = data->getAvatarAudioStream()->getPosition(); - if (_audioZones[_zoneReverbSettings[i].zone].contains(streamPosition)) { + AABox box = _audioZones[_zoneReverbSettings[i].zone]; + if (box.contains(streamPosition)) { hasReverb = true; reverbTime = _zoneReverbSettings[i].reverbTime; wetLevel = _zoneReverbSettings[i].wetLevel; + + // Modulate wet level with distance to wall + float MIN_ATTENUATION_DISTANCE = 2.0f; + float MAX_ATTENUATION = -12; // dB + glm::vec3 distanceToWalls = (box.getDimensions() / 2.0f) - glm::abs(streamPosition - box.calcCenter()); + float distanceToClosestWall = glm::min(distanceToWalls.x, distanceToWalls.z); + if (distanceToClosestWall < MIN_ATTENUATION_DISTANCE) { + wetLevel += MAX_ATTENUATION * (1.0f - distanceToClosestWall / MIN_ATTENUATION_DISTANCE); + } break; } } diff --git a/cmake/externals/gverb/CMakeLists.txt b/cmake/externals/gverb/CMakeLists.txt index 19a44781b1..a73b192fee 100644 --- a/cmake/externals/gverb/CMakeLists.txt +++ b/cmake/externals/gverb/CMakeLists.txt @@ -8,7 +8,7 @@ include(ExternalProject) ExternalProject_Add( ${EXTERNAL_NAME} PREFIX ${EXTERNAL_NAME} - GIT_REPOSITORY https://github.com/birarda/gverb.git + URL http://hifi-public.s3.amazonaws.com/dependencies/gverb-master.zip CMAKE_ARGS ${ANDROID_CMAKE_ARGS} -DCMAKE_INSTALL_PREFIX:PATH= LOG_DOWNLOAD ON ) diff --git a/examples/controllers/oculus/goTo.js b/examples/controllers/oculus/goTo.js index 91ae8e6141..5a5c3429ed 100644 --- a/examples/controllers/oculus/goTo.js +++ b/examples/controllers/oculus/goTo.js @@ -18,6 +18,7 @@ Script.include("../../libraries/globals.js"); Script.include("../../libraries/virtualKeyboard.js"); +Script.include("../../libraries/soundArray.js"); const MAX_SHOW_INSTRUCTION_TIMES = 2; const INSTRUCTIONS_SETTING = "GoToInstructionsShowCounter" @@ -82,6 +83,12 @@ var textFontSize = 9; var text = null; var locationURL = ""; +var randomSounds = new SoundArray({}, true); +var numberOfSounds = 7; +for (var i = 1; i <= numberOfSounds; i++) { + randomSounds.addSound(HIFI_PUBLIC_BUCKET + "sounds/UI/virtualKeyboard-press" + i + ".raw"); +} + function appendChar(char) { locationURL += char; updateTextOverlay(); @@ -107,6 +114,7 @@ function updateTextOverlay() { } keyboard.onKeyPress = function(event) { + randomSounds.playRandom(); if (event.event == 'keypress') { appendChar(event.char); } diff --git a/examples/controllers/oculus/virtualKeyboardTextEntityExample.js b/examples/controllers/oculus/virtualKeyboardTextEntityExample.js index c3cb3c6316..cf36fdbffb 100644 --- a/examples/controllers/oculus/virtualKeyboardTextEntityExample.js +++ b/examples/controllers/oculus/virtualKeyboardTextEntityExample.js @@ -17,6 +17,7 @@ Script.include("../../libraries/globals.js"); Script.include("../../libraries/virtualKeyboard.js"); +Script.include("../../libraries/soundArray.js"); const SPAWN_DISTANCE = 1; const DEFAULT_TEXT_DIMENSION_Z = 0.02; @@ -34,6 +35,12 @@ var text = null; var textText = ""; var textSizeMeasureOverlay = Overlays.addOverlay("text3d", {visible: false}); +var randomSounds = new SoundArray({}, true); +var numberOfSounds = 7; +for (var i = 1; i <= numberOfSounds; i++) { + randomSounds.addSound(HIFI_PUBLIC_BUCKET + "sounds/UI/virtualKeyboard-press" + i + ".raw"); +} + function appendChar(char) { textText += char; updateTextOverlay(); @@ -58,6 +65,7 @@ function updateTextOverlay() { } keyboard.onKeyPress = function(event) { + randomSounds.playRandom(); if (event.event == 'keypress') { appendChar(event.char); } else if (event.event == 'enter') { diff --git a/examples/libraries/soundArray.js b/examples/libraries/soundArray.js new file mode 100644 index 0000000000..813621fb4b --- /dev/null +++ b/examples/libraries/soundArray.js @@ -0,0 +1,42 @@ +/** + * An array for sounds, allows you to randomly play a sound + * taken from the removed editVoxels.js + */ +SoundArray = function(audioOptions, autoUpdateAudioPosition) { + this.audioOptions = audioOptions !== undefined ? audioOptions : {}; + this.autoUpdateAudioPosition = autoUpdateAudioPosition !== undefined ? autoUpdateAudioPosition : false; + if (this.audioOptions.position === undefined) { + this.audioOptions.position = Vec3.sum(MyAvatar.position, { x: 0, y: 1, z: 0}), + } + if (this.audioOptions.volume === undefined) { + this.audioOptions.volume = 1.0; + } + this.sounds = new Array(); + this.addSound = function (soundURL) { + this.sounds[this.sounds.length] = SoundCache.getSound(soundURL); + }; + this.play = function (index) { + if (0 <= index && index < this.sounds.length) { + if (this.autoUpdateAudioPosition) { + this.updateAudioPosition(); + } + if (this.sounds[index].downloaded) { + Audio.playSound(this.sounds[index], this.audioOptions); + } + } else { + print("[ERROR] libraries/soundArray.js:play() : Index " + index + " out of range."); + } + }; + this.playRandom = function () { + if (this.sounds.length > 0) { + this.play(Math.floor(Math.random() * this.sounds.length)); + } else { + print("[ERROR] libraries/soundArray.js:playRandom() : Array is empty."); + } + }; + this.updateAudioPosition = function() { + var position = MyAvatar.position; + var forwardVector = Quat.getFront(MyAvatar.orientation); + this.audioOptions.position = Vec3.sum(position, forwardVector); + }; +}; diff --git a/examples/notifications.js b/examples/notifications.js index 1b512634d7..287bfd0b36 100644 --- a/examples/notifications.js +++ b/examples/notifications.js @@ -1,13 +1,13 @@ -// -// notifications.js -// Version 0.801 -// Created by Adrian +// +// notifications.js +// Version 0.801 +// Created by Adrian // // Adrian McCarlie 8-10-14 // This script demonstrates on-screen overlay type notifications. // Copyright 2014 High Fidelity, Inc. // -// +// // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -20,29 +20,29 @@ // CTRL/m for mic mute and unmute. // System generated notifications: -// Displays users online at startup. +// Displays users online at startup. // If Screen is resized. // Triggers notification if @MyUserName is mentioned in chat. // Announces existing user logging out. // Announces new user logging in. // If mic is muted for any reason. -// +// // To add a new System notification type: // -// 1. Set the Event Connector at the bottom of the script. -// example: +// 1. Set the Event Connector at the bottom of the script. +// example: // GlobalServices.incomingMessage.connect(onIncomingMessage); // -// 2. Create a new function to produce a text string, do not include new line returns. +// 2. Create a new function to produce a text string, do not include new line returns. // example: // function onIncomingMessage(user, message) { -// //do stuff here; +// //do stuff here; // var text = "This is a notification"; // wordWrap(text); // } // // This new function must call wordWrap(text) if the length of message is longer than 42 chars or unknown. -// wordWrap() will format the text to fit the notifications overlay and send it to createNotification(text). +// wordWrap() will format the text to fit the notifications overlay and send it to createNotification(text). // If the message is 42 chars or less you should bypass wordWrap() and call createNotification() directly. @@ -57,6 +57,8 @@ // var welcome = "There are " + GlobalServices.onlineUsers.length + " users online now."; // createNotification(welcome); // } +Script.include("./libraries/globals.js"); +Script.include("./libraries/soundArray.js"); var width = 340.0; //width of notification overlay var windowDimensions = Controller.getViewportDimensions(); // get the size of the interface window @@ -66,7 +68,7 @@ var locationY = 20.0; // position down from top of interface window var topMargin = 13.0; var leftMargin = 10.0; var textColor = { red: 228, green: 228, blue: 228}; // text color -var backColor = { red: 2, green: 2, blue: 2}; // background color was 38,38,38 +var backColor = { red: 2, green: 2, blue: 2}; // background color was 38,38,38 var backgroundAlpha = 0; var fontSize = 12.0; var PERSIST_TIME_2D = 10.0; // Time in seconds before notification fades @@ -81,6 +83,22 @@ var last_users = GlobalServices.onlineUsers; var users = []; var ctrlIsPressed = false; var ready = true; + +var randomSounds = new SoundArray({}, true); +var numberOfSounds = 2; +for (var i = 1; i <= numberOfSounds; i++) { + randomSounds.addSound(HIFI_PUBLIC_BUCKET + "sounds/UI/notification-general" + i + ".raw"); +} + +// When our script shuts down, we should clean up all of our overlays +function scriptEnding() { + for (i = 0; i < notifications.length; i++) { + Overlays.deleteOverlay(notifications[i]); + Overlays.deleteOverlay(buttons[i]); + } +} +Script.scriptEnding.connect(scriptEnding); + var notifications = []; var buttons = []; var times = []; @@ -193,6 +211,8 @@ function notify(notice, button, height) { positions, last; + randomSounds.playRandom(); + if (isOnHMD) { // Calculate 3D values from 2D overlay properties. @@ -454,7 +474,6 @@ function onOnlineUsersChanged(users) { if (last_users.indexOf(users[i]) === -1.0) { createNotification(users[i] + " has joined"); } - } for (i = 0; i < last_users.length; i += 1) { diff --git a/examples/progress.js b/examples/progress.js index 57a1fd1a07..55ea15f218 100644 --- a/examples/progress.js +++ b/examples/progress.js @@ -32,8 +32,7 @@ BAR_URL = "http://hifi-public.s3.amazonaws.com/images/progress-bar.svg", BACKGROUND_WIDTH = 540, BACKGROUND_HEIGHT = 90, - BACKGROUND_URL = "http://ctrlaltstudio.com/hifi/progress-bar-background.svg", - //BACKGROUND_URL = "http://hifi-public.s3.amazonaws.com/images/progress-bar-background.svg", + BACKGROUND_URL = "http://hifi-public.s3.amazonaws.com/images/progress-bar-background.svg", isOnHMD = false, windowWidth = 0, windowHeight = 0, diff --git a/interface/resources/html/img/devices.png b/interface/resources/html/img/devices.png new file mode 100644 index 0000000000..fc4231e96e Binary files /dev/null and b/interface/resources/html/img/devices.png differ diff --git a/interface/resources/html/img/models.png b/interface/resources/html/img/models.png new file mode 100644 index 0000000000..b09c36011d Binary files /dev/null and b/interface/resources/html/img/models.png differ diff --git a/interface/resources/html/img/move.png b/interface/resources/html/img/move.png new file mode 100644 index 0000000000..4444ebbec5 Binary files /dev/null and b/interface/resources/html/img/move.png differ diff --git a/interface/resources/html/img/run-script.png b/interface/resources/html/img/run-script.png new file mode 100644 index 0000000000..941b8ee9f1 Binary files /dev/null and b/interface/resources/html/img/run-script.png differ diff --git a/interface/resources/html/img/talk.png b/interface/resources/html/img/talk.png new file mode 100644 index 0000000000..682e034ab0 Binary files /dev/null and b/interface/resources/html/img/talk.png differ diff --git a/interface/resources/html/img/write-script.png b/interface/resources/html/img/write-script.png new file mode 100644 index 0000000000..dae97e59b1 Binary files /dev/null and b/interface/resources/html/img/write-script.png differ diff --git a/interface/resources/html/interface-welcome-allsvg.html b/interface/resources/html/interface-welcome-allsvg.html deleted file mode 100644 index d025f8059b..0000000000 --- a/interface/resources/html/interface-welcome-allsvg.html +++ /dev/null @@ -1,628 +0,0 @@ - -
- - -Welcome to Interface -Created with Sketch (http://www.bohemiancoding.com/sketch) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Use your best headphones and microphone for high fidelity audio. Chat via text by pressing the Enter key. - -Use two fingers to look around. Turn this off by opening Running Scripts (Cmnd/Cntrl+J) and clicking the X next to lookWithTouch.js - -Move around with WASD & fly up or down with E & C.Cmnd/Cntrl+G will send you home. @ (Shift+2) will let you teleport to a user or location. - -Have an Oculus Rift, a Razer Hydra, or a PrimeSense 3D camera? We support them all. - -Use the editVoxels.js script to build with your mouse – use the tab key to toggle the tools on/off. - - - - - - - - - - - - - - - - - - -Write a script; we’re always adding new features. Cmnd/Cntrl+J will launch a Running Scripts dialog to help manage your scripts. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - A - C - D - S - - W - - - - - - - - - E - - - - - - - - - - - - - - -
diff --git a/interface/resources/html/interface-welcome.html b/interface/resources/html/interface-welcome.html new file mode 100644 index 0000000000..d632cb0e5c --- /dev/null +++ b/interface/resources/html/interface-welcome.html @@ -0,0 +1,190 @@ + + + + + + + Welcome to Interface + + + + + +
+
+

Move around

+ +

+ Move around with WASD & fly
+ up or down with E & C.
+ Cmnd/Ctrl+G will send you
+ home. Hitting Enter will let you
+ teleport to a user or location. +

+
+
+

Listen & talk

+ +

+ Use your best headphones
+ and microphone for high
+ fidelity audio. Chat via text by
+ pressing the \ key. +

+
+
+

Connect devices

+ +

+ Have an Oculus Rift, a Razer
+ Hydra, or a PrimeSense 3D
+ camera? We support them all. +

+
+
+

Run a script

+ +

+ Cmnd/Cntrl+J will launch a
+ Running Scripts dialog to help
+ manage your scripts and search
+ for new ones to run. +

+
+
+

Script something

+ +

+ Write a script; we're always
+ adding new features.
+ Cmnd/Cntrl+J will launch a
+ Running Scripts dialog to help
+ manage your scripts. +

+
+
+

Import models

+ +

+ Use the editEntitles.js script to
+ add FBX models in-world. You
+ can use grids and fine tune
+ placement-related parameters
+ with ease. +

+
+
+
+

Read the docs

+

+ We are writing documentation on
+ just about everything. Please,
+ devour all we've written and make
+ suggestions where necessary.
+ Documentation is always at
+ docs.highfidelity.io +

+
+
+
+
+ + + + + \ No newline at end of file diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index ce33b04777..1211df3727 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -140,6 +140,12 @@ static unsigned STARFIELD_SEED = 1; const qint64 MAXIMUM_CACHE_SIZE = 10737418240; // 10GB +static QTimer* locationUpdateTimer = NULL; +static QTimer* balanceUpdateTimer = NULL; +static QTimer* silentNodeTimer = NULL; +static QTimer* identityPacketTimer = NULL; +static QTimer* billboardPacketTimer = NULL; +static QTimer* checkFPStimer = NULL; static QTimer* idleTimer = NULL; const QString CHECK_VERSION_URL = "https://highfidelity.io/latestVersion.xml"; @@ -362,7 +368,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : // update our location every 5 seconds in the data-server, assuming that we are authenticated with one const qint64 DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS = 5 * 1000; - QTimer* locationUpdateTimer = new QTimer(this); + locationUpdateTimer = new QTimer(this); connect(locationUpdateTimer, &QTimer::timeout, this, &Application::updateLocationInServer); locationUpdateTimer->start(DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS); @@ -378,7 +384,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : const qint64 BALANCE_UPDATE_INTERVAL_MSECS = 5 * 1000; - QTimer* balanceUpdateTimer = new QTimer(this); + balanceUpdateTimer = new QTimer(this); connect(balanceUpdateTimer, &QTimer::timeout, &accountManager, &AccountManager::updateBalance); balanceUpdateTimer->start(BALANCE_UPDATE_INTERVAL_MSECS); @@ -417,18 +423,18 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : connect(&_entityEditSender, &EntityEditPacketSender::packetSent, this, &Application::packetSent); // move the silentNodeTimer to the _nodeThread - QTimer* silentNodeTimer = new QTimer(); + silentNodeTimer = new QTimer(); connect(silentNodeTimer, SIGNAL(timeout()), nodeList.data(), SLOT(removeSilentNodes())); silentNodeTimer->start(NODE_SILENCE_THRESHOLD_MSECS); silentNodeTimer->moveToThread(_nodeThread); // send the identity packet for our avatar each second to our avatar mixer - QTimer* identityPacketTimer = new QTimer(); + identityPacketTimer = new QTimer(); connect(identityPacketTimer, &QTimer::timeout, _myAvatar, &MyAvatar::sendIdentityPacket); identityPacketTimer->start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS); // send the billboard packet for our avatar every few seconds - QTimer* billboardPacketTimer = new QTimer(); + billboardPacketTimer = new QTimer(); connect(billboardPacketTimer, &QTimer::timeout, _myAvatar, &MyAvatar::sendBillboardPacket); billboardPacketTimer->start(AVATAR_BILLBOARD_PACKET_SEND_INTERVAL_MSECS); @@ -527,11 +533,28 @@ void Application::aboutToQuit() { } void Application::cleanupBeforeQuit() { - // make sure we don't call the idle timer any more + // first stop all timers directly or by invokeMethod + // depending on what thread they run in + locationUpdateTimer->stop(); + balanceUpdateTimer->stop(); + QMetaObject::invokeMethod(silentNodeTimer, "stop", Qt::BlockingQueuedConnection); + identityPacketTimer->stop(); + billboardPacketTimer->stop(); + checkFPStimer->stop(); + idleTimer->stop(); + QMetaObject::invokeMethod(&_settingsTimer, "stop", Qt::BlockingQueuedConnection); + + // and then delete those that got created by "new" + delete locationUpdateTimer; + delete balanceUpdateTimer; + delete silentNodeTimer; + delete identityPacketTimer; + delete billboardPacketTimer; + delete checkFPStimer; delete idleTimer; + // no need to delete _settingsTimer here as it is no pointer // save state - QMetaObject::invokeMethod(&_settingsTimer, "stop", Qt::BlockingQueuedConnection); _settingsThread.quit(); saveSettings(); _window->saveGeometry(); @@ -630,9 +653,9 @@ void Application::initializeGL() { _entityEditSender.initialize(_enableProcessOctreeThread); // call our timer function every second - QTimer* timer = new QTimer(this); - connect(timer, SIGNAL(timeout()), SLOT(timer())); - timer->start(1000); + checkFPStimer = new QTimer(this); + connect(checkFPStimer, SIGNAL(timeout()), SLOT(checkFPS())); + checkFPStimer->start(1000); // call our idle function whenever we can idleTimer = new QTimer(this); @@ -1428,7 +1451,7 @@ void Application::sendPingPackets() { } // Every second, check the frame rates and other stuff -void Application::timer() { +void Application::checkFPS() { if (Menu::getInstance()->isOptionChecked(MenuOption::TestPing)) { sendPingPackets(); } diff --git a/interface/src/Application.h b/interface/src/Application.h index da29920ef9..f7a71e1e7c 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -111,7 +111,7 @@ static const float MIRROR_FIELD_OF_VIEW = 30.0f; static const quint64 TOO_LONG_SINCE_LAST_SEND_DOWNSTREAM_AUDIO_STATS = 1 * USECS_PER_SECOND; -static const QString INFO_HELP_PATH = "html/interface-welcome-allsvg.html"; +static const QString INFO_HELP_PATH = "html/interface-welcome.html"; static const QString INFO_EDIT_ENTITIES_PATH = "html/edit-entities-commands.html"; #ifdef Q_OS_WIN @@ -368,7 +368,7 @@ public slots: private slots: void clearDomainOctreeDetails(); - void timer(); + void checkFPS(); void idle(); void aboutToQuit(); diff --git a/interface/src/ui/InfoView.cpp b/interface/src/ui/InfoView.cpp index 607b222207..b62b8f3f2d 100644 --- a/interface/src/ui/InfoView.cpp +++ b/interface/src/ui/InfoView.cpp @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -32,6 +33,9 @@ InfoView::InfoView(bool forced, QString path) : QString absPath = QFileInfo(PathUtils::resourcesPath() + path).absoluteFilePath(); QUrl url = QUrl::fromLocalFile(absPath); + page()->setLinkDelegationPolicy(QWebPage::DelegateExternalLinks); + connect(this, SIGNAL(linkClicked(QUrl)), this, SLOT(linkClickedInfoView(QUrl))); + load(url); connect(this, SIGNAL(loadFinished(bool)), this, SLOT(loaded(bool))); } @@ -83,3 +87,8 @@ void InfoView::loaded(bool ok) { setAttribute(Qt::WA_DeleteOnClose); show(); } + +void InfoView::linkClickedInfoView(QUrl url) { + close(); + QDesktopServices::openUrl(url); +} diff --git a/interface/src/ui/InfoView.h b/interface/src/ui/InfoView.h index 47d5dac9ce..1198a703e4 100644 --- a/interface/src/ui/InfoView.h +++ b/interface/src/ui/InfoView.h @@ -27,6 +27,7 @@ private: private slots: void loaded(bool ok); + void linkClickedInfoView(QUrl url); }; #endif // hifi_InfoView_h diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 49dd1e1a35..f7777c26a2 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -102,7 +102,6 @@ AudioClient::AudioClient() : _audioSourceInjectEnabled(false), _reverb(false), _reverbOptions(&_scriptReverbOptions), - _gverbLocal(NULL), _gverb(NULL), _inputToNetworkResampler(NULL), _networkToOutputResampler(NULL), @@ -119,26 +118,23 @@ AudioClient::AudioClient() : connect(&_receivedAudioStream, &MixedProcessedAudioStream::processSamples, this, &AudioClient::processReceivedSamples, Qt::DirectConnection); - - // Initialize GVerb - initGverb(); - - const qint64 DEVICE_CHECK_INTERVAL_MSECS = 2 * 1000; _inputDevices = getDeviceNames(QAudio::AudioInput); _outputDevices = getDeviceNames(QAudio::AudioOutput); + const qint64 DEVICE_CHECK_INTERVAL_MSECS = 2 * 1000; QTimer* updateTimer = new QTimer(this); connect(updateTimer, &QTimer::timeout, this, &AudioClient::checkDevices); updateTimer->start(DEVICE_CHECK_INTERVAL_MSECS); + + // create GVerb filter + _gverb = createGverbFilter(); + configureGverbFilter(_gverb); } AudioClient::~AudioClient() { stop(); - if (_gverbLocal) { - gverb_free(_gverbLocal); - } if (_gverb) { gverb_free(_gverb); } @@ -151,6 +147,8 @@ void AudioClient::reset() { _toneSource.reset(); _sourceGain.reset(); _inputGain.reset(); + + gverb_flush(_gverb); } void AudioClient::audioMixerKilled() { @@ -484,8 +482,8 @@ void AudioClient::start() { _sourceGain.initialize(); _noiseSource.initialize(); _toneSource.initialize(); - _sourceGain.setParameters(0.25f,0.0f); - _inputGain.setParameters(1.0f,0.0f); + _sourceGain.setParameters(0.25f, 0.0f); + _inputGain.setParameters(1.0f, 0.0f); } void AudioClient::stop() { @@ -528,38 +526,24 @@ bool AudioClient::switchOutputToAudioDevice(const QString& outputDeviceName) { return switchOutputToAudioDevice(getNamedAudioDeviceForMode(QAudio::AudioOutput, outputDeviceName)); } -void AudioClient::initGverb() { +ty_gverb* AudioClient::createGverbFilter() { // Initialize a new gverb instance - if (_gverbLocal) { - gverb_free(_gverbLocal); - } - _gverbLocal = gverb_new(_outputFormat.sampleRate(), _reverbOptions->getMaxRoomSize(), _reverbOptions->getRoomSize(), - _reverbOptions->getReverbTime(), _reverbOptions->getDamping(), _reverbOptions->getSpread(), - _reverbOptions->getInputBandwidth(), _reverbOptions->getEarlyLevel(), - _reverbOptions->getTailLevel()); - - if (_gverb) { - gverb_free(_gverb); - } - _gverb = gverb_new(_outputFormat.sampleRate(), _reverbOptions->getMaxRoomSize(), _reverbOptions->getRoomSize(), - _reverbOptions->getReverbTime(), _reverbOptions->getDamping(), _reverbOptions->getSpread(), - _reverbOptions->getInputBandwidth(), _reverbOptions->getEarlyLevel(), - _reverbOptions->getTailLevel()); + ty_gverb* filter = gverb_new(_outputFormat.sampleRate(), _reverbOptions->getMaxRoomSize(), _reverbOptions->getRoomSize(), + _reverbOptions->getReverbTime(), _reverbOptions->getDamping(), _reverbOptions->getSpread(), + _reverbOptions->getInputBandwidth(), _reverbOptions->getEarlyLevel(), + _reverbOptions->getTailLevel()); + return filter; +} + +void AudioClient::configureGverbFilter(ty_gverb* filter) { // Configure the instance (these functions are not super well named - they actually set several internal variables) - gverb_set_roomsize(_gverbLocal, _reverbOptions->getRoomSize()); - gverb_set_revtime(_gverbLocal, _reverbOptions->getReverbTime()); - gverb_set_damping(_gverbLocal, _reverbOptions->getDamping()); - gverb_set_inputbandwidth(_gverbLocal, _reverbOptions->getInputBandwidth()); - gverb_set_earlylevel(_gverbLocal, DB_CO(_reverbOptions->getEarlyLevel())); - gverb_set_taillevel(_gverbLocal, DB_CO(_reverbOptions->getTailLevel())); - - gverb_set_roomsize(_gverb, _reverbOptions->getRoomSize()); - gverb_set_revtime(_gverb, _reverbOptions->getReverbTime()); - gverb_set_damping(_gverb, _reverbOptions->getDamping()); - gverb_set_inputbandwidth(_gverb, _reverbOptions->getInputBandwidth()); - gverb_set_earlylevel(_gverb, DB_CO(_reverbOptions->getEarlyLevel())); - gverb_set_taillevel(_gverb, DB_CO(_reverbOptions->getTailLevel())); + gverb_set_roomsize(filter, _reverbOptions->getRoomSize()); + gverb_set_revtime(filter, _reverbOptions->getReverbTime()); + gverb_set_damping(filter, _reverbOptions->getDamping()); + gverb_set_inputbandwidth(filter, _reverbOptions->getInputBandwidth()); + gverb_set_earlylevel(filter, DB_CO(_reverbOptions->getEarlyLevel())); + gverb_set_taillevel(filter, DB_CO(_reverbOptions->getTailLevel())); } void AudioClient::updateGverbOptions() { @@ -572,7 +556,7 @@ void AudioClient::updateGverbOptions() { } if (_zoneReverbOptions.getWetLevel() != _receivedAudioStream.getWetLevel()) { _zoneReverbOptions.setWetLevel(_receivedAudioStream.getWetLevel()); - reverbChanged = true; + // Not part of actual filter config, no need to set reverbChanged to true } if (_reverbOptions != &_zoneReverbOptions) { @@ -585,7 +569,17 @@ void AudioClient::updateGverbOptions() { } if (reverbChanged) { - initGverb(); + gverb_free(_gverb); + _gverb = createGverbFilter(); + configureGverbFilter(_gverb); + } +} + +void AudioClient::setReverb(bool reverb) { + _reverb = reverb; + + if (!_reverb) { + gverb_flush(_gverb); } } @@ -604,14 +598,17 @@ void AudioClient::setReverbOptions(const AudioEffectOptions* options) { _scriptReverbOptions.setWetLevel(options->getWetLevel()); if (_reverbOptions == &_scriptReverbOptions) { - // Apply them to the reverb instance(s) - initGverb(); + // Apply them to the reverb instances + gverb_free(_gverb); + _gverb = createGverbFilter(); + configureGverbFilter(_gverb); } } -void AudioClient::addReverb(ty_gverb* gverb, int16_t* samplesData, int numSamples, QAudioFormat& audioFormat, bool noEcho) { +void AudioClient::addReverb(ty_gverb* gverb, int16_t* samplesData, int16_t* reverbAlone, int numSamples, + QAudioFormat& audioFormat, bool noEcho) { float wetFraction = DB_CO(_reverbOptions->getWetLevel()); - float dryFraction = (noEcho) ? 0.0f : (1.0f - wetFraction); + float dryFraction = 1.0f - wetFraction; float lValue,rValue; for (int sample = 0; sample < numSamples; sample += audioFormat.channelCount()) { @@ -626,11 +623,19 @@ void AudioClient::addReverb(ty_gverb* gverb, int16_t* samplesData, int numSample int lResult = glm::clamp((int)(samplesData[j] * dryFraction + lValue * wetFraction), AudioConstants::MIN_SAMPLE_VALUE, AudioConstants::MAX_SAMPLE_VALUE); samplesData[j] = (int16_t)lResult; + + if (noEcho) { + reverbAlone[j] = (int16_t)lValue * wetFraction; + } } else if (j == (sample + 1)) { // right channel int rResult = glm::clamp((int)(samplesData[j] * dryFraction + rValue * wetFraction), AudioConstants::MIN_SAMPLE_VALUE, AudioConstants::MAX_SAMPLE_VALUE); samplesData[j] = (int16_t)rResult; + + if (noEcho) { + reverbAlone[j] = (int16_t)rValue * wetFraction; + } } else { // ignore channels above 2 } @@ -640,9 +645,8 @@ void AudioClient::addReverb(ty_gverb* gverb, int16_t* samplesData, int numSample void AudioClient::handleLocalEchoAndReverb(QByteArray& inputByteArray) { // If there is server echo, reverb will be applied to the recieved audio stream so no need to have it here. - bool hasLocalReverb = (_reverb || _receivedAudioStream.hasReverb()) && - !_shouldEchoToServer; - if (_muted || !_audioOutput || (!_shouldEchoLocally && !hasLocalReverb)) { + bool hasReverb = _reverb || _receivedAudioStream.hasReverb(); + if (_muted || !_audioOutput || (!_shouldEchoLocally && !hasReverb)) { return; } @@ -652,6 +656,10 @@ void AudioClient::handleLocalEchoAndReverb(QByteArray& inputByteArray) { if (!_loopbackOutputDevice && _loopbackAudioOutput) { // we didn't have the loopback output device going so set that up now _loopbackOutputDevice = _loopbackAudioOutput->start(); + + if (!_loopbackOutputDevice) { + return; + } } // do we need to setup a resampler? @@ -664,26 +672,31 @@ void AudioClient::handleLocalEchoAndReverb(QByteArray& inputByteArray) { } } + static QByteArray reverbAlone; // Intermediary for local reverb with no echo static QByteArray loopBackByteArray; - loopBackByteArray.resize(numDestinationSamplesRequired(_inputFormat, _outputFormat, - inputByteArray.size() / sizeof(int16_t)) * sizeof(int16_t)); + + int numInputSamples = inputByteArray.size() / sizeof(int16_t); + int numLoopbackSamples = numDestinationSamplesRequired(_inputFormat, _outputFormat, numInputSamples); + + reverbAlone.resize(numInputSamples * sizeof(int16_t)); + loopBackByteArray.resize(numLoopbackSamples * sizeof(int16_t)); + + int16_t* inputSamples = reinterpret_cast(inputByteArray.data()); + int16_t* reverbAloneSamples = reinterpret_cast(reverbAlone.data()); + int16_t* loopbackSamples = reinterpret_cast(loopBackByteArray.data()); + + if (hasReverb) { + updateGverbOptions(); + addReverb(_gverb, inputSamples, reverbAloneSamples, numInputSamples, + _inputFormat, !_shouldEchoLocally); + } possibleResampling(_loopbackResampler, - reinterpret_cast(inputByteArray.data()), - reinterpret_cast(loopBackByteArray.data()), - inputByteArray.size() / sizeof(int16_t), loopBackByteArray.size() / sizeof(int16_t), + (_shouldEchoLocally) ? inputSamples : reverbAloneSamples, loopbackSamples, + numInputSamples, numLoopbackSamples, _inputFormat, _outputFormat); - if (hasLocalReverb) { - int16_t* loopbackSamples = reinterpret_cast(loopBackByteArray.data()); - int numLoopbackSamples = loopBackByteArray.size() / sizeof(int16_t); - updateGverbOptions(); - addReverb(_gverbLocal, loopbackSamples, numLoopbackSamples, _outputFormat, !_shouldEchoLocally); - } - - if (_loopbackOutputDevice) { - _loopbackOutputDevice->write(loopBackByteArray); - } + _loopbackOutputDevice->write(loopBackByteArray); } void AudioClient::handleAudioInput() { @@ -877,11 +890,6 @@ void AudioClient::processReceivedSamples(const QByteArray& inputBuffer, QByteArr reinterpret_cast(outputBuffer.data()), numNetworkOutputSamples, numDeviceOutputSamples, _desiredOutputFormat, _outputFormat); - - if(_reverb || _receivedAudioStream.hasReverb()) { - updateGverbOptions(); - addReverb(_gverb, (int16_t*)outputBuffer.data(), numDeviceOutputSamples, _outputFormat); - } } void AudioClient::sendMuteEnvironmentPacket() { diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 811282c0a1..941fcc6c12 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -176,7 +176,7 @@ public slots: float getInputVolume() const { return (_audioInput) ? _audioInput->volume() : 0.0f; } void setInputVolume(float volume) { if (_audioInput) _audioInput->setVolume(volume); } - void setReverb(bool reverb) { _reverb = reverb; } + void setReverb(bool reverb); void setReverbOptions(const AudioEffectOptions* options); void outputNotify(); @@ -251,7 +251,6 @@ private: AudioEffectOptions _scriptReverbOptions; AudioEffectOptions _zoneReverbOptions; AudioEffectOptions* _reverbOptions; - ty_gverb* _gverbLocal; ty_gverb* _gverb; // possible soxr streams needed for resample @@ -260,9 +259,10 @@ private: soxr* _loopbackResampler; // Adds Reverb - void initGverb(); + ty_gverb* createGverbFilter(); + void configureGverbFilter(ty_gverb* filter); void updateGverbOptions(); - void addReverb(ty_gverb* gverb, int16_t* samples, int numSamples, QAudioFormat& format, bool noEcho = false); + void addReverb(ty_gverb* gverb, int16_t* samples, int16_t* reverbAlone, int numSamples, QAudioFormat& format, bool noEcho = false); void handleLocalEchoAndReverb(QByteArray& inputByteArray); diff --git a/libraries/octree/src/OctreeQuery.h b/libraries/octree/src/OctreeQuery.h index 66abe53931..1fefabdf68 100644 --- a/libraries/octree/src/OctreeQuery.h +++ b/libraries/octree/src/OctreeQuery.h @@ -95,7 +95,7 @@ protected: glm::vec3 _cameraPosition = glm::vec3(0.0f); glm::quat _cameraOrientation = glm::quat(); float _cameraFov = 0.0f; - float _cameraAspectRatio = 0.0f; + float _cameraAspectRatio = 1.0f; float _cameraNearClip = 0.0f; float _cameraFarClip = 0.0f; glm::vec3 _cameraEyeOffsetPosition = glm::vec3(0.0f);