aboutsummaryrefslogtreecommitdiff
path: root/src/desktop/audio.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/desktop/audio.rs')
-rw-r--r--src/desktop/audio.rs85
1 files changed, 77 insertions, 8 deletions
diff --git a/src/desktop/audio.rs b/src/desktop/audio.rs
index a6905f8..42dc129 100644
--- a/src/desktop/audio.rs
+++ b/src/desktop/audio.rs
@@ -1,15 +1,63 @@
-use rodio::{OutputStream, Sink, Source};
+use rodio::{Sink, Source};
+use rodio::stream::{OutputStreamBuilder, OutputStream};
+use cpal::BufferSize;
use crate::audio::{MutableWave, SAMPLE_RATE};
use crate::io::{Audio, Wave};
+use crate::logs::{log, LogLevel};
use std::mem;
-use std::time::Duration;
+use std::time::{SystemTime, Duration};
+
+const BUFFER_SIZE: usize = 256;
+const RODIO_BUFFER_SIZE: usize = 1024;
+const RODIO_BUFFER_SINK_LATE_EXPECTED: f32 = 0.;
+const LATE_SPEEDUP_INTENSITY_INV: f32 = 2048.0;
+const SPEEDUP_SKIP_LIMIT: f32 = 1.008;
+
+const TIME_RING_BUFFER_SIZE: usize = (SAMPLE_RATE as usize / BUFFER_SIZE) * 10;
+struct SpeedFinder {
+ buf: [SystemTime; TIME_RING_BUFFER_SIZE],
+ i: usize,
+ has_circled: bool,
+}
+
+impl SpeedFinder {
+ fn new() -> Self {
+ Self {
+ buf: [SystemTime::now(); TIME_RING_BUFFER_SIZE],
+ i: 0,
+ has_circled: false,
+ }
+ }
+
+ fn tick(&mut self) -> Option<f32> {
+ if self.i >= TIME_RING_BUFFER_SIZE {
+ self.i = 0;
+ self.has_circled = true;
+ }
-const BUFFER_SIZE: usize = 1024;
+ let previous = self.buf[self.i];
+ let now = SystemTime::now();
+
+ self.buf[self.i] = now;
+ self.i += 1;
+ if !self.has_circled {
+ if self.i == 1 {
+ return None;
+ } else {
+ return Some(now.duration_since(self.buf[0]).unwrap().as_secs_f32() / (self.i - 1) as f32);
+ }
+ } else {
+ return Some(now.duration_since(previous).unwrap().as_secs_f32() / TIME_RING_BUFFER_SIZE as f32);
+ }
+ }
+}
pub struct RodioAudio {
- _stream: OutputStream,
sink: Sink,
+ stream: OutputStream,
+
+ speed_finder: SpeedFinder,
wave: RodioWave<MutableWave>,
buffer: Box<[f32; BUFFER_SIZE]>,
buffer_i: usize,
@@ -40,7 +88,7 @@ impl<I: Iterator<Item = f32>> Iterator for RodioBuffer<I> {
}
impl<I: Iterator<Item = f32>> Source for RodioBuffer<I> {
- fn current_frame_len(&self) -> Option<usize> {
+ fn current_span_len(&self) -> Option<usize> {
None
}
@@ -59,17 +107,18 @@ impl<I: Iterator<Item = f32>> Source for RodioBuffer<I> {
impl Audio for RodioAudio {
fn new(wave: MutableWave) -> Self {
- let (stream, stream_handle) = OutputStream::try_default().unwrap();
+ let stream = OutputStreamBuilder::from_default_device().unwrap().with_sample_rate(SAMPLE_RATE).with_buffer_size(BufferSize::Fixed(RODIO_BUFFER_SIZE as u32)).open_stream().unwrap();
- let sink = Sink::try_new(&stream_handle).unwrap();
+ let sink = Sink::connect_new(stream.mixer());
let wave = RodioWave(wave, 0);
RodioAudio {
- _stream: stream,
+ speed_finder: SpeedFinder::new(),
sink: sink,
wave,
buffer: Box::new([0.0; BUFFER_SIZE]),
buffer_i: 0,
+ stream,
}
}
@@ -82,6 +131,26 @@ impl Audio for RodioAudio {
self.buffer_i = 0;
let mut buffer = Box::new([0.0; BUFFER_SIZE]);
mem::swap(&mut self.buffer, &mut buffer);
+ if let Some(speed) = self.speed_finder.tick() {
+ let mut late_speedup: f32;
+ let rodio_buffers_sink_late = self.sink.len() as f32 / (RODIO_BUFFER_SIZE / BUFFER_SIZE) as f32;
+ late_speedup = ((rodio_buffers_sink_late - RODIO_BUFFER_SINK_LATE_EXPECTED).powi(3) / LATE_SPEEDUP_INTENSITY_INV) + 1.;
+
+ if late_speedup > SPEEDUP_SKIP_LIMIT {
+ while late_speedup > 1.0 {
+ let rodio_buffers_sink_late = self.sink.len() as f32 / (RODIO_BUFFER_SIZE / BUFFER_SIZE) as f32;
+ late_speedup = ((rodio_buffers_sink_late - RODIO_BUFFER_SINK_LATE_EXPECTED).powi(3) / LATE_SPEEDUP_INTENSITY_INV) + 1.;
+
+ self.sink.skip_one();
+ }
+ late_speedup = 1.;
+ }
+ let average_speed = (1./speed) / (2 * SAMPLE_RATE / BUFFER_SIZE as u32) as f32;
+ let rodio_buffers_sink_late = self.sink.len() as f32 / (RODIO_BUFFER_SIZE / BUFFER_SIZE) as f32;
+ log(LogLevel::AudioLatency, format!("audio sink latency: {}ms", (1000. * rodio_buffers_sink_late / ((2*SAMPLE_RATE) as f32 / RODIO_BUFFER_SIZE as f32))));
+
+ self.sink.set_speed(late_speedup * average_speed);
+ }
self.sink.append(RodioBuffer(buffer.into_iter()));
}
}