mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 19:56:42 +01:00
unified compact flag in groovebox
This commit is contained in:
parent
c9a79b1f29
commit
511ff91864
12 changed files with 129 additions and 188 deletions
|
|
@ -17,7 +17,7 @@ mod arranger_v_sep; pub(crate) use self::arranger_v_sep::*;
|
|||
pub struct ArrangerTui {
|
||||
jack: Arc<RwLock<JackConnection>>,
|
||||
pub clock: Clock,
|
||||
pub phrases: PoolModel,
|
||||
pub pool: PoolModel,
|
||||
pub tracks: Vec<ArrangerTrack>,
|
||||
pub scenes: Vec<ArrangerScene>,
|
||||
pub splits: [u16;2],
|
||||
|
|
@ -81,7 +81,7 @@ from_jack!(|jack| ArrangerTui {
|
|||
)));
|
||||
Self {
|
||||
clock,
|
||||
phrases: (&phrase).into(),
|
||||
pool: (&phrase).into(),
|
||||
editor: (&phrase).into(),
|
||||
selected: ArrangerSelection::Clip(0, 0),
|
||||
scenes: vec![],
|
||||
|
|
@ -105,8 +105,8 @@ impl ArrangerTui {
|
|||
}
|
||||
}
|
||||
render!(Tui: (self: ArrangerTui) => {
|
||||
let pool_size = if self.phrases.visible { self.splits[1] } else { 0 };
|
||||
let with_pool = |x|Bsp::w(Fixed::x(pool_size, PoolView(&self.phrases)), x);
|
||||
let pool_size = if self.pool.visible { self.splits[1] } else { 0 };
|
||||
let with_pool = |x|Bsp::w(Fixed::x(pool_size, PoolView(self.pool.visible, &self.pool)), x);
|
||||
let status = ArrangerStatus::from(self);
|
||||
let with_editbar = |x|Bsp::n(Fixed::y(1, MidiEditStatus(&self.editor)), x);
|
||||
let with_status = |x|Bsp::n(Fixed::y(2, status), x);
|
||||
|
|
@ -162,6 +162,6 @@ audio!(|self: ArrangerTui, client, scope|{
|
|||
return Control::Continue
|
||||
});
|
||||
has_clock!(|self: ArrangerTui|&self.clock);
|
||||
has_phrases!(|self: ArrangerTui|self.phrases.phrases);
|
||||
has_phrases!(|self: ArrangerTui|self.pool.phrases);
|
||||
has_editor!(|self: ArrangerTui|self.editor);
|
||||
handle!(<Tui>|self: ArrangerTui, input|ArrangerCommand::execute_with_state(self, input));
|
||||
|
|
|
|||
|
|
@ -57,14 +57,14 @@ input_to_command!(ArrangerCommand: <Tui>|state: ArrangerTui, input|match input.e
|
|||
key_pat!(Shift-Char(' ')) =>
|
||||
Self::Clock(if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) }),
|
||||
key_pat!(Char('e')) =>
|
||||
Self::Editor(MidiEditCommand::Show(Some(state.phrases.phrase().clone()))),
|
||||
Self::Editor(MidiEditCommand::Show(Some(state.pool.phrase().clone()))),
|
||||
key_pat!(Ctrl-Left) =>
|
||||
Self::Scene(ArrangerSceneCommand::Add),
|
||||
key_pat!(Ctrl-Char('t')) =>
|
||||
Self::Track(ArrangerTrackCommand::Add),
|
||||
// Tab: Toggle visibility of phrase pool column
|
||||
key_pat!(Tab) =>
|
||||
Self::Phrases(PoolCommand::Show(!state.phrases.visible)),
|
||||
Self::Phrases(PoolCommand::Show(!state.pool.visible)),
|
||||
_ => {
|
||||
use ArrangerCommand as Cmd;
|
||||
use ArrangerSelection as Selected;
|
||||
|
|
@ -81,7 +81,7 @@ input_to_command!(ArrangerCommand: <Tui>|state: ArrangerTui, input|match input.e
|
|||
key_pat!(Char('.')) => Some(Cmd::Clip(Clip::Put(t, s, None))),
|
||||
key_pat!(Char('<')) => Some(Cmd::Clip(Clip::Put(t, s, None))),
|
||||
key_pat!(Char('>')) => Some(Cmd::Clip(Clip::Put(t, s, None))),
|
||||
key_pat!(Char('p')) => Some(Cmd::Clip(Clip::Put(t, s, Some(state.phrases.phrase().clone())))),
|
||||
key_pat!(Char('p')) => Some(Cmd::Clip(Clip::Put(t, s, Some(state.pool.phrase().clone())))),
|
||||
key_pat!(Char('l')) => Some(Cmd::Clip(ArrangerClipCommand::SetLoop(t, s, false))),
|
||||
key_pat!(Delete) => Some(Cmd::Clip(Clip::Put(t, s, None))),
|
||||
|
||||
|
|
@ -154,106 +154,19 @@ input_to_command!(ArrangerCommand: <Tui>|state: ArrangerTui, input|match input.e
|
|||
}
|
||||
}.or_else(||if let Some(command) = MidiEditCommand::input_to_command(&state.editor, input) {
|
||||
Some(Self::Editor(command))
|
||||
} else if let Some(command) = PoolCommand::input_to_command(&state.phrases, input) {
|
||||
} else if let Some(command) = PoolCommand::input_to_command(&state.pool, input) {
|
||||
Some(Self::Phrases(command))
|
||||
} else {
|
||||
None
|
||||
})?
|
||||
});
|
||||
fn to_arrangement_command (state: &ArrangerTui, input: &TuiIn) -> Option<ArrangerCommand> {
|
||||
use ArrangerCommand as Cmd;
|
||||
use ArrangerSelection as Selected;
|
||||
use ArrangerSceneCommand as Scene;
|
||||
use ArrangerTrackCommand as Track;
|
||||
use ArrangerClipCommand as Clip;
|
||||
let t_len = state.tracks.len();
|
||||
let s_len = state.scenes.len();
|
||||
match state.selected() {
|
||||
Selected::Clip(t, s) => match input.event() {
|
||||
key_pat!(Char('g')) => Some(Cmd::Phrases(PoolCommand::Select(0))),
|
||||
key_pat!(Char('q')) => Some(Cmd::Clip(Clip::Enqueue(t, s))),
|
||||
key_pat!(Char(',')) => Some(Cmd::Clip(Clip::Put(t, s, None))),
|
||||
key_pat!(Char('.')) => Some(Cmd::Clip(Clip::Put(t, s, None))),
|
||||
key_pat!(Char('<')) => Some(Cmd::Clip(Clip::Put(t, s, None))),
|
||||
key_pat!(Char('>')) => Some(Cmd::Clip(Clip::Put(t, s, None))),
|
||||
key_pat!(Char('p')) => Some(Cmd::Clip(Clip::Put(t, s, Some(state.phrases.phrase().clone())))),
|
||||
key_pat!(Char('l')) => Some(Cmd::Clip(ArrangerClipCommand::SetLoop(t, s, false))),
|
||||
key_pat!(Delete) => Some(Cmd::Clip(Clip::Put(t, s, None))),
|
||||
|
||||
key_pat!(Up) => Some(Cmd::Select(
|
||||
if s > 0 { Selected::Clip(t, s - 1) } else { Selected::Track(t) })),
|
||||
key_pat!(Down) => Some(Cmd::Select(
|
||||
Selected::Clip(t, (s + 1).min(s_len.saturating_sub(1))))),
|
||||
key_pat!(Left) => Some(Cmd::Select(
|
||||
if t > 0 { Selected::Clip(t - 1, s) } else { Selected::Scene(s) })),
|
||||
key_pat!(Right) => Some(Cmd::Select(
|
||||
Selected::Clip((t + 1).min(t_len.saturating_sub(1)), s))),
|
||||
|
||||
_ => None
|
||||
},
|
||||
Selected::Scene(s) => match input.event() {
|
||||
key_pat!(Char(',')) => Some(Cmd::Scene(Scene::Swap(s, s - 1))),
|
||||
key_pat!(Char('.')) => Some(Cmd::Scene(Scene::Swap(s, s + 1))),
|
||||
key_pat!(Char('<')) => Some(Cmd::Scene(Scene::Swap(s, s - 1))),
|
||||
key_pat!(Char('>')) => Some(Cmd::Scene(Scene::Swap(s, s + 1))),
|
||||
key_pat!(Char('q')) => Some(Cmd::Scene(Scene::Enqueue(s))),
|
||||
key_pat!(Delete) => Some(Cmd::Scene(Scene::Delete(s))),
|
||||
key_pat!(Char('c')) => Some(Cmd::Scene(Scene::SetColor(s, ItemPalette::random()))),
|
||||
|
||||
key_pat!(Up) => Some(
|
||||
Cmd::Select(if s > 0 { Selected::Scene(s - 1) } else { Selected::Mix })),
|
||||
key_pat!(Down) => Some(
|
||||
Cmd::Select(Selected::Scene((s + 1).min(s_len.saturating_sub(1))))),
|
||||
key_pat!(Left) =>
|
||||
None,
|
||||
key_pat!(Right) => Some(
|
||||
Cmd::Select(Selected::Clip(0, s))),
|
||||
|
||||
_ => None
|
||||
},
|
||||
Selected::Track(t) => match input.event() {
|
||||
key_pat!(Char(',')) => Some(Cmd::Track(Track::Swap(t, t - 1))),
|
||||
key_pat!(Char('.')) => Some(Cmd::Track(Track::Swap(t, t + 1))),
|
||||
key_pat!(Char('<')) => Some(Cmd::Track(Track::Swap(t, t - 1))),
|
||||
key_pat!(Char('>')) => Some(Cmd::Track(Track::Swap(t, t + 1))),
|
||||
key_pat!(Delete) => Some(Cmd::Track(Track::Delete(t))),
|
||||
key_pat!(Char('c')) => Some(Cmd::Track(Track::SetColor(t, ItemPalette::random()))),
|
||||
|
||||
key_pat!(Up) =>
|
||||
None,
|
||||
key_pat!(Down) => Some(
|
||||
Cmd::Select(Selected::Clip(t, 0))),
|
||||
key_pat!(Left) => Some(
|
||||
Cmd::Select(if t > 0 { Selected::Track(t - 1) } else { Selected::Mix })),
|
||||
key_pat!(Right) => Some(
|
||||
Cmd::Select(Selected::Track((t + 1).min(t_len.saturating_sub(1))))),
|
||||
|
||||
_ => None
|
||||
},
|
||||
Selected::Mix => match input.event() {
|
||||
key_pat!(Delete) => Some(Cmd::Clear),
|
||||
key_pat!(Char('0')) => Some(Cmd::StopAll),
|
||||
key_pat!(Char('c')) => Some(Cmd::Color(ItemPalette::random())),
|
||||
|
||||
key_pat!(Up) =>
|
||||
None,
|
||||
key_pat!(Down) => Some(
|
||||
Cmd::Select(Selected::Scene(0))),
|
||||
key_pat!(Left) =>
|
||||
None,
|
||||
key_pat!(Right) => Some(
|
||||
Cmd::Select(Selected::Track(0))),
|
||||
|
||||
_ => None
|
||||
},
|
||||
}
|
||||
}
|
||||
command!(|self: ArrangerCommand, state: ArrangerTui|match self {
|
||||
Self::Scene(cmd) => cmd.execute(state)?.map(Self::Scene),
|
||||
Self::Track(cmd) => cmd.execute(state)?.map(Self::Track),
|
||||
Self::Clip(cmd) => cmd.execute(state)?.map(Self::Clip),
|
||||
Self::Editor(cmd) => cmd.execute(&mut state.editor)?.map(Self::Editor),
|
||||
Self::Clock(cmd) => cmd.execute(state)?.map(Self::Clock),
|
||||
Self::Clock(cmd) => cmd.delegate(state, Self::Clock)?,
|
||||
Self::Clip(cmd) => cmd.delegate(state, Self::Clip)?,
|
||||
Self::Scene(cmd) => cmd.delegate(state, Self::Scene)?,
|
||||
Self::Track(cmd) => cmd.delegate(state, Self::Track)?,
|
||||
Self::Editor(cmd) => cmd.delegate(&mut state.editor, Self::Editor)?,
|
||||
Self::Zoom(_) => { todo!(); },
|
||||
Self::Select(selected) => {
|
||||
*state.selected_mut() = selected;
|
||||
|
|
@ -265,23 +178,20 @@ command!(|self: ArrangerCommand, state: ArrangerTui|match self {
|
|||
Some(Self::Color(old))
|
||||
},
|
||||
Self::Phrases(cmd) => {
|
||||
let mut default = |cmd: PoolCommand|{
|
||||
cmd.execute(&mut state.phrases).map(|x|x.map(Self::Phrases))
|
||||
};
|
||||
match cmd {
|
||||
// autoselect: automatically load selected phrase in editor
|
||||
PoolCommand::Select(_) => {
|
||||
let undo = default(cmd)?;
|
||||
state.editor.set_phrase(Some(state.phrases.phrase()));
|
||||
let undo = cmd.delegate(&mut state.pool, Self::Phrases)?;
|
||||
state.editor.set_phrase(Some(state.pool.phrase()));
|
||||
undo
|
||||
},
|
||||
// reload phrase in editor to update color
|
||||
PoolCommand::Phrase(PhrasePoolCommand::SetColor(index, _)) => {
|
||||
let undo = default(cmd)?;
|
||||
state.editor.set_phrase(Some(state.phrases.phrase()));
|
||||
let undo = cmd.delegate(&mut state.pool, Self::Phrases)?;
|
||||
state.editor.set_phrase(Some(state.pool.phrase()));
|
||||
undo
|
||||
},
|
||||
_ => default(cmd)?
|
||||
_ => cmd.delegate(&mut state.pool, Self::Phrases)?
|
||||
}
|
||||
},
|
||||
Self::History(_) => { todo!() },
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ pub struct Groovebox {
|
|||
pub editor: MidiEditor,
|
||||
pub sampler: Sampler,
|
||||
|
||||
pub compact: bool,
|
||||
pub size: Measure<Tui>,
|
||||
pub status: bool,
|
||||
pub note_buf: Vec<u8>,
|
||||
|
|
@ -47,6 +48,7 @@ impl Groovebox {
|
|||
note_buf: vec![],
|
||||
perf: PerfModel::default(),
|
||||
status: true,
|
||||
compact: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -64,7 +66,7 @@ audio!(|self: Groovebox, client, scope|{
|
|||
if Control::Quit == SamplerAudio(&mut self.sampler).process(client, scope) {
|
||||
return Control::Quit
|
||||
}
|
||||
// TODO move this to sampler:
|
||||
// TODO move these to editor and sampler:
|
||||
for RawMidi { time, bytes } in self.player.midi_ins[0].iter(scope) {
|
||||
if let LiveEvent::Midi { message, .. } = LiveEvent::parse(bytes).unwrap() {
|
||||
match message {
|
||||
|
|
@ -73,22 +75,7 @@ audio!(|self: Groovebox, client, scope|{
|
|||
},
|
||||
MidiMessage::Controller { controller, value } => {
|
||||
if let Some(sample) = &self.sampler.mapped[self.editor.note_point()] {
|
||||
let mut sample = sample.write().unwrap();
|
||||
let percentage = value.as_int() as f64 / 127.;
|
||||
match controller.as_int() {
|
||||
20 => {
|
||||
sample.start = (percentage * sample.end as f64) as usize;
|
||||
},
|
||||
21 => {
|
||||
let length = sample.channels[0].len();
|
||||
sample.end = sample.start + (percentage * (length as f64 - sample.start as f64)) as usize;
|
||||
sample.end = sample.end.min(length);
|
||||
},
|
||||
24 => {
|
||||
sample.gain = percentage as f32 * 2.0;
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
sample.write().unwrap().handle_cc(controller, value)
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
|
|
@ -101,23 +88,23 @@ audio!(|self: Groovebox, client, scope|{
|
|||
render!(Tui: (self: Groovebox) => {
|
||||
let w = self.size.w();
|
||||
let phrase_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 };
|
||||
let pool_w = if self.pool.visible { phrase_w } else { 0 };
|
||||
let sampler_w = if self.pool.visible { phrase_w } else { 4 };
|
||||
let sample_h = if self.pool.visible { 8 } else { 0 };
|
||||
let pool_w = if !self.compact { phrase_w } else { 5 };
|
||||
let sampler_w = if !self.compact { phrase_w } else { 4 };
|
||||
let sample_h = if !self.compact { 5 } else { 0 };
|
||||
let note_pt = self.editor.note_point();
|
||||
let color = self.player.play_phrase().as_ref()
|
||||
.and_then(|(_,p)|p.as_ref().map(|p|p.read().unwrap().color))
|
||||
.clone();
|
||||
|
||||
let sampler =
|
||||
Fixed::x(sampler_w, Push::y(1, Align::w(Fill::y(SampleList::new(&self.sampler, &self.editor)))));
|
||||
Fixed::x(sampler_w, Push::y(1, Align::w(Fill::y(SampleList::new(self.compact, &self.sampler, &self.editor)))));
|
||||
let selectors =
|
||||
Bsp::e(ClipSelected::play_phrase(&self.player), ClipSelected::next_phrase(&self.player));
|
||||
let edit_clip =
|
||||
MidiEditClip(&self.editor);
|
||||
|
||||
self.size.of(Bsp::s(
|
||||
Fill::x(Fixed::y(if self.pool.visible { 3 } else { 1 }, lay!(
|
||||
Fill::x(Fixed::y(2, lay!(
|
||||
Align::w(Meter("L/", self.sampler.input_meter[0])),
|
||||
Align::e(Meter("R/", self.sampler.input_meter[1])),
|
||||
Align::x(Tui::bg(TuiTheme::g(32), TransportView::new(true, &self.player.clock))),
|
||||
|
|
@ -141,7 +128,7 @@ render!(Tui: (self: Groovebox) => {
|
|||
Align::x(Fixed::y(1, MidiEditStatus(&self.editor))),
|
||||
),
|
||||
Bsp::w(
|
||||
Fixed::x(pool_w, Align::e(Fill::y(PoolView(&self.pool)))),
|
||||
Fixed::x(pool_w, Align::e(Fill::y(PoolView(self.compact, &self.pool)))),
|
||||
Fill::xy(Bsp::e(sampler, &self.editor)),
|
||||
),
|
||||
)
|
||||
|
|
@ -151,6 +138,7 @@ render!(Tui: (self: Groovebox) => {
|
|||
});
|
||||
|
||||
pub enum GrooveboxCommand {
|
||||
Compact(bool),
|
||||
History(isize),
|
||||
Clock(ClockCommand),
|
||||
Pool(PoolCommand),
|
||||
|
|
@ -172,8 +160,8 @@ input_to_command!(GrooveboxCommand: <Tui>|state: Groovebox, input|match input.ev
|
|||
if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) }
|
||||
),
|
||||
|
||||
// Tab: Toggle visibility of phrase pool column
|
||||
key_pat!(Tab) => Cmd::Pool(PoolCommand::Show(!state.pool.visible)),
|
||||
// Tab: Toggle visibility of sidebars
|
||||
key_pat!(Tab) => Cmd::Compact(!state.compact),
|
||||
|
||||
// q: Enqueue currently edited phrase
|
||||
key_pat!(Char('q')) => Cmd::Enqueue(Some(state.pool.phrase().clone())),
|
||||
|
|
@ -211,9 +199,6 @@ input_to_command!(GrooveboxCommand: <Tui>|state: Groovebox, input|match input.ev
|
|||
});
|
||||
|
||||
command!(|self: GrooveboxCommand, state: Groovebox|match self {
|
||||
Self::History(delta) => {
|
||||
todo!("undo/redo")
|
||||
},
|
||||
Self::Enqueue(phrase) => {
|
||||
state.player.enqueue_next(phrase.as_ref());
|
||||
None
|
||||
|
|
@ -235,7 +220,14 @@ command!(|self: GrooveboxCommand, state: Groovebox|match self {
|
|||
_ => cmd.delegate(&mut state.pool, Self::Pool)?
|
||||
}
|
||||
},
|
||||
Self::Sampler(cmd) => cmd.delegate(&mut state.sampler, Self::Sampler)?,
|
||||
Self::Editor(cmd) => cmd.delegate(&mut state.editor, Self::Editor)?,
|
||||
Self::Clock(cmd) => cmd.delegate(state, Self::Clock)?,
|
||||
Self::Sampler(cmd) => cmd.delegate(&mut state.sampler, Self::Sampler)?,
|
||||
Self::Editor(cmd) => cmd.delegate(&mut state.editor, Self::Editor)?,
|
||||
Self::Clock(cmd) => cmd.delegate(state, Self::Clock)?,
|
||||
Self::History(delta) => { todo!("undo/redo") },
|
||||
Self::Compact(compact) => if state.compact != compact {
|
||||
state.compact = compact;
|
||||
Some(Self::Compact(!compact))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ render!(Tui: (self: PianoHorizontal) => Bsp::s(
|
|||
impl PianoHorizontal {
|
||||
/// Draw the piano roll foreground using full blocks on note on and half blocks on legato: █▄ █▄ █▄
|
||||
fn draw_bg (buf: &mut BigBuffer, phrase: &MidiClip, zoom: usize, note_len: usize) {
|
||||
for (y, note) in (0..127).rev().enumerate() {
|
||||
for (y, note) in (0..=127).rev().enumerate() {
|
||||
for (x, time) in (0..buf.width).map(|x|(x, x*zoom)) {
|
||||
let cell = buf.get_mut(x, y).unwrap();
|
||||
cell.set_bg(phrase.color.darkest.rgb);
|
||||
|
|
@ -47,7 +47,7 @@ impl PianoHorizontal {
|
|||
let mut notes_on = [false;128];
|
||||
for (x, time_start) in (0..phrase.length).step_by(zoom).enumerate() {
|
||||
|
||||
for (y, note) in (0..127).rev().enumerate() {
|
||||
for (y, note) in (0..=127).rev().enumerate() {
|
||||
if let Some(cell) = buf.get_mut(x, note) {
|
||||
if notes_on[note] {
|
||||
cell.set_char('▂');
|
||||
|
|
|
|||
34
src/pool.rs
34
src/pool.rs
|
|
@ -1,14 +1,10 @@
|
|||
mod pool_tui; pub use self::pool_tui::*;
|
||||
mod clip_length; pub use self::clip_length::*;
|
||||
mod clip_rename; pub use self::clip_rename::*;
|
||||
mod clip_select; pub use self::clip_select::*;
|
||||
|
||||
use super::*;
|
||||
|
||||
pub mod phrase_length;
|
||||
pub(crate) use phrase_length::*;
|
||||
|
||||
pub mod phrase_rename;
|
||||
pub(crate) use phrase_rename::*;
|
||||
|
||||
pub mod phrase_selector;
|
||||
pub(crate) use phrase_selector::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PoolModel {
|
||||
pub(crate) visible: bool,
|
||||
|
|
@ -205,23 +201,3 @@ impl PoolModel {
|
|||
}
|
||||
}
|
||||
}
|
||||
pub struct PoolView<'a>(pub(crate) &'a PoolModel);
|
||||
render!(Tui: (self: PoolView<'a>) => {
|
||||
let PoolModel { phrases, mode, .. } = self.0;
|
||||
let color = self.0.phrase().read().unwrap().color;
|
||||
Outer(
|
||||
Style::default().fg(color.dark.rgb).bg(color.darkest.rgb)
|
||||
).enclose(Tui::map(||self.0.phrases().iter(), |clip, i|{
|
||||
let MidiClip { ref name, color, length, .. } = *clip.read().unwrap();
|
||||
let item_height = 1;
|
||||
let item_offset = i as u16 * item_height;
|
||||
let selected = i == self.0.phrase_index();
|
||||
let offset = |a|Push::y(item_offset, Align::n(Fixed::y(item_height, Fill::x(a))));
|
||||
offset(Tui::bg(if selected { color.light.rgb } else { color.base.rgb }, lay!(
|
||||
Align::w(Tui::fg(color.lightest.rgb, Tui::bold(selected, format!(" {i:>3} {name}")))),
|
||||
Align::e(Tui::fg(color.lightest.rgb, Tui::bold(selected, format!("{length} ")))),
|
||||
Align::w(Tui::when(selected, Tui::bold(true, Tui::fg(TuiTheme::g(255), "▶")))),
|
||||
Align::e(Tui::when(selected, Tui::bold(true, Tui::fg(TuiTheme::g(255), "◀")))),
|
||||
)))
|
||||
}))
|
||||
});
|
||||
|
|
|
|||
27
src/pool/pool_tui.rs
Normal file
27
src/pool/pool_tui.rs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
use crate::*;
|
||||
|
||||
pub struct PoolView<'a>(pub bool, pub &'a PoolModel);
|
||||
render!(Tui: (self: PoolView<'a>) => {
|
||||
let Self(compact, model) = self;
|
||||
let PoolModel { phrases, mode, .. } = self.1;
|
||||
let color = self.1.phrase().read().unwrap().color;
|
||||
Outer(
|
||||
Style::default().fg(color.dark.rgb).bg(color.darkest.rgb)
|
||||
).enclose(Tui::map(||model.phrases().iter(), |clip, i|{
|
||||
let item_height = 1;
|
||||
let item_offset = i as u16 * item_height;
|
||||
let selected = i == model.phrase_index();
|
||||
let offset = |a|Push::y(item_offset, Align::n(Fixed::y(item_height, Fill::x(a))));
|
||||
|
||||
let MidiClip { ref name, color, length, .. } = *clip.read().unwrap();
|
||||
let name = if *compact { format!(" {i:>3}") } else { format!(" {i:>3} {name}") };
|
||||
let length = if *compact { String::default() } else { format!("{length} ") };
|
||||
|
||||
offset(Tui::bg(if selected { color.light.rgb } else { color.base.rgb }, lay!(
|
||||
Align::w(Tui::fg(color.lightest.rgb, Tui::bold(selected, name))),
|
||||
Align::e(Tui::fg(color.lightest.rgb, Tui::bold(selected, length))),
|
||||
Align::w(Tui::when(selected, Tui::bold(true, Tui::fg(TuiTheme::g(255), "▶")))),
|
||||
Align::e(Tui::when(selected, Tui::bold(true, Tui::fg(TuiTheme::g(255), "◀")))),
|
||||
)))
|
||||
}))
|
||||
});
|
||||
|
|
@ -118,4 +118,26 @@ impl Sample {
|
|||
}
|
||||
Ok(())
|
||||
}
|
||||
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 */ }
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,26 @@
|
|||
use crate::*;
|
||||
|
||||
pub struct SampleList<'a>(&'a Sampler, &'a MidiEditor);
|
||||
pub struct SampleList<'a> {
|
||||
compact: bool,
|
||||
sampler: &'a Sampler,
|
||||
editor: &'a MidiEditor
|
||||
}
|
||||
|
||||
impl<'a> SampleList<'a> {
|
||||
pub fn new (sampler: &'a Sampler, editor: &'a MidiEditor) -> Self {
|
||||
Self(sampler, editor)
|
||||
pub fn new (compact: bool, sampler: &'a Sampler, editor: &'a MidiEditor) -> Self {
|
||||
Self { compact, sampler, editor }
|
||||
}
|
||||
}
|
||||
|
||||
render!(Tui: (self: SampleList<'a>) => {
|
||||
let Self(sampler, editor) = self;
|
||||
let Self { compact, sampler, editor } = self;
|
||||
let note_lo = editor.note_lo().load(Relaxed);
|
||||
let note_pt = editor.note_point();
|
||||
let note_hi = editor.note_hi();
|
||||
Fill::xy(Tui::map(move||(note_lo..=note_hi).rev(), move|note, i| {
|
||||
|
||||
let offset = |a|Push::y(i as u16, Align::n(Fixed::y(1, Fill::x(a))));
|
||||
|
||||
let mut bg = if note == note_pt { TuiTheme::g(64) } else { Color::Reset };
|
||||
let mut fg = TuiTheme::g(160);
|
||||
if let Some((index, _)) = sampler.recording {
|
||||
|
|
@ -24,11 +31,14 @@ render!(Tui: (self: SampleList<'a>) => {
|
|||
} else if sampler.mapped[note].is_some() {
|
||||
fg = TuiTheme::g(224);
|
||||
}
|
||||
let offset = |a|Push::y(i as u16, Align::n(Fixed::y(1, Fill::x(a))));
|
||||
offset(Tui::bg(bg, if let Some(sample) = &sampler.mapped[note] {
|
||||
Tui::fg(fg, format!("{note:3} ?????? "))
|
||||
|
||||
offset(Tui::fg_bg(fg, bg, format!("{note:3} {}", if *compact {
|
||||
""
|
||||
} else if let Some(sample) = &sampler.mapped[note] {
|
||||
"??????"
|
||||
} else {
|
||||
Tui::fg(fg, format!("{note:3} (none) "))
|
||||
}))
|
||||
"(none)"
|
||||
})))
|
||||
|
||||
}))
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,12 +7,14 @@ use PhrasePoolCommand::*;
|
|||
/// Root view for standalone `tek_sequencer`.
|
||||
pub struct SequencerTui {
|
||||
_jack: Arc<RwLock<JackConnection>>,
|
||||
|
||||
pub pool: PoolModel,
|
||||
pub editor: MidiEditor,
|
||||
pub player: MidiPlayer,
|
||||
|
||||
pub transport: bool,
|
||||
pub selectors: bool,
|
||||
pub clock: Clock,
|
||||
pub pool: PoolModel,
|
||||
pub player: MidiPlayer,
|
||||
pub editor: MidiEditor,
|
||||
pub size: Measure<Tui>,
|
||||
pub status: bool,
|
||||
pub note_buf: Vec<u8>,
|
||||
|
|
@ -26,12 +28,14 @@ from_jack!(|jack|SequencerTui {
|
|||
None, Some(ItemColor::random().into())
|
||||
)));
|
||||
Self {
|
||||
_jack: jack.clone(),
|
||||
_jack: jack.clone(),
|
||||
|
||||
pool: PoolModel::from(&phrase),
|
||||
editor: MidiEditor::from(&phrase),
|
||||
player: MidiPlayer::from((&clock, &phrase)),
|
||||
|
||||
transport: true,
|
||||
selectors: true,
|
||||
pool: PoolModel::from(&phrase),
|
||||
editor: MidiEditor::from(&phrase),
|
||||
player: MidiPlayer::from((&clock, &phrase)),
|
||||
size: Measure::new(),
|
||||
midi_buf: vec![vec![];65536],
|
||||
note_buf: vec![],
|
||||
|
|
@ -44,7 +48,7 @@ render!(Tui: (self: SequencerTui) => {
|
|||
let w = self.size.w();
|
||||
let phrase_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 };
|
||||
let pool_w = if self.pool.visible { phrase_w } else { 0 };
|
||||
let pool = Pull::y(1, Fill::y(Align::e(PoolView(&self.pool))));
|
||||
let pool = Pull::y(1, Fill::y(Align::e(PoolView(self.pool.visible, &self.pool))));
|
||||
let with_pool = move|x|Bsp::w(Fixed::x(pool_w, pool), x);
|
||||
let status = SequencerStatus::from(self);
|
||||
let with_status = |x|Bsp::n(Fixed::x(if self.status { 2 } else { 0 }, status), x);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue