#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum AnalysisDomain {
Waveform,
Spectrum,
}
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
#[derive(Clone, Debug, PartialEq)]
pub struct AudioAnalyser {
node: web_sys::AnalyserNode,
}
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
impl AudioAnalyser {
pub(crate) fn new(node: web_sys::AnalyserNode) -> Self {
Self { node }
}
pub fn read(&self, domain: AnalysisDomain) -> Vec<f32> {
match domain {
AnalysisDomain::Waveform => {
let mut samples = vec![0_u8; self.node.fft_size() as usize];
self.node.get_byte_time_domain_data(&mut samples);
samples
.into_iter()
.map(|sample| ((sample as f32 - 128.0) / 128.0).clamp(-1.0, 1.0))
.collect()
}
AnalysisDomain::Spectrum => {
let samples = js_sys::Uint8Array::new_with_length(self.node.frequency_bin_count());
self.node.get_byte_frequency_data_with_u8_array(&samples);
let mut values = vec![0_u8; samples.length() as usize];
samples.copy_to(&mut values);
values
.into_iter()
.map(|sample| sample as f32 / 255.0)
.collect()
}
}
}
pub fn level(&self) -> f32 {
let waveform = self.read(AnalysisDomain::Waveform);
if waveform.is_empty() {
return 0.0;
}
let mean_square =
waveform.iter().map(|sample| sample * sample).sum::<f32>() / waveform.len() as f32;
mean_square.sqrt().clamp(0.0, 1.0)
}
pub(crate) fn node(&self) -> &web_sys::AnalyserNode {
&self.node
}
}
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
#[derive(Clone, Debug, PartialEq)]
pub struct AudioAnalyser {
_private: (),
}
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
impl AudioAnalyser {
pub fn read(&self, _domain: AnalysisDomain) -> Vec<f32> {
Vec::new()
}
pub fn level(&self) -> f32 {
0.0
}
}
pub fn downsample_peaks(peaks: &[u8], buckets: usize) -> Vec<u8> {
let bucket_count = peaks.len().min(buckets);
if bucket_count == 0 {
return Vec::new();
}
(0..bucket_count)
.map(|index| {
let start = index * peaks.len() / bucket_count;
let end = (index + 1) * peaks.len() / bucket_count;
peaks[start..end].iter().copied().max().unwrap_or(0)
})
.collect()
}
pub fn peak_amplitude(samples: &[u8]) -> u8 {
let distance = samples
.iter()
.map(|sample| (*sample as i16 - 128).unsigned_abs())
.max()
.unwrap_or(0);
((u32::from(distance) * 255 + 64) / 128).min(255) as u8
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct WaveformSelection {
start: f64,
end: f64,
}
impl WaveformSelection {
pub fn new(start: f64, end: f64) -> Self {
let start = if start.is_finite() {
start.clamp(0.0, 1.0)
} else {
0.0
};
let end = if end.is_finite() {
end.clamp(0.0, 1.0)
} else {
1.0
};
Self {
start: start.min(end),
end: start.max(end),
}
}
pub fn start(self) -> f64 {
self.start
}
pub fn end(self) -> f64 {
self.end
}
pub fn with_start(self, start: f64) -> Self {
let start = if start.is_finite() {
start.clamp(0.0, self.end)
} else {
self.start
};
Self {
start,
end: self.end,
}
}
pub fn with_end(self, end: f64) -> Self {
let end = if end.is_finite() {
end.clamp(self.start, 1.0)
} else {
self.end
};
Self {
start: self.start,
end,
}
}
}
pub fn trim_interleaved_pcm<T: Clone>(
samples: &[T],
channels: usize,
selection: WaveformSelection,
) -> Vec<T> {
if channels == 0 {
return Vec::new();
}
if selection.start() == selection.end() {
return Vec::new();
}
let frame_count = samples.len() / channels;
let first_frame = (selection.start() * frame_count as f64).floor() as usize;
let end_frame = (selection.end() * frame_count as f64).ceil() as usize;
samples[first_frame * channels..end_frame.min(frame_count) * channels].to_vec()
}