groovebox: looks surprisingly well

This commit is contained in:
🪞👃🪞 2025-05-14 22:17:42 +03:00
parent c66a006120
commit 03024f8a14
11 changed files with 130 additions and 91 deletions

View file

@ -14,5 +14,7 @@
(view (bsp/a :view-dialog (bsp/w :view-meters-output (bsp/e :view-meters-input
(bsp/n (fixed/y 6 (bsp/e :view-sample-status :view-sample-viewer))
(bsp/e
(fill/y (align/n (bsp/s :view-status-v (bsp/s :view-ports-status (bsp/s :view-editor-status :view-pool)))))
(fill/y (align/n (bsp/s :view-status-v (bsp/s
(bsp/s :view-midi-ports-status :view-audio-ports-status)
(bsp/n :view-editor-status :view-pool)))))
(bsp/e :view-samples-keys :view-editor)))))))

View file

@ -244,22 +244,22 @@ impl App {
Some(Dialog::Menu(0))
}
fn dialog_save (&self) -> Option<Dialog> {
Some(Dialog::Browser(BrowserTarget::SaveProject, Default::default()))
Some(Dialog::Browser(BrowserTarget::SaveProject, Browser::new(None).unwrap()))
}
fn dialog_load (&self) -> Option<Dialog> {
Some(Dialog::Browser(BrowserTarget::LoadProject, Default::default()))
Some(Dialog::Browser(BrowserTarget::LoadProject, Browser::new(None).unwrap()))
}
fn dialog_import_clip (&self) -> Option<Dialog> {
Some(Dialog::Browser(BrowserTarget::ImportClip(Default::default()), Default::default()))
Some(Dialog::Browser(BrowserTarget::ImportClip(Default::default()), Browser::new(None).unwrap()))
}
fn dialog_export_clip (&self) -> Option<Dialog> {
Some(Dialog::Browser(BrowserTarget::ExportClip(Default::default()), Default::default()))
Some(Dialog::Browser(BrowserTarget::ExportClip(Default::default()), Browser::new(None).unwrap()))
}
fn dialog_import_sample (&self) -> Option<Dialog> {
Some(Dialog::Browser(BrowserTarget::ImportSample(Default::default()), Default::default()))
Some(Dialog::Browser(BrowserTarget::ImportSample(Default::default()), Browser::new(None).unwrap()))
}
fn dialog_export_sample (&self) -> Option<Dialog> {
Some(Dialog::Browser(BrowserTarget::ExportSample(Default::default()), Default::default()))
Some(Dialog::Browser(BrowserTarget::ExportSample(Default::default()), Browser::new(None).unwrap()))
}
fn dialog_options (&self) -> Option<Dialog> {
Some(Dialog::Options)

View file

@ -19,7 +19,7 @@ impl App {
let cache = self.view_cache.read().unwrap();
let theme = self.color;
let playing = self.clock().is_rolling();
Fixed::xy(20, 7, col!(
Fixed::xy(20, 6, col!(
Fill::x(Align::w(Bsp::e(
Align::w(Tui::bg(if playing { Rgb(0, 128, 0) } else { Rgb(128, 64, 0) },
Either::new(false, // TODO
@ -60,22 +60,33 @@ impl App {
self.editor()
}
pub fn view_editor_status (&self) -> impl Content<TuiOut> + use<'_> {
Fixed::y(5, self.editor().map(|e|Bsp::s(e.clip_status(), e.edit_status())))
self.editor().map(|e|Bsp::s(e.clip_status(), e.edit_status()))
}
pub fn view_ports_status (&self) -> impl Content<TuiOut> + use<'_> {
pub fn view_midi_ports_status (&self) -> impl Content<TuiOut> + use<'_> {
self.project.get_track().map(|track|Bsp::s(
Fixed::y(2.max(track.sequencer.midi_ins.len() as u16), Align::n(Map::south(1,
||track.sequencer.midi_ins.iter(),
|port, index|Fixed::x(20, FieldV(
Fixed::xy(20, 1 + track.sequencer.midi_ins.len() as u16, FieldV(self.color,
format!("MIDI ins: "),
Map::south(1, ||track.sequencer.midi_ins.iter(),
|port, index|Fill::x(Align::w(format!(" {index} {}", port.name())))))),
Fixed::xy(20, 1 + track.sequencer.midi_outs.len() as u16, FieldV(
self.color,
format!("MIDI in {index}: "),
format!("{}", port.name())))))),
Fixed::y(2.max(track.sequencer.midi_outs.len() as u16), Align::n(Map::south(1,
||track.sequencer.midi_outs.iter(),
|port, index|Fixed::x(20, FieldV(
self.color,
format!("MIDI out {index}:"),
format!("{}", port.name()))))))
format!("MIDI outs: "),
Map::south(1, ||track.sequencer.midi_outs.iter(),
|port, index|Fill::x(Align::w(format!(" {index} {}", port.name()))))))
))
}
pub fn view_audio_ports_status (&self) -> impl Content<TuiOut> + use<'_> {
self.project.get_track().map(|track|Bsp::s(
track.devices.get(0).map(|device|
Fixed::xy(20, 1 + device.audio_ins().len() as u16, FieldV(self.color,
format!("Audio ins: "),
Map::south(1, ||device.audio_ins().iter(),
|port, index|Fill::x(Align::w(format!(" {index} {}", port.name()))))))),
track.devices.last().map(|device|
Fixed::xy(20, 1 + device.audio_outs().len() as u16, FieldV(self.color,
format!("Audio outs:"),
Map::south(1, ||device.audio_outs().iter(),
|port, index|Fill::x(Align::w(format!(" {index} {}", port.name()))))))),
))
}
pub fn view_arranger (&self) -> impl Content<TuiOut> + use<'_> {
@ -83,8 +94,8 @@ impl App {
}
pub fn view_pool (&self) -> impl Content<TuiOut> + use<'_> {
Fixed::x(20, Bsp::s(
Fill::x(Align::w(Bsp::s("", FieldH(self.color, "Pool", "")))),
Fill::y(Align::n(Tui::bg(Black, PoolView(&self.project.pool)))),
Fill::x(Align::w(FieldH(self.color, "MIDI clip pool:", ""))),
Fill::y(Align::n(Tui::bg(Rgb(0, 0, 0), PoolView(&self.project.pool)))),
))
}
pub fn view_samples_keys (&self) -> impl Content<TuiOut> + use<'_> {

View file

@ -1,19 +1,33 @@
use crate::*;
content!(TuiOut: |self: Browser| /*Stack::down(|add|{
let mut i = 0;
for (_, name) in self.dirs.iter() {
if i >= self.scroll {
add(&Tui::bold(i == self.index, name.as_str()))?;
content!(TuiOut: |self: Browser|Map::south(1, ||EntriesIterator {
offset: 0,
index: 0,
length: self.dirs.len() + self.files.len(),
browser: self,
}, |entry, index|Fill::x(Align::w(entry))));
struct EntriesIterator<'a> {
browser: &'a Browser,
offset: usize,
length: usize,
index: usize,
}
i += 1;
impl<'a> Iterator for EntriesIterator<'a> {
type Item = Modify<&'a str>;
fn next (&mut self) -> Option<Self::Item> {
let dirs = self.browser.dirs.len();
let files = self.browser.files.len();
let index = self.index;
if self.index < dirs {
self.index += 1;
Some(Tui::bold(true, self.browser.dirs[index].1.as_str()))
} else if self.index < dirs + files {
self.index += 1;
Some(Tui::bold(false, self.browser.files[index - dirs].1.as_str()))
} else {
None
}
for (_, name) in self.files.iter() {
if i >= self.scroll {
add(&Tui::bold(i == self.index, name.as_str()))?;
}
i += 1;
}
add(&format!("{}/{i}", self.index))?;
Ok(())
})*/"todo");

View file

@ -37,6 +37,30 @@ impl Device {
_ => todo!(),
}
}
pub fn midi_ins (&self) -> &[JackMidiIn] {
match self {
//Self::Sampler(Sampler { midi_in, .. }) => &[midi_in],
_ => todo!()
}
}
pub fn midi_outs (&self) -> &[JackMidiOut] {
match self {
Self::Sampler(_) => &[],
_ => todo!()
}
}
pub fn audio_ins (&self) -> &[JackAudioIn] {
match self {
Self::Sampler(Sampler { audio_ins, .. }) => audio_ins.as_slice(),
_ => todo!()
}
}
pub fn audio_outs (&self) -> &[JackAudioOut] {
match self {
Self::Sampler(Sampler { audio_outs, .. }) => audio_outs.as_slice(),
_ => todo!()
}
}
}
pub struct DeviceAudio<'a>(pub &'a mut Device);

View file

@ -30,8 +30,9 @@ impl MidiEditor {
let note_name = format!("{:4}", Note::pitch_to_name(note_pos));
let note_pos = format!("{:>3}", note_pos);
let note_len = format!("{:>4}", self.get_note_len());
Fixed::x(20, Bsp::s(
Fill::x(Align::w(FieldH(color, "Time", format!("{length}/{time_zoom}+{time_pos} {time_lock}")))),
Fixed::x(20, col!(
Fill::x(Align::w(FieldH(color, "Time", format!("{length}/{time_zoom}+{time_pos}")))),
Fill::x(Align::w(FieldH(color, "Lock", format!("{time_lock}")))),
Fill::x(Align::w(FieldH(color, "Note", format!("{note_name} {note_pos} {note_len}")))),
))
}

View file

@ -1,6 +1,7 @@
#![feature(let_chains)]
#![feature(trait_alias)]
#![feature(if_let_guard)]
#![feature(impl_trait_in_assoc_type)]
pub(crate) use std::cmp::Ord;
pub(crate) use std::fmt::{Debug, Formatter};

View file

@ -7,7 +7,6 @@ impl Sampler {
/// 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;
if let Some(ref midi_in) = midi_in {
for RawMidi { time, bytes } in midi_in.port().iter(scope) {
if let LiveEvent::Midi { message, .. } = LiveEvent::parse(bytes).unwrap() {
match message {
@ -24,7 +23,6 @@ impl Sampler {
}
}
}
}
}

View file

@ -22,7 +22,7 @@ pub struct Sampler {
/// Sample currently being edited.
pub editing: Option<Arc<RwLock<Sample>>>,
/// MIDI input port. Triggers sample playback.
pub midi_in: Option<JackMidiIn>,
pub midi_in: JackMidiIn,
/// Collection of currently playing instances of samples.
pub voices: Arc<RwLock<Vec<Voice>>>,
/// Audio output ports. Voices get played here.
@ -47,15 +47,28 @@ pub struct Sampler {
pub cursor: (AtomicUsize, AtomicUsize),
}
impl Default for Sampler {
fn default () -> Self {
Self {
midi_in: None,
audio_ins: vec![],
impl Sampler {
pub fn new (
jack: &Jack,
name: impl AsRef<str>,
midi_from: &[PortConnect],
audio_from: &[&[PortConnect];2],
audio_to: &[&[PortConnect];2],
) -> Usually<Self> {
let name = name.as_ref();
Ok(Self {
name: name.into(),
midi_in: JackMidiIn::new(jack, format!("M/{name}"), midi_from)?,
audio_ins: vec![
JackAudioIn::new(jack, &format!("L/{name}"), audio_from[0])?,
JackAudioIn::new(jack, &format!("R/{name}"), audio_from[1])?,
],
audio_outs: vec![
JackAudioOut::new(jack, &format!("{name}/L"), audio_to[0])?,
JackAudioOut::new(jack, &format!("{name}/R"), audio_to[1])?,
],
input_meters: vec![0.0;2],
output_meters: vec![0.0;2],
audio_outs: vec![],
name: "tek_sampler".into(),
mapped: [const { None };128],
unmapped: vec![],
voices: Arc::new(RwLock::new(vec![])),
@ -71,31 +84,6 @@ impl Default for Sampler {
color: Default::default(),
mixing_mode: Default::default(),
metering_mode: Default::default(),
}
}
}
impl Sampler {
pub fn new (
jack: &Jack,
name: impl AsRef<str>,
midi_from: &[PortConnect],
audio_from: &[&[PortConnect];2],
audio_to: &[&[PortConnect];2],
) -> Usually<Self> {
let name = name.as_ref();
Ok(Self {
name: name.into(),
midi_in: Some(JackMidiIn::new(jack, format!("M/{name}"), midi_from)?),
audio_ins: vec![
JackAudioIn::new(jack, &format!("L/{name}"), audio_from[0])?,
JackAudioIn::new(jack, &format!("R/{name}"), audio_from[1])?,
],
audio_outs: vec![
JackAudioOut::new(jack, &format!("{name}/L"), audio_to[0])?,
JackAudioOut::new(jack, &format!("{name}/R"), audio_to[1])?,
],
..Default::default()
})
}
/// Value of cursor

2
deps/tengri vendored

@ -1 +1 @@
Subproject commit 8bfd1a23a1f880a1d2fb104a158fc51f244acd6e
Subproject commit f7306de55faea42bb1531955a92220fd48d9aadb