
const DEFAULT_MAP_SIZE: usize = 256;

pub struct AmplitudeMap {
    duration: f32,
    map: Vec<f32>,
}

impl AmplitudeMap {
    pub fn from_function(duration: f32, generator: impl Fn(f32) -> f32) -> Self {
        Self::from_function_sized(duration, generator, DEFAULT_MAP_SIZE)
    }

    pub fn from_function_sized(duration: f32, generator: impl Fn(f32) -> f32, size: usize) -> Self {
        Self {
            duration: duration.max(0.0),
            map: (0..size)
                .map(|i| generator(duration * i as f32 / size as f32).max(0.0).min(1.0))
                .collect()
        }
    }

    pub fn from_data(duration: f32, map: &[f32]) -> Self {
        Self {
            duration: duration.max(0.0),
            map: map.iter().map(|v| v.max(0.0).min(1.0)).collect(),
        }
    }

    pub fn get_value(&self, x: f32) -> f32 {
        let y_last = *self.map.last().unwrap_or(&0.0);
        if self.duration == 0.0 {
            y_last
        } else {
            let i = (x / self.duration) * self.map.len() as f32;
            let i1 = i.floor() as usize;
            let i2 = i.ceil() as usize;
            let y1 = self.map.get(i1).unwrap_or(&y_last);
            let y2 = self.map.get(i2).unwrap_or(&y_last);
            y1 * (1.0 - i.fract()) + y2 * i.fract()
        }
    }
}


// ============================================================================

pub fn default_hold() -> AmplitudeMap {
    fade_out(3.0)
}

pub fn default_release() -> AmplitudeMap {
    none()
}

pub fn fade_in(duration: f32) -> AmplitudeMap {
    AmplitudeMap::from_function(duration, |t| {
        // FIXME: Temporary hack to avoid automatic note disactivation on low amplitude
        let threshold = 0.01;
        t / duration + threshold
    })
}

pub fn fade_out(duration: f32) -> AmplitudeMap {
    AmplitudeMap::from_function(duration, |t| {
        1.0 / (7.0 * t / duration).exp()
    }) 
}

pub fn fade(in_duration: f32, out_duration: f32) -> AmplitudeMap {
    AmplitudeMap::from_function(in_duration + out_duration, |t| {
        if t < in_duration {
            // FIXME: Temporary hack to avoid automatic note disactivation on low amplitude
            let threshold = 0.01;
            t / in_duration + threshold
        } else {
            1.0 / (7.0 * (t - in_duration) / out_duration).exp()
        }
    }) 
}

pub fn none() -> AmplitudeMap {
    AmplitudeMap::from_data(0.0, &[1.0])
}

pub fn _mute() -> AmplitudeMap {
    AmplitudeMap::from_data(0.0, &[0.0])
}
