mirror of
https://github.com/Boof2015/astra-mobile.git
synced 2026-08-12 05:10:52 +02:00
m4
This commit is contained in:
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+1
-1
@@ -1 +1 @@
|
||||
#Wed Jun 17 14:38:54 EDT 2026
|
||||
#Fri Jun 19 12:51:01 EDT 2026
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+1
-1
@@ -1,2 +1,2 @@
|
||||
31
|
||||
34
|
||||
0
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+7
@@ -24,6 +24,7 @@ import com.doublesymmetry.kotlinaudio.models.AudioItem
|
||||
import com.doublesymmetry.kotlinaudio.models.AudioItemHolder
|
||||
import com.doublesymmetry.kotlinaudio.models.AudioItemTransitionReason
|
||||
import com.doublesymmetry.kotlinaudio.models.AudioPlayerState
|
||||
import expo.modules.astrascope.GainBridge
|
||||
import com.doublesymmetry.kotlinaudio.models.BufferConfig
|
||||
import com.doublesymmetry.kotlinaudio.models.CacheConfig
|
||||
import com.doublesymmetry.kotlinaudio.models.DefaultPlayerOptions
|
||||
@@ -666,6 +667,12 @@ abstract class BaseAudioPlayer internal constructor(
|
||||
* playlist becomes non-empty or empty as a consequence of a playlist change.
|
||||
*/
|
||||
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
|
||||
// Apply the per-track normalization gain natively, exactly at the audio
|
||||
// transition — the gain map is pre-seeded from JS by URL, so this is just a
|
||||
// lock-free lookup (no JS round-trip on track change).
|
||||
val url = runCatching { mediaItem?.getAudioItemHolder()?.audioItem?.audioUrl }.getOrNull()
|
||||
GainBridge.activateFor(url)
|
||||
|
||||
when (reason) {
|
||||
Player.MEDIA_ITEM_TRANSITION_REASON_AUTO -> playerEventHolder.updateAudioItemTransition(
|
||||
AudioItemTransitionReason.AUTO(oldPosition)
|
||||
|
||||
+258
@@ -0,0 +1,258 @@
|
||||
package com.doublesymmetry.kotlinaudio.scope
|
||||
|
||||
import com.google.android.exoplayer2.C
|
||||
import com.google.android.exoplayer2.audio.AudioProcessor
|
||||
import com.google.android.exoplayer2.audio.BaseAudioProcessor
|
||||
import expo.modules.astrascope.EqBridge
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
import kotlin.math.PI
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.roundToInt
|
||||
import kotlin.math.sin
|
||||
import kotlin.math.sqrt
|
||||
|
||||
/**
|
||||
* Parametric EQ as an ExoPlayer AudioProcessor (M4). Reads raw band params from
|
||||
* [EqBridge] (set from JS) and computes Audio-EQ-Cookbook biquad coefficients at
|
||||
* the real stream sample rate — mirroring Web Audio's BiquadFilterNode on desktop.
|
||||
* A cascade of transposed-direct-form-II biquads runs per channel after a preamp.
|
||||
*
|
||||
* Passthrough (bit-exact) when the EQ is disabled or has no active bands and unity
|
||||
* preamp, so toggling EQ off is lossless. Handles PCM float and 16-bit; coefficients
|
||||
* are rebuilt only when [EqBridge.revision] changes (cheap, off the per-sample path).
|
||||
*/
|
||||
class EqAudioProcessor : BaseAudioProcessor() {
|
||||
private var channels = 0
|
||||
private var sampleRate = 0f
|
||||
|
||||
private var lastRevision = Int.MIN_VALUE
|
||||
private var enabled = false
|
||||
private var preamp = 1f
|
||||
private var bandCount = 0
|
||||
private var coeffs = FloatArray(0) // 5 per band: b0,b1,b2,a1,a2 (a0-normalized)
|
||||
private var z1 = FloatArray(0) // bandCount * channels
|
||||
private var z2 = FloatArray(0)
|
||||
|
||||
private var floatScratch = FloatArray(0)
|
||||
|
||||
override fun onConfigure(
|
||||
inputAudioFormat: AudioProcessor.AudioFormat
|
||||
): AudioProcessor.AudioFormat {
|
||||
channels = inputAudioFormat.channelCount
|
||||
sampleRate = inputAudioFormat.sampleRate.toFloat()
|
||||
lastRevision = Int.MIN_VALUE // force a rebuild on the next buffer
|
||||
return inputAudioFormat
|
||||
}
|
||||
|
||||
override fun queueInput(inputBuffer: ByteBuffer) {
|
||||
val remaining = inputBuffer.remaining()
|
||||
if (remaining <= 0) return
|
||||
|
||||
rebuildIfNeeded()
|
||||
|
||||
val passthrough = !enabled || channels <= 0 || (bandCount == 0 && preamp == 1f)
|
||||
if (passthrough) {
|
||||
val out = replaceOutputBuffer(remaining)
|
||||
out.put(inputBuffer)
|
||||
out.flip()
|
||||
return
|
||||
}
|
||||
|
||||
when (inputAudioFormat.encoding) {
|
||||
C.ENCODING_PCM_FLOAT -> processFloat(inputBuffer, remaining)
|
||||
C.ENCODING_PCM_16BIT -> process16(inputBuffer, remaining)
|
||||
else -> {
|
||||
val out = replaceOutputBuffer(remaining)
|
||||
out.put(inputBuffer)
|
||||
out.flip()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFlush() {
|
||||
z1.fill(0f)
|
||||
z2.fill(0f)
|
||||
}
|
||||
|
||||
private fun rebuildIfNeeded() {
|
||||
val rev = EqBridge.revision
|
||||
if (rev == lastRevision) return
|
||||
lastRevision = rev
|
||||
|
||||
enabled = EqBridge.enabled
|
||||
preamp = EqBridge.preampLinear
|
||||
val params = EqBridge.bands
|
||||
val total = params.size / 5
|
||||
|
||||
var active = 0
|
||||
for (i in 0 until total) if (params[i * 5 + 4] != 0f) active++
|
||||
|
||||
// Reset filter state only when the band count changes (avoid clicks on tweaks).
|
||||
if (active != bandCount) {
|
||||
bandCount = active
|
||||
coeffs = FloatArray(active * 5)
|
||||
z1 = FloatArray(active * channels.coerceAtLeast(1))
|
||||
z2 = FloatArray(active * channels.coerceAtLeast(1))
|
||||
}
|
||||
|
||||
var bi = 0
|
||||
for (i in 0 until total) {
|
||||
if (params[i * 5 + 4] == 0f) continue
|
||||
computeCoeffs(
|
||||
params[i * 5].toInt(),
|
||||
params[i * 5 + 1],
|
||||
params[i * 5 + 2],
|
||||
params[i * 5 + 3],
|
||||
sampleRate,
|
||||
coeffs,
|
||||
bi * 5
|
||||
)
|
||||
bi++
|
||||
}
|
||||
}
|
||||
|
||||
private fun processFloat(inputBuffer: ByteBuffer, remaining: Int) {
|
||||
val fb = inputBuffer.asFloatBuffer()
|
||||
val n = fb.remaining()
|
||||
if (n <= 0) return
|
||||
if (floatScratch.size < n) floatScratch = FloatArray(n)
|
||||
fb.get(floatScratch, 0, n)
|
||||
inputBuffer.position(inputBuffer.limit()) // mark input consumed
|
||||
|
||||
processSamples(floatScratch, n)
|
||||
|
||||
val out = replaceOutputBuffer(n * 4).order(ByteOrder.nativeOrder())
|
||||
out.asFloatBuffer().put(floatScratch, 0, n)
|
||||
out.position(n * 4)
|
||||
out.flip()
|
||||
}
|
||||
|
||||
private fun process16(inputBuffer: ByteBuffer, remaining: Int) {
|
||||
val sb = inputBuffer.asShortBuffer()
|
||||
val n = sb.remaining()
|
||||
if (n <= 0) return
|
||||
if (floatScratch.size < n) floatScratch = FloatArray(n)
|
||||
var i = 0
|
||||
while (i < n) {
|
||||
floatScratch[i] = sb.get(i) / 32768f
|
||||
i++
|
||||
}
|
||||
inputBuffer.position(inputBuffer.limit())
|
||||
|
||||
processSamples(floatScratch, n)
|
||||
|
||||
val out = replaceOutputBuffer(n * 2).order(ByteOrder.nativeOrder())
|
||||
val osb = out.asShortBuffer()
|
||||
i = 0
|
||||
while (i < n) {
|
||||
val v = (floatScratch[i] * 32768f).roundToInt().coerceIn(-32768, 32767)
|
||||
osb.put(v.toShort())
|
||||
i++
|
||||
}
|
||||
out.position(n * 2)
|
||||
out.flip()
|
||||
}
|
||||
|
||||
/** Apply preamp + the biquad cascade in place over interleaved samples. */
|
||||
private fun processSamples(buf: FloatArray, n: Int) {
|
||||
val ch = channels
|
||||
val bc = bandCount
|
||||
val pre = preamp
|
||||
var c = 0
|
||||
var i = 0
|
||||
while (i < n) {
|
||||
var x = buf[i] * pre
|
||||
var b = 0
|
||||
while (b < bc) {
|
||||
val co = b * 5
|
||||
val b0 = coeffs[co]
|
||||
val b1 = coeffs[co + 1]
|
||||
val b2 = coeffs[co + 2]
|
||||
val a1 = coeffs[co + 3]
|
||||
val a2 = coeffs[co + 4]
|
||||
val si = b * ch + c
|
||||
val s1 = z1[si]
|
||||
val s2 = z2[si]
|
||||
val y = b0 * x + s1
|
||||
z1[si] = b1 * x - a1 * y + s2
|
||||
z2[si] = b2 * x - a2 * y
|
||||
x = y
|
||||
b++
|
||||
}
|
||||
buf[i] = x
|
||||
c++
|
||||
if (c == ch) c = 0
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Audio-EQ-Cookbook biquad coefficients (a0-normalized) into out[off..off+4].
|
||||
* Type ordinals match EQ_BAND_TYPE_ORDINAL in src/audio/eq.ts:
|
||||
* 0 lowshelf, 1 peaking, 2 highshelf, 3 highpass, 4 lowpass.
|
||||
*/
|
||||
private fun computeCoeffs(
|
||||
type: Int,
|
||||
freq: Float,
|
||||
gainDb: Float,
|
||||
q: Float,
|
||||
sr: Float,
|
||||
out: FloatArray,
|
||||
off: Int
|
||||
) {
|
||||
if (sr <= 0f) {
|
||||
out[off] = 1f; out[off + 1] = 0f; out[off + 2] = 0f; out[off + 3] = 0f; out[off + 4] = 0f
|
||||
return
|
||||
}
|
||||
val w0 = 2.0 * PI * freq / sr
|
||||
val cosW0 = cos(w0)
|
||||
val sinW0 = sin(w0)
|
||||
val a = 10.0.pow(gainDb / 40.0)
|
||||
val alpha = sinW0 / (2.0 * q.coerceAtLeast(0.0001f))
|
||||
|
||||
var b0 = 1.0; var b1 = 0.0; var b2 = 0.0
|
||||
var a0 = 1.0; var a1 = 0.0; var a2 = 0.0
|
||||
|
||||
when (type) {
|
||||
1 -> { // peaking
|
||||
b0 = 1 + alpha * a; b1 = -2 * cosW0; b2 = 1 - alpha * a
|
||||
a0 = 1 + alpha / a; a1 = -2 * cosW0; a2 = 1 - alpha / a
|
||||
}
|
||||
0 -> { // lowshelf
|
||||
val sqrtA = sqrt(a)
|
||||
b0 = a * (a + 1 - (a - 1) * cosW0 + 2 * sqrtA * alpha)
|
||||
b1 = 2 * a * (a - 1 - (a + 1) * cosW0)
|
||||
b2 = a * (a + 1 - (a - 1) * cosW0 - 2 * sqrtA * alpha)
|
||||
a0 = a + 1 + (a - 1) * cosW0 + 2 * sqrtA * alpha
|
||||
a1 = -2 * (a - 1 + (a + 1) * cosW0)
|
||||
a2 = a + 1 + (a - 1) * cosW0 - 2 * sqrtA * alpha
|
||||
}
|
||||
2 -> { // highshelf
|
||||
val sqrtA = sqrt(a)
|
||||
b0 = a * (a + 1 + (a - 1) * cosW0 + 2 * sqrtA * alpha)
|
||||
b1 = -2 * a * (a - 1 + (a + 1) * cosW0)
|
||||
b2 = a * (a + 1 + (a - 1) * cosW0 - 2 * sqrtA * alpha)
|
||||
a0 = a + 1 - (a - 1) * cosW0 + 2 * sqrtA * alpha
|
||||
a1 = 2 * (a - 1 - (a + 1) * cosW0)
|
||||
a2 = a + 1 - (a - 1) * cosW0 - 2 * sqrtA * alpha
|
||||
}
|
||||
4 -> { // lowpass
|
||||
b0 = (1 - cosW0) / 2; b1 = 1 - cosW0; b2 = (1 - cosW0) / 2
|
||||
a0 = 1 + alpha; a1 = -2 * cosW0; a2 = 1 - alpha
|
||||
}
|
||||
3 -> { // highpass
|
||||
b0 = (1 + cosW0) / 2; b1 = -(1 + cosW0); b2 = (1 + cosW0) / 2
|
||||
a0 = 1 + alpha; a1 = -2 * cosW0; a2 = 1 - alpha
|
||||
}
|
||||
}
|
||||
|
||||
val inv = 1.0 / a0
|
||||
out[off] = (b0 * inv).toFloat()
|
||||
out[off + 1] = (b1 * inv).toFloat()
|
||||
out[off + 2] = (b2 * inv).toFloat()
|
||||
out[off + 3] = (a1 * inv).toFloat()
|
||||
out[off + 4] = (a2 * inv).toFloat()
|
||||
}
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
package com.doublesymmetry.kotlinaudio.scope
|
||||
|
||||
import com.google.android.exoplayer2.C
|
||||
import com.google.android.exoplayer2.audio.AudioProcessor
|
||||
import com.google.android.exoplayer2.audio.BaseAudioProcessor
|
||||
import expo.modules.astrascope.GainBridge
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
/**
|
||||
* Applies the per-track normalization / ReplayGain gain from [GainBridge] (set from
|
||||
* JS on track/settings change). Sits FIRST in the chain — before the scope taps —
|
||||
* so the visualizers see normalized levels (the user's "better for scopes" goal).
|
||||
*
|
||||
* Bit-exact passthrough when the gain is unity. Handles PCM float and 16-bit; the
|
||||
* 16-bit path clamps to int16 range. The gain resolver already backs off so the
|
||||
* post-gain peak stays <= 0.98, so clipping should not occur here in practice.
|
||||
*/
|
||||
class NormalizationGainProcessor : BaseAudioProcessor() {
|
||||
private var floatScratch = FloatArray(0)
|
||||
|
||||
override fun onConfigure(
|
||||
inputAudioFormat: AudioProcessor.AudioFormat
|
||||
): AudioProcessor.AudioFormat = inputAudioFormat
|
||||
|
||||
override fun queueInput(inputBuffer: ByteBuffer) {
|
||||
val remaining = inputBuffer.remaining()
|
||||
if (remaining <= 0) return
|
||||
|
||||
val gain = GainBridge.linearGain
|
||||
if (gain == 1f) {
|
||||
val out = replaceOutputBuffer(remaining)
|
||||
out.put(inputBuffer)
|
||||
out.flip()
|
||||
return
|
||||
}
|
||||
|
||||
when (inputAudioFormat.encoding) {
|
||||
C.ENCODING_PCM_FLOAT -> {
|
||||
val fb = inputBuffer.asFloatBuffer()
|
||||
val n = fb.remaining()
|
||||
if (n <= 0) return
|
||||
if (floatScratch.size < n) floatScratch = FloatArray(n)
|
||||
fb.get(floatScratch, 0, n)
|
||||
inputBuffer.position(inputBuffer.limit())
|
||||
var i = 0
|
||||
while (i < n) {
|
||||
floatScratch[i] = floatScratch[i] * gain
|
||||
i++
|
||||
}
|
||||
val out = replaceOutputBuffer(n * 4).order(ByteOrder.nativeOrder())
|
||||
out.asFloatBuffer().put(floatScratch, 0, n)
|
||||
out.position(n * 4)
|
||||
out.flip()
|
||||
}
|
||||
C.ENCODING_PCM_16BIT -> {
|
||||
val sb = inputBuffer.asShortBuffer()
|
||||
val n = sb.remaining()
|
||||
if (n <= 0) return
|
||||
val out = replaceOutputBuffer(n * 2).order(ByteOrder.nativeOrder())
|
||||
val osb = out.asShortBuffer()
|
||||
var i = 0
|
||||
while (i < n) {
|
||||
val v = (sb.get(i) * gain).roundToInt().coerceIn(-32768, 32767)
|
||||
osb.put(v.toShort())
|
||||
i++
|
||||
}
|
||||
inputBuffer.position(inputBuffer.limit())
|
||||
out.position(n * 2)
|
||||
out.flip()
|
||||
}
|
||||
else -> {
|
||||
val out = replaceOutputBuffer(remaining)
|
||||
out.put(inputBuffer)
|
||||
out.flip()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
package com.doublesymmetry.kotlinaudio.scope
|
||||
|
||||
import com.google.android.exoplayer2.C
|
||||
import com.google.android.exoplayer2.audio.AudioProcessor
|
||||
import com.google.android.exoplayer2.audio.BaseAudioProcessor
|
||||
import expo.modules.astrascope.ScopeBridge
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
|
||||
/**
|
||||
* Pass-through tap placed AFTER the EQ processor (M4). Identical to
|
||||
* ScopeTapAudioProcessor but pushes to the native post-EQ ring (ring #2) which
|
||||
* feeds the EQ screen's response-curve spectrum overlay. Gated by
|
||||
* `ScopeBridge.active && ScopeBridge.postEqActive` so it costs ~zero unless the
|
||||
* EQ screen is open and the app is foregrounded + playing.
|
||||
*/
|
||||
class PostEqTapAudioProcessor : BaseAudioProcessor() {
|
||||
private var scratch = FloatArray(0)
|
||||
|
||||
override fun onConfigure(
|
||||
inputAudioFormat: AudioProcessor.AudioFormat
|
||||
): AudioProcessor.AudioFormat = inputAudioFormat
|
||||
|
||||
override fun queueInput(inputBuffer: ByteBuffer) {
|
||||
val remaining = inputBuffer.remaining()
|
||||
if (remaining <= 0) return
|
||||
|
||||
if (ScopeBridge.active && ScopeBridge.postEqActive) {
|
||||
tap(inputBuffer)
|
||||
}
|
||||
|
||||
val out = replaceOutputBuffer(remaining)
|
||||
out.put(inputBuffer)
|
||||
out.flip()
|
||||
}
|
||||
|
||||
private fun tap(inputBuffer: ByteBuffer) {
|
||||
val channels = inputAudioFormat.channelCount
|
||||
if (channels <= 0) return
|
||||
val dup = inputBuffer.duplicate().order(ByteOrder.nativeOrder())
|
||||
|
||||
when (inputAudioFormat.encoding) {
|
||||
C.ENCODING_PCM_FLOAT -> {
|
||||
val fb = dup.asFloatBuffer()
|
||||
val n = fb.remaining()
|
||||
if (n <= 0) return
|
||||
if (scratch.size < n) scratch = FloatArray(n)
|
||||
fb.get(scratch, 0, n)
|
||||
ScopeBridge.nativePushFramesPostEq(scratch, n / channels, channels)
|
||||
}
|
||||
C.ENCODING_PCM_16BIT -> {
|
||||
val sb = dup.asShortBuffer()
|
||||
val n = sb.remaining()
|
||||
if (n <= 0) return
|
||||
if (scratch.size < n) scratch = FloatArray(n)
|
||||
var i = 0
|
||||
while (i < n) {
|
||||
scratch[i] = sb.get(i) / 32768f
|
||||
i++
|
||||
}
|
||||
ScopeBridge.nativePushFramesPostEq(scratch, n / channels, channels)
|
||||
}
|
||||
else -> { /* unsupported PCM encoding — forward only */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
+16
-5
@@ -7,10 +7,14 @@ import com.google.android.exoplayer2.audio.AudioSink
|
||||
import com.google.android.exoplayer2.audio.DefaultAudioSink
|
||||
|
||||
/**
|
||||
* A DefaultRenderersFactory whose audio sink runs our pre-EQ PCM tap as the
|
||||
* first (and, for M3, only) AudioProcessor. Float-output / playback-param
|
||||
* capabilities are preserved by forwarding the flags. M4 will prepend the EQ
|
||||
* AudioProcessor (and add a second post-EQ tap) to this same chain.
|
||||
* A DefaultRenderersFactory whose audio sink runs the M4 processing chain.
|
||||
* Order matters:
|
||||
* 1. NormalizationGainProcessor — per-track gain (before the taps, so the
|
||||
* scopes see normalized levels).
|
||||
* 2. ScopeTapAudioProcessor — the pre-EQ tap (post-normalization) → scope ring #1.
|
||||
* 3. EqAudioProcessor — preamp + parametric biquad chain.
|
||||
* 4. PostEqTapAudioProcessor — post-EQ tap → scope ring #2 (EQ screen overlay).
|
||||
* Float-output / playback-param capabilities are preserved by forwarding the flags.
|
||||
*/
|
||||
fun buildScopeRenderersFactory(context: Context): DefaultRenderersFactory =
|
||||
object : DefaultRenderersFactory(context) {
|
||||
@@ -23,6 +27,13 @@ fun buildScopeRenderersFactory(context: Context): DefaultRenderersFactory =
|
||||
DefaultAudioSink.Builder(context)
|
||||
.setEnableFloatOutput(enableFloatOutput)
|
||||
.setEnableAudioTrackPlaybackParams(enableAudioTrackPlaybackParams)
|
||||
.setAudioProcessors(arrayOf<AudioProcessor>(ScopeTapAudioProcessor()))
|
||||
.setAudioProcessors(
|
||||
arrayOf<AudioProcessor>(
|
||||
NormalizationGainProcessor(),
|
||||
ScopeTapAudioProcessor(),
|
||||
EqAudioProcessor(),
|
||||
PostEqTapAudioProcessor()
|
||||
)
|
||||
)
|
||||
.build()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user