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

Commit 85abcce5 authored by Antti S. Lankila's avatar Antti S. Lankila
Browse files

Add loudness compensation to eq

The equalizer is now capable of adjusting bass booth based on the
ISO equal loudness contours and a tunable that matches the sound
digital level with the level experienced by ear. Typical values
for the adjustment parameter should be between 40 dB to 20 dB.

Change-Id: I1b15130d42607ecb57c980038180ba7703411039
parent 64ad1a46
Loading
Loading
Loading
Loading
+83 −21
Original line number Original line Diff line number Diff line
@@ -61,17 +61,16 @@ static int64_t toFixedPoint(float in) {
}
}


EffectEqualizer::EffectEqualizer()
EffectEqualizer::EffectEqualizer()
    : mLoudnessAdjustment(10000.f), mLoudness(0.f)
{
{
    for (int32_t i = 0; i < 5; i ++) {
    for (int32_t i = 0; i < 5; i ++) {
        mBand[i] = 0;
        mBand[i] = 0;
    }
    }
    refreshBands();
}
}


int32_t EffectEqualizer::command(uint32_t cmdCode, uint32_t cmdSize, void* pCmdData, uint32_t* replySize, void* pReplyData)
int32_t EffectEqualizer::command(uint32_t cmdCode, uint32_t cmdSize, void* pCmdData, uint32_t* replySize, void* pReplyData)
{
{
	if (cmdCode == EFFECT_CMD_CONFIGURE) {
	if (cmdCode == EFFECT_CMD_CONFIGURE) {
		LOGI("EFFECT_CMD_CONFIGURE");
		int32_t ret = Effect::configure(pCmdData);
		int32_t ret = Effect::configure(pCmdData);
		if (ret != 0) {
		if (ret != 0) {
			LOGE("EFFECT_CMD_CONFIGURE failed");
			LOGE("EFFECT_CMD_CONFIGURE failed");
@@ -80,21 +79,20 @@ int32_t EffectEqualizer::command(uint32_t cmdCode, uint32_t cmdSize, void* pCmdD
			return 0;
			return 0;
		}
		}


		refreshBands();
                /* Weigher for estimating bass compensation. Our adjustments have range from 62.5 to 4000 Hz.
                 * Most of the adjustment is at the 62.5 Hz band, so we must concentrate on bass region. */
                mWeigher.setLowPass(1000.0, mSamplingRate, sqrtf(2)/2.);


		int32_t *replyData = (int32_t *) pReplyData;
		int32_t *replyData = (int32_t *) pReplyData;
		*replyData = 0;
		*replyData = 0;
		LOGI("EFFECT_CMD_CONFIGURE OK");
		return 0;
		return 0;
	}
	}


	if (cmdCode == EFFECT_CMD_GET_PARAM) {
	if (cmdCode == EFFECT_CMD_GET_PARAM) {
		effect_param_t *cep = (effect_param_t *) pCmdData;
		effect_param_t *cep = (effect_param_t *) pCmdData;
		LOGI("EFFECT_CMD_GET_PARAM + %d bytes of param", cep->psize);
		if (cep->psize == 4) {
		if (cep->psize == 4) {
			int cmd = ((int *) cep)[3];
			int32_t cmd = ((int32_t *) cep)[3];
			if (cmd == EQ_PARAM_NUM_BANDS) {
			if (cmd == EQ_PARAM_NUM_BANDS) {
				LOGI("Requested param: EQ_PARAM_NUM_BANDS");
				reply1x4_1x2_t *replyData = (reply1x4_1x2_t *) pReplyData;
				reply1x4_1x2_t *replyData = (reply1x4_1x2_t *) pReplyData;
				replyData->status = 0;
				replyData->status = 0;
				replyData->vsize = 2;
				replyData->vsize = 2;
@@ -104,7 +102,6 @@ int32_t EffectEqualizer::command(uint32_t cmdCode, uint32_t cmdSize, void* pCmdD
				return 0;
				return 0;
			}
			}
			if (cmd == EQ_PARAM_LEVEL_RANGE) {
			if (cmd == EQ_PARAM_LEVEL_RANGE) {
				LOGI("Requested param: EQ_PARAM_LEVEL_RANGE");
				reply1x4_2x2_t *replyData = (reply1x4_2x2_t *) pReplyData;
				reply1x4_2x2_t *replyData = (reply1x4_2x2_t *) pReplyData;
				replyData->status = 0;
				replyData->status = 0;
				replyData->vsize = 4;
				replyData->vsize = 4;
@@ -115,19 +112,16 @@ int32_t EffectEqualizer::command(uint32_t cmdCode, uint32_t cmdSize, void* pCmdD
				return 0;
				return 0;
			}
			}
			if (cmd == EQ_PARAM_GET_NUM_OF_PRESETS) {
			if (cmd == EQ_PARAM_GET_NUM_OF_PRESETS) {
				LOGI("Requested param: EQ_PARAM_GET_NUM_OF_PRESETS");
				reply1x4_1x2_t *replyData = (reply1x4_1x2_t *) pReplyData;
				reply1x4_1x2_t *replyData = (reply1x4_1x2_t *) pReplyData;
				replyData->status = 0;
				replyData->status = 0;
				replyData->vsize = 2;
				replyData->vsize = 2;
				replyData->data = 0;
				replyData->data = 0;
				*replySize = sizeof(reply1x4_1x2_t);
				*replySize = sizeof(reply1x4_1x2_t);
				LOGI("EQ_PARAM_GET_NUM_OF_PRESETS OK");
				return 0;
				return 0;
			}
			}
		} else if (cep->psize == 8) {
		} else if (cep->psize == 8) {
			int cmd = ((int *) cep)[3];
			int32_t cmd = ((int32_t *) cep)[3];
			int arg = ((int *) cep)[4];
			int32_t arg = ((int32_t *) cep)[4];
			LOGI("Requested param: %d, %d", cmd, arg);
			if (cmd == EQ_PARAM_BAND_LEVEL && arg >= 0 && arg < 5) {
			if (cmd == EQ_PARAM_BAND_LEVEL && arg >= 0 && arg < 5) {
				reply2x4_1x2_t *replyData = (reply2x4_1x2_t *) pReplyData;
				reply2x4_1x2_t *replyData = (reply2x4_1x2_t *) pReplyData;
				replyData->status = 0;
				replyData->status = 0;
@@ -158,9 +152,19 @@ int32_t EffectEqualizer::command(uint32_t cmdCode, uint32_t cmdSize, void* pCmdD


	if (cmdCode == EFFECT_CMD_SET_PARAM) {
	if (cmdCode == EFFECT_CMD_SET_PARAM) {
		effect_param_t *cep = (effect_param_t *) pCmdData;
		effect_param_t *cep = (effect_param_t *) pCmdData;
		LOGI("EFFECT_CMD_SET_PARAM, %d", cep->psize);
		int32_t *replyData = (int32_t *) pReplyData;
		int32_t *replyData = (int32_t *) pReplyData;


		if (cep->psize == 6) {
			int32_t cmd = ((int32_t *) cep)[3];
			if (cmd == CUSTOM_EQ_PARAM_LOUDNESS_CORRECTION) {
				int16_t value = ((int16_t *) cep)[8];
				mLoudnessAdjustment = value / 100.0f;
				LOGI("Setting loudness correction reference to %f dB", mLoudnessAdjustment);
				*replyData = 0;
				return 0;
			}
		}

		if (cep->psize == 8) {
		if (cep->psize == 8) {
			int32_t cmd = ((int32_t *) cep)[3];
			int32_t cmd = ((int32_t *) cep)[3];
			int32_t arg = ((int32_t *) cep)[4];
			int32_t arg = ((int32_t *) cep)[4];
@@ -170,7 +174,6 @@ int32_t EffectEqualizer::command(uint32_t cmdCode, uint32_t cmdSize, void* pCmdD
				*replyData = 0;
				*replyData = 0;
				int16_t value = ((int16_t *) cep)[10];
				int16_t value = ((int16_t *) cep)[10];
				mBand[arg] = value / 100.0f;
				mBand[arg] = value / 100.0f;
				refreshBands();
				return 0;
				return 0;
			}
			}
		}
		}
@@ -183,12 +186,48 @@ int32_t EffectEqualizer::command(uint32_t cmdCode, uint32_t cmdSize, void* pCmdD
	return Effect::command(cmdCode, cmdSize, pCmdData, replySize, pReplyData);
	return Effect::command(cmdCode, cmdSize, pCmdData, replySize, pReplyData);
}
}


/* Source material: ISO 226:2003 curves.
 *
 * On differencing 100 dB curves against 80 dB, 60 dB, 40 dB and 20 dB, a pattern
 * can be established where each loss of 20 dB of power in signal suggests gradually
 * decreasing ear sensitivity, until the bottom is reached at 20 dB SPL where no more
 * boosting is required. Measurements end at 100 dB, which is assumed to be the reference
 * sound pressure level.
 *
 * The boost can be calculated as linear scaling of the following adjustment:
 *   62.5 Hz +24 dB
 *    250 Hz +10 dB
 *   1000 Hz  0 dB
 *   4000 Hz -6 dB
 *
 * The boost will be applied maximally for signals of 20 dB and less,
 * and linearly decreased for signals 20 dB ... 100 dB, and no adjustment is
 * made for 100 dB or higher. User must configure a reference level that maps the
 * digital sound level against the audio.
 */
float EffectEqualizer::getAdjustedBand(int32_t band) {
    const float adj[5] = { 24.0, 10.0, 0.0, -6.0, 0.0 };

    float loudnessLevel = mLoudness + mLoudnessAdjustment;
    if (loudnessLevel > 100.f) {
        loudnessLevel = 100.f;
    }
    if (loudnessLevel < 20.f) {
        loudnessLevel = 20.f;
    }

    loudnessLevel = (loudnessLevel - 20) / (100.0 - 20.0);

    /* Maximum loudness = no adj (reference behavior at 100 dB) */
    return mBand[band] + adj[band] * (1. - loudnessLevel);
}

void EffectEqualizer::refreshBands()
void EffectEqualizer::refreshBands()
{
{
    mGain = toFixedPoint(powf(10.0f, mBand[0] / 20.0f));
    mGain = toFixedPoint(powf(10.0f, getAdjustedBand(0) / 20.0f));
    for (int band = 0; band < 4; band ++) {
    for (int32_t band = 0; band < 4; band ++) {
        float centerFrequency = 62.5f * powf(4, band);
        float centerFrequency = 62.5f * powf(4, band);
        float dB = mBand[band+1] - mBand[band];
        float dB = getAdjustedBand(band+1) - getAdjustedBand(band);


        mFilterL[band].setHighShelf(centerFrequency * 2.0f, mSamplingRate, dB, 1.0f);
        mFilterL[band].setHighShelf(centerFrequency * 2.0f, mSamplingRate, dB, 1.0f);
        mFilterR[band].setHighShelf(centerFrequency * 2.0f, mSamplingRate, dB, 1.0f);
        mFilterR[band].setHighShelf(centerFrequency * 2.0f, mSamplingRate, dB, 1.0f);
@@ -197,10 +236,17 @@ void EffectEqualizer::refreshBands()


int32_t EffectEqualizer::process_effect(audio_buffer_t *in, audio_buffer_t *out)
int32_t EffectEqualizer::process_effect(audio_buffer_t *in, audio_buffer_t *out)
{
{
    refreshBands();

    int64_t maximumPowerSquared = 0;
    for (uint32_t i = 0; i < in->frameCount; i ++) {
    for (uint32_t i = 0; i < in->frameCount; i ++) {
        int32_t tmpL = read(in, i * 2);
        int32_t tmpL = read(in, i * 2);
        int32_t tmpR = read(in, i * 2 + 1);
        int32_t tmpR = read(in, i * 2 + 1);


        /* Calculate signal loudness estimate */
        int64_t weight = mWeigher.process(tmpL + tmpR);
        maximumPowerSquared += weight * weight;
     
        /* first "shelve" is just gain */ 
        /* first "shelve" is just gain */ 
        tmpL = tmpL * mGain >> 32;
        tmpL = tmpL * mGain >> 32;
        tmpR = tmpR * mGain >> 32;
        tmpR = tmpR * mGain >> 32;
@@ -214,6 +260,22 @@ int32_t EffectEqualizer::process_effect(audio_buffer_t *in, audio_buffer_t *out)
        write(out, i * 2, tmpL);
        write(out, i * 2, tmpL);
        write(out, i * 2 + 1, tmpR);
        write(out, i * 2 + 1, tmpR);
    }
    }
    maximumPowerSquared /= in->frameCount;

    float signalPowerDb = logf(maximumPowerSquared / float(int64_t(1) << 48) + 1e-10f) / logf(10.0f) * 10.0f;
    signalPowerDb += 96.0f;

    /* Limit automatic EQ to maximum of 10 dB adjustment rate per second.
     * a frame-to-frame adjusted should not be very large because adjustments do cause
     * small glitches at the output. These may be audible if single step is too large. */
    float maxAdj = in->frameCount / mSamplingRate * 10.f;
    if (mLoudness < signalPowerDb - maxAdj) {
        mLoudness += maxAdj;
    } else if (mLoudness > signalPowerDb + maxAdj) {
        mLoudness -= maxAdj;
    } else {
        mLoudness = signalPowerDb;
    }


    return 0;
    return 0;
}
}
+11 −0
Original line number Original line Diff line number Diff line
@@ -5,13 +5,24 @@
#include "Biquad.h"
#include "Biquad.h"
#include "Effect.h"
#include "Effect.h"


#define CUSTOM_EQ_PARAM_LOUDNESS_CORRECTION 1000

class EffectEqualizer : public Effect {
class EffectEqualizer : public Effect {
    private:
    private:
    /* Equalizer */
    float mBand[5];
    float mBand[5];

    int64_t mGain;
    int64_t mGain;
    Biquad mFilterL[4], mFilterR[4];
    Biquad mFilterL[4], mFilterR[4];


    /* Automatic equalizer */
    float mLoudnessAdjustment;

    Biquad mWeigher;
    float mLoudness;

    void setBand(int32_t idx, float dB);   
    void setBand(int32_t idx, float dB);   
    float getAdjustedBand(int32_t idx);
    void refreshBands();
    void refreshBands();


    public:
    public: