Merge branch 'Khalil' into Gabriel
This commit is contained in:
@@ -62,6 +62,16 @@ class BowlingCameraActivity : AppCompatActivity(), CameraXController.Callback {
|
||||
private lateinit var cameraXController: CameraXController
|
||||
private var lensFacing = CameraSelector.LENS_FACING_BACK
|
||||
|
||||
private lateinit var audioFeedbackSettings: AudioFeedbackSettings
|
||||
private lateinit var audioFeedbackEngine: AudioFeedbackEngine
|
||||
|
||||
// Minimum gap between two audio cues, in milliseconds. Prevents the same
|
||||
// coaching note from re-triggering the moment TTS finishes if the posture
|
||||
// issue persists across many frames. DISCARD_IF_BUSY handles in-flight
|
||||
// overlap; this cooldown handles the gap immediately after TTS goes silent.
|
||||
private var lastCueMs = 0L
|
||||
private val cueCooldownMs = 3_000L
|
||||
|
||||
// Throttled diagnostic for step-count troubleshooting: confirms whether
|
||||
// ankles are actually clearing PoseSkeletonRenderer.MIN_LIKELIHOOD, since
|
||||
// LiveStepDetector silently sees nothing for a foot until they do.
|
||||
@@ -129,6 +139,15 @@ class BowlingCameraActivity : AppCompatActivity(), CameraXController.Callback {
|
||||
textStepCountBig = binding.textStepCountBig,
|
||||
)
|
||||
feedbackUI = FeedbackUI(binding.root)
|
||||
audioFeedbackSettings = AudioFeedbackSettings(applicationContext)
|
||||
audioFeedbackEngine = AudioFeedbackEngine(applicationContext, audioFeedbackSettings)
|
||||
|
||||
// Reflect persisted mute state onto the switch before attaching the
|
||||
// listener so the initial setChecked doesn't trigger the callback.
|
||||
binding.switchAudio.isChecked = !audioFeedbackSettings.isMuted
|
||||
binding.switchAudio.setOnCheckedChangeListener { _, isChecked ->
|
||||
audioFeedbackSettings.isMuted = !isChecked
|
||||
}
|
||||
|
||||
binding.poseOverlay.attachFeedback(feedbackUI)
|
||||
binding.btnGrantPermissions.setOnClickListener {
|
||||
@@ -528,6 +547,7 @@ class BowlingCameraActivity : AppCompatActivity(), CameraXController.Callback {
|
||||
override fun onPoseResult(result: PoseAnalyzer.PoseFrameResult) {
|
||||
binding.poseOverlay.update(result)
|
||||
viewModel.onPoseFrameUpdated(result.landmarks, result.angles)
|
||||
triggerAudioFeedback(result.angles)
|
||||
|
||||
val frameTimestampMs = System.currentTimeMillis()
|
||||
debugSessionLogger.log(
|
||||
@@ -554,12 +574,55 @@ class BowlingCameraActivity : AppCompatActivity(), CameraXController.Callback {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Evaluates [angles] against basic form thresholds and speaks a
|
||||
* coaching cue when a limit is exceeded.
|
||||
*
|
||||
* Only one cue is emitted per call (the highest-priority issue found
|
||||
* first). A [cueCooldownMs] guard prevents the same note from
|
||||
* re-firing the instant TTS goes silent after speaking it.
|
||||
* [QueuePolicy.DISCARD_IF_BUSY] means any cue arriving while TTS is
|
||||
* mid-sentence is silently dropped at the engine level, so live
|
||||
* per-frame calls here never stack up.
|
||||
*
|
||||
* Angle thresholds below are starting-point estimates; they should be
|
||||
* calibrated against recorded sessions once the posture-analysis
|
||||
* milestone establishes target ranges per bowling phase.
|
||||
*
|
||||
* @param angles Joint angles computed for the current frame.
|
||||
*/
|
||||
private fun triggerAudioFeedback(angles: PoseAngles) {
|
||||
val now = System.currentTimeMillis()
|
||||
if (now - lastCueMs < cueCooldownMs) return
|
||||
|
||||
// Prefer the dominant (right) arm; fall back to left if right is
|
||||
// not detected. Null means neither arm was reliably seen this frame.
|
||||
val elbowAngle = angles.rightElbow ?: angles.leftElbow
|
||||
val shoulderAngle = angles.rightShoulder ?: angles.leftShoulder
|
||||
|
||||
val cue: AudioCue? = when {
|
||||
// Arm locked straight well before the release point.
|
||||
elbowAngle != null && elbowAngle > 160f -> AudioCue.BendElbow
|
||||
// Arm over-bent; disrupts swing plane and release.
|
||||
elbowAngle != null && elbowAngle < 80f -> AudioCue.StraightenArm
|
||||
// Bowling-side shoulder lifting during the swing.
|
||||
shoulderAngle != null && shoulderAngle > 140f -> AudioCue.LowerShoulder
|
||||
else -> null
|
||||
}
|
||||
|
||||
if (cue != null) {
|
||||
audioFeedbackEngine.speak(cue, QueuePolicy.DISCARD_IF_BUSY)
|
||||
lastCueMs = now
|
||||
}
|
||||
}
|
||||
|
||||
/** @brief Releases the camera controller, shuts down the analysis executor, and closes any open debug trace file. */
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
cameraXController.release()
|
||||
cameraExecutor.shutdown()
|
||||
debugSessionLogger.stop()
|
||||
audioFeedbackEngine.release()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user