mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 19:56:42 +01:00
refactor sampler module
This commit is contained in:
parent
97920d7063
commit
df00fedfd6
6 changed files with 293 additions and 306 deletions
|
|
@ -110,9 +110,11 @@ render!(<Tui>|self: GrooveboxSamples<'a>|{
|
|||
bg = Color::Rgb(64,16,0);
|
||||
fg = Color::Rgb(224,64,32)
|
||||
}
|
||||
} else if self.0.sampler.mapped[note].is_some() {
|
||||
fg = TuiTheme::g(224);
|
||||
}
|
||||
Tui::bg(bg, if let Some(sample) = &self.0.sampler.mapped[note] {
|
||||
Tui::fg(fg, format!("{note:3} ????? "))
|
||||
Tui::fg(fg, format!("{note:3} ?????? "))
|
||||
} else {
|
||||
Tui::fg(fg, format!("{note:3} (none) "))
|
||||
})
|
||||
|
|
|
|||
|
|
@ -21,14 +21,9 @@ pub mod voice;
|
|||
pub(crate) use self::voice::*;
|
||||
pub use self::voice::Voice;
|
||||
|
||||
pub mod sampler_control;
|
||||
pub(crate) use self::sampler_control::*;
|
||||
|
||||
pub mod sampler_audio;
|
||||
pub(crate) use self::sampler_audio::*;
|
||||
|
||||
pub mod sampler_keys;
|
||||
pub(crate) use self::sampler_keys::*;
|
||||
pub mod sampler_tui;
|
||||
pub(crate) use self::sampler_tui::*;
|
||||
pub use self::sampler_tui::SamplerTui;
|
||||
|
||||
pub mod sample_import;
|
||||
pub(crate) use self::sample_import::*;
|
||||
|
|
@ -102,88 +97,138 @@ impl Sampler {
|
|||
}
|
||||
}
|
||||
}
|
||||
pub struct SamplerTui {
|
||||
pub state: Sampler,
|
||||
pub cursor: (usize, usize),
|
||||
pub editing: Option<Arc<RwLock<Sample>>>,
|
||||
pub mode: Option<SamplerMode>,
|
||||
/// Size of actual notes area
|
||||
pub size: Measure<Tui>,
|
||||
/// Lowest note displayed
|
||||
pub note_lo: AtomicUsize,
|
||||
pub note_pt: AtomicUsize,
|
||||
color: ItemPalette
|
||||
}
|
||||
impl SamplerTui {
|
||||
/// Immutable reference to sample at cursor.
|
||||
pub fn sample (&self) -> Option<&Arc<RwLock<Sample>>> {
|
||||
for (i, sample) in self.state.mapped.iter().enumerate() {
|
||||
if i == self.cursor.0 {
|
||||
return sample.as_ref()
|
||||
}
|
||||
}
|
||||
for (i, sample) in self.state.unmapped.iter().enumerate() {
|
||||
if i + self.state.mapped.len() == self.cursor.0 {
|
||||
return Some(sample)
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
from_jack!(|jack|SamplerTui{
|
||||
Self {
|
||||
cursor: (0, 0),
|
||||
editing: None,
|
||||
mode: None,
|
||||
size: Measure::new(),
|
||||
note_lo: 36.into(),
|
||||
note_pt: 36.into(),
|
||||
color: ItemPalette::from(Color::Rgb(64, 128, 32)),
|
||||
state: Sampler::new(jack, "sampler")?,
|
||||
}
|
||||
});
|
||||
render!(<Tui>|self: SamplerTui|{
|
||||
let keys_width = 5;
|
||||
let keys = move||SamplerKeys(self);
|
||||
let fg = self.color.base.rgb;
|
||||
let bg = self.color.darkest.rgb;
|
||||
let border = Fill::wh(Outer(Style::default().fg(fg).bg(bg)));
|
||||
let with_border = |x|lay!([border, Fill::wh(&x)]);
|
||||
let with_size = |x|lay!([self.size, x]);
|
||||
Tui::bg(bg, Fill::wh(with_border(Bsp::s(
|
||||
Tui::fg(self.color.light.rgb, Tui::bold(true, "Sampler")),
|
||||
with_size(Tui::shrink_y(1, Bsp::e(
|
||||
Fixed::w(keys_width, keys()),
|
||||
Fill::wh(render(|to: &mut TuiOutput|Ok({
|
||||
let x = to.area.x();
|
||||
let bg_base = self.color.darkest.rgb;
|
||||
let bg_selected = self.color.darker.rgb;
|
||||
let style_empty = Style::default().fg(self.color.base.rgb);
|
||||
let style_full = Style::default().fg(self.color.lighter.rgb);
|
||||
let note_hi = self.note_hi();
|
||||
let note_pt = self.note_point();
|
||||
for y in 0..self.size.h() {
|
||||
let note = note_hi - y as usize;
|
||||
let bg = if note == note_pt { bg_selected } else { bg_base };
|
||||
let style = Some(style_empty.bg(bg));
|
||||
to.blit(&" (no sample) ", x, to.area.y() + y as u16, style)
|
||||
}
|
||||
})))
|
||||
))),
|
||||
))))
|
||||
});
|
||||
impl NoteRange for SamplerTui {
|
||||
fn note_lo (&self) -> &AtomicUsize { &self.note_lo }
|
||||
fn note_axis (&self) -> &AtomicUsize { &self.size.y }
|
||||
}
|
||||
impl NotePoint for SamplerTui {
|
||||
fn note_len (&self) -> usize {0/*TODO*/}
|
||||
fn set_note_len (&self, x: usize) {}
|
||||
fn note_point (&self) -> usize { self.note_pt.load(Relaxed) }
|
||||
fn set_note_point (&self, x: usize) { self.note_pt.store(x, Relaxed); }
|
||||
|
||||
pub enum SamplerCommand {
|
||||
RecordBegin(u7),
|
||||
RecordCancel,
|
||||
RecordFinish,
|
||||
SetSample(u7, Option<Arc<RwLock<Sample>>>),
|
||||
SetGain(f32),
|
||||
NoteOn(u7, u7),
|
||||
NoteOff(u7),
|
||||
}
|
||||
|
||||
pub enum SamplerMode {
|
||||
// Load sample from path
|
||||
Import(usize, FileBrowser),
|
||||
command!(|self: SamplerCommand, state: Sampler|match self {
|
||||
Self::SetSample(index, sample) => {
|
||||
let i = index.as_int() as usize;
|
||||
let old = state.mapped[i].clone();
|
||||
state.mapped[i] = sample;
|
||||
Some(Self::SetSample(index, old))
|
||||
},
|
||||
Self::RecordBegin(index) => {
|
||||
state.begin_recording(index.as_int() as usize);
|
||||
None
|
||||
},
|
||||
Self::RecordCancel => {
|
||||
state.cancel_recording();
|
||||
None
|
||||
},
|
||||
Self::RecordFinish => {
|
||||
state.finish_recording();
|
||||
None
|
||||
},
|
||||
_ => todo!()
|
||||
});
|
||||
|
||||
audio!(|self: SamplerTui, client, scope|{
|
||||
SamplerAudio(&mut self.state).process(client, scope)
|
||||
});
|
||||
|
||||
pub struct SamplerAudio<'a>(pub &'a mut Sampler);
|
||||
|
||||
audio!(|self: SamplerAudio<'a>, _client, scope|{
|
||||
self.0.process_midi_in(scope);
|
||||
self.0.clear_output_buffer();
|
||||
self.0.process_audio_out(scope);
|
||||
self.0.write_output_buffer(scope);
|
||||
self.0.process_audio_in(scope);
|
||||
Control::Continue
|
||||
});
|
||||
|
||||
impl Sampler {
|
||||
|
||||
pub fn process_audio_in (&mut self, scope: &ProcessScope) {
|
||||
let Sampler { audio_ins, input_meter, recording, .. } = self;
|
||||
if audio_ins.len() != input_meter.len() {
|
||||
*input_meter = vec![0.0;audio_ins.len()];
|
||||
}
|
||||
if let Some((_, sample)) = recording {
|
||||
if sample.channels.len() != audio_ins.len() {
|
||||
panic!("channel count mismatch");
|
||||
}
|
||||
let iterator = audio_ins.iter().zip(input_meter).zip(sample.channels.iter_mut());
|
||||
let mut length = 0;
|
||||
for ((input, meter), channel) in iterator {
|
||||
let slice = input.as_slice(scope);
|
||||
length = length.max(slice.len());
|
||||
let total: f32 = slice.iter().map(|x|x.abs()).sum();
|
||||
let count = slice.len() as f32;
|
||||
*meter = 10. * (total / count).log10();
|
||||
channel.extend_from_slice(slice);
|
||||
}
|
||||
sample.end += length;
|
||||
} else {
|
||||
for (input, meter) in audio_ins.iter().zip(input_meter) {
|
||||
let slice = input.as_slice(scope);
|
||||
let total: f32 = slice.iter().map(|x|x.abs()).sum();
|
||||
let count = slice.len() as f32;
|
||||
*meter = 10. * (total / count).log10();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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;
|
||||
for RawMidi { time, bytes } in midi_in.iter(scope) {
|
||||
if let LiveEvent::Midi {
|
||||
message: MidiMessage::NoteOn { ref key, ref vel }, ..
|
||||
} = LiveEvent::parse(bytes).unwrap() {
|
||||
if let Some(ref sample) = mapped[key.as_int() as usize] {
|
||||
voices.write().unwrap().push(Sample::play(sample, time as usize, vel));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Zero the output buffer.
|
||||
pub fn clear_output_buffer (&mut self) {
|
||||
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, .. } = self;
|
||||
let channel_count = buffer.len();
|
||||
voices.write().unwrap().retain_mut(|voice|{
|
||||
for index in 0..scope.n_frames() as usize {
|
||||
if let Some(frame) = voice.next() {
|
||||
for (channel, sample) in frame.iter().enumerate() {
|
||||
// Averaging mixer:
|
||||
//self.buffer[channel % channel_count][index] = (
|
||||
//(self.buffer[channel % channel_count][index] + sample * self.output_gain) / 2.0
|
||||
//);
|
||||
buffer[channel % channel_count][index] += sample * *output_gain;
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
true
|
||||
});
|
||||
}
|
||||
|
||||
/// Write output buffer to output ports.
|
||||
pub fn write_output_buffer (&mut self, scope: &ProcessScope) {
|
||||
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() {
|
||||
*value = *buffer.get(i).unwrap_or(&0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,104 +0,0 @@
|
|||
use crate::*;
|
||||
|
||||
audio!(|self: SamplerTui, client, scope|{
|
||||
SamplerAudio(&mut self.state).process(client, scope)
|
||||
});
|
||||
|
||||
pub struct SamplerAudio<'a>(pub &'a mut Sampler);
|
||||
|
||||
audio!(|self: SamplerAudio<'a>, _client, scope|{
|
||||
self.0.process_midi_in(scope);
|
||||
self.0.clear_output_buffer();
|
||||
self.0.process_audio_out(scope);
|
||||
self.0.write_output_buffer(scope);
|
||||
self.0.process_audio_in(scope);
|
||||
Control::Continue
|
||||
});
|
||||
|
||||
impl Sampler {
|
||||
|
||||
pub fn process_audio_in (&mut self, scope: &ProcessScope) {
|
||||
let Sampler { audio_ins, input_meter, recording, .. } = self;
|
||||
if audio_ins.len() != input_meter.len() {
|
||||
*input_meter = vec![0.0;audio_ins.len()];
|
||||
}
|
||||
if let Some((_, sample)) = recording {
|
||||
if sample.channels.len() != audio_ins.len() {
|
||||
panic!("channel count mismatch");
|
||||
}
|
||||
let iterator = audio_ins.iter().zip(input_meter).zip(sample.channels.iter_mut());
|
||||
let mut length = 0;
|
||||
for ((input, meter), channel) in iterator {
|
||||
let slice = input.as_slice(scope);
|
||||
length = length.max(slice.len());
|
||||
let total: f32 = slice.iter().map(|x|x.abs()).sum();
|
||||
let count = slice.len() as f32;
|
||||
*meter = 10. * (total / count).log10();
|
||||
channel.extend_from_slice(slice);
|
||||
}
|
||||
sample.end += length;
|
||||
} else {
|
||||
for (input, meter) in audio_ins.iter().zip(input_meter) {
|
||||
let slice = input.as_slice(scope);
|
||||
let total: f32 = slice.iter().map(|x|x.abs()).sum();
|
||||
let count = slice.len() as f32;
|
||||
*meter = 10. * (total / count).log10();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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;
|
||||
for RawMidi { time, bytes } in midi_in.iter(scope) {
|
||||
if let LiveEvent::Midi {
|
||||
message: MidiMessage::NoteOn { ref key, ref vel }, ..
|
||||
} = LiveEvent::parse(bytes).unwrap() {
|
||||
if let Some(ref sample) = mapped[key.as_int() as usize] {
|
||||
voices.write().unwrap().push(Sample::play(sample, time as usize, vel));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Zero the output buffer.
|
||||
pub fn clear_output_buffer (&mut self) {
|
||||
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, .. } = self;
|
||||
let channel_count = buffer.len();
|
||||
voices.write().unwrap().retain_mut(|voice|{
|
||||
for index in 0..scope.n_frames() as usize {
|
||||
if let Some(frame) = voice.next() {
|
||||
for (channel, sample) in frame.iter().enumerate() {
|
||||
// Averaging mixer:
|
||||
//self.buffer[channel % channel_count][index] = (
|
||||
//(self.buffer[channel % channel_count][index] + sample * self.output_gain) / 2.0
|
||||
//);
|
||||
buffer[channel % channel_count][index] += sample * *output_gain;
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
true
|
||||
});
|
||||
}
|
||||
|
||||
/// Write output buffer to output ports.
|
||||
pub fn write_output_buffer (&mut self, scope: &ProcessScope) {
|
||||
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() {
|
||||
*value = *buffer.get(i).unwrap_or(&0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,96 +0,0 @@
|
|||
use crate::*;
|
||||
use KeyCode::Char;
|
||||
|
||||
handle!(<Tui>|self: SamplerTui, input|SamplerTuiCommand::execute_with_state(self, input));
|
||||
|
||||
pub enum SamplerTuiCommand {
|
||||
Import(FileBrowserCommand),
|
||||
SelectNote(usize),
|
||||
SelectField(usize),
|
||||
Sample(SamplerCommand),
|
||||
}
|
||||
|
||||
pub enum SamplerCommand {
|
||||
RecordBegin(u7),
|
||||
RecordCancel,
|
||||
RecordFinish,
|
||||
SetSample(u7, Option<Arc<RwLock<Sample>>>),
|
||||
SetGain(f32),
|
||||
NoteOn(u7, u7),
|
||||
NoteOff(u7),
|
||||
}
|
||||
|
||||
input_to_command!(SamplerTuiCommand: <Tui>|state: SamplerTui, input|match state.mode {
|
||||
Some(SamplerMode::Import(..)) => Self::Import(
|
||||
FileBrowserCommand::input_to_command(state, input)?
|
||||
),
|
||||
_ => match input.event() {
|
||||
// load sample
|
||||
key_pat!(Shift-Char('L')) => {
|
||||
Self::Import(FileBrowserCommand::Begin)
|
||||
},
|
||||
key_pat!(KeyCode::Up) => {
|
||||
Self::SelectNote(state.note_point().overflowing_add(1).0.min(127))
|
||||
},
|
||||
key_pat!(KeyCode::Down) => {
|
||||
Self::SelectNote(state.note_point().overflowing_sub(1).0.min(127))
|
||||
},
|
||||
_ => return None
|
||||
}
|
||||
//key_pat!(KeyCode::Char('p')) => if let Some(sample) = self.sample() {
|
||||
//voices.write().unwrap().push(Sample::play(sample, 0, &100.into()));
|
||||
//},
|
||||
//key_pat!(KeyCode::Char('a')) => {
|
||||
//let sample = Arc::new(RwLock::new(Sample::new("", 0, 0, vec![])));
|
||||
//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.mode = None;//Some(Exit::boxed(AddSampleModal::new(&sample, &voices)?));
|
||||
//},
|
||||
//key_pat!(KeyCode::Enter) => if let Some(sample) = self.sample() {
|
||||
//self.editing = Some(sample.clone());
|
||||
//},
|
||||
//_ => {
|
||||
//return Ok(None)
|
||||
//}
|
||||
//}
|
||||
});
|
||||
|
||||
command!(|self: SamplerTuiCommand, state: SamplerTui|match self {
|
||||
Self::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
|
||||
},
|
||||
Self::SelectNote(index) => {
|
||||
let old = state.note_point();
|
||||
state.set_note_point(index);
|
||||
Some(Self::SelectNote(old))
|
||||
},
|
||||
Self::Sample(cmd) => cmd.execute(&mut state.state)?.map(Self::Sample),
|
||||
_ => todo!()
|
||||
});
|
||||
|
||||
command!(|self: SamplerCommand, state: Sampler|match self {
|
||||
Self::SetSample(index, sample) => {
|
||||
let i = index.as_int() as usize;
|
||||
let old = state.mapped[i].clone();
|
||||
state.mapped[i] = sample;
|
||||
Some(Self::SetSample(index, old))
|
||||
},
|
||||
Self::RecordBegin(index) => {
|
||||
state.begin_recording(index.as_int() as usize);
|
||||
None
|
||||
},
|
||||
Self::RecordCancel => {
|
||||
state.cancel_recording();
|
||||
None
|
||||
},
|
||||
Self::RecordFinish => {
|
||||
state.finish_recording();
|
||||
None
|
||||
},
|
||||
_ => todo!()
|
||||
});
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
use crate::*;
|
||||
|
||||
pub struct SamplerKeys<'a>(pub &'a SamplerTui);
|
||||
has_color!(|self: SamplerKeys<'a>|self.0.color.base);
|
||||
render!(<Tui>|self: SamplerKeys<'a>|render(|to|Ok(render_keys_v(to, self))));
|
||||
impl<'a> NoteRange for SamplerKeys<'a> {
|
||||
fn note_lo (&self) -> &AtomicUsize { &self.0.note_lo }
|
||||
fn note_axis (&self) -> &AtomicUsize { &self.0.size.y }
|
||||
}
|
||||
impl<'a> NotePoint for SamplerKeys<'a> {
|
||||
fn note_len (&self) -> usize {0/*TODO*/}
|
||||
fn set_note_len (&self, x: usize) {}
|
||||
fn note_point (&self) -> usize { self.0.note_point() }
|
||||
fn set_note_point (&self, x: usize) { self.0.set_note_point(x); }
|
||||
}
|
||||
155
crates/tek/src/sampler/sampler_tui.rs
Normal file
155
crates/tek/src/sampler/sampler_tui.rs
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
use crate::*;
|
||||
use KeyCode::Char;
|
||||
|
||||
pub struct SamplerTui {
|
||||
pub state: Sampler,
|
||||
pub cursor: (usize, usize),
|
||||
pub editing: Option<Arc<RwLock<Sample>>>,
|
||||
pub mode: Option<SamplerMode>,
|
||||
/// Size of actual notes area
|
||||
pub size: Measure<Tui>,
|
||||
/// Lowest note displayed
|
||||
pub note_lo: AtomicUsize,
|
||||
pub note_pt: AtomicUsize,
|
||||
color: ItemPalette
|
||||
}
|
||||
|
||||
impl SamplerTui {
|
||||
/// Immutable reference to sample at cursor.
|
||||
pub fn sample (&self) -> Option<&Arc<RwLock<Sample>>> {
|
||||
for (i, sample) in self.state.mapped.iter().enumerate() {
|
||||
if i == self.cursor.0 {
|
||||
return sample.as_ref()
|
||||
}
|
||||
}
|
||||
for (i, sample) in self.state.unmapped.iter().enumerate() {
|
||||
if i + self.state.mapped.len() == self.cursor.0 {
|
||||
return Some(sample)
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
from_jack!(|jack|SamplerTui{
|
||||
Self {
|
||||
cursor: (0, 0),
|
||||
editing: None,
|
||||
mode: None,
|
||||
size: Measure::new(),
|
||||
note_lo: 36.into(),
|
||||
note_pt: 36.into(),
|
||||
color: ItemPalette::from(Color::Rgb(64, 128, 32)),
|
||||
state: Sampler::new(jack, "sampler")?,
|
||||
}
|
||||
});
|
||||
|
||||
render!(<Tui>|self: SamplerTui|{
|
||||
let keys_width = 5;
|
||||
let keys = move||"";//SamplerKeys(self);
|
||||
let fg = self.color.base.rgb;
|
||||
let bg = self.color.darkest.rgb;
|
||||
let border = Fill::wh(Outer(Style::default().fg(fg).bg(bg)));
|
||||
let with_border = |x|lay!([border, Fill::wh(&x)]);
|
||||
let with_size = |x|lay!([self.size, x]);
|
||||
Tui::bg(bg, Fill::wh(with_border(Bsp::s(
|
||||
Tui::fg(self.color.light.rgb, Tui::bold(true, "Sampler")),
|
||||
with_size(Tui::shrink_y(1, Bsp::e(
|
||||
Fixed::w(keys_width, keys()),
|
||||
Fill::wh(render(|to: &mut TuiOutput|Ok({
|
||||
let x = to.area.x();
|
||||
let bg_base = self.color.darkest.rgb;
|
||||
let bg_selected = self.color.darker.rgb;
|
||||
let style_empty = Style::default().fg(self.color.base.rgb);
|
||||
let style_full = Style::default().fg(self.color.lighter.rgb);
|
||||
let note_hi = self.note_hi();
|
||||
let note_pt = self.note_point();
|
||||
for y in 0..self.size.h() {
|
||||
let note = note_hi - y as usize;
|
||||
let bg = if note == note_pt { bg_selected } else { bg_base };
|
||||
let style = Some(style_empty.bg(bg));
|
||||
to.blit(&" (no sample) ", x, to.area.y() + y as u16, style)
|
||||
}
|
||||
})))
|
||||
))),
|
||||
))))
|
||||
});
|
||||
|
||||
impl NoteRange for SamplerTui {
|
||||
fn note_lo (&self) -> &AtomicUsize { &self.note_lo }
|
||||
fn note_axis (&self) -> &AtomicUsize { &self.size.y }
|
||||
}
|
||||
|
||||
impl NotePoint for SamplerTui {
|
||||
fn note_len (&self) -> usize {0/*TODO*/}
|
||||
fn set_note_len (&self, x: usize) {}
|
||||
fn note_point (&self) -> usize { self.note_pt.load(Relaxed) }
|
||||
fn set_note_point (&self, x: usize) { self.note_pt.store(x, Relaxed); }
|
||||
}
|
||||
|
||||
pub enum SamplerMode {
|
||||
// Load sample from path
|
||||
Import(usize, FileBrowser),
|
||||
}
|
||||
|
||||
handle!(<Tui>|self: SamplerTui, input|SamplerTuiCommand::execute_with_state(self, input));
|
||||
|
||||
pub enum SamplerTuiCommand {
|
||||
Import(FileBrowserCommand),
|
||||
SelectNote(usize),
|
||||
SelectField(usize),
|
||||
Sample(SamplerCommand),
|
||||
}
|
||||
|
||||
input_to_command!(SamplerTuiCommand: <Tui>|state: SamplerTui, input|match state.mode {
|
||||
Some(SamplerMode::Import(..)) => Self::Import(
|
||||
FileBrowserCommand::input_to_command(state, input)?
|
||||
),
|
||||
_ => match input.event() {
|
||||
// load sample
|
||||
key_pat!(Shift-Char('L')) => {
|
||||
Self::Import(FileBrowserCommand::Begin)
|
||||
},
|
||||
key_pat!(KeyCode::Up) => {
|
||||
Self::SelectNote(state.note_point().overflowing_add(1).0.min(127))
|
||||
},
|
||||
key_pat!(KeyCode::Down) => {
|
||||
Self::SelectNote(state.note_point().overflowing_sub(1).0.min(127))
|
||||
},
|
||||
_ => return None
|
||||
}
|
||||
//key_pat!(KeyCode::Char('p')) => if let Some(sample) = self.sample() {
|
||||
//voices.write().unwrap().push(Sample::play(sample, 0, &100.into()));
|
||||
//},
|
||||
//key_pat!(KeyCode::Char('a')) => {
|
||||
//let sample = Arc::new(RwLock::new(Sample::new("", 0, 0, vec![])));
|
||||
//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.mode = None;//Some(Exit::boxed(AddSampleModal::new(&sample, &voices)?));
|
||||
//},
|
||||
//key_pat!(KeyCode::Enter) => if let Some(sample) = self.sample() {
|
||||
//self.editing = Some(sample.clone());
|
||||
//},
|
||||
//_ => {
|
||||
//return Ok(None)
|
||||
//}
|
||||
//}
|
||||
});
|
||||
|
||||
command!(|self: SamplerTuiCommand, state: SamplerTui|match self {
|
||||
Self::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
|
||||
},
|
||||
Self::SelectNote(index) => {
|
||||
let old = state.note_point();
|
||||
state.set_note_point(index);
|
||||
Some(Self::SelectNote(old))
|
||||
},
|
||||
Self::Sample(cmd) => cmd.execute(&mut state.state)?.map(Self::Sample),
|
||||
_ => todo!()
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue