<commit_before>use libc::{uint32_t, c_void};
use std::mem;
use sys::timer as ll;

pub fn get_ticks() -> u32 {
    unsafe { ll::SDL_GetTicks() }
}

pub fn get_performance_counter() -> u64 {
    unsafe { ll::SDL_GetPerformanceCounter() }
}

pub fn get_performance_frequency() -> u64 {
    unsafe { ll::SDL_GetPerformanceFrequency() }
}

pub fn delay(ms: u32) {
    unsafe { ll::SDL_Delay(ms) }
}

pub type TimerCallback<'a> = Box<FnMut() -> u32+'a+Sync>;

#[unstable = "Unstable because of move to unboxed closures and `box` syntax"]
pub struct Timer<'a> {
    callback: Option<Box<TimerCallback<'a>>>,
    _delay: u32,
    raw: ll::SDL_TimerID,
}

impl<'a> Timer<'a> {
    /// Constructs a new timer using the boxed closure `callback`.
    /// The timer is started immediately, it will be cancelled either:
    ///   * when the timer is dropped
    ///   * or when the callback returns a non-positive continuation interval
    pub fn new(delay: u32, callback: TimerCallback<'a>) -> Timer<'a> {
        unsafe {
			let callback = Box::new(callback);
            let timer_id = ll::SDL_AddTimer(delay,
                                            Some(c_timer_callback),
                                            mem::transmute_copy(&callback));

            Timer {
                callback: Some(callback),
                _delay: delay,
                raw: timer_id,
            }
        }
    }

	/// Returns the closure as a trait-object and cancels the timer
	/// by consuming it...
	pub fn into_inner(mut self) -> TimerCallback<'a> {
		*self.callback.take().unwrap()
	}
}

#[unsafe_destructor]
impl<'a> Drop for Timer<'a> {
    fn drop(&mut self) {
        let ret = unsafe { ll::SDL_RemoveTimer(self.raw) };
        if ret != 1 {
            println!("error dropping timer {}, maybe already removed.", self.raw);
        }
    }
}

extern "C" fn c_timer_callback(_interval: u32, param: *const c_void) -> uint32_t {
    unsafe {
        let f: *const  Box<Fn() -> u32> = mem::transmute(param);
        (*f)() as uint32_t
    }
}

#[test]
fn test_timer_runs_multiple_times() {
    use std::sync::{Arc, Mutex};

    let local_num = Arc::new(Mutex::new(0));
    let timer_num = local_num.clone();

    let timer = Timer::new(100, Box::new(|| {
        // increment up to 10 times (0 -> 9)
        // tick again in 100ms after each increment
        //
        let mut num = timer_num.lock().unwrap();
        if *num < 9 {
            *num += 1;
            100
        } else { 0 }
    }));

    delay(1200);                         // tick the timer at least 10 times w/ 200ms of "buffer"
    let num = local_num.lock().unwrap(); // read the number back
    assert_eq!(*num, 9);                 // it should have incremented at least 10 times...
}

#[test]
fn test_timer_runs_at_least_once() {
    use std::sync::{Arc, Mutex};

    let local_flag = Arc::new(Mutex::new(false));
    let timer_flag = local_flag.clone();

    let timer = Timer::new(500, Box::new(|| {
        let mut flag = timer_flag.lock().unwrap();
        *flag = true; 0
    }));

    delay(700);
    let flag = local_flag.lock().unwrap();
    assert_eq!(*flag, true);
}

#[test]
fn test_timer_can_be_recreated() {
    use std::sync::{Arc, Mutex};

    let local_num = Arc::new(Mutex::new(0));
    let timer_num = local_num.clone();

    // run the timer once and reclaim its closure
    let mut timer_1 = Timer::new(100, Box::new(move|| {
        let mut num = timer_num.lock().unwrap();
        *num += 1; // increment the number
        0          // do not run timer again
    }));

    // reclaim closure after timer runs
    delay(200);
    let closure = timer_1.into_inner();

    // create a second timer and increment again
    let timer_2 = Timer::new(100, closure);
    delay(200);

    // check that timer was incremented twice
    let num = local_num.lock().unwrap();
    assert_eq!(*num, 2);
}
<commit_msg>Remove spurious failures from timer tests:<commit_after>use libc::{uint32_t, c_void};
use std::mem;
use sys::timer as ll;

pub fn get_ticks() -> u32 {
    unsafe { ll::SDL_GetTicks() }
}

pub fn get_performance_counter() -> u64 {
    unsafe { ll::SDL_GetPerformanceCounter() }
}

pub fn get_performance_frequency() -> u64 {
    unsafe { ll::SDL_GetPerformanceFrequency() }
}

pub fn delay(ms: u32) {
    unsafe { ll::SDL_Delay(ms) }
}

pub type TimerCallback<'a> = Box<FnMut() -> u32+'a+Sync>;

#[unstable = "Unstable because of move to unboxed closures and `box` syntax"]
pub struct Timer<'a> {
    callback: Option<Box<TimerCallback<'a>>>,
    _delay: u32,
    raw: ll::SDL_TimerID,
}

impl<'a> Timer<'a> {
    /// Constructs a new timer using the boxed closure `callback`.
    /// The timer is started immediately, it will be cancelled either:
    ///   * when the timer is dropped
    ///   * or when the callback returns a non-positive continuation interval
    pub fn new(delay: u32, callback: TimerCallback<'a>) -> Timer<'a> {
        unsafe {
			let callback = Box::new(callback);
            let timer_id = ll::SDL_AddTimer(delay,
                                            Some(c_timer_callback),
                                            mem::transmute_copy(&callback));

            Timer {
                callback: Some(callback),
                _delay: delay,
                raw: timer_id,
            }
        }
    }

	/// Returns the closure as a trait-object and cancels the timer
	/// by consuming it...
	pub fn into_inner(mut self) -> TimerCallback<'a> {
		*self.callback.take().unwrap()
	}
}

#[unsafe_destructor]
impl<'a> Drop for Timer<'a> {
    fn drop(&mut self) {
        let ret = unsafe { ll::SDL_RemoveTimer(self.raw) };
        if ret != 1 {
            println!("error dropping timer {}, maybe already removed.", self.raw);
        }
    }
}

extern "C" fn c_timer_callback(_interval: u32, param: *const c_void) -> uint32_t {
    unsafe {
        let f: *const  Box<Fn() -> u32> = mem::transmute(param);
        (*f)() as uint32_t
    }
}

#[cfg(test)] use std::sync::{StaticMutex, MUTEX_INIT};
#[cfg(test)] static TIMER_INIT_LOCK: StaticMutex = MUTEX_INIT;

#[test]
fn test_timer_runs_multiple_times() {
    use std::sync::{Arc, Mutex};
    let running = TIMER_INIT_LOCK.lock().unwrap();
    ::sdl::init(::sdl::INIT_TIMER);

    let local_num = Arc::new(Mutex::new(0));
    let timer_num = local_num.clone();

    let timer = Timer::new(20, Box::new(|| {
        // increment up to 10 times (0 -> 9)
        // tick again in 100ms after each increment
        //
        let mut num = timer_num.lock().unwrap();
        if *num < 9 {
            *num += 1;
            20
        } else { 0 }
    }));

    delay(250);                         // tick the timer at least 10 times w/ 200ms of "buffer"
    let num = local_num.lock().unwrap(); // read the number back
    assert_eq!(*num, 9);                 // it should have incremented at least 10 times...
}

#[test]
fn test_timer_runs_at_least_once() {
    use std::sync::{Arc, Mutex};
    let running = TIMER_INIT_LOCK.lock().unwrap();
    ::sdl::init(::sdl::INIT_TIMER);

    let local_flag = Arc::new(Mutex::new(false));
    let timer_flag = local_flag.clone();

    let timer = Timer::new(20, Box::new(|| {
        let mut flag = timer_flag.lock().unwrap();
        *flag = true; 0
    }));

    delay(50);
    let flag = local_flag.lock().unwrap();
    assert_eq!(*flag, true);
}

#[test]
fn test_timer_can_be_recreated() {
    use std::sync::{Arc, Mutex};
    let running = TIMER_INIT_LOCK.lock().unwrap();
    ::sdl::init(::sdl::INIT_TIMER);

    let local_num = Arc::new(Mutex::new(0));
    let timer_num = local_num.clone();

    // run the timer once and reclaim its closure
    let mut timer_1 = Timer::new(20, Box::new(move|| {
        let mut num = timer_num.lock().unwrap();
        *num += 1; // increment the number
        0          // do not run timer again
    }));

    // reclaim closure after timer runs
    delay(50);
    let closure = timer_1.into_inner();

    // create a second timer and increment again
    let timer_2 = Timer::new(20, closure);
    delay(50);

    // check that timer was incremented twice
    let num = local_num.lock().unwrap();
    assert_eq!(*num, 2);
}
<|endoftext|>


<commit_before>pub mod dir;

use self::dir::FolderData;
use backend::{AnimeEntry, AnimeInfo, Status, SyncBackend};
use chrono::Local;
use error::SeriesError;
use input::{self, Answer};
use process;
use std::borrow::Cow;

pub struct SeriesConfig<B>
where
    B: SyncBackend,
{
    pub offline_mode: bool,
    pub sync_service: B,
    pub season_num: usize,
}

#[derive(Copy, Clone)]
pub enum Prompt {
    EpisodeCompleted,
    UpdateScore,
    SeriesCompleted,
    AlreadyCompleted,
    BeginRewatch,
    ResumePausedSeries,
    PauseSeries(Status),
}

pub struct Series<B>
where
    B: SyncBackend,
{
    config: SeriesConfig<B>,
    dir: FolderData,
    season: SeasonState,
    ep_offset: u32,
}

impl<B> Series<B>
where
    B: SyncBackend,
{
    pub fn init(config: SeriesConfig<B>, mut dir: FolderData) -> Result<Series<B>, SeriesError> {
        dir.populate_season_data(&config)?;
        let ep_offset = dir.calculate_season_offset(0..config.season_num);
        let season = dir.seasons()[config.season_num].clone();

        let series = Series {
            config,
            dir,
            season,
            ep_offset,
        };

        Ok(series)
    }

    pub fn sync_remote_states(&mut self) -> Result<(), SeriesError> {
        self.season
            .sync_data_from_remote(&self.config, &self.dir, self.config.season_num)?;

        self.save_updated_season_data()
    }

    pub fn prepare_initial_state(&mut self) -> Result<(), SeriesError> {
        match self.season.state.status {
            Status::Watching | Status::Rewatching => {
                // Handle potential edge-case where all episodes have already been watched
                // but the series is still set in a watching state
                if !self.has_unwatched_episodes() {
                    self.prompt(Prompt::SeriesCompleted)?;
                }

                Ok(())
            }
            Status::PlanToWatch => self.set_list_entry_status(Status::Watching),
            Status::Completed => self.prompt(Prompt::AlreadyCompleted),
            Status::OnHold | Status::Dropped => self.prompt(Prompt::ResumePausedSeries),
        }
    }

    pub fn prompt(&mut self, prompt: Prompt) -> Result<(), SeriesError> {
        let state = &mut self.season.state;

        match prompt {
            Prompt::EpisodeCompleted => {
                let total_episodes = state
                    .info
                    .episodes
                    .map(|e| Cow::Owned(e.to_string()))
                    .unwrap_or_else(|| Cow::Borrowed("?"));

                println!(
                    "[{}] episode {}/{} completed",
                    state.info.title, state.watched_episodes, total_episodes
                );

                self.update_list_entry()?;
                self.prompt_next_episode_options()
            }
            Prompt::UpdateScore => {
                let (min_score, max_score) = self.config.sync_service.formatted_score_range();

                println!(
                    "enter your score between {} and {} (press return to skip):",
                    min_score, max_score
                );

                // Read & parse score input until we get a valid one
                loop {
                    let input = match input::read_line() {
                        Ok(ref input) if input.is_empty() => return Ok(()),
                        Ok(input) => input,
                        Err(err) => {
                            eprintln!("failed to read score input: {}", err);
                            return Ok(());
                        }
                    };

                    match self.config.sync_service.parse_score(&input) {
                        Ok(score) => {
                            state.score = Some(score);
                            break;
                        }
                        Err(err) => eprintln!("error: {}", err),
                    }
                }

                self.update_list_entry()
            }
            Prompt::SeriesCompleted => {
                println!("[{}] completed!", state.info.title);
                self.set_list_entry_status(Status::Completed)?;
                self.prompt_series_completed_options()
            }
            Prompt::AlreadyCompleted => {
                println!("[{}] already completed", state.info.title);
                self.prompt_series_completed_options()
            }
            Prompt::BeginRewatch => {
                println!("[{}] starting rewatch", state.info.title);
                println!("do you want to reset the start and end dates? (Y/n)");

                if input::read_yn(Answer::Yes)? {
                    state.start_date = None;
                    state.finish_date = None;
                }

                self.set_list_entry_status(Status::Rewatching)
            }
            Prompt::ResumePausedSeries => {
                println!(
                    "[{}] was previously put on hold or dropped",
                    state.info.title
                );

                println!("do you want to watch it from the beginning? (Y/n)");

                if input::read_yn(Answer::Yes)? {
                    state.watched_episodes = 0;
                }

                self.set_list_entry_status(Status::Watching)
            }
            Prompt::PauseSeries(status) => {
                self.set_list_entry_status(status)?;

                println!("do you want to remove the episodes on disk? (Y/n)");

                if input::read_yn(Answer::Yes)? {
                    self.dir.try_remove_dir();
                }

                Ok(())
            }
        }
    }

    pub fn prompt_next_episode_options(&mut self) -> Result<(), SeriesError> {
        let current_score_text: Cow<str> = match self.format_entry_score() {
            Some(score) => Cow::Owned(format!(" [{}]", score)),
            None => Cow::Borrowed(""),
        };

        println!("series options:");
        println!("\t[d] drop\n\t[h] put on hold\n\t[r] rate{}\n\t[x] exit\n\t[n] watch next episode (default)", current_score_text);

        let input = input::read_line()?.to_lowercase();

        match input.as_str() {
            "d" | "h" => {
                let status = if input == "d" {
                    Status::Dropped
                } else {
                    Status::OnHold
                };

                self.prompt(Prompt::PauseSeries(status))?;
                Err(SeriesError::RequestExit)
            }
            "r" => {
                self.prompt(Prompt::UpdateScore)?;
                self.prompt_next_episode_options()
            }
            "x" => Err(SeriesError::RequestExit),
            _ => Ok(()),
        }
    }

    pub fn prompt_series_completed_options(&mut self) -> Result<(), SeriesError> {
        let current_score_text: Cow<str> = match self.format_entry_score() {
            Some(score) => Cow::Owned(format!(" [{}]", score)),
            None => Cow::Borrowed(""),
        };

        println!("series options:");
        println!(
            "\t[r] rate{}\n\t[w] rewatch\n\t[d] delete local files\n\t[x] exit",
            current_score_text
        );

        let input = input::read_line()?.to_lowercase();

        match input.as_str() {
            "r" => {
                self.prompt(Prompt::UpdateScore)?;
                self.prompt_series_completed_options()
            }
            "w" => {
                self.prompt(Prompt::BeginRewatch)?;
                self.play_all_episodes()
            }
            "d" => {
                self.dir.try_remove_dir();
                Err(SeriesError::RequestExit)
            }
            "x" => Err(SeriesError::RequestExit),
            _ => Ok(()),
        }
    }

    pub fn prompt_series_options(&mut self) -> Result<(), SeriesError> {
        let current_score_text: Cow<str> = match self.format_entry_score() {
            Some(score) => Cow::Owned(format!(" [{}]", score)),
            None => Cow::Borrowed(""),
        };

        println!("[{}] series options:", self.season.state.info.title);
        println!(
            "\t[r] rate{}\n\t[dd] delete local files\n\t[d] drop\n\t[h] put on hold\n\t[x] exit",
            current_score_text
        );

        let input = input::read_line()?.to_lowercase();

        match input.as_str() {
            "r" => {
                self.prompt(Prompt::UpdateScore)?;
                self.prompt_series_options()
            }
            "dd" => {
                self.dir.try_remove_dir();
                Err(SeriesError::RequestExit)
            }
            "d" | "h" => {
                let status = if input == "d" {
                    Status::Dropped
                } else {
                    Status::OnHold
                };

                self.prompt(Prompt::PauseSeries(status))?;
                Err(SeriesError::RequestExit)
            }
            "x" => Err(SeriesError::RequestExit),
            _ => self.prompt_series_options(),
        }
    }

    fn format_entry_score(&self) -> Option<String> {
        let state = &self.season.state;

        match state.score {
            Some(score) => {
                let formatted_score = self.config.sync_service.format_score(score);

                match formatted_score {
                    Ok(score) => Some(score),
                    Err(err) => {
                        eprintln!("failed to read existing list entry score: {}", err);
                        None
                    }
                }
            }
            None => None,
        }
    }

    fn set_list_entry_status(&mut self, status: Status) -> Result<(), SeriesError> {
        let state = &mut self.season.state;

        match status {
            Status::Watching => {
                // A series that was on hold probably already has a starting date, and it would make
                // more sense to use that one instead of replacing it
                if state.status != Status::OnHold {
                    state.start_date = Some(Local::today().naive_local());
                }

                state.finish_date = None;
            }
            Status::Rewatching => {
                if state.start_date.is_none() {
                    state.start_date = Some(Local::today().naive_local());
                    state.finish_date = None;
                }

                state.watched_episodes = 0;
            }
            Status::Completed => {
                if state.finish_date.is_none() {
                    state.finish_date = Some(Local::today().naive_local());
                }
            }
            Status::Dropped => {
                if state.finish_date.is_none() {
                    state.finish_date = Some(Local::today().naive_local());
                }
            }
            Status::OnHold | Status::PlanToWatch => (),
        }

        state.status = status;
        self.update_list_entry()
    }

    pub fn update_list_entry(&mut self) -> Result<(), SeriesError> {
        self.season.needs_sync = self.config.offline_mode;
        self.save_updated_season_data()?;

        if self.config.offline_mode {
            return Ok(());
        }

        self.config
            .sync_service
            .update_list_entry(&self.season.state)?;

        Ok(())
    }

    pub fn play_episode(&mut self, episode: u32) -> Result<(), SeriesError> {
        let absolute_ep = self.ep_offset + episode;
        let path = self.dir.get_episode(absolute_ep)?.clone();

        let status = process::open_with_default(path).map_err(SeriesError::FailedToOpenPlayer)?;

        if !status.success() {
            eprintln!("video player not exited normally");
            eprintln!("do you still want to count the episode as completed? (y/N)");

            if !input::read_yn(Answer::No)? {
                return Ok(());
            }
        }

        let state = &mut self.season.state;
        state.watched_episodes = episode.max(state.watched_episodes);

        Ok(())
    }

    pub fn play_all_episodes(&mut self) -> Result<(), SeriesError> {
        loop {
            let next_episode = self.season.state.watched_episodes + 1;
            self.play_episode(next_episode)?;

            if self.has_unwatched_episodes() {
                self.prompt(Prompt::EpisodeCompleted)?;
            } else {
                self.prompt(Prompt::SeriesCompleted)?;
                break;
            }
        }

        Ok(())
    }

    pub fn has_unwatched_episodes(&self) -> bool {
        let state = &self.season.state;

        match state.info.episodes {
            Some(total_eps) if total_eps > state.watched_episodes => true,
            _ => false,
        }
    }

    fn save_updated_season_data(&mut self) -> Result<(), SeriesError> {
        if self.config.season_num >= self.dir.seasons().len() {
            return Ok(());
        }

        self.dir.seasons_mut()[self.config.season_num] = self.season.clone();
        self.dir.save()
    }
}

pub fn search_for_series_info<B>(
    backend: &B,
    name: &str,
    season: usize,
) -> Result<AnimeInfo, SeriesError>
where
    B: SyncBackend,
{
    println!("[{}] searching on {}..", name, B::name());

    let mut found = backend.search_by_name(name)?;

    println!(
        "select season {} by entering the number next to its name:\n",
        1 + season
    );

    println!("0 [manual search]");

    for (i, series) in found.iter().enumerate() {
        println!("{} [{}]", 1 + i, series.title);
    }

    let index = input::read_range(0, found.len())?;

    match index {
        0 => {
            println!("enter the name you want to search for:");
            let name = input::read_line()?;

            search_for_series_info(backend, &name, season)
        }
        _ => Ok(found.swap_remove(index - 1)),
    }
}

#[derive(Clone, Serialize, Deserialize)]
pub struct SeasonState {
    #[serde(flatten)]
    pub state: AnimeEntry,
    pub needs_info: bool,
    pub needs_sync: bool,
}

impl SeasonState {
    pub fn sync_info_from_remote<B>(
        &mut self,
        config: &SeriesConfig<B>,
        dir: &FolderData,
        season_num: usize,
    ) -> Result<(), SeriesError>
    where
        B: SyncBackend,
    {
        if !self.needs_info {
            return Ok(());
        }

        // When offline, use data from the files on disk
        if config.offline_mode {
            // We want to use the highest episode number we have on disk to represent the
            // total number of episodes for a series. While it's possible that only part of the
            // series is downloaded, it is much more likely that the entire series is there if
            // the user is using offline mode. This will also allow the user to never have to use
            // online mode to get the real series information first.
            let num_eps = dir.series_info.episodes.keys().max();

            self.state.info.title = dir.series_info.name.clone();
            self.state.info.episodes = num_eps.cloned();

            return Ok(());
        }

        let info = search_for_series_info(&config.sync_service, &dir.series_info.name, season_num)?;

        self.state.info = info;
        // We only want to set this flag when online, since offline mode only provides
        // very basic information at best
        self.needs_info = false;
        Ok(())
    }

    pub fn sync_data_from_remote<B>(
        &mut self,
        config: &SeriesConfig<B>,
        dir: &FolderData,
        season_num: usize,
    ) -> Result<(), SeriesError>
    where
        B: SyncBackend,
    {
        self.sync_info_from_remote(config, dir, season_num)?;

        // There's no information we can sync when offline
        if config.offline_mode {
            return Ok(());
        }

        let entry = config
            .sync_service
            .get_list_entry(self.state.info.clone())?;

        if let Some(entry) = entry {
            // If we don't have new data to report, we should sync the data from the backend to keep up with
            // any changes made outside of the program
            if !self.needs_sync {
                self.state = entry;
            }
        }

        Ok(())
    }
}
<commit_msg>[series] Remove unnecessary clone<commit_after>pub mod dir;

use self::dir::FolderData;
use backend::{AnimeEntry, AnimeInfo, Status, SyncBackend};
use chrono::Local;
use error::SeriesError;
use input::{self, Answer};
use process;
use std::borrow::Cow;

pub struct SeriesConfig<B>
where
    B: SyncBackend,
{
    pub offline_mode: bool,
    pub sync_service: B,
    pub season_num: usize,
}

#[derive(Copy, Clone)]
pub enum Prompt {
    EpisodeCompleted,
    UpdateScore,
    SeriesCompleted,
    AlreadyCompleted,
    BeginRewatch,
    ResumePausedSeries,
    PauseSeries(Status),
}

pub struct Series<B>
where
    B: SyncBackend,
{
    config: SeriesConfig<B>,
    dir: FolderData,
    season: SeasonState,
    ep_offset: u32,
}

impl<B> Series<B>
where
    B: SyncBackend,
{
    pub fn init(config: SeriesConfig<B>, mut dir: FolderData) -> Result<Series<B>, SeriesError> {
        dir.populate_season_data(&config)?;
        let ep_offset = dir.calculate_season_offset(0..config.season_num);
        let season = dir.seasons()[config.season_num].clone();

        let series = Series {
            config,
            dir,
            season,
            ep_offset,
        };

        Ok(series)
    }

    pub fn sync_remote_states(&mut self) -> Result<(), SeriesError> {
        self.season
            .sync_data_from_remote(&self.config, &self.dir, self.config.season_num)?;

        self.save_updated_season_data()
    }

    pub fn prepare_initial_state(&mut self) -> Result<(), SeriesError> {
        match self.season.state.status {
            Status::Watching | Status::Rewatching => {
                // Handle potential edge-case where all episodes have already been watched
                // but the series is still set in a watching state
                if !self.has_unwatched_episodes() {
                    self.prompt(Prompt::SeriesCompleted)?;
                }

                Ok(())
            }
            Status::PlanToWatch => self.set_list_entry_status(Status::Watching),
            Status::Completed => self.prompt(Prompt::AlreadyCompleted),
            Status::OnHold | Status::Dropped => self.prompt(Prompt::ResumePausedSeries),
        }
    }

    pub fn prompt(&mut self, prompt: Prompt) -> Result<(), SeriesError> {
        let state = &mut self.season.state;

        match prompt {
            Prompt::EpisodeCompleted => {
                let total_episodes = state
                    .info
                    .episodes
                    .map(|e| Cow::Owned(e.to_string()))
                    .unwrap_or_else(|| Cow::Borrowed("?"));

                println!(
                    "[{}] episode {}/{} completed",
                    state.info.title, state.watched_episodes, total_episodes
                );

                self.update_list_entry()?;
                self.prompt_next_episode_options()
            }
            Prompt::UpdateScore => {
                let (min_score, max_score) = self.config.sync_service.formatted_score_range();

                println!(
                    "enter your score between {} and {} (press return to skip):",
                    min_score, max_score
                );

                // Read & parse score input until we get a valid one
                loop {
                    let input = match input::read_line() {
                        Ok(ref input) if input.is_empty() => return Ok(()),
                        Ok(input) => input,
                        Err(err) => {
                            eprintln!("failed to read score input: {}", err);
                            return Ok(());
                        }
                    };

                    match self.config.sync_service.parse_score(&input) {
                        Ok(score) => {
                            state.score = Some(score);
                            break;
                        }
                        Err(err) => eprintln!("error: {}", err),
                    }
                }

                self.update_list_entry()
            }
            Prompt::SeriesCompleted => {
                println!("[{}] completed!", state.info.title);
                self.set_list_entry_status(Status::Completed)?;
                self.prompt_series_completed_options()
            }
            Prompt::AlreadyCompleted => {
                println!("[{}] already completed", state.info.title);
                self.prompt_series_completed_options()
            }
            Prompt::BeginRewatch => {
                println!("[{}] starting rewatch", state.info.title);
                println!("do you want to reset the start and end dates? (Y/n)");

                if input::read_yn(Answer::Yes)? {
                    state.start_date = None;
                    state.finish_date = None;
                }

                self.set_list_entry_status(Status::Rewatching)
            }
            Prompt::ResumePausedSeries => {
                println!(
                    "[{}] was previously put on hold or dropped",
                    state.info.title
                );

                println!("do you want to watch it from the beginning? (Y/n)");

                if input::read_yn(Answer::Yes)? {
                    state.watched_episodes = 0;
                }

                self.set_list_entry_status(Status::Watching)
            }
            Prompt::PauseSeries(status) => {
                self.set_list_entry_status(status)?;

                println!("do you want to remove the episodes on disk? (Y/n)");

                if input::read_yn(Answer::Yes)? {
                    self.dir.try_remove_dir();
                }

                Ok(())
            }
        }
    }

    pub fn prompt_next_episode_options(&mut self) -> Result<(), SeriesError> {
        let current_score_text: Cow<str> = match self.format_entry_score() {
            Some(score) => Cow::Owned(format!(" [{}]", score)),
            None => Cow::Borrowed(""),
        };

        println!("series options:");
        println!("\t[d] drop\n\t[h] put on hold\n\t[r] rate{}\n\t[x] exit\n\t[n] watch next episode (default)", current_score_text);

        let input = input::read_line()?.to_lowercase();

        match input.as_str() {
            "d" | "h" => {
                let status = if input == "d" {
                    Status::Dropped
                } else {
                    Status::OnHold
                };

                self.prompt(Prompt::PauseSeries(status))?;
                Err(SeriesError::RequestExit)
            }
            "r" => {
                self.prompt(Prompt::UpdateScore)?;
                self.prompt_next_episode_options()
            }
            "x" => Err(SeriesError::RequestExit),
            _ => Ok(()),
        }
    }

    pub fn prompt_series_completed_options(&mut self) -> Result<(), SeriesError> {
        let current_score_text: Cow<str> = match self.format_entry_score() {
            Some(score) => Cow::Owned(format!(" [{}]", score)),
            None => Cow::Borrowed(""),
        };

        println!("series options:");
        println!(
            "\t[r] rate{}\n\t[w] rewatch\n\t[d] delete local files\n\t[x] exit",
            current_score_text
        );

        let input = input::read_line()?.to_lowercase();

        match input.as_str() {
            "r" => {
                self.prompt(Prompt::UpdateScore)?;
                self.prompt_series_completed_options()
            }
            "w" => {
                self.prompt(Prompt::BeginRewatch)?;
                self.play_all_episodes()
            }
            "d" => {
                self.dir.try_remove_dir();
                Err(SeriesError::RequestExit)
            }
            "x" => Err(SeriesError::RequestExit),
            _ => Ok(()),
        }
    }

    pub fn prompt_series_options(&mut self) -> Result<(), SeriesError> {
        let current_score_text: Cow<str> = match self.format_entry_score() {
            Some(score) => Cow::Owned(format!(" [{}]", score)),
            None => Cow::Borrowed(""),
        };

        println!("[{}] series options:", self.season.state.info.title);
        println!(
            "\t[r] rate{}\n\t[dd] delete local files\n\t[d] drop\n\t[h] put on hold\n\t[x] exit",
            current_score_text
        );

        let input = input::read_line()?.to_lowercase();

        match input.as_str() {
            "r" => {
                self.prompt(Prompt::UpdateScore)?;
                self.prompt_series_options()
            }
            "dd" => {
                self.dir.try_remove_dir();
                Err(SeriesError::RequestExit)
            }
            "d" | "h" => {
                let status = if input == "d" {
                    Status::Dropped
                } else {
                    Status::OnHold
                };

                self.prompt(Prompt::PauseSeries(status))?;
                Err(SeriesError::RequestExit)
            }
            "x" => Err(SeriesError::RequestExit),
            _ => self.prompt_series_options(),
        }
    }

    fn format_entry_score(&self) -> Option<String> {
        let state = &self.season.state;

        match state.score {
            Some(score) => {
                let formatted_score = self.config.sync_service.format_score(score);

                match formatted_score {
                    Ok(score) => Some(score),
                    Err(err) => {
                        eprintln!("failed to read existing list entry score: {}", err);
                        None
                    }
                }
            }
            None => None,
        }
    }

    fn set_list_entry_status(&mut self, status: Status) -> Result<(), SeriesError> {
        let state = &mut self.season.state;

        match status {
            Status::Watching => {
                // A series that was on hold probably already has a starting date, and it would make
                // more sense to use that one instead of replacing it
                if state.status != Status::OnHold {
                    state.start_date = Some(Local::today().naive_local());
                }

                state.finish_date = None;
            }
            Status::Rewatching => {
                if state.start_date.is_none() {
                    state.start_date = Some(Local::today().naive_local());
                    state.finish_date = None;
                }

                state.watched_episodes = 0;
            }
            Status::Completed => {
                if state.finish_date.is_none() {
                    state.finish_date = Some(Local::today().naive_local());
                }
            }
            Status::Dropped => {
                if state.finish_date.is_none() {
                    state.finish_date = Some(Local::today().naive_local());
                }
            }
            Status::OnHold | Status::PlanToWatch => (),
        }

        state.status = status;
        self.update_list_entry()
    }

    pub fn update_list_entry(&mut self) -> Result<(), SeriesError> {
        self.season.needs_sync = self.config.offline_mode;
        self.save_updated_season_data()?;

        if self.config.offline_mode {
            return Ok(());
        }

        self.config
            .sync_service
            .update_list_entry(&self.season.state)?;

        Ok(())
    }

    pub fn play_episode(&mut self, episode: u32) -> Result<(), SeriesError> {
        let absolute_ep = self.ep_offset + episode;
        let path = self.dir.get_episode(absolute_ep)?;

        let status = process::open_with_default(path).map_err(SeriesError::FailedToOpenPlayer)?;

        if !status.success() {
            eprintln!("video player not exited normally");
            eprintln!("do you still want to count the episode as completed? (y/N)");

            if !input::read_yn(Answer::No)? {
                return Ok(());
            }
        }

        let state = &mut self.season.state;
        state.watched_episodes = episode.max(state.watched_episodes);

        Ok(())
    }

    pub fn play_all_episodes(&mut self) -> Result<(), SeriesError> {
        loop {
            let next_episode = self.season.state.watched_episodes + 1;
            self.play_episode(next_episode)?;

            if self.has_unwatched_episodes() {
                self.prompt(Prompt::EpisodeCompleted)?;
            } else {
                self.prompt(Prompt::SeriesCompleted)?;
                break;
            }
        }

        Ok(())
    }

    pub fn has_unwatched_episodes(&self) -> bool {
        let state = &self.season.state;

        match state.info.episodes {
            Some(total_eps) if total_eps > state.watched_episodes => true,
            _ => false,
        }
    }

    fn save_updated_season_data(&mut self) -> Result<(), SeriesError> {
        if self.config.season_num >= self.dir.seasons().len() {
            return Ok(());
        }

        self.dir.seasons_mut()[self.config.season_num] = self.season.clone();
        self.dir.save()
    }
}

pub fn search_for_series_info<B>(
    backend: &B,
    name: &str,
    season: usize,
) -> Result<AnimeInfo, SeriesError>
where
    B: SyncBackend,
{
    println!("[{}] searching on {}..", name, B::name());

    let mut found = backend.search_by_name(name)?;

    println!(
        "select season {} by entering the number next to its name:\n",
        1 + season
    );

    println!("0 [manual search]");

    for (i, series) in found.iter().enumerate() {
        println!("{} [{}]", 1 + i, series.title);
    }

    let index = input::read_range(0, found.len())?;

    match index {
        0 => {
            println!("enter the name you want to search for:");
            let name = input::read_line()?;

            search_for_series_info(backend, &name, season)
        }
        _ => Ok(found.swap_remove(index - 1)),
    }
}

#[derive(Clone, Serialize, Deserialize)]
pub struct SeasonState {
    #[serde(flatten)]
    pub state: AnimeEntry,
    pub needs_info: bool,
    pub needs_sync: bool,
}

impl SeasonState {
    pub fn sync_info_from_remote<B>(
        &mut self,
        config: &SeriesConfig<B>,
        dir: &FolderData,
        season_num: usize,
    ) -> Result<(), SeriesError>
    where
        B: SyncBackend,
    {
        if !self.needs_info {
            return Ok(());
        }

        // When offline, use data from the files on disk
        if config.offline_mode {
            // We want to use the highest episode number we have on disk to represent the
            // total number of episodes for a series. While it's possible that only part of the
            // series is downloaded, it is much more likely that the entire series is there if
            // the user is using offline mode. This will also allow the user to never have to use
            // online mode to get the real series information first.
            let num_eps = dir.series_info.episodes.keys().max();

            self.state.info.title = dir.series_info.name.clone();
            self.state.info.episodes = num_eps.cloned();

            return Ok(());
        }

        let info = search_for_series_info(&config.sync_service, &dir.series_info.name, season_num)?;

        self.state.info = info;
        // We only want to set this flag when online, since offline mode only provides
        // very basic information at best
        self.needs_info = false;
        Ok(())
    }

    pub fn sync_data_from_remote<B>(
        &mut self,
        config: &SeriesConfig<B>,
        dir: &FolderData,
        season_num: usize,
    ) -> Result<(), SeriesError>
    where
        B: SyncBackend,
    {
        self.sync_info_from_remote(config, dir, season_num)?;

        // There's no information we can sync when offline
        if config.offline_mode {
            return Ok(());
        }

        let entry = config
            .sync_service
            .get_list_entry(self.state.info.clone())?;

        if let Some(entry) = entry {
            // If we don't have new data to report, we should sync the data from the backend to keep up with
            // any changes made outside of the program
            if !self.needs_sync {
                self.state = entry;
            }
        }

        Ok(())
    }
}
<|endoftext|>


<commit_before>use super::*;

pub mod impls;

#[derive(Debug, PartialEq)]
pub enum DecodeError {
    /// The bytes supplied were too short to be decoded into the specified type.
    InvalidByteLength { len: usize, expected: usize },
    /// The given bytes were too short to be read as a length prefix.
    InvalidLengthPrefix { len: usize, expected: usize },
    /// A length offset pointed to a byte that was out-of-bounds (OOB).
    ///
    /// A bytes may be OOB for the following reasons:
    ///
    /// - It is `>= bytes.len()`.
    /// - When decoding variable length items, the 1st offset points "backwards" into the fixed
    /// length items (i.e., `length[0] < BYTES_PER_LENGTH_OFFSET`).
    /// - When decoding variable-length items, the `n`'th offset was less than the `n-1`'th offset.
    OutOfBoundsByte { i: usize },
    /// The given bytes were invalid for some application-level reason.
    BytesInvalid(String),
}

pub trait Decodable: Sized {
    fn is_ssz_fixed_len() -> bool;

    /// The number of bytes this object occupies in the fixed-length portion of the SSZ bytes.
    ///
    /// By default, this is set to `BYTES_PER_LENGTH_OFFSET` which is suitable for variable length
    /// objects, but not fixed-length objects. Fixed-length objects _must_ return a value which
    /// represents their length.
    fn ssz_fixed_len() -> usize {
        BYTES_PER_LENGTH_OFFSET
    }

    fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, DecodeError>;
}

#[derive(Copy, Clone, Debug)]
pub struct Offset {
    position: usize,
    offset: usize,
}

pub struct SszDecoderBuilder<'a> {
    bytes: &'a [u8],
    items: Vec<&'a [u8]>,
    offsets: Vec<Offset>,
    items_index: usize,
}

impl<'a> SszDecoderBuilder<'a> {
    pub fn new(bytes: &'a [u8]) -> Self {
        Self {
            bytes,
            items: vec![],
            offsets: vec![],
            items_index: 0,
        }
    }

    pub fn register_type<T: Decodable>(&mut self) -> Result<(), DecodeError> {
        if T::is_ssz_fixed_len() {
            let start = self.items_index;
            self.items_index += T::ssz_fixed_len();

            let slice = self.bytes.get(start..self.items_index).ok_or_else(|| {
                DecodeError::InvalidByteLength {
                    len: self.bytes.len(),
                    expected: self.items_index,
                }
            })?;

            self.items.push(slice);
        } else {
            let offset = read_offset(&self.bytes[self.items_index..])?;

            let previous_offset = self
                .offsets
                .last()
                .and_then(|o| Some(o.offset))
                .unwrap_or_else(|| BYTES_PER_LENGTH_OFFSET);

            if previous_offset > offset {
                return Err(DecodeError::OutOfBoundsByte { i: offset });
            } else if offset > self.bytes.len() {
                return Err(DecodeError::OutOfBoundsByte { i: offset });
            }

            self.offsets.push(Offset {
                position: self.items.len(),
                offset,
            });

            // Push an empty slice into items; it will be replaced later.
            self.items.push(&[]);

            self.items_index += BYTES_PER_LENGTH_OFFSET;
        }

        Ok(())
    }

    fn apply_offsets(&mut self) -> Result<(), DecodeError> {
        dbg!(&self.offsets);
        dbg!(&self.items);
        if !self.offsets.is_empty() {

            let mut running_offset = self.offsets[0].offset;

            if running_offset != self.items_index {
                return Err(DecodeError::OutOfBoundsByte { i: running_offset })
            }

            for i in 1..=self.offsets.len() {
                let slice_option = if i == self.offsets.len() {
                    self.bytes.get(running_offset..)
                } else {
                    let offset = self.offsets[i];
                    let start = running_offset;
                    running_offset = offset.offset;

                    self.bytes.get(start..running_offset)
                };

                let slice = slice_option
                    .ok_or_else(|| DecodeError::OutOfBoundsByte { i: running_offset })?;

                self.items[self.offsets[i - 1].position] = slice;
            }
        }

        Ok(())
    }

    pub fn build(mut self) -> Result<SszDecoder<'a>, DecodeError> {
        self.apply_offsets()?;

        Ok(SszDecoder { items: self.items })
    }
}

pub struct SszDecoder<'a> {
    items: Vec<&'a [u8]>,
}

impl<'a> SszDecoder<'a> {
    /// Decodes the next item.
    ///
    /// # Panics
    ///
    /// Panics when attempting to decode more items than actually exist.
    pub fn decode_next<T: Decodable>(&mut self) -> Result<T, DecodeError> {
        T::from_ssz_bytes(self.items.remove(0))
    }
}

/// Reads a `BYTES_PER_LENGTH_OFFSET`-byte length from `bytes`, where `bytes.len() >=
/// BYTES_PER_LENGTH_OFFSET`.
fn read_offset(bytes: &[u8]) -> Result<usize, DecodeError> {
    decode_offset(bytes.get(0..BYTES_PER_LENGTH_OFFSET).ok_or_else(|| {
        DecodeError::InvalidLengthPrefix {
            len: bytes.len(),
            expected: BYTES_PER_LENGTH_OFFSET,
        }
    })?)
}

/// Decode bytes as a little-endian usize, returning an `Err` if `bytes.len() !=
/// BYTES_PER_LENGTH_OFFSET`.
fn decode_offset(bytes: &[u8]) -> Result<usize, DecodeError> {
    let len = bytes.len();
    let expected = BYTES_PER_LENGTH_OFFSET;

    if len != expected {
        Err(DecodeError::InvalidLengthPrefix { len, expected })
    } else {
        let mut array: [u8; BYTES_PER_LENGTH_OFFSET] = std::default::Default::default();
        array.clone_from_slice(bytes);

        Ok(u32::from_le_bytes(array) as usize)
    }
}
<commit_msg>Tidy ssz decoding code<commit_after>use super::*;

pub mod impls;

#[derive(Debug, PartialEq)]
pub enum DecodeError {
    /// The bytes supplied were too short to be decoded into the specified type.
    InvalidByteLength { len: usize, expected: usize },
    /// The given bytes were too short to be read as a length prefix.
    InvalidLengthPrefix { len: usize, expected: usize },
    /// A length offset pointed to a byte that was out-of-bounds (OOB).
    ///
    /// A bytes may be OOB for the following reasons:
    ///
    /// - It is `>= bytes.len()`.
    /// - When decoding variable length items, the 1st offset points "backwards" into the fixed
    /// length items (i.e., `length[0] < BYTES_PER_LENGTH_OFFSET`).
    /// - When decoding variable-length items, the `n`'th offset was less than the `n-1`'th offset.
    OutOfBoundsByte { i: usize },
    /// The given bytes were invalid for some application-level reason.
    BytesInvalid(String),
}

pub trait Decodable: Sized {
    fn is_ssz_fixed_len() -> bool;

    /// The number of bytes this object occupies in the fixed-length portion of the SSZ bytes.
    ///
    /// By default, this is set to `BYTES_PER_LENGTH_OFFSET` which is suitable for variable length
    /// objects, but not fixed-length objects. Fixed-length objects _must_ return a value which
    /// represents their length.
    fn ssz_fixed_len() -> usize {
        BYTES_PER_LENGTH_OFFSET
    }

    fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, DecodeError>;
}

#[derive(Copy, Clone, Debug)]
pub struct Offset {
    position: usize,
    offset: usize,
}

pub struct SszDecoderBuilder<'a> {
    bytes: &'a [u8],
    items: Vec<&'a [u8]>,
    offsets: Vec<Offset>,
    items_index: usize,
}

impl<'a> SszDecoderBuilder<'a> {
    pub fn new(bytes: &'a [u8]) -> Self {
        Self {
            bytes,
            items: vec![],
            offsets: vec![],
            items_index: 0,
        }
    }

    pub fn register_type<T: Decodable>(&mut self) -> Result<(), DecodeError> {
        if T::is_ssz_fixed_len() {
            let start = self.items_index;
            self.items_index += T::ssz_fixed_len();

            let slice = self.bytes.get(start..self.items_index).ok_or_else(|| {
                DecodeError::InvalidByteLength {
                    len: self.bytes.len(),
                    expected: self.items_index,
                }
            })?;

            self.items.push(slice);
        } else {
            let offset = read_offset(&self.bytes[self.items_index..])?;

            let previous_offset = self
                .offsets
                .last()
                .and_then(|o| Some(o.offset))
                .unwrap_or_else(|| BYTES_PER_LENGTH_OFFSET);

            if previous_offset > offset {
                return Err(DecodeError::OutOfBoundsByte { i: offset });
            } else if offset > self.bytes.len() {
                return Err(DecodeError::OutOfBoundsByte { i: offset });
            }

            self.offsets.push(Offset {
                position: self.items.len(),
                offset,
            });

            // Push an empty slice into items; it will be replaced later.
            self.items.push(&[]);

            self.items_index += BYTES_PER_LENGTH_OFFSET;
        }

        Ok(())
    }

    fn apply_offsets(&mut self) -> Result<(), DecodeError> {
        if !self.offsets.is_empty() {
            // Check to ensure the first offset points to the byte immediately following the
            // fixed-length bytes.
            if self.offsets[0].offset != self.items_index {
                return Err(DecodeError::OutOfBoundsByte {
                    i: self.offsets[0].offset,
                });
            }

            // Iterate through each pair of offsets, grabbing the slice between each of the offsets.
            for pair in self.offsets.windows(2) {
                let a = pair[0];
                let b = pair[1];

                self.items[a.position] = &self.bytes[a.offset..b.offset];
            }

            // Handle the last offset, pushing a slice from it's start through to the end of
            // `self.bytes`.
            if let Some(last) = self.offsets.last() {
                self.items[last.position] = &self.bytes[last.offset..]
            }
        }

        Ok(())
    }

    pub fn build(mut self) -> Result<SszDecoder<'a>, DecodeError> {
        self.apply_offsets()?;

        Ok(SszDecoder { items: self.items })
    }
}

pub struct SszDecoder<'a> {
    items: Vec<&'a [u8]>,
}

impl<'a> SszDecoder<'a> {
    /// Decodes the next item.
    ///
    /// # Panics
    ///
    /// Panics when attempting to decode more items than actually exist.
    pub fn decode_next<T: Decodable>(&mut self) -> Result<T, DecodeError> {
        T::from_ssz_bytes(self.items.remove(0))
    }
}

/// Reads a `BYTES_PER_LENGTH_OFFSET`-byte length from `bytes`, where `bytes.len() >=
/// BYTES_PER_LENGTH_OFFSET`.
fn read_offset(bytes: &[u8]) -> Result<usize, DecodeError> {
    decode_offset(bytes.get(0..BYTES_PER_LENGTH_OFFSET).ok_or_else(|| {
        DecodeError::InvalidLengthPrefix {
            len: bytes.len(),
            expected: BYTES_PER_LENGTH_OFFSET,
        }
    })?)
}

/// Decode bytes as a little-endian usize, returning an `Err` if `bytes.len() !=
/// BYTES_PER_LENGTH_OFFSET`.
fn decode_offset(bytes: &[u8]) -> Result<usize, DecodeError> {
    let len = bytes.len();
    let expected = BYTES_PER_LENGTH_OFFSET;

    if len != expected {
        Err(DecodeError::InvalidLengthPrefix { len, expected })
    } else {
        let mut array: [u8; BYTES_PER_LENGTH_OFFSET] = std::default::Default::default();
        array.clone_from_slice(bytes);

        Ok(u32::from_le_bytes(array) as usize)
    }
}
<|endoftext|>


<commit_before>// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

use std::cell::Ref;
use std::cell::RefCell;
use std::cell::RefMut;
use std::collections::HashSet;
use std::rc::Rc;

use thiserror::Error;

use crate::DecodedFormat;
use crate::Resolution;

pub mod h264;
pub mod vp8;
pub mod vp9;

pub type Result<T> = std::result::Result<T, Error>;

#[derive(Error, Debug)]
pub enum Error {
    #[error(transparent)]
    StatelessBackendError(#[from] StatelessBackendError),
    #[error(transparent)]
    Other(#[from] anyhow::Error),
}

#[derive(Error, Debug)]
pub enum StatelessBackendError {
    #[error("Not enough resources to proceed with the operation now.")]
    OutOfResources,
    #[error("This resource is not ready.")]
    ResourceNotReady,
    #[error("This format is not supported.")]
    UnsupportedFormat,
    #[error("Negotiation failed")]
    NegotiationFailed(anyhow::Error),
    #[error(transparent)]
    Other(#[from] anyhow::Error),
}

pub trait VideoDecoderBackend {
    /// Returns the current coded resolution of the bitstream being processed.
    /// This may be None if we have not read the stream parameters yet.
    fn coded_resolution(&self) -> Option<Resolution>;

    /// Returns the current display resolution of the bitstream being processed.
    /// This may be None if we have not read the stream parameters yet.
    fn display_resolution(&self) -> Option<Resolution>;

    /// Gets the number of output resources allocated by the backend.
    fn num_resources_total(&self) -> usize;

    /// Gets the number of output resources left in the backend.
    fn num_resources_left(&self) -> usize;

    /// Gets the chosen format. This is set to a default after the decoder reads
    /// enough stream metadata from the bitstream. Some buffers need to be
    /// processed first before the default format can be set.
    fn format(&self) -> Option<DecodedFormat>;

    /// Gets a set of supported formats for the particular stream being
    /// processed. This required that some buffers be processed before this call
    /// is made. Only formats that are compatible with the current color space,
    /// bit depth, and chroma format are returned such that no conversion is
    /// needed.
    ///
    /// The format can be altered by calling `try_format` if the new format is
    /// supported by the implementation.
    fn supported_formats_for_stream(&self) -> Result<HashSet<DecodedFormat>>;

    /// Try altering the decoded format.
    fn try_format(&mut self, format: DecodedFormat) -> Result<()>;
}

pub trait VideoDecoder {
    /// Decode the `bitstream` represented by `timestamp`. Returns zero or more
    /// decoded handles representing the decoded data.
    fn decode(
        &mut self,
        timestamp: u64,
        bitstream: &dyn AsRef<[u8]>,
    ) -> Result<Vec<Box<dyn DynDecodedHandle>>>;

    /// Flush the decoder i.e. finish processing all queued decode requests and
    /// emit frames for them.
    fn flush(&mut self) -> Result<Vec<Box<dyn DynDecodedHandle>>>;

    /// Gets a shared handle to the backend in use.
    fn backend(&self) -> &dyn VideoDecoderBackend;

    /// Gets a mutable handle to the backend in use.
    fn backend_mut(&mut self) -> &mut dyn VideoDecoderBackend;

    /// Whether negotiation of the decoded format is possible. In particular, a
    /// decoder will indicate that negotiation is possible after enough metadata
    /// is collected from parsing the bitstream through calls to the `decode()`
    /// method.
    ///
    /// The negotiation process will start as soon as `negotiation_possible()`
    /// returns true. At this moment, the client and the backend can settle on a
    /// format by using the `supported_formats_for_stream()`, `format()` and
    /// `try_format()` methods.
    ///
    /// When `negotiation_possible()` returns true, the client may also query
    /// the backend for new values for the coded resolution, display resolution
    /// and/or to the number of resources allocated.
    ///
    /// The negotiation process ends as soon as another call to `decode()` is
    /// made, at which point any queued data will be processed first in order to
    /// generate any frames that might have been pending while the negotiation
    /// process was under way and `negotiation_possible()` will from then on
    /// return false.
    ///
    /// If no action is undertaken by the client in the window of time where
    /// `negotiation_possible()` returns true, it is assumed that the default
    /// format chosen by the backend is acceptable.
    ///
    /// The negotiation process can happen more than once if new stream metadata
    /// indicate a change of the stream parameters such that the current decoded
    /// format becomes incompatible with the stream. In this case,
    /// `negotiation_possible()` will once again return true and the same
    /// process described above will take place.
    fn negotiation_possible(&self) -> bool;

    /// Gets the number of output resources left in the backend after accounting
    /// for any buffers that might be queued in the decoder.
    fn num_resources_left(&self) -> Option<usize>;

    /// Polls the decoder, emitting frames for all queued decode requests. This
    /// is similar to flush, but it does not change the state of the decoded
    /// picture buffer nor does it reset any internal state.
    fn poll(&mut self, blocking_mode: BlockingMode) -> Result<Vec<Box<dyn DynDecodedHandle>>>;
}

pub trait DynDecodedHandle {
    fn dyn_picture(&self) -> Ref<dyn DynPicture>;
    fn dyn_picture_mut(&self) -> RefMut<dyn DynPicture>;
    fn timestamp(&self) -> u64;
    fn display_resolution(&self) -> Resolution;
    fn display_order(&self) -> Option<u64>;
}

impl<T> DynDecodedHandle for T
where
    T: DecodedHandle,
    <T as DecodedHandle>::BackendHandle: MappableHandle,
{
    fn dyn_picture(&self) -> Ref<dyn DynPicture> {
        self.picture()
    }

    fn dyn_picture_mut(&self) -> RefMut<dyn DynPicture> {
        self.picture_mut()
    }

    fn timestamp(&self) -> u64 {
        DecodedHandle::timestamp(self)
    }

    fn display_resolution(&self) -> Resolution {
        DecodedHandle::display_resolution(self)
    }

    fn display_order(&self) -> Option<u64> {
        DecodedHandle::display_order(self)
    }
}

pub trait DynPicture {
    /// Gets an exclusive reference to the backend handle of this picture.
    /// Assumes that this picture is backed by a handle and panics if not the case.
    fn dyn_mappable_handle_mut(&mut self) -> &mut dyn MappableHandle;
}

/// A trait for types that can be mapped into the client's address space.
pub trait MappableHandle: downcast_rs::Downcast {
    /// Map &self as-is into the client's address space. The bytes may be laid out
    /// in a hardware-optimized way.
    fn map(&mut self) -> Result<Box<dyn AsRef<[u8]> + '_>>;

    /// Read the contents of `self` into `buffer`.
    fn read(&mut self, buffer: &mut [u8]) -> Result<()>;

    fn mapped_resolution(&mut self) -> Result<Resolution>;
}
downcast_rs::impl_downcast!(MappableHandle);

/// Instructs the decoder on whether it should block on the decode operations.
/// Nonblocking mode is conditional on backend support.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum BlockingMode {
    Blocking,
    NonBlocking,
}

impl Default for BlockingMode {
    fn default() -> Self {
        Self::Blocking
    }
}

/// Type for a picture being decoded by a stateless codec.
///
/// This contains the codec-specific state of the picture, as well as the backend-specific handle
/// representing the memory into which the frame will be decoded.
pub struct Picture<CodecData, BackendHandle> {
    /// Codec-specific data for this picture.
    pub data: CodecData,
    /// Backend-specific handle with the memory needed by the backend to back this picture.
    pub backend_handle: Option<BackendHandle>,
    /// A number that identifies the picture.
    timestamp: u64,
}

impl<CodecData, BackendHandle> Picture<CodecData, BackendHandle> {
    /// Whether two pictures are the same.
    pub fn same(lhs: &Rc<RefCell<Self>>, rhs: &Rc<RefCell<Self>>) -> bool {
        Rc::ptr_eq(lhs, rhs)
    }

    /// Get a reference to the picture's timestamp.
    pub fn timestamp(&self) -> u64 {
        self.timestamp
    }
}

/// Automatic `DynPicture` implementation for handles that are `MappableHandle`s.
impl<CodecData, BackendHandle: MappableHandle> DynPicture for Picture<CodecData, BackendHandle> {
    fn dyn_mappable_handle_mut(&mut self) -> &mut dyn MappableHandle {
        self.backend_handle.as_mut().unwrap()
    }
}

/// The handle type used by the stateless decoder backend. The only requirement
/// from implementors is that they give access to the underlying Picture and
/// that they can be (cheaply) cloned.
pub trait DecodedHandle: Clone {
    /// Codec-specific data for the handle.
    type CodecData;
    /// The type of the handle used by the backend.
    type BackendHandle;

    /// Returns the actual container of the inner `Picture`.
    fn picture_container(&self) -> &Rc<RefCell<Picture<Self::CodecData, Self::BackendHandle>>>;
    /// Returns the display resolution at the time this handle was decoded.
    fn display_resolution(&self) -> Resolution;
    /// Returns the display order for this picture, if set by the decoder.
    fn display_order(&self) -> Option<u64>;
    /// Sets the display order for this picture.
    fn set_display_order(&mut self, display_order: u64);

    /// Returns a shared reference to the inner `Picture`.
    fn picture(&self) -> Ref<Picture<Self::CodecData, Self::BackendHandle>> {
        self.picture_container().borrow()
    }
    /// Returns a mutable reference to the inner `Picture`.
    fn picture_mut(&self) -> RefMut<Picture<Self::CodecData, Self::BackendHandle>> {
        self.picture_container().borrow_mut()
    }
    /// Returns the timestamp for the picture.
    fn timestamp(&self) -> u64 {
        self.picture().timestamp()
    }
}
<commit_msg>media: cros-codecs: harmonize error descriptions<commit_after>// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

use std::cell::Ref;
use std::cell::RefCell;
use std::cell::RefMut;
use std::collections::HashSet;
use std::rc::Rc;

use thiserror::Error;

use crate::DecodedFormat;
use crate::Resolution;

pub mod h264;
pub mod vp8;
pub mod vp9;

pub type Result<T> = std::result::Result<T, Error>;

#[derive(Error, Debug)]
pub enum Error {
    #[error(transparent)]
    StatelessBackendError(#[from] StatelessBackendError),
    #[error(transparent)]
    Other(#[from] anyhow::Error),
}

#[derive(Error, Debug)]
pub enum StatelessBackendError {
    #[error("not enough resources to proceed with the operation now")]
    OutOfResources,
    #[error("this resource is not ready")]
    ResourceNotReady,
    #[error("this format is not supported")]
    UnsupportedFormat,
    #[error("negotiation failed")]
    NegotiationFailed(anyhow::Error),
    #[error(transparent)]
    Other(#[from] anyhow::Error),
}

pub trait VideoDecoderBackend {
    /// Returns the current coded resolution of the bitstream being processed.
    /// This may be None if we have not read the stream parameters yet.
    fn coded_resolution(&self) -> Option<Resolution>;

    /// Returns the current display resolution of the bitstream being processed.
    /// This may be None if we have not read the stream parameters yet.
    fn display_resolution(&self) -> Option<Resolution>;

    /// Gets the number of output resources allocated by the backend.
    fn num_resources_total(&self) -> usize;

    /// Gets the number of output resources left in the backend.
    fn num_resources_left(&self) -> usize;

    /// Gets the chosen format. This is set to a default after the decoder reads
    /// enough stream metadata from the bitstream. Some buffers need to be
    /// processed first before the default format can be set.
    fn format(&self) -> Option<DecodedFormat>;

    /// Gets a set of supported formats for the particular stream being
    /// processed. This required that some buffers be processed before this call
    /// is made. Only formats that are compatible with the current color space,
    /// bit depth, and chroma format are returned such that no conversion is
    /// needed.
    ///
    /// The format can be altered by calling `try_format` if the new format is
    /// supported by the implementation.
    fn supported_formats_for_stream(&self) -> Result<HashSet<DecodedFormat>>;

    /// Try altering the decoded format.
    fn try_format(&mut self, format: DecodedFormat) -> Result<()>;
}

pub trait VideoDecoder {
    /// Decode the `bitstream` represented by `timestamp`. Returns zero or more
    /// decoded handles representing the decoded data.
    fn decode(
        &mut self,
        timestamp: u64,
        bitstream: &dyn AsRef<[u8]>,
    ) -> Result<Vec<Box<dyn DynDecodedHandle>>>;

    /// Flush the decoder i.e. finish processing all queued decode requests and
    /// emit frames for them.
    fn flush(&mut self) -> Result<Vec<Box<dyn DynDecodedHandle>>>;

    /// Gets a shared handle to the backend in use.
    fn backend(&self) -> &dyn VideoDecoderBackend;

    /// Gets a mutable handle to the backend in use.
    fn backend_mut(&mut self) -> &mut dyn VideoDecoderBackend;

    /// Whether negotiation of the decoded format is possible. In particular, a
    /// decoder will indicate that negotiation is possible after enough metadata
    /// is collected from parsing the bitstream through calls to the `decode()`
    /// method.
    ///
    /// The negotiation process will start as soon as `negotiation_possible()`
    /// returns true. At this moment, the client and the backend can settle on a
    /// format by using the `supported_formats_for_stream()`, `format()` and
    /// `try_format()` methods.
    ///
    /// When `negotiation_possible()` returns true, the client may also query
    /// the backend for new values for the coded resolution, display resolution
    /// and/or to the number of resources allocated.
    ///
    /// The negotiation process ends as soon as another call to `decode()` is
    /// made, at which point any queued data will be processed first in order to
    /// generate any frames that might have been pending while the negotiation
    /// process was under way and `negotiation_possible()` will from then on
    /// return false.
    ///
    /// If no action is undertaken by the client in the window of time where
    /// `negotiation_possible()` returns true, it is assumed that the default
    /// format chosen by the backend is acceptable.
    ///
    /// The negotiation process can happen more than once if new stream metadata
    /// indicate a change of the stream parameters such that the current decoded
    /// format becomes incompatible with the stream. In this case,
    /// `negotiation_possible()` will once again return true and the same
    /// process described above will take place.
    fn negotiation_possible(&self) -> bool;

    /// Gets the number of output resources left in the backend after accounting
    /// for any buffers that might be queued in the decoder.
    fn num_resources_left(&self) -> Option<usize>;

    /// Polls the decoder, emitting frames for all queued decode requests. This
    /// is similar to flush, but it does not change the state of the decoded
    /// picture buffer nor does it reset any internal state.
    fn poll(&mut self, blocking_mode: BlockingMode) -> Result<Vec<Box<dyn DynDecodedHandle>>>;
}

pub trait DynDecodedHandle {
    fn dyn_picture(&self) -> Ref<dyn DynPicture>;
    fn dyn_picture_mut(&self) -> RefMut<dyn DynPicture>;
    fn timestamp(&self) -> u64;
    fn display_resolution(&self) -> Resolution;
    fn display_order(&self) -> Option<u64>;
}

impl<T> DynDecodedHandle for T
where
    T: DecodedHandle,
    <T as DecodedHandle>::BackendHandle: MappableHandle,
{
    fn dyn_picture(&self) -> Ref<dyn DynPicture> {
        self.picture()
    }

    fn dyn_picture_mut(&self) -> RefMut<dyn DynPicture> {
        self.picture_mut()
    }

    fn timestamp(&self) -> u64 {
        DecodedHandle::timestamp(self)
    }

    fn display_resolution(&self) -> Resolution {
        DecodedHandle::display_resolution(self)
    }

    fn display_order(&self) -> Option<u64> {
        DecodedHandle::display_order(self)
    }
}

pub trait DynPicture {
    /// Gets an exclusive reference to the backend handle of this picture.
    /// Assumes that this picture is backed by a handle and panics if not the case.
    fn dyn_mappable_handle_mut(&mut self) -> &mut dyn MappableHandle;
}

/// A trait for types that can be mapped into the client's address space.
pub trait MappableHandle: downcast_rs::Downcast {
    /// Map &self as-is into the client's address space. The bytes may be laid out
    /// in a hardware-optimized way.
    fn map(&mut self) -> Result<Box<dyn AsRef<[u8]> + '_>>;

    /// Read the contents of `self` into `buffer`.
    fn read(&mut self, buffer: &mut [u8]) -> Result<()>;

    fn mapped_resolution(&mut self) -> Result<Resolution>;
}
downcast_rs::impl_downcast!(MappableHandle);

/// Instructs the decoder on whether it should block on the decode operations.
/// Nonblocking mode is conditional on backend support.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum BlockingMode {
    Blocking,
    NonBlocking,
}

impl Default for BlockingMode {
    fn default() -> Self {
        Self::Blocking
    }
}

/// Type for a picture being decoded by a stateless codec.
///
/// This contains the codec-specific state of the picture, as well as the backend-specific handle
/// representing the memory into which the frame will be decoded.
pub struct Picture<CodecData, BackendHandle> {
    /// Codec-specific data for this picture.
    pub data: CodecData,
    /// Backend-specific handle with the memory needed by the backend to back this picture.
    pub backend_handle: Option<BackendHandle>,
    /// A number that identifies the picture.
    timestamp: u64,
}

impl<CodecData, BackendHandle> Picture<CodecData, BackendHandle> {
    /// Whether two pictures are the same.
    pub fn same(lhs: &Rc<RefCell<Self>>, rhs: &Rc<RefCell<Self>>) -> bool {
        Rc::ptr_eq(lhs, rhs)
    }

    /// Get a reference to the picture's timestamp.
    pub fn timestamp(&self) -> u64 {
        self.timestamp
    }
}

/// Automatic `DynPicture` implementation for handles that are `MappableHandle`s.
impl<CodecData, BackendHandle: MappableHandle> DynPicture for Picture<CodecData, BackendHandle> {
    fn dyn_mappable_handle_mut(&mut self) -> &mut dyn MappableHandle {
        self.backend_handle.as_mut().unwrap()
    }
}

/// The handle type used by the stateless decoder backend. The only requirement
/// from implementors is that they give access to the underlying Picture and
/// that they can be (cheaply) cloned.
pub trait DecodedHandle: Clone {
    /// Codec-specific data for the handle.
    type CodecData;
    /// The type of the handle used by the backend.
    type BackendHandle;

    /// Returns the actual container of the inner `Picture`.
    fn picture_container(&self) -> &Rc<RefCell<Picture<Self::CodecData, Self::BackendHandle>>>;
    /// Returns the display resolution at the time this handle was decoded.
    fn display_resolution(&self) -> Resolution;
    /// Returns the display order for this picture, if set by the decoder.
    fn display_order(&self) -> Option<u64>;
    /// Sets the display order for this picture.
    fn set_display_order(&mut self, display_order: u64);

    /// Returns a shared reference to the inner `Picture`.
    fn picture(&self) -> Ref<Picture<Self::CodecData, Self::BackendHandle>> {
        self.picture_container().borrow()
    }
    /// Returns a mutable reference to the inner `Picture`.
    fn picture_mut(&self) -> RefMut<Picture<Self::CodecData, Self::BackendHandle>> {
        self.picture_container().borrow_mut()
    }
    /// Returns the timestamp for the picture.
    fn timestamp(&self) -> u64 {
        self.picture().timestamp()
    }
}
<|endoftext|>


<commit_before>//! A minimal example to demonstrate the effect of HRTF (head-related transfer function) on a
//! real-time stream of audio. HRTF is a popular approach to achieving binaural audio.
//!
//! The example uses generated white noise as the sound source. The source position can be rotated
//! around the user by moving the mouse along the *x* axis.
//!
//! The example will fail if the default cpal output device under the default host offers less than
//! two channels.
//!
//! This effect is best experienced with headphones!

use hrtf::{HrirSphere, HrtfContext, HrtfProcessor};
use nannou::prelude::*;
use nannou::rand::{rngs::SmallRng, Rng, SeedableRng};
use nannou_audio as audio;
use nannou_audio::Buffer;

fn main() {
    nannou::app(model).run();
}

struct Model {
    stream: audio::Stream<Audio>,
    source_position: Point3,
}

// HRTF requires a fixed sample rate and "block length" (i.e. buffer length in frames).
const SAMPLE_RATE: u32 = 44_100;
const BUFFER_LEN_FRAMES: usize = 64;

// Taken from rg3d-sound.
const HRTF_BLOCK_LEN: usize = 513;
const HRTF_INTERPOLATION_STEPS: usize = 8;
const HRTF_BUFFER_LEN: usize = HRTF_BLOCK_LEN * HRTF_INTERPOLATION_STEPS;

// The radius in which we can hear the sound in points.
const LISTENING_RADIUS: f32 = 300.0;
const WINDOW_SIDE: u32 = (LISTENING_RADIUS * 2.0) as u32;

struct Audio {
    rng: SmallRng,
    hrtf_data: HrtfData,
    hrtf_processor: HrtfProcessor,
    source_position: Point3,
    prev_source_position: Point3,
}

// A set of buffers to re-use for HRTF processing.
struct HrtfData {
    source: Vec<f32>,
    output: Box<[(f32, f32); HRTF_BUFFER_LEN]>,
    prev_left_samples: Vec<f32>,
    prev_right_samples: Vec<f32>,
}

impl HrtfData {
    fn new() -> Self {
        HrtfData {
            source: vec![0.0; HRTF_BUFFER_LEN],
            output: Box::new([(0.0, 0.0); HRTF_BUFFER_LEN]),
            prev_left_samples: vec![0.0; HRTF_BUFFER_LEN],
            prev_right_samples: vec![0.0; HRTF_BUFFER_LEN],
        }
    }
}

fn model(app: &App) -> Model {
    app.new_window()
        .size(WINDOW_SIDE, WINDOW_SIDE)
        .key_pressed(key_pressed)
        .mouse_moved(mouse_moved)
        .view(view)
        .build()
        .unwrap();

    // Initialise the audio API so we can spawn an audio stream.
    let audio_host = audio::Host::new();

    // Load a HRIR sphere and initialise the processor.
    let assets = app.assets_path().unwrap();
    let hrir_sphere_path = assets.join("hrir").join("IRC_1002_C").with_extension("bin");
    let hrir_sphere = HrirSphere::from_file(hrir_sphere_path, SAMPLE_RATE)
        .expect("failed to load HRIR sphere from file");
    let hrtf_processor = HrtfProcessor::new(hrir_sphere, HRTF_INTERPOLATION_STEPS, HRTF_BLOCK_LEN);

    // Initialise the state that we want to live on the audio thread.
    let source_position = [0.0; 3].into();
    let audio_model = Audio {
        rng: SmallRng::from_seed([0; 16]),
        hrtf_data: HrtfData::new(),
        hrtf_processor,
        source_position,
        prev_source_position: [0.0; 3].into(),
    };

    let stream = audio_host
        .new_output_stream(audio_model)
        .render(audio)
        .channels(2)
        .sample_rate(SAMPLE_RATE)
        .frames_per_buffer(BUFFER_LEN_FRAMES)
        .build()
        .unwrap();
    Model {
        stream,
        source_position,
    }
}

// A function that renders the given `Audio` to the given `Buffer`.
// In this case we play a simple sine wave at the audio's current frequency in `hz`.
fn audio(audio: &mut Audio, output: &mut Buffer) {
    // Silence the output buffers.
    for sample in output.iter_mut() {
        *sample = 0.0;
    }
    for sample in audio.hrtf_data.output.iter_mut() {
        *sample = (0.0, 0.0);
    }

    // Fill the source buffer with new noise.
    audio.hrtf_data.source.drain(..BUFFER_LEN_FRAMES);
    for _ in 0..BUFFER_LEN_FRAMES {
        let sample = audio.rng.gen::<f32>() * 2.0 - 1.0;
        audio.hrtf_data.source.push(sample);
    }

    // Calculate the distance based gain.
    let new_distance_gain = dist_gain(&audio.source_position);
    let prev_distance_gain = dist_gain(&audio.prev_source_position);

    // Apply the HRTF.
    let hrtf_ctxt = HrtfContext {
        source: &audio.hrtf_data.source[..],
        output: &mut audio.hrtf_data.output[..],
        new_sample_vector: (-audio.source_position).into(),
        prev_sample_vector: (-audio.prev_source_position).into(),
        prev_left_samples: &mut audio.hrtf_data.prev_left_samples,
        prev_right_samples: &mut audio.hrtf_data.prev_right_samples,
        new_distance_gain,
        prev_distance_gain,
    };
    audio.hrtf_processor.process_samples(hrtf_ctxt);

    // Update `prev` data with current data.
    audio.prev_source_position = audio.source_position;

    // Write the result to the output buffer.
    let hrtf_out = &audio.hrtf_data.output[HRTF_BUFFER_LEN - BUFFER_LEN_FRAMES..];
    for (out_f, &(l, r)) in output.frames_mut().zip(hrtf_out) {
        // Try not to scare the bajeezus out of anyone running the example.
        let volume = 0.1;
        out_f[0] = l * volume;
        out_f[1] = r * volume;
    }
}

fn key_pressed(_app: &App, model: &mut Model, key: Key) {
    // Pause or unpause the audio when Space is pressed.
    if let Key::Space = key {
        if model.stream.is_playing() {
            model.stream.pause().unwrap();
        } else {
            model.stream.play().unwrap();
        }
    }
}

fn mouse_moved(_app: &App, model: &mut Model, p: Point2) {
    let (x, y) = p.into();
    // Use the y axis of the mouse position for the z axis in space.
    let new_source_position = pt3(x, 0.0, y) / LISTENING_RADIUS;
    model.source_position = new_source_position;
    model
        .stream
        .send(move |audio| audio.source_position = new_source_position)
        .ok();
}

fn view(app: &App, model: &Model, frame: Frame) {
    frame.clear(rgb(0.1, 0.12, 0.13));
    let draw = app.draw();

    // Listenable area.
    draw.ellipse().radius(LISTENING_RADIUS).rgb(0.1, 0.1, 0.1);

    // Draw the head.
    draw.ellipse().color(BLUE);
    draw.arrow()
        .color(BLACK)
        .weight(5.0)
        .points(pt2(0.0, -30.0), pt2(0.0, 30.0));
    draw.text("HEAD").color(WHITE);

    // Draw the source.
    let (x, y, z) = model.source_position.into();
    let text = format!("Noise Source:\n[{:.2}, {:.2}, {:.2}]", x, y, z);
    draw.text(&text).xy(app.mouse.position() + vec2(0.0, 20.0));

    draw.to_frame(app, &frame).unwrap();
}

// Simple function for determining a gain based on the distance from the listener.
fn dist_gain(p: &Point3) -> f32 {
    let m = p.magnitude();
    if m == 0.0 {
        1.0
    } else if m > 1.0 {
        0.0
    } else {
        1.0 - m
    }
    .powf(1.6)
    .min(1.0)
}
<commit_msg>Remove incorrect comment<commit_after>//! A minimal example to demonstrate the effect of HRTF (head-related transfer function) on a
//! real-time stream of audio. HRTF is a popular approach to achieving binaural audio.
//!
//! The example uses generated white noise as the sound source. The source position can be rotated
//! around the user by moving the mouse along the *x* axis.
//!
//! The example will fail if the default cpal output device under the default host offers less than
//! two channels.
//!
//! This effect is best experienced with headphones!

use hrtf::{HrirSphere, HrtfContext, HrtfProcessor};
use nannou::prelude::*;
use nannou::rand::{rngs::SmallRng, Rng, SeedableRng};
use nannou_audio as audio;
use nannou_audio::Buffer;

fn main() {
    nannou::app(model).run();
}

struct Model {
    stream: audio::Stream<Audio>,
    source_position: Point3,
}

// HRTF requires a fixed sample rate and "block length" (i.e. buffer length in frames).
const SAMPLE_RATE: u32 = 44_100;
const BUFFER_LEN_FRAMES: usize = 64;

// Taken from rg3d-sound.
const HRTF_BLOCK_LEN: usize = 513;
const HRTF_INTERPOLATION_STEPS: usize = 8;
const HRTF_BUFFER_LEN: usize = HRTF_BLOCK_LEN * HRTF_INTERPOLATION_STEPS;

// The radius in which we can hear the sound in points.
const LISTENING_RADIUS: f32 = 300.0;
const WINDOW_SIDE: u32 = (LISTENING_RADIUS * 2.0) as u32;

struct Audio {
    rng: SmallRng,
    hrtf_data: HrtfData,
    hrtf_processor: HrtfProcessor,
    source_position: Point3,
    prev_source_position: Point3,
}

// A set of buffers to re-use for HRTF processing.
struct HrtfData {
    source: Vec<f32>,
    output: Box<[(f32, f32); HRTF_BUFFER_LEN]>,
    prev_left_samples: Vec<f32>,
    prev_right_samples: Vec<f32>,
}

impl HrtfData {
    fn new() -> Self {
        HrtfData {
            source: vec![0.0; HRTF_BUFFER_LEN],
            output: Box::new([(0.0, 0.0); HRTF_BUFFER_LEN]),
            prev_left_samples: vec![0.0; HRTF_BUFFER_LEN],
            prev_right_samples: vec![0.0; HRTF_BUFFER_LEN],
        }
    }
}

fn model(app: &App) -> Model {
    app.new_window()
        .size(WINDOW_SIDE, WINDOW_SIDE)
        .key_pressed(key_pressed)
        .mouse_moved(mouse_moved)
        .view(view)
        .build()
        .unwrap();

    // Initialise the audio API so we can spawn an audio stream.
    let audio_host = audio::Host::new();

    // Load a HRIR sphere and initialise the processor.
    let assets = app.assets_path().unwrap();
    let hrir_sphere_path = assets.join("hrir").join("IRC_1002_C").with_extension("bin");
    let hrir_sphere = HrirSphere::from_file(hrir_sphere_path, SAMPLE_RATE)
        .expect("failed to load HRIR sphere from file");
    let hrtf_processor = HrtfProcessor::new(hrir_sphere, HRTF_INTERPOLATION_STEPS, HRTF_BLOCK_LEN);

    // Initialise the state that we want to live on the audio thread.
    let source_position = [0.0; 3].into();
    let audio_model = Audio {
        rng: SmallRng::from_seed([0; 16]),
        hrtf_data: HrtfData::new(),
        hrtf_processor,
        source_position,
        prev_source_position: [0.0; 3].into(),
    };

    let stream = audio_host
        .new_output_stream(audio_model)
        .render(audio)
        .channels(2)
        .sample_rate(SAMPLE_RATE)
        .frames_per_buffer(BUFFER_LEN_FRAMES)
        .build()
        .unwrap();
    Model {
        stream,
        source_position,
    }
}

// A function that renders the given `Audio` to the given `Buffer`.
fn audio(audio: &mut Audio, output: &mut Buffer) {
    // Silence the output buffers.
    for sample in output.iter_mut() {
        *sample = 0.0;
    }
    for sample in audio.hrtf_data.output.iter_mut() {
        *sample = (0.0, 0.0);
    }

    // Fill the source buffer with new noise.
    audio.hrtf_data.source.drain(..BUFFER_LEN_FRAMES);
    for _ in 0..BUFFER_LEN_FRAMES {
        let sample = audio.rng.gen::<f32>() * 2.0 - 1.0;
        audio.hrtf_data.source.push(sample);
    }

    // Calculate the distance based gain.
    let new_distance_gain = dist_gain(&audio.source_position);
    let prev_distance_gain = dist_gain(&audio.prev_source_position);

    // Apply the HRTF.
    let hrtf_ctxt = HrtfContext {
        source: &audio.hrtf_data.source[..],
        output: &mut audio.hrtf_data.output[..],
        new_sample_vector: (-audio.source_position).into(),
        prev_sample_vector: (-audio.prev_source_position).into(),
        prev_left_samples: &mut audio.hrtf_data.prev_left_samples,
        prev_right_samples: &mut audio.hrtf_data.prev_right_samples,
        new_distance_gain,
        prev_distance_gain,
    };
    audio.hrtf_processor.process_samples(hrtf_ctxt);

    // Update `prev` data with current data.
    audio.prev_source_position = audio.source_position;

    // Write the result to the output buffer.
    let hrtf_out = &audio.hrtf_data.output[HRTF_BUFFER_LEN - BUFFER_LEN_FRAMES..];
    for (out_f, &(l, r)) in output.frames_mut().zip(hrtf_out) {
        // Try not to scare the bajeezus out of anyone running the example.
        let volume = 0.1;
        out_f[0] = l * volume;
        out_f[1] = r * volume;
    }
}

fn key_pressed(_app: &App, model: &mut Model, key: Key) {
    // Pause or unpause the audio when Space is pressed.
    if let Key::Space = key {
        if model.stream.is_playing() {
            model.stream.pause().unwrap();
        } else {
            model.stream.play().unwrap();
        }
    }
}

fn mouse_moved(_app: &App, model: &mut Model, p: Point2) {
    let (x, y) = p.into();
    // Use the y axis of the mouse position for the z axis in space.
    let new_source_position = pt3(x, 0.0, y) / LISTENING_RADIUS;
    model.source_position = new_source_position;
    model
        .stream
        .send(move |audio| audio.source_position = new_source_position)
        .ok();
}

fn view(app: &App, model: &Model, frame: Frame) {
    frame.clear(rgb(0.1, 0.12, 0.13));
    let draw = app.draw();

    // Listenable area.
    draw.ellipse().radius(LISTENING_RADIUS).rgb(0.1, 0.1, 0.1);

    // Draw the head.
    draw.ellipse().color(BLUE);
    draw.arrow()
        .color(BLACK)
        .weight(5.0)
        .points(pt2(0.0, -30.0), pt2(0.0, 30.0));
    draw.text("HEAD").color(WHITE);

    // Draw the source.
    let (x, y, z) = model.source_position.into();
    let text = format!("Noise Source:\n[{:.2}, {:.2}, {:.2}]", x, y, z);
    draw.text(&text).xy(app.mouse.position() + vec2(0.0, 20.0));

    draw.to_frame(app, &frame).unwrap();
}

// Simple function for determining a gain based on the distance from the listener.
fn dist_gain(p: &Point3) -> f32 {
    let m = p.magnitude();
    if m == 0.0 {
        1.0
    } else if m > 1.0 {
        0.0
    } else {
        1.0 - m
    }
    .powf(1.6)
    .min(1.0)
}
<|endoftext|>


<commit_before><commit_msg>Fixed windows support<commit_after><|endoftext|>