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

Commit 0320f8bd authored by Jean-Michel Trivi's avatar Jean-Michel Trivi
Browse files

Apply EQ on the output of the speech synthesis only when using the Pico

TTS engine.
parent 9b0b155e
Loading
Loading
Loading
Loading
+59 −19
Original line number Diff line number Diff line
@@ -41,8 +41,6 @@
#define FILTER_TRANSITION_FREQ 1100.0f     // in Hz
#define FILTER_SHELF_SLOPE 1.0f            // Q
#define FILTER_GAIN 5.5f // linear gain
// such a huge gain is justified by how much energy in the low frequencies is "wasted" at the output
// of the synthesis. The low shelving filter removes it, leaving room for amplification.

#define USAGEMODE_PLAY_IMMEDIATELY 0
#define USAGEMODE_WRITE_TO_FILE    1
@@ -80,13 +78,19 @@ double out0;// y[n]
double out1;// y[n-1]
double out2;// y[n-2]

static float fFilterLowshelfAttenuation = FILTER_LOWSHELF_ATTENUATION;
static float fFilterTransitionFreq = FILTER_TRANSITION_FREQ;
static float fFilterShelfSlope = FILTER_SHELF_SLOPE;
static float fFilterGain = FILTER_GAIN;
static bool  bUseFilter = false;

void initializeEQ() {

    amp = float(pow(10.0, FILTER_LOWSHELF_ATTENUATION / 40.0));
    w = 2.0 * M_PI * (FILTER_TRANSITION_FREQ / DEFAULT_TTS_RATE);
    amp = float(pow(10.0, fFilterLowshelfAttenuation / 40.0));
    w = 2.0 * M_PI * (fFilterTransitionFreq / DEFAULT_TTS_RATE);
    sinw = float(sin(w));
    cosw = float(cos(w));
    beta = float(sqrt(amp)/FILTER_SHELF_SLOPE);
    beta = float(sqrt(amp)/fFilterShelfSlope);

    // initialize low-shelf parameters
    b0 = amp * ((amp+1.0F) - ((amp-1.0F)*cosw) + (beta*sinw));
@@ -96,9 +100,9 @@ void initializeEQ() {
    a1 = 2.0F * ((amp-1.0F) + ((amp+1.0F)*cosw));
    a2 = -((amp+1.0F) + ((amp-1.0F)*cosw) - (beta*sinw));

    m_fa = FILTER_GAIN * b0/a0;
    m_fb = FILTER_GAIN * b1/a0;
    m_fc = FILTER_GAIN * b2/a0;
    m_fa = fFilterGain * b0/a0;
    m_fb = fFilterGain * b1/a0;
    m_fc = fFilterGain * b2/a0;
    m_fd = a1/a0;
    m_fe = a2/a0;
}
@@ -284,7 +288,9 @@ static tts_callback_status ttsSynthDoneCB(void *& userdata, uint32_t rate,
        if (bufferSize > 0) {
            prepAudioTrack(pJniData, pForAfter->streamType, rate, (AudioSystem::audio_format)format, channel);
            if (pJniData->mAudioOut) {
                if (bUseFilter) {
                    applyFilter((int16_t*)wav, bufferSize/2);
                }
                pJniData->mAudioOut->write(wav, bufferSize);
                memset(wav, 0, bufferSize);
                //LOGV("AudioTrack wrote: %d bytes", bufferSize);
@@ -300,7 +306,9 @@ static tts_callback_status ttsSynthDoneCB(void *& userdata, uint32_t rate,
            return TTS_CALLBACK_HALT;
        }
        if (bufferSize > 0){
            if (bUseFilter) {
                applyFilter((int16_t*)wav, bufferSize/2);
            }
            fwrite(wav, 1, bufferSize, pForAfter->outputFile);
            memset(wav, 0, bufferSize);
        }
@@ -334,23 +342,50 @@ static tts_callback_status ttsSynthDoneCB(void *& userdata, uint32_t rate,


// ----------------------------------------------------------------------------
static void
static int
android_tts_SynthProxy_setLowShelf(JNIEnv *env, jobject thiz, jboolean applyFilter,
        jfloat filterGain, jfloat attenuationInDb, jfloat freqInHz, jfloat slope)
{
    int result = TTS_SUCCESS;

    bUseFilter = applyFilter;
    if (applyFilter) {
        fFilterLowshelfAttenuation = attenuationInDb;
        fFilterTransitionFreq = freqInHz;
        fFilterShelfSlope = slope;
        fFilterGain = filterGain;

        if (fFilterShelfSlope != 0.0f) {
            initializeEQ();
        } else {
            LOGE("Invalid slope, can't be null");
            result = TTS_FAILURE;
        }
    }

    return result;
}

// ----------------------------------------------------------------------------
static int
android_tts_SynthProxy_native_setup(JNIEnv *env, jobject thiz,
        jobject weak_this, jstring nativeSoLib)
{
    int result = TTS_FAILURE;

    bUseFilter = false;

    SynthProxyJniStorage* pJniStorage = new SynthProxyJniStorage();

    prepAudioTrack(pJniStorage,
            DEFAULT_TTS_STREAM_TYPE, DEFAULT_TTS_RATE, DEFAULT_TTS_FORMAT, DEFAULT_TTS_NB_CHANNELS);

    const char *nativeSoLibNativeString =
            env->GetStringUTFChars(nativeSoLib, 0);
    const char *nativeSoLibNativeString =  env->GetStringUTFChars(nativeSoLib, 0);

    void *engine_lib_handle = dlopen(nativeSoLibNativeString,
            RTLD_NOW | RTLD_LOCAL);
    if (engine_lib_handle == NULL) {
       LOGE("android_tts_SynthProxy_native_setup(): engine_lib_handle == NULL");
       // TODO report error so the TTS can't be used
    } else {
        TtsEngine *(*get_TtsEngine)() =
            reinterpret_cast<TtsEngine* (*)()>(dlsym(engine_lib_handle, "getTtsEngine"));
@@ -362,18 +397,19 @@ android_tts_SynthProxy_native_setup(JNIEnv *env, jobject thiz,
            Mutex::Autolock l(engineMutex);
            pJniStorage->mNativeSynthInterface->init(ttsSynthDoneCB);
        }

        result = TTS_SUCCESS;
    }

    // we use a weak reference so the SynthProxy object can be garbage collected.
    pJniStorage->tts_ref = env->NewGlobalRef(weak_this);

    // save the JNI resources so we can use them (and free them) later
    env->SetIntField(thiz, javaTTSFields.synthProxyFieldJniData,
            (int)pJniStorage);

    initializeEQ();
    env->SetIntField(thiz, javaTTSFields.synthProxyFieldJniData, (int)pJniStorage);

    env->ReleaseStringUTFChars(nativeSoLib, nativeSoLibNativeString);

    return result;
}


@@ -842,9 +878,13 @@ static JNINativeMethod gMethods[] = {
        (void*)android_tts_SynthProxy_shutdown
    },
    {   "native_setup",
        "(Ljava/lang/Object;Ljava/lang/String;)V",
        "(Ljava/lang/Object;Ljava/lang/String;)I",
        (void*)android_tts_SynthProxy_native_setup
    },
    {   "native_setLowShelf",
        "(ZFFFF)I",
        (void*)android_tts_SynthProxy_setLowShelf
    },
    {   "native_finalize",
        "(I)V",
        (void*)android_tts_SynthProxy_native_finalize
+17 −3
Original line number Diff line number Diff line
@@ -32,6 +32,15 @@ import java.lang.ref.WeakReference;
@SuppressWarnings("unused")
public class SynthProxy {

    // Default parameters of a filter to be applied when using the Pico engine.
    // Such a huge filter gain is justified by how much energy in the low frequencies is "wasted" at
    // the output of the synthesis. The low shelving filter removes it, leaving room for
    // amplification.
    private final static float PICO_FILTER_GAIN = 5.5f; // linear gain
    private final static float PICO_FILTER_LOWSHELF_ATTENUATION = -18.0f; // in dB
    private final static float PICO_FILTER_TRANSITION_FREQ = 1100.0f;     // in Hz
    private final static float PICO_FILTER_SHELF_SLOPE = 1.0f;            // Q

    //
    // External API
    //
@@ -40,8 +49,11 @@ public class SynthProxy {
     * Constructor; pass the location of the native TTS .so to use.
     */
    public SynthProxy(String nativeSoLib) {
        Log.v(TtsService.SERVICE_TAG, "TTS is loading " + nativeSoLib);
        boolean applyFilter = nativeSoLib.toLowerCase().contains("pico");
        Log.v(TtsService.SERVICE_TAG, "about to load "+ nativeSoLib + ", applyFilter="+applyFilter);
        native_setup(new WeakReference<SynthProxy>(this), nativeSoLib);
        native_setLowShelf(applyFilter, PICO_FILTER_GAIN, PICO_FILTER_LOWSHELF_ATTENUATION,
                PICO_FILTER_TRANSITION_FREQ, PICO_FILTER_SHELF_SLOPE);
    }

    /**
@@ -161,8 +173,10 @@ public class SynthProxy {
     */
    private int mJniData = 0;

    private native final void native_setup(Object weak_this,
            String nativeSoLib);
    private native final int native_setup(Object weak_this, String nativeSoLib);

    private native final int native_setLowShelf(boolean applyFilter, float filterGain,
            float attenuationInDb, float freqInHz, float slope);

    private native final void native_finalize(int jniData);