From b9005a9e7653cba52284718e9931a1e1166137d5 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 16 May 2017 14:32:55 -0700 Subject: [PATCH 01/49] fix up queryAACubes before sending imported entities to server --- libraries/entities/src/EntityTree.cpp | 20 +++++++++++--------- libraries/entities/src/EntityTree.h | 1 - 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 457b7c21f6..35f3b44fd4 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -1574,7 +1574,6 @@ QByteArray EntityTree::remapActionDataIDs(QByteArray actionData, QHash EntityTree::sendEntities(EntityEditPacketSender* packetSender, EntityTreePointer localTree, float x, float y, float z) { SendEntitiesOperationArgs args; - args.packetSender = packetSender; args.ourTree = this; args.otherTree = localTree; args.root = glm::vec3(x, y, z); @@ -1585,21 +1584,29 @@ QVector EntityTree::sendEntities(EntityEditPacketSender* packetSen withReadLock([&] { recurseTreeWithOperation(sendEntitiesOperation, &args); }); - packetSender->releaseQueuedMessages(); // the values from map are used as the list of successfully "sent" entities. If some didn't actually make it, // pull them out. Bogus entries could happen if part of the imported data makes some reference to an entity - // that isn't in the data being imported. + // that isn't in the data being imported. For those that made it, fix up their queryAACubes and send an + // add-entity packet to the server. QHash::iterator i = map.begin(); while (i != map.end()) { EntityItemID newID = i.value(); - if (localTree->findEntityByEntityItemID(newID)) { + EntityItemPointer entity = localTree->findEntityByEntityItemID(newID); + if (entity) { + entity->checkAndAdjustQueryAACube(); + // queue the packet to send to the server + EntityItemProperties properties = entity->getProperties(); + properties.markAllChanged(); // so the entire property set is considered new, since we're making a new entity + packetSender->queueEditEntityMessage(PacketType::EntityAdd, localTree, newID, properties); i++; } else { i = map.erase(i); } } + packetSender->releaseQueuedMessages(); + return map.values().toVector(); } @@ -1652,14 +1659,9 @@ bool EntityTree::sendEntitiesOperation(OctreeElementPointer element, void* extra // set creation time to "now" for imported entities properties.setCreated(usecTimestampNow()); - properties.markAllChanged(); // so the entire property set is considered new, since we're making a new entity - EntityTreeElementPointer entityTreeElement = std::static_pointer_cast(element); EntityTreePointer tree = entityTreeElement->getTree(); - // queue the packet to send to the server - args->packetSender->queueEditEntityMessage(PacketType::EntityAdd, tree, newID, properties); - // also update the local tree instantly (note: this is not our tree, but an alternate tree) if (args->otherTree) { args->otherTree->withWriteLock([&] { diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index d7e069b005..6137d14aac 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -53,7 +53,6 @@ public: glm::vec3 root; EntityTree* ourTree; EntityTreePointer otherTree; - EntityEditPacketSender* packetSender; QHash* map; }; From d90843428df2579be44fd9ac305727da39eb8e92 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 16 May 2017 16:54:16 -0700 Subject: [PATCH 02/49] when importing entities, make a queryAACube that takes children into account --- libraries/entities/src/EntityTree.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 35f3b44fd4..2ece07e86d 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -1594,7 +1594,7 @@ QVector EntityTree::sendEntities(EntityEditPacketSender* packetSen EntityItemID newID = i.value(); EntityItemPointer entity = localTree->findEntityByEntityItemID(newID); if (entity) { - entity->checkAndAdjustQueryAACube(); + entity->computePuffedQueryAACube(); // queue the packet to send to the server EntityItemProperties properties = entity->getProperties(); properties.markAllChanged(); // so the entire property set is considered new, since we're making a new entity From ec02887ae615fd8bd75444e457b3abc2afcf506e Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 18 May 2017 13:20:44 -0700 Subject: [PATCH 03/49] attempt to get imported entities into the correct octree-element --- libraries/entities/src/EntityItem.cpp | 9 ++++--- libraries/entities/src/EntityTree.cpp | 30 ++++++++++++++++++---- libraries/shared/src/SpatiallyNestable.cpp | 3 ++- libraries/shared/src/SpatiallyNestable.h | 3 ++- 4 files changed, 35 insertions(+), 10 deletions(-) diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 14122594fe..c9a26a9f4d 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -1369,9 +1369,12 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(lastEditedBy, setLastEditedBy); AACube saveQueryAACube = _queryAACube; - checkAndAdjustQueryAACube(); - if (saveQueryAACube != _queryAACube) { - somethingChanged = true; + if (checkAndAdjustQueryAACube()) { + if (saveQueryAACube != _queryAACube) { + somethingChanged = true; + } + } else { + markAncestorMissing(true); } // Now check the sub classes diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index c6a42c2f3a..7d353f69a1 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -1584,25 +1584,46 @@ QVector EntityTree::sendEntities(EntityEditPacketSender* packetSen args.ourTree = this; args.otherTree = localTree; args.root = glm::vec3(x, y, z); - // If this is called repeatedly (e.g., multiple pastes with the same data), the new elements will clash unless we use new identifiers. - // We need to keep a map so that we can map parent identifiers correctly. + // If this is called repeatedly (e.g., multiple pastes with the same data), the new elements will clash unless we + // use new identifiers. We need to keep a map so that we can map parent identifiers correctly. QHash map; args.map = ↦ withReadLock([&] { recurseTreeWithOperation(sendEntitiesOperation, &args); }); - // the values from map are used as the list of successfully "sent" entities. If some didn't actually make it, + // The values from map are used as the list of successfully "sent" entities. If some didn't actually make it, // pull them out. Bogus entries could happen if part of the imported data makes some reference to an entity // that isn't in the data being imported. For those that made it, fix up their queryAACubes and send an // add-entity packet to the server. + + // fix the queryAACubes of any children that were read in before their parents, get them into the correct element + MovingEntitiesOperator moveOperator(localTree); QHash::iterator i = map.begin(); while (i != map.end()) { EntityItemID newID = i.value(); EntityItemPointer entity = localTree->findEntityByEntityItemID(newID); if (entity) { - entity->computePuffedQueryAACube(); + entity->forceQueryAACubeUpdate(); + moveOperator.addEntityToMoveList(entity, entity->getQueryAACube()); + i++; + } else { + i = map.erase(i); + } + } + if (moveOperator.hasMovingEntities()) { + PerformanceTimer perfTimer("recurseTreeWithOperator"); + localTree->recurseTreeWithOperator(&moveOperator); + } + + // send add-entity packets to the server + i = map.begin(); + while (i != map.end()) { + EntityItemID newID = i.value(); + EntityItemPointer entity = localTree->findEntityByEntityItemID(newID); + if (entity) { // queue the packet to send to the server + entity->computePuffedQueryAACube(); EntityItemProperties properties = entity->getProperties(); properties.markAllChanged(); // so the entire property set is considered new, since we're making a new entity packetSender->queueEditEntityMessage(PacketType::EntityAdd, localTree, newID, properties); @@ -1611,7 +1632,6 @@ QVector EntityTree::sendEntities(EntityEditPacketSender* packetSen i = map.erase(i); } } - packetSender->releaseQueuedMessages(); return map.values().toVector(); diff --git a/libraries/shared/src/SpatiallyNestable.cpp b/libraries/shared/src/SpatiallyNestable.cpp index 9c7e216cb6..4fe9cda825 100644 --- a/libraries/shared/src/SpatiallyNestable.cpp +++ b/libraries/shared/src/SpatiallyNestable.cpp @@ -946,12 +946,13 @@ AACube SpatiallyNestable::getMaximumAACube(bool& success) const { return AACube(getPosition(success) - glm::vec3(defaultAACubeSize / 2.0f), defaultAACubeSize); } -void SpatiallyNestable::checkAndAdjustQueryAACube() { +bool SpatiallyNestable::checkAndAdjustQueryAACube() { bool success; AACube maxAACube = getMaximumAACube(success); if (success && (!_queryAACubeSet || !_queryAACube.contains(maxAACube))) { setQueryAACube(maxAACube); } + return success; } void SpatiallyNestable::setQueryAACube(const AACube& queryAACube) { diff --git a/libraries/shared/src/SpatiallyNestable.h b/libraries/shared/src/SpatiallyNestable.h index e8961dba98..6589a44307 100644 --- a/libraries/shared/src/SpatiallyNestable.h +++ b/libraries/shared/src/SpatiallyNestable.h @@ -102,11 +102,12 @@ public: virtual glm::vec3 getParentAngularVelocity(bool& success) const; virtual AACube getMaximumAACube(bool& success) const; - virtual void checkAndAdjustQueryAACube(); + virtual bool checkAndAdjustQueryAACube(); virtual bool computePuffedQueryAACube(); virtual void setQueryAACube(const AACube& queryAACube); virtual bool queryAABoxNeedsUpdate() const; + void forceQueryAACubeUpdate() { _queryAACubeSet = false; } virtual AACube getQueryAACube(bool& success) const; virtual AACube getQueryAACube() const; From cad9eeb8ba2f619deeb78d498a14e553c4f27739 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Thu, 18 May 2017 14:13:24 -0700 Subject: [PATCH 04/49] Refactor the peak limiter (output is bit-exact) --- libraries/audio/src/AudioDynamics.h | 522 +++++++++++++++++++++++++++ libraries/audio/src/AudioLimiter.cpp | 449 +---------------------- libraries/audio/src/AudioLimiter.h | 2 +- 3 files changed, 527 insertions(+), 446 deletions(-) create mode 100644 libraries/audio/src/AudioDynamics.h diff --git a/libraries/audio/src/AudioDynamics.h b/libraries/audio/src/AudioDynamics.h new file mode 100644 index 0000000000..2306fecf24 --- /dev/null +++ b/libraries/audio/src/AudioDynamics.h @@ -0,0 +1,522 @@ +// +// AudioDynamics.h +// libraries/audio/src +// +// Created by Ken Cooke on 5/5/17. +// Copyright 2017 High Fidelity, Inc. +// + +// +// Inline functions to implement audio dynamics processing +// + +#include +#include + +#ifndef MAX +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#endif +#ifndef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif + +#ifdef _MSC_VER +#include +#define MUL64(a,b) __emul((a), (b)) +#else +#define MUL64(a,b) ((int64_t)(a) * (int64_t)(b)) +#endif + +#define MULHI(a,b) ((int32_t)(MUL64(a, b) >> 32)) +#define MULQ31(a,b) ((int32_t)(MUL64(a, b) >> 31)) +#define MULDIV64(a,b,c) (int32_t)(MUL64(a, b) / (c)) + +// +// on x86 architecture, assume that SSE2 is present +// +#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__x86_64__) + +#include +// convert float to int using round-to-nearest +static inline int32_t floatToInt(float x) { + return _mm_cvt_ss2si(_mm_load_ss(&x)); +} + +#else + +// convert float to int using round-to-nearest +static inline int32_t floatToInt(float x) { + x += (x < 0.0f ? -0.5f : 0.5f); // round + return (int32_t)x; +} + +#endif // _M_IX86 + +static const double FIXQ31 = 2147483648.0; // convert float to Q31 +static const double DB_TO_LOG2 = 0.16609640474436813; // convert dB to log2 + +// convert dB to amplitude +static double dBToGain(double dB) { + return pow(10.0, dB / 20.0); +} + +// convert milliseconds to first-order time constant +static int32_t msToTc(double ms, double sampleRate) { + double tc = exp(-1000.0 / (ms * sampleRate)); + return (int32_t)(FIXQ31 * tc); // Q31 +} + +// log2 domain values are Q26 +static const int LOG2_INTBITS = 5; +static const int LOG2_FRACBITS = 31 - LOG2_INTBITS; + +// log2 domain headroom bits above 0dB +static const int LOG2_HEADROOM = 15; + +// log2 domain offsets so error < 0 +static const int32_t LOG2_BIAS = 347; +static const int32_t EXP2_BIAS = 64; + +// +// P(x) = log2(1+x) for x=[0,1] +// scaled by 1, 0.5, 0.25 +// +// |error| < 347 ulp, smooth +// +static const int LOG2_TABBITS = 4; +static const int32_t log2Table[1 << LOG2_TABBITS][3] = { + { -0x56dfe26d, 0x5c46daff, 0x00000000 }, + { -0x4d397571, 0x5bae58e7, 0x00025a75 }, + { -0x4518f84b, 0x5aabcac4, 0x000a62db }, + { -0x3e3075ec, 0x596168c0, 0x0019d0e6 }, + { -0x384486e9, 0x57e769c7, 0x00316109 }, + { -0x332742ba, 0x564f1461, 0x00513776 }, + { -0x2eb4bad4, 0x54a4cdfe, 0x00791de2 }, + { -0x2ad07c6c, 0x52f18320, 0x00a8aa46 }, + { -0x2763c4d6, 0x513ba123, 0x00df574c }, + { -0x245c319b, 0x4f87c5c4, 0x011c9399 }, + { -0x21aac79f, 0x4dd93bef, 0x015fcb52 }, + { -0x1f433872, 0x4c325584, 0x01a86ddc }, + { -0x1d1b54b4, 0x4a94ac6e, 0x01f5f13e }, + { -0x1b2a9f81, 0x4901524f, 0x0247d3f2 }, + { -0x1969fa57, 0x4778f3a7, 0x029d9dbf }, + { -0x17d36370, 0x45fbf1e8, 0x02f6dfe8 }, +}; + +// +// P(x) = exp2(x) for x=[0,1] +// scaled by 2, 1, 0.5 +// Uses exp2(-x) = exp2(1-x)/2 +// +// |error| < 1387 ulp, smooth +// +static const int EXP2_TABBITS = 4; +static const int32_t exp2Table[1 << EXP2_TABBITS][3] = { + { 0x3ed838c8, 0x58b574b7, 0x40000000 }, + { 0x41a0821c, 0x5888db8f, 0x4000b2b7 }, + { 0x4488548d, 0x582bcbc6, 0x40039be1 }, + { 0x4791158a, 0x579a1128, 0x400a71ae }, + { 0x4abc3a53, 0x56cf3089, 0x4017212e }, + { 0x4e0b48af, 0x55c66396, 0x402bd31b }, + { 0x517fd7a7, 0x547a946d, 0x404af0ec }, + { 0x551b9049, 0x52e658f9, 0x40772a57 }, + { 0x58e02e75, 0x5103ee08, 0x40b37b31 }, + { 0x5ccf81b1, 0x4ecd321f, 0x410331b5 }, + { 0x60eb6e09, 0x4c3ba007, 0x4169f548 }, + { 0x6535ecf9, 0x49484909, 0x41ebcdaf }, + { 0x69b10e5b, 0x45ebcede, 0x428d2acd }, + { 0x6e5ef96c, 0x421e5d48, 0x4352ece7 }, + { 0x7341edcb, 0x3dd7a354, 0x44426d7b }, + { 0x785c4499, 0x390ecc3a, 0x456188bd }, +}; + +static const int IEEE754_FABS_MASK = 0x7fffffff; +static const int IEEE754_MANT_BITS = 23; +static const int IEEE754_EXPN_BIAS = 127; + +// +// Peak detection and -log2(x) for float input (mono) +// x < 2^(31-LOG2_HEADROOM) returns 0x7fffffff +// x > 2^LOG2_HEADROOM undefined +// +static inline int32_t peaklog2(float* input) { + + // float as integer bits + int32_t u = *(int32_t*)input; + + // absolute value + int32_t peak = u & IEEE754_FABS_MASK; + + // split into e and x - 1.0 + int32_t e = IEEE754_EXPN_BIAS - (peak >> IEEE754_MANT_BITS) + LOG2_HEADROOM; + int32_t x = (peak << (31 - IEEE754_MANT_BITS)) & 0x7fffffff; + + // saturate + if (e > 31) { + return 0x7fffffff; + } + + int k = x >> (31 - LOG2_TABBITS); + + // polynomial for log2(1+x) over x=[0,1] + int32_t c0 = log2Table[k][0]; + int32_t c1 = log2Table[k][1]; + int32_t c2 = log2Table[k][2]; + + c1 += MULHI(c0, x); + c2 += MULHI(c1, x); + + // reconstruct result in Q26 + return (e << LOG2_FRACBITS) - (c2 >> 3); +} + +// +// Peak detection and -log2(x) for float input (stereo) +// x < 2^(31-LOG2_HEADROOM) returns 0x7fffffff +// x > 2^LOG2_HEADROOM undefined +// +static inline int32_t peaklog2(float* input0, float* input1) { + + // float as integer bits + int32_t u0 = *(int32_t*)input0; + int32_t u1 = *(int32_t*)input1; + + // max absolute value + u0 &= IEEE754_FABS_MASK; + u1 &= IEEE754_FABS_MASK; + int32_t peak = MAX(u0, u1); + + // split into e and x - 1.0 + int32_t e = IEEE754_EXPN_BIAS - (peak >> IEEE754_MANT_BITS) + LOG2_HEADROOM; + int32_t x = (peak << (31 - IEEE754_MANT_BITS)) & 0x7fffffff; + + // saturate + if (e > 31) { + return 0x7fffffff; + } + + int k = x >> (31 - LOG2_TABBITS); + + // polynomial for log2(1+x) over x=[0,1] + int32_t c0 = log2Table[k][0]; + int32_t c1 = log2Table[k][1]; + int32_t c2 = log2Table[k][2]; + + c1 += MULHI(c0, x); + c2 += MULHI(c1, x); + + // reconstruct result in Q26 + return (e << LOG2_FRACBITS) - (c2 >> 3); +} + +// +// Peak detection and -log2(x) for float input (quad) +// x < 2^(31-LOG2_HEADROOM) returns 0x7fffffff +// x > 2^LOG2_HEADROOM undefined +// +static inline int32_t peaklog2(float* input0, float* input1, float* input2, float* input3) { + + // float as integer bits + int32_t u0 = *(int32_t*)input0; + int32_t u1 = *(int32_t*)input1; + int32_t u2 = *(int32_t*)input2; + int32_t u3 = *(int32_t*)input3; + + // max absolute value + u0 &= IEEE754_FABS_MASK; + u1 &= IEEE754_FABS_MASK; + u2 &= IEEE754_FABS_MASK; + u3 &= IEEE754_FABS_MASK; + int32_t peak = MAX(MAX(u0, u1), MAX(u2, u3)); + + // split into e and x - 1.0 + int32_t e = IEEE754_EXPN_BIAS - (peak >> IEEE754_MANT_BITS) + LOG2_HEADROOM; + int32_t x = (peak << (31 - IEEE754_MANT_BITS)) & 0x7fffffff; + + // saturate + if (e > 31) { + return 0x7fffffff; + } + + int k = x >> (31 - LOG2_TABBITS); + + // polynomial for log2(1+x) over x=[0,1] + int32_t c0 = log2Table[k][0]; + int32_t c1 = log2Table[k][1]; + int32_t c2 = log2Table[k][2]; + + c1 += MULHI(c0, x); + c2 += MULHI(c1, x); + + // reconstruct result in Q26 + return (e << LOG2_FRACBITS) - (c2 >> 3); +} + +// +// Compute exp2(-x) for x=[0,32] in Q26, result in Q31 +// x < 0 undefined +// +static inline int32_t fixexp2(int32_t x) { + + // split into e and 1.0 - x + int e = x >> LOG2_FRACBITS; + x = ~(x << LOG2_INTBITS) & 0x7fffffff; + + int k = x >> (31 - EXP2_TABBITS); + + // polynomial for exp2(x) + int32_t c0 = exp2Table[k][0]; + int32_t c1 = exp2Table[k][1]; + int32_t c2 = exp2Table[k][2]; + + c1 += MULHI(c0, x); + c2 += MULHI(c1, x); + + // reconstruct result in Q31 + return c2 >> e; +} + +// fast TPDF dither in [-1.0f, 1.0f] +static inline float dither() { + static uint32_t rz = 0; + rz = rz * 69069 + 1; + int32_t r0 = rz & 0xffff; + int32_t r1 = rz >> 16; + return (int32_t)(r0 - r1) * (1/65536.0f); +} + +// +// Min-hold lowpass filter +// +// Bandlimits the gain control signal to greatly reduce the modulation distortion, +// while still reaching the peak attenuation after exactly N-1 samples of delay. +// N completely determines the attack time. +// +template +class MinFilterT { + + static_assert((N & (N - 1)) == 0, "N must be a power of 2"); + static_assert((CIC1 - 1) + (CIC2 - 1) == (N - 1), "Total CIC delay must be N-1"); + + int32_t _buffer[2*N] = {}; // shared FIFO + size_t _index = 0; + + int32_t _acc1 = 0; // CIC1 integrator + int32_t _acc2 = 0; // CIC2 integrator + +public: + MinFilterT() { + + // fill history + for (size_t n = 0; n < N-1; n++) { + process(0x7fffffff); + } + } + + int32_t process(int32_t x) { + + const size_t MASK = 2*N - 1; // buffer wrap + size_t i = _index; + + // Fast min-hold using a running-min filter. Finds the peak (min) value + // in the sliding window of N-1 samples, using only log2(N) comparisons. + // Hold time of N-1 samples exactly cancels the step response of FIR filter. + + for (size_t n = 1; n < N; n <<= 1) { + + _buffer[i] = x; + i = (i + n) & MASK; + x = MIN(x, _buffer[i]); + } + + // Fast FIR attack/lowpass filter using a 2-stage CIC filter. + // The step response reaches final value after N-1 samples. + + const int32_t CICGAIN = 0xffffffff / (CIC1 * CIC2); // Q32 + x = MULHI(x, CICGAIN); + + _buffer[i] = _acc1; + _acc1 += x; // integrator + i = (i + CIC1 - 1) & MASK; + x = _acc1 - _buffer[i]; // comb + + _buffer[i] = _acc2; + _acc2 += x; // integrator + i = (i + CIC2 - 1) & MASK; + x = _acc2 - _buffer[i]; // comb + + _index = (i + 1) & MASK; // skip unused tap + return x; + } +}; + +// +// Max-hold lowpass filter +// +// Bandlimits the gain control signal to greatly reduce the modulation distortion, +// while still reaching the peak attenuation after exactly N-1 samples of delay. +// N completely determines the attack time. +// +template +class MaxFilterT { + + static_assert((N & (N - 1)) == 0, "N must be a power of 2"); + static_assert((CIC1 - 1) + (CIC2 - 1) == (N - 1), "Total CIC delay must be N-1"); + + int32_t _buffer[2*N] = {}; // shared FIFO + size_t _index = 0; + + int32_t _acc1 = 0; // CIC1 integrator + int32_t _acc2 = 0; // CIC2 integrator + +public: + MaxFilterT() { + + // fill history + for (size_t n = 0; n < N-1; n++) { + process(0); + } + } + + int32_t process(int32_t x) { + + const size_t MASK = 2*N - 1; // buffer wrap + size_t i = _index; + + // Fast max-hold using a running-max filter. Finds the peak (max) value + // in the sliding window of N-1 samples, using only log2(N) comparisons. + // Hold time of N-1 samples exactly cancels the step response of FIR filter. + + for (size_t n = 1; n < N; n <<= 1) { + + _buffer[i] = x; + i = (i + n) & MASK; + x = MAX(x, _buffer[i]); + } + + // Fast FIR attack/lowpass filter using a 2-stage CIC filter. + // The step response reaches final value after N-1 samples. + + const int32_t CICGAIN = 0xffffffff / (CIC1 * CIC2); // Q32 + x = MULHI(x, CICGAIN); + + _buffer[i] = _acc1; + _acc1 += x; // integrator + i = (i + CIC1 - 1) & MASK; + x = _acc1 - _buffer[i]; // comb + + _buffer[i] = _acc2; + _acc2 += x; // integrator + i = (i + CIC2 - 1) & MASK; + x = _acc2 - _buffer[i]; // comb + + _index = (i + 1) & MASK; // skip unused tap + return x; + } +}; + +// +// Specializations that define the optimum lowpass filter for each length. +// +template class MinFilter; +template<> class MinFilter< 16> : public MinFilterT< 16, 7, 10> {}; +template<> class MinFilter< 32> : public MinFilterT< 32, 14, 19> {}; +template<> class MinFilter< 64> : public MinFilterT< 64, 27, 38> {}; +template<> class MinFilter<128> : public MinFilterT<128, 53, 76> {}; +template<> class MinFilter<256> : public MinFilterT<256, 106, 151> {}; + +template class MaxFilter; +template<> class MaxFilter< 16> : public MaxFilterT< 16, 7, 10> {}; +template<> class MaxFilter< 32> : public MaxFilterT< 32, 14, 19> {}; +template<> class MaxFilter< 64> : public MaxFilterT< 64, 27, 38> {}; +template<> class MaxFilter<128> : public MaxFilterT<128, 53, 76> {}; +template<> class MaxFilter<256> : public MaxFilterT<256, 106, 151> {}; + +// +// N-1 sample delay (mono) +// +template +class MonoDelay { + + static_assert((N & (N - 1)) == 0, "N must be a power of 2"); + + T _buffer[N] = {}; + size_t _index = 0; + +public: + void process(T& x) { + + const size_t MASK = N - 1; // buffer wrap + size_t i = _index; + + _buffer[i] = x; + + i = (i + (N - 1)) & MASK; + + x = _buffer[i]; + + _index = i; + } +}; + +// +// N-1 sample delay (stereo) +// +template +class StereoDelay { + + static_assert((N & (N - 1)) == 0, "N must be a power of 2"); + + T _buffer[2*N] = {}; + size_t _index = 0; + +public: + void process(T& x0, T& x1) { + + const size_t MASK = 2*N - 1; // buffer wrap + size_t i = _index; + + _buffer[i+0] = x0; + _buffer[i+1] = x1; + + i = (i + 2*(N - 1)) & MASK; + + x0 = _buffer[i+0]; + x1 = _buffer[i+1]; + + _index = i; + } +}; + +// +// N-1 sample delay (quad) +// +template +class QuadDelay { + + static_assert((N & (N - 1)) == 0, "N must be a power of 2"); + + T _buffer[4*N] = {}; + size_t _index = 0; + +public: + void process(T& x0, T& x1, T& x2, T& x3) { + + const size_t MASK = 4*N - 1; // buffer wrap + size_t i = _index; + + _buffer[i+0] = x0; + _buffer[i+1] = x1; + _buffer[i+2] = x2; + _buffer[i+3] = x3; + + i = (i + 4*(N - 1)) & MASK; + + x0 = _buffer[i+0]; + x1 = _buffer[i+1]; + x2 = _buffer[i+2]; + x3 = _buffer[i+3]; + + _index = i; + } +}; diff --git a/libraries/audio/src/AudioLimiter.cpp b/libraries/audio/src/AudioLimiter.cpp index 316c0b181a..4ddff1f5a5 100644 --- a/libraries/audio/src/AudioLimiter.cpp +++ b/libraries/audio/src/AudioLimiter.cpp @@ -6,452 +6,11 @@ // Copyright 2016 High Fidelity, Inc. // -#include #include +#include "AudioDynamics.h" #include "AudioLimiter.h" -#ifndef MAX -#define MAX(a,b) ((a) > (b) ? (a) : (b)) -#endif -#ifndef MIN -#define MIN(a,b) ((a) < (b) ? (a) : (b)) -#endif - -#ifdef _MSC_VER - -#include -#define MUL64(a,b) __emul((a), (b)) -#define MULHI(a,b) ((int)(MUL64(a, b) >> 32)) -#define MULQ31(a,b) ((int)(MUL64(a, b) >> 31)) - -#else - -#define MUL64(a,b) ((long long)(a) * (b)) -#define MULHI(a,b) ((int)(MUL64(a, b) >> 32)) -#define MULQ31(a,b) ((int)(MUL64(a, b) >> 31)) - -#endif // _MSC_VER - -// -// on x86 architecture, assume that SSE2 is present -// -#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__x86_64__) - -#include -// convert float to int using round-to-nearest -static inline int32_t floatToInt(float x) { - return _mm_cvt_ss2si(_mm_load_ss(&x)); -} - -#else - -// convert float to int using round-to-nearest -static inline int32_t floatToInt(float x) { - x += (x < 0.0f ? -0.5f : 0.5f); // round - return (int32_t)x; -} - -#endif // _M_IX86 - -static const double FIXQ31 = 2147483648.0; // convert float to Q31 -static const double DB_TO_LOG2 = 0.16609640474436813; // convert dB to log2 - -// convert dB to amplitude -static double dBToGain(double dB) { - return pow(10.0, dB / 20.0); -} - -// convert milliseconds to first-order time constant -static int32_t msToTc(double ms, double sampleRate) { - double tc = exp(-1000.0 / (ms * sampleRate)); - return (int32_t)(FIXQ31 * tc); // Q31 -} - -// log2 domain values are Q26 -static const int LOG2_INTBITS = 5; -static const int LOG2_FRACBITS = 31 - LOG2_INTBITS; - -// log2 domain headroom bits above 0dB -static const int LOG2_HEADROOM = 15; - -// log2 domain offsets so error < 0 -static const int32_t LOG2_BIAS = 347; -static const int32_t EXP2_BIAS = 64; - -// -// P(x) = log2(1+x) for x=[0,1] -// scaled by 1, 0.5, 0.25 -// -// |error| < 347 ulp, smooth -// -static const int LOG2_TABBITS = 4; -static const int32_t log2Table[1 << LOG2_TABBITS][3] = { - { -0x56dfe26d, 0x5c46daff, 0x00000000 }, - { -0x4d397571, 0x5bae58e7, 0x00025a75 }, - { -0x4518f84b, 0x5aabcac4, 0x000a62db }, - { -0x3e3075ec, 0x596168c0, 0x0019d0e6 }, - { -0x384486e9, 0x57e769c7, 0x00316109 }, - { -0x332742ba, 0x564f1461, 0x00513776 }, - { -0x2eb4bad4, 0x54a4cdfe, 0x00791de2 }, - { -0x2ad07c6c, 0x52f18320, 0x00a8aa46 }, - { -0x2763c4d6, 0x513ba123, 0x00df574c }, - { -0x245c319b, 0x4f87c5c4, 0x011c9399 }, - { -0x21aac79f, 0x4dd93bef, 0x015fcb52 }, - { -0x1f433872, 0x4c325584, 0x01a86ddc }, - { -0x1d1b54b4, 0x4a94ac6e, 0x01f5f13e }, - { -0x1b2a9f81, 0x4901524f, 0x0247d3f2 }, - { -0x1969fa57, 0x4778f3a7, 0x029d9dbf }, - { -0x17d36370, 0x45fbf1e8, 0x02f6dfe8 }, -}; - -// -// P(x) = exp2(x) for x=[0,1] -// scaled by 2, 1, 0.5 -// Uses exp2(-x) = exp2(1-x)/2 -// -// |error| < 1387 ulp, smooth -// -static const int EXP2_TABBITS = 4; -static const int32_t exp2Table[1 << EXP2_TABBITS][3] = { - { 0x3ed838c8, 0x58b574b7, 0x40000000 }, - { 0x41a0821c, 0x5888db8f, 0x4000b2b7 }, - { 0x4488548d, 0x582bcbc6, 0x40039be1 }, - { 0x4791158a, 0x579a1128, 0x400a71ae }, - { 0x4abc3a53, 0x56cf3089, 0x4017212e }, - { 0x4e0b48af, 0x55c66396, 0x402bd31b }, - { 0x517fd7a7, 0x547a946d, 0x404af0ec }, - { 0x551b9049, 0x52e658f9, 0x40772a57 }, - { 0x58e02e75, 0x5103ee08, 0x40b37b31 }, - { 0x5ccf81b1, 0x4ecd321f, 0x410331b5 }, - { 0x60eb6e09, 0x4c3ba007, 0x4169f548 }, - { 0x6535ecf9, 0x49484909, 0x41ebcdaf }, - { 0x69b10e5b, 0x45ebcede, 0x428d2acd }, - { 0x6e5ef96c, 0x421e5d48, 0x4352ece7 }, - { 0x7341edcb, 0x3dd7a354, 0x44426d7b }, - { 0x785c4499, 0x390ecc3a, 0x456188bd }, -}; - -static const int IEEE754_FABS_MASK = 0x7fffffff; -static const int IEEE754_MANT_BITS = 23; -static const int IEEE754_EXPN_BIAS = 127; - -// -// Peak detection and -log2(x) for float input (mono) -// x < 2^(31-LOG2_HEADROOM) returns 0x7fffffff -// x > 2^LOG2_HEADROOM undefined -// -static inline int32_t peaklog2(float* input) { - - // float as integer bits - int32_t u = *(int32_t*)input; - - // absolute value - int32_t peak = u & IEEE754_FABS_MASK; - - // split into e and x - 1.0 - int32_t e = IEEE754_EXPN_BIAS - (peak >> IEEE754_MANT_BITS) + LOG2_HEADROOM; - int32_t x = (peak << (31 - IEEE754_MANT_BITS)) & 0x7fffffff; - - // saturate - if (e > 31) { - return 0x7fffffff; - } - - int k = x >> (31 - LOG2_TABBITS); - - // polynomial for log2(1+x) over x=[0,1] - int32_t c0 = log2Table[k][0]; - int32_t c1 = log2Table[k][1]; - int32_t c2 = log2Table[k][2]; - - c1 += MULHI(c0, x); - c2 += MULHI(c1, x); - - // reconstruct result in Q26 - return (e << LOG2_FRACBITS) - (c2 >> 3); -} - -// -// Peak detection and -log2(x) for float input (stereo) -// x < 2^(31-LOG2_HEADROOM) returns 0x7fffffff -// x > 2^LOG2_HEADROOM undefined -// -static inline int32_t peaklog2(float* input0, float* input1) { - - // float as integer bits - int32_t u0 = *(int32_t*)input0; - int32_t u1 = *(int32_t*)input1; - - // max absolute value - u0 &= IEEE754_FABS_MASK; - u1 &= IEEE754_FABS_MASK; - int32_t peak = MAX(u0, u1); - - // split into e and x - 1.0 - int32_t e = IEEE754_EXPN_BIAS - (peak >> IEEE754_MANT_BITS) + LOG2_HEADROOM; - int32_t x = (peak << (31 - IEEE754_MANT_BITS)) & 0x7fffffff; - - // saturate - if (e > 31) { - return 0x7fffffff; - } - - int k = x >> (31 - LOG2_TABBITS); - - // polynomial for log2(1+x) over x=[0,1] - int32_t c0 = log2Table[k][0]; - int32_t c1 = log2Table[k][1]; - int32_t c2 = log2Table[k][2]; - - c1 += MULHI(c0, x); - c2 += MULHI(c1, x); - - // reconstruct result in Q26 - return (e << LOG2_FRACBITS) - (c2 >> 3); -} - -// -// Peak detection and -log2(x) for float input (quad) -// x < 2^(31-LOG2_HEADROOM) returns 0x7fffffff -// x > 2^LOG2_HEADROOM undefined -// -static inline int32_t peaklog2(float* input0, float* input1, float* input2, float* input3) { - - // float as integer bits - int32_t u0 = *(int32_t*)input0; - int32_t u1 = *(int32_t*)input1; - int32_t u2 = *(int32_t*)input2; - int32_t u3 = *(int32_t*)input3; - - // max absolute value - u0 &= IEEE754_FABS_MASK; - u1 &= IEEE754_FABS_MASK; - u2 &= IEEE754_FABS_MASK; - u3 &= IEEE754_FABS_MASK; - int32_t peak = MAX(MAX(u0, u1), MAX(u2, u3)); - - // split into e and x - 1.0 - int32_t e = IEEE754_EXPN_BIAS - (peak >> IEEE754_MANT_BITS) + LOG2_HEADROOM; - int32_t x = (peak << (31 - IEEE754_MANT_BITS)) & 0x7fffffff; - - // saturate - if (e > 31) { - return 0x7fffffff; - } - - int k = x >> (31 - LOG2_TABBITS); - - // polynomial for log2(1+x) over x=[0,1] - int32_t c0 = log2Table[k][0]; - int32_t c1 = log2Table[k][1]; - int32_t c2 = log2Table[k][2]; - - c1 += MULHI(c0, x); - c2 += MULHI(c1, x); - - // reconstruct result in Q26 - return (e << LOG2_FRACBITS) - (c2 >> 3); -} - -// -// Compute exp2(-x) for x=[0,32] in Q26, result in Q31 -// x < 0 undefined -// -static inline int32_t fixexp2(int32_t x) { - - // split into e and 1.0 - x - int32_t e = x >> LOG2_FRACBITS; - x = ~(x << LOG2_INTBITS) & 0x7fffffff; - - int k = x >> (31 - EXP2_TABBITS); - - // polynomial for exp2(x) - int32_t c0 = exp2Table[k][0]; - int32_t c1 = exp2Table[k][1]; - int32_t c2 = exp2Table[k][2]; - - c1 += MULHI(c0, x); - c2 += MULHI(c1, x); - - // reconstruct result in Q31 - return c2 >> e; -} - -// fast TPDF dither in [-1.0f, 1.0f] -static inline float dither() { - static uint32_t rz = 0; - rz = rz * 69069 + 1; - int32_t r0 = rz & 0xffff; - int32_t r1 = rz >> 16; - return (int32_t)(r0 - r1) * (1/65536.0f); -} - -// -// Peak-hold lowpass filter -// -// Bandlimits the gain control signal to greatly reduce the modulation distortion, -// while still reaching the peak attenuation after exactly N-1 samples of delay. -// N completely determines the limiter attack time. -// -template -class PeakFilterT { - - static_assert((N & (N - 1)) == 0, "N must be a power of 2"); - static_assert((CIC1 - 1) + (CIC2 - 1) == (N - 1), "Total CIC delay must be N-1"); - - int32_t _buffer[2*N] = {}; // shared FIFO - size_t _index = 0; - - int32_t _acc1 = 0; // CIC1 integrator - int32_t _acc2 = 0; // CIC2 integrator - -public: - PeakFilterT() { - - // fill history - for (size_t n = 0; n < N-1; n++) { - process(0x7fffffff); - } - } - - int32_t process(int32_t x) { - - const size_t MASK = 2*N - 1; // buffer wrap - size_t i = _index; - - // Fast peak-hold using a running-min filter. Finds the peak (min) value - // in the sliding window of N-1 samples, using only log2(N) comparisons. - // Hold time of N-1 samples exactly cancels the step response of FIR filter. - - for (size_t n = 1; n < N; n <<= 1) { - - _buffer[i] = x; - i = (i + n) & MASK; - x = MIN(x, _buffer[i]); - } - - // Fast FIR attack/lowpass filter using a 2-stage CIC filter. - // The step response reaches final value after N-1 samples. - - const int32_t CICGAIN = 0xffffffff / (CIC1 * CIC2); // Q32 - x = MULHI(x, CICGAIN); - - _buffer[i] = _acc1; - _acc1 += x; // integrator - i = (i + CIC1 - 1) & MASK; - x = _acc1 - _buffer[i]; // comb - - _buffer[i] = _acc2; - _acc2 += x; // integrator - i = (i + CIC2 - 1) & MASK; - x = _acc2 - _buffer[i]; // comb - - _index = (i + 1) & MASK; // skip unused tap - return x; - } -}; - -// -// Specializations that define the optimum lowpass filter for each length. -// -template class PeakFilter; - -template<> class PeakFilter< 16> : public PeakFilterT< 16, 7, 10> {}; -template<> class PeakFilter< 32> : public PeakFilterT< 32, 14, 19> {}; -template<> class PeakFilter< 64> : public PeakFilterT< 64, 27, 38> {}; -template<> class PeakFilter<128> : public PeakFilterT<128, 53, 76> {}; -template<> class PeakFilter<256> : public PeakFilterT<256, 106, 151> {}; - -// -// N-1 sample delay (mono) -// -template -class MonoDelay { - - static_assert((N & (N - 1)) == 0, "N must be a power of 2"); - - float _buffer[N] = {}; - size_t _index = 0; - -public: - void process(float& x) { - - const size_t MASK = N - 1; // buffer wrap - size_t i = _index; - - _buffer[i] = x; - - i = (i + (N - 1)) & MASK; - - x = _buffer[i]; - - _index = i; - } -}; - -// -// N-1 sample delay (stereo) -// -template -class StereoDelay { - - static_assert((N & (N - 1)) == 0, "N must be a power of 2"); - - float _buffer[2*N] = {}; - size_t _index = 0; - -public: - void process(float& x0, float& x1) { - - const size_t MASK = 2*N - 1; // buffer wrap - size_t i = _index; - - _buffer[i+0] = x0; - _buffer[i+1] = x1; - - i = (i + 2*(N - 1)) & MASK; - - x0 = _buffer[i+0]; - x1 = _buffer[i+1]; - - _index = i; - } -}; - -// -// N-1 sample delay (quad) -// -template -class QuadDelay { - - static_assert((N & (N - 1)) == 0, "N must be a power of 2"); - - float _buffer[4*N] = {}; - size_t _index = 0; - -public: - void process(float& x0, float& x1, float& x2, float& x3) { - - const size_t MASK = 4*N - 1; // buffer wrap - size_t i = _index; - - _buffer[i+0] = x0; - _buffer[i+1] = x1; - _buffer[i+2] = x2; - _buffer[i+3] = x3; - - i = (i + 4*(N - 1)) & MASK; - - x0 = _buffer[i+0]; - x1 = _buffer[i+1]; - x2 = _buffer[i+2]; - x3 = _buffer[i+3]; - - _index = i; - } -}; - // // Limiter (common) // @@ -637,7 +196,7 @@ int32_t LimiterImpl::envelope(int32_t attn) { template class LimiterMono : public LimiterImpl { - PeakFilter _filter; + MinFilter _filter; MonoDelay _delay; public: @@ -688,7 +247,7 @@ void LimiterMono::process(float* input, int16_t* output, int numFrames) { template class LimiterStereo : public LimiterImpl { - PeakFilter _filter; + MinFilter _filter; StereoDelay _delay; public: @@ -745,7 +304,7 @@ void LimiterStereo::process(float* input, int16_t* output, int numFrames) { template class LimiterQuad : public LimiterImpl { - PeakFilter _filter; + MinFilter _filter; QuadDelay _delay; public: diff --git a/libraries/audio/src/AudioLimiter.h b/libraries/audio/src/AudioLimiter.h index 96bfd610c2..2d4881869a 100644 --- a/libraries/audio/src/AudioLimiter.h +++ b/libraries/audio/src/AudioLimiter.h @@ -9,7 +9,7 @@ #ifndef hifi_AudioLimiter_h #define hifi_AudioLimiter_h -#include "stdint.h" +#include class LimiterImpl; From ef556fae9b8292309bf88e5f60afe6e851d97af3 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 18 May 2017 14:13:33 -0700 Subject: [PATCH 05/49] when an entity's parent wasn't known and then becomes known, patch up the rigid-body and the render-item bounds --- libraries/entities/src/EntityItem.cpp | 2 +- libraries/entities/src/EntityItem.h | 3 ++- libraries/entities/src/EntityTree.cpp | 3 +++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index c9a26a9f4d..43ba881432 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -1624,7 +1624,7 @@ void EntityItem::updateParentID(const QUuid& value) { setParentID(value); // children are forced to be kinematic // may need to not collide with own avatar - markDirtyFlags(Simulation::DIRTY_MOTION_TYPE | Simulation::DIRTY_COLLISION_GROUP); + markDirtyFlags(Simulation::DIRTY_MOTION_TYPE | Simulation::DIRTY_COLLISION_GROUP | Simulation::DIRTY_POSITION); } } diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 1896893b52..73107a4b42 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -475,6 +475,8 @@ public: virtual bool getMeshes(MeshProxyList& result) { return true; } + virtual void locationChanged(bool tellPhysics = true) override; + protected: void setSimulated(bool simulated) { _simulated = simulated; } @@ -482,7 +484,6 @@ protected: const QByteArray getDynamicDataInternal() const; void setDynamicDataInternal(QByteArray dynamicData); - virtual void locationChanged(bool tellPhysics = true) override; virtual void dimensionsChanged() override; EntityTypes::EntityType _type; diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 7d353f69a1..820b3d279e 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -1249,6 +1249,9 @@ void EntityTree::fixupMissingParents() { if (entity->isParentIDValid()) { // this entity's parent was previously not known, and now is. Update its location in the EntityTree... doMove = true; + // the bounds on the render-item may need to be updated, the rigid body in the physics engine may + // need to be moved. + entity->locationChanged(true); } else if (getIsServer() && _avatarIDs.contains(entity->getParentID())) { // this is a child of an avatar, which the entity server will never have // a SpatiallyNestable object for. Add it to a list for cleanup when the avatar leaves. From 106dd6ce6a1e55f104d90ab85946a63008a50f7e Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Thu, 18 May 2017 14:15:10 -0700 Subject: [PATCH 06/49] Fast fixed-point log2 approximation --- libraries/audio/src/AudioDynamics.h | 63 +++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/libraries/audio/src/AudioDynamics.h b/libraries/audio/src/AudioDynamics.h index 2306fecf24..4c2f890c67 100644 --- a/libraries/audio/src/AudioDynamics.h +++ b/libraries/audio/src/AudioDynamics.h @@ -252,6 +252,69 @@ static inline int32_t peaklog2(float* input0, float* input1, float* input2, floa return (e << LOG2_FRACBITS) - (c2 >> 3); } +// +// Count Leading Zeros +// Emulates the CLZ (ARM) and LZCNT (x86) instruction +// +static inline int CLZ(uint32_t x) { + + if (x == 0) { + return 32; + } + + int e = 0; + if (x < 0x00010000) { + x <<= 16; + e += 16; + } + if (x < 0x01000000) { + x <<= 8; + e += 8; + } + if (x < 0x10000000) { + x <<= 4; + e += 4; + } + if (x < 0x40000000) { + x <<= 2; + e += 2; + } + if (x < 0x80000000) { + e += 1; + } + return e; +} + +// +// Compute -log2(x) for x=[0,1] in Q31, result in Q26 +// x = 0 returns 0x7fffffff +// x < 0 undefined +// +static inline int32_t fixlog2(int32_t x) { + + if (x == 0) { + return 0x7fffffff; + } + + // split into e and x - 1.0 + int e = CLZ((uint32_t)x); + x <<= e; // normalize to [0x80000000, 0xffffffff] + x &= 0x7fffffff; // x - 1.0 + + int k = x >> (31 - LOG2_TABBITS); + + // polynomial for log2(1+x) over x=[0,1] + int32_t c0 = log2Table[k][0]; + int32_t c1 = log2Table[k][1]; + int32_t c2 = log2Table[k][2]; + + c1 += MULHI(c0, x); + c2 += MULHI(c1, x); + + // reconstruct result in Q26 + return (e << LOG2_FRACBITS) - (c2 >> 3); +} + // // Compute exp2(-x) for x=[0,32] in Q26, result in Q31 // x < 0 undefined From c1cd26b47329cf1d3e82aa7a8e1e9453d3e54793 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Thu, 18 May 2017 14:21:56 -0700 Subject: [PATCH 07/49] New log-domain noise gate with adaptive threshold --- libraries/audio/src/AudioGate.cpp | 686 ++++++++++++++++++++++++++++++ libraries/audio/src/AudioGate.h | 31 ++ 2 files changed, 717 insertions(+) create mode 100644 libraries/audio/src/AudioGate.cpp create mode 100644 libraries/audio/src/AudioGate.h diff --git a/libraries/audio/src/AudioGate.cpp b/libraries/audio/src/AudioGate.cpp new file mode 100644 index 0000000000..15574f64f2 --- /dev/null +++ b/libraries/audio/src/AudioGate.cpp @@ -0,0 +1,686 @@ +// +// AudioGate.cpp +// libraries/audio/src +// +// Created by Ken Cooke on 5/5/17. +// Copyright 2017 High Fidelity, Inc. +// + +#include +#include + +#include "AudioDynamics.h" +#include "AudioGate.h" + +//#define ENABLE_GRAPH_WINDOW +#ifdef ENABLE_GRAPH_WINDOW +int GraphInit(int nBins, int delay); +void GraphDraw(int *data, int threshold, int peak, bool flag); +void GraphClose(void); +int initDone = GraphInit(256, 1); +#endif + +// log2 domain headroom bits above 0dB (int32_t) +static const int LOG2_HEADROOM_Q30 = 1; + +// convert Q30 to Q15 with saturation +static inline int32_t saturateQ30(int32_t x) { + + x = (x + (1 << 14)) >> 15; + x = MIN(MAX(x, -32768), 32767); + + return x; +} + +// +// First-order DC-blocking filter, with zero at 1.0 and pole at 0.99994 +// +// -3dB @ 0.5 Hz (48KHz) +// -3dB @ 0.2 Hz (24KHz) +// +// input in Q15, output in Q30 +// +class MonoDCBlock { + + int32_t _dcOffset = {}; // Q30, cannot overflow + +public: + void process(int32_t& x) { + + x <<= 15; // scale to Q30 + x -= _dcOffset; // remove DC + _dcOffset += x >> 14; // pole = (1.0 - 2^-14) = 0.99994 + } +}; + +class StereoDCBlock { + + int32_t _dcOffset[2] = {}; + +public: + void process(int32_t& x0, int32_t& x1) { + + x0 <<= 15; + x1 <<= 15; + + x0 -= _dcOffset[0]; + x1 -= _dcOffset[1]; + + _dcOffset[0] += x0 >> 14; + _dcOffset[1] += x1 >> 14; + } +}; + +class QuadDCBlock { + + int32_t _dcOffset[4] = {}; + +public: + void process(int32_t& x0, int32_t& x1, int32_t& x2, int32_t& x3) { + + x0 <<= 15; + x1 <<= 15; + x2 <<= 15; + x3 <<= 15; + + x0 -= _dcOffset[0]; + x1 -= _dcOffset[1]; + x2 -= _dcOffset[2]; + x3 -= _dcOffset[3]; + + _dcOffset[0] += x0 >> 14; + _dcOffset[1] += x1 >> 14; + _dcOffset[2] += x2 >> 14; + _dcOffset[3] += x3 >> 14; + } +}; + +// +// Gate (common) +// +class GateImpl { +protected: + + // histogram + static const int NHIST = 256; + int _update[NHIST] = {}; + int _histogram[NHIST] = {}; + + // peakhold + int32_t _holdMin = 0x7fffffff; + int32_t _holdInc = 0x7fffffff; + uint32_t _holdMax = 0x7fffffff; + int32_t _holdRel = 0x7fffffff; + int32_t _holdPeak = 0x7fffffff; + + // hysteresis + int32_t _hysteresis = 0; + int32_t _hystOffset = 0; + int32_t _hystPeak = 0x7fffffff; + + int32_t _release = 0x7fffffff; + + int32_t _threshFixed = 0; + int32_t _threshAdapt = 0; + int32_t _attn = 0x7fffffff; + + int _sampleRate; + +public: + GateImpl(int sampleRate); + virtual ~GateImpl() {} + + void setThreshold(float threshold); + void setHold(float hold); + void setHysteresis(float hysteresis); + void setRelease(float release); + + void clearHistogram() { memset(_update, 0, sizeof(_update)); } + void updateHistogram(int32_t value, int count); + int partitionHistogram(); + void processHistogram(int numFrames); + + int32_t peakhold(int32_t peak); + int32_t hysteresis(int32_t peak); + int32_t envelope(int32_t attn); + + virtual void process(int16_t* input, int16_t* output, int numFrames) = 0; +}; + +GateImpl::GateImpl(int sampleRate) { + + sampleRate = MAX(sampleRate, 8000); + sampleRate = MIN(sampleRate, 96000); + _sampleRate = sampleRate; + + // defaults + setThreshold(-36.0); + setHold(20.0); + setHysteresis(6.0); + setRelease(1000.0); +} + +// +// Set the gate threshold (dB) +// This is a base value that is modulated by the adaptive threshold algorithm. +// +void GateImpl::setThreshold(float threshold) { + + // gate threshold = -96dB to 0dB + threshold = MAX(threshold, -96.0f); + threshold = MIN(threshold, 0.0f); + + // gate threshold in log2 domain + _threshFixed = (int32_t)(-(double)threshold * DB_TO_LOG2 * (1 << LOG2_FRACBITS)); + _threshFixed += LOG2_HEADROOM_Q30 << LOG2_FRACBITS; + + _threshAdapt = _threshFixed; +} + +// +// Set the detector hold time (milliseconds) +// +void GateImpl::setHold(float hold) { + + const double RELEASE = 100.0; // release = 100ms + const double PROGHOLD = 0.100; // progressive hold = 100ms + + // pure hold = 1 to 1000ms + hold = MAX(hold, 1.0f); + hold = MIN(hold, 1000.0f); + + _holdMin = msToTc(RELEASE, _sampleRate); + + _holdInc = (int32_t)((_holdMin - 0x7fffffff) / (PROGHOLD * _sampleRate)); + _holdInc = MIN(_holdInc, -1); // prevent 0 on long releases + + _holdMax = 0x7fffffff - (uint32_t)(_holdInc * hold/1000.0 * _sampleRate); +} + +// +// Set the detector hysteresis (dB) +// +void GateImpl::setHysteresis(float hysteresis) { + + // gate hysteresis in log2 domain + _hysteresis = (int32_t)(hysteresis * DB_TO_LOG2 * (1 << LOG2_FRACBITS)); +} + +// +// Set the gate release time (milliseconds) +// +void GateImpl::setRelease(float release) { + + // gate release = 50 to 5000ms + release = MAX(release, 50.0f); + release = MIN(release, 5000.0f); + + _release = msToTc((double)release, _sampleRate); +} + +// +// Update the histogram count of the bin which contains value +// +void GateImpl::updateHistogram(int32_t value, int count = 1) { + + // quantize to LOG2 + 3 fraction bits (0.75dB steps) + int index = (NHIST-1) - (value >> (LOG2_FRACBITS - 3)); + + assert(index >= 0); + assert(index < NHIST); + + _update[index] += count << 16; // Q16 for filtering + + assert(_update[index] >= 0); +} + +// +// Partition the histogram +// +// The idea behind the adaptive threshold: +// +// When processing a gaussian mixture of signal and noise, separated by minimal SNR, +// a bimodal distribution emerges in the histogram of preprocessed peak levels. +// In this case, the threshold adapts toward the level that optimally partitions the distributions. +// Partitioning is computed using Otsu's method. +// +// When only a single distribution is present, the threshold becomes level-dependent: +// At levels below the fixed threshold, the threshold adapts toward the upper edge +// of the distribution, presumed to be noise. +// At levels above the fixed threshold, he threshold adapts toward the lower edge +// of the distribution, presumed to be signal. +// This is implemented by adding a hidden (bias) distribution at the fixed threshold. +// +int GateImpl::partitionHistogram() { + + // initialize + int total = 0; + float sum = 0.0f; + for (int i = 0; i < NHIST; i++) { + total += _histogram[i]; + sum += (float)i * _histogram[i]; + } + + int w0 = 0; + float sum0 = 0.0f; + float max = 0.0f; + int index = 0; + + // find the index that maximizes the between-class variance + for (int i = 0 ; i < NHIST; i++) { + + // update weights + w0 += _histogram[i]; + int w1 = total - w0; + + if (w0 == 0) { + continue; // skip leading zeros + } + if (w1 == 0) { + break; // skip trailing zeros + } + + // update means + sum0 += (float)i * _histogram[i]; + float sum1 = sum - sum0; + + float m0 = sum0 / (float)w0; + float m1 = sum1 / (float)w1; + + // between-class variance + float variance = (float)w0 * (float)w1 * (m0 - m1) * (m0 - m1); + + // update threshold + if (variance > max) { + max = variance; + index = i; + } + } + return index; +} + +// +// Process the histogram to update the adaptive threshold +// +void GateImpl::processHistogram(int numFrames) { + + const int32_t LOG2E_Q26 = (int32_t)(log2(exp(1.0)) * (1 << LOG2_FRACBITS) + 0.5); + + // compute time constants, for sampleRate downsampled by numFrames + int32_t tcHistogram = fixexp2(MULDIV64(numFrames, LOG2E_Q26, _sampleRate * 10)); // 10 seconds + int32_t tcThreshold = fixexp2(MULDIV64(numFrames, LOG2E_Q26, _sampleRate * 1)); // 1 second + + // add bias at the fixed threshold + updateHistogram(_threshFixed, (numFrames+7)/8); + + // leaky integrate into long-term histogram + for (int i = 0; i < NHIST; i++) { + _histogram[i] = _update[i] + MULQ31((_histogram[i] - _update[i]), tcHistogram); + } + + // compute new threshold + int index = partitionHistogram(); + int32_t threshold = ((NHIST-1) - index) << (LOG2_FRACBITS - 3); + + // smooth threshold update + _threshAdapt = threshold + MULQ31((_threshAdapt - threshold), tcThreshold); + +#ifdef ENABLE_GRAPH_WINDOW + int peakIndex = (NHIST-1) - (_holdPeak >> (LOG2_FRACBITS - 3)); + GraphDraw(_histogram, index, peakIndex, _attn == 0x0); +#endif + //printf("threshold = %0.1f\n", (_threshAdapt - (LOG2_HEADROOM_Q15 << LOG2_FRACBITS)) * -6.02f / (1 << LOG2_FRACBITS)); +} + +// +// Gate detector peakhold +// +int32_t GateImpl::peakhold(int32_t peak) { + + if (peak > _holdPeak) { + + // RELEASE + // 3-stage progressive hold + // + // (_holdRel > 0x7fffffff) pure hold + // (_holdRel > _holdMin) progressive hold + // (_holdRel = _holdMin) release + + _holdRel += _holdInc; // update progressive hold + _holdRel = MAX((uint32_t)_holdRel, (uint32_t)_holdMin); // saturate at final value + + int32_t tc = MIN((uint32_t)_holdRel, 0x7fffffff); + peak += MULQ31((_holdPeak - peak), tc); // apply release + + } else { + + // ATTACK + _holdRel = _holdMax; // reset release + } + _holdPeak = peak; + + return peak; +} + +// +// Gate hysteresis +// Implemented as detector hysteresis instead of high/low thresholds, to simplify adaptive threshold. +// +int32_t GateImpl::hysteresis(int32_t peak) { + + // by construction, cannot overflow/underflow + assert((double)_hystOffset + (peak - _hystPeak) <= +(double)0x7fffffff); + assert((double)_hystOffset + (peak - _hystPeak) >= -(double)0x80000000); + + // accumulate the offset, with saturation + _hystOffset += peak - _hystPeak; + _hystOffset = MIN(MAX(_hystOffset, 0), _hysteresis); + + _hystPeak = peak; + peak -= _hystOffset; // apply hysteresis + + assert(peak >= 0); + return peak; +} + +// +// Gate envelope processing +// zero attack, fixed release +// +int32_t GateImpl::envelope(int32_t attn) { + + if (attn > _attn) { + attn += MULQ31((_attn - attn), _release); // apply release + } + _attn = attn; + + return attn; +} + +// +// Gate (mono) +// +template +class GateMono : public GateImpl { + + MonoDCBlock _dc; + MaxFilter _filter; + MonoDelay _delay; + +public: + GateMono(int sampleRate) : GateImpl(sampleRate) {} + + // mono input/output (in-place is allowed) + void process(int16_t* input, int16_t* output, int numFrames) override; +}; + +template +void GateMono::process(int16_t* input, int16_t* output, int numFrames) { + + clearHistogram(); + + for (int n = 0; n < numFrames; n++) { + + int32_t x = input[n]; + + // remove DC + _dc.process(x); + + // peak detect + int32_t peak = abs(x); + + // convert to log2 domain + peak = fixlog2(peak); + + // apply peak hold + peak = peakhold(peak); + + // count peak level + updateHistogram(peak); + + // apply hysteresis + peak = hysteresis(peak); + + // compute gate attenuation + int32_t attn = (peak > _threshAdapt) ? 0x7fffffff : 0; // hard-knee, 1:inf ratio + + // apply envelope + attn = envelope(attn); + + // convert from log2 domain + attn = fixexp2(attn); + + // lowpass filter + attn = _filter.process(attn); + + // delay audio + _delay.process(x); + + // apply gain + x = MULQ31(x, attn); + + // store 16-bit output + output[n] = (int16_t)saturateQ30(x); + } + + // update adaptive threshold + processHistogram(numFrames); +} + +// +// Gate (stereo) +// +template +class GateStereo : public GateImpl { + + StereoDCBlock _dc; + MaxFilter _filter; + StereoDelay _delay; + +public: + GateStereo(int sampleRate) : GateImpl(sampleRate) {} + + // interleaved stereo input/output (in-place is allowed) + void process(int16_t* input, int16_t* output, int numFrames) override; +}; + +template +void GateStereo::process(int16_t* input, int16_t* output, int numFrames) { + + clearHistogram(); + + for (int n = 0; n < numFrames; n++) { + + int32_t x0 = input[2*n+0]; + int32_t x1 = input[2*n+1]; + + // remove DC + _dc.process(x0, x1); + + // peak detect + int32_t peak = MAX(abs(x0), abs(x1)); + + // convert to log2 domain + peak = fixlog2(peak); + + // apply peak hold + peak = peakhold(peak); + + // count peak level + updateHistogram(peak); + + // apply hysteresis + peak = hysteresis(peak); + + // compute gate attenuation + int32_t attn = (peak > _threshAdapt) ? 0x7fffffff : 0; // hard-knee, 1:inf ratio + + // apply envelope + attn = envelope(attn); + + // convert from log2 domain + attn = fixexp2(attn); + + // lowpass filter + attn = _filter.process(attn); + + // delay audio + _delay.process(x0, x1); + + // apply gain + x0 = MULQ31(x0, attn); + x1 = MULQ31(x1, attn); + + // store 16-bit output + output[2*n+0] = (int16_t)saturateQ30(x0); + output[2*n+1] = (int16_t)saturateQ30(x1); + } + + // update adaptive threshold + processHistogram(numFrames); +} + +// +// Gate (quad) +// +template +class GateQuad : public GateImpl { + + QuadDCBlock _dc; + MaxFilter _filter; + QuadDelay _delay; + +public: + GateQuad(int sampleRate) : GateImpl(sampleRate) {} + + // interleaved quad input/output (in-place is allowed) + void process(int16_t* input, int16_t* output, int numFrames) override; +}; + +template +void GateQuad::process(int16_t* input, int16_t* output, int numFrames) { + + clearHistogram(); + + for (int n = 0; n < numFrames; n++) { + + int32_t x0 = input[4*n+0]; + int32_t x1 = input[4*n+1]; + int32_t x2 = input[4*n+2]; + int32_t x3 = input[4*n+3]; + + // remove DC + _dc.process(x0, x1, x2, x3); + + // peak detect + int32_t peak = MAX(MAX(abs(x0), abs(x1)), MAX(abs(x2), abs(x3))); + + // convert to log2 domain + peak = fixlog2(peak); + + // apply peak hold + peak = peakhold(peak); + + // count peak level + updateHistogram(peak); + + // apply hysteresis + peak = hysteresis(peak); + + // compute gate attenuation + int32_t attn = (peak > _threshAdapt) ? 0x7fffffff : 0; // hard-knee, 1:inf ratio + + // apply envelope + attn = envelope(attn); + + // convert from log2 domain + attn = fixexp2(attn); + + // lowpass filter + attn = _filter.process(attn); + + // delay audio + _delay.process(x0, x1, x2, x3); + + // apply gain + x0 = MULQ31(x0, attn); + x1 = MULQ31(x1, attn); + x2 = MULQ31(x2, attn); + x3 = MULQ31(x3, attn); + + // store 16-bit output + output[4*n+0] = (int16_t)saturateQ30(x0); + output[4*n+1] = (int16_t)saturateQ30(x1); + output[4*n+2] = (int16_t)saturateQ30(x2); + output[4*n+3] = (int16_t)saturateQ30(x3); + } + + // update adaptive threshold + processHistogram(numFrames); +} + +// +// Public API +// + +AudioGate::AudioGate(int sampleRate, int numChannels) { + + if (numChannels == 1) { + + // ~3ms lookahead for all rates + if (sampleRate < 16000) { + _impl = new GateMono<32>(sampleRate); + } else if (sampleRate < 32000) { + _impl = new GateMono<64>(sampleRate); + } else if (sampleRate < 64000) { + _impl = new GateMono<128>(sampleRate); + } else { + _impl = new GateMono<256>(sampleRate); + } + + } else if (numChannels == 2) { + + // ~3ms lookahead for all rates + if (sampleRate < 16000) { + _impl = new GateStereo<32>(sampleRate); + } else if (sampleRate < 32000) { + _impl = new GateStereo<64>(sampleRate); + } else if (sampleRate < 64000) { + _impl = new GateStereo<128>(sampleRate); + } else { + _impl = new GateStereo<256>(sampleRate); + } + + } else if (numChannels == 4) { + + // ~3ms lookahead for all rates + if (sampleRate < 16000) { + _impl = new GateQuad<32>(sampleRate); + } else if (sampleRate < 32000) { + _impl = new GateQuad<64>(sampleRate); + } else if (sampleRate < 64000) { + _impl = new GateQuad<128>(sampleRate); + } else { + _impl = new GateQuad<256>(sampleRate); + } + + } else { + assert(0); // unsupported + } +} + +AudioGate::~AudioGate() { + delete _impl; +} + +void AudioGate::render(int16_t* input, int16_t* output, int numFrames) { + _impl->process(input, output, numFrames); +} + +void AudioGate::setThreshold(float threshold) { + _impl->setThreshold(threshold); +} + +void AudioGate::setRelease(float release) { + _impl->setRelease(release); +} diff --git a/libraries/audio/src/AudioGate.h b/libraries/audio/src/AudioGate.h new file mode 100644 index 0000000000..527de17eb4 --- /dev/null +++ b/libraries/audio/src/AudioGate.h @@ -0,0 +1,31 @@ +// +// AudioGate.h +// libraries/audio/src +// +// Created by Ken Cooke on 5/5/17. +// Copyright 2016 High Fidelity, Inc. +// + +#ifndef hifi_AudioGate_h +#define hifi_AudioGate_h + +#include + +class GateImpl; + +class AudioGate { +public: + AudioGate(int sampleRate, int numChannels); + ~AudioGate(); + + // interleaved int16_t input/output (in-place is allowed) + void render(int16_t* input, int16_t* output, int numFrames); + + void setThreshold(float threshold); + void setRelease(float release); + +private: + GateImpl* _impl; +}; + +#endif // hifi_AudioGate_h From 6f2a84a08494ed5df8bd29c627f22cc5268b8c02 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Thu, 18 May 2017 14:26:35 -0700 Subject: [PATCH 08/49] Add stand-alone DC blocking filter --- libraries/audio/src/AudioGate.cpp | 61 +++++++++++++++++++++++++++++++ libraries/audio/src/AudioGate.h | 1 + 2 files changed, 62 insertions(+) diff --git a/libraries/audio/src/AudioGate.cpp b/libraries/audio/src/AudioGate.cpp index 15574f64f2..0bed2ef2dc 100644 --- a/libraries/audio/src/AudioGate.cpp +++ b/libraries/audio/src/AudioGate.cpp @@ -145,6 +145,7 @@ public: int32_t envelope(int32_t attn); virtual void process(int16_t* input, int16_t* output, int numFrames) = 0; + virtual void removeDC(int16_t* input, int16_t* output, int numFrames) = 0; }; GateImpl::GateImpl(int sampleRate) { @@ -412,6 +413,7 @@ public: // mono input/output (in-place is allowed) void process(int16_t* input, int16_t* output, int numFrames) override; + void removeDC(int16_t* input, int16_t* output, int numFrames) override; }; template @@ -467,6 +469,21 @@ void GateMono::process(int16_t* input, int16_t* output, int numFrames) { processHistogram(numFrames); } +template +void GateMono::removeDC(int16_t* input, int16_t* output, int numFrames) { + + for (int n = 0; n < numFrames; n++) { + + int32_t x = input[n]; + + // remove DC + _dc.process(x); + + // store 16-bit output + output[n] = (int16_t)saturateQ30(x); + } +} + // // Gate (stereo) // @@ -482,6 +499,7 @@ public: // interleaved stereo input/output (in-place is allowed) void process(int16_t* input, int16_t* output, int numFrames) override; + void removeDC(int16_t* input, int16_t* output, int numFrames) override; }; template @@ -540,6 +558,23 @@ void GateStereo::process(int16_t* input, int16_t* output, int numFrames) { processHistogram(numFrames); } +template +void GateStereo::removeDC(int16_t* input, int16_t* output, int numFrames) { + + for (int n = 0; n < numFrames; n++) { + + int32_t x0 = input[2*n+0]; + int32_t x1 = input[2*n+1]; + + // remove DC + _dc.process(x0, x1); + + // store 16-bit output + output[2*n+0] = (int16_t)saturateQ30(x0); + output[2*n+1] = (int16_t)saturateQ30(x1); + } +} + // // Gate (quad) // @@ -555,6 +590,7 @@ public: // interleaved quad input/output (in-place is allowed) void process(int16_t* input, int16_t* output, int numFrames) override; + void removeDC(int16_t* input, int16_t* output, int numFrames) override; }; template @@ -619,6 +655,27 @@ void GateQuad::process(int16_t* input, int16_t* output, int numFrames) { processHistogram(numFrames); } +template +void GateQuad::removeDC(int16_t* input, int16_t* output, int numFrames) { + + for (int n = 0; n < numFrames; n++) { + + int32_t x0 = input[4*n+0]; + int32_t x1 = input[4*n+1]; + int32_t x2 = input[4*n+2]; + int32_t x3 = input[4*n+3]; + + // remove DC + _dc.process(x0, x1, x2, x3); + + // store 16-bit output + output[4*n+0] = (int16_t)saturateQ30(x0); + output[4*n+1] = (int16_t)saturateQ30(x1); + output[4*n+2] = (int16_t)saturateQ30(x2); + output[4*n+3] = (int16_t)saturateQ30(x3); + } +} + // // Public API // @@ -677,6 +734,10 @@ void AudioGate::render(int16_t* input, int16_t* output, int numFrames) { _impl->process(input, output, numFrames); } +void AudioGate::removeDC(int16_t* input, int16_t* output, int numFrames) { + _impl->removeDC(input, output, numFrames); +} + void AudioGate::setThreshold(float threshold) { _impl->setThreshold(threshold); } diff --git a/libraries/audio/src/AudioGate.h b/libraries/audio/src/AudioGate.h index 527de17eb4..d4ae3c5fe8 100644 --- a/libraries/audio/src/AudioGate.h +++ b/libraries/audio/src/AudioGate.h @@ -20,6 +20,7 @@ public: // interleaved int16_t input/output (in-place is allowed) void render(int16_t* input, int16_t* output, int numFrames); + void removeDC(int16_t* input, int16_t* output, int numFrames); void setThreshold(float threshold); void setRelease(float release); From 18ed3dd6ed787e5dd35456900ef248a64a639912 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Thu, 18 May 2017 14:30:25 -0700 Subject: [PATCH 09/49] Remove debug code --- libraries/audio/src/AudioGate.cpp | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/libraries/audio/src/AudioGate.cpp b/libraries/audio/src/AudioGate.cpp index 0bed2ef2dc..cd49b90245 100644 --- a/libraries/audio/src/AudioGate.cpp +++ b/libraries/audio/src/AudioGate.cpp @@ -12,14 +12,6 @@ #include "AudioDynamics.h" #include "AudioGate.h" -//#define ENABLE_GRAPH_WINDOW -#ifdef ENABLE_GRAPH_WINDOW -int GraphInit(int nBins, int delay); -void GraphDraw(int *data, int threshold, int peak, bool flag); -void GraphClose(void); -int initDone = GraphInit(256, 1); -#endif - // log2 domain headroom bits above 0dB (int32_t) static const int LOG2_HEADROOM_Q30 = 1; @@ -326,10 +318,6 @@ void GateImpl::processHistogram(int numFrames) { // smooth threshold update _threshAdapt = threshold + MULQ31((_threshAdapt - threshold), tcThreshold); -#ifdef ENABLE_GRAPH_WINDOW - int peakIndex = (NHIST-1) - (_holdPeak >> (LOG2_FRACBITS - 3)); - GraphDraw(_histogram, index, peakIndex, _attn == 0x0); -#endif //printf("threshold = %0.1f\n", (_threshAdapt - (LOG2_HEADROOM_Q15 << LOG2_FRACBITS)) * -6.02f / (1 << LOG2_FRACBITS)); } From 1b0eeb9c2c8cc01a1e87a343cb205451fc0f383d Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 18 May 2017 14:35:39 -0700 Subject: [PATCH 10/49] try harder to put things in place when their parent entities are discovered --- libraries/entities/src/EntityItem.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 43ba881432..7f7c8b63cb 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -1616,6 +1616,7 @@ void EntityItem::updatePosition(const glm::vec3& value) { entity->markDirtyFlags(Simulation::DIRTY_POSITION); } }); + locationChanged(); } } @@ -1625,6 +1626,13 @@ void EntityItem::updateParentID(const QUuid& value) { // children are forced to be kinematic // may need to not collide with own avatar markDirtyFlags(Simulation::DIRTY_MOTION_TYPE | Simulation::DIRTY_COLLISION_GROUP | Simulation::DIRTY_POSITION); + forEachDescendant([&](SpatiallyNestablePointer object) { + if (object->getNestableType() == NestableType::Entity) { + EntityItemPointer entity = std::static_pointer_cast(object); + entity->markDirtyFlags(Simulation::DIRTY_POSITION); + } + }); + locationChanged(); } } From 4007133423cf3a342d11a3ef9cdb1e7c826ce533 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 18 May 2017 14:59:26 -0700 Subject: [PATCH 11/49] collision hulls are still not always in the right place --- libraries/entities/src/EntityTree.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 820b3d279e..d85bdc11bf 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -1251,6 +1251,15 @@ void EntityTree::fixupMissingParents() { doMove = true; // the bounds on the render-item may need to be updated, the rigid body in the physics engine may // need to be moved. + entity->markDirtyFlags(Simulation::DIRTY_MOTION_TYPE | + Simulation::DIRTY_COLLISION_GROUP | + Simulation::DIRTY_POSITION); + entity->forEachDescendant([&](SpatiallyNestablePointer object) { + if (object->getNestableType() == NestableType::Entity) { + EntityItemPointer entity = std::static_pointer_cast(object); + entity->markDirtyFlags(Simulation::DIRTY_POSITION); + } + }); entity->locationChanged(true); } else if (getIsServer() && _avatarIDs.contains(entity->getParentID())) { // this is a child of an avatar, which the entity server will never have From 205b9d4aec60c60f91ae9f38c8fb9ad9b67eadcf Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Thu, 18 May 2017 16:49:02 -0700 Subject: [PATCH 12/49] Fix compiler warnings --- libraries/audio/src/AudioDynamics.h | 4 ++-- libraries/audio/src/AudioGate.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/libraries/audio/src/AudioDynamics.h b/libraries/audio/src/AudioDynamics.h index 4c2f890c67..08daa21348 100644 --- a/libraries/audio/src/AudioDynamics.h +++ b/libraries/audio/src/AudioDynamics.h @@ -56,12 +56,12 @@ static const double FIXQ31 = 2147483648.0; // convert float to Q31 static const double DB_TO_LOG2 = 0.16609640474436813; // convert dB to log2 // convert dB to amplitude -static double dBToGain(double dB) { +static inline double dBToGain(double dB) { return pow(10.0, dB / 20.0); } // convert milliseconds to first-order time constant -static int32_t msToTc(double ms, double sampleRate) { +static inline int32_t msToTc(double ms, double sampleRate) { double tc = exp(-1000.0 / (ms * sampleRate)); return (int32_t)(FIXQ31 * tc); // Q31 } diff --git a/libraries/audio/src/AudioGate.cpp b/libraries/audio/src/AudioGate.cpp index cd49b90245..737e2359f1 100644 --- a/libraries/audio/src/AudioGate.cpp +++ b/libraries/audio/src/AudioGate.cpp @@ -187,7 +187,7 @@ void GateImpl::setHold(float hold) { _holdInc = (int32_t)((_holdMin - 0x7fffffff) / (PROGHOLD * _sampleRate)); _holdInc = MIN(_holdInc, -1); // prevent 0 on long releases - _holdMax = 0x7fffffff - (uint32_t)(_holdInc * hold/1000.0 * _sampleRate); + _holdMax = 0x7fffffff - (uint32_t)(_holdInc * (double)hold/1000.0 * _sampleRate); } // @@ -196,7 +196,7 @@ void GateImpl::setHold(float hold) { void GateImpl::setHysteresis(float hysteresis) { // gate hysteresis in log2 domain - _hysteresis = (int32_t)(hysteresis * DB_TO_LOG2 * (1 << LOG2_FRACBITS)); + _hysteresis = (int32_t)((double)hysteresis * DB_TO_LOG2 * (1 << LOG2_FRACBITS)); } // From 746b41e50946b570f39f15f494ff73e638e8617c Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Thu, 18 May 2017 17:30:07 -0700 Subject: [PATCH 13/49] Fix typo in comments --- libraries/audio/src/AudioGate.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/audio/src/AudioGate.cpp b/libraries/audio/src/AudioGate.cpp index 737e2359f1..0afeee19be 100644 --- a/libraries/audio/src/AudioGate.cpp +++ b/libraries/audio/src/AudioGate.cpp @@ -240,7 +240,7 @@ void GateImpl::updateHistogram(int32_t value, int count = 1) { // When only a single distribution is present, the threshold becomes level-dependent: // At levels below the fixed threshold, the threshold adapts toward the upper edge // of the distribution, presumed to be noise. -// At levels above the fixed threshold, he threshold adapts toward the lower edge +// At levels above the fixed threshold, the threshold adapts toward the lower edge // of the distribution, presumed to be signal. // This is implemented by adding a hidden (bias) distribution at the fixed threshold. // From 0c12baa2584f101c279b5b019dd3f367451c7ae0 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 19 May 2017 09:57:42 -0700 Subject: [PATCH 14/49] call entityChanged after settings flags --- libraries/entities/src/EntityItem.cpp | 16 ++++++++++++++++ libraries/entities/src/EntityTree.cpp | 6 ++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 7f7c8b63cb..b9ef606867 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -1609,11 +1609,19 @@ void EntityItem::updateRegistrationPoint(const glm::vec3& value) { void EntityItem::updatePosition(const glm::vec3& value) { if (getLocalPosition() != value) { setLocalPosition(value); + + EntityTreePointer tree = getTree(); + if (!tree) { + return; + } + markDirtyFlags(Simulation::DIRTY_POSITION); + tree->entityChanged(getThisPointer()); forEachDescendant([&](SpatiallyNestablePointer object) { if (object->getNestableType() == NestableType::Entity) { EntityItemPointer entity = std::static_pointer_cast(object); entity->markDirtyFlags(Simulation::DIRTY_POSITION); + tree->entityChanged(entity); } }); locationChanged(); @@ -1623,13 +1631,21 @@ void EntityItem::updatePosition(const glm::vec3& value) { void EntityItem::updateParentID(const QUuid& value) { if (getParentID() != value) { setParentID(value); + + EntityTreePointer tree = getTree(); + if (!tree) { + return; + } + // children are forced to be kinematic // may need to not collide with own avatar markDirtyFlags(Simulation::DIRTY_MOTION_TYPE | Simulation::DIRTY_COLLISION_GROUP | Simulation::DIRTY_POSITION); + tree->entityChanged(getThisPointer()); forEachDescendant([&](SpatiallyNestablePointer object) { if (object->getNestableType() == NestableType::Entity) { EntityItemPointer entity = std::static_pointer_cast(object); entity->markDirtyFlags(Simulation::DIRTY_POSITION); + tree->entityChanged(entity); } }); locationChanged(); diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index d85bdc11bf..4f9320b931 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -1254,10 +1254,12 @@ void EntityTree::fixupMissingParents() { entity->markDirtyFlags(Simulation::DIRTY_MOTION_TYPE | Simulation::DIRTY_COLLISION_GROUP | Simulation::DIRTY_POSITION); + entityChanged(entity); entity->forEachDescendant([&](SpatiallyNestablePointer object) { if (object->getNestableType() == NestableType::Entity) { - EntityItemPointer entity = std::static_pointer_cast(object); - entity->markDirtyFlags(Simulation::DIRTY_POSITION); + EntityItemPointer descendantEntity = std::static_pointer_cast(object); + descendantEntity->markDirtyFlags(Simulation::DIRTY_POSITION); + entityChanged(descendantEntity); } }); entity->locationChanged(true); From 77ce8a19cf6a0df3824bdd1078f6e9921a4ad92f Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 19 May 2017 10:11:02 -0700 Subject: [PATCH 15/49] call entityChanged after settings flags --- libraries/entities/src/EntityItem.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index b9ef606867..873bc6f33b 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -1644,7 +1644,9 @@ void EntityItem::updateParentID(const QUuid& value) { forEachDescendant([&](SpatiallyNestablePointer object) { if (object->getNestableType() == NestableType::Entity) { EntityItemPointer entity = std::static_pointer_cast(object); - entity->markDirtyFlags(Simulation::DIRTY_POSITION); + entity->markDirtyFlags(Simulation::DIRTY_MOTION_TYPE | + Simulation::DIRTY_COLLISION_GROUP | + Simulation::DIRTY_POSITION); tree->entityChanged(entity); } }); From 7220fe0ad8493522cb4c9369c0c425f4ad7c8131 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 19 May 2017 10:40:24 -0700 Subject: [PATCH 16/49] set more physics flags dirty when an entity parent is found --- libraries/entities/src/EntityTree.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 4f9320b931..ebad27a063 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -1258,7 +1258,9 @@ void EntityTree::fixupMissingParents() { entity->forEachDescendant([&](SpatiallyNestablePointer object) { if (object->getNestableType() == NestableType::Entity) { EntityItemPointer descendantEntity = std::static_pointer_cast(object); - descendantEntity->markDirtyFlags(Simulation::DIRTY_POSITION); + descendantEntity->markDirtyFlags(Simulation::DIRTY_MOTION_TYPE | + Simulation::DIRTY_COLLISION_GROUP | + Simulation::DIRTY_POSITION); entityChanged(descendantEntity); } }); From 735e4b7d05ecc672827b96036bff2eadfd6937e6 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 19 May 2017 10:59:59 -0700 Subject: [PATCH 17/49] trying to get child collision hulls to be in the right place after import --- libraries/entities/src/EntityItem.cpp | 4 ++-- libraries/entities/src/EntityTree.cpp | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 873bc6f33b..44b0869d85 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -1639,14 +1639,14 @@ void EntityItem::updateParentID(const QUuid& value) { // children are forced to be kinematic // may need to not collide with own avatar - markDirtyFlags(Simulation::DIRTY_MOTION_TYPE | Simulation::DIRTY_COLLISION_GROUP | Simulation::DIRTY_POSITION); + markDirtyFlags(Simulation::DIRTY_MOTION_TYPE | Simulation::DIRTY_COLLISION_GROUP | Simulation::DIRTY_TRANSFORM); tree->entityChanged(getThisPointer()); forEachDescendant([&](SpatiallyNestablePointer object) { if (object->getNestableType() == NestableType::Entity) { EntityItemPointer entity = std::static_pointer_cast(object); entity->markDirtyFlags(Simulation::DIRTY_MOTION_TYPE | Simulation::DIRTY_COLLISION_GROUP | - Simulation::DIRTY_POSITION); + Simulation::DIRTY_TRANSFORM); tree->entityChanged(entity); } }); diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index ebad27a063..6f790bdd87 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -1253,14 +1253,14 @@ void EntityTree::fixupMissingParents() { // need to be moved. entity->markDirtyFlags(Simulation::DIRTY_MOTION_TYPE | Simulation::DIRTY_COLLISION_GROUP | - Simulation::DIRTY_POSITION); + Simulation::DIRTY_TRANSFORM); entityChanged(entity); entity->forEachDescendant([&](SpatiallyNestablePointer object) { if (object->getNestableType() == NestableType::Entity) { EntityItemPointer descendantEntity = std::static_pointer_cast(object); descendantEntity->markDirtyFlags(Simulation::DIRTY_MOTION_TYPE | Simulation::DIRTY_COLLISION_GROUP | - Simulation::DIRTY_POSITION); + Simulation::DIRTY_TRANSFORM); entityChanged(descendantEntity); } }); @@ -1620,6 +1620,10 @@ QVector EntityTree::sendEntities(EntityEditPacketSender* packetSen EntityItemID newID = i.value(); EntityItemPointer entity = localTree->findEntityByEntityItemID(newID); if (entity) { + if (!entity->getParentID().isNull()) { + QWriteLocker locker(&_missingParentLock); + _missingParent.append(entity); + } entity->forceQueryAACubeUpdate(); moveOperator.addEntityToMoveList(entity, entity->getQueryAACube()); i++; From 936d0e2d500b5e4b4020ebc9c4c8cfb2cf3f3d45 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 19 May 2017 12:13:43 -0700 Subject: [PATCH 18/49] be more careful about fixing up entities which arrived before their parents --- libraries/entities/src/EntityTree.cpp | 112 ++++++++++++-------------- 1 file changed, 50 insertions(+), 62 deletions(-) diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 6f790bdd87..84dc3844e3 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -122,7 +122,7 @@ void EntityTree::postAddEntity(EntityItemPointer entity) { _simulation->addEntity(entity); } - if (!entity->isParentIDValid()) { + if (!entity->getParentID().isNull()) { QWriteLocker locker(&_missingParentLock); _missingParent.append(entity); } @@ -1212,73 +1212,61 @@ void EntityTree::entityChanged(EntityItemPointer entity) { void EntityTree::fixupMissingParents() { MovingEntitiesOperator moveOperator(getThisPointer()); - QList missingParents; - { - QWriteLocker locker(&_missingParentLock); - QMutableVectorIterator iter(_missingParent); - while (iter.hasNext()) { - EntityItemWeakPointer entityWP = iter.next(); - EntityItemPointer entity = entityWP.lock(); - if (entity) { - if (entity->isParentIDValid()) { - iter.remove(); - } else { - missingParents.append(entity); - } - } else { - // entity was deleted before we found its parent. - iter.remove(); + QWriteLocker locker(&_missingParentLock); + QMutableVectorIterator iter(_missingParent); + while (iter.hasNext()) { + EntityItemWeakPointer entityWP = iter.next(); + EntityItemPointer entity = entityWP.lock(); + if (!entity) { + // entity was deleted before we found its parent + iter.remove(); + } + bool queryAACubeSuccess; + AACube newCube = entity->getQueryAACube(queryAACubeSuccess); + if (queryAACubeSuccess) { + // make sure queryAACube encompasses maxAACube + bool maxAACubeSuccess; + AACube maxAACube = entity->getMaximumAACube(maxAACubeSuccess); + if (maxAACubeSuccess && !newCube.contains(maxAACube)) { + newCube = maxAACube; } } - } - for (EntityItemPointer entity : missingParents) { - if (entity) { - bool queryAACubeSuccess; - AACube newCube = entity->getQueryAACube(queryAACubeSuccess); - if (queryAACubeSuccess) { - // make sure queryAACube encompasses maxAACube - bool maxAACubeSuccess; - AACube maxAACube = entity->getMaximumAACube(maxAACubeSuccess); - if (maxAACubeSuccess && !newCube.contains(maxAACube)) { - newCube = maxAACube; + bool doMove = false; + if (entity->isParentIDValid()) { + iter.remove(); // this entity is all hooked up; we can remove it from the list + // this entity's parent was previously not known, and now is. Update its location in the EntityTree... + doMove = true; + // the bounds on the render-item may need to be updated, the rigid body in the physics engine may + // need to be moved. + entity->markDirtyFlags(Simulation::DIRTY_MOTION_TYPE | + Simulation::DIRTY_COLLISION_GROUP | + Simulation::DIRTY_TRANSFORM); + entityChanged(entity); + entity->forEachDescendant([&](SpatiallyNestablePointer object) { + if (object->getNestableType() == NestableType::Entity) { + EntityItemPointer descendantEntity = std::static_pointer_cast(object); + descendantEntity->markDirtyFlags(Simulation::DIRTY_MOTION_TYPE | + Simulation::DIRTY_COLLISION_GROUP | + Simulation::DIRTY_TRANSFORM); + entityChanged(descendantEntity); } + }); + entity->locationChanged(true); + } else if (getIsServer() && _avatarIDs.contains(entity->getParentID())) { + // this is a child of an avatar, which the entity server will never have + // a SpatiallyNestable object for. Add it to a list for cleanup when the avatar leaves. + if (!_childrenOfAvatars.contains(entity->getParentID())) { + _childrenOfAvatars[entity->getParentID()] = QSet(); } + _childrenOfAvatars[entity->getParentID()] += entity->getEntityItemID(); + doMove = true; + iter.remove(); // and pull it out of the list + } - bool doMove = false; - if (entity->isParentIDValid()) { - // this entity's parent was previously not known, and now is. Update its location in the EntityTree... - doMove = true; - // the bounds on the render-item may need to be updated, the rigid body in the physics engine may - // need to be moved. - entity->markDirtyFlags(Simulation::DIRTY_MOTION_TYPE | - Simulation::DIRTY_COLLISION_GROUP | - Simulation::DIRTY_TRANSFORM); - entityChanged(entity); - entity->forEachDescendant([&](SpatiallyNestablePointer object) { - if (object->getNestableType() == NestableType::Entity) { - EntityItemPointer descendantEntity = std::static_pointer_cast(object); - descendantEntity->markDirtyFlags(Simulation::DIRTY_MOTION_TYPE | - Simulation::DIRTY_COLLISION_GROUP | - Simulation::DIRTY_TRANSFORM); - entityChanged(descendantEntity); - } - }); - entity->locationChanged(true); - } else if (getIsServer() && _avatarIDs.contains(entity->getParentID())) { - // this is a child of an avatar, which the entity server will never have - // a SpatiallyNestable object for. Add it to a list for cleanup when the avatar leaves. - if (!_childrenOfAvatars.contains(entity->getParentID())) { - _childrenOfAvatars[entity->getParentID()] = QSet(); - } - _childrenOfAvatars[entity->getParentID()] += entity->getEntityItemID(); - doMove = true; - } - - if (queryAACubeSuccess && doMove) { - moveOperator.addEntityToMoveList(entity, newCube); - entity->markAncestorMissing(false); - } + if (queryAACubeSuccess && doMove) { + moveOperator.addEntityToMoveList(entity, newCube); + entity->markAncestorMissing(false); } } From 5c93dbd20d35c79b7a73cad5f30d3cd352b23560 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 19 May 2017 13:48:29 -0700 Subject: [PATCH 19/49] be more careful about fixing up entities which arrived before their parents --- libraries/entities/src/AddEntityOperator.cpp | 4 ---- libraries/entities/src/EntityItem.cpp | 4 +--- libraries/entities/src/EntityTree.cpp | 13 +++++-------- libraries/shared/src/SpatiallyNestable.h | 4 ---- 4 files changed, 6 insertions(+), 19 deletions(-) diff --git a/libraries/entities/src/AddEntityOperator.cpp b/libraries/entities/src/AddEntityOperator.cpp index 1c39b40495..7cf4e4a472 100644 --- a/libraries/entities/src/AddEntityOperator.cpp +++ b/libraries/entities/src/AddEntityOperator.cpp @@ -27,10 +27,6 @@ AddEntityOperator::AddEntityOperator(EntityTreePointer tree, EntityItemPointer n bool success; auto queryCube = _newEntity->getQueryAACube(success); - if (!success) { - _newEntity->markAncestorMissing(true); - } - _newEntityBox = queryCube.clamp((float)(-HALF_TREE_SCALE), (float)HALF_TREE_SCALE); } diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 44b0869d85..800bae5617 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -1373,11 +1373,9 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) { if (saveQueryAACube != _queryAACube) { somethingChanged = true; } - } else { - markAncestorMissing(true); } - // Now check the sub classes + // Now check the sub classes somethingChanged |= setSubClassProperties(properties); // Finally notify if change detected diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 84dc3844e3..403d981409 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -295,7 +295,7 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI _missingParent.append(childEntity); continue; } - if (!childEntity->isParentIDValid()) { + if (!childEntity->getParentID().isNull()) { QWriteLocker locker(&_missingParentLock); _missingParent.append(childEntity); } @@ -383,9 +383,7 @@ EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const Enti // Recurse the tree and store the entity in the correct tree element AddEntityOperator theOperator(getThisPointer(), result); recurseTreeWithOperator(&theOperator); - if (result->getAncestorMissing()) { - // we added the entity, but didn't know about all its ancestors, so it went into the wrong place. - // add it to a list of entities needing to be fixed once their parents are known. + if (!result->getParentID().isNull()) { QWriteLocker locker(&_missingParentLock); _missingParent.append(result); } @@ -1221,11 +1219,11 @@ void EntityTree::fixupMissingParents() { // entity was deleted before we found its parent iter.remove(); } - bool queryAACubeSuccess; + bool queryAACubeSuccess { false }; + bool maxAACubeSuccess { false }; AACube newCube = entity->getQueryAACube(queryAACubeSuccess); if (queryAACubeSuccess) { // make sure queryAACube encompasses maxAACube - bool maxAACubeSuccess; AACube maxAACube = entity->getMaximumAACube(maxAACubeSuccess); if (maxAACubeSuccess && !newCube.contains(maxAACube)) { newCube = maxAACube; @@ -1233,7 +1231,7 @@ void EntityTree::fixupMissingParents() { } bool doMove = false; - if (entity->isParentIDValid()) { + if (entity->isParentIDValid() && maxAACubeSuccess) { // maxAACubeSuccess of true means all ancestors are known iter.remove(); // this entity is all hooked up; we can remove it from the list // this entity's parent was previously not known, and now is. Update its location in the EntityTree... doMove = true; @@ -1266,7 +1264,6 @@ void EntityTree::fixupMissingParents() { if (queryAACubeSuccess && doMove) { moveOperator.addEntityToMoveList(entity, newCube); - entity->markAncestorMissing(false); } } diff --git a/libraries/shared/src/SpatiallyNestable.h b/libraries/shared/src/SpatiallyNestable.h index 6589a44307..b98ab4c358 100644 --- a/libraries/shared/src/SpatiallyNestable.h +++ b/libraries/shared/src/SpatiallyNestable.h @@ -158,9 +158,6 @@ public: SpatiallyNestablePointer getThisPointer() const; - void markAncestorMissing(bool value) { _missingAncestor = value; } - bool getAncestorMissing() { return _missingAncestor; } - void forEachChild(std::function actor); void forEachDescendant(std::function actor); @@ -207,7 +204,6 @@ protected: mutable AACube _queryAACube; mutable bool _queryAACubeSet { false }; - bool _missingAncestor { false }; quint64 _scaleChanged { 0 }; quint64 _translationChanged { 0 }; quint64 _rotationChanged { 0 }; From 54dafa0f83af630cab79f91247bd2fb609bab15b Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 19 May 2017 14:14:00 -0700 Subject: [PATCH 20/49] be more careful about fixing up entities which arrived before their parents --- libraries/entities/src/EntityTree.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 403d981409..e88575fc10 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -1219,6 +1219,8 @@ void EntityTree::fixupMissingParents() { // entity was deleted before we found its parent iter.remove(); } + + entity->requiresRecalcBoxes(); bool queryAACubeSuccess { false }; bool maxAACubeSuccess { false }; AACube newCube = entity->getQueryAACube(queryAACubeSuccess); From 386c76545ce960c3c025fec023345c2117de268b Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 19 May 2017 14:21:05 -0700 Subject: [PATCH 21/49] be more careful about fixing up entities which arrived before their parents --- libraries/entities/src/EntityItem.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 73107a4b42..dbfb99b06b 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -187,7 +187,7 @@ public: const Transform getTransformToCenter(bool& success) const; - inline void requiresRecalcBoxes(); + void requiresRecalcBoxes(); // Hyperlink related getters and setters QString getHref() const; From 67f222cb3f507656de689e3d6ffc9c9eb8d324c5 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 19 May 2017 15:19:16 -0700 Subject: [PATCH 22/49] be more careful about fixing up entities which arrived before their parents --- libraries/entities/src/EntityTree.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index e88575fc10..c25685ba81 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -1218,6 +1218,7 @@ void EntityTree::fixupMissingParents() { if (!entity) { // entity was deleted before we found its parent iter.remove(); + continue; } entity->requiresRecalcBoxes(); From 5c94147f40ff095795196a37c4ce7dece06ce903 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 19 May 2017 17:09:42 -0700 Subject: [PATCH 23/49] children collision hulls appear to be in the right place, now --- libraries/entities/src/EntityTree.cpp | 32 +++++++++++--------- libraries/entities/src/EntityTree.h | 8 +++-- libraries/entities/src/EntityTreeElement.cpp | 8 ++++- 3 files changed, 29 insertions(+), 19 deletions(-) diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index c25685ba81..a98e07b9e3 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -123,15 +123,14 @@ void EntityTree::postAddEntity(EntityItemPointer entity) { } if (!entity->getParentID().isNull()) { - QWriteLocker locker(&_missingParentLock); - _missingParent.append(entity); + addToNeedsParentFixupList(entity); } _isDirty = true; emit addingEntity(entity->getEntityItemID()); // find and hook up any entities with this entity as a (previously) missing parent - fixupMissingParents(); + fixupNeedsParentFixups(); } bool EntityTree::updateEntity(const EntityItemID& entityID, const EntityItemProperties& properties, const SharedNodePointer& senderNode) { @@ -291,13 +290,11 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI bool success; AACube queryCube = childEntity->getQueryAACube(success); if (!success) { - QWriteLocker locker(&_missingParentLock); - _missingParent.append(childEntity); + addToNeedsParentFixupList(childEntity); continue; } if (!childEntity->getParentID().isNull()) { - QWriteLocker locker(&_missingParentLock); - _missingParent.append(childEntity); + addToNeedsParentFixupList(childEntity); } UpdateEntityOperator theChildOperator(getThisPointer(), containingElement, childEntity, queryCube); @@ -384,8 +381,7 @@ EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const Enti AddEntityOperator theOperator(getThisPointer(), result); recurseTreeWithOperator(&theOperator); if (!result->getParentID().isNull()) { - QWriteLocker locker(&_missingParentLock); - _missingParent.append(result); + addToNeedsParentFixupList(result); } postAddEntity(result); @@ -1207,11 +1203,13 @@ void EntityTree::entityChanged(EntityItemPointer entity) { } } -void EntityTree::fixupMissingParents() { + +void EntityTree::fixupNeedsParentFixups() { MovingEntitiesOperator moveOperator(getThisPointer()); - QWriteLocker locker(&_missingParentLock); - QMutableVectorIterator iter(_missingParent); + QWriteLocker locker(&_needsParentFixupLock); + + QMutableVectorIterator iter(_needsParentFixup); while (iter.hasNext()) { EntityItemWeakPointer entityWP = iter.next(); EntityItemPointer entity = entityWP.lock(); @@ -1283,8 +1281,13 @@ void EntityTree::deleteDescendantsOfAvatar(QUuid avatarID) { } } +void EntityTree::addToNeedsParentFixupList(EntityItemPointer entity) { + QWriteLocker locker(&_needsParentFixupLock); + _needsParentFixup.append(entity); +} + void EntityTree::update() { - fixupMissingParents(); + fixupNeedsParentFixups(); if (_simulation) { withWriteLock([&] { _simulation->updateEntities(); @@ -1609,8 +1612,7 @@ QVector EntityTree::sendEntities(EntityEditPacketSender* packetSen EntityItemPointer entity = localTree->findEntityByEntityItemID(newID); if (entity) { if (!entity->getParentID().isNull()) { - QWriteLocker locker(&_missingParentLock); - _missingParent.append(entity); + addToNeedsParentFixupList(entity); } entity->forceQueryAACubeUpdate(); moveOperator.addEntityToMoveList(entity, entity->getQueryAACube()); diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 6137d14aac..2703060719 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -271,6 +271,8 @@ public: void forgetAvatarID(QUuid avatarID) { _avatarIDs -= avatarID; } void deleteDescendantsOfAvatar(QUuid avatarID); + void addToNeedsParentFixupList(EntityItemPointer entity); + void notifyNewCollisionSoundURL(const QString& newCollisionSoundURL, const EntityItemID& entityID); static const float DEFAULT_MAX_TMP_ENTITY_LIFETIME; @@ -351,9 +353,9 @@ protected: quint64 _maxEditDelta = 0; quint64 _treeResetTime = 0; - void fixupMissingParents(); // try to hook members of _missingParent to parent instances - QVector _missingParent; // entites with a parentID but no (yet) known parent instance - mutable QReadWriteLock _missingParentLock; + void fixupNeedsParentFixups(); // try to hook members of _needsParentFixup to parent instances + QVector _needsParentFixup; // entites with a parentID but no (yet) known parent instance + mutable QReadWriteLock _needsParentFixupLock; // we maintain a list of avatarIDs to notice when an entity is a child of one. QSet _avatarIDs; // IDs of avatars connected to entity server diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index 98287541e3..0dc42717f5 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -208,7 +208,7 @@ void EntityTreeElement::elementEncodeComplete(EncodeBitstreamParams& params) con // why would this ever fail??? // If we've encoding this element before... but we're coming back a second time in an attempt to - // encoud our parent... this might happen. + // encode our parent... this might happen. if (extraEncodeData->contains(childElement.get())) { EntityTreeElementExtraEncodeDataPointer childExtraEncodeData = std::static_pointer_cast((*extraEncodeData)[childElement.get()]); @@ -981,6 +981,7 @@ int EntityTreeElement::readElementDataFromBuffer(const unsigned char* data, int // 3) remember the old cube for the entity so we can mark it as dirty if (entityItem) { QString entityScriptBefore = entityItem->getScript(); + QUuid parentIDBefore = entityItem->getParentID(); QString entityServerScriptsBefore = entityItem->getServerScripts(); quint64 entityScriptTimestampBefore = entityItem->getScriptTimestamp(); bool bestFitBefore = bestFitEntityBounds(entityItem); @@ -1018,6 +1019,11 @@ int EntityTreeElement::readElementDataFromBuffer(const unsigned char* data, int _myTree->emitEntityServerScriptChanging(entityItemID, reload); // the entity server script has changed } + QUuid parentIDAfter = entityItem->getParentID(); + if (parentIDBefore != parentIDAfter) { + _myTree->addToNeedsParentFixupList(entityItem); + } + } else { entityItem = EntityTypes::constructEntityItem(dataAt, bytesLeftToRead, args); if (entityItem) { From a442181859485403b003a977c25511f40b9724fb Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sat, 20 May 2017 08:51:44 -0700 Subject: [PATCH 24/49] remove some redundancy --- libraries/entities/src/EntityItem.cpp | 44 +++++++++------------------ 1 file changed, 15 insertions(+), 29 deletions(-) diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 800bae5617..fc1b6490cd 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -1369,10 +1369,9 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(lastEditedBy, setLastEditedBy); AACube saveQueryAACube = _queryAACube; - if (checkAndAdjustQueryAACube()) { - if (saveQueryAACube != _queryAACube) { - somethingChanged = true; - } + checkAndAdjustQueryAACube(); + if (saveQueryAACube != _queryAACube) { + somethingChanged = true; } // Now check the sub classes @@ -1609,46 +1608,33 @@ void EntityItem::updatePosition(const glm::vec3& value) { setLocalPosition(value); EntityTreePointer tree = getTree(); - if (!tree) { - return; - } - markDirtyFlags(Simulation::DIRTY_POSITION); - tree->entityChanged(getThisPointer()); + if (tree) { + tree->entityChanged(getThisPointer()); + } forEachDescendant([&](SpatiallyNestablePointer object) { if (object->getNestableType() == NestableType::Entity) { EntityItemPointer entity = std::static_pointer_cast(object); entity->markDirtyFlags(Simulation::DIRTY_POSITION); - tree->entityChanged(entity); + if (tree) { + tree->entityChanged(entity); + } } }); - locationChanged(); } } void EntityItem::updateParentID(const QUuid& value) { if (getParentID() != value) { setParentID(value); - - EntityTreePointer tree = getTree(); - if (!tree) { - return; - } - // children are forced to be kinematic // may need to not collide with own avatar - markDirtyFlags(Simulation::DIRTY_MOTION_TYPE | Simulation::DIRTY_COLLISION_GROUP | Simulation::DIRTY_TRANSFORM); - tree->entityChanged(getThisPointer()); - forEachDescendant([&](SpatiallyNestablePointer object) { - if (object->getNestableType() == NestableType::Entity) { - EntityItemPointer entity = std::static_pointer_cast(object); - entity->markDirtyFlags(Simulation::DIRTY_MOTION_TYPE | - Simulation::DIRTY_COLLISION_GROUP | - Simulation::DIRTY_TRANSFORM); - tree->entityChanged(entity); - } - }); - locationChanged(); + markDirtyFlags(Simulation::DIRTY_MOTION_TYPE | Simulation::DIRTY_COLLISION_GROUP); + + EntityTreePointer tree = getTree(); + if (tree) { + tree->addToNeedsParentFixupList(getThisPointer()); + } } } From 3825830fac860f157193932463ace3a9e47616e1 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Mon, 22 May 2017 14:07:53 -0700 Subject: [PATCH 25/49] if something is locked but in motion, make it kinematic rather than static --- libraries/physics/src/EntityMotionState.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 452495cf18..fdd290bfca 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -186,6 +186,9 @@ PhysicsMotionType EntityMotionState::computePhysicsMotionType() const { } if (_entity->getLocked()) { + if (_entity->isMoving()) { + return MOTION_TYPE_KINEMATIC; + } return MOTION_TYPE_STATIC; } From 71de3d5ca103f32d85ef95854496fedd2ca0175d Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Tue, 23 May 2017 08:40:15 -0700 Subject: [PATCH 26/49] Instantiate new gate with the correct number of channels --- libraries/audio-client/src/AudioClient.cpp | 8 ++++++++ libraries/audio-client/src/AudioClient.h | 2 ++ 2 files changed, 10 insertions(+) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 1282dbb2dc..5f187b599c 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -1415,6 +1415,10 @@ bool AudioClient::switchInputToAudioDevice(const QAudioDeviceInfo& inputDeviceIn delete _inputToNetworkResampler; _inputToNetworkResampler = NULL; } + if (_audioGate) { + delete _audioGate; + _audioGate = nullptr; + } if (!inputDeviceInfo.isNull()) { qCDebug(audioclient) << "The audio input device " << inputDeviceInfo.deviceName() << "is available."; @@ -1440,6 +1444,10 @@ bool AudioClient::switchInputToAudioDevice(const QAudioDeviceInfo& inputDeviceIn qCDebug(audioclient) << "No resampling required for audio input to match desired network format."; } + // the audio gate runs after the resampler + _audioGate = new AudioGate(_desiredInputFormat.sampleRate(), _desiredInputFormat.channelCount()); + qCDebug(audioclient) << "Noise gate created with" << _desiredInputFormat.channelCount() << "channels."; + // if the user wants stereo but this device can't provide then bail if (!_isStereoInput || _inputFormat.channelCount() == 2) { _audioInput = new QAudioInput(inputDeviceInfo, _inputFormat, this); diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 47808767b3..563c01d98a 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -46,6 +46,7 @@ #include #include #include +#include #include @@ -361,6 +362,7 @@ private: AudioIOStats _stats; AudioNoiseGate _noiseGate; + AudioGate* _audioGate { nullptr }; AudioPositionGetter _positionGetter; AudioOrientationGetter _orientationGetter; From 986d86ec17a6a581e634753bddefb4ad5949f5d0 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Tue, 23 May 2017 10:29:43 -0700 Subject: [PATCH 27/49] Fix nasty preexisting bug in computing numSamples, where sizeof(SAMPLE_SIZE) incorrectly returns 4 instead of 2. --- libraries/audio-client/src/AudioClient.cpp | 2 +- libraries/audio/src/AudioInjector.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 5f187b599c..34afde6332 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -1007,7 +1007,7 @@ void AudioClient::handleAudioInput(QByteArray& audioBuffer) { _timeSinceLastClip = 0.0f; } else { int16_t* samples = reinterpret_cast(audioBuffer.data()); - int numSamples = audioBuffer.size() / sizeof(AudioConstants::SAMPLE_SIZE); + int numSamples = audioBuffer.size() / AudioConstants::SAMPLE_SIZE; bool didClip = false; bool shouldRemoveDCOffset = !_isPlayingBackRecording && !_isStereoInput; diff --git a/libraries/audio/src/AudioInjector.cpp b/libraries/audio/src/AudioInjector.cpp index ce98225190..47e6c98144 100644 --- a/libraries/audio/src/AudioInjector.cpp +++ b/libraries/audio/src/AudioInjector.cpp @@ -139,7 +139,7 @@ bool AudioInjector::inject(bool(AudioInjectorManager::*injection)(AudioInjector* if (_options.secondOffset > 0.0f) { int numChannels = _options.ambisonic ? 4 : (_options.stereo ? 2 : 1); byteOffset = (int)(AudioConstants::SAMPLE_RATE * _options.secondOffset * numChannels); - byteOffset *= sizeof(AudioConstants::SAMPLE_SIZE); + byteOffset *= AudioConstants::SAMPLE_SIZE; } _currentSendOffset = byteOffset; From 850dbd76c9dcd0278fbab46fef893363f81ed5b5 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Tue, 23 May 2017 11:12:36 -0700 Subject: [PATCH 28/49] Integrate new noise gate --- libraries/audio-client/src/AudioClient.cpp | 55 +++++++++++++++------- 1 file changed, 37 insertions(+), 18 deletions(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 34afde6332..a7d5be22ef 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -1008,29 +1008,48 @@ void AudioClient::handleAudioInput(QByteArray& audioBuffer) { } else { int16_t* samples = reinterpret_cast(audioBuffer.data()); int numSamples = audioBuffer.size() / AudioConstants::SAMPLE_SIZE; - bool didClip = false; + int numFrames = numSamples / (_isStereoInput ? AudioConstants::STEREO : AudioConstants::MONO); - bool shouldRemoveDCOffset = !_isPlayingBackRecording && !_isStereoInput; - if (shouldRemoveDCOffset) { - _noiseGate.removeDCOffset(samples, numSamples); - } + //bool didClip = false; - bool shouldNoiseGate = (_isPlayingBackRecording || !_isStereoInput) && _isNoiseGateEnabled; - if (shouldNoiseGate) { - _noiseGate.gateSamples(samples, numSamples); - _lastInputLoudness = _noiseGate.getLastLoudness(); - didClip = _noiseGate.clippedInLastBlock(); + //bool shouldRemoveDCOffset = !_isPlayingBackRecording && !_isStereoInput; + //if (shouldRemoveDCOffset) { + // _noiseGate.removeDCOffset(samples, numSamples); + //} + + //bool shouldNoiseGate = (_isPlayingBackRecording || !_isStereoInput) && _isNoiseGateEnabled; + //if (shouldNoiseGate) { + // _noiseGate.gateSamples(samples, numSamples); + // _lastInputLoudness = _noiseGate.getLastLoudness(); + // didClip = _noiseGate.clippedInLastBlock(); + //} else { + // float loudness = 0.0f; + // for (int i = 0; i < numSamples; ++i) { + // int16_t sample = std::abs(samples[i]); + // loudness += (float)sample; + // didClip = didClip || + // (sample > (AudioConstants::MAX_SAMPLE_VALUE * AudioNoiseGate::CLIPPING_THRESHOLD)); + // } + // _lastInputLoudness = fabs(loudness / numSamples); + //} + + if (_isNoiseGateEnabled) { + // The audio gate includes DC removal + _audioGate->render(samples, samples, numFrames); } else { - float loudness = 0.0f; - for (int i = 0; i < numSamples; ++i) { - int16_t sample = std::abs(samples[i]); - loudness += (float)sample; - didClip = didClip || - (sample > (AudioConstants::MAX_SAMPLE_VALUE * AudioNoiseGate::CLIPPING_THRESHOLD)); - } - _lastInputLoudness = fabs(loudness / numSamples); + _audioGate->removeDC(samples, samples, numFrames); } + // TODO: optimize this + float loudness = 0.0f; + bool didClip = false; + for (int i = 0; i < numSamples; ++i) { + int16_t sample = std::abs(samples[i]); + loudness += (float)sample; + didClip = didClip || (sample > (AudioConstants::MAX_SAMPLE_VALUE * AudioNoiseGate::CLIPPING_THRESHOLD)); + } + _lastInputLoudness = fabs(loudness / numSamples); + if (didClip) { _timeSinceLastClip = 0.0f; } else if (_timeSinceLastClip >= 0.0f) { From a7f154a853b3156032152fb7818afdee4a8f3dc3 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 18 May 2017 09:29:04 -0700 Subject: [PATCH 29/49] make a copy of original models file when baking domain --- tools/oven/src/DomainBaker.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tools/oven/src/DomainBaker.cpp b/tools/oven/src/DomainBaker.cpp index cb2a6bca29..1d38c732dc 100644 --- a/tools/oven/src/DomainBaker.cpp +++ b/tools/oven/src/DomainBaker.cpp @@ -100,6 +100,15 @@ void DomainBaker::loadLocalFile() { // load up the local entities file QFile entitiesFile { _localEntitiesFileURL.toLocalFile() }; + // first make a copy of the local entities file in our output folder + if (!entitiesFile.copy(_uniqueOutputPath + "/" + "original-" + _localEntitiesFileURL.fileName())) { + // add an error to our list to specify that the file could not be copied + handleError("Could not make a copy of entities file"); + + // return to stop processing + return; + } + if (!entitiesFile.open(QIODevice::ReadOnly)) { // add an error to our list to specify that the file could not be read handleError("Could not open entities file"); From 3edbd41027d5c88f47e95745fd9538ba437544ac Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 18 May 2017 11:27:24 -0700 Subject: [PATCH 30/49] enable image compression at run time in baker --- tools/oven/src/Oven.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tools/oven/src/Oven.cpp b/tools/oven/src/Oven.cpp index ac8ef505ba..39187aedc4 100644 --- a/tools/oven/src/Oven.cpp +++ b/tools/oven/src/Oven.cpp @@ -12,6 +12,7 @@ #include #include +#include #include #include "ui/OvenMainWindow.h" @@ -29,6 +30,12 @@ Oven::Oven(int argc, char* argv[]) : // init the settings interface so we can save and load settings Setting::init(); + // enable compression in image library + image::setColorTexturesCompressionEnabled(true); + image::setGrayscaleTexturesCompressionEnabled(true); + image::setNormalTexturesCompressionEnabled(true); + image::setCubeTexturesCompressionEnabled(true); + // check if we were passed any command line arguments that would tell us just to run without the GUI // setup the GUI From 2ba700d062932bbbb1495f9ac11ec9c7ecfaa92b Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 18 May 2017 11:57:18 -0700 Subject: [PATCH 31/49] add a toggle to domain baker to re-bake originals --- tools/oven/src/DomainBaker.cpp | 28 +++++++++++++++++++++----- tools/oven/src/DomainBaker.h | 5 ++++- tools/oven/src/ui/DomainBakeWidget.cpp | 8 +++++++- tools/oven/src/ui/DomainBakeWidget.h | 2 ++ 4 files changed, 36 insertions(+), 7 deletions(-) diff --git a/tools/oven/src/DomainBaker.cpp b/tools/oven/src/DomainBaker.cpp index 1d38c732dc..fe0808de73 100644 --- a/tools/oven/src/DomainBaker.cpp +++ b/tools/oven/src/DomainBaker.cpp @@ -23,10 +23,12 @@ #include "DomainBaker.h" DomainBaker::DomainBaker(const QUrl& localModelFileURL, const QString& domainName, - const QString& baseOutputPath, const QUrl& destinationPath) : + const QString& baseOutputPath, const QUrl& destinationPath, + bool shouldRebakeOriginals) : _localEntitiesFileURL(localModelFileURL), _domainName(domainName), - _baseOutputPath(baseOutputPath) + _baseOutputPath(baseOutputPath), + _shouldRebakeOriginals(shouldRebakeOriginals) { // make sure the destination path has a trailing slash if (!destinationPath.toString().endsWith('/')) { @@ -168,9 +170,25 @@ void DomainBaker::enumerateEntities() { static const QStringList BAKEABLE_MODEL_EXTENSIONS { ".fbx" }; auto completeLowerExtension = modelFileName.mid(modelFileName.indexOf('.')).toLower(); - if (BAKEABLE_MODEL_EXTENSIONS.contains(completeLowerExtension)) { - // grab a clean version of the URL without a query or fragment - modelURL = modelURL.adjusted(QUrl::RemoveQuery | QUrl::RemoveFragment); + static const QString BAKED_MODEL_EXTENSION = ".baked.fbx"; + + if (BAKEABLE_MODEL_EXTENSIONS.contains(completeLowerExtension) || + (_shouldRebakeOriginals && completeLowerExtension == BAKED_MODEL_EXTENSION)) { + + if (completeLowerExtension == BAKED_MODEL_EXTENSION) { + // grab a URL to the original, that we assume is stored a directory up, in the "original" folder + // with just the fbx extension + qDebug() << "Re-baking original for" << modelURL; + + auto originalFileName = modelFileName; + originalFileName.replace(".baked", ""); + modelURL = modelURL.resolved("../original/" + originalFileName); + + qDebug() << "Original must be present at" << modelURL; + } else { + // grab a clean version of the URL without a query or fragment + modelURL = modelURL.adjusted(QUrl::RemoveQuery | QUrl::RemoveFragment); + } // setup an FBXBaker for this URL, as long as we don't already have one if (!_modelBakers.contains(modelURL)) { diff --git a/tools/oven/src/DomainBaker.h b/tools/oven/src/DomainBaker.h index 5244408115..34c5e11e63 100644 --- a/tools/oven/src/DomainBaker.h +++ b/tools/oven/src/DomainBaker.h @@ -28,7 +28,8 @@ public: // This means that we need to put all of the FBX importing/exporting from the same process on the same thread. // That means you must pass a usable running QThread when constructing a domain baker. DomainBaker(const QUrl& localEntitiesFileURL, const QString& domainName, - const QString& baseOutputPath, const QUrl& destinationPath); + const QString& baseOutputPath, const QUrl& destinationPath, + bool shouldRebakeOriginals = false); signals: void allModelsFinished(); @@ -65,6 +66,8 @@ private: int _totalNumberOfSubBakes { 0 }; int _completedSubBakes { 0 }; + + bool _shouldRebakeOriginals { false }; }; #endif // hifi_DomainBaker_h diff --git a/tools/oven/src/ui/DomainBakeWidget.cpp b/tools/oven/src/ui/DomainBakeWidget.cpp index 7d667305bb..41452c7283 100644 --- a/tools/oven/src/ui/DomainBakeWidget.cpp +++ b/tools/oven/src/ui/DomainBakeWidget.cpp @@ -11,6 +11,7 @@ #include +#include #include #include #include @@ -126,6 +127,10 @@ void DomainBakeWidget::setupUI() { // start a new row for the next component ++rowIndex; + // setup a checkbox to allow re-baking of original assets + _rebakeOriginalsCheckBox = new QCheckBox("Re-bake originals"); + gridLayout->addWidget(_rebakeOriginalsCheckBox, rowIndex, 0); + // add a button that will kickoff the bake QPushButton* bakeButton = new QPushButton("Bake"); connect(bakeButton, &QPushButton::clicked, this, &DomainBakeWidget::bakeButtonClicked); @@ -207,7 +212,8 @@ void DomainBakeWidget::bakeButtonClicked() { auto fileToBakeURL = QUrl::fromLocalFile(_entitiesFileLineEdit->text()); auto domainBaker = std::unique_ptr { new DomainBaker(fileToBakeURL, _domainNameLineEdit->text(), - outputDirectory.absolutePath(), _destinationPathLineEdit->text()) + outputDirectory.absolutePath(), _destinationPathLineEdit->text(), + _rebakeOriginalsCheckBox->isChecked()) }; // make sure we hear from the baker when it is done diff --git a/tools/oven/src/ui/DomainBakeWidget.h b/tools/oven/src/ui/DomainBakeWidget.h index cd8c4a012e..a6f26b3731 100644 --- a/tools/oven/src/ui/DomainBakeWidget.h +++ b/tools/oven/src/ui/DomainBakeWidget.h @@ -19,6 +19,7 @@ #include "../DomainBaker.h" #include "BakeWidget.h" +class QCheckBox; class QLineEdit; class DomainBakeWidget : public BakeWidget { @@ -44,6 +45,7 @@ private: QLineEdit* _entitiesFileLineEdit; QLineEdit* _outputDirLineEdit; QLineEdit* _destinationPathLineEdit; + QCheckBox* _rebakeOriginalsCheckBox; Setting::Handle _domainNameSetting; Setting::Handle _exportDirectory; From 0621ddfd9f8855a02f1e4723b4034886d60f708a Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 18 May 2017 17:47:10 -0700 Subject: [PATCH 32/49] don't enable cube map compression by default --- tools/oven/src/Oven.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tools/oven/src/Oven.cpp b/tools/oven/src/Oven.cpp index 39187aedc4..df232899d4 100644 --- a/tools/oven/src/Oven.cpp +++ b/tools/oven/src/Oven.cpp @@ -30,11 +30,10 @@ Oven::Oven(int argc, char* argv[]) : // init the settings interface so we can save and load settings Setting::init(); - // enable compression in image library + // enable compression in image library, except for cube maps image::setColorTexturesCompressionEnabled(true); image::setGrayscaleTexturesCompressionEnabled(true); image::setNormalTexturesCompressionEnabled(true); - image::setCubeTexturesCompressionEnabled(true); // check if we were passed any command line arguments that would tell us just to run without the GUI From 175d1be7ca9fceb90addd992368c728ce0cdf294 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Tue, 23 May 2017 12:32:41 -0700 Subject: [PATCH 33/49] Implement state-machine to detect gate opening/closing. Fixes bugs with mute. --- libraries/audio-client/src/AudioClient.cpp | 27 +++++++++++++--------- libraries/audio-client/src/AudioClient.h | 1 + 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index a7d5be22ef..573e0a455f 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -1057,19 +1057,24 @@ void AudioClient::handleAudioInput(QByteArray& audioBuffer) { } emit inputReceived(audioBuffer); - - if (_noiseGate.openedInLastBlock()) { - emit noiseGateOpened(); - } else if (_noiseGate.closedInLastBlock()) { - emit noiseGateClosed(); - } } - // the codec needs a flush frame before sending silent packets, so - // do not send one if the gate closed in this block (eventually this can be crossfaded). - auto packetType = _shouldEchoToServer ? - PacketType::MicrophoneAudioWithEcho : PacketType::MicrophoneAudioNoEcho; - if (_lastInputLoudness == 0.0f && !_noiseGate.closedInLastBlock()) { + // state machine to detect gate opening and closing + bool audioGateOpen = (_lastInputLoudness != 0.0f); + bool openedInLastBlock = !_audioGateOpen && audioGateOpen; // the gate just opened + bool closedInLastBlock = _audioGateOpen && !audioGateOpen; // the gate just closed + _audioGateOpen = audioGateOpen; + + if (openedInLastBlock) { + emit noiseGateOpened(); + } else if (closedInLastBlock) { + emit noiseGateClosed(); + } + + // the codec must be flushed to silence before sending silent packets, + // so delay the transition to silent packets by one packet after becoming silent. + auto packetType = _shouldEchoToServer ? PacketType::MicrophoneAudioWithEcho : PacketType::MicrophoneAudioNoEcho; + if (!audioGateOpen && !closedInLastBlock) { packetType = PacketType::SilentAudioFrame; _silentOutbound.increment(); } else { diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 563c01d98a..c82f406f86 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -363,6 +363,7 @@ private: AudioNoiseGate _noiseGate; AudioGate* _audioGate { nullptr }; + bool _audioGateOpen { false }; AudioPositionGetter _positionGetter; AudioOrientationGetter _orientationGetter; From 860869569523611f98bfa4c5732e3522986aac7e Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Tue, 23 May 2017 12:52:29 -0700 Subject: [PATCH 34/49] Remove old noise gate --- libraries/audio-client/src/AudioClient.cpp | 3 ++- libraries/audio-client/src/AudioClient.h | 6 ++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 573e0a455f..cbe19cb4db 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -1041,12 +1041,13 @@ void AudioClient::handleAudioInput(QByteArray& audioBuffer) { } // TODO: optimize this + const float CLIPPING_THRESHOLD = 0.90f; float loudness = 0.0f; bool didClip = false; for (int i = 0; i < numSamples; ++i) { int16_t sample = std::abs(samples[i]); loudness += (float)sample; - didClip = didClip || (sample > (AudioConstants::MAX_SAMPLE_VALUE * AudioNoiseGate::CLIPPING_THRESHOLD)); + didClip = didClip || (sample > (AudioConstants::MAX_SAMPLE_VALUE * CLIPPING_THRESHOLD)); } _lastInputLoudness = fabs(loudness / numSamples); diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index c82f406f86..9793ee3288 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -45,7 +45,6 @@ #include #include #include -#include #include #include @@ -110,7 +109,7 @@ public: void selectAudioFormat(const QString& selectedCodecName); Q_INVOKABLE QString getSelectedAudioFormat() const { return _selectedCodecName; } - Q_INVOKABLE bool getNoiseGateOpen() const { return _noiseGate.isOpen(); } + Q_INVOKABLE bool getNoiseGateOpen() const { return _audioGateOpen; } Q_INVOKABLE float getSilentInboundPPS() const { return _silentInbound.rate(); } Q_INVOKABLE float getAudioInboundPPS() const { return _audioInbound.rate(); } Q_INVOKABLE float getSilentOutboundPPS() const { return _silentOutbound.rate(); } @@ -119,7 +118,7 @@ public: const MixedProcessedAudioStream& getReceivedAudioStream() const { return _receivedAudioStream; } MixedProcessedAudioStream& getReceivedAudioStream() { return _receivedAudioStream; } - float getLastInputLoudness() const { return glm::max(_lastInputLoudness - _noiseGate.getMeasuredFloor(), 0.0f); } + float getLastInputLoudness() const { return _lastInputLoudness; } // TODO: relative to noise floor? float getTimeSinceLastClip() const { return _timeSinceLastClip; } float getAudioAverageInputLoudness() const { return _lastInputLoudness; } @@ -361,7 +360,6 @@ private: AudioIOStats _stats; - AudioNoiseGate _noiseGate; AudioGate* _audioGate { nullptr }; bool _audioGateOpen { false }; From a6a29786aeeb46880617f03935309479a99b594a Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Tue, 23 May 2017 12:53:39 -0700 Subject: [PATCH 35/49] Cleanup --- libraries/audio-client/src/AudioClient.cpp | 23 ---------------------- 1 file changed, 23 deletions(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index cbe19cb4db..f55ee0cb72 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -1010,29 +1010,6 @@ void AudioClient::handleAudioInput(QByteArray& audioBuffer) { int numSamples = audioBuffer.size() / AudioConstants::SAMPLE_SIZE; int numFrames = numSamples / (_isStereoInput ? AudioConstants::STEREO : AudioConstants::MONO); - //bool didClip = false; - - //bool shouldRemoveDCOffset = !_isPlayingBackRecording && !_isStereoInput; - //if (shouldRemoveDCOffset) { - // _noiseGate.removeDCOffset(samples, numSamples); - //} - - //bool shouldNoiseGate = (_isPlayingBackRecording || !_isStereoInput) && _isNoiseGateEnabled; - //if (shouldNoiseGate) { - // _noiseGate.gateSamples(samples, numSamples); - // _lastInputLoudness = _noiseGate.getLastLoudness(); - // didClip = _noiseGate.clippedInLastBlock(); - //} else { - // float loudness = 0.0f; - // for (int i = 0; i < numSamples; ++i) { - // int16_t sample = std::abs(samples[i]); - // loudness += (float)sample; - // didClip = didClip || - // (sample > (AudioConstants::MAX_SAMPLE_VALUE * AudioNoiseGate::CLIPPING_THRESHOLD)); - // } - // _lastInputLoudness = fabs(loudness / numSamples); - //} - if (_isNoiseGateEnabled) { // The audio gate includes DC removal _audioGate->render(samples, samples, numFrames); From 4c652487d0f88e22d2526dfb8473c9e1955c32b2 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 23 May 2017 14:14:22 -0700 Subject: [PATCH 36/49] enable skybox compression (via BC7) by default --- tools/oven/src/Oven.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/oven/src/Oven.cpp b/tools/oven/src/Oven.cpp index df232899d4..af660e9795 100644 --- a/tools/oven/src/Oven.cpp +++ b/tools/oven/src/Oven.cpp @@ -34,6 +34,7 @@ Oven::Oven(int argc, char* argv[]) : image::setColorTexturesCompressionEnabled(true); image::setGrayscaleTexturesCompressionEnabled(true); image::setNormalTexturesCompressionEnabled(true); + image::setCubeTexturesCompressionEnabled(true); // check if we were passed any command line arguments that would tell us just to run without the GUI From 898433f42e06d91384430019d16dda4685e3a17e Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 23 May 2017 14:16:43 -0700 Subject: [PATCH 37/49] force BC3 instead of BC1a compression for alpha textures --- libraries/image/src/image/Image.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libraries/image/src/image/Image.cpp b/libraries/image/src/image/Image.cpp index 7f0674b17d..2a72304f5c 100644 --- a/libraries/image/src/image/Image.cpp +++ b/libraries/image/src/image/Image.cpp @@ -493,6 +493,10 @@ gpu::TexturePointer TextureUsage::process2DTextureColorFromImage(const QImage& s if (validAlpha) { processTextureAlpha(image, validAlpha, alphaAsMask); + + // NOTE: This disables BC1a compression becuase it was producing odd artifacts on text textures + // for the tutorial. Instead we use BC3 (which is larger) but doesn't produce the same artifacts). + alphaAsMask = false; } gpu::TexturePointer theTexture = nullptr; From 6b740e855d03edde44d172e332c8e3e9b51a709e Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Tue, 23 May 2017 14:31:24 -0700 Subject: [PATCH 38/49] Optimize the loudness computation NOTES: Loudness is now measured post-gating, for improved metering. Keep original algorithm, since other code depends on loudness behavior. --- libraries/audio-client/src/AudioClient.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index f55ee0cb72..9864b3f67a 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -1017,16 +1017,16 @@ void AudioClient::handleAudioInput(QByteArray& audioBuffer) { _audioGate->removeDC(samples, samples, numFrames); } - // TODO: optimize this - const float CLIPPING_THRESHOLD = 0.90f; - float loudness = 0.0f; + int32_t loudness = 0; + assert(numSamples < 65536); // int32_t loudness cannot overflow bool didClip = false; for (int i = 0; i < numSamples; ++i) { - int16_t sample = std::abs(samples[i]); - loudness += (float)sample; - didClip = didClip || (sample > (AudioConstants::MAX_SAMPLE_VALUE * CLIPPING_THRESHOLD)); + const int32_t CLIPPING_THRESHOLD = (int32_t)(AudioConstants::MAX_SAMPLE_VALUE * 0.9f); + int32_t sample = std::abs((int32_t)samples[i]); + loudness += sample; + didClip |= (sample > CLIPPING_THRESHOLD); } - _lastInputLoudness = fabs(loudness / numSamples); + _lastInputLoudness = (float)loudness / numSamples; if (didClip) { _timeSinceLastClip = 0.0f; From c47d80574e61e49564ff281e38fc584ac65fc6ba Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Tue, 23 May 2017 15:32:41 -0700 Subject: [PATCH 39/49] Replace the other noise gate, in Agent.cpp --- assignment-client/src/Agent.cpp | 54 ++++++++++++++++++++++++--------- assignment-client/src/Agent.h | 5 +-- 2 files changed, 42 insertions(+), 17 deletions(-) diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 5864cadc15..ef396a06b3 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -55,7 +55,8 @@ static const int RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES = 10; Agent::Agent(ReceivedMessage& message) : ThreadedAssignment(message), - _receivedAudioStream(RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES, RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES) + _receivedAudioStream(RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES, RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES), + _audioGate(AudioConstants::SAMPLE_RATE, AudioConstants::MONO) { _entityEditSender.setPacketsPerSecond(DEFAULT_ENTITY_PPS_PER_SCRIPT); DependencyManager::get()->setPacketSender(&_entityEditSender); @@ -397,16 +398,23 @@ void Agent::executeScript() { QByteArray audio(frame->data); if (_isNoiseGateEnabled) { - static int numSamples = AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL; - _noiseGate.gateSamples(reinterpret_cast(audio.data()), numSamples); + int16_t* samples = reinterpret_cast(audio.data()); + int numSamples = AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL; + _audioGate.render(samples, samples, numSamples); } computeLoudness(&audio, scriptedAvatar); - // the codec needs a flush frame before sending silent packets, so - // do not send one if the gate closed in this block (eventually this can be crossfaded). + // state machine to detect gate opening and closing + bool audioGateOpen = (scriptedAvatar->getAudioLoudness() != 0.0f); + bool openedInLastBlock = !_audioGateOpen && audioGateOpen; // the gate just opened + bool closedInLastBlock = _audioGateOpen && !audioGateOpen; // the gate just closed + _audioGateOpen = audioGateOpen; + + // the codec must be flushed to silence before sending silent packets, + // so delay the transition to silent packets by one packet after becoming silent. auto packetType = PacketType::MicrophoneAudioNoEcho; - if (scriptedAvatar->getAudioLoudness() == 0.0f && !_noiseGate.closedInLastBlock()) { + if (!audioGateOpen && !closedInLastBlock) { packetType = PacketType::SilentAudioFrame; } @@ -619,19 +627,35 @@ void Agent::encodeFrameOfZeros(QByteArray& encodedZeros) { } void Agent::computeLoudness(const QByteArray* decodedBuffer, QSharedPointer scriptableAvatar) { - float loudness = 0.0f; + //float loudness = 0.0f; + //if (decodedBuffer) { + // auto soundData = reinterpret_cast(decodedBuffer->constData()); + // int numFrames = decodedBuffer->size() / sizeof(int16_t); + // // now iterate and come up with average + // if (numFrames > 0) { + // for(int i = 0; i < numFrames; i++) { + // loudness += (float) std::abs(soundData[i]); + // } + // loudness /= numFrames; + // } + //} + //scriptableAvatar->setAudioLoudness(loudness); + + float lastInputLoudness = 0.0f; if (decodedBuffer) { - auto soundData = reinterpret_cast(decodedBuffer->constData()); - int numFrames = decodedBuffer->size() / sizeof(int16_t); - // now iterate and come up with average - if (numFrames > 0) { - for(int i = 0; i < numFrames; i++) { - loudness += (float) std::abs(soundData[i]); + auto samples = reinterpret_cast(decodedBuffer->constData()); + int numSamples = decodedBuffer->size() / AudioConstants::SAMPLE_SIZE; + + assert(numSamples < 65536); // int32_t loudness cannot overflow + if (numSamples > 0) { + int32_t loudness = 0; + for (int i = 0; i < numSamples; ++i) { + loudness += std::abs((int32_t)samples[i]); } - loudness /= numFrames; + lastInputLoudness = (float)loudness / numSamples; } } - scriptableAvatar->setAudioLoudness(loudness); + scriptableAvatar->setAudioLoudness(lastInputLoudness); } void Agent::processAgentAvatarAudio() { diff --git a/assignment-client/src/Agent.h b/assignment-client/src/Agent.h index 2a156aba18..edba366601 100644 --- a/assignment-client/src/Agent.h +++ b/assignment-client/src/Agent.h @@ -29,7 +29,7 @@ #include -#include "AudioNoiseGate.h" +#include "AudioGate.h" #include "MixedAudioStream.h" #include "avatars/ScriptableAvatar.h" @@ -111,7 +111,8 @@ private: QTimer* _avatarIdentityTimer = nullptr; QHash _outgoingScriptAudioSequenceNumbers; - AudioNoiseGate _noiseGate; + AudioGate _audioGate; + bool _audioGateOpen { false }; bool _isNoiseGateEnabled { false }; CodecPluginPointer _codec; From f3797798d3083af1aafb26739622bd0f3a9984f0 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Tue, 23 May 2017 15:33:37 -0700 Subject: [PATCH 40/49] Cleanup --- assignment-client/src/Agent.cpp | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index ef396a06b3..469ca9f178 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -627,20 +627,6 @@ void Agent::encodeFrameOfZeros(QByteArray& encodedZeros) { } void Agent::computeLoudness(const QByteArray* decodedBuffer, QSharedPointer scriptableAvatar) { - //float loudness = 0.0f; - //if (decodedBuffer) { - // auto soundData = reinterpret_cast(decodedBuffer->constData()); - // int numFrames = decodedBuffer->size() / sizeof(int16_t); - // // now iterate and come up with average - // if (numFrames > 0) { - // for(int i = 0; i < numFrames; i++) { - // loudness += (float) std::abs(soundData[i]); - // } - // loudness /= numFrames; - // } - //} - //scriptableAvatar->setAudioLoudness(loudness); - float lastInputLoudness = 0.0f; if (decodedBuffer) { auto samples = reinterpret_cast(decodedBuffer->constData()); From 9cdd7cc8959e92abfa656de8caa9ede54c0dbb8f Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 23 May 2017 14:22:16 -0700 Subject: [PATCH 41/49] fix model check for filenames with periods --- libraries/image/src/image/Image.cpp | 2 +- tools/oven/src/DomainBaker.cpp | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/libraries/image/src/image/Image.cpp b/libraries/image/src/image/Image.cpp index 2a72304f5c..dcc65e8995 100644 --- a/libraries/image/src/image/Image.cpp +++ b/libraries/image/src/image/Image.cpp @@ -494,7 +494,7 @@ gpu::TexturePointer TextureUsage::process2DTextureColorFromImage(const QImage& s if (validAlpha) { processTextureAlpha(image, validAlpha, alphaAsMask); - // NOTE: This disables BC1a compression becuase it was producing odd artifacts on text textures + // NOTE: This disables BC1a compression because it was producing odd artifacts on text textures // for the tutorial. Instead we use BC3 (which is larger) but doesn't produce the same artifacts). alphaAsMask = false; } diff --git a/tools/oven/src/DomainBaker.cpp b/tools/oven/src/DomainBaker.cpp index fe0808de73..03bc350f42 100644 --- a/tools/oven/src/DomainBaker.cpp +++ b/tools/oven/src/DomainBaker.cpp @@ -167,15 +167,15 @@ void DomainBaker::enumerateEntities() { // check if the file pointed to by this URL is a bakeable model, by comparing extensions auto modelFileName = modelURL.fileName(); - static const QStringList BAKEABLE_MODEL_EXTENSIONS { ".fbx" }; - auto completeLowerExtension = modelFileName.mid(modelFileName.indexOf('.')).toLower(); - + static const QString BAKEABLE_MODEL_EXTENSION { ".fbx" }; static const QString BAKED_MODEL_EXTENSION = ".baked.fbx"; - if (BAKEABLE_MODEL_EXTENSIONS.contains(completeLowerExtension) || - (_shouldRebakeOriginals && completeLowerExtension == BAKED_MODEL_EXTENSION)) { + bool isBakedFBX = modelFileName.endsWith(BAKED_MODEL_EXTENSION, Qt::CaseInsensitive); + bool isUnbakedFBX = modelFileName.endsWith(BAKEABLE_MODEL_EXTENSION, Qt::CaseInsensitive) && !isBakedFBX; - if (completeLowerExtension == BAKED_MODEL_EXTENSION) { + if (isUnbakedFBX || (_shouldRebakeOriginals && isBakedFBX)) { + + if (isBakedFBX) { // grab a URL to the original, that we assume is stored a directory up, in the "original" folder // with just the fbx extension qDebug() << "Re-baking original for" << modelURL; From 3d67978caa9b81bca11f0585a69dbf8521059f2c Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Tue, 23 May 2017 15:53:38 -0700 Subject: [PATCH 42/49] Delete the old noise gate --- libraries/audio/src/AudioNoiseGate.cpp | 164 ------------------------- libraries/audio/src/AudioNoiseGate.h | 48 -------- 2 files changed, 212 deletions(-) delete mode 100644 libraries/audio/src/AudioNoiseGate.cpp delete mode 100644 libraries/audio/src/AudioNoiseGate.h diff --git a/libraries/audio/src/AudioNoiseGate.cpp b/libraries/audio/src/AudioNoiseGate.cpp deleted file mode 100644 index 604897af8a..0000000000 --- a/libraries/audio/src/AudioNoiseGate.cpp +++ /dev/null @@ -1,164 +0,0 @@ -// -// AudioNoiseGate.cpp -// libraries/audio -// -// Created by Stephen Birarda on 2014-12-16. -// 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 -// - -#include "AudioNoiseGate.h" - -#include -#include - -#include "AudioConstants.h" - -const float AudioNoiseGate::CLIPPING_THRESHOLD = 0.90f; - -AudioNoiseGate::AudioNoiseGate() : - _lastLoudness(0.0f), - _didClipInLastBlock(false), - _dcOffset(0.0f), - _measuredFloor(0.0f), - _sampleCounter(0), - _isOpen(false), - _blocksToClose(0) {} - -void AudioNoiseGate::removeDCOffset(int16_t* samples, int numSamples) { - // - // DC Offset correction - // - // Measure the DC offset over a trailing number of blocks, and remove it from the input signal. - // This causes the noise background measurements and server muting to be more accurate. Many off-board - // ADC's have a noticeable DC offset. - // - const float DC_OFFSET_AVERAGING = 0.99f; - float measuredDcOffset = 0.0f; - // Remove trailing DC offset from samples - for (int i = 0; i < numSamples; i++) { - measuredDcOffset += samples[i]; - samples[i] -= (int16_t) _dcOffset; - } - // Update measured DC offset - measuredDcOffset /= numSamples; - if (_dcOffset == 0.0f) { - // On first block, copy over measured offset - _dcOffset = measuredDcOffset; - } else { - _dcOffset = DC_OFFSET_AVERAGING * _dcOffset + (1.0f - DC_OFFSET_AVERAGING) * measuredDcOffset; - } -} - -void AudioNoiseGate::gateSamples(int16_t* samples, int numSamples) { - // - // Impose Noise Gate - // - // The Noise Gate is used to reject constant background noise by measuring the noise - // floor observed at the microphone and then opening the 'gate' to allow microphone - // signals to be transmitted when the microphone samples average level exceeds a multiple - // of the noise floor. - // - // NOISE_GATE_HEIGHT: How loud you have to speak relative to noise background to open the gate. - // Make this value lower for more sensitivity and less rejection of noise. - // NOISE_GATE_WIDTH: The number of samples in an audio block for which the height must be exceeded - // to open the gate. - // NOISE_GATE_CLOSE_BLOCK_DELAY: Once the noise is below the gate height for the block, how many blocks - // will we wait before closing the gate. - // NOISE_GATE_BLOCKS_TO_AVERAGE: How many audio blocks should we average together to compute noise floor. - // More means better rejection but also can reject continuous things like singing. - // NUMBER_OF_NOISE_SAMPLE_BLOCKS: How often should we re-evaluate the noise floor? - - float loudness = 0; - int thisSample = 0; - int samplesOverNoiseGate = 0; - - const float NOISE_GATE_HEIGHT = 7.0f; - const int NOISE_GATE_WIDTH = 5; - const int NOISE_GATE_CLOSE_BLOCK_DELAY = 5; - const int NOISE_GATE_BLOCKS_TO_AVERAGE = 5; - - // Check clipping, and check if should open noise gate - _didClipInLastBlock = false; - - for (int i = 0; i < numSamples; i++) { - thisSample = std::abs(samples[i]); - if (thisSample >= ((float) AudioConstants::MAX_SAMPLE_VALUE * CLIPPING_THRESHOLD)) { - _didClipInLastBlock = true; - } - - loudness += thisSample; - // Noise Reduction: Count peaks above the average loudness - if (thisSample > (_measuredFloor * NOISE_GATE_HEIGHT)) { - samplesOverNoiseGate++; - } - } - - _lastLoudness = fabs(loudness / numSamples); - - // If Noise Gate is enabled, check and turn the gate on and off - float averageOfAllSampleBlocks = 0.0f; - _sampleBlocks[_sampleCounter++] = _lastLoudness; - if (_sampleCounter == NUMBER_OF_NOISE_SAMPLE_BLOCKS) { - float smallestSample = std::numeric_limits::max(); - for (int i = 0; i <= NUMBER_OF_NOISE_SAMPLE_BLOCKS - NOISE_GATE_BLOCKS_TO_AVERAGE; i += NOISE_GATE_BLOCKS_TO_AVERAGE) { - float thisAverage = 0.0f; - for (int j = i; j < i + NOISE_GATE_BLOCKS_TO_AVERAGE; j++) { - thisAverage += _sampleBlocks[j]; - averageOfAllSampleBlocks += _sampleBlocks[j]; - } - thisAverage /= NOISE_GATE_BLOCKS_TO_AVERAGE; - - if (thisAverage < smallestSample) { - smallestSample = thisAverage; - } - } - averageOfAllSampleBlocks /= NUMBER_OF_NOISE_SAMPLE_BLOCKS; - _measuredFloor = smallestSample; - _sampleCounter = 0; - - } - - _closedInLastBlock = false; - _openedInLastBlock = false; - - if (samplesOverNoiseGate > NOISE_GATE_WIDTH) { - _openedInLastBlock = !_isOpen; - _isOpen = true; - _blocksToClose = NOISE_GATE_CLOSE_BLOCK_DELAY; - } else { - if (--_blocksToClose == 0) { - _closedInLastBlock = _isOpen; - _isOpen = false; - } - } - if (!_isOpen) { - // First block after being closed gets faded to silence, we fade across - // the entire block on fading out. All subsequent blocks are muted by being slammed - // to zeros - if (_closedInLastBlock) { - float fadeSlope = (1.0f / numSamples); - for (int i = 0; i < numSamples; i++) { - float fadedSample = (1.0f - ((float)i * fadeSlope)) * (float)samples[i]; - samples[i] = (int16_t)fadedSample; - } - } else { - memset(samples, 0, numSamples * sizeof(int16_t)); - } - _lastLoudness = 0; - } - - if (_openedInLastBlock) { - // would be nice to do a little crossfade from silence, but we only want to fade - // across the first 1/10th of the block, because we don't want to miss early - // transients. - int fadeSamples = numSamples / 10; // fade over 1/10th of the samples - float fadeSlope = (1.0f / fadeSamples); - for (int i = 0; i < fadeSamples; i++) { - float fadedSample = (float)i * fadeSlope * (float)samples[i]; - samples[i] = (int16_t)fadedSample; - } - } -} diff --git a/libraries/audio/src/AudioNoiseGate.h b/libraries/audio/src/AudioNoiseGate.h deleted file mode 100644 index 8430f120e5..0000000000 --- a/libraries/audio/src/AudioNoiseGate.h +++ /dev/null @@ -1,48 +0,0 @@ -// -// AudioNoiseGate.h -// libraries/audio -// -// Created by Stephen Birarda on 2014-12-16. -// 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 -// - -#ifndef hifi_AudioNoiseGate_h -#define hifi_AudioNoiseGate_h - -#include - -const int NUMBER_OF_NOISE_SAMPLE_BLOCKS = 300; - -class AudioNoiseGate { -public: - AudioNoiseGate(); - - void gateSamples(int16_t* samples, int numSamples); - void removeDCOffset(int16_t* samples, int numSamples); - - bool clippedInLastBlock() const { return _didClipInLastBlock; } - bool closedInLastBlock() const { return _closedInLastBlock; } - bool openedInLastBlock() const { return _openedInLastBlock; } - bool isOpen() const { return _isOpen; } - float getMeasuredFloor() const { return _measuredFloor; } - float getLastLoudness() const { return _lastLoudness; } - - static const float CLIPPING_THRESHOLD; - -private: - float _lastLoudness; - bool _didClipInLastBlock; - float _dcOffset; - float _measuredFloor; - float _sampleBlocks[NUMBER_OF_NOISE_SAMPLE_BLOCKS]; - int _sampleCounter; - bool _isOpen; - bool _closedInLastBlock { false }; - bool _openedInLastBlock { false }; - int _blocksToClose; -}; - -#endif // hifi_AudioNoiseGate_h From 6490c52245ee004e43000854b7e05a0dcfe33743 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 24 May 2017 13:02:54 -0700 Subject: [PATCH 43/49] Do not allow divide by zero on assigning mip data --- libraries/gpu/src/gpu/Texture.cpp | 36 +++++++++++++++---------------- libraries/gpu/src/gpu/Texture.h | 2 +- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/libraries/gpu/src/gpu/Texture.cpp b/libraries/gpu/src/gpu/Texture.cpp index 4dd40eb861..4b836512c4 100755 --- a/libraries/gpu/src/gpu/Texture.cpp +++ b/libraries/gpu/src/gpu/Texture.cpp @@ -123,42 +123,40 @@ bool MemoryStorage::isMipAvailable(uint16 level, uint8 face) const { return (mipFace && mipFace->getSize()); } -bool MemoryStorage::allocateMip(uint16 level) { - bool changed = false; +void MemoryStorage::allocateMip(uint16 level) { + auto faceCount = Texture::NUM_FACES_PER_TYPE[getType()]; if (level >= _mips.size()) { - _mips.resize(level+1, std::vector(Texture::NUM_FACES_PER_TYPE[getType()])); - changed = true; + _mips.resize(level + 1, std::vector(faceCount)); } auto& mip = _mips[level]; - for (auto& face : mip) { - if (!face) { - changed = true; - } + if (mip.size() != faceCount) { + mip.resize(faceCount); } - bumpStamp(); - - return changed; } void MemoryStorage::assignMipData(uint16 level, const storage::StoragePointer& storagePointer) { - allocateMip(level); auto& mip = _mips[level]; + auto faceCount = Texture::NUM_FACES_PER_TYPE[getType()]; + // here we grabbed an array of faces // The bytes assigned here are supposed to contain all the faces bytes of the mip. // For tex1D, 2D, 3D there is only one face // For Cube, we expect the 6 faces in the order X+, X-, Y+, Y-, Z+, Z- - auto sizePerFace = storagePointer->size() / mip.size(); - size_t offset = 0; - for (auto& face : mip) { - face = storagePointer->createView(sizePerFace, offset); - offset += sizePerFace; - } + auto sizePerFace = storagePointer->size() / faceCount; - bumpStamp(); + if (sizePerFace > 0) { + size_t offset = 0; + for (auto& face : mip) { + face = storagePointer->createView(sizePerFace, offset); + offset += sizePerFace; + } + + bumpStamp(); + } } diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h index 98a4add3c8..211dc7b8ce 100755 --- a/libraries/gpu/src/gpu/Texture.h +++ b/libraries/gpu/src/gpu/Texture.h @@ -304,7 +304,7 @@ public: bool isMipAvailable(uint16 level, uint8 face = 0) const override; protected: - bool allocateMip(uint16 level); + void allocateMip(uint16 level); std::vector> _mips; // an array of mips, each mip is an array of faces }; From 14ff6a06b42ae3706c1058387b9d43d1529b89ac Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 24 May 2017 13:16:26 -0700 Subject: [PATCH 44/49] require a valid access token and expiry to attempt refresh --- libraries/networking/src/AccountManager.cpp | 41 +++++++++++---------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index 471448d596..6617c8f945 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -193,7 +193,6 @@ void AccountManager::setAuthURL(const QUrl& authURL) { // prepare to refresh our token if it is about to expire if (needsToRefreshToken()) { - qCDebug(networking) << "Refreshing access token since it will be expiring soon."; refreshAccessToken(); } @@ -457,7 +456,6 @@ bool AccountManager::hasValidAccessToken() { } else { if (!_isWaitingForTokenRefresh && needsToRefreshToken()) { - qCDebug(networking) << "Refreshing access token since it will be expiring soon."; refreshAccessToken(); } @@ -477,7 +475,7 @@ bool AccountManager::checkAndSignalForAccessToken() { } bool AccountManager::needsToRefreshToken() { - if (!_accountInfo.getAccessToken().token.isEmpty()) { + if (!_accountInfo.getAccessToken().token.isEmpty() && _accountInfo.getAccessToken().expiryTimestamp > 0) { qlonglong expireThreshold = QDateTime::currentDateTime().addSecs(1 * 60 * 60).toMSecsSinceEpoch(); return _accountInfo.getAccessToken().expiryTimestamp < expireThreshold; } else { @@ -555,28 +553,33 @@ void AccountManager::requestAccessTokenWithSteam(QByteArray authSessionTicket) { void AccountManager::refreshAccessToken() { - _isWaitingForTokenRefresh = true; + // we can't refresh our access token if we don't have a refresh token, so check for that first + if (!_accountInfo.getAccessToken().refreshToken.isEmpty()) { + qCDebug(networking) << "Refreshing access token since it will be expiring soon."; - QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + _isWaitingForTokenRefresh = true; - QNetworkRequest request; - request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); - request.setHeader(QNetworkRequest::UserAgentHeader, _userAgentGetter()); + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); - QUrl grantURL = _authURL; - grantURL.setPath("/oauth/token"); + QNetworkRequest request; + request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); + request.setHeader(QNetworkRequest::UserAgentHeader, _userAgentGetter()); - QByteArray postData; - postData.append("grant_type=refresh_token&"); - postData.append("refresh_token=" + QUrl::toPercentEncoding(_accountInfo.getAccessToken().refreshToken) + "&"); - postData.append("scope=" + ACCOUNT_MANAGER_REQUESTED_SCOPE); + QUrl grantURL = _authURL; + grantURL.setPath("/oauth/token"); - request.setUrl(grantURL); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + QByteArray postData; + postData.append("grant_type=refresh_token&"); + postData.append("refresh_token=" + QUrl::toPercentEncoding(_accountInfo.getAccessToken().refreshToken) + "&"); + postData.append("scope=" + ACCOUNT_MANAGER_REQUESTED_SCOPE); - QNetworkReply* requestReply = networkAccessManager.post(request, postData); - connect(requestReply, &QNetworkReply::finished, this, &AccountManager::refreshAccessTokenFinished); - connect(requestReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(refreshAccessTokenError(QNetworkReply::NetworkError))); + request.setUrl(grantURL); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + + QNetworkReply* requestReply = networkAccessManager.post(request, postData); + connect(requestReply, &QNetworkReply::finished, this, &AccountManager::refreshAccessTokenFinished); + connect(requestReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(refreshAccessTokenError(QNetworkReply::NetworkError))); + } } void AccountManager::requestAccessTokenFinished() { From 9cd11a32e2c7e7d494a2237cf4b31caa0d1da7b6 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 24 May 2017 13:47:24 -0700 Subject: [PATCH 45/49] add a debug log for failed token refresh --- libraries/networking/src/AccountManager.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index 6617c8f945..6266ad0f89 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -579,6 +579,9 @@ void AccountManager::refreshAccessToken() { QNetworkReply* requestReply = networkAccessManager.post(request, postData); connect(requestReply, &QNetworkReply::finished, this, &AccountManager::refreshAccessTokenFinished); connect(requestReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(refreshAccessTokenError(QNetworkReply::NetworkError))); + } else { + qCWarning(networking) << "Cannot refresh access token without refresh token." + << "Access token will need to be manually refreshed."; } } From 504faf430d6b9a7f3d66ba3573534b5197939438 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 19 May 2017 12:50:29 +1200 Subject: [PATCH 46/49] Fix rotation axes of stopwatch hands --- .../marketplace/stopwatch/stopwatchServer.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/unpublishedScripts/marketplace/stopwatch/stopwatchServer.js b/unpublishedScripts/marketplace/stopwatch/stopwatchServer.js index 8b36b48cde..925db565c3 100644 --- a/unpublishedScripts/marketplace/stopwatch/stopwatchServer.js +++ b/unpublishedScripts/marketplace/stopwatch/stopwatchServer.js @@ -67,11 +67,11 @@ self.tickIntervalID = null; } Entities.editEntity(self.secondHandID, { - rotation: Quat.fromPitchYawRollDegrees(0, 0, 0), + localRotation: Quat.fromPitchYawRollDegrees(0, 0, 0), angularVelocity: { x: 0, y: 0, z: 0 }, }); Entities.editEntity(self.minuteHandID, { - rotation: Quat.fromPitchYawRollDegrees(0, 0, 0), + localRotation: Quat.fromPitchYawRollDegrees(0, 0, 0), angularVelocity: { x: 0, y: 0, z: 0 }, }); self.isActive = false; @@ -100,11 +100,12 @@ seconds++; const degreesPerTick = -360 / 60; Entities.editEntity(self.secondHandID, { - rotation: Quat.fromPitchYawRollDegrees(0, seconds * degreesPerTick, 0), + localRotation: Quat.fromPitchYawRollDegrees(0, seconds * degreesPerTick, 0), }); + if (seconds % 60 == 0) { Entities.editEntity(self.minuteHandID, { - rotation: Quat.fromPitchYawRollDegrees(0, (seconds / 60) * degreesPerTick, 0), + localRotation: Quat.fromPitchYawRollDegrees(0, (seconds / 60) * degreesPerTick, 0), }); Audio.playSound(self.chimeSound, { position: self.getStopwatchPosition(), From 05f69ade32432fd76a44477fe9dc8c946c7e01e8 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Mon, 22 May 2017 18:28:37 -0700 Subject: [PATCH 47/49] Never store an address with an empty host --- libraries/networking/src/AddressManager.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 8fc1d66cf1..99e1962387 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -136,7 +136,13 @@ void AddressManager::goForward() { } void AddressManager::storeCurrentAddress() { - currentAddressHandle.set(currentAddress()); + auto url = currentAddress(); + + if (!url.host().isEmpty()) { + currentAddressHandle.set(url); + } else { + qCWarning(networking) << "Ignoring attempt to save current address with an empty host" << url; + } } QString AddressManager::currentPath(bool withOrientation) const { From 80e641b6d4bf6891b534130e8e43b629fd98c77d Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 25 May 2017 09:47:02 -0700 Subject: [PATCH 48/49] fix code that was using old raypickInfo.properties field --- .../system/controllers/handControllerGrab.js | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index f5c3e6eafa..0a08b60281 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -1036,8 +1036,8 @@ function getControllerJointIndex(hand) { "_CONTROLLER_RIGHTHAND" : "_CONTROLLER_LEFTHAND"); } - - return MyAvatar.getJointIndex("Head"); + + return MyAvatar.getJointIndex("Head"); } // global EquipHotspotBuddy instance @@ -1331,7 +1331,7 @@ function MyController(hand) { if (this.stylus) { return; } - + var stylusProperties = { name: "stylus", url: Script.resourcesPath() + "meshes/tablet-stylus-fat.fbx", @@ -2134,7 +2134,7 @@ function MyController(hand) { return null; } }; - + this.chooseNearEquipHotspotsForFarToNearEquip = function(candidateEntities, distance) { var equippableHotspots = flatten(candidateEntities.map(function(entityID) { return _this.collectEquipHotspots(entityID); @@ -2291,7 +2291,7 @@ function MyController(hand) { return; } } - + if (isInEditMode()) { this.searchIndicatorOn(rayPickInfo.searchRay); if (this.triggerSmoothedGrab()) { @@ -2347,10 +2347,11 @@ function MyController(hand) { var avatar = AvatarList.getAvatar(this.otherGrabbingUUID); var IN_FRONT_OF_AVATAR = { x: 0, y: 0.2, z: 0.4 }; // Up from hips and in front of avatar. var startPosition = Vec3.sum(avatar.position, Vec3.multiplyQbyV(avatar.rotation, IN_FRONT_OF_AVATAR)); - var finishPisition = Vec3.sum(rayPickInfo.properties.position, // Entity's centroid. - Vec3.multiplyQbyV(rayPickInfo.properties.rotation , - Vec3.multiplyVbyV(rayPickInfo.properties.dimensions, - Vec3.subtract(DEFAULT_REGISTRATION_POINT, rayPickInfo.properties.registrationPoint)))); + var rayHitProps = entityPropertiesCache.getProps(rayPickInfo.entityID); + var finishPisition = Vec3.sum(rayHitProps.position, // Entity's centroid. + Vec3.multiplyQbyV(rayHitProps.rotation , + Vec3.multiplyVbyV(rayHitProps.dimensions, + Vec3.subtract(DEFAULT_REGISTRATION_POINT, rayHitProps.registrationPoint)))); this.otherGrabbingLineOn(startPosition, finishPisition, COLORS_GRAB_DISTANCE_HOLD); } else { this.otherGrabbingLineOff(); @@ -3442,14 +3443,14 @@ function MyController(hand) { }; this.offEnter = function() { - // Reuse the existing search distance if lasers were active since + // Reuse the existing search distance if lasers were active since // they will be shown in OFF state while in edit mode. var existingSearchDistance = this.searchSphereDistance; this.release(); - + if (isInEditMode()) { this.searchSphereDistance = existingSearchDistance; - } + } }; this.entityLaserTouchingEnter = function() { @@ -4154,7 +4155,7 @@ var updateWrapper = function () { } Script.setTimeout(updateWrapper, UPDATE_SLEEP_MS); -} +}; Script.setTimeout(updateWrapper, UPDATE_SLEEP_MS); function cleanup() { From de40604042c52902d8b03fe5e9d230458322d247 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 25 May 2017 09:54:40 -0700 Subject: [PATCH 49/49] whitespace --- scripts/system/controllers/handControllerGrab.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 0a08b60281..993cf22d83 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -2349,7 +2349,7 @@ function MyController(hand) { var startPosition = Vec3.sum(avatar.position, Vec3.multiplyQbyV(avatar.rotation, IN_FRONT_OF_AVATAR)); var rayHitProps = entityPropertiesCache.getProps(rayPickInfo.entityID); var finishPisition = Vec3.sum(rayHitProps.position, // Entity's centroid. - Vec3.multiplyQbyV(rayHitProps.rotation , + Vec3.multiplyQbyV(rayHitProps.rotation, Vec3.multiplyVbyV(rayHitProps.dimensions, Vec3.subtract(DEFAULT_REGISTRATION_POINT, rayHitProps.registrationPoint)))); this.otherGrabbingLineOn(startPosition, finishPisition, COLORS_GRAB_DISTANCE_HOLD);