Files
soundcraft/crates/model/src/source.rs
T
Brandon ThomasandClaude Opus 5.5 cdefc57c91 Video track, Audio Unit hosting, plugin state and editors, Renderer and UI Customization
Video (new standalone crate soundcraft-video):
- Pure-Rust H.264 (Baseline/Main/High), ProRes (all flavours) and Motion JPEG decoding with
  ISO-BMFF demux, adapted from FilmCraft's crates; fuzzed with mutated/truncated movies
- TrackKind::Video with ClipContent::Video referencing Session.videos; file.import_video adds
  the picture track and the movie's audio; relinking on open; video.offset/online/list
- Video and Video Universe windows, thumbnails on the timeline, background decoding

Third-party plugins:
- Plugin state (CLAP state ext, VST3 component + controller state) saved in sessions
- Hosted instances created, state-loaded and dropped on the UI thread, never the audio thread
- Plugin editors: floating CLAP GUIs and VST3 views on macOS (verified with Melodyne)
- New isolated FFI crate soundcraft-au-host: Audio Units on macOS (effects and instruments,
  parameters, latency, MIDI, ClassInfo state), wired into the mixer and plugin browser

UI: Window > Renderer (speaker layout with live levels and object positions), Window > UI
Customization (interface scale, reduce motion), Window > Configurations > Auto-Update.

Fix: saturating sample-index math in clip rendering (hostile stretch values overflowed).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 01:41:05 -04:00

113 lines
3.4 KiB
Rust

//! Audio sources (files in the session's Audio Files folder) and their decoded data, and the
//! movies video clips reference.
use crate::SourceId;
use soundcraft_audio_io::{AudioBuffer, FileFormat, peaks::Peaks};
use std::collections::BTreeMap;
use std::sync::Arc;
/// Metadata for an audio file used by the session (serialised).
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct Source {
pub id: SourceId,
pub name: String,
/// Path relative to the session folder (or absolute for referenced media).
pub path: String,
pub channels: u16,
pub frames: u64,
pub sample_rate: u32,
pub format: FileFormat,
/// Original BWF time stamp (samples), if any.
#[serde(default)]
pub time_reference: u64,
/// True when the file has not been written to disk yet (recorded/rendered this session).
#[serde(default)]
pub unsaved: bool,
}
/// A movie used by Video track clips (serialised). The pictures themselves are never stored in
/// the session: the UI opens the movie from `path` and decodes frames on demand.
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct VideoSource {
pub id: SourceId,
pub name: String,
/// Absolute path of the movie (referenced, never copied into the session folder).
pub path: String,
#[serde(default)]
pub width: u32,
#[serde(default)]
pub height: u32,
/// Frames per second (0 = unknown).
#[serde(default)]
pub frame_rate: f64,
/// Picture length in seconds.
#[serde(default)]
pub duration: f64,
/// Codec id (`h264`, `prores`, `mjpeg`, `hevc`, …).
#[serde(default)]
pub codec: String,
}
impl VideoSource {
/// Display aspect ratio (width / height), 16:9 when unknown.
pub fn aspect(&self) -> f64 {
if self.width == 0 || self.height == 0 { 16.0 / 9.0 } else { f64::from(self.width) / f64::from(self.height) }
}
}
/// Decoded audio plus waveform overviews (not serialised).
#[derive(Debug)]
pub struct SourceAudio {
pub buffer: AudioBuffer,
pub peaks: Vec<Peaks>,
}
impl SourceAudio {
pub fn new(buffer: AudioBuffer) -> Self {
let peaks = buffer.channels.iter().map(|c| Peaks::build(c)).collect();
SourceAudio { buffer, peaks }
}
pub fn frames(&self) -> usize {
self.buffer.frames()
}
pub fn channel(&self, ch: usize) -> Option<&[f32]> {
self.buffer.channels.get(ch).map(Vec::as_slice)
}
}
/// Shared decoded audio, keyed by source id.
#[derive(Debug, Clone, Default)]
pub struct SourcePool {
map: BTreeMap<SourceId, Arc<SourceAudio>>,
}
impl SourcePool {
pub fn insert(&mut self, id: SourceId, audio: Arc<SourceAudio>) {
self.map.insert(id, audio);
}
pub fn get(&self, id: SourceId) -> Option<&Arc<SourceAudio>> {
self.map.get(&id)
}
pub fn remove(&mut self, id: SourceId) -> Option<Arc<SourceAudio>> {
self.map.remove(&id)
}
pub fn contains(&self, id: SourceId) -> bool {
self.map.contains_key(&id)
}
pub fn len(&self) -> usize {
self.map.len()
}
pub fn is_empty(&self) -> bool {
self.map.is_empty()
}
pub fn ids(&self) -> impl Iterator<Item = SourceId> + '_ {
self.map.keys().copied()
}
}
impl PartialEq for SourcePool {
fn eq(&self, other: &Self) -> bool {
self.map.len() == other.map.len() && self.map.iter().zip(other.map.iter()).all(|((a, x), (b, y))| a == b && Arc::ptr_eq(x, y))
}
}