mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
extract edn; build out more groovebox
This commit is contained in:
parent
950dbdfe8e
commit
8cf42aff0b
10 changed files with 231 additions and 205 deletions
|
|
@ -1,18 +1,5 @@
|
|||
use crate::*;
|
||||
|
||||
pub struct SamplerAudio {
|
||||
model: Arc<RwLock<Sampler>>
|
||||
}
|
||||
|
||||
/// 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,
|
||||
}
|
||||
|
||||
/// The sampler plugin plays sounds.
|
||||
#[derive(Debug)]
|
||||
pub struct Sampler {
|
||||
|
|
@ -37,6 +24,15 @@ pub struct Sample {
|
|||
pub rate: Option<usize>,
|
||||
}
|
||||
|
||||
/// 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,
|
||||
}
|
||||
|
||||
/// Load sample from WAV and assign to MIDI note.
|
||||
#[macro_export] macro_rules! sample {
|
||||
($note:expr, $name:expr, $src:expr) => {{
|
||||
|
|
@ -49,66 +45,10 @@ pub struct Sample {
|
|||
}
|
||||
|
||||
impl Sampler {
|
||||
pub fn from_edn <'e> (jack: &Arc<RwLock<JackClient>>, args: &[Edn<'e>]) -> Usually<Self> {
|
||||
let mut name = String::new();
|
||||
let mut dir = String::new();
|
||||
let mut samples = BTreeMap::new();
|
||||
edn!(edn in args {
|
||||
Edn::Map(map) => {
|
||||
if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) {
|
||||
name = String::from(*n);
|
||||
}
|
||||
if let Some(Edn::Str(n)) = map.get(&Edn::Key(":dir")) {
|
||||
dir = String::from(*n);
|
||||
}
|
||||
},
|
||||
Edn::List(args) => match args.get(0) {
|
||||
Some(Edn::Symbol("sample")) => {
|
||||
let (midi, sample) = Sample::from_edn(jack, &dir, &args[1..])?;
|
||||
if let Some(midi) = midi {
|
||||
samples.insert(midi, sample);
|
||||
} else {
|
||||
panic!("sample without midi binding: {}", sample.read().unwrap().name);
|
||||
}
|
||||
},
|
||||
_ => panic!("unexpected in sampler {name}: {args:?}")
|
||||
},
|
||||
_ => panic!("unexpected in sampler {name}: {edn:?}")
|
||||
});
|
||||
let midi_in = jack.read().unwrap().client().register_port("in", MidiIn::default())?;
|
||||
Ok(Sampler {
|
||||
jack: jack.clone(),
|
||||
name: name.into(),
|
||||
mapped: samples,
|
||||
unmapped: Default::default(),
|
||||
voices: Default::default(),
|
||||
buffer: Default::default(),
|
||||
midi_in: midi_in,
|
||||
audio_outs: vec![],
|
||||
output_gain: 0.
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Arc<RwLock<Sampler>>> for SamplerAudio {
|
||||
fn from (model: &Arc<RwLock<Sampler>>) -> Self {
|
||||
Self { model: model.clone() }
|
||||
}
|
||||
}
|
||||
|
||||
audio!(|self: SamplerAudio, _client, scope|{
|
||||
self.process_midi_in(scope);
|
||||
self.clear_output_buffer();
|
||||
self.process_audio_out(scope);
|
||||
self.write_output_buffer(scope);
|
||||
Control::Continue
|
||||
});
|
||||
|
||||
impl SamplerAudio {
|
||||
|
||||
/// Create [Voice]s from [Sample]s in response to MIDI input.
|
||||
pub fn process_midi_in (&mut self, scope: &ProcessScope) {
|
||||
let Sampler { midi_in, mapped, voices, .. } = &*self.model.read().unwrap();
|
||||
let Sampler { midi_in, mapped, voices, .. } = self;
|
||||
for RawMidi { time, bytes } in midi_in.iter(scope) {
|
||||
if let LiveEvent::Midi { message, .. } = LiveEvent::parse(bytes).unwrap() {
|
||||
if let MidiMessage::NoteOn { ref key, ref vel } = message {
|
||||
|
|
@ -122,14 +62,14 @@ impl SamplerAudio {
|
|||
|
||||
/// Zero the output buffer.
|
||||
pub fn clear_output_buffer (&mut self) {
|
||||
for buffer in self.model.write().unwrap().buffer.iter_mut() {
|
||||
for buffer in self.buffer.iter_mut() {
|
||||
buffer.fill(0.0);
|
||||
}
|
||||
}
|
||||
|
||||
/// Mix all currently playing samples into the output.
|
||||
pub fn process_audio_out (&mut self, scope: &ProcessScope) {
|
||||
let Sampler { ref mut buffer, voices, output_gain, .. } = &mut*self.model.write().unwrap();
|
||||
let Sampler { ref mut buffer, voices, output_gain, .. } = self;
|
||||
let channel_count = buffer.len();
|
||||
voices.write().unwrap().retain_mut(|voice|{
|
||||
for index in 0..scope.n_frames() as usize {
|
||||
|
|
@ -151,7 +91,7 @@ impl SamplerAudio {
|
|||
|
||||
/// Write output buffer to output ports.
|
||||
pub fn write_output_buffer (&mut self, scope: &ProcessScope) {
|
||||
let Sampler { ref mut audio_outs, buffer, .. } = &mut*self.model.write().unwrap();
|
||||
let Sampler { ref mut audio_outs, buffer, .. } = self;
|
||||
for (i, port) in audio_outs.iter_mut().enumerate() {
|
||||
let buffer = &buffer[i];
|
||||
for (i, value) in port.as_mut_slice(scope).iter_mut().enumerate() {
|
||||
|
|
@ -162,7 +102,6 @@ impl SamplerAudio {
|
|||
|
||||
}
|
||||
|
||||
|
||||
impl Sample {
|
||||
pub fn new (name: &str, start: usize, end: usize, channels: Vec<Vec<f32>>) -> Self {
|
||||
Self { name: name.to_string(), start, end, channels, rate: None }
|
||||
|
|
@ -175,38 +114,6 @@ impl Sample {
|
|||
velocity: velocity.as_int() as f32 / 127.0,
|
||||
}
|
||||
}
|
||||
pub fn from_edn <'e> (jack: &Arc<RwLock<JackClient>>, dir: &str, args: &[Edn<'e>]) -> Usually<(Option<u7>, Arc<RwLock<Self>>)> {
|
||||
let mut name = String::new();
|
||||
let mut file = String::new();
|
||||
let mut midi = None;
|
||||
let mut start = 0usize;
|
||||
edn!(edn in args {
|
||||
Edn::Map(map) => {
|
||||
if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) {
|
||||
name = String::from(*n);
|
||||
}
|
||||
if let Some(Edn::Str(f)) = map.get(&Edn::Key(":file")) {
|
||||
file = String::from(*f);
|
||||
}
|
||||
if let Some(Edn::Int(i)) = map.get(&Edn::Key(":start")) {
|
||||
start = *i as usize;
|
||||
}
|
||||
if let Some(Edn::Int(m)) = map.get(&Edn::Key(":midi")) {
|
||||
midi = Some(u7::from(*m as u8));
|
||||
}
|
||||
},
|
||||
_ => panic!("unexpected in sample {name}"),
|
||||
});
|
||||
let (end, data) = Sample::read_data(&format!("{dir}/{file}"))?;
|
||||
Ok((midi, Arc::new(RwLock::new(Self {
|
||||
name: name.into(),
|
||||
start,
|
||||
end,
|
||||
channels: data,
|
||||
rate: None
|
||||
}))))
|
||||
}
|
||||
|
||||
/// Read WAV from file
|
||||
pub fn read_data (src: &str) -> Usually<(usize, Vec<Vec<f32>>)> {
|
||||
let mut channels: Vec<wavers::Samples<f32>> = vec![];
|
||||
|
|
|
|||
|
|
@ -94,34 +94,3 @@ pub trait ArrangerSceneApi: Sized {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
//impl ArrangerScene {
|
||||
|
||||
////TODO
|
||||
////pub fn from_edn <'a, 'e> (args: &[Edn<'e>]) -> Usually<Self> {
|
||||
////let mut name = None;
|
||||
////let mut clips = vec![];
|
||||
////edn!(edn in args {
|
||||
////Edn::Map(map) => {
|
||||
////let key = map.get(&Edn::Key(":name"));
|
||||
////if let Some(Edn::Str(n)) = key {
|
||||
////name = Some(*n);
|
||||
////} else {
|
||||
////panic!("unexpected key in scene '{name:?}': {key:?}")
|
||||
////}
|
||||
////},
|
||||
////Edn::Symbol("_") => {
|
||||
////clips.push(None);
|
||||
////},
|
||||
////Edn::Int(i) => {
|
||||
////clips.push(Some(*i as usize));
|
||||
////},
|
||||
////_ => panic!("unexpected in scene '{name:?}': {edn:?}")
|
||||
////});
|
||||
////Ok(ArrangerScene {
|
||||
////name: Arc::new(name.unwrap_or("").to_string().into()),
|
||||
////color: ItemColor::random(),
|
||||
////clips,
|
||||
////})
|
||||
////}
|
||||
//}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
pub(crate) mod audio; pub(crate) use audio::*;
|
||||
pub(crate) mod color; pub(crate) use color::*;
|
||||
pub(crate) mod command; pub(crate) use command::*;
|
||||
pub(crate) mod edn; pub(crate) use edn::*;
|
||||
pub(crate) mod engine; pub(crate) use engine::*;
|
||||
pub(crate) mod focus; pub(crate) use focus::*;
|
||||
pub(crate) mod input; pub(crate) use input::*;
|
||||
|
|
|
|||
|
|
@ -1,15 +0,0 @@
|
|||
pub use clojure_reader::edn::Edn;
|
||||
//pub use clojure_reader::{edn::{read, Edn}, error::Error as EdnError};
|
||||
|
||||
/// EDN parsing helper.
|
||||
#[macro_export] macro_rules! edn {
|
||||
($edn:ident { $($pat:pat => $expr:expr),* $(,)? }) => {
|
||||
match $edn { $($pat => $expr),* }
|
||||
};
|
||||
($edn:ident in $args:ident { $($pat:pat => $expr:expr),* $(,)? }) => {
|
||||
for $edn in $args {
|
||||
edn!($edn { $($pat => $expr),* })
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -76,8 +76,12 @@ pub trait Area<N: Coordinate>: Copy {
|
|||
#[inline] fn lrtb (&self) -> [N;4] { [self.x(), self.x2(), self.y(), self.y2()] }
|
||||
#[inline] fn push_x (&self, x: N) -> [N;4] { [self.x() + x, self.y(), self.w(), self.h()] }
|
||||
#[inline] fn push_y (&self, y: N) -> [N;4] { [self.x(), self.y() + y, self.w(), self.h()] }
|
||||
#[inline] fn shrink_x (&self, x: N) -> [N;4] { [self.x(), self.y(), self.w() - x, self.h()] }
|
||||
#[inline] fn shrink_y (&self, y: N) -> [N;4] { [self.x(), self.y(), self.w(), self.h() - y] }
|
||||
#[inline] fn shrink_x (&self, x: N) -> [N;4] {
|
||||
[self.x(), self.y(), self.w().minus(x), self.h()]
|
||||
}
|
||||
#[inline] fn shrink_y (&self, y: N) -> [N;4] {
|
||||
[self.x(), self.y(), self.w(), self.h().minus(y)]
|
||||
}
|
||||
#[inline] fn set_w (&self, w: N) -> [N;4] { [self.x(), self.y(), w, self.h()] }
|
||||
#[inline] fn set_h (&self, h: N) -> [N;4] { [self.x(), self.y(), self.w(), h] }
|
||||
#[inline] fn clip_h (&self, h: N) -> [N;4] {
|
||||
|
|
|
|||
123
crates/tek/src/edn.rs
Normal file
123
crates/tek/src/edn.rs
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
use crate::*;
|
||||
pub use clojure_reader::edn::Edn;
|
||||
//pub use clojure_reader::{edn::{read, Edn}, error::Error as EdnError};
|
||||
|
||||
/// EDN parsing helper.
|
||||
#[macro_export] macro_rules! edn {
|
||||
($edn:ident { $($pat:pat => $expr:expr),* $(,)? }) => {
|
||||
match $edn { $($pat => $expr),* }
|
||||
};
|
||||
($edn:ident in $args:ident { $($pat:pat => $expr:expr),* $(,)? }) => {
|
||||
for $edn in $args {
|
||||
edn!($edn { $($pat => $expr),* })
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl crate::Sampler {
|
||||
pub fn from_edn <'e> (jack: &Arc<RwLock<JackClient>>, args: &[Edn<'e>]) -> Usually<Self> {
|
||||
let mut name = String::new();
|
||||
let mut dir = String::new();
|
||||
let mut samples = BTreeMap::new();
|
||||
edn!(edn in args {
|
||||
Edn::Map(map) => {
|
||||
if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) {
|
||||
name = String::from(*n);
|
||||
}
|
||||
if let Some(Edn::Str(n)) = map.get(&Edn::Key(":dir")) {
|
||||
dir = String::from(*n);
|
||||
}
|
||||
},
|
||||
Edn::List(args) => match args.get(0) {
|
||||
Some(Edn::Symbol("sample")) => {
|
||||
let (midi, sample) = Sample::from_edn(jack, &dir, &args[1..])?;
|
||||
if let Some(midi) = midi {
|
||||
samples.insert(midi, sample);
|
||||
} else {
|
||||
panic!("sample without midi binding: {}", sample.read().unwrap().name);
|
||||
}
|
||||
},
|
||||
_ => panic!("unexpected in sampler {name}: {args:?}")
|
||||
},
|
||||
_ => panic!("unexpected in sampler {name}: {edn:?}")
|
||||
});
|
||||
let midi_in = jack.read().unwrap().client().register_port("in", MidiIn::default())?;
|
||||
Ok(Sampler {
|
||||
jack: jack.clone(),
|
||||
name: name.into(),
|
||||
mapped: samples,
|
||||
unmapped: Default::default(),
|
||||
voices: Default::default(),
|
||||
buffer: Default::default(),
|
||||
midi_in: midi_in,
|
||||
audio_outs: vec![],
|
||||
output_gain: 0.
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::Sample {
|
||||
pub fn from_edn <'e> (jack: &Arc<RwLock<JackClient>>, dir: &str, args: &[Edn<'e>]) -> Usually<(Option<u7>, Arc<RwLock<Self>>)> {
|
||||
let mut name = String::new();
|
||||
let mut file = String::new();
|
||||
let mut midi = None;
|
||||
let mut start = 0usize;
|
||||
edn!(edn in args {
|
||||
Edn::Map(map) => {
|
||||
if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) {
|
||||
name = String::from(*n);
|
||||
}
|
||||
if let Some(Edn::Str(f)) = map.get(&Edn::Key(":file")) {
|
||||
file = String::from(*f);
|
||||
}
|
||||
if let Some(Edn::Int(i)) = map.get(&Edn::Key(":start")) {
|
||||
start = *i as usize;
|
||||
}
|
||||
if let Some(Edn::Int(m)) = map.get(&Edn::Key(":midi")) {
|
||||
midi = Some(u7::from(*m as u8));
|
||||
}
|
||||
},
|
||||
_ => panic!("unexpected in sample {name}"),
|
||||
});
|
||||
let (end, data) = Sample::read_data(&format!("{dir}/{file}"))?;
|
||||
Ok((midi, Arc::new(RwLock::new(Self {
|
||||
name: name.into(),
|
||||
start,
|
||||
end,
|
||||
channels: data,
|
||||
rate: None
|
||||
}))))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//impl ArrangerScene {
|
||||
|
||||
////TODO
|
||||
////pub fn from_edn <'a, 'e> (args: &[Edn<'e>]) -> Usually<Self> {
|
||||
////let mut name = None;
|
||||
////let mut clips = vec![];
|
||||
////edn!(edn in args {
|
||||
////Edn::Map(map) => {
|
||||
////let key = map.get(&Edn::Key(":name"));
|
||||
////if let Some(Edn::Str(n)) = key {
|
||||
////name = Some(*n);
|
||||
////} else {
|
||||
////panic!("unexpected key in scene '{name:?}': {key:?}")
|
||||
////}
|
||||
////},
|
||||
////Edn::Symbol("_") => {
|
||||
////clips.push(None);
|
||||
////},
|
||||
////Edn::Int(i) => {
|
||||
////clips.push(Some(*i as usize));
|
||||
////},
|
||||
////_ => panic!("unexpected in scene '{name:?}': {edn:?}")
|
||||
////});
|
||||
////Ok(ArrangerScene {
|
||||
////name: Arc::new(name.unwrap_or("").to_string().into()),
|
||||
////color: ItemColor::random(),
|
||||
////clips,
|
||||
////})
|
||||
////}
|
||||
//}
|
||||
|
|
@ -31,6 +31,9 @@ pub(crate) use api::*;
|
|||
pub mod tui;
|
||||
pub(crate) use tui::*;
|
||||
|
||||
pub mod edn;
|
||||
pub(crate) use edn::*;
|
||||
|
||||
testmod! { test }
|
||||
|
||||
pub(crate) use clap::{self, Parser};
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@ impl TryFrom<&Arc<RwLock<JackClient>>> for GrooveboxTui {
|
|||
Ok(Self {
|
||||
sequencer,
|
||||
sampler: SamplerTui::try_from(jack)?,
|
||||
split: 16
|
||||
split: 16,
|
||||
focus: GrooveboxFocus::Sequencer,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -19,24 +20,39 @@ pub struct GrooveboxTui {
|
|||
pub sequencer: SequencerTui,
|
||||
pub sampler: SamplerTui,
|
||||
pub split: u16,
|
||||
pub focus: GrooveboxFocus
|
||||
}
|
||||
|
||||
pub enum GrooveboxFocus {
|
||||
Sequencer,
|
||||
Sampler
|
||||
}
|
||||
|
||||
audio!(|self:GrooveboxTui,_client,_process|Control::Continue);
|
||||
|
||||
render!(<Tui>|self:GrooveboxTui|Bsp::n(
|
||||
Fixed::h(2, SequencerStatusBar::from(&self.sequencer)),
|
||||
Fill::h(Bsp::s(&self.sequencer, &self.sampler)),
|
||||
));
|
||||
|
||||
pub enum GrooveboxCommand {
|
||||
Sequencer(SequencerCommand),
|
||||
Sampler(SamplerCommand),
|
||||
}
|
||||
|
||||
render!(<Tui>|self:GrooveboxTui|Bsp::n(
|
||||
Fixed::h(2, SequencerStatusBar::from(&self.sequencer)),
|
||||
Bsp::s(Fixed::h(self.split, &self.sequencer), &self.sampler),
|
||||
));
|
||||
audio!(|self:GrooveboxTui,_client,_process|Control::Continue);
|
||||
handle!(<Tui>|self:GrooveboxTui,input|GrooveboxCommand::execute_with_state(self, input));
|
||||
|
||||
input_to_command!(GrooveboxCommand: <Tui>|state:GrooveboxTui,input|match input.event() {
|
||||
// load sample
|
||||
key_pat!(Char('l')) => GrooveboxCommand::Sampler(SamplerCommand::Import),
|
||||
_ => GrooveboxCommand::Sequencer(SequencerCommand::input_to_command(&state.sequencer, input)?),
|
||||
key_pat!(Char('l')) => GrooveboxCommand::Sampler(SamplerCommand::Import(FileBrowserCommand::Begin)),
|
||||
_ => match state.focus {
|
||||
GrooveboxFocus::Sequencer =>
|
||||
GrooveboxCommand::Sequencer(SequencerCommand::input_to_command(&state.sequencer, input)?),
|
||||
GrooveboxFocus::Sampler =>
|
||||
GrooveboxCommand::Sampler(SamplerCommand::input_to_command(&state.sampler, input)?),
|
||||
}
|
||||
});
|
||||
|
||||
command!(|self:GrooveboxCommand,state:GrooveboxTui|match self {
|
||||
GrooveboxCommand::Sequencer(command) =>
|
||||
command.execute(&mut state.sequencer)?.map(GrooveboxCommand::Sequencer),
|
||||
|
|
|
|||
|
|
@ -9,18 +9,32 @@ use symphonia::core::audio::SampleBuffer;
|
|||
use symphonia::default::get_codecs;
|
||||
|
||||
pub enum SamplerCommand {
|
||||
Import
|
||||
Import(FileBrowserCommand),
|
||||
SetName(String),
|
||||
SetNote(u7, Arc<RwLock<Sample>>),
|
||||
SetGain(f32),
|
||||
NoteOn(u7, u7),
|
||||
NoteOff(u7)
|
||||
}
|
||||
input_to_command!(SamplerCommand: <Tui>|state:SamplerTui,input|match input.event() {
|
||||
input_to_command!(SamplerCommand:<Tui>|state:SamplerTui,input|match state.mode {
|
||||
Some(SamplerMode::Import(..)) => Self::Import(FileBrowserCommand::input_to_command(state, input)?),
|
||||
_ => return None
|
||||
});
|
||||
input_to_command!(FileBrowserCommand:<Tui>|state:SamplerTui,input|match input {
|
||||
_ => return None
|
||||
});
|
||||
command!(|self:SamplerCommand,state:SamplerTui|match self {
|
||||
SamplerCommand::Import => {
|
||||
SamplerCommand::Import(FileBrowserCommand::Begin) => {
|
||||
let voices = &state.state.voices;
|
||||
let sample = Arc::new(RwLock::new(Sample::new("", 0, 0, vec![])));
|
||||
state.mode = Some(SamplerMode::Import(0, FileBrowser::new(None)?));
|
||||
None
|
||||
},
|
||||
_ => todo!()
|
||||
});
|
||||
|
||||
command!(|self:FileBrowserCommand,state:SamplerTui|match self {
|
||||
_ => todo!()
|
||||
});
|
||||
|
||||
impl TryFrom<&Arc<RwLock<JackClient>>> for SamplerTui {
|
||||
type Error = Box<dyn std::error::Error>;
|
||||
|
|
@ -31,10 +45,9 @@ impl TryFrom<&Arc<RwLock<JackClient>>> for SamplerTui {
|
|||
jack.read().unwrap().client().register_port("outR", AudioOut::default())?,
|
||||
];
|
||||
Ok(Self {
|
||||
focus: SamplerFocus::_TODO,
|
||||
cursor: (0, 0),
|
||||
editing: None,
|
||||
modal: Default::default(),
|
||||
mode: None,
|
||||
state: Sampler {
|
||||
jack: jack.clone(),
|
||||
name: "Sampler".into(),
|
||||
|
|
@ -51,47 +64,54 @@ impl TryFrom<&Arc<RwLock<JackClient>>> for SamplerTui {
|
|||
}
|
||||
|
||||
pub struct SamplerTui {
|
||||
pub focus: SamplerFocus,
|
||||
pub state: Sampler,
|
||||
pub cursor: (usize, usize),
|
||||
pub editing: Option<Arc<RwLock<Sample>>>,
|
||||
pub modal: Arc<Mutex<Option<Box<dyn Exit + Send>>>>,
|
||||
pub mode: Option<SamplerMode>,
|
||||
}
|
||||
|
||||
/// Sections that may be focused
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
pub enum SamplerFocus {
|
||||
_TODO
|
||||
enum SamplerMode {
|
||||
// Load sample from path
|
||||
Import(usize, FileBrowser),
|
||||
}
|
||||
|
||||
audio!(|self: SamplerTui, _client, _scope|Control::Continue);
|
||||
render!(<Tui>|self: SamplerTui|{
|
||||
Fill::wh(lay!([
|
||||
|
||||
Fill::wh(render(|to|{ // border
|
||||
let [x, y, w, h] = to.area();
|
||||
let green = Some(Style::default().fg(Color::Green));
|
||||
to.blit(&"🭚", x, y, green);
|
||||
to.blit(&"🭥", x + w.saturating_sub(1), y, green);
|
||||
to.blit(&"🬿", x, y + h.saturating_sub(1), green);
|
||||
to.blit(&"🭊", x + w.saturating_sub(1), y + h.saturating_sub(1), green);
|
||||
Ok(())
|
||||
})),
|
||||
|
||||
col!([
|
||||
Tui::push_x(2, row!([
|
||||
Tui::bold(true, "Sampler"), "|Voices: ",
|
||||
&format!("{}", self.state.voices.read().unwrap().len()),
|
||||
])),
|
||||
Tui::either(self.state.unmapped.len() > 0,
|
||||
col!((index, sample) in self.state.unmapped.iter().enumerate() =>
|
||||
{ &format!("| Unmapped #{index}") }), "· No unmapped samples."),
|
||||
Tui::either(self.state.mapped.len() > 0,
|
||||
col!((index, sample) in self.state.unmapped.iter().enumerate() =>
|
||||
{ &format!("| Mapped #{index}") }), "· No mapped samples."),
|
||||
])
|
||||
]))
|
||||
audio!(|self: SamplerTui, _client, scope|{
|
||||
self.state.process_midi_in(scope);
|
||||
self.state.clear_output_buffer();
|
||||
self.state.process_audio_out(scope);
|
||||
self.state.write_output_buffer(scope);
|
||||
Control::Continue
|
||||
});
|
||||
render!(<Tui>|self: SamplerTui|Tui::min_y(10, Fill::wh(lay!([
|
||||
|
||||
Fill::wh(render(|to|{ // border
|
||||
let [x, y, w, h] = to.area();
|
||||
let green = Some(Style::default().fg(Color::Green));
|
||||
to.blit(&"🭚", x, y, green);
|
||||
to.blit(&"🭥", x + w.saturating_sub(1), y, green);
|
||||
to.blit(&"🬿", x, y + h.saturating_sub(1), green);
|
||||
to.blit(&"🭊", x + w.saturating_sub(1), y + h.saturating_sub(1), green);
|
||||
Ok(())
|
||||
})),
|
||||
|
||||
col!(|add|{
|
||||
add(&Tui::push_x(2, row!([
|
||||
Tui::bold(true, "Sampler"), "|Voices: ",
|
||||
&format!("{}", self.state.voices.read().unwrap().len()),
|
||||
])))?;
|
||||
if let Some(SamplerMode::Import(_, browser)) = self.mode.as_ref() {
|
||||
add(&browser)
|
||||
} else {
|
||||
add(&row!([
|
||||
" ",
|
||||
col!(note in 35..=42 => {
|
||||
&format!("{note:>3} ---------------- +0.0dB")
|
||||
}),
|
||||
]))
|
||||
}
|
||||
}),
|
||||
|
||||
]))));
|
||||
|
||||
handle!(<Tui>|self:SamplerTui,from|{
|
||||
let cursor = &mut self.cursor;
|
||||
|
|
@ -112,11 +132,11 @@ handle!(<Tui>|self:SamplerTui,from|{
|
|||
},
|
||||
key_pat!(KeyCode::Char('a')) => {
|
||||
let sample = Arc::new(RwLock::new(Sample::new("", 0, 0, vec![])));
|
||||
*self.modal.lock().unwrap() = Some(Exit::boxed(AddSampleModal::new(&sample, &voices)?));
|
||||
self.mode = None;//Some(Exit::boxed(AddSampleModal::new(&sample, &voices)?));
|
||||
unmapped.push(sample);
|
||||
},
|
||||
key_pat!(KeyCode::Char('r')) => if let Some(sample) = self.sample() {
|
||||
*self.modal.lock().unwrap() = Some(Exit::boxed(AddSampleModal::new(&sample, &voices)?));
|
||||
self.mode = None;//Some(Exit::boxed(AddSampleModal::new(&sample, &voices)?));
|
||||
},
|
||||
key_pat!(KeyCode::Enter) => if let Some(sample) = self.sample() {
|
||||
self.editing = Some(sample.clone());
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ render!(<Tui>|self: SequencerTui|{
|
|||
PhraseSelector::play_phrase(&self.player),
|
||||
PhraseSelector::next_phrase(&self.player),
|
||||
]), transport]);
|
||||
with_size(with_status(col!([ toolbar, editor, ])))
|
||||
Tui::min_y(15, with_size(with_status(col!([ toolbar, editor, ]))))
|
||||
});
|
||||
audio!(|self:SequencerTui, client, scope|{
|
||||
// Start profiling cycle
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue