tauri-plugin-audioplayer 0.1.2

Tauri Audioplayer Plugin
use std::sync::{Arc, Mutex};

pub(crate) struct PlaybackState {
    pub(crate) samples: Arc<Vec<f32>>,
    pub(crate) source_channels: usize,
    pub(crate) sample_rate: u32,
    pub(crate) position_samples: usize,
    pub(crate) playing: bool,
    pub(crate) volume: f32,
    dither_state: u64,
}

impl PlaybackState {
    pub(crate) fn new(samples: Arc<Vec<f32>>, source_channels: usize, sample_rate: u32) -> Self {
        Self {
            samples,
            source_channels,
            sample_rate,
            position_samples: 0,
            playing: true,
            volume: 1.0,
            // A non-zero seed is required by the xorshift generator used for TPDF dither.
            dither_state: 0x9e37_79b9_7f4a_7c15,
        }
    }
}

pub(crate) fn seek_position_samples(
    position_seconds: f64,
    sample_rate: u32,
    channels: usize,
    sample_count: usize,
) -> Option<usize> {
    if !position_seconds.is_finite() || position_seconds < 0.0 || sample_rate == 0 || channels == 0
    {
        return None;
    }

    let total_frames = sample_count / channels;
    let requested_frame = (position_seconds * sample_rate as f64).floor() as usize;
    Some(requested_frame.min(total_frames) * channels)
}

pub(crate) fn render_samples(
    data: &mut [f32],
    output_channels: usize,
    playback: &Arc<Mutex<PlaybackState>>,
) {
    render_converted_samples(data, output_channels, playback, 0.0, |sample, _| sample);
}

pub(crate) fn render_i32_samples(
    data: &mut [i32],
    output_channels: usize,
    playback: &Arc<Mutex<PlaybackState>>,
) {
    render_converted_samples(data, output_channels, playback, 0, |sample, _| {
        (sample.clamp(-1.0, 1.0) * i32::MAX as f32).round() as i32
    });
}

pub(crate) fn render_i16_samples(
    data: &mut [i16],
    output_channels: usize,
    playback: &Arc<Mutex<PlaybackState>>,
) {
    render_converted_samples(data, output_channels, playback, 0, |sample, playback| {
        let dithered = sample + tpdf_dither(playback, 1.0 / i16::MAX as f32);
        (dithered.clamp(-1.0, 1.0) * i16::MAX as f32).round() as i16
    });
}

pub(crate) fn render_u16_samples(
    data: &mut [u16],
    output_channels: usize,
    playback: &Arc<Mutex<PlaybackState>>,
) {
    render_converted_samples(
        data,
        output_channels,
        playback,
        32_768,
        |sample, playback| {
            let dithered = sample + tpdf_dither(playback, 1.0 / i16::MAX as f32);
            (dithered.clamp(-1.0, 1.0) * i16::MAX as f32 + 32_768.0).round() as u16
        },
    );
}

fn render_converted_samples<T: Copy>(
    data: &mut [T],
    output_channels: usize,
    playback: &Arc<Mutex<PlaybackState>>,
    silence: T,
    mut convert: impl FnMut(f32, &mut PlaybackState) -> T,
) {
    if output_channels == 0 {
        return;
    }

    let Ok(mut playback) = playback.lock() else {
        data.fill(silence);
        return;
    };

    for output_frame in data.chunks_mut(output_channels) {
        if !playback.playing
            || playback.position_samples + playback.source_channels > playback.samples.len()
        {
            output_frame.fill(silence);
            playback.playing = false;
            continue;
        }

        for (output_channel, sample) in output_frame.iter_mut().enumerate() {
            let source_channel = output_channel * playback.source_channels / output_channels;
            let sample_index = playback.position_samples + source_channel;
            let source_sample = playback.samples[sample_index];
            *sample = convert(source_sample * playback.volume, &mut playback);
        }
        playback.position_samples += playback.source_channels;
    }
}

fn tpdf_dither(playback: &mut PlaybackState, amplitude: f32) -> f32 {
    (next_random(playback) - next_random(playback)) * amplitude
}

fn next_random(playback: &mut PlaybackState) -> f32 {
    let mut state = playback.dither_state;
    state ^= state << 13;
    state ^= state >> 7;
    state ^= state << 17;
    playback.dither_state = state;
    (state as f64 / u64::MAX as f64) as f32
}