/** * @file AudioFeedbackSettings.kt * @brief SharedPreferences-backed user preferences for audio coaching feedback. */ package com.example.jnicpp.bowling import android.content.Context /** * @brief Persisted audio-feedback preferences: mute toggle and volume level. * * Reads and writes go straight to SharedPreferences via `apply()` (async * disk write), so property access is safe on any thread. [AudioFeedbackEngine] * reads these on every [AudioFeedbackEngine.speak] call, meaning a mute or * volume change takes effect for the very next cue without restarting the engine. * * Instantiate once per app session (e.g. in the Activity that owns * [AudioFeedbackEngine]) and pass the same instance wherever settings need * to be read or written. */ class AudioFeedbackSettings(context: Context) { private val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) /** * @brief Whether all audio feedback is silenced. * * When true, [AudioFeedbackEngine.speak] is a no-op regardless of * [volume]. Defaults to false (audio on). */ var isMuted: Boolean get() = prefs.getBoolean(KEY_MUTED, false) set(value) { prefs.edit().putBoolean(KEY_MUTED, value).apply() } /** * @brief Playback volume in [0.0, 1.0]. * * Maps directly to TTS's `KEY_PARAM_VOLUME`; 1.0 means full stream * volume, 0.0 is silent (distinct from [isMuted] -- a volume of 0.0 * with mute off still triggers TTS but produces no audible output, * whereas mute suppresses the TTS call entirely). Defaults to 1.0. * Values outside [0.0, 1.0] are clamped on write. */ var volume: Float get() = prefs.getFloat(KEY_VOLUME, DEFAULT_VOLUME) set(value) { prefs.edit().putFloat(KEY_VOLUME, value.coerceIn(0f, 1f)).apply() } companion object { private const val PREFS_NAME = "audio_feedback_prefs" private const val KEY_MUTED = "muted" private const val KEY_VOLUME = "volume" private const val DEFAULT_VOLUME = 1.0f } }