Skip to content

Commit

Permalink
Add basic waveform view and playback
Browse files Browse the repository at this point in the history
  • Loading branch information
geom3trik committed Dec 26, 2023
1 parent 4632cb9 commit 1867474
Show file tree
Hide file tree
Showing 40 changed files with 840 additions and 106 deletions.
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ sha2 = "0.10.7"
base64ct = {version = "1.6.0", features = ["alloc"]}
itertools = "0.11.0"
fuzzy-matcher = "0.3.7"
hound = "3.5.1"
cpal = "0.15.2"
basedrop = { git = "https://github.com/glowcoil/basedrop.git" }
ringbuf = "0.3.3"

[profile.dev.package."*"]
opt-level = 3
2 changes: 2 additions & 0 deletions resources/themes/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,8 @@ wave-panel > .footer {
wave-panel .waveform {
background-color: #181818;
border-radius: 4px;
child-top: 20px;
child-bottom: 20px;
}

.resize_handle {
Expand Down
49 changes: 0 additions & 49 deletions src/app_data.rs

This file was deleted.

66 changes: 66 additions & 0 deletions src/engine/audio_data.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
use super::utils::deinterleave;
use hound::{SampleFormat, WavReader};

/// An audio file, loaded into memory.
pub struct AudioData {
/// The sample data.
pub data: Vec<f32>,
/// Sample rate of the audio file.
pub sample_rate: f64,
/// number of channels in the audio file.
pub num_channels: usize,
/// number of samples in the audio file.
pub num_samples: usize,
}

impl AudioData {
/// return a buffer of samples corresponding to a channel in the audio file
#[allow(dead_code)]
pub fn get_channel(&self, idx: usize) -> &'_ [f32] {
debug_assert!(idx < self.num_channels);
let start = self.num_samples * idx;
&self.data[start..(start + self.num_samples)]
}

/// open a file
pub fn open(path: &str) -> Result<Self, hound::Error> {
let mut reader = WavReader::open(path)?;
let spec = reader.spec();
let mut data = Vec::with_capacity((spec.channels as usize) * (reader.duration() as usize));
match (spec.bits_per_sample, spec.sample_format) {
(16, SampleFormat::Int) => {
for sample in reader.samples::<i16>() {
data.push((sample? as f32) / (0x7fffi32 as f32));
}
}
(24, SampleFormat::Int) => {
for sample in reader.samples::<i32>() {
let val = (sample? as f32) / (0x00ff_ffffi32 as f32);
data.push(val);
}
}
(32, SampleFormat::Int) => {
for sample in reader.samples::<i32>() {
data.push((sample? as f32) / (0x7fff_ffffi32 as f32));
}
}
(32, SampleFormat::Float) => {
for sample in reader.samples::<f32>() {
data.push(sample?);
}
}
_ => return Err(hound::Error::Unsupported),
}

let mut deinterleaved = vec![0.0; data.len()];
let num_channels = spec.channels as usize;
let num_samples = deinterleaved.len() / num_channels;
deinterleave(&data, &mut deinterleaved, num_channels);
Ok(Self {
data: deinterleaved,
sample_rate: spec.sample_rate as f64,
num_channels,
num_samples,
})
}
}
60 changes: 60 additions & 0 deletions src/engine/audio_stream.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
use crate::utils::interleave;
use cpal::traits::{DeviceTrait, HostTrait};
use cpal::Stream;

/// The playback context is used by the audio callback to map data from the audio
/// file to the playback buffer.
pub struct PlaybackContext<'a> {
pub buffer_size: usize,
pub sample_rate: f64,
pub num_channels: usize,
output_buffer: &'a mut [f32],
}

impl<'a> PlaybackContext<'a> {
/// Return a buffer of output samples corresponding to a channel index
pub fn get_output(&mut self, idx: usize) -> &'_ mut [f32] {
let offset = idx * self.buffer_size;
&mut self.output_buffer[offset..offset + self.buffer_size]
}
}

/// Start the audio stream
pub fn audio_stream(mut main_callback: impl FnMut(PlaybackContext) + Send + 'static) -> Stream {
let host = cpal::default_host();
let output_device = host.default_output_device().expect("no output found");
let config = output_device.default_output_config().expect("no default output config").config();

let sample_rate = config.sample_rate.0 as f64;
let num_channels = config.channels as usize;
let mut output_buffer = vec![];
let mut input_buffer = vec![];

output_buffer.resize_with(1 << 16, || 0.0);
input_buffer.resize_with(1 << 16, || 0.0);

let callback = move |data: &mut [f32], _: &cpal::OutputCallbackInfo| {
let buffer_size = data.len() / num_channels;
output_buffer.resize(data.len(), 0.0);
for sample in data.iter_mut() {
*sample = 0.0;
}
for sample in &mut output_buffer.iter_mut() {
*sample = 0.0;
}

let context = PlaybackContext {
buffer_size,
num_channels,
sample_rate,
output_buffer: &mut output_buffer,
};

main_callback(context);
interleave(&output_buffer, data, num_channels);
};

output_device
.build_output_stream(&config, callback, |err| eprintln!("{}", err), None)
.expect("failed to open stream")
}
14 changes: 14 additions & 0 deletions src/engine/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
pub mod audio_data;
pub use audio_data::*;

pub mod utils;
pub use utils::*;

pub mod audio_stream;
pub use audio_stream::*;

pub mod sample_player;
pub use sample_player::*;

pub mod waveform;
pub use waveform::*;
Loading

0 comments on commit 1867474

Please sign in to comment.