Skip to main content

logicaffeine_web/
audio.rs

1//! Sound effect playback for gamification feedback.
2//!
3//! Provides audio cues for learning events like correct answers, XP gains,
4//! combo achievements, and streak status. Uses JavaScript interop on WASM
5//! targets; no-op on native targets for testing.
6//!
7//! # Usage
8//!
9//! ```no_run
10//! use logicaffeine_web::audio::{SoundEffect, play_sound};
11//!
12//! // Play a sound when the user answers correctly
13//! play_sound(SoundEffect::Correct);
14//!
15//! // Play combo sound for streak multipliers
16//! play_sound(SoundEffect::ComboUp);
17//! ```
18//!
19//! # JavaScript Integration
20//!
21//! On WASM targets, this module expects a global `window.playSound(name)` function
22//! to be defined in the host page. The function receives the sound effect name
23//! as a string (e.g., "correct", "combo_up").
24
25/// Audio cues for gamification events.
26///
27/// Each variant maps to a distinct sound file. The [`SoundEffect::as_str`] method
28/// returns the identifier passed to the JavaScript audio system.
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum SoundEffect {
31    /// Played when the user earns XP from any source.
32    XpGain,
33    /// Played for bonus XP events (perfect answers, fast responses).
34    CriticalHit,
35    /// Played when the combo multiplier increases.
36    ComboUp,
37    /// Played when a combo streak is broken by an incorrect answer.
38    ComboBreak,
39    /// Played when an achievement is unlocked.
40    Achievement,
41    /// Played when the user gains a level.
42    LevelUp,
43    /// Played when a daily streak is preserved (e.g., freeze used).
44    StreakSaved,
45    /// Played when a daily streak is lost.
46    StreakLost,
47    /// Played for correct answers.
48    Correct,
49    /// Played for incorrect answers.
50    Incorrect,
51}
52
53impl SoundEffect {
54    /// Returns the string identifier for this sound effect.
55    ///
56    /// This identifier is passed to the JavaScript `playSound` function
57    /// and should match the audio file naming convention.
58    pub fn as_str(&self) -> &'static str {
59        match self {
60            Self::XpGain => "xp_gain",
61            Self::CriticalHit => "critical",
62            Self::ComboUp => "combo_up",
63            Self::ComboBreak => "combo_break",
64            Self::Achievement => "achievement",
65            Self::LevelUp => "level_up",
66            Self::StreakSaved => "streak_saved",
67            Self::StreakLost => "streak_lost",
68            Self::Correct => "correct",
69            Self::Incorrect => "incorrect",
70        }
71    }
72}
73
74#[cfg(target_arch = "wasm32")]
75mod wasm {
76    use super::SoundEffect;
77    use wasm_bindgen::prelude::*;
78
79    #[wasm_bindgen]
80    extern "C" {
81        #[wasm_bindgen(js_namespace = window, js_name = playSound)]
82        fn play_sound_js(effect: &str);
83    }
84
85    /// Plays the specified sound effect through the browser audio system.
86    pub fn play_sound(effect: SoundEffect) {
87        play_sound_js(effect.as_str());
88    }
89}
90
91#[cfg(target_arch = "wasm32")]
92pub use wasm::play_sound;
93
94/// Plays a sound effect (no-op on non-WASM targets).
95///
96/// On WASM targets, this calls the JavaScript `window.playSound()` function.
97/// On native targets, this is a no-op to allow testing without audio setup.
98#[cfg(not(target_arch = "wasm32"))]
99pub fn play_sound(_effect: SoundEffect) {
100    // No-op on non-wasm targets
101}