organicomplex 0.7.0

Interactive complex-valued cellular automaton on 2D and 3D grids in search of that stuff - emergence, open-endedness, organicity etc.
extern crate sdl2;

use sdl2::mixer::{
    self,
    Channel,
    Chunk,
    Music as sdl_Music
};

use crate::base::BASE_DIR;

use super::config::Config;

const FREQUENCY: i32 = 44100;
const FORMAT_DATA: mixer::AudioFormat = mixer::DEFAULT_FORMAT;
const FORMAT_CHANNELS: i32 = 2;
const CHUNKSIZE: i32 = 1024;

const CHANNELS: i32 = 0x10;

pub const MUSICS_DIR: &str = "audio/music";
pub const SOUNDS_DIR: &str = "audio/sound";
pub const SPEECHES_DIR: &str = "audio/speech";

// const MUSIC_FADE_OUT_DELAY: i32 = 1000; // milsec
const MUSIC_FADE_IN_DELAY: i32 = 1000; // milsec

const SPEECH_FADE_OUT_DELAY: i32 = 200; // milsec
const SPEECH_FADE_IN_DELAY: i32 = 200; // milsec

pub struct System {
    mute: bool,
    vol_sound: i32,
    vol_speech: i32
}

pub struct Music {
    sdl_music: sdl_Music<'static>
}

pub struct Sound {
    chunk: Chunk,
    channel: Option<Channel>
}

impl System {
    pub fn init(config: &Config) -> Result<System, String> {
        if !config.mute {
            mixer::open_audio(FREQUENCY, FORMAT_DATA, FORMAT_CHANNELS, CHUNKSIZE)?; // here "channels" are 1 (mono) / 2 (stereo), not channels to play chunks through...
            mixer::allocate_channels(CHANNELS);
            sdl_Music::set_volume(config.vol_music * 128 / 100);
            mixer::reserve_channels(1); // 0-th channel to play speech chunk through
        }

        Ok(System{mute: config.mute, vol_sound: config.vol_sound, vol_speech: config.vol_speech})
    }

    pub fn load_music(&self, filepath: impl ToString) -> Option<Music> {
        if !self.mute {
            let filepath = filepath.to_string();
            match sdl_Music::from_file(format!("{}/{}/{}", BASE_DIR, MUSICS_DIR, filepath)) {
                Ok(sdl_music) => Some(Music{sdl_music}),
                Err(..) => None
            }
        } else {
            None
        }
    }

    pub fn play_music(&self, music: Option<&Music>) -> Result<(), String> {
        if !self.mute {
            match music {
                Some(m) => {
                    // sdl_Music::halt();
                    // sdl_Music::fade_out(MUSIC_FADE_OUT_DELAY)?; // not async, freezes main thread for some time...
                    // std::thread::spawn(|| {let _ = sdl_Music::fade_out(MUSIC_FADE_OUT_DELAY);} ); // silence, "new" music does not start... too asynced?

                    // m.sdl_music.play(-1)?;
                    m.sdl_music.fade_in(-1, MUSIC_FADE_IN_DELAY)?; // lessens (?) "click" between musics

                    Ok(())
                },
                None => Err(String::from("no music to play"))
            }
        } else {
            Ok(())
        }
    }

    fn load_chunk(&self, base_dirpath: impl ToString, filepath: impl ToString, volume: i32) -> Option<Sound> {
        if !self.mute {
            let base_dirpath = base_dirpath.to_string();
            let filepath = filepath.to_string();
            match Chunk::from_file(format!("{}/{}/{}", BASE_DIR, base_dirpath, filepath)) {
                Ok(mut chunk) => {
                    chunk.set_volume(volume * 128 / 100);
                    let channel = None;
                    Some(Sound{chunk, channel})
                },
                Err(..) => None
            }
        } else {
            None
        }
    }

    pub fn load_sound(&self, filepath: impl ToString) -> Option<Sound> {
        self.load_chunk(SOUNDS_DIR, filepath, self.vol_sound)
    }

    pub fn play_sound(&self, sound: Option<&mut Sound>, loops: i32) -> Result<(), String> {
        if !self.mute {
            match sound {
                Some(s) => {
                    s.channel = Some(sdl2::mixer::Channel::all().play(& s.chunk, loops)?);
                    Ok(())
                },
                None => Err(String::from("no sound to play"))
            }
        } else {
            Ok(())
        }
    }

    pub fn pause_sound(&self, sound: Option<&Sound>) -> Result<(), String> {
        if !self.mute {
            match sound {
                Some(s) => {
                    match s.channel {
                        Some(Channel(chan)) => {
                            Channel(chan).pause();
                            Ok(())
                        }
                        None => Err(String::from("sound is not played via any channel to pause"))
                    }
                }
                None => Err(String::from("no sound to pause"))
            }
        } else {
            Ok(())
        }
    }

    pub fn resume_sound(&self, sound: Option<&Sound>) -> Result<(), String> {
        if !self.mute {
            match sound {
                Some(s) => {
                    match s.channel {
                        Some(Channel(chan)) => {
                            Channel(chan).resume();
                            Ok(())
                        }
                        None => Err(String::from("sound is not played via any channel to resume"))
                    }
                }
                None => Err(String::from("no sound to resume"))
            }
        } else {
            Ok(())
        }
    }

    pub fn halt_sound(&self, sound: Option<&mut Sound>) -> Result<(), String> {
        if !self.mute {
            match sound {
                Some(s) => {
                    match s.channel {
                        Some(Channel(chan)) => {
                            Channel(chan).halt();
                            s.channel = None;
                            Ok(())
                        }
                        None => Err(String::from("sound is not played via any channel to halt"))
                    }
                }
                None => Err(String::from("no sound to halt"))
            }
        } else {
            Ok(())
        }
    }

    pub fn load_speech(&self, filepath: impl ToString) -> Option<Sound> {
        self.load_chunk(SPEECHES_DIR, filepath, self.vol_speech)
    }

    pub fn play_speech(&self, speech: Option<&Sound>) -> Result<(), String> {
        if !self.mute {
            let chan = Channel(0); // reserved for speech
            // chan.halt(); // stop already playing speech if any
            // Fade out/in to reduce (?) "click" between speeches
            chan.fade_out(SPEECH_FADE_OUT_DELAY);
            match speech {
                Some(s) => {
                    // chan.play(&s.chunk, 0)?;
                    chan.fade_in(&s.chunk, 0, SPEECH_FADE_IN_DELAY)?;
                    Ok(())
                },
                None => Err(String::from("no speech to play"))
            }
        } else {
            Ok(())
        }
    }

    pub fn pause(&self) {
        if !self.mute {
            sdl_Music::pause();
            Channel::all().pause();
            Channel(0).pause();
        }
    }

    pub fn resume(&self) {
        if !self.mute {
            sdl_Music::resume();
            Channel::all().resume();
            Channel(0).resume();
        }
    }

    pub fn halt(&self) {
        if !self.mute {
            sdl_Music::halt();
            Channel::all().halt();
            Channel(0).halt();
        }
    }

    pub fn shut(&mut self) -> Result<(), String> {
        if !self.mute {
            mixer::close_audio();
        }
        Ok(())
    }
}