diff --git a/app/src/main/cpp/dsp.cpp b/app/src/main/cpp/dsp.cpp index 9bd1733..5fdced4 100644 --- a/app/src/main/cpp/dsp.cpp +++ b/app/src/main/cpp/dsp.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #if defined(__ARM_NEON) #include @@ -12,6 +13,9 @@ #define M_PI 3.14159265358979323846 #endif +static float gSampleRate = 44100.0f; +static std::mutex gAudioMutex; + static constexpr int FFT_SIZE = 512; static constexpr int NUM_EQ_BANDS = 10; static constexpr float INV_32768 = 1.0f / 32768.0f; @@ -209,6 +213,13 @@ inline void fastFFT(std::complex* __restrict__ data, int n) { } } +inline void applyHannWindow(float* __restrict__ data, int size) { + for (int i = 0; i < size; i++) { + float window = 0.5f * (1.0f - cosf(2.0f * static_cast(M_PI) * i / (size - 1))); + data[i] *= window; + } +} + inline float fastSoftClip(float x) { float ax = fabsf(x); float sign = x > 0 ? 1.0f : -1.0f; @@ -218,23 +229,39 @@ inline float fastSoftClip(float x) { extern "C" { -JNIEXPORT void JNICALL Java_com_michatec_radio_helpers_NativeAudioProcessor_setDrcEnabled(JNIEnv*, jobject, jboolean e) { gDrcEnabled = e; } -JNIEXPORT void JNICALL Java_com_michatec_radio_helpers_NativeAudioProcessor_setReverbMix(JNIEnv*, jobject, jfloat m) { gReverbL.setMix(m); gReverbR.setMix(m); } +JNIEXPORT void JNICALL Java_com_michatec_radio_helpers_NativeAudioProcessor_setSampleRate(JNIEnv*, jobject, jfloat sr) { + std::lock_guard lock(gAudioMutex); + gSampleRate = sr; + gCompressor.sampleRate = sr; +} +JNIEXPORT void JNICALL Java_com_michatec_radio_helpers_NativeAudioProcessor_setDrcEnabled(JNIEnv*, jobject, jboolean e) { + std::lock_guard lock(gAudioMutex); + gDrcEnabled = e; +} +JNIEXPORT void JNICALL Java_com_michatec_radio_helpers_NativeAudioProcessor_setReverbMix(JNIEnv*, jobject, jfloat m) { + std::lock_guard lock(gAudioMutex); + gReverbL.setMix(m); gReverbR.setMix(m); +} JNIEXPORT void JNICALL Java_com_michatec_radio_helpers_NativeAudioProcessor_setEqBand(JNIEnv*, jobject, jint b, jfloat g) { + std::lock_guard lock(gAudioMutex); if (b >= 0 && b < NUM_EQ_BANDS) { - gEqL.setPeakingEQ(b, 44100.0f, EQ_FREQUENCIES[b], g, 1.0f); - gEqR.setPeakingEQ(b, 44100.0f, EQ_FREQUENCIES[b], g, 1.0f); + gEqL.setPeakingEQ(b, gSampleRate, EQ_FREQUENCIES[b], g, 1.0f); + gEqR.setPeakingEQ(b, gSampleRate, EQ_FREQUENCIES[b], g, 1.0f); } gEqEnabled = gEqL.hasActiveBands(); } JNIEXPORT void JNICALL Java_com_michatec_radio_helpers_NativeAudioProcessor_setBassBoost(JNIEnv*, jobject, jfloat g) { + std::lock_guard lock(gAudioMutex); if (g > 0.01f) { - gBassL.setLowShelf(44100.0f, 150.0f, g, SQRT_2_INV); - gBassR.setLowShelf(44100.0f, 150.0f, g, SQRT_2_INV); + gBassL.setLowShelf(gSampleRate, 150.0f, g, SQRT_2_INV); + gBassR.setLowShelf(gSampleRate, 150.0f, g, SQRT_2_INV); gBassBoostEnabled = true; } else { gBassBoostEnabled = false; } } -JNIEXPORT void JNICALL Java_com_michatec_radio_helpers_NativeAudioProcessor_setStereoWidth(JNIEnv*, jobject, jfloat w) { gStereoWidth = fmaxf(0.0f, fminf(w, 2.0f)); } +JNIEXPORT void JNICALL Java_com_michatec_radio_helpers_NativeAudioProcessor_setStereoWidth(JNIEnv*, jobject, jfloat w) { + std::lock_guard lock(gAudioMutex); + gStereoWidth = fmaxf(0.0f, fminf(w, 2.0f)); +} JNIEXPORT jfloatArray JNICALL Java_com_michatec_radio_helpers_NativeAudioProcessor_getFftData(JNIEnv* env, jobject) { jfloatArray arr = env->NewFloatArray(256); @@ -270,13 +297,18 @@ JNIEXPORT void JNICALL Java_com_michatec_radio_helpers_NativeAudioProcessor_proc if (gDrcEnabled) gCompressor.process(gLeftBuf.data(), gRightBuf.data(), numFrames); - // FFT for visualization + // FFT for visualization with Hann window + alignas(16) std::array fftWindow; + for (int k = 0; k < 256; k++) { + fftWindow[k] = (k < numFrames) ? gLeftBuf[k] : 0.0f; + } + applyHannWindow(fftWindow.data(), 256); for (int k = 0; k < FFT_SIZE; k++) { - gFFTWork[k] = (k < 256 && k < numFrames) ? std::complex(gLeftBuf[k], 0.0f) : std::complex(0.0f, 0.0f); + gFFTWork[k] = (k < 256) ? std::complex(fftWindow[k], 0.0f) : std::complex(0.0f, 0.0f); } fastFFT(gFFTWork.data(), FFT_SIZE); for (int k = 0; k < 256; k++) { - gFFTData[k] = std::abs(gFFTWork[k]) * 0.5f; // Increased scale + gFFTData[k] = std::abs(gFFTWork[k]) * 0.5f; } for (int k = 0; k < numFrames; k++) { diff --git a/app/src/main/java/com/michatec/radio/helpers/NativeAudioProcessor.kt b/app/src/main/java/com/michatec/radio/helpers/NativeAudioProcessor.kt index ed7b20c..b15ee6f 100644 --- a/app/src/main/java/com/michatec/radio/helpers/NativeAudioProcessor.kt +++ b/app/src/main/java/com/michatec/radio/helpers/NativeAudioProcessor.kt @@ -27,6 +27,7 @@ class NativeAudioProcessor : BaseAudioProcessor() { private var directBuffer: ByteBuffer? = null // ===== JNI ===== + private external fun setSampleRate(sampleRate: Float) private external fun setDrcEnabled(enabled: Boolean) private external fun setReverbMix(mix: Float) private external fun setEqBand(band: Int, gainDb: Float) @@ -59,6 +60,8 @@ class NativeAudioProcessor : BaseAudioProcessor() { Log.e(TAG, "Unsupported encoding: ${inputAudioFormat.encoding}") throw AudioProcessor.UnhandledAudioFormatException(inputAudioFormat) } + // Pass the actual sample rate to native + setSampleRate(inputAudioFormat.sampleRate.toFloat()) return inputAudioFormat }