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}