/** * @file AudioCue.kt * @brief Spoken coaching cues and queue policy for [AudioFeedbackEngine]. */ package com.example.jnicpp.bowling /** * @brief A spoken coaching cue delivered by [AudioFeedbackEngine]. * * [text] is handed verbatim to TTS. [priority] is informational for callers * deciding which [QueuePolicy] to apply, higher numbers are more urgent * (e.g. a timing correction mid-approach outranks a general encouragement). * * Extend this sealed class to add new domain-specific cues without touching * the engine; the engine only cares about [text] and the policy the caller * chooses. */ sealed class AudioCue(val text: String, val priority: Int) { // -- Arm / elbow cues (medium priority) -- /** Elbow angle too acute -- arm is over-bent during swing. */ object StraightenArm : AudioCue("Straighten your bowling arm", priority = 5) /** Elbow locking out too early, arm rigid before release point. */ object BendElbow : AudioCue("Bend your elbow slightly", priority = 5) // -- Shoulder cues (medium priority) -- /** Bowling-side shoulder rising, disrupting swing plane. */ object LowerShoulder : AudioCue("Keep your shoulder down", priority = 5) /** Shoulder plane collapsing -- both shoulders dropping together. */ object LevelShoulders : AudioCue("Level your shoulders", priority = 5) // -- Approach / timing cues (higher priority) -- /** Approach tempo too fast; bowler rushing the delivery. */ object SlowDown : AudioCue("Slow down your approach", priority = 7) /** Positive reinforcement -- form looks good this frame. */ object GoodForm : AudioCue("Good form, keep it up", priority = 3) // -- Escape hatch for one-off or dynamically constructed messages -- /** * @brief Arbitrary spoken message not covered by the predefined cues above. * @param message The text to speak. * @param p Priority; defaults to medium (5). */ data class Custom(val message: String, val p: Int = 5) : AudioCue(message, p) } /** * @brief Controls how [AudioFeedbackEngine] handles a new cue when TTS is already busy. * * Choose the policy at the call site based on how time-sensitive the cue is: * * | Policy | Behaviour | * |-------------------|------------------------------------------------------------------| * | [INTERRUPT] | Flushes the TTS queue and speaks immediately. Use for urgent | * | | corrections that must be heard right now. | * | [QUEUE] | Appended after whatever is currently playing. Use for cues | * | | that can wait their turn (e.g. a sequence of tips post-throw). | * | [DISCARD_IF_BUSY] | Silently dropped if TTS is already speaking. The default for | * | | live per-frame cues, preventing the same note from stacking up | * | | across many frames while TTS works through an earlier one. | */ enum class QueuePolicy { INTERRUPT, QUEUE, DISCARD_IF_BUSY }