rgchart 0.0.13

A library for parsing and writing rhythm game charts.
Documentation
use crate::wasm_bindgen;
use std::collections::HashMap;

#[wasm_bindgen]
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum HitSoundType {
    Normal,
    Clap,
    Whistle,
    Finish,
}

#[wasm_bindgen]
#[derive(Debug, Clone)]
pub struct SoundEffect {
    #[wasm_bindgen(getter_with_clone)]
    pub time: i32,
    #[wasm_bindgen(getter_with_clone)]
    pub volume: u8,
    #[wasm_bindgen(getter_with_clone)]
    pub sample: usize,
}

#[wasm_bindgen]
impl SoundEffect {
    #[inline]
    #[wasm_bindgen(constructor)]
    pub fn new(time: i32, volume: u8, sample: usize) -> Self {
        Self { time, volume, sample }
    }
}

#[wasm_bindgen]
#[derive(Debug, Clone, Copy)]
pub struct KeySound {
    #[wasm_bindgen(getter_with_clone)]
    pub volume: u8,
    #[wasm_bindgen(getter_with_clone)]
    pub hitsound_type: HitSoundType,
    #[wasm_bindgen(getter_with_clone)]
    pub sample: Option<usize>,
    #[wasm_bindgen(getter_with_clone)]
    pub has_custom: bool,
}

impl Default for KeySound {
    fn default() -> Self {
        KeySound::normal(100)
    }
}

impl KeySound {
    pub fn of_type(volume: u8, hitsound_type: HitSoundType) -> Self  {
        Self {
            volume,
            hitsound_type,
            sample: None,
            has_custom: false
        }
    }

    pub fn normal(volume: u8) -> Self {
        Self {
            volume,
            hitsound_type: HitSoundType::Normal,
            sample: None,
            has_custom: false
        }
    }

    pub fn clap(volume: u8) -> Self {
        Self {
            volume,
            hitsound_type: HitSoundType::Clap,
            sample: None,
            has_custom: false
        }
    }

    pub fn whistle(volume: u8) -> Self {
        Self {
            volume,
            hitsound_type: HitSoundType::Whistle,
            sample: None,
            has_custom: false
        }
    }

    pub fn finish(volume: u8) -> Self {
        Self {
            volume,
            hitsound_type: HitSoundType::Finish,
            sample: None,
            has_custom: false
        }
    }

    pub fn with_custom(volume: u8, sample_index: usize, hitsound_type: Option<HitSoundType>) -> Self {
        let hstype = if let Some(hstype) = hitsound_type {
            hstype
        } else {
            HitSoundType::Normal
        };
        Self {
            volume,
            hitsound_type: hstype,
            sample: Some(sample_index),
            has_custom: true
        }
    }
}

#[wasm_bindgen]
#[derive(Debug, Clone)]
pub struct KeySoundRow {
    sounds: Vec<KeySound>,
    pub is_empty: bool,
}

impl KeySoundRow {
    pub fn with_capacity(capacity: usize) -> Self {
        Self {
            sounds: vec![KeySound::normal(100); capacity],
            is_empty: true,
        }
    }

    pub fn empty() -> Self {
        Self {
            sounds: Vec::new(),
            is_empty: true,
        }
    }
    
    pub fn with(sounds: Vec<KeySound>) -> Self {
        Self {
            sounds,
            is_empty: false,
        }
    }

    pub fn with_unwrap(sounds: &[Option<KeySound>]) -> Self {
        Self {
            sounds: sounds
                .iter()
                .map(|s| s.unwrap_or(KeySound::normal(100)))
                .collect(),
            is_empty: false,
        }
    }
    
    pub fn get_sounds(&self) -> &Vec<KeySound> {
        &self.sounds
    }

    pub fn len(&self) -> usize {
        self.sounds.len()
    }
    
    pub fn as_ptr(&mut self) -> *const KeySound {
        self.sounds.as_ptr()
    }

    pub fn as_mut_ptr(&mut self) -> *mut KeySound {
        self.sounds.as_mut_ptr()
    }
}

impl std::ops::Index<usize> for KeySoundRow {
    type Output = KeySound;

    fn index(&self, index: usize) -> &Self::Output {
        &self.sounds[index]
    }
}

impl std::ops::IndexMut<usize> for KeySoundRow {
    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
        &mut self.sounds[index]
    }
}

#[wasm_bindgen]
#[derive(Debug, Clone)]
pub struct SoundBank {
    #[wasm_bindgen(getter_with_clone)]
    pub audio_tracks: Vec<String>,
    sound_sample_paths: Vec<String>,
    #[wasm_bindgen(getter_with_clone)]
    pub sound_effects: Vec<SoundEffect>,
    sample_map: HashMap<String, usize>,
}

impl Default for SoundBank {
    fn default() -> Self {
        SoundBank::new()
    }
}

#[wasm_bindgen]
impl SoundBank {
    #[wasm_bindgen(constructor)]
    pub fn new() -> Self {
        Self {
            audio_tracks: Vec::new(),
            sound_sample_paths: Vec::new(),
            sound_effects: Vec::new(),
            sample_map: HashMap::new(),
        }
    }

    #[wasm_bindgen]
    pub fn add_sound_sample(&mut self, path: String) -> usize {
        if let Some(&index) = self.sample_map.get(&path) {
            index
        } else {
            let index = self.sound_sample_paths.len();
            self.sound_sample_paths.push(path.clone());
            self.sample_map.insert(path, index);
            index
        }
    }

    #[wasm_bindgen]
    pub fn add_sound_sample_with_index(&mut self, index: usize, path: String) {
        if path.is_empty() {
            return;
        }

        if index >= self.sound_sample_paths.len() {
            self.sound_sample_paths.resize(index + 1, String::new());
        }
        
        if !self.sound_sample_paths[index].is_empty() {
            self.sample_map.remove(&self.sound_sample_paths[index]);
        }
        
        self.sound_sample_paths[index] = path.clone();
        self.sample_map.insert(path, index);
    }

    pub fn add_sound_effect(&mut self, sound_effect: SoundEffect) {
        self.sound_effects.push(sound_effect);
    }

    #[wasm_bindgen]
    pub fn get_sound_sample(&self, index: usize) -> Option<String> {
        self.sound_sample_paths.get(index)
            .filter(|s| !s.is_empty())  
            .cloned()
    }

    #[wasm_bindgen]
    pub fn get_index_sample(&self, sample_path: &str) -> Option<usize> {
        self.sample_map.get(sample_path).copied()
    }

    #[wasm_bindgen]
    pub fn get_sample_paths(&self) -> Vec<String> {
        self.sound_sample_paths.clone()
    }

    #[wasm_bindgen]
    pub fn contains_path(&self, path: &str) -> bool {
        self.sample_map.contains_key(path)
    }

    #[wasm_bindgen]
    pub fn sample_count(&self) -> usize {
        self.sound_sample_paths.iter().filter(|s| !s.is_empty()).count()
    }

    #[wasm_bindgen]
    pub fn is_empty(&self) -> bool {
        self.sample_map.is_empty()
    }
}