mirror of
https://github.com/Boof2015/astra-mobile.git
synced 2026-08-17 11:12:33 +02:00
better normalization scheduling and logic
This commit is contained in:
+15
-7
@@ -57,8 +57,9 @@ class AstraScopeModule : Module() {
|
||||
EqBridge.revision += 1
|
||||
}
|
||||
|
||||
// Glide to an explicit gain (unity paths: no track / remote track / disabled).
|
||||
Function("setNormalizationGain") { linear: Double ->
|
||||
GainBridge.linearGain = linear.toFloat()
|
||||
GainBridge.setGainSmooth(linear.toFloat())
|
||||
}
|
||||
|
||||
// Register a queued track's gain by URL so the player can switch to it natively at
|
||||
@@ -67,14 +68,21 @@ class AstraScopeModule : Module() {
|
||||
GainBridge.putGain(url, linear.toFloat())
|
||||
}
|
||||
|
||||
// Make the registered gain for this URL active now (used for the current track on
|
||||
// mount / settings change, where no transition fires).
|
||||
Function("activateTrackGain") { url: String ->
|
||||
GainBridge.activateFor(url)
|
||||
// Bulk-register the whole queue's gains in one bridge call (gainRegistry.ts).
|
||||
Function("setTrackGains") { entries: Map<String, Double>, clearExisting: Boolean ->
|
||||
GainBridge.putGains(entries.mapValues { it.value.toFloat() }, clearExisting)
|
||||
}
|
||||
|
||||
Function("clearTrackGains") {
|
||||
GainBridge.clearGains()
|
||||
// Make the registered gain for this URL active now, as a smooth glide (mount /
|
||||
// settings change / late measurement — no media-item transition fires for these).
|
||||
Function("activateTrackGain") { url: String ->
|
||||
GainBridge.activateSmoothFor(url)
|
||||
}
|
||||
|
||||
// Conservative temp gain applied when a transition hits an unregistered URL
|
||||
// (unanalyzed track). JS keeps this at 1 while normalization is disabled.
|
||||
Function("setFallbackGain") { linear: Double ->
|
||||
GainBridge.fallbackGain = linear.toFloat()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,15 +7,54 @@ import java.util.concurrent.ConcurrentHashMap
|
||||
* kotlin-audio `NormalizationGainProcessor` on the audio thread. Applied BEFORE the
|
||||
* scope taps so the scopes see normalized levels.
|
||||
*
|
||||
* JS pre-registers each queued track's gain by URL ([putGain]); the player then swaps
|
||||
* the active gain to the matching one natively at the real media-item transition
|
||||
* ([activateFor], called from BaseAudioPlayer.onMediaItemTransition). That lands the
|
||||
* gain at the actual audio boundary instead of after a JS round-trip on track change.
|
||||
* JS pre-registers queued tracks' gains by URL ([putGain]/[putGains]); the player
|
||||
* swaps the active gain to the matching one natively at the real media-item
|
||||
* transition ([activateFor], called from BaseAudioPlayer.onMediaItemTransition).
|
||||
* That lands the gain at the actual audio boundary instead of after a JS round-trip.
|
||||
*
|
||||
* Gains are published as a (target, rampMs) pair under a [revision] counter — the
|
||||
* same pattern as [EqBridge.revision]. The processor re-arms its ramp whenever the
|
||||
* revision moves: effectively instant at media boundaries ([TRANSITION_RAMP_MS]),
|
||||
* an audible glide for mid-track corrections ([CORRECTION_RAMP_MS]) such as a late
|
||||
* loudness measurement landing or a settings toggle.
|
||||
*
|
||||
* A URL with no registered gain activates at [fallbackGain] instead of unity — a
|
||||
* conservative temporary attenuation (Poweramp-style) so an unanalyzed loud track
|
||||
* starts slightly quiet and glides up, rather than blasting and ducking. JS keeps
|
||||
* fallbackGain pinned to 1 while normalization is disabled.
|
||||
*
|
||||
* Publication race note: a reader could pair a fresh [targetGain] with a stale
|
||||
* [rampMs] for one buffer (5-20 ms); the next buffer's revision check corrects it.
|
||||
* Deliberately unfixed — no locking on the audio-thread read path.
|
||||
*/
|
||||
object GainBridge {
|
||||
/** Active linear amplitude multiplier (1 = unity). Read on the audio thread. */
|
||||
/** Declick ramp for media-item boundaries (effectively instant). */
|
||||
const val TRANSITION_RAMP_MS = 30
|
||||
|
||||
/** Audible glide for mid-track corrections (late measurement, settings toggle). */
|
||||
const val CORRECTION_RAMP_MS = 1200
|
||||
|
||||
/** Linear amplitude target the processor ramps toward (1 = unity). */
|
||||
@Volatile
|
||||
var linearGain: Float = 1f
|
||||
var targetGain: Float = 1f
|
||||
private set
|
||||
|
||||
/** Ramp duration for the most recent target change. */
|
||||
@Volatile
|
||||
var rampMs: Int = 0
|
||||
private set
|
||||
|
||||
/** Bumped LAST on every target change; the processor re-arms when it moves. */
|
||||
@Volatile
|
||||
var revision: Int = 0
|
||||
private set
|
||||
|
||||
/**
|
||||
* Applied when a transition hits a URL with no registered gain (track not yet
|
||||
* analyzed). Errs quiet by construction; JS sets 1 while normalization is off.
|
||||
*/
|
||||
@Volatile
|
||||
var fallbackGain: Float = 1f
|
||||
|
||||
// url -> linear gain, seeded from JS ahead of playback.
|
||||
private val gains = ConcurrentHashMap<String, Float>()
|
||||
@@ -25,13 +64,45 @@ object GainBridge {
|
||||
gains[url] = gain
|
||||
}
|
||||
|
||||
/** Make the gain registered for [url] active (unity if unknown/null). */
|
||||
/**
|
||||
* Bulk-register the whole queue's gains in one call. With [clearExisting] the map
|
||||
* is cleared first (bounds it to the live queue, drops stale-settings entries);
|
||||
* a transition landing in the microsecond clear-to-putAll window would activate
|
||||
* [fallbackGain] — errs quiet, acceptable.
|
||||
*/
|
||||
fun putGains(entries: Map<String, Float>, clearExisting: Boolean) {
|
||||
if (clearExisting) gains.clear()
|
||||
gains.putAll(entries)
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the gain registered for [url] active at the audio boundary (declick-fast
|
||||
* ramp). Misses fall back to [fallbackGain], NOT unity — that is the core fix for
|
||||
* the "loud first seconds" burst on unanalyzed tracks.
|
||||
*/
|
||||
fun activateFor(url: String?) {
|
||||
linearGain = if (url != null) gains[url] ?: 1f else 1f
|
||||
setTarget(url?.let { gains[it] } ?: fallbackGain, TRANSITION_RAMP_MS)
|
||||
}
|
||||
|
||||
/** Like [activateFor] but glides — for JS-initiated mid-track corrections. */
|
||||
fun activateSmoothFor(url: String) {
|
||||
setTarget(gains[url] ?: fallbackGain, CORRECTION_RAMP_MS)
|
||||
}
|
||||
|
||||
/** Glide to an explicit gain (unity paths: no track / remote track / disabled). */
|
||||
fun setGainSmooth(linear: Float) {
|
||||
setTarget(linear, CORRECTION_RAMP_MS)
|
||||
}
|
||||
|
||||
/** Drop all registered gains (e.g. on full queue reset). */
|
||||
fun clearGains() {
|
||||
gains.clear()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun setTarget(gain: Float, durationMs: Int) {
|
||||
targetGain = gain
|
||||
rampMs = durationMs
|
||||
revision += 1
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user