734 lines · 33.5 KB · C++
RawDownload
////  AudioReflector.cpp//  interface////  Created by Brad Hefta-Gaub on 4/2/2014//  Copyright (c) 2014 High Fidelity, Inc. All rights reserved.// #include <QMutexLocker> #include "AudioReflector.h"#include "Menu.h" const float DEFAULT_PRE_DELAY = 20.0f; // this delay in msecs will always be added to all reflectionsconst float DEFAULT_MS_DELAY_PER_METER = 3.0f;const float MINIMUM_ATTENUATION_TO_REFLECT = 1.0f / 256.0f;const float DEFAULT_DISTANCE_SCALING_FACTOR = 2.0f; const float MAXIMUM_DELAY_MS = 1000.0 * 20.0f; // stop reflecting after path is this longconst int DEFAULT_DIFFUSION_FANOUT = 5;const int ABSOLUTE_MAXIMUM_BOUNCE_COUNT = 10;const float DEFAULT_LOCAL_ATTENUATION_FACTOR = 0.125; const float SLIGHTLY_SHORT = 0.999f; // slightly inside the distance so we're on the inside of the reflection point const float DEFAULT_ABSORPTION_RATIO = 0.125; // 12.5% is absorbedconst float DEFAULT_DIFFUSION_RATIO = 0.125; // 12.5% is diffused AudioReflector::AudioReflector(QObject* parent) :     QObject(parent),    _preDelay(DEFAULT_PRE_DELAY),    _soundMsPerMeter(DEFAULT_MS_DELAY_PER_METER),    _distanceAttenuationScalingFactor(DEFAULT_DISTANCE_SCALING_FACTOR),    _localAudioAttenuationFactor(DEFAULT_LOCAL_ATTENUATION_FACTOR),    _diffusionFanout(DEFAULT_DIFFUSION_FANOUT),    _absorptionRatio(DEFAULT_ABSORPTION_RATIO),    _diffusionRatio(DEFAULT_DIFFUSION_RATIO),    _withDiffusion(false),    _lastPreDelay(DEFAULT_PRE_DELAY),    _lastSoundMsPerMeter(DEFAULT_MS_DELAY_PER_METER),    _lastDistanceAttenuationScalingFactor(DEFAULT_DISTANCE_SCALING_FACTOR),    _lastLocalAudioAttenuationFactor(DEFAULT_LOCAL_ATTENUATION_FACTOR),    _lastDiffusionFanout(DEFAULT_DIFFUSION_FANOUT),    _lastAbsorptionRatio(DEFAULT_ABSORPTION_RATIO),    _lastDiffusionRatio(DEFAULT_DIFFUSION_RATIO){    _reflections = 0;    _diffusionPathCount = 0;    _averageAttenuation = 0.0f;    _maxAttenuation = 0.0f;    _minAttenuation = 0.0f;    _averageDelay = 0;    _maxDelay = 0;    _minDelay = 0;} bool AudioReflector::haveAttributesChanged() {    bool withDiffusion = Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingWithDiffusions);        bool attributesChange = (_withDiffusion != withDiffusion        || _lastPreDelay != _preDelay        || _lastSoundMsPerMeter != _soundMsPerMeter        || _lastDistanceAttenuationScalingFactor != _distanceAttenuationScalingFactor        || _lastDiffusionFanout != _diffusionFanout        || _lastAbsorptionRatio != _absorptionRatio        || _lastDiffusionRatio != _diffusionRatio);     if (attributesChange) {        _withDiffusion = withDiffusion;        _lastPreDelay = _preDelay;        _lastSoundMsPerMeter = _soundMsPerMeter;        _lastDistanceAttenuationScalingFactor = _distanceAttenuationScalingFactor;        _lastDiffusionFanout = _diffusionFanout;        _lastAbsorptionRatio = _absorptionRatio;        _lastDiffusionRatio = _diffusionRatio;    }        return attributesChange;} void AudioReflector::render() {     // if we're not set up yet, or we're not processing spatial audio, then exit early    if (!_myAvatar || !_audio->getProcessSpatialAudio()) {        return;    }     // use this oportunity to calculate our reflections    calculateAllReflections();        // only render if we've been asked to do so    if (Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingRenderPaths)) {        drawRays();    }} // delay = 1ms per foot//       = 3ms per meterfloat AudioReflector::getDelayFromDistance(float distance) {    float delay = (_soundMsPerMeter * distance);    if (Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingPreDelay)) {        delay += _preDelay;    }    return delay;} // attenuation = from the Audio Mixerfloat AudioReflector::getDistanceAttenuationCoefficient(float distance) {    const float DISTANCE_SCALE = 2.5f;    const float GEOMETRIC_AMPLITUDE_SCALAR = 0.3f;    const float DISTANCE_LOG_BASE = 2.5f;    const float DISTANCE_SCALE_LOG = logf(DISTANCE_SCALE) / logf(DISTANCE_LOG_BASE);        float distanceSquareToSource = distance * distance;     // calculate the distance coefficient using the distance to this node    float distanceCoefficient = powf(GEOMETRIC_AMPLITUDE_SCALAR,                                     DISTANCE_SCALE_LOG +                                     (0.5f * logf(distanceSquareToSource) / logf(DISTANCE_LOG_BASE)) - 1);     distanceCoefficient = std::min(1.0f, distanceCoefficient * getDistanceAttenuationScalingFactor());        return distanceCoefficient;} glm::vec3 AudioReflector::getFaceNormal(BoxFace face) {    bool wantSlightRandomness = Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingSlightlyRandomSurfaces);    glm::vec3 faceNormal;    const float MIN_RANDOM_LENGTH = 0.99f;    const float MAX_RANDOM_LENGTH = 1.0f;    const float NON_RANDOM_LENGTH = 1.0f;    float normalLength = wantSlightRandomness ? randFloatInRange(MIN_RANDOM_LENGTH, MAX_RANDOM_LENGTH) : NON_RANDOM_LENGTH;    float remainder = (1.0f - normalLength)/2.0f;    float remainderSignA = randomSign();    float remainderSignB = randomSign();     if (face == MIN_X_FACE) {        faceNormal = glm::vec3(-normalLength, remainder * remainderSignA, remainder * remainderSignB);    } else if (face == MAX_X_FACE) {        faceNormal = glm::vec3(normalLength, remainder * remainderSignA, remainder * remainderSignB);    } else if (face == MIN_Y_FACE) {        faceNormal = glm::vec3(remainder * remainderSignA, -normalLength, remainder * remainderSignB);    } else if (face == MAX_Y_FACE) {        faceNormal = glm::vec3(remainder * remainderSignA, normalLength, remainder * remainderSignB);    } else if (face == MIN_Z_FACE) {        faceNormal = glm::vec3(remainder * remainderSignA, remainder * remainderSignB, -normalLength);    } else if (face == MAX_Z_FACE) {        faceNormal = glm::vec3(remainder * remainderSignA, remainder * remainderSignB, normalLength);    }    return faceNormal;} // set up our buffers for our attenuated and delayed samplesconst int NUMBER_OF_CHANNELS = 2; void AudioReflector::injectAudiblePoint(const AudiblePoint& audiblePoint,                        const QByteArray& samples, unsigned int sampleTime, int sampleRate) {     bool wantEarSeparation = Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingSeparateEars);    bool wantStereo = Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingStereoSource);    glm::vec3 rightEarPosition = wantEarSeparation ? _myAvatar->getHead()->getRightEarPosition() :                                     _myAvatar->getHead()->getPosition();    glm::vec3 leftEarPosition = wantEarSeparation ? _myAvatar->getHead()->getLeftEarPosition() :                                    _myAvatar->getHead()->getPosition();     int totalNumberOfSamples = samples.size() / sizeof(int16_t);    int totalNumberOfStereoSamples = samples.size() / (sizeof(int16_t) * NUMBER_OF_CHANNELS);     const int16_t* originalSamplesData = (const int16_t*)samples.constData();    QByteArray attenuatedLeftSamples;    QByteArray attenuatedRightSamples;    attenuatedLeftSamples.resize(samples.size());    attenuatedRightSamples.resize(samples.size());     int16_t* attenuatedLeftSamplesData = (int16_t*)attenuatedLeftSamples.data();    int16_t* attenuatedRightSamplesData = (int16_t*)attenuatedRightSamples.data();        // calculate the distance to the ears    float rightEarDistance = glm::distance(audiblePoint.location, rightEarPosition);    float leftEarDistance = glm::distance(audiblePoint.location, leftEarPosition);     float rightEarDelayMsecs = getDelayFromDistance(rightEarDistance) + audiblePoint.delay;    float leftEarDelayMsecs = getDelayFromDistance(leftEarDistance) + audiblePoint.delay;     _totalDelay += rightEarDelayMsecs + leftEarDelayMsecs;    _delayCount += 2;    _maxDelay = std::max(_maxDelay,rightEarDelayMsecs);    _maxDelay = std::max(_maxDelay,leftEarDelayMsecs);    _minDelay = std::min(_minDelay,rightEarDelayMsecs);    _minDelay = std::min(_minDelay,leftEarDelayMsecs);            int rightEarDelay = rightEarDelayMsecs * sampleRate / MSECS_PER_SECOND;    int leftEarDelay = leftEarDelayMsecs * sampleRate / MSECS_PER_SECOND;     float rightEarAttenuation = audiblePoint.attenuation *                                     getDistanceAttenuationCoefficient(rightEarDistance + audiblePoint.distance);     float leftEarAttenuation = audiblePoint.attenuation *                                     getDistanceAttenuationCoefficient(leftEarDistance + audiblePoint.distance);     _totalAttenuation += rightEarAttenuation + leftEarAttenuation;    _attenuationCount += 2;    _maxAttenuation = std::max(_maxAttenuation,rightEarAttenuation);    _maxAttenuation = std::max(_maxAttenuation,leftEarAttenuation);    _minAttenuation = std::min(_minAttenuation,rightEarAttenuation);    _minAttenuation = std::min(_minAttenuation,leftEarAttenuation);        // run through the samples, and attenuate them                                                                for (int sample = 0; sample < totalNumberOfStereoSamples; sample++) {        int16_t leftSample = originalSamplesData[sample * NUMBER_OF_CHANNELS];        int16_t rightSample = leftSample;        if (wantStereo) {            rightSample = originalSamplesData[(sample * NUMBER_OF_CHANNELS) + 1];        }         attenuatedLeftSamplesData[sample * NUMBER_OF_CHANNELS] = leftSample * leftEarAttenuation;        attenuatedLeftSamplesData[sample * NUMBER_OF_CHANNELS + 1] = 0;         attenuatedRightSamplesData[sample * NUMBER_OF_CHANNELS] = 0;        attenuatedRightSamplesData[sample * NUMBER_OF_CHANNELS + 1] = rightSample * rightEarAttenuation;    }            // now inject the attenuated array with the appropriate delay    unsigned int sampleTimeLeft = sampleTime + leftEarDelay;    unsigned int sampleTimeRight = sampleTime + rightEarDelay;        _audio->addSpatialAudioToBuffer(sampleTimeLeft, attenuatedLeftSamples, totalNumberOfSamples);    _audio->addSpatialAudioToBuffer(sampleTimeRight, attenuatedRightSamples, totalNumberOfSamples);} void AudioReflector::processLocalAudio(unsigned int sampleTime, const QByteArray& samples, const QAudioFormat& format) {    if (Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingProcessLocalAudio)) {        const int NUM_CHANNELS_INPUT = 1;        const int NUM_CHANNELS_OUTPUT = 2;        const int EXPECTED_SAMPLE_RATE = 24000;        if (format.channelCount() == NUM_CHANNELS_INPUT && format.sampleRate() == EXPECTED_SAMPLE_RATE) {            QAudioFormat outputFormat = format;            outputFormat.setChannelCount(NUM_CHANNELS_OUTPUT);            QByteArray stereoInputData(samples.size() * NUM_CHANNELS_OUTPUT, 0);            int numberOfSamples = (samples.size() / sizeof(int16_t));            int16_t* monoSamples = (int16_t*)samples.data();            int16_t* stereoSamples = (int16_t*)stereoInputData.data();                        for (int i = 0; i < numberOfSamples; i++) {                stereoSamples[i* NUM_CHANNELS_OUTPUT] = monoSamples[i] * _localAudioAttenuationFactor;                stereoSamples[(i * NUM_CHANNELS_OUTPUT) + 1] = monoSamples[i] * _localAudioAttenuationFactor;            }            echoAudio(LOCAL_AUDIO, sampleTime, stereoInputData, outputFormat);        }    }} void AudioReflector::processInboundAudio(unsigned int sampleTime, const QByteArray& samples, const QAudioFormat& format) {    echoAudio(INBOUND_AUDIO, sampleTime, samples, format);} void AudioReflector::echoAudio(AudioSource source, unsigned int sampleTime, const QByteArray& samples, const QAudioFormat& format) {    _maxDelay = 0;    _maxAttenuation = 0.0f;    _minDelay = std::numeric_limits<int>::max();    _minAttenuation = std::numeric_limits<float>::max();    _totalDelay = 0.0f;    _delayCount = 0;    _totalAttenuation = 0.0f;    _attenuationCount = 0;     QMutexLocker locker(&_mutex);     // depending on if we're processing local or external audio, pick the correct points vector    QVector<AudiblePoint>& audiblePoints = source == INBOUND_AUDIO ? _inboundAudiblePoints : _localAudiblePoints;     foreach(const AudiblePoint& audiblePoint, audiblePoints) {        injectAudiblePoint(audiblePoint, samples, sampleTime, format.sampleRate());    }     _averageDelay = _delayCount == 0 ? 0 : _totalDelay / _delayCount;    _averageAttenuation = _attenuationCount == 0 ? 0 : _totalAttenuation / _attenuationCount;        if (_reflections == 0) {        _minDelay = 0.0f;        _minAttenuation = 0.0f;    }} void AudioReflector::drawVector(const glm::vec3& start, const glm::vec3& end, const glm::vec3& color) {    glDisable(GL_LIGHTING);    glLineWidth(2.0);     // Draw the vector itself    glBegin(GL_LINES);    glColor3f(color.x,color.y,color.z);    glVertex3f(start.x, start.y, start.z);    glVertex3f(end.x, end.y, end.z);    glEnd();     glEnable(GL_LIGHTING);}   AudioPath::AudioPath(AudioSource source, const glm::vec3& origin, const glm::vec3& direction,                         float attenuation, float delay, float distance,bool isDiffusion, int bounceCount) :                            source(source),    isDiffusion(isDiffusion),    startPoint(origin),    startDirection(direction),    startDelay(delay),    startAttenuation(attenuation),     lastPoint(origin),    lastDirection(direction),    lastDistance(distance),    lastDelay(delay),    lastAttenuation(attenuation),    bounceCount(bounceCount),     finalized(false),    reflections(){} void AudioReflector::addAudioPath(AudioSource source, const glm::vec3& origin, const glm::vec3& initialDirection,                                         float initialAttenuation, float initialDelay, float initialDistance, bool isDiffusion) {                                            AudioPath* path = new AudioPath(source, origin, initialDirection, initialAttenuation, initialDelay,                                        initialDistance, isDiffusion, 0);     QVector<AudioPath*>& audioPaths = source == INBOUND_AUDIO ? _inboundAudioPaths : _localAudioPaths;     audioPaths.push_back(path);} void AudioReflector::calculateAllReflections() {    // only recalculate when we've moved, or if the attributes have changed    // TODO: what about case where new voxels are added in front of us???    bool wantHeadOrientation = Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingHeadOriented);    glm::quat orientation = wantHeadOrientation ? _myAvatar->getHead()->getFinalOrientation() : _myAvatar->getOrientation();    glm::vec3 origin = _myAvatar->getHead()->getPosition();    glm::vec3 listenerPosition = _myAvatar->getHead()->getPosition();     bool shouldRecalc = _reflections == 0                            || !isSimilarPosition(origin, _origin)                             || !isSimilarOrientation(orientation, _orientation)                             || !isSimilarPosition(listenerPosition, _listenerPosition)                            || haveAttributesChanged();     if (shouldRecalc) {        QMutexLocker locker(&_mutex);        quint64 start = usecTimestampNow();                _origin = origin;        _orientation = orientation;        _listenerPosition = listenerPosition;        analyzePaths(); // actually does the work        quint64 end = usecTimestampNow();        const bool wantDebugging = false;        if (wantDebugging) {            qDebug() << "newCalculateAllReflections() elapsed=" << (end - start);        }    }}     void AudioReflector::drawRays() {    const glm::vec3 RED(1,0,0);    const glm::vec3 GREEN(0,1,0);    const glm::vec3 BLUE(0,0,1);    const glm::vec3 CYAN(0,1,1);        int diffusionNumber = 0;        QMutexLocker locker(&_mutex);     // draw the paths for inbound audio    foreach(AudioPath* const& path, _inboundAudioPaths) {        // if this is an original reflection, draw it in RED        if (path->isDiffusion) {            diffusionNumber++;            drawPath(path, GREEN);        } else {            drawPath(path, RED);        }    }     if (Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingProcessLocalAudio)) {        // draw the paths for local audio        foreach(AudioPath* const& path, _localAudioPaths) {            // if this is an original reflection, draw it in RED            if (path->isDiffusion) {                diffusionNumber++;                drawPath(path, CYAN);            } else {                drawPath(path, BLUE);            }        }    }} void AudioReflector::drawPath(AudioPath* path, const glm::vec3& originalColor) {    glm::vec3 start = path->startPoint;    glm::vec3 color = originalColor;    const float COLOR_ADJUST_PER_BOUNCE = 0.75f;        foreach (glm::vec3 end, path->reflections) {        drawVector(start, end, color);        start = end;        color = color * COLOR_ADJUST_PER_BOUNCE;    }} void AudioReflector::clearPaths() {    // clear our inbound audio paths    foreach(AudioPath* const& path, _inboundAudioPaths) {        delete path;    }    _inboundAudioPaths.clear();    _inboundAudiblePoints.clear(); // clear our inbound audible points     // clear our local audio paths    foreach(AudioPath* const& path, _localAudioPaths) {        delete path;    }    _localAudioPaths.clear();    _localAudiblePoints.clear(); // clear our local audible points} // Here's how this works: we have an array of AudioPaths, we loop on all of our currently calculating audio // paths, and calculate one ray per path. If that ray doesn't reflect, or reaches a max distance/attenuation, then it// is considered finalized.// If the ray hits a surface, then, based on the characteristics of that surface, it will calculate the new // attenuation, path length, and delay for the primary path. For surfaces that have diffusion, it will also create// fanout number of new paths, those new paths will have an origin of the reflection point, and an initial attenuation// of their diffusion ratio. Those new paths will be added to the active audio paths, and be analyzed for the next loop.void AudioReflector::analyzePaths() {    clearPaths();        // add our initial paths    glm::vec3 right = glm::normalize(_orientation * IDENTITY_RIGHT);    glm::vec3 up = glm::normalize(_orientation * IDENTITY_UP);    glm::vec3 front = glm::normalize(_orientation * IDENTITY_FRONT);    glm::vec3 left = -right;    glm::vec3 down = -up;    glm::vec3 back = -front;    glm::vec3 frontRightUp = glm::normalize(front + right + up);    glm::vec3 frontLeftUp = glm::normalize(front + left + up);    glm::vec3 backRightUp = glm::normalize(back + right + up);    glm::vec3 backLeftUp = glm::normalize(back + left + up);    glm::vec3 frontRightDown = glm::normalize(front + right + down);    glm::vec3 frontLeftDown = glm::normalize(front + left + down);    glm::vec3 backRightDown = glm::normalize(back + right + down);    glm::vec3 backLeftDown = glm::normalize(back + left + down);     float initialAttenuation = 1.0f;         float preDelay = Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingPreDelay) ? _preDelay : 0.0f;     // NOTE: we're still calculating our initial paths based on the listeners position. But the analysis code has been    // updated to support individual sound sources (which is how we support diffusion), we can use this new paradigm to    // add support for individual sound sources, and more directional sound sources         addAudioPath(INBOUND_AUDIO, _origin, front, initialAttenuation, preDelay);    addAudioPath(INBOUND_AUDIO, _origin, right, initialAttenuation, preDelay);    addAudioPath(INBOUND_AUDIO, _origin, up, initialAttenuation, preDelay);    addAudioPath(INBOUND_AUDIO, _origin, down, initialAttenuation, preDelay);    addAudioPath(INBOUND_AUDIO, _origin, back, initialAttenuation, preDelay);    addAudioPath(INBOUND_AUDIO, _origin, left, initialAttenuation, preDelay);    addAudioPath(INBOUND_AUDIO, _origin, frontRightUp, initialAttenuation, preDelay);    addAudioPath(INBOUND_AUDIO, _origin, frontLeftUp, initialAttenuation, preDelay);    addAudioPath(INBOUND_AUDIO, _origin, backRightUp, initialAttenuation, preDelay);    addAudioPath(INBOUND_AUDIO, _origin, backLeftUp, initialAttenuation, preDelay);    addAudioPath(INBOUND_AUDIO, _origin, frontRightDown, initialAttenuation, preDelay);    addAudioPath(INBOUND_AUDIO, _origin, frontLeftDown, initialAttenuation, preDelay);    addAudioPath(INBOUND_AUDIO, _origin, backRightDown, initialAttenuation, preDelay);    addAudioPath(INBOUND_AUDIO, _origin, backLeftDown, initialAttenuation, preDelay);        // the original paths for the local audio are directional to the front of the origin    addAudioPath(LOCAL_AUDIO, _origin, front, initialAttenuation, preDelay);    addAudioPath(LOCAL_AUDIO, _origin, frontRightUp, initialAttenuation, preDelay);    addAudioPath(LOCAL_AUDIO, _origin, frontLeftUp, initialAttenuation, preDelay);    addAudioPath(LOCAL_AUDIO, _origin, frontRightDown, initialAttenuation, preDelay);    addAudioPath(LOCAL_AUDIO, _origin, frontLeftDown, initialAttenuation, preDelay);     // loop through all our audio paths and keep analyzing them until they complete    int steps = 0;    int acitvePaths = _inboundAudioPaths.size() + _localAudioPaths.size(); // when we start, all paths are active    while(acitvePaths > 0) {        acitvePaths = analyzePathsSingleStep();        steps++;    }    _reflections = _inboundAudiblePoints.size() + _localAudiblePoints.size();    _diffusionPathCount = countDiffusionPaths();} int AudioReflector::countDiffusionPaths() {    int diffusionCount = 0;        foreach(AudioPath* const& path, _inboundAudioPaths) {        if (path->isDiffusion) {            diffusionCount++;        }    }    foreach(AudioPath* const& path, _localAudioPaths) {

Lines 1–500 of 734.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t