Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a9622046 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Update the algorithm of generating haptic data." into sc-dev

parents 7ee744f7 e5d7c4a4
Loading
Loading
Loading
Loading
+49 −32
Original line number Diff line number Diff line
@@ -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;
}
@@ -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
@@ -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) {
@@ -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);
@@ -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;
}
@@ -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;
}

+11 −0
Original line number Diff line number Diff line
@@ -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.
@@ -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.
+105 −57
Original line number Diff line number Diff line
@@ -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,
@@ -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) {
@@ -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);
}

@@ -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);
}

+45 −13
Original line number Diff line number Diff line
@@ -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
@@ -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);
@@ -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,