tek/src/device/sampler.rs
i do not exist e6984915e4
Some checks are pending
/ build (push) Waiting to run
bizzle
2026-08-19 18:21:42 +03:00

843 lines
28 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#![allow(unused)]
use crate::*;
pub(crate) use symphonia::{
default::get_codecs,
core::{//errors::Error as SymphoniaError,
audio::SampleBuffer, formats::Packet, io::MediaSourceStream, probe::Hint,
codecs::{Decoder, CODEC_TYPE_NULL},
},
};
pub trait HasSampler: AsRef<Sampler> + AsMut<Sampler> {
fn sampler (&self) -> &Sampler {
self.as_ref()
}
fn sampler_mut (&mut self) -> &mut Sampler {
self.as_mut()
}
}
impl<T: AsRef<Sampler> + AsMut<Sampler>> HasSampler for T {}
#[tek_proc::commands(SamplerCommand = "sampler")]
pub trait SamplerController: HasSampler
+ Namespace<usize>
{
#[command(RecordToggle = "rec-toggle")]
fn record_toggle (&mut self, slot: usize) -> Perhaps<SamplerCommand>
where Self: Sized
{
let sampler = self.sampler_mut();
let recording = sampler.recording.as_ref().map(|x|x.0);
let _ = SamplerCommand::RecordFinish.dispatch(self)?;
// autoslice: continue recording at next slot
if recording != Some(slot) {
SamplerCommand::RecordBegin { slot }.dispatch(self)
} else {
Ok(None)
}
}
#[command(RecordBegin = "rec-begin")]
fn record_begin (&mut self, slot: usize) -> Perhaps<SamplerCommand> {
let sampler = self.sampler_mut();
sampler.recording = Some((
slot,
Some(Arc::new(RwLock::new(Sample::new(
"Sample", 0, 0, vec![vec![]; sampler.audio_ins.len()]
))))
));
Ok(None)
}
#[command(RecordFinish = "rec-finish")]
fn record_finish (&mut self) -> Perhaps<SamplerCommand> {
let sampler = self.sampler_mut();
let _prev_sample = sampler.recording.as_mut().map(|(index, sample)|{
std::mem::swap(sample, &mut sampler.samples.0[*index]);
sample
}); // TODO: undo
Ok(None)
}
#[command(RecordCancel = "rec-cancel")]
fn record_cancel (&mut self) -> Perhaps<SamplerCommand> {
self.sampler_mut().recording = None;
Ok(None)
}
#[command(PlaySample = "sample-play")]
fn sample_play (&mut self, slot: usize) -> Perhaps<SamplerCommand> {
let sampler = self.sampler_mut();
if let Some(ref sample) = sampler.samples.0[slot] {
sampler.voices.write().unwrap().push(Sample::play(sample, 0, &u7::from(128)));
}
Ok(None)
}
#[command(StopSample = "sample-stop")]
fn sample_stop (&mut self, slot: usize) -> Perhaps<SamplerCommand> {
todo!();
//Ok(None)
}
}
/// Plays [Voice]s from [Sample]s.
///
/// ```
/// let sampler = tek::Sampler::default();
/// ```
#[derive(Debug, Default)] pub struct Sampler {
/// Name of sampler.
pub name: Arc<str>,
/// Device color.
pub color: ItemTheme,
/// Sample currently being recorded.
pub recording: Option<(usize, Option<Arc<RwLock<Sample>>>)>,
/// Recording buffer.
pub buffer: Vec<Vec<f32>>,
/// Samples mapped to MIDI notes.
pub samples: SampleKit<128>,
/// Collection of currently playing instances of samples.
pub voices: Arc<RwLock<Vec<Voice>>>,
/// Samples that are not mapped to MIDI notes.
pub unmapped: Vec<Arc<RwLock<Sample>>>,
/// Sample currently being edited.
pub editing: Option<Arc<RwLock<Sample>>>,
/// How to mix the voices.
pub mixing_mode: MixingMode,
/// How to meter the inputs and outputs.
pub metering_mode: MeteringMode,
/// Fixed gain applied to all output.
pub output_gain: f32,
/// Currently active modal, if any.
pub mode: Option<SamplerMode>,
/// Size of rendered sampler.
pub size: Sizer,
/// Lowest note displayed.
pub note_lo: AtomicUsize,
/// Currently selected note.
pub note_pt: AtomicUsize,
/// Selected note as row/col.
pub cursor: (AtomicUsize, AtomicUsize),
/// Audio input meters.
#[cfg(feature = "meter")] pub input_meters: Vec<f32>,
/// Audio input ports. Samples are recorded from here.
#[cfg(feature = "port")] pub audio_ins: Vec<AudioInput>,
/// MIDI input port. Sampler are triggered from here.
#[cfg(feature = "port")] pub midi_in: Option<MidiInput>,
/// Audio output ports. Voices are played into here.
#[cfg(feature = "port")] pub audio_outs: Vec<AudioOutput>,
/// Audio output meters.
#[cfg(feature = "meter")] pub output_meters: Vec<f32>,
}
impl_audio!(Sampler: sampler_jack_process);
pub(crate) fn sampler_jack_process (state: &mut Sampler, _: &Client, scope: &ProcessScope) -> Control {
if let Some(midi_in) = &state.midi_in {
for midi in midi_in.port().iter(scope) {
sampler_midi_in(&state.samples, &state.voices, midi)
}
}
state.process_audio_out(scope);
state.process_audio_in(scope);
Control::Continue
}
#[derive(Debug)] pub enum SamplerMode {
// Load sample from path
Import(usize, Browse),
}
pub type MidiSample =
(Option<u7>, Arc<RwLock<crate::Sample>>);
impl NoteRange for Sampler {
fn note_lo (&self) -> &AtomicUsize {
&self.note_lo
}
fn note_axis (&self) -> &AtomicUsize {
&self.size.1
}
}
impl NotePoint for Sampler {
fn note_len (&self) -> &AtomicUsize {
unreachable!();
}
fn get_note_len (&self) -> usize {
0
}
fn set_note_len (&self, _x: usize) -> usize {
0 /*TODO?*/
}
fn note_pos (&self) -> &AtomicUsize {
&self.note_pt
}
fn get_note_pos (&self) -> usize {
self.note_pt.load(Relaxed)
}
fn set_note_pos (&self, x: usize) -> usize {
let old = self.note_pt.swap(x, Relaxed);
self.cursor.0.store(x % 8, Relaxed);
self.cursor.1.store(x / 8, Relaxed);
old
}
}
impl Sampler {
pub fn new (
jack: &Jack<'static>,
name: impl AsRef<str>,
#[cfg(feature = "port")] midi_from: &[Connect],
#[cfg(feature = "port")] audio_from: &[&[Connect];2],
#[cfg(feature = "port")] audio_to: &[&[Connect];2],
) -> Usually<Self> {
let name = name.as_ref();
Ok(Self {
name: name.into(),
input_meters: vec![0.0;2],
output_meters: vec![0.0;2],
output_gain: 1.,
buffer: vec![vec![0.0;16384];2],
#[cfg(feature = "port")] midi_in: Some(
MidiInput::new(jack, &format!("M/{name}"), midi_from)?
),
#[cfg(feature = "port")] audio_ins: vec![
AudioInput::new(jack, &format!("L/{name}"), audio_from[0])?,
AudioInput::new(jack, &format!("R/{name}"), audio_from[1])?,
],
#[cfg(feature = "port")] audio_outs: vec![
AudioOutput::new(jack, &format!("{name}/L"), audio_to[0])?,
AudioOutput::new(jack, &format!("{name}/R"), audio_to[1])?,
],
..Default::default()
})
}
/// Value of cursor
pub fn cursor (&self) -> (usize, usize) {
(self.cursor.0.load(Relaxed), self.cursor.1.load(Relaxed))
}
fn sample_selected (&self) -> usize {
(self.get_note_pos() as u8).into()
}
fn sample_selected_pitch (&self) -> u7 {
(self.get_note_pos() as u8).into()
}
pub fn process_audio_in (&mut self, scope: &ProcessScope) {
self.reset_input_meters();
if self.recording.is_some() {
self.record_into(scope);
} else {
self.update_input_meters(scope);
}
}
/// Make sure that input meter count corresponds to input channel count
fn reset_input_meters (&mut self) {
let channels = self.audio_ins.len();
if self.input_meters.len() != channels {
self.input_meters = vec![f32::MIN;channels];
}
}
/// Record from inputs to sample
fn record_into (&mut self, scope: &ProcessScope) {
if let Some(ref sample) = self.recording.as_ref().expect("no recording sample").1 {
let mut sample = sample.write().unwrap();
if sample.channels.len() != self.audio_ins.len() {
panic!("channel count mismatch");
}
let samples_with_meters = self.audio_ins.iter()
.zip(self.input_meters.iter_mut())
.zip(sample.channels.iter_mut());
let mut length = 0;
for ((input, meter), channel) in samples_with_meters {
let slice = input.port().as_slice(scope);
length = length.max(slice.len());
*meter = to_rms(slice);
channel.extend_from_slice(slice);
}
sample.end += length;
} else {
panic!("tried to record into the void")
}
}
/// Update input meters
fn update_input_meters (&mut self, scope: &ProcessScope) {
for (input, meter) in self.audio_ins.iter().zip(self.input_meters.iter_mut()) {
let slice = input.port().as_slice(scope);
*meter = to_rms(slice);
}
}
/// Make sure that output meter count corresponds to input channel count
fn reset_output_meters (&mut self) {
let channels = self.audio_outs.len();
if self.output_meters.len() != channels {
self.output_meters = vec![f32::MIN;channels];
}
}
/// Mix all currently playing samples into the output.
pub fn process_audio_out (&mut self, scope: &ProcessScope) {
self.clear_output_buffer();
self.populate_output_buffer(scope.n_frames() as usize);
self.write_output_buffer(scope);
}
/// Zero the output buffer.
fn clear_output_buffer (&mut self) {
for buffer in self.buffer.iter_mut() {
buffer.fill(0.0);
}
}
/// Write playing voices to output buffer
fn populate_output_buffer (&mut self, frames: usize) {
let Sampler { buffer, voices, output_gain, mixing_mode, .. } = self;
let _channel_count = buffer.len();
match mixing_mode {
MixingMode::Summing => voices.write().unwrap().retain_mut(|voice|{
mix_summing(buffer.as_mut_slice(), *output_gain, frames, ||voice.next())
}),
MixingMode::Average => voices.write().unwrap().retain_mut(|voice|{
mix_average(buffer.as_mut_slice(), *output_gain, frames, ||voice.next())
}),
}
}
/// Write output buffer to output ports.
fn write_output_buffer (&mut self, scope: &ProcessScope) {
let Sampler { audio_outs, buffer, .. } = self;
for (i, port) in audio_outs.iter_mut().enumerate() {
let buffer = &buffer[i];
for (i, value) in port.port_mut().as_mut_slice(scope).iter_mut().enumerate() {
*value = *buffer.get(i).unwrap_or(&0.0);
}
}
}
}
fn draw_list_item (sample: &Option<Arc<RwLock<Sample>>>) -> String {
if let Some(sample) = sample {
let sample = sample.read().unwrap();
format!("{:8}", sample.name)
//format!("{:8} {:3} {:6}-{:6}/{:6}",
//sample.name,
//sample.gain,
//sample.start,
//sample.end,
//sample.channels[0].len()
//)
} else {
String::from("........")
}
}
fn draw_viewer (sample: Option<&Arc<RwLock<Sample>>>) -> impl Draw<'_, Tui> + use<'_> {
let min_db = -64.0;
draw(move|to: &mut Tui|{
let xywh = to.area().into();
let XYWH(x, y, width, height) = xywh;
let area = Rect { x, y, width, height };
if let Some(sample) = &sample {
let sample = sample.read().unwrap();
let start = sample.start as f64;
let end = sample.end as f64;
let length = end - start;
let step = length / width as f64;
let mut t = start;
let mut lines = vec![];
while t < end {
let chunk = &sample.channels[0][t as usize..((t + step) as usize).min(sample.end)];
let total: f32 = chunk.iter().map(|x|x.abs()).sum();
let count = chunk.len() as f32;
let meter = 10. * (total / count).log10();
let x = t as f64;
let y = meter as f64;
lines.push(Line::new(x, min_db, x, y, Color::Green));
t += step / 2.;
}
if let Tui::Draw(buf, ..) = to {
Canvas::default()
.x_bounds([sample.start as f64, sample.end as f64])
.y_bounds([min_db, 0.])
.paint(|ctx| {
for line in lines.iter() {
ctx.draw(line);
}
//FIXME: proportions
//let text = "press record to finish sampling";
//ctx.print(
//(width - text.len() as u16) as f64 / 2.0,
//height as f64 / 2.0,
//text.red()
//);
})
.render(area, buf);
}
} else {
if let Tui::Draw(buf, ..) = to {
Canvas::default()
.x_bounds([0.0, width as f64])
.y_bounds([0.0, height as f64])
.paint(|_ctx| {
//let text = "press record to begin sampling";
//ctx.print(
//(width - text.len() as u16) as f64 / 2.0,
//height as f64 / 2.0,
//text.red()
//);
})
.render(area, buf);
}
}
Ok(Some(xywh))
})
}
/// Create [Voice]s from [Sample]s in response to MIDI input.
fn sampler_midi_in (
samples: &SampleKit<128>, voices: &Arc<RwLock<Vec<Voice>>>, RawMidi { time, bytes }: RawMidi
) {
if let Ok(LiveEvent::Midi { message, .. }) = LiveEvent::parse(bytes) {
match message {
MidiMessage::NoteOn { ref key, ref vel } => {
if let Some(sample) = samples.get(key.as_int() as usize) {
voices.write().unwrap().push(Sample::play(sample, time as usize, vel));
}
},
MidiMessage::Controller { controller: _, value: _ } => {
// TODO
}
_ => {}
}
}
}
fn draw_sample (
to: &mut Tui, x: u16, y: u16, note: Option<&u7>, sample: &Sample, focus: bool
) -> Usually<usize> {
let style = if focus { Style::default().green() } else { Style::default() };
if focus {
to.blit(&"🬴", x+1, y, Some(style.bold()));
}
let label1 = format!("{:3} {:12}",
note.map(|n|n.to_string()).unwrap_or(String::default()),
sample.name);
let label2 = format!("{:>6} {:>6} +0.0",
sample.start,
sample.end);
to.blit(&label1, x+2, y, Some(style.bold()));
to.blit(&label2, x+3+label1.len()as u16, y, Some(style));
Ok(label1.len() + label2.len() + 4)
}
/// 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!();
}
pub fn view_sample_info (sample: Option<&Arc<RwLock<Sample>>>) -> impl Draw<'_, Tui> + use<'_> {
when(sample.is_some(), draw(move|to: &mut Tui|{
let sample = sample.unwrap().read().unwrap();
let theme = sample.color;
east!(
field_h(theme, "Name", format!("{:<10}", sample.name.clone())),
field_h(theme, "Length", format!("{:<8}", sample.channels[0].len())),
field_h(theme, "Start", format!("{:<8}", sample.start)),
field_h(theme, "End", format!("{:<8}", sample.end)),
field_h(theme, "Trans", "0"),
field_h(theme, "Gain", format!("{}", sample.gain)),
).draw(to)
}))
}
pub fn view_sample_info_v (sample: Option<&Arc<RwLock<Sample>>>) -> impl Draw<'_, Tui> + use<'_> {
let a = draw(move|to: &mut Tui|{
let sample = sample.unwrap().read().unwrap();
let theme = sample.color;
south!(
field_h(theme, "Name ", format!("{:<10}", sample.name.clone())) .align_w().full_w(),
field_h(theme, "Length", format!("{:<8}", sample.channels[0].len())).align_w().full_w(),
field_h(theme, "Start ", format!("{:<8}", sample.start)) .align_w().full_w(),
field_h(theme, "End ", format!("{:<8}", sample.end)) .align_w().full_w(),
field_h(theme, "Trans ", "0") .align_w().full_w(),
field_h(theme, "Gain ", format!("{}", sample.gain)) .align_w().full_w(),
).exact_w(20).draw(to)
});
let b = draw(|to: &mut Tui|fg(Red, south!(
bold(true, "× No sample."),
"[r] record",
"[Shift-F9] import",
)).draw(to));
either(sample.is_some(), a, b)
}
pub fn view_sample_status (sample: Option<&Arc<RwLock<Sample>>>) -> impl Draw<'_, Tui> {
bold(true, fg(g(224), sample
.map(|sample|{
let sample = sample.read().unwrap();
format!("Sample {}-{}", sample.start, sample.end)
})
.unwrap_or_else(||"No sample".to_string())))
}
#[cfg(feature = "track")]
impl Track {
/// Create a new track connecting the [Sequencer] to a [Sampler].
pub fn new_with_sampler (
name: &impl AsRef<str>,
color: Option<ItemTheme>,
jack: &Jack<'static>,
clock: Option<&Clock>,
clip: Option<&Arc<RwLock<MidiClip>>>,
midi_from: &[Connect],
midi_to: &[Connect],
audio_from: &[&[Connect];2],
audio_to: &[&[Connect];2],
) -> Usually<Self> {
let mut track = Self::new(name, color, jack, clock, clip, midi_from, midi_to)?;
let client_name = jack.with_client(|c|c.name().to_string());
let port_name = track.sequencer.midi_outs[0].port_name();
let connect = [Connect::exact(format!("{client_name}:{}", port_name))];
track.devices.push(Device::Sampler(Sampler::new(
jack, &format!("{}/sampler", name.as_ref()), &connect, audio_from, audio_to
)?));
Ok(track)
}
pub fn sampler (&self, mut nth: usize) -> Option<&Sampler> {
for device in self.devices.iter() {
match device {
Device::Sampler(s) => if nth == 0 { return Some(s); } else { nth -= 1; },
_ => {}
}
}
None
}
pub fn sampler_mut (&mut self, mut nth: usize) -> Option<&mut Sampler> {
for device in self.devices.iter_mut() {
match device {
Device::Sampler(s) => if nth == 0 { return Some(s); } else { nth -= 1; },
_ => {}
}
}
None
}
}