mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
Compare commits
9 commits
0f16c89248
...
5a360d02fa
| Author | SHA1 | Date | |
|---|---|---|---|
| 5a360d02fa | |||
| 094d5dd451 | |||
| 182107bfa5 | |||
| 2013bac62f | |||
| 03024f8a14 | |||
| c66a006120 | |||
| b7152ef807 | |||
| 4a9e9132f3 | |||
| d45bd2122e |
27 changed files with 507 additions and 212 deletions
|
|
@ -2,14 +2,6 @@
|
|||
|
||||
(info "A session grid.")
|
||||
|
||||
(view
|
||||
(bsp/a :view-dialog
|
||||
(bsp/s (fixed/y 1 :view-transport)
|
||||
(bsp/n (fixed/y 1 :view-status)
|
||||
(fill/xy (bsp/a
|
||||
(fill/xy (align/e :view-pool))
|
||||
:view-arranger))))))
|
||||
|
||||
(keys
|
||||
(layer-if :focus-message "./keys_message.edn")
|
||||
(layer-if :focus-device-add "./keys_device_add.edn")
|
||||
|
|
@ -24,3 +16,17 @@
|
|||
(layer "./keys_clock.edn")
|
||||
(layer "./keys_arranger.edn")
|
||||
(layer "./keys_global.edn"))
|
||||
|
||||
(view (bsp/a :view-dialog
|
||||
(bsp/s
|
||||
(fixed/y 8 (bsp/e
|
||||
(fixed/x 20 (fill/y (align/n (bsp/s :view-status-v
|
||||
(bsp/s :view-audio-ports-status :view-editor-status)))))
|
||||
(fill/xy (align/n (bsp/s :view-arranger-track-names
|
||||
(bsp/s :view-arranger-track-outputs
|
||||
(bsp/s :view-arranger-track-devices :view-arranger-track-inputs)))))))
|
||||
(bsp/w
|
||||
(fixed/x 20 :view-pool)
|
||||
(bsp/e
|
||||
(fixed/x 20 (fill/xy (align/n :view-arranger-scene-names)))
|
||||
(fill/xy (align/n :view-arranger-scene-clips)))))))
|
||||
|
|
|
|||
|
|
@ -2,18 +2,6 @@
|
|||
|
||||
(info "A sequencer with built-in sampler.")
|
||||
|
||||
(view
|
||||
(bsp/a :view-dialog
|
||||
(bsp/s (fixed/y 1 :view-transport)
|
||||
(bsp/n (fixed/y 1 :view-status)
|
||||
(bsp/w :view-meters-output
|
||||
(bsp/e :view-meters-input
|
||||
(bsp/n :view-sample-info
|
||||
(bsp/n (fixed/y 5 :view-sample-viewer)
|
||||
(bsp/w :view-pool
|
||||
(bsp/e :view-samples-keys
|
||||
(fill/y :view-editor)))))))))))
|
||||
|
||||
(keys
|
||||
(layer-if :focus-browser "./keys_browser.edn")
|
||||
(layer-if :focus-pool-rename "./keys_rename.edn")
|
||||
|
|
@ -22,3 +10,11 @@
|
|||
(layer "./keys_editor.edn")
|
||||
(layer "./keys_sampler.edn")
|
||||
(layer "./keys_global.edn"))
|
||||
|
||||
(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
|
||||
(bsp/s :view-midi-ports-status :view-audio-ports-status)
|
||||
(bsp/n :view-editor-status :view-pool)))))
|
||||
(bsp/e :view-samples-keys :view-editor)))))))
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@
|
|||
|
||||
(info "A sampling soundboard.")
|
||||
|
||||
(keys
|
||||
(layer "./keys_sampler.edn")
|
||||
(layer "./keys_global.edn"))
|
||||
|
||||
(view
|
||||
(bsp/a :view-dialog
|
||||
(bsp/s (fixed/y 1 :view-transport)
|
||||
(bsp/n (fixed/y 1 :view-status)
|
||||
(fill/xy :view-samples-grid)))))
|
||||
|
||||
(keys
|
||||
(layer "./keys_sampler.edn")
|
||||
(layer "./keys_global.edn"))
|
||||
|
|
|
|||
|
|
@ -2,14 +2,6 @@
|
|||
|
||||
(info "A MIDI sequencer.")
|
||||
|
||||
(view
|
||||
(bsp/a :view-dialog
|
||||
(bsp/s (fixed/y 1 :view-transport)
|
||||
(bsp/n (fixed/y 1 :view-status)
|
||||
(fill/xy (bsp/a
|
||||
(fill/xy (align/e :view-pool))
|
||||
:view-editor)))))
|
||||
|
||||
(keys
|
||||
(layer-if :focus-browser "./keys_browser.edn")
|
||||
(layer-if :mode-pool-rename "./keys_rename.edn")
|
||||
|
|
@ -17,3 +9,11 @@
|
|||
(layer "./keys_editor.edn")
|
||||
(layer "./keys_clock.edn")
|
||||
(layer "./keys_global.edn"))
|
||||
|
||||
(view
|
||||
(bsp/a :view-dialog
|
||||
(bsp/s (fixed/y 1 :view-transport)
|
||||
(bsp/n (fixed/y 1 :view-status)
|
||||
(fill/xy (bsp/a
|
||||
(fill/xy (align/e :view-pool))
|
||||
:view-editor)))))
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
(info "A JACK transport controller.")
|
||||
|
||||
(view :view-transport)
|
||||
|
||||
(keys
|
||||
(layer "./keys_clock.edn")
|
||||
(layer "./keys_global.edn"))
|
||||
|
||||
(view :view-transport)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
(@esc dialog :dialog-none)
|
||||
(@f1 dialog :dialog-help)
|
||||
(@f6 dialog :dialog-save)
|
||||
(@f8 dialog :dialog-options)
|
||||
(@f9 dialog :dialog-load)
|
||||
(@f10 dialog :dialog-quit)
|
||||
(@esc dialog :dialog-none)
|
||||
(@f1 dialog :dialog-help)
|
||||
(@f6 dialog :dialog-save)
|
||||
(@f8 dialog :dialog-options)
|
||||
(@f9 dialog :dialog-load)
|
||||
(@f10 dialog :dialog-quit)
|
||||
|
||||
(@u undo 1)
|
||||
(@r redo 1)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,11 @@
|
|||
(@down sampler select :sample-below)
|
||||
(@left sampler select :sample-to-left)
|
||||
(@right sampler select :sample-to-right)
|
||||
|
||||
(@r sampler record-toggle :sample-selected)
|
||||
(@shift-R sampler record-cancel)
|
||||
(@p sampler play-sample :sample-selected)
|
||||
(@P sampler stop-sample :sample-selected)
|
||||
|
||||
(@shift-f6 dialog :dialog-import-sample)
|
||||
(@shift-f9 dialog :dialog-export-sample)
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ impl App {
|
|||
}
|
||||
pub fn browser (&self) -> Option<&Browser> {
|
||||
self.dialog.as_ref().and_then(|dialog|match dialog {
|
||||
Dialog::Save(b) | Dialog::Load(b) => Some(b),
|
||||
Dialog::Browser(_, b) => Some(b),
|
||||
_ => None
|
||||
})
|
||||
}
|
||||
|
|
@ -156,11 +156,20 @@ pub enum Dialog {
|
|||
Menu(usize),
|
||||
Device(usize),
|
||||
Message(Message),
|
||||
Save(Browser),
|
||||
Load(Browser),
|
||||
Browser(BrowserTarget, Browser),
|
||||
Options,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum BrowserTarget {
|
||||
SaveProject,
|
||||
LoadProject,
|
||||
ImportSample(Arc<RwLock<Option<Sample>>>),
|
||||
ExportSample(Arc<RwLock<Option<Sample>>>),
|
||||
ImportClip(Arc<RwLock<Option<MidiClip>>>),
|
||||
ExportClip(Arc<RwLock<Option<MidiClip>>>),
|
||||
}
|
||||
|
||||
/// Various possible messages
|
||||
#[derive(PartialEq, Clone, Copy, Debug)]
|
||||
pub enum Message {
|
||||
|
|
@ -235,10 +244,22 @@ impl App {
|
|||
Some(Dialog::Menu(0))
|
||||
}
|
||||
fn dialog_save (&self) -> Option<Dialog> {
|
||||
Some(Dialog::Save(Default::default()))
|
||||
Some(Dialog::Browser(BrowserTarget::SaveProject, Browser::new(None).unwrap()))
|
||||
}
|
||||
fn dialog_load (&self) -> Option<Dialog> {
|
||||
Some(Dialog::Load(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()), Browser::new(None).unwrap()))
|
||||
}
|
||||
fn dialog_export_clip (&self) -> Option<Dialog> {
|
||||
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()), Browser::new(None).unwrap()))
|
||||
}
|
||||
fn dialog_export_sample (&self) -> Option<Dialog> {
|
||||
Some(Dialog::Browser(BrowserTarget::ExportSample(Default::default()), Browser::new(None).unwrap()))
|
||||
}
|
||||
fn dialog_options (&self) -> Option<Dialog> {
|
||||
Some(Dialog::Options)
|
||||
|
|
|
|||
|
|
@ -14,6 +14,36 @@ impl App {
|
|||
pub fn view_nil (&self) -> impl Content<TuiOut> + use<'_> {
|
||||
"nil"
|
||||
}
|
||||
pub fn view_status_v (&self) -> impl Content<TuiOut> + use<'_> {
|
||||
self.update_clock();
|
||||
let cache = self.view_cache.read().unwrap();
|
||||
let theme = self.color;
|
||||
let playing = self.clock().is_rolling();
|
||||
Tui::bg(theme.darkest.rgb, 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
|
||||
Thunk::new(move||Fixed::x(9, Either::new(playing,
|
||||
Tui::fg(Rgb(0, 255, 0), " PLAYING "),
|
||||
Tui::fg(Rgb(255, 128, 0), " STOPPED ")))
|
||||
),
|
||||
Thunk::new(move||Fixed::x(5, Either::new(playing,
|
||||
Tui::fg(Rgb(0, 255, 0), Bsp::s(" 🭍🭑🬽 ", " 🭞🭜🭘 ",)),
|
||||
Tui::fg(Rgb(255, 128, 0), Bsp::s(" ▗▄▖ ", " ▝▀▘ ",))))
|
||||
)
|
||||
)
|
||||
)),
|
||||
Bsp::s(
|
||||
FieldH(theme, "Beat", cache.beat.view.clone()),
|
||||
FieldH(theme, "Time", cache.time.view.clone()),
|
||||
),
|
||||
))),
|
||||
Fill::x(Align::w(FieldH(theme, "BPM", cache.bpm.view.clone()))),
|
||||
Fill::x(Align::w(FieldH(theme, "SR ", cache.sr.view.clone()))),
|
||||
Fill::x(Align::w(FieldH(theme, "Buf", cache.buf.view.clone()))),
|
||||
Fill::x(Align::w(FieldH(theme, "Lat", cache.lat.view.clone()))),
|
||||
)))
|
||||
}
|
||||
pub fn view_status (&self) -> impl Content<TuiOut> + use<'_> {
|
||||
self.update_clock();
|
||||
let cache = self.view_cache.read().unwrap();
|
||||
|
|
@ -27,16 +57,172 @@ impl App {
|
|||
cache.bpm.view.clone(), cache.beat.view.clone(), cache.time.view.clone())
|
||||
}
|
||||
pub fn view_editor (&self) -> impl Content<TuiOut> + use<'_> {
|
||||
self.editor().map(|e|Bsp::n(Bsp::e(e.clip_status(), e.edit_status()), e))
|
||||
self.editor()
|
||||
}
|
||||
pub fn view_editor_status (&self) -> impl Content<TuiOut> + use<'_> {
|
||||
self.editor().map(|e|Bsp::s(e.clip_status(), e.edit_status()))
|
||||
}
|
||||
pub fn view_midi_ports_status (&self) -> impl Content<TuiOut> + use<'_> {
|
||||
self.project.get_track().map(|track|Bsp::s(
|
||||
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 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<'_> {
|
||||
ArrangerView::new(&self.project, self.editor.as_ref())
|
||||
}
|
||||
pub fn view_arranger_scene_names <'a> (&'a self) -> impl Content<TuiOut> + use<'a> {
|
||||
let h = self.project.scenes.len() as u16 * 2;
|
||||
let bg = self.color.darker.rgb;
|
||||
Fixed::y(h, Tui::bg(bg, Align::w(Fill::x(Map::new(
|
||||
||self.project.scenes.iter().skip(self.project.scene_scroll),
|
||||
move|scene: &Scene, index|
|
||||
Push::y(index as u16 * 2u16, Fixed::xy(20, 2,
|
||||
Tui::bg(scene.color.dark.rgb, Align::nw(Bsp::e(
|
||||
format!(" {index:2} "),
|
||||
Tui::fg(Rgb(255, 255, 255),
|
||||
Tui::bold(true, format!("{}", scene.name)))))))))))))
|
||||
}
|
||||
pub fn view_arranger_scene_clips <'a> (&'a self) -> impl Content<TuiOut> + use<'a> {
|
||||
let h = self.project.scenes.len() as u16 * 2;
|
||||
let bg = self.color.darker.rgb;
|
||||
Fixed::y(h, Tui::bg(bg, Align::w(Fill::x(Map::new(
|
||||
||self.project.scenes.iter().skip(self.project.scene_scroll),
|
||||
move|scene: &'a Scene, index1|
|
||||
Push::y(index1 as u16 * 2u16, Fixed::xy(20, 2,
|
||||
Map::new(
|
||||
move||scene.clips.iter().skip(self.project.track_scroll),
|
||||
move|clip: &'a Option<Arc<RwLock<MidiClip>>>, index2|{
|
||||
let (theme, text) = if let Some(clip) = clip {
|
||||
let clip = clip.read().unwrap();
|
||||
(clip.color, clip.name.clone())
|
||||
} else {
|
||||
(scene.color, Default::default())
|
||||
};
|
||||
Push::x(index2 as u16 * 14, Tui::bg(theme.dark.rgb, Bsp::e(
|
||||
format!(" {index1:2} {index2:2} "),
|
||||
Tui::fg(Rgb(255, 255, 255),
|
||||
Tui::bold(true, format!("{}", text))))))
|
||||
}))))))))
|
||||
}
|
||||
pub fn view_arranger_track_names (&self) -> impl Content<TuiOut> + use<'_> {
|
||||
let mut max_outputs = 0u16;
|
||||
for track in self.project.tracks.iter() {
|
||||
max_outputs = max_outputs.max(track.sequencer.midi_outs.len() as u16);
|
||||
}
|
||||
Bsp::w(
|
||||
Fixed::x(20, Tui::bg(self.color.darkest.rgb,
|
||||
col!(Tui::bold(true, "[t]rack"), "[T] Add"))),
|
||||
Align::w(Fixed::y(max_outputs + 1, Tui::bg(self.color.darker.rgb, Align::w(Fill::x(Map::new(
|
||||
||self.project.tracks_with_sizes(&self.project.selection, None)
|
||||
.skip(self.project.track_scroll),
|
||||
move|(index, track, x1, x2): (usize, &Track, usize, usize), _|
|
||||
Push::x(index as u16 * 14, Fixed::xy(track.width as u16, max_outputs + 1,
|
||||
Tui::bg(track.color.dark.rgb, Align::nw(Bsp::s(
|
||||
Tui::fg(Rgb(255, 255, 255), Tui::bold(true, format!("{}", track.name))),
|
||||
format!("{index} {x1} {x2}")))))))))))))
|
||||
}
|
||||
pub fn view_arranger_track_outputs <'a> (&'a self) -> impl Content<TuiOut> + use<'a> {
|
||||
let mut max_outputs = 0u16;
|
||||
for track in self.project.tracks.iter() {
|
||||
max_outputs = max_outputs.max(track.sequencer.midi_outs.len() as u16);
|
||||
}
|
||||
Bsp::w(
|
||||
Fixed::x(20, Tui::bg(self.color.darkest.rgb,
|
||||
col!(Tui::bold(true, "[o]utput"), "[O] Add"))),
|
||||
Align::w(Fixed::y(max_outputs + 1, Tui::bg(self.color.darker.rgb, Align::w(Fill::x(Map::new(
|
||||
||self.project.tracks_with_sizes(&self.project.selection, None)
|
||||
.skip(self.project.track_scroll),
|
||||
move|(index, track, x1, x2): (usize, &'a Track, usize, usize), _|
|
||||
Push::x(x2 as u16, Tui::bg(track.color.dark.rgb, Fixed::xy(
|
||||
track.width as u16,
|
||||
max_outputs + 1,
|
||||
Align::nw(Bsp::s(
|
||||
format!("[mut] [sol]"),
|
||||
Map::south(1, ||track.sequencer.midi_outs.iter(),
|
||||
|port, index|Tui::fg(Rgb(255, 255, 255),
|
||||
format!("{index}: {}", port.name())))))))))))))))
|
||||
}
|
||||
pub fn view_arranger_track_inputs <'a> (&'a self) -> impl Content<TuiOut> + use<'a> {
|
||||
let mut max_inputs = 0u16;
|
||||
for track in self.project.tracks.iter() {
|
||||
max_inputs = max_inputs.max(track.sequencer.midi_ins.len() as u16);
|
||||
}
|
||||
Bsp::w(
|
||||
Fixed::x(20, Tui::bg(self.color.darkest.rgb,
|
||||
col!(Tui::bold(true, "[i]nputs"), "[I] Add"))),
|
||||
Fill::x(Align::w(Fixed::y(max_inputs + 1, Tui::bg(self.color.darker.rgb, Align::w(Fill::x(Map::new(
|
||||
||self.project.tracks_with_sizes(&self.project.selection, None)
|
||||
.skip(self.project.track_scroll),
|
||||
move|(index, track, x1, x2): (usize, &'a Track, usize, usize), _|
|
||||
Push::x(x2 as u16, Fixed::xy(track.width as u16, max_inputs + 1,
|
||||
Tui::bg(track.color.dark.rgb, Align::nw(Bsp::s(
|
||||
format!("[rec] [mon]"),
|
||||
Map::south(1, ||track.sequencer.midi_ins.iter(),
|
||||
|port, index|Tui::fg(Rgb(255, 255, 255),
|
||||
format!("{index}: {}", port.name()))))))))))))))))
|
||||
}
|
||||
pub fn view_arranger_track_devices <'a> (&'a self) -> impl Content<TuiOut> + use<'a> {
|
||||
let mut max_devices = 2u16;
|
||||
for track in self.project.tracks.iter() {
|
||||
max_devices = max_devices.max(track.devices.len() as u16);
|
||||
}
|
||||
Bsp::w(
|
||||
Fixed::x(20, Tui::bg(self.color.darkest.rgb,
|
||||
col!(Tui::bold(true, "[d]evice"), "[D] Add"))),
|
||||
Fixed::y(max_devices, Tui::bg(self.color.darker.rgb, Align::w(Fill::x(Map::new(
|
||||
||self.project.tracks_with_sizes(&self.project.selection, None)
|
||||
.skip(self.project.track_scroll),
|
||||
move|(index, track, x1, x2): (usize, &'a Track, usize, usize), _|
|
||||
Push::x(x2 as u16, Fixed::xy(track.width as u16, max_devices + 1,
|
||||
Tui::bg(track.color.dark.rgb, Align::nw(Map::south(1, move||0..max_devices,
|
||||
|_, index|format!("{index}: {}", "--------"))))))))))))
|
||||
}
|
||||
pub fn view_arranger_track_scenes <'a> (&'a self) -> impl Content<TuiOut> + use<'a> {
|
||||
let mut max_devices = 0u16;
|
||||
for track in self.project.tracks.iter() {
|
||||
max_devices = max_devices.max(track.devices.len() as u16);
|
||||
}
|
||||
Bsp::w(
|
||||
Fixed::x(20, Tui::bg(self.color.darkest.rgb,
|
||||
col!(Tui::bold(true, "Devices"), "[d] Select", "[D] Add"))),
|
||||
Fixed::y(max_devices + 1, Tui::bg(self.color.darker.rgb, Align::w(Fill::x(Map::new(
|
||||
||self.project.tracks_with_sizes(&self.project.selection, None)
|
||||
.skip(self.project.track_scroll),
|
||||
move|(index, track, x1, x2): (usize, &'a Track, usize, usize), _|
|
||||
Push::x(x2 as u16, Fixed::xy(track.width as u16, max_devices + 1,
|
||||
Align::nw(Map::south(1, ||track.devices.iter(),
|
||||
|device, index|format!("{index}: {}", device.name())))))))))))
|
||||
}
|
||||
pub fn view_pool (&self) -> impl Content<TuiOut> + use<'_> {
|
||||
PoolView(&self.project.pool)
|
||||
Fixed::x(20, Bsp::s(
|
||||
Fill::x(Align::w(FieldH(self.color, "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<'_> {
|
||||
self.project.sampler().map(|s|s.view_list(false, self.editor().unwrap()))
|
||||
self.project.sampler().map(|s|s.view_list(true, self.editor().unwrap()))
|
||||
}
|
||||
pub fn view_samples_grid (&self) -> impl Content<TuiOut> + use<'_> {
|
||||
self.project.sampler().map(|s|s.view_grid())
|
||||
|
|
@ -47,6 +233,9 @@ impl App {
|
|||
pub fn view_sample_info (&self) -> impl Content<TuiOut> + use<'_> {
|
||||
self.project.sampler().map(|s|s.view_sample_info(self.editor().unwrap().get_note_pos()))
|
||||
}
|
||||
pub fn view_sample_status (&self) -> impl Content<TuiOut> + use<'_> {
|
||||
self.project.sampler().map(|s|s.view_sample_status(self.editor().unwrap().get_note_pos()))
|
||||
}
|
||||
pub fn view_meters_input (&self) -> impl Content<TuiOut> + use<'_> {
|
||||
self.project.sampler().map(|s|s.view_meters_input())
|
||||
}
|
||||
|
|
@ -62,10 +251,8 @@ impl App {
|
|||
self.view_dialog_menu().boxed(),
|
||||
Dialog::Help(offset) =>
|
||||
self.view_dialog_help(*offset).boxed(),
|
||||
Dialog::Save(browser) =>
|
||||
self.view_dialog_save().boxed(),
|
||||
Dialog::Load(browser) =>
|
||||
self.view_dialog_load().boxed(),
|
||||
Dialog::Browser(target, browser) =>
|
||||
self.view_dialog_browser(target, browser).boxed(),
|
||||
Dialog::Options =>
|
||||
self.view_dialog_options().boxed(),
|
||||
Dialog::Device(index) =>
|
||||
|
|
@ -108,21 +295,45 @@ impl App {
|
|||
pub fn view_dialog_message <'a> (&'a self, message: &'a Message) -> impl Content<TuiOut> + use<'a> {
|
||||
Bsp::s(message, Bsp::s("", "[ OK ]"))
|
||||
}
|
||||
pub fn view_dialog_save <'a> (&'a self) -> impl Content<TuiOut> + use<'a> {
|
||||
pub fn view_dialog_browser <'a> (&'a self, target: &BrowserTarget, browser: &'a Browser) -> impl Content<TuiOut> + use<'a> {
|
||||
Bsp::s(
|
||||
Fill::x(Align::w(Margin::xy(1, 1, Bsp::e(
|
||||
Tui::bold(true, " Save project: "),
|
||||
Shrink::x(3, Fixed::y(1, RepeatH("🭻"))))))),
|
||||
Padding::xy(3, 1, Fill::x(Align::w(FieldV(
|
||||
self.color,
|
||||
match target {
|
||||
BrowserTarget::SaveProject => "Save project:",
|
||||
BrowserTarget::LoadProject => "Load project:",
|
||||
BrowserTarget::ImportSample(_) => "Import sample:",
|
||||
BrowserTarget::ExportSample(_) => "Export sample:",
|
||||
BrowserTarget::ImportClip(_) => "Import clip:",
|
||||
BrowserTarget::ExportClip(_) => "Export clip:",
|
||||
},
|
||||
Shrink::x(3, Fixed::y(1, Tui::fg(Tui::g(96), RepeatH("🭻")))))))),
|
||||
Outer(true, Style::default().fg(Tui::g(96)))
|
||||
.enclose(Fill::xy("todo file browser")))
|
||||
.enclose(Fill::xy(browser)))
|
||||
}
|
||||
pub fn view_dialog_load <'a> (&'a self) -> impl Content<TuiOut> + use<'a> {
|
||||
pub fn view_dialog_load <'a> (&'a self, browser: &'a Browser) -> impl Content<TuiOut> + use<'a> {
|
||||
Bsp::s(
|
||||
Fill::x(Align::w(Margin::xy(1, 1, Bsp::e(
|
||||
Tui::bold(true, " Load project: "),
|
||||
Shrink::x(3, Fixed::y(1, RepeatH("🭻"))))))),
|
||||
Outer(true, Style::default().fg(Tui::g(96)))
|
||||
.enclose(Fill::xy("todo file browser")))
|
||||
.enclose(Fill::xy(browser)))
|
||||
}
|
||||
pub fn view_dialog_export <'a> (&'a self, browser: &'a Browser) -> impl Content<TuiOut> + use<'a> {
|
||||
Bsp::s(
|
||||
Fill::x(Align::w(Margin::xy(1, 1, Bsp::e(
|
||||
Tui::bold(true, " Export: "),
|
||||
Shrink::x(3, Fixed::y(1, RepeatH("🭻"))))))),
|
||||
Outer(true, Style::default().fg(Tui::g(96)))
|
||||
.enclose(Fill::xy(browser)))
|
||||
}
|
||||
pub fn view_dialog_import <'a> (&'a self, browser: &'a Browser) -> impl Content<TuiOut> + use<'a> {
|
||||
Bsp::s(
|
||||
Fill::x(Align::w(Margin::xy(1, 1, Bsp::e(
|
||||
Tui::bold(true, " Import: "),
|
||||
Shrink::x(3, Fixed::y(1, RepeatH("🭻"))))))),
|
||||
Outer(true, Style::default().fg(Tui::g(96)))
|
||||
.enclose(Fill::xy(browser)))
|
||||
}
|
||||
pub fn view_dialog_options <'a> (&'a self) -> impl Content<TuiOut> + use<'a> {
|
||||
"TODO"
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ pub enum LaunchMode {
|
|||
/// Number of tracks
|
||||
#[arg(short = 'x', long, default_value_t = 4)] tracks: usize,
|
||||
/// Width of tracks
|
||||
#[arg(short = 'w', long, default_value_t = 12)] track_width: usize,
|
||||
#[arg(short = 'w', long, default_value_t = 14)] track_width: usize,
|
||||
},
|
||||
/// TODO: A MIDI-controlled audio mixer
|
||||
Mixer,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use crate::*;
|
|||
/// Define a type alias for iterators of sized items (columns).
|
||||
macro_rules! def_sizes_iter {
|
||||
($Type:ident => $($Item:ty),+) => {
|
||||
pub(crate) trait $Type<'a> =
|
||||
pub trait $Type<'a> =
|
||||
Iterator<Item=(usize, $(&'a $Item,)+ usize, usize)> + Send + Sync + 'a;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,27 +64,12 @@ impl Arrangement {
|
|||
}
|
||||
/// Width available to display tracks.
|
||||
pub fn w_tracks_area (&self, is_editing: bool) -> u16 {
|
||||
self.w().saturating_sub(2 * self.w_sidebar(is_editing))
|
||||
self.w().saturating_sub(self.w_sidebar(is_editing))
|
||||
}
|
||||
/// Height of display
|
||||
pub fn h (&self) -> u16 {
|
||||
self.size.h() as u16
|
||||
}
|
||||
/// Height available to display track headers.
|
||||
pub fn h_tracks_area (&self) -> u16 {
|
||||
5 // FIXME
|
||||
//self.h().saturating_sub(self.h_inputs() + self.h_outputs())
|
||||
}
|
||||
/// Height available to display tracks.
|
||||
pub fn h_scenes_area (&self) -> u16 {
|
||||
//15
|
||||
self.h().saturating_sub(
|
||||
self.h_inputs() +
|
||||
self.h_outputs() +
|
||||
self.h_devices() +
|
||||
13 // FIXME
|
||||
)
|
||||
}
|
||||
/// Height taken by all scenes.
|
||||
pub fn h_scenes (&self, is_editing: bool) -> u16 {
|
||||
self.scenes_with_sizes(
|
||||
|
|
@ -111,27 +96,27 @@ impl Arrangement {
|
|||
//1 + self.devices_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0)
|
||||
}
|
||||
/// Get the active track
|
||||
fn get_track (&self) -> Option<&Track> {
|
||||
pub fn get_track (&self) -> Option<&Track> {
|
||||
let index = self.selection().track()?;
|
||||
Has::<Vec<Track>>::get(self).get(index)
|
||||
}
|
||||
/// Get a mutable reference to the active track
|
||||
fn get_track_mut (&mut self) -> Option<&mut Track> {
|
||||
pub fn get_track_mut (&mut self) -> Option<&mut Track> {
|
||||
let index = self.selection().track()?;
|
||||
Has::<Vec<Track>>::get_mut(self).get_mut(index)
|
||||
}
|
||||
/// Get the active scene
|
||||
fn get_scene (&self) -> Option<&Scene> {
|
||||
pub fn get_scene (&self) -> Option<&Scene> {
|
||||
let index = self.selection().scene()?;
|
||||
Has::<Vec<Scene>>::get(self).get(index)
|
||||
}
|
||||
/// Get a mutable reference to the active scene
|
||||
fn get_scene_mut (&mut self) -> Option<&mut Scene> {
|
||||
pub fn get_scene_mut (&mut self) -> Option<&mut Scene> {
|
||||
let index = self.selection().scene()?;
|
||||
Has::<Vec<Scene>>::get_mut(self).get_mut(index)
|
||||
}
|
||||
/// Get the active clip
|
||||
fn get_clip (&self) -> Option<Arc<RwLock<MidiClip>>> {
|
||||
pub fn get_clip (&self) -> Option<Arc<RwLock<MidiClip>>> {
|
||||
self.get_scene()?.clips.get(self.selection().track()?)?.clone()
|
||||
}
|
||||
/// Put a clip in a slot
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use crate::*;
|
|||
impl<'a> ArrangerView<'a> {
|
||||
|
||||
pub(crate) fn input_routes (&'a self) -> impl Content<TuiOut> + 'a {
|
||||
Tryptich::top(self.inputs_height)
|
||||
Tryptich::top(self.arrangement.h_inputs())
|
||||
.left(self.width_side,
|
||||
io_ports(Tui::g(224), Tui::g(32), ||self.arrangement.midi_ins_with_sizes()))
|
||||
.middle(self.width_mid,
|
||||
|
|
@ -127,7 +127,7 @@ impl<'a> ArrangerView<'a> {
|
|||
}
|
||||
|
||||
pub(crate) fn output_conns (&'a self) -> impl Content<TuiOut> + 'a {
|
||||
Tryptich::top(self.outputs_height)
|
||||
Tryptich::top(self.arrangement.h_outputs())
|
||||
.left(self.width_side, io_ports(
|
||||
Tui::g(224), Tui::g(32), ||self.arrangement.midi_outs_with_sizes()))
|
||||
.middle(self.width_mid, per_track_top(
|
||||
|
|
|
|||
|
|
@ -2,26 +2,19 @@ use crate::*;
|
|||
|
||||
pub struct ArrangerView<'a> {
|
||||
pub arrangement: &'a Arrangement,
|
||||
|
||||
pub is_editing: bool,
|
||||
|
||||
pub width: u16,
|
||||
pub width_mid: u16,
|
||||
pub width_side: u16,
|
||||
|
||||
pub inputs_height: u16,
|
||||
pub outputs_height: u16,
|
||||
|
||||
pub scene_last: usize,
|
||||
pub scene_scroll: Fill<Fixed<u16, ScrollbarV>>,
|
||||
pub scene_selected: Option<usize>,
|
||||
/// Height available to display scene/track content.
|
||||
pub scenes_height: u16,
|
||||
|
||||
pub track_scroll: Fill<Fixed<u16, ScrollbarH>>,
|
||||
pub track_selected: Option<usize>,
|
||||
/// Height available to display track headers.
|
||||
pub tracks_height: u16,
|
||||
|
||||
pub show_debug_info: bool,
|
||||
}
|
||||
|
||||
impl<'a> ArrangerView<'a> {
|
||||
|
|
@ -30,20 +23,15 @@ impl<'a> ArrangerView<'a> {
|
|||
editor: Option<&'a MidiEditor>
|
||||
) -> Self {
|
||||
let is_editing = editor.is_some();
|
||||
let h_tracks_area = arrangement.h_tracks_area();
|
||||
let h_scenes_area = arrangement.h_scenes_area();
|
||||
let h_tracks_area = 5;
|
||||
let h_scenes_area = (arrangement.height() as u16).saturating_sub(20);
|
||||
let h_scenes = arrangement.h_scenes(is_editing);
|
||||
Self {
|
||||
arrangement,
|
||||
is_editing,
|
||||
|
||||
width: arrangement.w(),
|
||||
width_mid: arrangement.w_tracks_area(is_editing),
|
||||
width_side: arrangement.w_sidebar(is_editing),
|
||||
|
||||
inputs_height: arrangement.h_inputs(),
|
||||
outputs_height: arrangement.h_outputs(),
|
||||
|
||||
width: arrangement.w_tracks_area(is_editing),
|
||||
width_mid: arrangement.w_tracks_area(is_editing).saturating_sub(20),
|
||||
width_side: 20,
|
||||
scenes_height: h_scenes_area,
|
||||
scene_selected: arrangement.selection().scene(),
|
||||
scene_last: arrangement.scenes.len().saturating_sub(1),
|
||||
|
|
@ -52,7 +40,6 @@ impl<'a> ArrangerView<'a> {
|
|||
length: h_scenes_area as usize,
|
||||
total: h_scenes as usize,
|
||||
})),
|
||||
|
||||
tracks_height: h_tracks_area,
|
||||
track_selected: arrangement.selection().track(),
|
||||
track_scroll: Fill::x(Fixed::y(1, ScrollbarH {
|
||||
|
|
@ -60,8 +47,6 @@ impl<'a> ArrangerView<'a> {
|
|||
length: h_tracks_area as usize,
|
||||
total: h_scenes as usize,
|
||||
})),
|
||||
|
||||
show_debug_info: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -75,7 +60,7 @@ impl<'a> Content<TuiOut> for ArrangerView<'a> {
|
|||
let bg = |x|Tui::bg(Reset, x);
|
||||
//let track_scroll = |x|Bsp::s(&self.track_scroll, x);
|
||||
//let scene_scroll = |x|Bsp::e(&self.scene_scroll, x);
|
||||
outs(tracks(devices(ins(bg(self.scenes(&None))))))
|
||||
self.arrangement.size.of(outs(tracks(devices(ins(bg(self.scenes(&None)))))))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -148,7 +133,6 @@ impl<'a> ArrangerView<'a> {
|
|||
scenes_height, scene_last, scene_selected,
|
||||
track_selected, is_editing, ..
|
||||
} = self;
|
||||
|
||||
let selection = Has::<Selection>::get(self.arrangement);
|
||||
let selected_track = selection.track();
|
||||
let selected_scene = selection.scene();
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
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");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -95,34 +95,36 @@ use crate::*;
|
|||
#[tengri_proc::command(MidiEditor)] impl MidiEditCommand {
|
||||
fn append_note (editor: &mut MidiEditor, advance: bool) -> Perhaps<Self> {
|
||||
editor.put_note(advance);
|
||||
editor.redraw();
|
||||
Ok(None)
|
||||
}
|
||||
fn delete_note (_editor: &mut MidiEditor) -> Perhaps<Self> {
|
||||
fn delete_note (editor: &mut MidiEditor) -> Perhaps<Self> {
|
||||
editor.redraw();
|
||||
todo!()
|
||||
}
|
||||
fn set_note_pos (editor: &mut MidiEditor, pos: usize) -> Perhaps<Self> {
|
||||
editor.set_note_pos(pos.min(127));
|
||||
editor.redraw();
|
||||
Ok(None)
|
||||
}
|
||||
fn set_note_len (editor: &mut MidiEditor, value: usize) -> Perhaps<Self> {
|
||||
//let note_len = editor.get_note_len();
|
||||
//let time_zoom = editor.get_time_zoom();
|
||||
editor.set_note_len(value);
|
||||
//if note_len / time_zoom != x / time_zoom {
|
||||
editor.redraw();
|
||||
//}
|
||||
editor.redraw();
|
||||
Ok(None)
|
||||
}
|
||||
fn set_note_scroll (editor: &mut MidiEditor, value: usize) -> Perhaps<Self> {
|
||||
editor.set_note_lo(value.min(127));
|
||||
editor.redraw();
|
||||
Ok(None)
|
||||
}
|
||||
fn set_time_pos (editor: &mut MidiEditor, value: usize) -> Perhaps<Self> {
|
||||
editor.set_time_pos(value);
|
||||
editor.redraw();
|
||||
Ok(None)
|
||||
}
|
||||
fn set_time_scroll (editor: &mut MidiEditor, value: usize) -> Perhaps<Self> {
|
||||
editor.set_time_start(value);
|
||||
editor.redraw();
|
||||
Ok(None)
|
||||
}
|
||||
fn set_time_zoom (editor: &mut MidiEditor, value: usize) -> Perhaps<Self> {
|
||||
|
|
@ -132,10 +134,12 @@ use crate::*;
|
|||
}
|
||||
fn set_time_lock (editor: &mut MidiEditor, value: bool) -> Perhaps<Self> {
|
||||
editor.set_time_lock(value);
|
||||
editor.redraw();
|
||||
Ok(None)
|
||||
}
|
||||
fn show (editor: &mut MidiEditor, clip: Option<Arc<RwLock<MidiClip>>>) -> Perhaps<Self> {
|
||||
editor.set_clip(clip.as_ref());
|
||||
editor.redraw();
|
||||
Ok(None)
|
||||
}
|
||||
// TODO: 1-9 seek markers that by default start every 8th of the clip
|
||||
|
|
|
|||
|
|
@ -12,10 +12,11 @@ impl MidiEditor {
|
|||
let (color, name, length, looped) = if let Some(clip) = self.clip().as_ref().map(|p|p.read().unwrap()) {
|
||||
(clip.color, clip.name.clone(), clip.length, clip.looped)
|
||||
} else { (ItemTheme::G[64], String::new().into(), 0, false) };
|
||||
Bsp::e(
|
||||
FieldH(color, "Edit", format!("{name} ({length})")),
|
||||
FieldH(color, "Loop", looped.to_string())
|
||||
)
|
||||
Fixed::x(20, col!(
|
||||
Fill::x(Align::w(FieldV(color, "Clip ", format!("{name}")))),
|
||||
Fill::x(Align::w(FieldH(color, "Length", format!("{length}")))),
|
||||
Fill::x(Align::w(FieldH(color, "Loop ", looped.to_string()))),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn edit_status (&self) -> impl Content<TuiOut> + '_ {
|
||||
|
|
@ -29,10 +30,11 @@ 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());
|
||||
Bsp::e(
|
||||
FieldH(color, "Time", format!("{length}/{time_zoom}+{time_pos} {time_lock}")),
|
||||
FieldH(color, "Note", format!("{note_name} {note_pos} {note_len}")),
|
||||
)
|
||||
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}")))),
|
||||
))
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,11 +64,22 @@ impl PianoHorizontal {
|
|||
/// Draw the piano roll background.
|
||||
///
|
||||
/// This mode uses full blocks on note on and half blocks on legato: █▄ █▄ █▄
|
||||
fn draw_bg (buf: &mut BigBuffer, clip: &MidiClip, zoom: usize, note_len: usize) {
|
||||
fn draw_bg (
|
||||
buf: &mut BigBuffer,
|
||||
clip: &MidiClip,
|
||||
zoom: usize,
|
||||
note_len: usize,
|
||||
note_point: usize,
|
||||
time_point: usize,
|
||||
) {
|
||||
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(clip.color.darkest.rgb);
|
||||
if note == (127-note_point) || time == time_point {
|
||||
cell.set_bg(Rgb(0,0,0));
|
||||
} else {
|
||||
cell.set_bg(clip.color.darkest.rgb);
|
||||
}
|
||||
if time % 384 == 0 {
|
||||
cell.set_fg(clip.color.darker.rgb);
|
||||
cell.set_char('│');
|
||||
|
|
@ -267,10 +278,10 @@ impl MidiViewer for PianoHorizontal {
|
|||
let clip = clip.read().unwrap();
|
||||
let buf_size = self.buffer_size(&clip);
|
||||
let mut buffer = BigBuffer::from(buf_size);
|
||||
let note_len = self.get_note_len();
|
||||
let time_zoom = self.get_time_zoom();
|
||||
self.time_len().set(clip.length);
|
||||
PianoHorizontal::draw_bg(&mut buffer, &clip, time_zoom, note_len);
|
||||
PianoHorizontal::draw_bg(&mut buffer, &clip, time_zoom,
|
||||
self.get_note_len(), self.get_note_pos(), self.get_time_pos());
|
||||
PianoHorizontal::draw_fg(&mut buffer, &clip, time_zoom);
|
||||
buffer
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ pub fn to_rms (samples: &[f32]) -> f32 {
|
|||
(sum / samples.len() as f32).sqrt()
|
||||
}
|
||||
|
||||
pub (crate) fn view_meter <'a> (label: &'a str, value: f32) -> impl Content<TuiOut> + 'a {
|
||||
pub fn view_meter <'a> (label: &'a str, value: f32) -> impl Content<TuiOut> + 'a {
|
||||
col!(
|
||||
FieldH(ItemTheme::G[128], label, format!("{:>+9.3}", value)),
|
||||
Fixed::xy(if value >= 0.0 { 13 }
|
||||
|
|
@ -74,7 +74,7 @@ pub (crate) fn view_meter <'a> (label: &'a str, value: f32) -> impl Content<TuiO
|
|||
else { Green }, ())))
|
||||
}
|
||||
|
||||
pub(crate) fn view_meters (values: &[f32;2]) -> impl Content<TuiOut> + use<'_> {
|
||||
pub fn view_meters (values: &[f32;2]) -> impl Content<TuiOut> + use<'_> {
|
||||
let left = format!("L/{:>+9.3}", values[0]);
|
||||
let right = format!("R/{:>+9.3}", values[1]);
|
||||
Bsp::s(left, right)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ content!(TuiOut: |self: PoolView<'a>| {
|
|||
//let on_bg = |x|x;//Bsp::b(Repeat(" "), Tui::bg(color.darkest.rgb, x));
|
||||
//let border = |x|x;//Outer(Style::default().fg(color.dark.rgb).bg(color.darkest.rgb)).enclose(x);
|
||||
let height = pool.clips.read().unwrap().len() as u16;
|
||||
Fixed::x(20, Fill::y(Align::c(Map::new(
|
||||
Fixed::x(20, Fill::y(Align::n(Map::new(
|
||||
||pool.clips().clone().into_iter(),
|
||||
move|clip: Arc<RwLock<MidiClip>>, i: usize|{
|
||||
let item_height = 1;
|
||||
|
|
|
|||
|
|
@ -7,20 +7,18 @@ 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 {
|
||||
MidiMessage::NoteOn { ref key, ref vel } => {
|
||||
if let Some(ref sample) = mapped[key.as_int() as usize] {
|
||||
voices.write().unwrap().push(Sample::play(sample, time as usize, vel));
|
||||
}
|
||||
},
|
||||
MidiMessage::Controller { controller: _, value: _ } => {
|
||||
// TODO
|
||||
for RawMidi { time, bytes } in midi_in.port().iter(scope) {
|
||||
if let LiveEvent::Midi { message, .. } = LiveEvent::parse(bytes).unwrap() {
|
||||
match message {
|
||||
MidiMessage::NoteOn { ref key, ref vel } => {
|
||||
if let Some(ref sample) = mapped[key.as_int() as usize] {
|
||||
voices.write().unwrap().push(Sample::play(sample, time as usize, vel));
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
MidiMessage::Controller { controller: _, value: _ } => {
|
||||
// TODO
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -146,11 +134,20 @@ pub struct Sample {
|
|||
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 }
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -59,17 +59,25 @@ impl Sampler {
|
|||
let note_lo = editor.get_note_lo();
|
||||
let note_pt = editor.get_note_pos();
|
||||
let note_hi = editor.get_note_hi();
|
||||
Fixed::x(12, Map::south(
|
||||
Fixed::x(if compact { 4 } else { 12 }, Map::south(
|
||||
1,
|
||||
move||(note_lo..=note_hi).rev(),
|
||||
move|note, _index| {
|
||||
//let offset = |a|Push::y(i as u16, Align::n(Fixed::y(1, Fill::x(a))));
|
||||
let mut bg = if note == note_pt { Tui::g(64) } else { Color::Reset };
|
||||
let mut fg = Tui::g(160);
|
||||
let mapped: &Option<Arc<RwLock<Sample>>> = &self.mapped[note];
|
||||
if mapped.is_some() {
|
||||
fg = Tui::g(224);
|
||||
bg = Color::Rgb(0, if note == note_pt { 96 } else { 64 }, 0);
|
||||
if let Some(mapped) = &self.mapped[note] {
|
||||
let sample = mapped.read().unwrap();
|
||||
fg = if note == note_pt {
|
||||
sample.color.lightest.rgb
|
||||
} else {
|
||||
Tui::g(224)
|
||||
};
|
||||
bg = if note == note_pt {
|
||||
sample.color.light.rgb
|
||||
} else {
|
||||
sample.color.base.rgb
|
||||
};
|
||||
}
|
||||
if let Some((index, _)) = self.recording {
|
||||
if note == index {
|
||||
|
|
@ -110,6 +118,16 @@ impl Sampler {
|
|||
})))
|
||||
}
|
||||
|
||||
pub fn view_sample_status (&self, note_pt: usize) -> impl Content<TuiOut> + use<'_> {
|
||||
Fixed::x(20, draw_info_v(if let Some((_, sample)) = &self.recording {
|
||||
Some(sample)
|
||||
} else if let Some(sample) = &self.mapped[note_pt] {
|
||||
Some(sample)
|
||||
} else {
|
||||
None
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn view_status (&self, index: usize) -> impl Content<TuiOut> {
|
||||
draw_status(self.mapped[index].as_ref())
|
||||
}
|
||||
|
|
@ -189,11 +207,11 @@ fn draw_viewer (sample: Option<&Arc<RwLock<Sample>>>) -> impl Content<TuiOut> +
|
|||
.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()
|
||||
);
|
||||
//ctx.print(
|
||||
//(width - text.len() as u16) as f64 / 2.0,
|
||||
//height as f64 / 2.0,
|
||||
//text.red()
|
||||
//);
|
||||
})
|
||||
.render(area, &mut to.buffer);
|
||||
}
|
||||
|
|
@ -203,18 +221,37 @@ fn draw_viewer (sample: Option<&Arc<RwLock<Sample>>>) -> impl Content<TuiOut> +
|
|||
fn draw_info (sample: Option<&Arc<RwLock<Sample>>>) -> impl Content<TuiOut> + use<'_> {
|
||||
When(sample.is_some(), Thunk::new(move||{
|
||||
let sample = sample.unwrap().read().unwrap();
|
||||
let theme = ItemTheme::G[96];
|
||||
let theme = sample.color;
|
||||
row!(
|
||||
FieldH(theme, "Name", format!("{:<10}", sample.name.clone())),
|
||||
FieldH(theme, "Length", format!("{:<8}", sample.channels[0].len())),
|
||||
FieldH(theme, "Start", format!("{:<8}", sample.start)),
|
||||
FieldH(theme, "End", format!("{:<8}", sample.end)),
|
||||
FieldH(theme, "Transpose", " 0 "),
|
||||
FieldH(theme, "Gain", format!("{}", sample.gain)),
|
||||
FieldH(theme, "Name", format!("{:<10}", sample.name.clone())),
|
||||
FieldH(theme, "Length", format!("{:<8}", sample.channels[0].len())),
|
||||
FieldH(theme, "Start", format!("{:<8}", sample.start)),
|
||||
FieldH(theme, "End", format!("{:<8}", sample.end)),
|
||||
FieldH(theme, "Trans", "0"),
|
||||
FieldH(theme, "Gain", format!("{}", sample.gain)),
|
||||
)
|
||||
}))
|
||||
}
|
||||
|
||||
fn draw_info_v (sample: Option<&Arc<RwLock<Sample>>>) -> impl Content<TuiOut> + use<'_> {
|
||||
Either(sample.is_some(), Thunk::new(move||{
|
||||
let sample = sample.unwrap().read().unwrap();
|
||||
let theme = sample.color;
|
||||
Fixed::x(20, col!(
|
||||
Fill::x(Align::w(FieldH(theme, "Name ", format!("{:<10}", sample.name.clone())))),
|
||||
Fill::x(Align::w(FieldH(theme, "Length", format!("{:<8}", sample.channels[0].len())))),
|
||||
Fill::x(Align::w(FieldH(theme, "Start ", format!("{:<8}", sample.start)))),
|
||||
Fill::x(Align::w(FieldH(theme, "End ", format!("{:<8}", sample.end)))),
|
||||
Fill::x(Align::w(FieldH(theme, "Trans ", "0"))),
|
||||
Fill::x(Align::w(FieldH(theme, "Gain ", format!("{}", sample.gain)))),
|
||||
))
|
||||
}), Thunk::new(move||Tui::fg(Red, col!(
|
||||
Tui::bold(true, "× No sample."),
|
||||
"[r] record",
|
||||
"[Shift-F9] import",
|
||||
))))
|
||||
}
|
||||
|
||||
fn draw_status (sample: Option<&Arc<RwLock<Sample>>>) -> impl Content<TuiOut> {
|
||||
Tui::bold(true, Tui::fg(Tui::g(224), sample
|
||||
.map(|sample|{
|
||||
|
|
|
|||
|
|
@ -36,16 +36,16 @@ impl<E: Engine> Handle<E> for JackDevice<E> {
|
|||
}
|
||||
|
||||
impl<E: Engine> Ports for JackDevice<E> {
|
||||
fn audio_ins(&self) -> Usually<Vec<&Port<Unowned>>> {
|
||||
fn audio_ins (&self) -> Usually<Vec<&Port<Unowned>>> {
|
||||
Ok(self.ports.audio_ins.values().collect())
|
||||
}
|
||||
fn audio_outs(&self) -> Usually<Vec<&Port<Unowned>>> {
|
||||
fn audio_outs (&self) -> Usually<Vec<&Port<Unowned>>> {
|
||||
Ok(self.ports.audio_outs.values().collect())
|
||||
}
|
||||
fn midi_ins(&self) -> Usually<Vec<&Port<Unowned>>> {
|
||||
fn midi_ins (&self) -> Usually<Vec<&Port<Unowned>>> {
|
||||
Ok(self.ports.midi_ins.values().collect())
|
||||
}
|
||||
fn midi_outs(&self) -> Usually<Vec<&Port<Unowned>>> {
|
||||
fn midi_outs (&self) -> Usually<Vec<&Port<Unowned>>> {
|
||||
Ok(self.ports.midi_outs.values().collect())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
2
deps/tengri
vendored
2
deps/tengri
vendored
|
|
@ -1 +1 @@
|
|||
Subproject commit 8bfd1a23a1f880a1d2fb104a158fc51f244acd6e
|
||||
Subproject commit c954965ae125136286291e5f0d4532edf98f46ad
|
||||
Loading…
Add table
Add a link
Reference in a new issue