fix warns, simplify, begin watcher
Some checks failed
/ build (push) Has been cancelled

This commit is contained in:
i do not exist 2026-07-30 16:38:45 +03:00
parent b4424fcb69
commit ba8ff1ae69
23 changed files with 1737 additions and 1689 deletions

View file

@ -1,5 +1,5 @@
use crate::{*, device::*, browse::*, mix::*};
#![allow(unused)]
use crate::*;
pub(crate) use symphonia::{
default::get_codecs,
core::{//errors::Error as SymphoniaError,
@ -8,11 +8,6 @@ pub(crate) use symphonia::{
},
};
mod voice; pub use self::voice::*;
mod sample; pub use self::sample::*;
mod sample_add; pub use self::sample_add::*;
mod sample_kit; pub use self::sample_kit::*;
/// Plays [Voice]s from [Sample]s.
///
/// ```
@ -355,10 +350,6 @@ fn draw_sample (
Ok(label1.len() + label2.len() + 4)
}
fn read_sample_data (_: &str) -> Usually<(usize, Vec<Vec<f32>>)> {
todo!();
}
def_command!(SamplerCommand: |sampler: Sampler| {
RecordToggle { slot: usize } => {
let slot = *slot;
@ -405,3 +396,324 @@ def_command!(SamplerCommand: |sampler: Sampler| {
//Ok(None)
},
});
/// A currently playing instance of a sample.
#[derive(Default, Debug, Clone)] pub struct Voice {
pub sample: Arc<RwLock<Sample>>,
pub after: usize,
pub position: usize,
pub velocity: f32,
}
impl Iterator for Voice {
type Item = [f32;2];
fn next (&mut self) -> Option<Self::Item> {
if self.after > 0 {
self.after -= 1;
return Some([0.0, 0.0])
}
let sample = self.sample.read().unwrap();
if self.position < sample.end {
let position = self.position;
self.position += 1;
return sample.channels[0].get(position).map(|_amplitude|[
sample.channels[0][position] * self.velocity * sample.gain,
sample.channels[0][position] * self.velocity * sample.gain,
])
}
None
}
}
/// Collection of samples, one per slot, fixed number of slots.
///
/// History: Separated to cleanly implement [Default].
///
/// ```
/// let samples = tek::SampleKit([None, None, None, None]);
/// ```
#[derive(Debug)] pub struct SampleKit <const N: usize> (
pub [Option<Arc<RwLock<Sample>>>;N]
);
impl<const N: usize> Default for SampleKit<N> {
fn default () -> Self { Self([const { None }; N]) }
}
impl<const N: usize> SampleKit<N> {
pub fn get (&self, index: usize) -> &Option<Arc<RwLock<Sample>>> {
if index < self.0.len() {
&self.0[index]
} else {
&None
}
}
}
/// A sound cut.
///
/// ```
/// let sample = tek::Sample::default();
/// let sample = tek::Sample::new("test", 0, 0, vec![]);
/// ```
#[derive(Default, Debug)] pub struct Sample {
pub name: Arc<str>,
pub start: usize,
pub end: usize,
pub channels: Vec<Vec<f32>>,
pub rate: Option<usize>,
pub gain: f32,
pub color: ItemTheme,
}
impl Sample {
pub fn new (name: impl AsRef<str>, start: usize, end: usize, channels: Vec<Vec<f32>>) -> Self {
Self {
name: name.as_ref().into(),
start,
end,
channels,
rate: None,
gain: 1.0,
color: ItemTheme::random(),
}
}
pub fn play (sample: &Arc<RwLock<Self>>, after: usize, velocity: &u7) -> Voice {
Voice {
sample: sample.clone(),
after,
position: sample.read().unwrap().start,
velocity: velocity.as_int() as f32 / 127.0,
}
}
pub fn handle_cc (&mut self, controller: u7, value: u7) {
let percentage = value.as_int() as f64 / 127.;
match controller.as_int() {
20 => {
self.start = (percentage * self.end as f64) as usize;
},
21 => {
let length = self.channels[0].len();
self.end = length.min(
self.start + (percentage * (length as f64 - self.start as f64)) as usize
);
},
22 => { /*attack*/ },
23 => { /*decay*/ },
24 => {
self.gain = percentage as f32 * 2.0;
},
26 => { /* pan */ }
25 => { /* pitch */ }
_ => {}
}
}
/// Read WAV from file
pub fn read_data (src: &str) -> Usually<(usize, Vec<Vec<f32>>)> {
let mut channels: Vec<wavers::Samples<f32>> = vec![];
for channel in wavers::Wav::from_path(src)?.channels() {
channels.push(channel);
}
let mut end = 0;
let mut data: Vec<Vec<f32>> = vec![];
for samples in channels.iter() {
let channel = Vec::from(samples.as_ref());
end = end.max(channel.len());
data.push(channel);
}
Ok((end, data))
}
pub fn from_file (path: &PathBuf) -> Usually<Self> {
let name = path.file_name().unwrap().to_string_lossy().into();
let mut sample = Self { name, ..Default::default() };
// Use file extension if present
let mut hint = Hint::new();
if let Some(ext) = path.extension() {
hint.with_extension(&ext.to_string_lossy());
}
let probed = symphonia::default::get_probe().format(
&hint,
MediaSourceStream::new(
Box::new(File::open(path)?),
Default::default(),
),
&Default::default(),
&Default::default()
)?;
let mut format = probed.format;
let params = &format.tracks().iter()
.find(|t| t.codec_params.codec != CODEC_TYPE_NULL)
.expect("no tracks found")
.codec_params;
let mut decoder = get_codecs().make(params, &Default::default())?;
loop {
match format.next_packet() {
Ok(packet) => sample.decode_packet(&mut decoder, packet)?,
Err(symphonia::core::errors::Error::IoError(_)) => break decoder.last_decoded(),
Err(err) => return Err(err.into()),
};
};
sample.end = sample.channels.iter().fold(0, |l, c|l + c.len());
Ok(sample)
}
fn decode_packet (
&mut self, decoder: &mut Box<dyn Decoder>, packet: Packet
) -> Usually<()> {
// Decode a packet
let decoded = decoder
.decode(&packet)
.map_err(|e|Box::<dyn std::error::Error>::from(e))?;
// Determine sample rate
let spec = *decoded.spec();
if let Some(rate) = self.rate {
if rate != spec.rate as usize {
panic!("sample rate changed");
}
} else {
self.rate = Some(spec.rate as usize);
}
// Determine channel count
while self.channels.len() < spec.channels.count() {
self.channels.push(vec![]);
}
// Load sample
let mut samples = SampleBuffer::new(
decoded.frames() as u64,
spec
);
if samples.capacity() > 0 {
samples.copy_interleaved_ref(decoded);
for frame in samples.samples().chunks(spec.channels.count()) {
for (chan, frame) in frame.iter().enumerate() {
self.channels[chan].push(*frame)
}
}
}
Ok(())
}
}
#[derive(Default, Debug)] pub struct SampleAdd {
pub exited: bool,
pub dir: PathBuf,
pub subdirs: Vec<OsString>,
pub files: Vec<OsString>,
pub cursor: usize,
pub offset: usize,
pub sample: Arc<RwLock<Sample>>,
pub voices: Arc<RwLock<Vec<Voice>>>,
pub _search: Option<String>,
}
impl_draw!(|self: SampleAdd, to: Tui|{ todo!() });
impl SampleAdd {
fn exited (&self) -> bool {
self.exited
}
fn exit (&mut self) {
self.exited = true
}
pub fn new (
sample: &Arc<RwLock<Sample>>,
voices: &Arc<RwLock<Vec<Voice>>>
) -> Usually<Self> {
let dir = std::env::current_dir()?;
let (subdirs, files) = scan(&dir)?;
Ok(Self {
exited: false,
dir,
subdirs,
files,
cursor: 0,
offset: 0,
sample: sample.clone(),
voices: voices.clone(),
_search: None
})
}
fn rescan (&mut self) -> Usually<()> {
scan(&self.dir).map(|(subdirs, files)|{
self.subdirs = subdirs;
self.files = files;
})
}
fn prev (&mut self) {
self.cursor = self.cursor.saturating_sub(1);
}
fn next (&mut self) {
self.cursor = self.cursor + 1;
}
fn try_preview (&mut self) -> Usually<()> {
if let Some(path) = self.cursor_file() {
if let Ok(sample) = Sample::from_file(&path) {
*self.sample.write().unwrap() = sample;
self.voices.write().unwrap().push(
Sample::play(&self.sample, 0, &u7::from(100u8))
);
}
//load_sample(&path)?;
//let src = std::fs::File::open(&path)?;
//let mss = MediaSourceStream::new(Box::new(src), Default::default());
//let mut hint = Hint::new();
//if let Some(ext) = path.extension() {
//hint.with_extension(&ext.to_string_lossy());
//}
//let meta_opts: MetadataOptions = Default::default();
//let fmt_opts: FormatOptions = Default::default();
//if let Ok(mut probed) = symphonia::default::get_probe()
//.format(&hint, mss, &fmt_opts, &meta_opts)
//{
//panic!("{:?}", probed.format.metadata());
//};
}
Ok(())
}
fn cursor_dir (&self) -> Option<PathBuf> {
if self.cursor < self.subdirs.len() {
Some(self.dir.join(&self.subdirs[self.cursor]))
} else {
None
}
}
fn cursor_file (&self) -> Option<PathBuf> {
if self.cursor < self.subdirs.len() {
return None
}
let index = self.cursor.saturating_sub(self.subdirs.len());
if index < self.files.len() {
Some(self.dir.join(&self.files[index]))
} else {
None
}
}
fn pick (&mut self) -> Usually<bool> {
if self.cursor == 0 {
if let Some(parent) = self.dir.parent() {
self.dir = parent.into();
self.rescan()?;
self.cursor = 0;
return Ok(false)
}
}
if let Some(dir) = self.cursor_dir() {
self.dir = dir;
self.rescan()?;
self.cursor = 0;
return Ok(false)
}
if let Some(path) = self.cursor_file() {
let (end, channels) = read_sample_data(&path.to_string_lossy())?;
let mut sample = self.sample.write().unwrap();
sample.name = path.file_name().unwrap().to_string_lossy().into();
sample.end = end;
sample.channels = channels;
return Ok(true)
}
return Ok(false)
}
}
fn read_sample_data (_: &str) -> Usually<(usize, Vec<Vec<f32>>)> {
todo!();
}