diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 84022d0fb9..6d8095ccf0 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -45,6 +45,7 @@ extern "C" { #include #include #include +#include #include #include "AudioInjector.h" @@ -1338,3 +1339,71 @@ void AudioClient::audioStateChanged(QAudio::State state) { emit audioFinished(); } } + +AudioInjector* AudioClient::playSound(const QString& soundUrl, const float volume, const float stretchFactor, const glm::vec3 position) { + if (soundUrl.isEmpty()) { + return NULL; + } + auto soundCache = DependencyManager::get(); + if (soundCache.isNull()) { + return NULL; + } + SharedSoundPointer sound = soundCache.data()->getSound(QUrl(soundUrl)); + if (sound.isNull() || !sound->isReady()) { + return NULL; + } + + // Quiet sound aren't really heard at all, so we can compress everything to the range [1-c, 1], if we play it all. + const float COLLISION_SOUND_COMPRESSION_RANGE = 1.0f; // This section could be removed when the value is 1, but let's see how it goes. + const float compressedVolume = (volume * COLLISION_SOUND_COMPRESSION_RANGE) + (1.0f - COLLISION_SOUND_COMPRESSION_RANGE); + + // This is quite similar to AudioScriptingInterface::playSound() and should probably be refactored. + AudioInjectorOptions options; + options.stereo = sound->isStereo(); + options.position = position; + options.volume = compressedVolume; + + QByteArray samples = sound->getByteArray(); + if (stretchFactor == 1.0f) { + return playSound(samples, options); + } + + soxr_io_spec_t spec = soxr_io_spec(SOXR_INT16_I, SOXR_INT16_I); + soxr_quality_spec_t qualitySpec = soxr_quality_spec(SOXR_MQ, 0); + const int channelCount = sound->isStereo() ? 2 : 1; + const int standardRate = AudioConstants::SAMPLE_RATE; + const int resampledRate = standardRate * stretchFactor; + const int nInputSamples = samples.size() / sizeof(int16_t); + const int nOutputSamples = nInputSamples * stretchFactor; + QByteArray resampled(nOutputSamples * sizeof(int16_t), '\0'); + const int16_t* receivedSamples = reinterpret_cast(samples.data()); + soxr_error_t soxError = soxr_oneshot(standardRate, resampledRate, channelCount, + receivedSamples, nInputSamples, NULL, + reinterpret_cast(resampled.data()), nOutputSamples, NULL, + &spec, &qualitySpec, 0); + if (soxError) { + qCDebug(audioclient) << "Unable to resample" << soundUrl << "from" << nInputSamples << "@" << standardRate << "to" << nOutputSamples << "@" << resampledRate; + resampled = samples; + } + return playSound(resampled, options); +} + +AudioInjector* AudioClient::playSound(const QByteArray& buffer, const AudioInjectorOptions options) { + QThread* injectorThread = new QThread(); + injectorThread->setObjectName("Audio Injector Thread"); + + AudioInjector* injector = new AudioInjector(buffer, options); + injector->setLocalAudioInterface(this); + + injector->moveToThread(injectorThread); + + // start injecting when the injector thread starts + connect(injectorThread, &QThread::started, injector, &AudioInjector::injectAudio); + + // connect the right slots and signals for AudioInjector and thread cleanup + connect(injector, &AudioInjector::destroyed, injectorThread, &QThread::quit); + connect(injectorThread, &QThread::finished, injectorThread, &QThread::deleteLater); + + injectorThread->start(); + return injector; +} diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index d2492e1064..93afb71fef 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -40,6 +40,7 @@ #include #include +#include "AudioInjector.h" #include "AudioIOStats.h" #include "AudioNoiseGate.h" @@ -130,6 +131,10 @@ public: static const float CALLBACK_ACCELERATOR_RATIO; + AudioInjector* playSound(const QByteArray& buffer, const AudioInjectorOptions options); + AudioInjector* playSound(const QString& soundUrl, const float volume, const float stretchFactor, const glm::vec3 position); + + public slots: void start(); void stop(); diff --git a/libraries/entities-renderer/CMakeLists.txt b/libraries/entities-renderer/CMakeLists.txt index 19cc8d8412..40ca1ccbc9 100644 --- a/libraries/entities-renderer/CMakeLists.txt +++ b/libraries/entities-renderer/CMakeLists.txt @@ -17,10 +17,4 @@ find_package(PolyVox REQUIRED) target_include_directories(${TARGET_NAME} SYSTEM PUBLIC ${POLYVOX_INCLUDE_DIRS}) target_link_libraries(${TARGET_NAME} ${POLYVOX_LIBRARIES}) -# for changing the pitch of collision sounds -add_dependency_external_projects(soxr) -find_package(Soxr REQUIRED) -target_link_libraries(${TARGET_NAME} ${SOXR_LIBRARIES}) -target_include_directories(${TARGET_NAME} SYSTEM PRIVATE ${SOXR_INCLUDE_DIRS}) - link_hifi_libraries(shared gpu script-engine render render-utils) diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index e1bd01547e..f2d015960c 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -27,10 +28,6 @@ #include #include #include -#include -#include -#include - #include "EntityTreeRenderer.h" @@ -57,7 +54,6 @@ EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterf _wantScripts(wantScripts), _entitiesScriptEngine(NULL), _sandboxScriptEngine(NULL), - _localAudioInterface(NULL), _lastMouseEventValid(false), _viewState(viewState), _scriptingServices(scriptingServices), @@ -1057,7 +1053,6 @@ void EntityTreeRenderer::checkAndCallUnload(const EntityItemID& entityID) { } } - void EntityTreeRenderer::playEntityCollisionSound(const QUuid& myNodeID, EntityTree* entityTree, const EntityItemID& id, const Collision& collision) { EntityItemPointer entity = entityTree->findEntityByEntityItemID(id); if (!entity) { @@ -1101,60 +1096,10 @@ void EntityTreeRenderer::playEntityCollisionSound(const QUuid& myNodeID, EntityT return; } - auto soundCache = DependencyManager::get(); - if (soundCache.isNull()) { - return; - } - SharedSoundPointer sound = soundCache.data()->getSound(QUrl(collisionSoundURL)); - if (sound.isNull() || !sound->isReady()) { - return; - } - - // This is a hack. Quiet sound aren't really heard at all, so we compress everything to the range [1-c, 1], if we play it all. - const float COLLISION_SOUND_COMPRESSION_RANGE = 1.0f; // This section could be removed when the value is 1, but let's see how it goes. - float volume = energyFactorOfFull; - volume = (volume * COLLISION_SOUND_COMPRESSION_RANGE) + (1.0f - COLLISION_SOUND_COMPRESSION_RANGE); - - // This is quite similar to AudioScriptingInterface::playSound() and should probably be refactored. - AudioInjectorOptions options; - options.stereo = sound->isStereo(); - options.position = position; - options.volume = volume; - // Shift the pitch down by ln(1 + (size / COLLISION_SIZE_FOR_STANDARD_PITCH)) / ln(2) const float COLLISION_SIZE_FOR_STANDARD_PITCH = 0.2f; - QByteArray samples = sound->getByteArray(); - soxr_io_spec_t spec = soxr_io_spec(SOXR_INT16_I, SOXR_INT16_I); - soxr_quality_spec_t qualitySpec = soxr_quality_spec(SOXR_MQ, 0); - const int channelCount = sound->isStereo() ? 2 : 1; - const float factor = log(1.0f + (entity->getMinimumAACube().getLargestDimension() / COLLISION_SIZE_FOR_STANDARD_PITCH)) / log(2); - const int standardRate = AudioConstants::SAMPLE_RATE; - const int resampledRate = standardRate * factor; - const int nInputSamples = samples.size() / sizeof(int16_t); - const int nOutputSamples = nInputSamples * factor; - QByteArray resampled(nOutputSamples * sizeof(int16_t), '\0'); - const int16_t* receivedSamples = reinterpret_cast(samples.data()); - soxr_error_t soxError = soxr_oneshot(standardRate, resampledRate, channelCount, - receivedSamples, nInputSamples, NULL, - reinterpret_cast(resampled.data()), nOutputSamples, NULL, - &spec, &qualitySpec, 0); - if (soxError) { - qCDebug(entitiesrenderer) << "Unable to resample" << collisionSoundURL << "from" << nInputSamples << "@" << standardRate << "to" << nOutputSamples << "@" << resampledRate; - resampled = samples; - } - - AudioInjector* injector = new AudioInjector(resampled, options); - injector->setLocalAudioInterface(_localAudioInterface); - injector->triggerDeleteAfterFinish(); - QThread* injectorThread = new QThread(); - injectorThread->setObjectName("Audio Injector Thread"); - injector->moveToThread(injectorThread); - // start injecting when the injector thread starts - connect(injectorThread, &QThread::started, injector, &AudioInjector::injectAudio); - // connect the right slots and signals for AudioInjector and thread cleanup - connect(injector, &AudioInjector::destroyed, injectorThread, &QThread::quit); - connect(injectorThread, &QThread::finished, injectorThread, &QThread::deleteLater); - injectorThread->start(); + const float stretchFactor = log(1.0f + (entity->getMinimumAACube().getLargestDimension() / COLLISION_SIZE_FOR_STANDARD_PITCH)) / log(2); + DependencyManager::get()->playSound(collisionSoundURL, energyFactorOfFull, stretchFactor, position); } void EntityTreeRenderer::entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index e491524c78..56a8208eb4 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -158,7 +158,6 @@ private: QHash _entityScripts; void playEntityCollisionSound(const QUuid& myNodeID, EntityTree* entityTree, const EntityItemID& id, const Collision& collision); - AbstractAudioInterface* _localAudioInterface; // So we can render collision sounds bool _lastMouseEventValid; MouseEvent _lastMouseEvent; diff --git a/libraries/script-engine/CMakeLists.txt b/libraries/script-engine/CMakeLists.txt index 99d9149c3a..78db92b4f1 100644 --- a/libraries/script-engine/CMakeLists.txt +++ b/libraries/script-engine/CMakeLists.txt @@ -7,4 +7,4 @@ add_dependency_external_projects(glm) find_package(GLM REQUIRED) target_include_directories(${TARGET_NAME} PUBLIC ${GLM_INCLUDE_DIRS}) -link_hifi_libraries(shared octree gpu model fbx entities animation audio physics) +link_hifi_libraries(shared octree gpu model fbx entities animation audio audio-client physics) diff --git a/libraries/script-engine/src/AudioScriptingInterface.cpp b/libraries/script-engine/src/AudioScriptingInterface.cpp index 9e3e924933..0b55ed1bca 100644 --- a/libraries/script-engine/src/AudioScriptingInterface.cpp +++ b/libraries/script-engine/src/AudioScriptingInterface.cpp @@ -11,6 +11,7 @@ #include "AudioScriptingInterface.h" +#include "AudioClient.h" #include "ScriptAudioInjector.h" #include "ScriptEngineLogging.h" @@ -46,24 +47,7 @@ ScriptAudioInjector* AudioScriptingInterface::playSound(Sound* sound, const Audi AudioInjectorOptions optionsCopy = injectorOptions; optionsCopy.stereo = sound->isStereo(); - QThread* injectorThread = new QThread(); - injectorThread->setObjectName("Audio Injector Thread"); - - AudioInjector* injector = new AudioInjector(sound, optionsCopy); - injector->setLocalAudioInterface(_localAudioInterface); - - injector->moveToThread(injectorThread); - - // start injecting when the injector thread starts - connect(injectorThread, &QThread::started, injector, &AudioInjector::injectAudio); - - // connect the right slots and signals for AudioInjector and thread cleanup - connect(injector, &AudioInjector::destroyed, injectorThread, &QThread::quit); - connect(injectorThread, &QThread::finished, injectorThread, &QThread::deleteLater); - - injectorThread->start(); - - return new ScriptAudioInjector(injector); + return new ScriptAudioInjector(DependencyManager::get()->playSound(sound->getByteArray(), optionsCopy)); } else { qCDebug(scriptengine) << "AudioScriptingInterface::playSound called with null Sound object.";