Loading media/libeffects/hapticgenerator/EffectHapticGenerator.cpp +49 −32 Original line number Diff line number Diff line Loading @@ -101,6 +101,16 @@ int HapticGenerator_Init(struct HapticGeneratorContext *context) { context->param.audioChannelCount = 0; context->param.maxHapticIntensity = os::HapticScale::MUTE; context->param.resonantFrequency = 150.0f; context->param.bpfQ = 1.0f; context->param.slowEnvNormalizationPower = -0.8f; context->param.bsfZeroQ = 8.0f; context->param.bsfPoleQ = 4.0f; context->param.distortionCornerFrequency = 300.0f; context->param.distortionInputGain = 0.3f; context->param.distortionCubeThreshold = 0.1f; context->param.distortionOutputGain = 1.5f; context->state = HAPTICGENERATOR_STATE_INITIALIZED; return 0; } Loading Loading @@ -128,16 +138,17 @@ void addBiquadFilter( */ void HapticGenerator_buildProcessingChain( std::vector<std::function<void(float*, const float*, size_t)>>& processingChain, struct HapticGeneratorProcessorsRecord& processorsRecord, float sampleRate, size_t channelCount) { float highPassCornerFrequency = 100.0f; struct HapticGeneratorProcessorsRecord& processorsRecord, float sampleRate, const struct HapticGeneratorParam* param) { const size_t channelCount = param->hapticChannelCount; float highPassCornerFrequency = 50.0f; auto hpf = createHPF2(highPassCornerFrequency, sampleRate, channelCount); addBiquadFilter(processingChain, processorsRecord, hpf); float lowPassCornerFrequency = 3000.0f; float lowPassCornerFrequency = 9000.0f; auto lpf = createLPF2(lowPassCornerFrequency, sampleRate, channelCount); addBiquadFilter(processingChain, processorsRecord, lpf); auto ramp = std::make_shared<Ramp>(channelCount); auto ramp = std::make_shared<Ramp>(channelCount); // ramp = half-wave rectifier. // The process chain captures the shared pointer of the ramp in lambda. It will be the only // reference to the ramp. // The process record will keep a weak pointer to the ramp so that it is possible to access Loading @@ -154,43 +165,45 @@ void HapticGenerator_buildProcessingChain( lpf = createLPF2(lowPassCornerFrequency, sampleRate, channelCount); addBiquadFilter(processingChain, processorsRecord, lpf); lowPassCornerFrequency = 5.0f; float normalizationPower = -0.3f; lowPassCornerFrequency = 400.0f; lpf = createLPF2(lowPassCornerFrequency, sampleRate, channelCount); addBiquadFilter(processingChain, processorsRecord, lpf); lowPassCornerFrequency = 500.0f; lpf = createLPF2(lowPassCornerFrequency, sampleRate, channelCount); addBiquadFilter(processingChain, processorsRecord, lpf); auto bpf = createBPF(param->resonantFrequency, param->bpfQ, sampleRate, channelCount); addBiquadFilter(processingChain, processorsRecord, bpf); float normalizationPower = param->slowEnvNormalizationPower; // The process chain captures the shared pointer of the slow envelope in lambda. It will // be the only reference to the slow envelope. // The process record will keep a weak pointer to the slow envelope so that it is possible // to access the slow envelope outside of the process chain. auto slowEnv = std::make_shared<SlowEnvelope>( lowPassCornerFrequency, sampleRate, normalizationPower, channelCount); auto slowEnv = std::make_shared<SlowEnvelope>( // SlowEnvelope = partial normalizer, or AGC. 5.0f /*envCornerFrequency*/, sampleRate, normalizationPower, 0.01f /*envOffset*/, channelCount); processorsRecord.slowEnvs.push_back(slowEnv); processingChain.push_back([slowEnv](float *out, const float *in, size_t frameCount) { slowEnv->process(out, in, frameCount); }); lowPassCornerFrequency = 400.0f; lpf = createLPF2(lowPassCornerFrequency, sampleRate, channelCount); addBiquadFilter(processingChain, processorsRecord, lpf); lowPassCornerFrequency = 500.0f; lpf = createLPF2(lowPassCornerFrequency, sampleRate, channelCount); addBiquadFilter(processingChain, processorsRecord, lpf); auto apf = createAPF2(400.0f, 200.0f, sampleRate, channelCount); addBiquadFilter(processingChain, processorsRecord, apf); apf = createAPF2(100.0f, 50.0f, sampleRate, channelCount); addBiquadFilter(processingChain, processorsRecord, apf); float allPassCornerFrequency = 25.0f; apf = createAPF(allPassCornerFrequency, sampleRate, channelCount); addBiquadFilter(processingChain, processorsRecord, apf); float resonantFrequency = 150.0f; float bandpassQ = 1.0f; auto bpf = createBPF(resonantFrequency, bandpassQ, sampleRate, channelCount); addBiquadFilter(processingChain, processorsRecord, bpf); float zeroQ = 8.0f; float poleQ = 4.0f; auto bsf = createBSF(resonantFrequency, zeroQ, poleQ, sampleRate, channelCount); auto bsf = createBSF( param->resonantFrequency, param->bsfZeroQ, param->bsfPoleQ, sampleRate, channelCount); addBiquadFilter(processingChain, processorsRecord, bsf); // The process chain captures the shared pointer of the Distortion in lambda. It will // be the only reference to the Distortion. // The process record will keep a weak pointer to the Distortion so that it is possible // to access the Distortion outside of the process chain. auto distortion = std::make_shared<Distortion>( param->distortionCornerFrequency, sampleRate, param->distortionInputGain, param->distortionCubeThreshold, param->distortionOutputGain, channelCount); processorsRecord.distortions.push_back(distortion); processingChain.push_back([distortion](float *out, const float *in, size_t frameCount) { distortion->process(out, in, frameCount); }); } int HapticGenerator_Configure(struct HapticGeneratorContext *context, effect_config_t *config) { Loading @@ -206,6 +219,7 @@ int HapticGenerator_Configure(struct HapticGeneratorContext *context, effect_con context->processorsRecord.filters.clear(); context->processorsRecord.ramps.clear(); context->processorsRecord.slowEnvs.clear(); context->processorsRecord.distortions.clear(); memcpy(&context->config, config, sizeof(effect_config_t)); context->param.audioChannelCount = audio_channel_count_from_out_mask( ((audio_channel_mask_t) config->inputCfg.channels) & ~AUDIO_CHANNEL_HAPTIC_ALL); Loading @@ -224,7 +238,7 @@ int HapticGenerator_Configure(struct HapticGeneratorContext *context, effect_con HapticGenerator_buildProcessingChain(context->processingChain, context->processorsRecord, config->inputCfg.samplingRate, context->param.hapticChannelCount); &context->param); } return 0; } Loading @@ -236,6 +250,9 @@ int HapticGenerator_Reset(struct HapticGeneratorContext *context) { for (auto& slowEnv : context->processorsRecord.slowEnvs) { slowEnv->clear(); } for (auto& distortion : context->processorsRecord.distortions) { distortion->clear(); } return 0; } Loading media/libeffects/hapticgenerator/EffectHapticGenerator.h +11 −0 Original line number Diff line number Diff line Loading @@ -51,6 +51,16 @@ struct HapticGeneratorParam { // A map from track id to haptic intensity. std::map<int, os::HapticScale> id2Intensity; os::HapticScale maxHapticIntensity; // max intensity will be used to scale haptic data. float resonantFrequency; float bpfQ; float slowEnvNormalizationPower; float bsfZeroQ; float bsfPoleQ; float distortionCornerFrequency; float distortionInputGain; float distortionCubeThreshold; float distortionOutputGain; }; // A structure to keep all shared pointers for all processors in HapticGenerator. Loading @@ -58,6 +68,7 @@ struct HapticGeneratorProcessorsRecord { std::vector<std::shared_ptr<HapticBiquadFilter>> filters; std::vector<std::shared_ptr<Ramp>> ramps; std::vector<std::shared_ptr<SlowEnvelope>> slowEnvs; std::vector<std::shared_ptr<Distortion>> distortions; }; // A structure to keep all the context for HapticGenerator. Loading media/libeffects/hapticgenerator/Processors.cpp +105 −57 Original line number Diff line number Diff line Loading @@ -83,30 +83,92 @@ SlowEnvelope::SlowEnvelope( float cornerFrequency, float sampleRate, float normalizationPower, float envOffset, size_t channelCount) : mLpf(createLPF(cornerFrequency, sampleRate, channelCount)), mNormalizationPower(normalizationPower), mChannelCount(channelCount), mEnv(0.25 * (sampleRate / (2 * M_PI * cornerFrequency))) {} mEnvOffset(envOffset), mChannelCount(channelCount) {} void SlowEnvelope::process(float* out, const float* in, size_t frameCount) { size_t sampleCount = frameCount * mChannelCount; if (sampleCount > mLpfInBuffer.size()) { mLpfInBuffer.resize(sampleCount, mEnv); if (sampleCount > mLpfOutBuffer.size()) { mLpfOutBuffer.resize(sampleCount); mLpfInBuffer.resize(sampleCount); } for (size_t i = 0; i < sampleCount; ++i) { mLpfInBuffer[i] = fabs(in[i]); } mLpf->process(mLpfOutBuffer.data(), mLpfInBuffer.data(), frameCount); for (size_t i = 0; i < sampleCount; ++i) { *out = *in * pow(mLpfOutBuffer[i], mNormalizationPower); out++; in++; out[i] = in[i] * pow(mLpfOutBuffer[i] + mEnvOffset, mNormalizationPower); } } void SlowEnvelope::setNormalizationPower(float normalizationPower) { mNormalizationPower = normalizationPower; } void SlowEnvelope::clear() { mLpf->clear(); } // Implementation of distortion Distortion::Distortion( float cornerFrequency, float sampleRate, float inputGain, float cubeThreshold, float outputGain, size_t channelCount) : mLpf(createLPF2(cornerFrequency, sampleRate, channelCount)), mSampleRate(sampleRate), mCornerFrequency(cornerFrequency), mInputGain(inputGain), mCubeThreshold(cubeThreshold), mOutputGain(outputGain), mChannelCount(channelCount) {} void Distortion::process(float *out, const float *in, size_t frameCount) { size_t sampleCount = frameCount * mChannelCount; if (sampleCount > mLpfInBuffer.size()) { mLpfInBuffer.resize(sampleCount); } for (size_t i = 0; i < sampleCount; ++i) { const float x = mInputGain * in[i]; mLpfInBuffer[i] = x * x * x / (mCubeThreshold + x * x); // "Coring" nonlinearity. } mLpf->process(out, mLpfInBuffer.data(), frameCount); // Reduce 3*F components. for (size_t i = 0; i < sampleCount; ++i) { const float x = out[i]; out[i] = mOutputGain * x / (1.0f + fabs(x)); // Soft limiter. } } void Distortion::setCornerFrequency(float cornerFrequency) { mCornerFrequency = cornerFrequency; BiquadFilterCoefficients coefficient = lpfCoefs(cornerFrequency, mSampleRate); mLpf->setCoefficients(coefficient); } void Distortion::setInputGain(float inputGain) { mInputGain = inputGain; } void Distortion::setCubeThrehold(float cubeThreshold) { mCubeThreshold = cubeThreshold; } void Distortion::setOutputGain(float outputGain) { mOutputGain = outputGain; } void Distortion::clear() { mLpf->clear(); } // Implementation of helper functions BiquadFilterCoefficients cascadeFirstOrderFilters(const BiquadFilterCoefficients &coefs1, Loading Loading @@ -134,6 +196,40 @@ BiquadFilterCoefficients lpfCoefs(const float cornerFrequency, const float sampl return coefficient; } BiquadFilterCoefficients bpfCoefs(const float ringingFrequency, const float q, const float sampleRate) { BiquadFilterCoefficients coefficient; const auto [real, img] = getComplexPoleZ(ringingFrequency, q, sampleRate); // Note: this is not a standard cookbook BPF, but a low pass filter with zero at DC coefficient[0] = 1.0f; coefficient[1] = -1.0f; coefficient[2] = 0.0f; coefficient[3] = -2 * real; coefficient[4] = real * real + img * img; return coefficient; } BiquadFilterCoefficients bsfCoefs(const float ringingFrequency, const float sampleRate, const float zq, const float pq) { BiquadFilterCoefficients coefficient; const auto [zeroReal, zeroImg] = getComplexPoleZ(ringingFrequency, zq, sampleRate); float zeroCoeff1 = -2 * zeroReal; float zeroCoeff2 = zeroReal* zeroReal + zeroImg * zeroImg; const auto [poleReal, poleImg] = getComplexPoleZ(ringingFrequency, pq, sampleRate); float poleCoeff1 = -2 * poleReal; float poleCoeff2 = poleReal * poleReal + poleImg * poleImg; const float norm = (1.0f + poleCoeff1 + poleCoeff2) / (1.0f + zeroCoeff1 + zeroCoeff2); coefficient[0] = 1.0f * norm; coefficient[1] = zeroCoeff1 * norm; coefficient[2] = zeroCoeff2 * norm; coefficient[3] = poleCoeff1; coefficient[4] = poleCoeff2; return coefficient; } std::shared_ptr<HapticBiquadFilter> createLPF(const float cornerFrequency, const float sampleRate, const size_t channelCount) { Loading Loading @@ -166,47 +262,11 @@ std::shared_ptr<HapticBiquadFilter> createHPF2(const float cornerFrequency, channelCount, cascadeFirstOrderFilters(coefficient, coefficient)); } BiquadFilterCoefficients apfCoefs(const float cornerFrequency, const float sampleRate) { BiquadFilterCoefficients coefficient; float realPoleZ = getRealPoleZ(cornerFrequency, sampleRate); float zeroZ = 1.0f / realPoleZ; coefficient[0] = (1.0f - realPoleZ) / (1.0f - zeroZ); coefficient[1] = -coefficient[0] * zeroZ; coefficient[2] = 0.0f; coefficient[3] = -realPoleZ; coefficient[4] = 0.0f; return coefficient; } std::shared_ptr<HapticBiquadFilter> createAPF(const float cornerFrequency, const float sampleRate, const size_t channelCount) { BiquadFilterCoefficients coefficient = apfCoefs(cornerFrequency, sampleRate); return std::make_shared<HapticBiquadFilter>(channelCount, coefficient); } std::shared_ptr<HapticBiquadFilter> createAPF2(const float cornerFrequency1, const float cornerFrequency2, const float sampleRate, const size_t channelCount) { BiquadFilterCoefficients coefs1 = apfCoefs(cornerFrequency1, sampleRate); BiquadFilterCoefficients coefs2 = apfCoefs(cornerFrequency2, sampleRate); return std::make_shared<HapticBiquadFilter>( channelCount, cascadeFirstOrderFilters(coefs1, coefs2)); } std::shared_ptr<HapticBiquadFilter> createBPF(const float ringingFrequency, const float q, const float sampleRate, const size_t channelCount) { BiquadFilterCoefficients coefficient; const auto [real, img] = getComplexPoleZ(ringingFrequency, q, sampleRate); // Note: this is not a standard cookbook BPF, but a low pass filter with zero at DC coefficient[0] = 1.0f; coefficient[1] = -1.0f; coefficient[2] = 0.0f; coefficient[3] = -2 * real; coefficient[4] = real * real + img * img; BiquadFilterCoefficients coefficient = bpfCoefs(ringingFrequency, q, sampleRate); return std::make_shared<HapticBiquadFilter>(channelCount, coefficient); } Loading @@ -215,19 +275,7 @@ std::shared_ptr<HapticBiquadFilter> createBSF(const float ringingFrequency, const float pq, const float sampleRate, const size_t channelCount) { BiquadFilterCoefficients coefficient; const auto [zeroReal, zeroImg] = getComplexPoleZ(ringingFrequency, zq, sampleRate); float zeroCoeff1 = -2 * zeroReal; float zeroCoeff2 = zeroReal* zeroReal + zeroImg * zeroImg; const auto [poleReal, poleImg] = getComplexPoleZ(ringingFrequency, pq, sampleRate); float poleCoeff1 = -2 * poleReal; float poleCoeff2 = poleReal * poleReal + poleImg * poleImg; const float norm = (1.0f + poleCoeff1 + poleCoeff2) / (1.0f + zeroCoeff1 + zeroCoeff2); coefficient[0] = 1.0f * norm; coefficient[1] = zeroCoeff1 * norm; coefficient[2] = zeroCoeff2 * norm; coefficient[3] = poleCoeff1; coefficient[4] = poleCoeff2; BiquadFilterCoefficients coefficient = bsfCoefs(ringingFrequency, sampleRate, zq, pq); return std::make_shared<HapticBiquadFilter>(channelCount, coefficient); } Loading media/libeffects/hapticgenerator/Processors.h +45 −13 Original line number Diff line number Diff line Loading @@ -44,19 +44,50 @@ private: class SlowEnvelope { public: SlowEnvelope(float cornerFrequency, float sampleRate, float normalizationPower, size_t channelCount); float normalizationPower, float envOffset, size_t channelCount); void process(float *out, const float *in, size_t frameCount); void setNormalizationPower(float normalizationPower); void clear(); private: const std::shared_ptr<HapticBiquadFilter> mLpf; std::vector<float> mLpfInBuffer; std::vector<float> mLpfOutBuffer; const float mNormalizationPower; float mNormalizationPower; const float mEnvOffset; const float mChannelCount; const float mEnv; }; // A class providing a process function that compressively distorts a waveforms class Distortion { public: Distortion(float cornerFrequency, float sampleRate, float inputGain, float cubeThreshold, float outputGain, size_t channelCount); void process(float *out, const float *in, size_t frameCount); void setCornerFrequency(float cornerFrequency); void setInputGain(float inputGain); void setCubeThrehold(float cubeThreshold); void setOutputGain(float outputGain); void clear(); private: const std::shared_ptr<HapticBiquadFilter> mLpf; std::vector<float> mLpfInBuffer; float mSampleRate; float mCornerFrequency; float mInputGain; float mCubeThreshold; float mOutputGain; const size_t mChannelCount; }; // Helper functions Loading @@ -64,6 +95,17 @@ private: BiquadFilterCoefficients cascadeFirstOrderFilters(const BiquadFilterCoefficients &coefs1, const BiquadFilterCoefficients &coefs2); BiquadFilterCoefficients lpfCoefs(const float cornerFrequency, const float sampleRate); BiquadFilterCoefficients bpfCoefs(const float ringingFrequency, const float q, const float sampleRate); BiquadFilterCoefficients bsfCoefs(const float ringingFrequency, const float sampleRate, const float zq, const float pq); std::shared_ptr<HapticBiquadFilter> createLPF(const float cornerFrequency, const float sampleRate, const size_t channelCount); Loading @@ -78,16 +120,6 @@ std::shared_ptr<HapticBiquadFilter> createHPF2(const float cornerFrequency, const float sampleRate, const size_t channelCount); std::shared_ptr<HapticBiquadFilter> createAPF(const float cornerFrequency, const float sampleRate, const size_t channelCount); // Create two cascaded APF with two different corner frequency. std::shared_ptr<HapticBiquadFilter> createAPF2(const float cornerFrequency1, const float cornerFrequency2, const float sampleRate, const size_t channelCount); std::shared_ptr<HapticBiquadFilter> createBPF(const float ringingFrequency, const float q, const float sampleRate, Loading Loading
media/libeffects/hapticgenerator/EffectHapticGenerator.cpp +49 −32 Original line number Diff line number Diff line Loading @@ -101,6 +101,16 @@ int HapticGenerator_Init(struct HapticGeneratorContext *context) { context->param.audioChannelCount = 0; context->param.maxHapticIntensity = os::HapticScale::MUTE; context->param.resonantFrequency = 150.0f; context->param.bpfQ = 1.0f; context->param.slowEnvNormalizationPower = -0.8f; context->param.bsfZeroQ = 8.0f; context->param.bsfPoleQ = 4.0f; context->param.distortionCornerFrequency = 300.0f; context->param.distortionInputGain = 0.3f; context->param.distortionCubeThreshold = 0.1f; context->param.distortionOutputGain = 1.5f; context->state = HAPTICGENERATOR_STATE_INITIALIZED; return 0; } Loading Loading @@ -128,16 +138,17 @@ void addBiquadFilter( */ void HapticGenerator_buildProcessingChain( std::vector<std::function<void(float*, const float*, size_t)>>& processingChain, struct HapticGeneratorProcessorsRecord& processorsRecord, float sampleRate, size_t channelCount) { float highPassCornerFrequency = 100.0f; struct HapticGeneratorProcessorsRecord& processorsRecord, float sampleRate, const struct HapticGeneratorParam* param) { const size_t channelCount = param->hapticChannelCount; float highPassCornerFrequency = 50.0f; auto hpf = createHPF2(highPassCornerFrequency, sampleRate, channelCount); addBiquadFilter(processingChain, processorsRecord, hpf); float lowPassCornerFrequency = 3000.0f; float lowPassCornerFrequency = 9000.0f; auto lpf = createLPF2(lowPassCornerFrequency, sampleRate, channelCount); addBiquadFilter(processingChain, processorsRecord, lpf); auto ramp = std::make_shared<Ramp>(channelCount); auto ramp = std::make_shared<Ramp>(channelCount); // ramp = half-wave rectifier. // The process chain captures the shared pointer of the ramp in lambda. It will be the only // reference to the ramp. // The process record will keep a weak pointer to the ramp so that it is possible to access Loading @@ -154,43 +165,45 @@ void HapticGenerator_buildProcessingChain( lpf = createLPF2(lowPassCornerFrequency, sampleRate, channelCount); addBiquadFilter(processingChain, processorsRecord, lpf); lowPassCornerFrequency = 5.0f; float normalizationPower = -0.3f; lowPassCornerFrequency = 400.0f; lpf = createLPF2(lowPassCornerFrequency, sampleRate, channelCount); addBiquadFilter(processingChain, processorsRecord, lpf); lowPassCornerFrequency = 500.0f; lpf = createLPF2(lowPassCornerFrequency, sampleRate, channelCount); addBiquadFilter(processingChain, processorsRecord, lpf); auto bpf = createBPF(param->resonantFrequency, param->bpfQ, sampleRate, channelCount); addBiquadFilter(processingChain, processorsRecord, bpf); float normalizationPower = param->slowEnvNormalizationPower; // The process chain captures the shared pointer of the slow envelope in lambda. It will // be the only reference to the slow envelope. // The process record will keep a weak pointer to the slow envelope so that it is possible // to access the slow envelope outside of the process chain. auto slowEnv = std::make_shared<SlowEnvelope>( lowPassCornerFrequency, sampleRate, normalizationPower, channelCount); auto slowEnv = std::make_shared<SlowEnvelope>( // SlowEnvelope = partial normalizer, or AGC. 5.0f /*envCornerFrequency*/, sampleRate, normalizationPower, 0.01f /*envOffset*/, channelCount); processorsRecord.slowEnvs.push_back(slowEnv); processingChain.push_back([slowEnv](float *out, const float *in, size_t frameCount) { slowEnv->process(out, in, frameCount); }); lowPassCornerFrequency = 400.0f; lpf = createLPF2(lowPassCornerFrequency, sampleRate, channelCount); addBiquadFilter(processingChain, processorsRecord, lpf); lowPassCornerFrequency = 500.0f; lpf = createLPF2(lowPassCornerFrequency, sampleRate, channelCount); addBiquadFilter(processingChain, processorsRecord, lpf); auto apf = createAPF2(400.0f, 200.0f, sampleRate, channelCount); addBiquadFilter(processingChain, processorsRecord, apf); apf = createAPF2(100.0f, 50.0f, sampleRate, channelCount); addBiquadFilter(processingChain, processorsRecord, apf); float allPassCornerFrequency = 25.0f; apf = createAPF(allPassCornerFrequency, sampleRate, channelCount); addBiquadFilter(processingChain, processorsRecord, apf); float resonantFrequency = 150.0f; float bandpassQ = 1.0f; auto bpf = createBPF(resonantFrequency, bandpassQ, sampleRate, channelCount); addBiquadFilter(processingChain, processorsRecord, bpf); float zeroQ = 8.0f; float poleQ = 4.0f; auto bsf = createBSF(resonantFrequency, zeroQ, poleQ, sampleRate, channelCount); auto bsf = createBSF( param->resonantFrequency, param->bsfZeroQ, param->bsfPoleQ, sampleRate, channelCount); addBiquadFilter(processingChain, processorsRecord, bsf); // The process chain captures the shared pointer of the Distortion in lambda. It will // be the only reference to the Distortion. // The process record will keep a weak pointer to the Distortion so that it is possible // to access the Distortion outside of the process chain. auto distortion = std::make_shared<Distortion>( param->distortionCornerFrequency, sampleRate, param->distortionInputGain, param->distortionCubeThreshold, param->distortionOutputGain, channelCount); processorsRecord.distortions.push_back(distortion); processingChain.push_back([distortion](float *out, const float *in, size_t frameCount) { distortion->process(out, in, frameCount); }); } int HapticGenerator_Configure(struct HapticGeneratorContext *context, effect_config_t *config) { Loading @@ -206,6 +219,7 @@ int HapticGenerator_Configure(struct HapticGeneratorContext *context, effect_con context->processorsRecord.filters.clear(); context->processorsRecord.ramps.clear(); context->processorsRecord.slowEnvs.clear(); context->processorsRecord.distortions.clear(); memcpy(&context->config, config, sizeof(effect_config_t)); context->param.audioChannelCount = audio_channel_count_from_out_mask( ((audio_channel_mask_t) config->inputCfg.channels) & ~AUDIO_CHANNEL_HAPTIC_ALL); Loading @@ -224,7 +238,7 @@ int HapticGenerator_Configure(struct HapticGeneratorContext *context, effect_con HapticGenerator_buildProcessingChain(context->processingChain, context->processorsRecord, config->inputCfg.samplingRate, context->param.hapticChannelCount); &context->param); } return 0; } Loading @@ -236,6 +250,9 @@ int HapticGenerator_Reset(struct HapticGeneratorContext *context) { for (auto& slowEnv : context->processorsRecord.slowEnvs) { slowEnv->clear(); } for (auto& distortion : context->processorsRecord.distortions) { distortion->clear(); } return 0; } Loading
media/libeffects/hapticgenerator/EffectHapticGenerator.h +11 −0 Original line number Diff line number Diff line Loading @@ -51,6 +51,16 @@ struct HapticGeneratorParam { // A map from track id to haptic intensity. std::map<int, os::HapticScale> id2Intensity; os::HapticScale maxHapticIntensity; // max intensity will be used to scale haptic data. float resonantFrequency; float bpfQ; float slowEnvNormalizationPower; float bsfZeroQ; float bsfPoleQ; float distortionCornerFrequency; float distortionInputGain; float distortionCubeThreshold; float distortionOutputGain; }; // A structure to keep all shared pointers for all processors in HapticGenerator. Loading @@ -58,6 +68,7 @@ struct HapticGeneratorProcessorsRecord { std::vector<std::shared_ptr<HapticBiquadFilter>> filters; std::vector<std::shared_ptr<Ramp>> ramps; std::vector<std::shared_ptr<SlowEnvelope>> slowEnvs; std::vector<std::shared_ptr<Distortion>> distortions; }; // A structure to keep all the context for HapticGenerator. Loading
media/libeffects/hapticgenerator/Processors.cpp +105 −57 Original line number Diff line number Diff line Loading @@ -83,30 +83,92 @@ SlowEnvelope::SlowEnvelope( float cornerFrequency, float sampleRate, float normalizationPower, float envOffset, size_t channelCount) : mLpf(createLPF(cornerFrequency, sampleRate, channelCount)), mNormalizationPower(normalizationPower), mChannelCount(channelCount), mEnv(0.25 * (sampleRate / (2 * M_PI * cornerFrequency))) {} mEnvOffset(envOffset), mChannelCount(channelCount) {} void SlowEnvelope::process(float* out, const float* in, size_t frameCount) { size_t sampleCount = frameCount * mChannelCount; if (sampleCount > mLpfInBuffer.size()) { mLpfInBuffer.resize(sampleCount, mEnv); if (sampleCount > mLpfOutBuffer.size()) { mLpfOutBuffer.resize(sampleCount); mLpfInBuffer.resize(sampleCount); } for (size_t i = 0; i < sampleCount; ++i) { mLpfInBuffer[i] = fabs(in[i]); } mLpf->process(mLpfOutBuffer.data(), mLpfInBuffer.data(), frameCount); for (size_t i = 0; i < sampleCount; ++i) { *out = *in * pow(mLpfOutBuffer[i], mNormalizationPower); out++; in++; out[i] = in[i] * pow(mLpfOutBuffer[i] + mEnvOffset, mNormalizationPower); } } void SlowEnvelope::setNormalizationPower(float normalizationPower) { mNormalizationPower = normalizationPower; } void SlowEnvelope::clear() { mLpf->clear(); } // Implementation of distortion Distortion::Distortion( float cornerFrequency, float sampleRate, float inputGain, float cubeThreshold, float outputGain, size_t channelCount) : mLpf(createLPF2(cornerFrequency, sampleRate, channelCount)), mSampleRate(sampleRate), mCornerFrequency(cornerFrequency), mInputGain(inputGain), mCubeThreshold(cubeThreshold), mOutputGain(outputGain), mChannelCount(channelCount) {} void Distortion::process(float *out, const float *in, size_t frameCount) { size_t sampleCount = frameCount * mChannelCount; if (sampleCount > mLpfInBuffer.size()) { mLpfInBuffer.resize(sampleCount); } for (size_t i = 0; i < sampleCount; ++i) { const float x = mInputGain * in[i]; mLpfInBuffer[i] = x * x * x / (mCubeThreshold + x * x); // "Coring" nonlinearity. } mLpf->process(out, mLpfInBuffer.data(), frameCount); // Reduce 3*F components. for (size_t i = 0; i < sampleCount; ++i) { const float x = out[i]; out[i] = mOutputGain * x / (1.0f + fabs(x)); // Soft limiter. } } void Distortion::setCornerFrequency(float cornerFrequency) { mCornerFrequency = cornerFrequency; BiquadFilterCoefficients coefficient = lpfCoefs(cornerFrequency, mSampleRate); mLpf->setCoefficients(coefficient); } void Distortion::setInputGain(float inputGain) { mInputGain = inputGain; } void Distortion::setCubeThrehold(float cubeThreshold) { mCubeThreshold = cubeThreshold; } void Distortion::setOutputGain(float outputGain) { mOutputGain = outputGain; } void Distortion::clear() { mLpf->clear(); } // Implementation of helper functions BiquadFilterCoefficients cascadeFirstOrderFilters(const BiquadFilterCoefficients &coefs1, Loading Loading @@ -134,6 +196,40 @@ BiquadFilterCoefficients lpfCoefs(const float cornerFrequency, const float sampl return coefficient; } BiquadFilterCoefficients bpfCoefs(const float ringingFrequency, const float q, const float sampleRate) { BiquadFilterCoefficients coefficient; const auto [real, img] = getComplexPoleZ(ringingFrequency, q, sampleRate); // Note: this is not a standard cookbook BPF, but a low pass filter with zero at DC coefficient[0] = 1.0f; coefficient[1] = -1.0f; coefficient[2] = 0.0f; coefficient[3] = -2 * real; coefficient[4] = real * real + img * img; return coefficient; } BiquadFilterCoefficients bsfCoefs(const float ringingFrequency, const float sampleRate, const float zq, const float pq) { BiquadFilterCoefficients coefficient; const auto [zeroReal, zeroImg] = getComplexPoleZ(ringingFrequency, zq, sampleRate); float zeroCoeff1 = -2 * zeroReal; float zeroCoeff2 = zeroReal* zeroReal + zeroImg * zeroImg; const auto [poleReal, poleImg] = getComplexPoleZ(ringingFrequency, pq, sampleRate); float poleCoeff1 = -2 * poleReal; float poleCoeff2 = poleReal * poleReal + poleImg * poleImg; const float norm = (1.0f + poleCoeff1 + poleCoeff2) / (1.0f + zeroCoeff1 + zeroCoeff2); coefficient[0] = 1.0f * norm; coefficient[1] = zeroCoeff1 * norm; coefficient[2] = zeroCoeff2 * norm; coefficient[3] = poleCoeff1; coefficient[4] = poleCoeff2; return coefficient; } std::shared_ptr<HapticBiquadFilter> createLPF(const float cornerFrequency, const float sampleRate, const size_t channelCount) { Loading Loading @@ -166,47 +262,11 @@ std::shared_ptr<HapticBiquadFilter> createHPF2(const float cornerFrequency, channelCount, cascadeFirstOrderFilters(coefficient, coefficient)); } BiquadFilterCoefficients apfCoefs(const float cornerFrequency, const float sampleRate) { BiquadFilterCoefficients coefficient; float realPoleZ = getRealPoleZ(cornerFrequency, sampleRate); float zeroZ = 1.0f / realPoleZ; coefficient[0] = (1.0f - realPoleZ) / (1.0f - zeroZ); coefficient[1] = -coefficient[0] * zeroZ; coefficient[2] = 0.0f; coefficient[3] = -realPoleZ; coefficient[4] = 0.0f; return coefficient; } std::shared_ptr<HapticBiquadFilter> createAPF(const float cornerFrequency, const float sampleRate, const size_t channelCount) { BiquadFilterCoefficients coefficient = apfCoefs(cornerFrequency, sampleRate); return std::make_shared<HapticBiquadFilter>(channelCount, coefficient); } std::shared_ptr<HapticBiquadFilter> createAPF2(const float cornerFrequency1, const float cornerFrequency2, const float sampleRate, const size_t channelCount) { BiquadFilterCoefficients coefs1 = apfCoefs(cornerFrequency1, sampleRate); BiquadFilterCoefficients coefs2 = apfCoefs(cornerFrequency2, sampleRate); return std::make_shared<HapticBiquadFilter>( channelCount, cascadeFirstOrderFilters(coefs1, coefs2)); } std::shared_ptr<HapticBiquadFilter> createBPF(const float ringingFrequency, const float q, const float sampleRate, const size_t channelCount) { BiquadFilterCoefficients coefficient; const auto [real, img] = getComplexPoleZ(ringingFrequency, q, sampleRate); // Note: this is not a standard cookbook BPF, but a low pass filter with zero at DC coefficient[0] = 1.0f; coefficient[1] = -1.0f; coefficient[2] = 0.0f; coefficient[3] = -2 * real; coefficient[4] = real * real + img * img; BiquadFilterCoefficients coefficient = bpfCoefs(ringingFrequency, q, sampleRate); return std::make_shared<HapticBiquadFilter>(channelCount, coefficient); } Loading @@ -215,19 +275,7 @@ std::shared_ptr<HapticBiquadFilter> createBSF(const float ringingFrequency, const float pq, const float sampleRate, const size_t channelCount) { BiquadFilterCoefficients coefficient; const auto [zeroReal, zeroImg] = getComplexPoleZ(ringingFrequency, zq, sampleRate); float zeroCoeff1 = -2 * zeroReal; float zeroCoeff2 = zeroReal* zeroReal + zeroImg * zeroImg; const auto [poleReal, poleImg] = getComplexPoleZ(ringingFrequency, pq, sampleRate); float poleCoeff1 = -2 * poleReal; float poleCoeff2 = poleReal * poleReal + poleImg * poleImg; const float norm = (1.0f + poleCoeff1 + poleCoeff2) / (1.0f + zeroCoeff1 + zeroCoeff2); coefficient[0] = 1.0f * norm; coefficient[1] = zeroCoeff1 * norm; coefficient[2] = zeroCoeff2 * norm; coefficient[3] = poleCoeff1; coefficient[4] = poleCoeff2; BiquadFilterCoefficients coefficient = bsfCoefs(ringingFrequency, sampleRate, zq, pq); return std::make_shared<HapticBiquadFilter>(channelCount, coefficient); } Loading
media/libeffects/hapticgenerator/Processors.h +45 −13 Original line number Diff line number Diff line Loading @@ -44,19 +44,50 @@ private: class SlowEnvelope { public: SlowEnvelope(float cornerFrequency, float sampleRate, float normalizationPower, size_t channelCount); float normalizationPower, float envOffset, size_t channelCount); void process(float *out, const float *in, size_t frameCount); void setNormalizationPower(float normalizationPower); void clear(); private: const std::shared_ptr<HapticBiquadFilter> mLpf; std::vector<float> mLpfInBuffer; std::vector<float> mLpfOutBuffer; const float mNormalizationPower; float mNormalizationPower; const float mEnvOffset; const float mChannelCount; const float mEnv; }; // A class providing a process function that compressively distorts a waveforms class Distortion { public: Distortion(float cornerFrequency, float sampleRate, float inputGain, float cubeThreshold, float outputGain, size_t channelCount); void process(float *out, const float *in, size_t frameCount); void setCornerFrequency(float cornerFrequency); void setInputGain(float inputGain); void setCubeThrehold(float cubeThreshold); void setOutputGain(float outputGain); void clear(); private: const std::shared_ptr<HapticBiquadFilter> mLpf; std::vector<float> mLpfInBuffer; float mSampleRate; float mCornerFrequency; float mInputGain; float mCubeThreshold; float mOutputGain; const size_t mChannelCount; }; // Helper functions Loading @@ -64,6 +95,17 @@ private: BiquadFilterCoefficients cascadeFirstOrderFilters(const BiquadFilterCoefficients &coefs1, const BiquadFilterCoefficients &coefs2); BiquadFilterCoefficients lpfCoefs(const float cornerFrequency, const float sampleRate); BiquadFilterCoefficients bpfCoefs(const float ringingFrequency, const float q, const float sampleRate); BiquadFilterCoefficients bsfCoefs(const float ringingFrequency, const float sampleRate, const float zq, const float pq); std::shared_ptr<HapticBiquadFilter> createLPF(const float cornerFrequency, const float sampleRate, const size_t channelCount); Loading @@ -78,16 +120,6 @@ std::shared_ptr<HapticBiquadFilter> createHPF2(const float cornerFrequency, const float sampleRate, const size_t channelCount); std::shared_ptr<HapticBiquadFilter> createAPF(const float cornerFrequency, const float sampleRate, const size_t channelCount); // Create two cascaded APF with two different corner frequency. std::shared_ptr<HapticBiquadFilter> createAPF2(const float cornerFrequency1, const float cornerFrequency2, const float sampleRate, const size_t channelCount); std::shared_ptr<HapticBiquadFilter> createBPF(const float ringingFrequency, const float q, const float sampleRate, Loading