mirror of
https://github.com/overte-org/overte.git
synced 2025-04-26 00:56:17 +02:00
Refactor playSound.
This commit is contained in:
parent
db56e15410
commit
fdf5860c4f
7 changed files with 80 additions and 84 deletions
|
@ -45,6 +45,7 @@ extern "C" {
|
||||||
#include <PositionalAudioStream.h>
|
#include <PositionalAudioStream.h>
|
||||||
#include <SettingHandle.h>
|
#include <SettingHandle.h>
|
||||||
#include <SharedUtil.h>
|
#include <SharedUtil.h>
|
||||||
|
#include <SoundCache.h>
|
||||||
#include <UUID.h>
|
#include <UUID.h>
|
||||||
|
|
||||||
#include "AudioInjector.h"
|
#include "AudioInjector.h"
|
||||||
|
@ -1338,3 +1339,71 @@ void AudioClient::audioStateChanged(QAudio::State state) {
|
||||||
emit audioFinished();
|
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<SoundCache>();
|
||||||
|
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<const int16_t*>(samples.data());
|
||||||
|
soxr_error_t soxError = soxr_oneshot(standardRate, resampledRate, channelCount,
|
||||||
|
receivedSamples, nInputSamples, NULL,
|
||||||
|
reinterpret_cast<int16_t*>(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;
|
||||||
|
}
|
||||||
|
|
|
@ -40,6 +40,7 @@
|
||||||
#include <Sound.h>
|
#include <Sound.h>
|
||||||
#include <StDev.h>
|
#include <StDev.h>
|
||||||
|
|
||||||
|
#include "AudioInjector.h"
|
||||||
#include "AudioIOStats.h"
|
#include "AudioIOStats.h"
|
||||||
#include "AudioNoiseGate.h"
|
#include "AudioNoiseGate.h"
|
||||||
|
|
||||||
|
@ -130,6 +131,10 @@ public:
|
||||||
|
|
||||||
static const float CALLBACK_ACCELERATOR_RATIO;
|
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:
|
public slots:
|
||||||
void start();
|
void start();
|
||||||
void stop();
|
void stop();
|
||||||
|
|
|
@ -17,10 +17,4 @@ find_package(PolyVox REQUIRED)
|
||||||
target_include_directories(${TARGET_NAME} SYSTEM PUBLIC ${POLYVOX_INCLUDE_DIRS})
|
target_include_directories(${TARGET_NAME} SYSTEM PUBLIC ${POLYVOX_INCLUDE_DIRS})
|
||||||
target_link_libraries(${TARGET_NAME} ${POLYVOX_LIBRARIES})
|
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)
|
link_hifi_libraries(shared gpu script-engine render render-utils)
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
|
|
||||||
#include <AbstractScriptingServicesInterface.h>
|
#include <AbstractScriptingServicesInterface.h>
|
||||||
#include <AbstractViewStateInterface.h>
|
#include <AbstractViewStateInterface.h>
|
||||||
|
#include <AudioClient.h>
|
||||||
#include <DeferredLightingEffect.h>
|
#include <DeferredLightingEffect.h>
|
||||||
#include <GlowEffect.h>
|
#include <GlowEffect.h>
|
||||||
#include <Model.h>
|
#include <Model.h>
|
||||||
|
@ -27,10 +28,6 @@
|
||||||
#include <SceneScriptingInterface.h>
|
#include <SceneScriptingInterface.h>
|
||||||
#include <ScriptEngine.h>
|
#include <ScriptEngine.h>
|
||||||
#include <TextureCache.h>
|
#include <TextureCache.h>
|
||||||
#include <SoundCache.h>
|
|
||||||
#include <soxr.h>
|
|
||||||
#include <AudioConstants.h>
|
|
||||||
|
|
||||||
|
|
||||||
#include "EntityTreeRenderer.h"
|
#include "EntityTreeRenderer.h"
|
||||||
|
|
||||||
|
@ -57,7 +54,6 @@ EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterf
|
||||||
_wantScripts(wantScripts),
|
_wantScripts(wantScripts),
|
||||||
_entitiesScriptEngine(NULL),
|
_entitiesScriptEngine(NULL),
|
||||||
_sandboxScriptEngine(NULL),
|
_sandboxScriptEngine(NULL),
|
||||||
_localAudioInterface(NULL),
|
|
||||||
_lastMouseEventValid(false),
|
_lastMouseEventValid(false),
|
||||||
_viewState(viewState),
|
_viewState(viewState),
|
||||||
_scriptingServices(scriptingServices),
|
_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) {
|
void EntityTreeRenderer::playEntityCollisionSound(const QUuid& myNodeID, EntityTree* entityTree, const EntityItemID& id, const Collision& collision) {
|
||||||
EntityItemPointer entity = entityTree->findEntityByEntityItemID(id);
|
EntityItemPointer entity = entityTree->findEntityByEntityItemID(id);
|
||||||
if (!entity) {
|
if (!entity) {
|
||||||
|
@ -1101,60 +1096,10 @@ void EntityTreeRenderer::playEntityCollisionSound(const QUuid& myNodeID, EntityT
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto soundCache = DependencyManager::get<SoundCache>();
|
|
||||||
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)
|
// Shift the pitch down by ln(1 + (size / COLLISION_SIZE_FOR_STANDARD_PITCH)) / ln(2)
|
||||||
const float COLLISION_SIZE_FOR_STANDARD_PITCH = 0.2f;
|
const float COLLISION_SIZE_FOR_STANDARD_PITCH = 0.2f;
|
||||||
QByteArray samples = sound->getByteArray();
|
const float stretchFactor = log(1.0f + (entity->getMinimumAACube().getLargestDimension() / COLLISION_SIZE_FOR_STANDARD_PITCH)) / log(2);
|
||||||
soxr_io_spec_t spec = soxr_io_spec(SOXR_INT16_I, SOXR_INT16_I);
|
DependencyManager::get<AudioClient>()->playSound(collisionSoundURL, energyFactorOfFull, stretchFactor, position);
|
||||||
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<const int16_t*>(samples.data());
|
|
||||||
soxr_error_t soxError = soxr_oneshot(standardRate, resampledRate, channelCount,
|
|
||||||
receivedSamples, nInputSamples, NULL,
|
|
||||||
reinterpret_cast<int16_t*>(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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntityTreeRenderer::entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB,
|
void EntityTreeRenderer::entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB,
|
||||||
|
|
|
@ -158,7 +158,6 @@ private:
|
||||||
QHash<EntityItemID, EntityScriptDetails> _entityScripts;
|
QHash<EntityItemID, EntityScriptDetails> _entityScripts;
|
||||||
|
|
||||||
void playEntityCollisionSound(const QUuid& myNodeID, EntityTree* entityTree, const EntityItemID& id, const Collision& collision);
|
void playEntityCollisionSound(const QUuid& myNodeID, EntityTree* entityTree, const EntityItemID& id, const Collision& collision);
|
||||||
AbstractAudioInterface* _localAudioInterface; // So we can render collision sounds
|
|
||||||
|
|
||||||
bool _lastMouseEventValid;
|
bool _lastMouseEventValid;
|
||||||
MouseEvent _lastMouseEvent;
|
MouseEvent _lastMouseEvent;
|
||||||
|
|
|
@ -7,4 +7,4 @@ add_dependency_external_projects(glm)
|
||||||
find_package(GLM REQUIRED)
|
find_package(GLM REQUIRED)
|
||||||
target_include_directories(${TARGET_NAME} PUBLIC ${GLM_INCLUDE_DIRS})
|
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)
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
|
|
||||||
#include "AudioScriptingInterface.h"
|
#include "AudioScriptingInterface.h"
|
||||||
|
|
||||||
|
#include "AudioClient.h"
|
||||||
#include "ScriptAudioInjector.h"
|
#include "ScriptAudioInjector.h"
|
||||||
#include "ScriptEngineLogging.h"
|
#include "ScriptEngineLogging.h"
|
||||||
|
|
||||||
|
@ -46,24 +47,7 @@ ScriptAudioInjector* AudioScriptingInterface::playSound(Sound* sound, const Audi
|
||||||
AudioInjectorOptions optionsCopy = injectorOptions;
|
AudioInjectorOptions optionsCopy = injectorOptions;
|
||||||
optionsCopy.stereo = sound->isStereo();
|
optionsCopy.stereo = sound->isStereo();
|
||||||
|
|
||||||
QThread* injectorThread = new QThread();
|
return new ScriptAudioInjector(DependencyManager::get<AudioClient>()->playSound(sound->getByteArray(), optionsCopy));
|
||||||
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);
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
qCDebug(scriptengine) << "AudioScriptingInterface::playSound called with null Sound object.";
|
qCDebug(scriptengine) << "AudioScriptingInterface::playSound called with null Sound object.";
|
||||||
|
|
Loading…
Reference in a new issue