diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3a386fd42..26e672260 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -197,26 +197,19 @@ jobs: echo echo "## Downloads" echo - echo "| Variant | Architecture | Download | Asset |" - echo "| --- | --- | --- | --- |" + echo "| Variant | Architecture | Download |" + echo "| --- | --- | --- |" - for variant in rootless root; do - if [ "$variant" = "rootless" ]; then - variant_label="RootlessJamesDSP" + variant_label="RootlessJamesDSP" + for arch in universal arm64-v8a armeabi-v7a x86_64 x86; do + asset_name="$(jq -r --arg arch "$arch" '.assets[] | select(.name | test("-rootless-full-" + $arch + "-release-signed\\.apk$")) | .name' assets.json | head -n 1)" + + if [ -n "$asset_name" ] && [ "$asset_name" != "null" ]; then + download_url="https://github.com/${GITHUB_REPOSITORY}/releases/download/${TAG}/${asset_name}" + echo "| ${variant_label} | ${arch} | [Download](${download_url}) |" else - variant_label="JamesDSP (root)" + echo "| ${variant_label} | ${arch} | N/A |" fi - - for arch in universal arm64-v8a armeabi-v7a x86_64 x86; do - url="$(jq -r --arg variant "$variant" --arg arch "$arch" '.assets[] | select(.name | test("-" + $variant + "-full-" + $arch + "-release-signed\\.apk$")) | .url' assets.json | head -n 1)" - - if [ -n "$url" ] && [ "$url" != "null" ]; then - asset_name="${url##*/}" - echo "| ${variant_label} | ${arch} | [Download](${url}) | \`${asset_name}\` |" - else - echo "| ${variant_label} | ${arch} | N/A | - |" - fi - done done } > release_downloads.md diff --git a/app/src/main/cpp/libjamesdsp b/app/src/main/cpp/libjamesdsp index 499cea91c..315192494 160000 --- a/app/src/main/cpp/libjamesdsp +++ b/app/src/main/cpp/libjamesdsp @@ -1 +1 @@ -Subproject commit 499cea91cb63ee1ae61fcc4371eb934113379f38 +Subproject commit 315192494ff6733ebddc1ded37a4f409023afe0e diff --git a/app/src/main/cpp/libjamesdsp-wrapper/JamesDspWrapper.cpp b/app/src/main/cpp/libjamesdsp-wrapper/JamesDspWrapper.cpp index 8c326278d..6263004e9 100755 --- a/app/src/main/cpp/libjamesdsp-wrapper/JamesDspWrapper.cpp +++ b/app/src/main/cpp/libjamesdsp-wrapper/JamesDspWrapper.cpp @@ -963,6 +963,7 @@ Java_me_timschneeberger_rootlessjamesdsp_interop_JamesDspWrapper_setSpectrumExte jfloat strengthLinear, jint referenceFreq, jfloat wetMix, + jboolean wetOnlyMonitor, jfloat postGainDb, jboolean safetyEnabled, jfloat hpQ, @@ -1015,6 +1016,7 @@ Java_me_timschneeberger_rootlessjamesdsp_interop_JamesDspWrapper_setSpectrumExte safeStrength, safeReferenceFreq, safeWetMix, + wetOnlyMonitor ? 1 : 0, safePostGainDb, safetyEnabled ? 1 : 0, safeHpQ, diff --git a/app/src/main/java/me/timschneeberger/rootlessjamesdsp/fragment/PreferenceGroupFragment.kt b/app/src/main/java/me/timschneeberger/rootlessjamesdsp/fragment/PreferenceGroupFragment.kt index fec154950..0a96d361a 100644 --- a/app/src/main/java/me/timschneeberger/rootlessjamesdsp/fragment/PreferenceGroupFragment.kt +++ b/app/src/main/java/me/timschneeberger/rootlessjamesdsp/fragment/PreferenceGroupFragment.kt @@ -15,6 +15,7 @@ import androidx.preference.Preference.SummaryProvider import androidx.preference.PreferenceFragmentCompat import androidx.preference.ListPreference import androidx.preference.PreferenceScreen +import androidx.preference.SwitchPreferenceCompat import androidx.recyclerview.widget.RecyclerView import me.timschneeberger.rootlessjamesdsp.R import me.timschneeberger.rootlessjamesdsp.activity.GraphicEqualizerActivity @@ -248,19 +249,53 @@ class PreferenceGroupFragment : PreferenceFragmentCompat(), KoinComponent { val unitPref = findPreference(getString(R.string.key_spectrum_ext_strength_unit)) val strengthPercentPref = findPreference(getString(R.string.key_spectrum_ext_strength_percent)) val strengthDbPref = findPreference(getString(R.string.key_spectrum_ext_strength_db)) + val allowBoostPref = findPreference(getString(R.string.key_spectrum_ext_allow_boost)) val harmonicsPref = findPreference(getString(R.string.key_spectrum_ext_harmonics)) bindStrengthUnitPreferences( unitPref = unitPref, strengthPercentPref = strengthPercentPref, strengthDbPref = strengthDbPref, - maxDb = 0.0f, + maxDbProvider = { + if (allowBoostPref?.isChecked == true) { + SPECTRUM_STRENGTH_BOOST_DB_MAX + } else { + SPECTRUM_STRENGTH_DB_DEFAULT_MAX + } + }, minPercent = 0.0f, - maxPercent = 100.0f, + maxPercentProvider = { + if (allowBoostPref?.isChecked == true) { + strengthPercentFromDb(SPECTRUM_STRENGTH_BOOST_DB_MAX) + } else { + 100.0f + } + }, minLinear = 0.0f, mapMinDbToZero = true ) + fun applyBoostUi(isEnabled: Boolean) { + val maxDb = if (isEnabled) SPECTRUM_STRENGTH_BOOST_DB_MAX else SPECTRUM_STRENGTH_DB_DEFAULT_MAX + val maxPercent = if (isEnabled) strengthPercentFromDb(SPECTRUM_STRENGTH_BOOST_DB_MAX) else 100.0f + + strengthDbPref?.setMax(maxDb) + strengthPercentPref?.setMax(maxPercent) + + if ((strengthDbPref?.getValue() ?: maxDb) > maxDb) { + strengthDbPref?.setValue(maxDb) + } + if ((strengthPercentPref?.getValue() ?: maxPercent) > maxPercent) { + strengthPercentPref?.setValue(maxPercent) + } + } + + applyBoostUi(allowBoostPref?.isChecked == true) + allowBoostPref?.setOnPreferenceChangeListener { _, newValue -> + applyBoostUi(newValue as Boolean) + true + } + harmonicsPref?.setOnPreferenceChangeListener { _, newValue -> val harmonicsRaw = (newValue as? String)?.trim().orEmpty() if (isValidSemicolonDelimitedHarmonics(harmonicsRaw)) { @@ -279,9 +314,9 @@ class PreferenceGroupFragment : PreferenceFragmentCompat(), KoinComponent { unitPref = unitPref, strengthPercentPref = strengthPercentPref, strengthDbPref = strengthDbPref, - maxDb = CLARITY_STRENGTH_DB_MAX, + maxDbProvider = { CLARITY_STRENGTH_DB_MAX }, minPercent = 0.0f, - maxPercent = 800.0f, + maxPercentProvider = { 800.0f }, minLinear = 0.0f, mapMinDbToZero = true ) @@ -322,19 +357,19 @@ class PreferenceGroupFragment : PreferenceFragmentCompat(), KoinComponent { unitPref: ListPreference?, strengthPercentPref: MaterialSeekbarPreference?, strengthDbPref: MaterialSeekbarPreference?, - maxDb: Float, + maxDbProvider: () -> Float, minPercent: Float, - maxPercent: Float, + maxPercentProvider: () -> Float, minLinear: Float = 0.01f, mapMinDbToZero: Boolean = false, ) { - val maxLinear = dbToLinear(maxDb) val minDb = MIN_STRENGTH_DB val percentUnit = requireContext().getString(R.string.strength_unit_value_percent) val dbUnit = requireContext().getString(R.string.strength_unit_value_db) var internalUpdate = false fun percentToLinear(percent: Float): Float { + val maxLinear = dbToLinear(maxDbProvider()) return clampLinear(percent / 100.0f, minLinear, maxLinear) } @@ -342,6 +377,7 @@ class PreferenceGroupFragment : PreferenceFragmentCompat(), KoinComponent { if (mapMinDbToZero && db <= minDb) { return 0.0f } + val maxLinear = dbToLinear(maxDbProvider()) return clampLinear(dbToLinear(db), minLinear, maxLinear) } @@ -349,12 +385,12 @@ class PreferenceGroupFragment : PreferenceFragmentCompat(), KoinComponent { if (mapMinDbToZero && linear <= 0.0f) { return minDb } - return linearToDb(linear).coerceIn(minDb, maxDb) + return linearToDb(linear).coerceIn(minDb, maxDbProvider()) } strengthPercentPref?.setOnPreferenceChangeListener { _, newValue -> if (internalUpdate) return@setOnPreferenceChangeListener true - val percent = (newValue as Float).coerceIn(minPercent, maxPercent) + val percent = (newValue as Float).coerceIn(minPercent, maxPercentProvider()) val linear = percentToLinear(percent) val db = linearToDbMapped(linear) internalUpdate = true @@ -365,9 +401,9 @@ class PreferenceGroupFragment : PreferenceFragmentCompat(), KoinComponent { strengthDbPref?.setOnPreferenceChangeListener { _, newValue -> if (internalUpdate) return@setOnPreferenceChangeListener true - val db = (newValue as Float).coerceIn(minDb, maxDb) + val db = (newValue as Float).coerceIn(minDb, maxDbProvider()) val linear = dbToLinearMapped(db) - val percent = (linear * 100.0f).coerceIn(minPercent, maxPercent) + val percent = (linear * 100.0f).coerceIn(minPercent, maxPercentProvider()) internalUpdate = true strengthPercentPref?.setValue(percent) internalUpdate = false @@ -384,7 +420,7 @@ class PreferenceGroupFragment : PreferenceFragmentCompat(), KoinComponent { } else if (newUnit == percentUnit) { val db = strengthDbPref?.getValue() ?: minDb val linear = dbToLinearMapped(db) - strengthPercentPref?.setValue((linear * 100.0f).coerceIn(minPercent, maxPercent)) + strengthPercentPref?.setValue((linear * 100.0f).coerceIn(minPercent, maxPercentProvider())) } internalUpdate = false setStrengthVisibility(newUnit, strengthPercentPref, strengthDbPref) @@ -413,6 +449,8 @@ class PreferenceGroupFragment : PreferenceFragmentCompat(), KoinComponent { private fun dbToLinear(db: Float): Float = 10.0.pow((db / 20.0f).toDouble()).toFloat() + private fun strengthPercentFromDb(db: Float): Float = dbToLinear(db) * 100.0f + /** * Domain-specific clamping for linear gain values used by [linearToDb] and [dbToLinear]. * Inputs and bounds are linear-domain amplitudes and must stay positive for log conversion. @@ -460,6 +498,8 @@ class PreferenceGroupFragment : PreferenceFragmentCompat(), KoinComponent { private const val CROSSFEED_MODE_DEFAULT_VALUE = "5" private const val CUSTOM_CROSSFEED_MODE_VALUE = "99" private const val MIN_STRENGTH_DB = -40.0f + private const val SPECTRUM_STRENGTH_DB_DEFAULT_MAX = 0.0f + private const val SPECTRUM_STRENGTH_BOOST_DB_MAX = 12.0f private const val CLARITY_STRENGTH_LINEAR_MAX = 8.0f private val CLARITY_STRENGTH_DB_MAX = (20.0 * log10(CLARITY_STRENGTH_LINEAR_MAX.toDouble())).toFloat() // Semicolon-separated decimal numbers used by Spectrum Extension harmonics list. diff --git a/app/src/main/java/me/timschneeberger/rootlessjamesdsp/interop/JamesDspBaseEngine.kt b/app/src/main/java/me/timschneeberger/rootlessjamesdsp/interop/JamesDspBaseEngine.kt index 3e545ea2b..9ce2f8668 100644 --- a/app/src/main/java/me/timschneeberger/rootlessjamesdsp/interop/JamesDspBaseEngine.kt +++ b/app/src/main/java/me/timschneeberger/rootlessjamesdsp/interop/JamesDspBaseEngine.kt @@ -32,12 +32,14 @@ abstract class JamesDspBaseEngine(val context: Context, val callbacks: JamesDspW private const val SPECTRUM_STRENGTH_UNIT_PERCENT = "percent" private const val SPECTRUM_STRENGTH_UNIT_DB = "db" private const val SPECTRUM_STRENGTH_DB_MIN = -40.0f - private const val SPECTRUM_STRENGTH_DB_MAX = 0.0f + private const val SPECTRUM_STRENGTH_DB_DEFAULT_MAX = 0.0f + private const val SPECTRUM_STRENGTH_DB_BOOST_MAX = 12.0f private const val SPECTRUM_STRENGTH_PERCENT_MIN = 0.0f private const val SPECTRUM_STRENGTH_PERCENT_MAX = 100.0f private const val SPECTRUM_HARMONICS_DEFAULT_RAW = "0.02;0;0.02;0;0.02;0;0.02;0;0.02;0" private const val MAX_EQ_INTERPOLATION_MODE = 1 private val DEFAULT_SPECTRUM_HARMONICS = doubleArrayOf(0.02, 0.0, 0.02, 0.0, 0.02, 0.0, 0.02, 0.0, 0.02, 0.0) + private val SPECTRUM_STRENGTH_PERCENT_BOOST_MAX = 10.0.pow((SPECTRUM_STRENGTH_DB_BOOST_MAX / 20.0f).toDouble()).toFloat() * SPECTRUM_STRENGTH_PERCENT_MAX } abstract var enabled: Boolean @@ -128,8 +130,10 @@ abstract class JamesDspBaseEngine(val context: Context, val callbacks: JamesDspW val spectrumStrengthUnit = cache.get(R.string.key_spectrum_ext_strength_unit, SPECTRUM_STRENGTH_UNIT_PERCENT) val spectrumStrengthPercent = cache.get(R.string.key_spectrum_ext_strength_percent, 10f) val spectrumStrengthDb = cache.get(R.string.key_spectrum_ext_strength_db, -20f) + val spectrumAllowBoost = cache.get(R.string.key_spectrum_ext_allow_boost, false) val spectrumRefFreq = cache.get(R.string.key_spectrum_ext_ref_freq, 7600f).toInt() val spectrumWetMix = cache.get(R.string.key_spectrum_ext_wet_mix, 100f) + val spectrumWetOnlyMonitor = cache.get(R.string.key_spectrum_ext_wet_only_monitor, false) val spectrumPostGain = cache.get(R.string.key_spectrum_ext_post_gain, 0f) val spectrumSafety = cache.get(R.string.key_spectrum_ext_safety, false) val spectrumHpQ = cache.get(R.string.key_spectrum_ext_hp_q, 0.717f) @@ -246,8 +250,10 @@ abstract class JamesDspBaseEngine(val context: Context, val callbacks: JamesDspW spectrumStrengthUnit, spectrumStrengthPercent, spectrumStrengthDb, + spectrumAllowBoost, spectrumRefFreq, spectrumWetMix, + spectrumWetOnlyMonitor, spectrumPostGain, spectrumSafety, spectrumHpQ, @@ -491,8 +497,10 @@ abstract class JamesDspBaseEngine(val context: Context, val callbacks: JamesDspW strengthUnit: String, strengthPercent: Float, strengthDb: Float, + allowBoost: Boolean, referenceFreq: Int, wetMixPercent: Float, + wetOnlyMonitor: Boolean, postGainDb: Float, safetyEnabled: Boolean, hpQ: Float, @@ -500,8 +508,10 @@ abstract class JamesDspBaseEngine(val context: Context, val callbacks: JamesDspW lpCutoffOffsetHz: Int, harmonicsRaw: String, ): Boolean { + val maxDb = if (allowBoost) SPECTRUM_STRENGTH_DB_BOOST_MAX else SPECTRUM_STRENGTH_DB_DEFAULT_MAX + val maxPercent = if (allowBoost) SPECTRUM_STRENGTH_PERCENT_BOOST_MAX else SPECTRUM_STRENGTH_PERCENT_MAX val uiStrength = if (strengthUnit == SPECTRUM_STRENGTH_UNIT_DB) { - val clampedDb = strengthDb.coerceIn(SPECTRUM_STRENGTH_DB_MIN, SPECTRUM_STRENGTH_DB_MAX) + val clampedDb = strengthDb.coerceIn(SPECTRUM_STRENGTH_DB_MIN, maxDb) // Map the minimum db value to full-off for stable round-tripping with percent mode. if (clampedDb <= SPECTRUM_STRENGTH_DB_MIN) { 0.0f @@ -510,7 +520,7 @@ abstract class JamesDspBaseEngine(val context: Context, val callbacks: JamesDspW } } else { strengthPercent - }.coerceIn(SPECTRUM_STRENGTH_PERCENT_MIN, SPECTRUM_STRENGTH_PERCENT_MAX) + }.coerceIn(SPECTRUM_STRENGTH_PERCENT_MIN, maxPercent) // Stock ViPER mapping: param65550 = trunc(strength * 5.6), core uses /100. // The local JNI path applies Spectrum params as a single native call instead of discrete @@ -524,6 +534,7 @@ abstract class JamesDspBaseEngine(val context: Context, val callbacks: JamesDspW exciter, referenceFreq, wetMixPercent.coerceIn(0f, 100f) / 100.0f, + wetOnlyMonitor, postGainDb.coerceIn(-24f, 24f), safetyEnabled, hpQ.coerceIn(0.1f, 3.0f), @@ -791,6 +802,7 @@ abstract class JamesDspBaseEngine(val context: Context, val callbacks: JamesDspW strengthLinear: Float, referenceFreq: Int, wetMix: Float, + wetOnlyMonitor: Boolean, postGainDb: Float, safetyEnabled: Boolean, hpQ: Float, diff --git a/app/src/main/java/me/timschneeberger/rootlessjamesdsp/interop/JamesDspLocalEngine.kt b/app/src/main/java/me/timschneeberger/rootlessjamesdsp/interop/JamesDspLocalEngine.kt index 661fb8976..1585b8f4d 100644 --- a/app/src/main/java/me/timschneeberger/rootlessjamesdsp/interop/JamesDspLocalEngine.kt +++ b/app/src/main/java/me/timschneeberger/rootlessjamesdsp/interop/JamesDspLocalEngine.kt @@ -221,6 +221,7 @@ class JamesDspLocalEngine(context: Context, callbacks: JamesDspWrapper.JamesDspC strengthLinear: Float, referenceFreq: Int, wetMix: Float, + wetOnlyMonitor: Boolean, postGainDb: Float, safetyEnabled: Boolean, hpQ: Float, @@ -236,6 +237,7 @@ class JamesDspLocalEngine(context: Context, callbacks: JamesDspWrapper.JamesDspC strengthLinear, referenceFreq, wetMix, + wetOnlyMonitor, postGainDb, safetyEnabled, hpQ, diff --git a/app/src/main/java/me/timschneeberger/rootlessjamesdsp/interop/JamesDspRemoteEngine.kt b/app/src/main/java/me/timschneeberger/rootlessjamesdsp/interop/JamesDspRemoteEngine.kt index a3dd91d57..8b14f8042 100644 --- a/app/src/main/java/me/timschneeberger/rootlessjamesdsp/interop/JamesDspRemoteEngine.kt +++ b/app/src/main/java/me/timschneeberger/rootlessjamesdsp/interop/JamesDspRemoteEngine.kt @@ -478,6 +478,7 @@ class JamesDspRemoteEngine( strengthLinear: Float, referenceFreq: Int, wetMix: Float, + wetOnlyMonitor: Boolean, postGainDb: Float, safetyEnabled: Boolean, hpQ: Float, @@ -503,6 +504,9 @@ class JamesDspRemoteEngine( if (enable) { val fallbackToLegacy = { + if (wetOnlyMonitor) { + Timber.w("Spectrum wet-only monitor is unavailable in legacy protocol mode.") + } val referenceResult = effect.setParameter(PARAM_SPECTRUM_EXTENSION_BARK, referenceFreq) if (referenceResult != AudioEffect.SUCCESS) { markUnsupported(referenceResult, true) @@ -533,7 +537,7 @@ class JamesDspRemoteEngine( hpQ, lpQ, lpCutoffOffsetHz.toFloat() - ) + safeHarmonics.map { it.toFloat() } + ) + safeHarmonics.map { it.toFloat() } + floatArrayOf(if (wetOnlyMonitor) 1.0f else 0.0f) val payloadResult = effect.setParameterFloatArray(PARAM_SPECTRUM_EXTENSION, payload) if (payloadResult == AudioEffect.SUCCESS) { diff --git a/app/src/main/java/me/timschneeberger/rootlessjamesdsp/interop/JamesDspWrapper.kt b/app/src/main/java/me/timschneeberger/rootlessjamesdsp/interop/JamesDspWrapper.kt index 8e17798e3..c15bf5317 100644 --- a/app/src/main/java/me/timschneeberger/rootlessjamesdsp/interop/JamesDspWrapper.kt +++ b/app/src/main/java/me/timschneeberger/rootlessjamesdsp/interop/JamesDspWrapper.kt @@ -67,6 +67,7 @@ object JamesDspWrapper { strengthLinear: Float, referenceFreq: Int, wetMix: Float, + wetOnlyMonitor: Boolean, postGainDb: Float, safetyEnabled: Boolean, hpQ: Float, diff --git a/app/src/main/res/values/keys.xml b/app/src/main/res/values/keys.xml index 14a1c9091..27f3d7d3d 100644 --- a/app/src/main/res/values/keys.xml +++ b/app/src/main/res/values/keys.xml @@ -110,8 +110,10 @@ spectrum_ext_strength_unit spectrum_ext_strength_percent spectrum_ext_strength_db + spectrum_ext_allow_boost spectrum_ext_ref_freq spectrum_ext_wet_mix + spectrum_ext_wet_only_monitor spectrum_ext_post_gain spectrum_ext_safety spectrum_ext_hp_q diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 27045b976..de1db79d1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -147,10 +147,14 @@ Spectrum extension Strength Strength unit + Allow strength above 0 dB + Experimental. Expands the strength range above stock limits. Percent dB Reference frequency Wet mix + Monitor added signal only + Mutes the original signal and outputs only Spectrum Extension content. Post gain Safety limiter Disabled by default. Enable to reduce overload and clipping. diff --git a/app/src/main/res/xml/dsp_spectrum_ext_preferences.xml b/app/src/main/res/xml/dsp_spectrum_ext_preferences.xml index d1ad5356e..e189f5c75 100644 --- a/app/src/main/res/xml/dsp_spectrum_ext_preferences.xml +++ b/app/src/main/res/xml/dsp_spectrum_ext_preferences.xml @@ -42,6 +42,14 @@ app:showSeekBarValue="true" app:iconSpaceReserved="false" /> + + + +