Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ android {
applicationId = "tech.capullo.quantumcast"
minSdk = 26
targetSdk = 36
versionCode = 12
versionCode = 14
versionName = "1.0"
}

Expand Down
6 changes: 6 additions & 0 deletions app/src/main/java/tech/capullo/quantumcast/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,15 @@ class MainActivity : ComponentActivity() {
// /sdcard/Android/data/<pkg>/files/, so estimator questions can be answered offline
// instead of costing a rig session. --ez probe true separates the arrivals first.
// am start ... --es dbg pcmdump [--ez probe true] [--es target <client name>]
// --ei settle <ms> overrides the post-probe settle, so SETTLE_MS can be measured
// from dumps instead of from 7-minute calibrations. --ei capture <ms> shortens the
// recording, which is what makes a too-short settle visible: at 12 s the contamination
// is ~10 % of the window and averages away.
"pcmdump" -> vm.dumpCapturePcm(
intent.getStringExtra("target"),
intent.getBooleanExtra("probe", true),
intent.getIntExtra("settle", 7_000).toLong(),
intent.getIntExtra("capture", 12_000),
)
// Fix the snapserver base port (--ei port N; omit or -1 to auto-pick, 0 to disable).
// Applies on the next broadcast start. am start ... --es dbg fixport --ei port 34000
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -421,8 +421,26 @@ class PlaybackService : Service() {
*
* [probe] optionally offsets one client's latency first, so the dump can capture the SEPARATED
* geometry the balance actually harvests from rather than the overlapped baseline.
*
* [settleMs] is the pause between commanding that offset and recording — the same quantity
* SyncCalibrator spends as SETTLE_MS, exposed here so it can be MEASURED instead of assumed.
* A dump costs ~40 s against a calibration's ~7 min, and the commanded offset (SWEEP_PROBE_MS)
* is ground truth: recover it and the settle was long enough. Debug-only path; the default is
* the production value.
*
* [captureMs] shortens the recording for the same reason. At the production 12 s, the ~1.15 s
* of audio still in flight when a latency is written is only ~10 % of the window, so the
* correlation averages it away and the measurement cannot tell a settle of 0 from one of 7000
* — which is exactly what the first run of this experiment found. Shrink the window and the
* contamination dominates instead: at 2 s it is more than half, so a too-short settle has to
* move the peak. That is what makes the instrument able to discriminate at all.
*/
fun dumpCapturePcm(targetName: String?, probe: Boolean) {
fun dumpCapturePcm(
targetName: String?,
probe: Boolean,
settleMs: Long = 7_000L,
captureMs: Int = 12_000,
) {
if (calibrationJob?.isActive == true) return
val control = snapcastControl
val connected = _state.value.snapcastGroups.flatMap { it.clients }.filter { it.connected }
Expand Down Expand Up @@ -454,10 +472,11 @@ class PlaybackService : Service() {
Log.w(TAG_CAL, "pcmdump: could not journal — dumping unprobed to stay recoverable")
} else {
control!!.sendSetLatency(target.id, baseLatency - SWEEP_PROBE_MS)
delay(7_000L)
Log.i(TAG_CAL, "pcmdump: settle ${settleMs}ms after probing $SWEEP_PROBE_MS ms")
delay(settleMs)
}
}
val cap = mic.record(12_000)
val cap = mic.record(captureMs)
?: run {
Log.w(TAG_CAL, "pcmdump: capture failed")
return@launch
Expand Down Expand Up @@ -498,6 +517,8 @@ class PlaybackService : Service() {
appendLine("lagFormula=(index-pre)*1000/fs")
appendLine("probed=$probing")
appendLine("probeMs=${if (probing) SWEEP_PROBE_MS else 0}")
appendLine("settleMs=${if (probing) settleMs else 0}")
appendLine("captureMs=$captureMs")
appendLine("probeTarget=${if (probing) target?.config?.name else ""}")
appendLine(
"gains=" + connected.joinToString(",") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,12 @@ class RadioViewModel @Inject constructor(
fun measureLevelSweep(target: String?) = playbackService?.measureLevelSweep(target)

/** Dump one capture's decimated reference+mic PCM to external files, for offline estimator work. */
fun dumpCapturePcm(target: String?, probe: Boolean) = playbackService?.dumpCapturePcm(target, probe)
fun dumpCapturePcm(
target: String?,
probe: Boolean,
settleMs: Long = 7_000L,
captureMs: Int = 12_000,
) = playbackService?.dumpCapturePcm(target, probe, settleMs, captureMs)

/** Revert the volumes the last calibration balance overwrote. */
fun undoBalancedVolumes() = playbackService?.undoBalancedVolumes()
Expand Down
Loading