обновление

This commit is contained in:
2026-10-13 21:15:09 +02:00
parent 08a8a7cce5
commit f554de7908
8 changed files with 938 additions and 233 deletions

View File

@@ -1,14 +1,140 @@
use std::collections::VecDeque;
use std::fs::File;
use std::io::BufReader;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use rodio::{Decoder, OutputStream, OutputStreamBuilder, Sink};
use rodio::{Decoder, OutputStream, OutputStreamBuilder, Sink, Source};
const FFT_SIZE: usize = 1024;
const NUM_BARS: usize = 64;
#[derive(Clone)]
pub struct SpectrumBus {
buffer: Arc<Mutex<VecDeque<f32>>>,
bars: Arc<Mutex<[f32; NUM_BARS]>>,
}
impl SpectrumBus {
pub fn new() -> Self {
Self {
buffer: Arc::new(Mutex::new(VecDeque::with_capacity(FFT_SIZE * 2))),
bars: Arc::new(Mutex::new([0.0; NUM_BARS])),
}
}
fn push_sample(&self, s: f32) {
if let Ok(mut buf) = self.buffer.lock() {
buf.push_back(s);
if buf.len() > FFT_SIZE * 2 {
buf.pop_front();
}
}
}
pub fn bars(&self) -> [f32; NUM_BARS] {
self.bars.lock().map(|b| *b).unwrap_or([0.0; NUM_BARS])
}
pub fn recompute(&self) {
use rustfft::num_complex::Complex;
use rustfft::FftPlanner;
let samples: Vec<f32> = match self.buffer.lock() {
Ok(buf) => buf.iter().rev().take(FFT_SIZE).rev().copied().collect(),
Err(_) => return,
};
if samples.len() < FFT_SIZE {
return;
}
let mut data: Vec<Complex<f32>> = samples
.iter()
.enumerate()
.map(|(i, &s)| {
let w = 0.5
* (1.0
- (2.0 * std::f32::consts::PI * i as f32 / (FFT_SIZE - 1) as f32).cos());
Complex::new(s * w, 0.0)
})
.collect();
let mut planner = FftPlanner::<f32>::new();
let fft = planner.plan_fft_forward(FFT_SIZE);
fft.process(&mut data);
let half = FFT_SIZE / 2;
let mut bars = [0.0f32; NUM_BARS];
for i in 0..NUM_BARS {
let lo = ((i as f32 / NUM_BARS as f32).powi(2) * half as f32) as usize;
let hi = (((i + 1) as f32 / NUM_BARS as f32).powi(2) * half as f32) as usize;
let hi = hi.max(lo + 1).min(half);
let mut sum = 0.0;
for k in lo..hi {
sum += data[k].norm();
}
let avg = sum / (hi - lo) as f32;
let v = (avg / FFT_SIZE as f32).sqrt() * 4.0;
bars[i] = v.clamp(0.0, 1.0);
}
if let Ok(mut current) = self.bars.lock() {
for i in 0..NUM_BARS {
if bars[i] > current[i] {
current[i] = bars[i];
} else {
current[i] = current[i] * 0.7 + bars[i] * 0.3;
}
}
}
}
}
struct TapSource<S> {
inner: S,
bus: SpectrumBus,
}
impl<S> Iterator for TapSource<S>
where
S: Source<Item = f32>,
{
type Item = f32;
fn next(&mut self) -> Option<f32> {
let s = self.inner.next()?;
self.bus.push_sample(s);
Some(s)
}
}
impl<S> Source for TapSource<S>
where
S: Source<Item = f32>,
{
fn current_span_len(&self) -> Option<usize> {
self.inner.current_span_len()
}
fn channels(&self) -> u16 {
self.inner.channels()
}
fn sample_rate(&self) -> u32 {
self.inner.sample_rate()
}
fn total_duration(&self) -> Option<Duration> {
self.inner.total_duration()
}
// ВАЖНО: пробрасываем try_seek, иначе Sink::try_seek вернёт NotSupported
fn try_seek(&mut self, pos: Duration) -> Result<(), rodio::source::SeekError> {
self.inner.try_seek(pos)
}
}
pub struct AudioPlayer {
_stream: OutputStream,
sink: Sink,
paused_pos: Option<Duration>,
pub spectrum: SpectrumBus,
}
impl AudioPlayer {
@@ -20,15 +146,21 @@ impl AudioPlayer {
_stream: stream_handle,
sink,
paused_pos: None,
spectrum: SpectrumBus::new(),
})
}
pub fn play(&mut self, path: &PathBuf) -> Result<(), Box<dyn std::error::Error>> {
self.sink.stop();
self.paused_pos = None;
let file = BufReader::new(File::open(path)?);
let source = Decoder::new(file)?;
self.sink.append(source);
let file = File::open(path)?;
// Decoder::try_from автоматически настраивает seekable и byte_len[citation:1][citation:19]
let decoder = Decoder::try_from(file)?;
let tapped = TapSource {
inner: decoder,
bus: self.spectrum.clone(),
};
self.sink.append(tapped);
self.sink.play();
Ok(())
}
@@ -41,6 +173,10 @@ impl AudioPlayer {
}
}
pub fn is_paused(&self) -> bool {
self.sink.is_paused()
}
pub fn toggle_pause(&mut self) {
if self.sink.is_paused() {
self.sink.play();
@@ -64,11 +200,23 @@ impl AudioPlayer {
self.sink.volume()
}
pub fn set_volume(&mut self, v: f32) {
self.sink.set_volume(v.clamp(0.0, 2.0));
}
pub fn change_volume(&mut self, delta: f32) {
let v = (self.sink.volume() + delta).clamp(0.0, 2.0);
self.sink.set_volume(v);
}
pub fn seek_to(&mut self, pos: Duration) -> Result<(), Box<dyn std::error::Error>> {
self.sink.try_seek(pos)?;
if self.sink.is_paused() {
self.paused_pos = Some(pos);
}
Ok(())
}
pub fn seek_by(
&mut self,
delta: i64,
@@ -80,10 +228,7 @@ impl AudioPlayer {
if !total.is_zero() && new_pos >= total {
new_pos = total.saturating_sub(Duration::from_millis(100));
}
self.sink.try_seek(new_pos)?;
if self.sink.is_paused() {
self.paused_pos = Some(new_pos);
}
self.seek_to(new_pos)?;
Ok(new_pos)
}
}