forked from Keelrleek/SoulStream
dj sets + lyrics: a mix that names its own tracks, and words pulled off the separated voice
This commit is contained in:
1 parent
4efe85b6bf
commit
1dd8573808
41 files changed
+9288
-647
No files matched your search
@@ -10,10 +10,11 @@ use std::path::Path;
|
||||
|
||||
use symphonia::core::audio::SampleBuffer;
|
||||
use symphonia::core::codecs::DecoderOptions;
|
||||
use symphonia::core::formats::FormatOptions;
|
||||
use symphonia::core::formats::{FormatOptions, SeekMode, SeekTo};
|
||||
use symphonia::core::io::MediaSourceStream;
|
||||
use symphonia::core::meta::MetadataOptions;
|
||||
use symphonia::core::probe::Hint;
|
||||
use symphonia::core::units::Time;
|
||||
|
||||
/// Peaks folded over this many frames before resampling to the final bucket
|
||||
/// count. Keeps memory bounded regardless of track length.
|
||||
@@ -31,6 +32,25 @@ pub fn compute_waveform(path: &Path, buckets: usize) -> Vec<f32> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Like [`compute_waveform`], but for one SLICE of the file — the setlist
|
||||
/// segment rows' lane. `end_secs: None` runs to the file's end. The decode
|
||||
/// seeks coarsely and then trims by packet timestamps, so a 4-minute slice
|
||||
/// of a 90-minute set costs a 4-minute decode, not a 90-minute one.
|
||||
pub fn compute_waveform_range(
|
||||
path: &Path,
|
||||
buckets: usize,
|
||||
start_secs: f64,
|
||||
end_secs: Option<f64>,
|
||||
) -> Vec<f32> {
|
||||
match coarse_rms_range(path, start_secs, end_secs).map(|c| resample(&c, buckets)) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
log::debug!("range waveform for {} failed: {e}", path.display());
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn try_compute(path: &Path, buckets: usize) -> Result<Vec<f32>, String> {
|
||||
Ok(resample(&coarse_rms(path)?, buckets))
|
||||
}
|
||||
@@ -41,6 +61,14 @@ fn try_compute(path: &Path, buckets: usize) -> Result<Vec<f32>, String> {
|
||||
/// against a real decode without re-reading the file per variant — see
|
||||
/// `bucketing_strategies_on_a_real_track`.
|
||||
fn coarse_rms(path: &Path) -> Result<Vec<f32>, String> {
|
||||
coarse_rms_range(path, 0.0, None)
|
||||
}
|
||||
|
||||
fn coarse_rms_range(
|
||||
path: &Path,
|
||||
start_secs: f64,
|
||||
end_secs: Option<f64>,
|
||||
) -> Result<Vec<f32>, String> {
|
||||
let file = std::fs::File::open(path).map_err(|e| e.to_string())?;
|
||||
let mss = MediaSourceStream::new(Box::new(file), Default::default());
|
||||
|
||||
@@ -73,14 +101,34 @@ fn coarse_rms(path: &Path) -> Result<Vec<f32>, String> {
|
||||
.default_track()
|
||||
.ok_or_else(|| "no default track".to_string())?;
|
||||
let track_id = track.id;
|
||||
let time_base = track.codec_params.time_base;
|
||||
let sample_rate = track.codec_params.sample_rate;
|
||||
let mut decoder = symphonia::default::get_codecs()
|
||||
.make(&track.codec_params, &DecoderOptions::default())
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
if start_secs > 0.0 {
|
||||
// A coarse seek lands at a sync point near (usually before) the
|
||||
// target; the timestamp gate in the loop trims the difference. A
|
||||
// format that cannot seek just decodes from the top — slower, same
|
||||
// result.
|
||||
let _ = format.seek(
|
||||
SeekMode::Coarse,
|
||||
SeekTo::Time {
|
||||
time: Time::from(start_secs),
|
||||
track_id: Some(track_id),
|
||||
},
|
||||
);
|
||||
decoder.reset();
|
||||
}
|
||||
|
||||
let mut sample_buf: Option<SampleBuffer<f32>> = None;
|
||||
let mut coarse: Vec<f32> = Vec::new();
|
||||
let mut win_energy = 0f64;
|
||||
let mut win_count = 0usize;
|
||||
// When the container carries no timestamps, the slice is bounded by
|
||||
// counted frames from wherever the seek landed instead.
|
||||
let mut frames_seen: u64 = 0;
|
||||
|
||||
loop {
|
||||
let packet = match format.next_packet() {
|
||||
@@ -90,6 +138,22 @@ fn coarse_rms(path: &Path) -> Result<Vec<f32>, String> {
|
||||
if packet.track_id() != track_id {
|
||||
continue;
|
||||
}
|
||||
// Where this packet sits in the file, when the format can say.
|
||||
let packet_secs = time_base.map(|tb| {
|
||||
let t = tb.calc_time(packet.ts());
|
||||
t.seconds as f64 + t.frac
|
||||
});
|
||||
if let Some(t) = packet_secs {
|
||||
if t < start_secs {
|
||||
// Before the slice: decode and discard, keeping codec state
|
||||
// warm across the boundary.
|
||||
let _ = decoder.decode(&packet);
|
||||
continue;
|
||||
}
|
||||
if end_secs.is_some_and(|end| t >= end) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let decoded = match decoder.decode(&packet) {
|
||||
Ok(d) => d,
|
||||
Err(symphonia::core::errors::Error::DecodeError(_)) => continue,
|
||||
@@ -115,6 +179,14 @@ fn coarse_rms(path: &Path) -> Result<Vec<f32>, String> {
|
||||
win_count = 0;
|
||||
}
|
||||
}
|
||||
if packet_secs.is_none() {
|
||||
frames_seen += (sb.samples().len() / channels) as u64;
|
||||
if let (Some(end), Some(rate)) = (end_secs, sample_rate) {
|
||||
if frames_seen as f64 / rate as f64 >= end - start_secs {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if win_count > 0 {
|
||||
coarse.push((win_energy / win_count as f64).sqrt() as f32);
|
||||
|
||||
Reference in new issue
Block a user