diff --git a/interface/src/avatar/Hand.cpp b/interface/src/avatar/Hand.cpp index 5f712d947b..6b776f5cfa 100644 --- a/interface/src/avatar/Hand.cpp +++ b/interface/src/avatar/Hand.cpp @@ -54,8 +54,8 @@ Hand::Hand(Avatar* owningAvatar) : _grabDeltaVelocity(0, 0, 0), _grabStartRotation(0, 0, 0, 1), _grabCurrentRotation(0, 0, 0, 1), - _throwInjector(QUrl("https://dl.dropboxusercontent.com/u/1864924/hifi-sounds/throw.raw")), - _catchInjector(QUrl("https://dl.dropboxusercontent.com/u/1864924/hifi-sounds/catch.raw")) + _throwSound(QUrl("https://dl.dropboxusercontent.com/u/1864924/hifi-sounds/throw.raw")), + _catchSound(QUrl("https://dl.dropboxusercontent.com/u/1864924/hifi-sounds/catch.raw")) { for (int i = 0; i < MAX_HANDS; i++) { _toyBallInHand[i] = false; @@ -63,10 +63,6 @@ Hand::Hand(Avatar* owningAvatar) : _whichBallColor[i] = 0; } _lastControllerButtons = 0; - - // the throw and catch sounds should not loopback, we'll play them locally - _throwInjector.setShouldLoopback(false); - _catchInjector.setShouldLoopback(false); } void Hand::init() { @@ -128,11 +124,14 @@ void Hand::simulateToyBall(PalmData& palm, const glm::vec3& fingerTipPosition, f _ballParticleEditHandles[handID] = caughtParticle; caughtParticle = NULL; - // set the position of the catch sound to the new position of the ball - _catchInjector.setPosition(targetPosition); + // use the threadSound static method to inject the catch sound + // pass an AudioInjectorOptions struct to set position and disable loopback + AudioInjectorOptions injectorOptions; + injectorOptions.position = targetPosition; + injectorOptions.shouldLoopback = false; + injectorOptions.loopbackAudioInterface = app->getAudio(); - // inject the catch sound to the mixer and play it locally - _catchInjector.injectViaThread(app->getAudio()); + AudioInjector::threadSound(&_catchSound, injectorOptions); } } @@ -214,11 +213,14 @@ void Hand::simulateToyBall(PalmData& palm, const glm::vec3& fingerTipPosition, f delete _ballParticleEditHandles[handID]; _ballParticleEditHandles[handID] = NULL; - // move the throw injector to inject from the position of the ball - _throwInjector.setPosition(ballPosition); + // use the threadSound static method to inject the throw sound + // pass an AudioInjectorOptions struct to set position and disable loopback + AudioInjectorOptions injectorOptions; + injectorOptions.position = targetPosition; + injectorOptions.shouldLoopback = false; + injectorOptions.loopbackAudioInterface = app->getAudio(); - // inject the throw sound and play it locally - _throwInjector.injectViaThread(app->getAudio()); + AudioInjector::threadSound(&_throwSound, injectorOptions); } } diff --git a/interface/src/avatar/Hand.h b/interface/src/avatar/Hand.h index e938135a29..88ca4a61e7 100755 --- a/interface/src/avatar/Hand.h +++ b/interface/src/avatar/Hand.h @@ -114,8 +114,8 @@ private: glm::quat _grabStartRotation; glm::quat _grabCurrentRotation; - AudioInjector _throwInjector; - AudioInjector _catchInjector; + Sound _throwSound; + Sound _catchSound; }; #endif diff --git a/libraries/audio/src/AudioInjector.cpp b/libraries/audio/src/AudioInjector.cpp index 00c1fa2b21..5ad4dc6f4d 100644 --- a/libraries/audio/src/AudioInjector.cpp +++ b/libraries/audio/src/AudioInjector.cpp @@ -2,16 +2,10 @@ // AudioInjector.cpp // hifi // -// Created by Stephen Birarda on 12/19/2013. +// Created by Stephen Birarda on 1/2/2014. // Copyright (c) 2013 HighFidelity, Inc. All rights reserved. // -#include - -#include -#include -#include - #include #include #include @@ -24,59 +18,54 @@ int abstractAudioPointerMeta = qRegisterMetaType("AbstractAudioInterface*"); -AudioInjector::AudioInjector(const QUrl& sampleURL) : - _currentSendPosition(0), - _sourceURL(sampleURL), - _position(0,0,0), - _orientation(), - _volume(1.0f), - _shouldLoopback(true) +AudioInjector::AudioInjector(Sound* sound, AudioInjectorOptions injectorOptions) : + _thread(NULL), + _sound(sound), + _volume(injectorOptions.volume), + _shouldLoopback(injectorOptions.shouldLoopback), + _position(injectorOptions.position), + _orientation(injectorOptions.orientation), + _loopbackAudioInterface(injectorOptions.loopbackAudioInterface) { + _thread = new QThread(); + // we want to live on our own thread - moveToThread(&_thread); - connect(&_thread, SIGNAL(started()), this, SLOT(startDownload())); - _thread.start(); + moveToThread(_thread); } -void AudioInjector::startDownload() { - // assume we have a QApplication or QCoreApplication instance and use the - // QNetworkAccess manager to grab the raw audio file at the given URL +void AudioInjector::threadSound(Sound* sound, AudioInjectorOptions injectorOptions) { + AudioInjector* injector = new AudioInjector(sound, injectorOptions); - QNetworkAccessManager *manager = new QNetworkAccessManager(this); - connect(manager, SIGNAL(finished(QNetworkReply*)), - this, SLOT(replyFinished(QNetworkReply*))); + // start injecting when the injector thread starts + connect(injector->_thread, SIGNAL(started()), injector, SLOT(injectAudio())); - manager->get(QNetworkRequest(_sourceURL)); + // connect the right slots and signals so that the AudioInjector is killed once the injection is complete + connect(injector, SIGNAL(finished()), injector, SLOT(deleteLater())); + connect(injector, SIGNAL(finished()), injector->_thread, SLOT(quit())); + connect(injector->_thread, SIGNAL(finished()), injector->_thread, SLOT(deleteLater())); + + injector->_thread->start(); } -void AudioInjector::replyFinished(QNetworkReply* reply) { - // replace our samples array with the downloaded data - _sampleByteArray = reply->readAll(); -} +const uchar MAX_INJECTOR_VOLUME = 0xFF; -void AudioInjector::injectViaThread(AbstractAudioInterface* localAudioInterface) { - // use Qt::AutoConnection so that this is called on our thread, if appropriate - QMetaObject::invokeMethod(this, "injectAudio", Qt::AutoConnection, Q_ARG(AbstractAudioInterface*, localAudioInterface)); -} - -void AudioInjector::injectAudio(AbstractAudioInterface* localAudioInterface) { +void AudioInjector::injectAudio() { + + QByteArray soundByteArray = _sound->getByteArray(); // make sure we actually have samples downloaded to inject - if (_sampleByteArray.size()) { + if (soundByteArray.size()) { // give our sample byte array to the local audio interface, if we have it, so it can be handled locally - if (localAudioInterface) { + if (_loopbackAudioInterface) { // assume that localAudioInterface could be on a separate thread, use Qt::AutoConnection to handle properly - QMetaObject::invokeMethod(localAudioInterface, "handleAudioByteArray", + QMetaObject::invokeMethod(_loopbackAudioInterface, "handleAudioByteArray", Qt::AutoConnection, - Q_ARG(QByteArray, _sampleByteArray)); + Q_ARG(QByteArray, soundByteArray)); } NodeList* nodeList = NodeList::getInstance(); - // reset the current send position to the beginning - _currentSendPosition = 0; - // setup the packet for injected audio unsigned char injectedAudioPacket[MAX_PACKET_SIZE]; unsigned char* currentPacketPosition = injectedAudioPacket; @@ -121,14 +110,16 @@ void AudioInjector::injectAudio(AbstractAudioInterface* localAudioInterface) { gettimeofday(&startTime, NULL); int nextFrame = 0; + int currentSendPosition = 0; + // loop to send off our audio in NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL byte chunks - while (_currentSendPosition < _sampleByteArray.size()) { + while (currentSendPosition < soundByteArray.size()) { int bytesToCopy = std::min(NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL, - _sampleByteArray.size() - _currentSendPosition); + soundByteArray.size() - currentSendPosition); // copy the next NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL bytes to the packet - memcpy(currentPacketPosition, _sampleByteArray.data() + _currentSendPosition, + memcpy(currentPacketPosition, soundByteArray.data() + currentSendPosition, bytesToCopy); @@ -143,11 +134,11 @@ void AudioInjector::injectAudio(AbstractAudioInterface* localAudioInterface) { audioMixer->getActiveSocket()->getPort()); } - _currentSendPosition += bytesToCopy; + currentSendPosition += bytesToCopy; // send two packets before the first sleep so the mixer can start playback right away - if (_currentSendPosition != bytesToCopy && _currentSendPosition < _sampleByteArray.size()) { + if (currentSendPosition != bytesToCopy && currentSendPosition < soundByteArray.size()) { // not the first packet and not done // sleep for the appropriate time int usecToSleep = usecTimestamp(&startTime) + (++nextFrame * BUFFER_SEND_INTERVAL_USECS) - usecTimestampNow(); @@ -157,6 +148,7 @@ void AudioInjector::injectAudio(AbstractAudioInterface* localAudioInterface) { } } } - } + + emit finished(); } \ No newline at end of file diff --git a/libraries/audio/src/AudioInjector.h b/libraries/audio/src/AudioInjector.h index 425ae7477f..f2e7b3b498 100644 --- a/libraries/audio/src/AudioInjector.h +++ b/libraries/audio/src/AudioInjector.h @@ -2,7 +2,7 @@ // AudioInjector.h // hifi // -// Created by Stephen Birarda on 12/19/2013. +// Created by Stephen Birarda on 1/2/2014. // Copyright (c) 2013 HighFidelity, Inc. All rights reserved. // @@ -11,44 +11,46 @@ #include #include -#include #include #include -class AbstractAudioInterface; -class QNetworkReply; +#include "Sound.h" -const uchar MAX_INJECTOR_VOLUME = 0xFF; +class AbstractAudioInterface; + +struct AudioInjectorOptions { + AudioInjectorOptions() : position(glm::vec3(0.0f, 0.0f, 0.0f)), + volume(1.0f), + orientation(glm::quat()), + shouldLoopback(true), + loopbackAudioInterface(NULL) {}; + + glm::vec3 position; + float volume; + const glm::quat orientation; + bool shouldLoopback; + AbstractAudioInterface* loopbackAudioInterface; +}; class AudioInjector : public QObject { Q_OBJECT public: - AudioInjector(const QUrl& sampleURL); - - int size() const { return _sampleByteArray.size(); } - - void setPosition(const glm::vec3& position) { _position = position; } - void setOrientation(const glm::quat& orientation) { _orientation = orientation; } - void setVolume(float volume) { _volume = std::max(fabsf(volume), 1.0f); } - void setShouldLoopback(bool shouldLoopback) { _shouldLoopback = shouldLoopback; } -public slots: - void injectViaThread(AbstractAudioInterface* localAudioInterface = NULL); - + static void threadSound(Sound* sound, AudioInjectorOptions injectorOptions = AudioInjectorOptions()); private: - QByteArray _sampleByteArray; - int _currentSendPosition; - QThread _thread; - QUrl _sourceURL; - glm::vec3 _position; - glm::quat _orientation; + AudioInjector(Sound* sound, AudioInjectorOptions injectorOptions); + + QThread* _thread; + Sound* _sound; float _volume; uchar _shouldLoopback; - + glm::vec3 _position; + glm::quat _orientation; + AbstractAudioInterface* _loopbackAudioInterface; private slots: - void startDownload(); - void replyFinished(QNetworkReply* reply); - void injectAudio(AbstractAudioInterface* localAudioInterface); + void injectAudio(); +signals: + void finished(); }; #endif /* defined(__hifi__AudioInjector__) */ diff --git a/libraries/audio/src/Sound.cpp b/libraries/audio/src/Sound.cpp new file mode 100644 index 0000000000..0362561fff --- /dev/null +++ b/libraries/audio/src/Sound.cpp @@ -0,0 +1,29 @@ +// +// Sound.cpp +// hifi +// +// Created by Stephen Birarda on 1/2/2014. +// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// + +#include +#include +#include + +#include "Sound.h" + +Sound::Sound(const QUrl& sampleURL) { + // assume we have a QApplication or QCoreApplication instance and use the + // QNetworkAccess manager to grab the raw audio file at the given URL + + QNetworkAccessManager *manager = new QNetworkAccessManager(this); + connect(manager, SIGNAL(finished(QNetworkReply*)), + this, SLOT(replyFinished(QNetworkReply*))); + + manager->get(QNetworkRequest(sampleURL)); +} + +void Sound::replyFinished(QNetworkReply* reply) { + // replace our byte array with the downloaded data + _byteArray = reply->readAll(); +} \ No newline at end of file diff --git a/libraries/audio/src/Sound.h b/libraries/audio/src/Sound.h new file mode 100644 index 0000000000..e9a6e856ba --- /dev/null +++ b/libraries/audio/src/Sound.h @@ -0,0 +1,28 @@ +// +// Sound.h +// hifi +// +// Created by Stephen Birarda on 1/2/2014. +// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// + +#ifndef __hifi__Sound__ +#define __hifi__Sound__ + +#include + +class QNetworkReply; + +class Sound : public QObject { + Q_OBJECT +public: + Sound(const QUrl& sampleURL); + + const QByteArray& getByteArray() { return _byteArray; } +private: + QByteArray _byteArray; +private slots: + void replyFinished(QNetworkReply* reply); +}; + +#endif /* defined(__hifi__Sound__) */