wip: refactor pt.5, no translate

This commit is contained in:
🪞👃🪞 2024-11-10 01:34:17 +01:00
parent 8c37c95cc6
commit 5df08409e5
19 changed files with 389 additions and 457 deletions

View file

@ -1,66 +1 @@
use crate::*; use crate::*;
#[derive(Clone, PartialEq)]
pub enum SequencerCommand {
Focus(FocusCommand),
Transport(TransportCommand),
Phrases(PhrasePoolCommand),
Editor(PhraseEditorCommand),
}
#[derive(Clone, PartialEq)]
pub enum PhrasePoolCommand {
Prev,
Next,
MoveUp,
MoveDown,
Delete,
Append,
Insert,
Duplicate,
RandomColor,
Edit,
Import,
Export,
Rename(PhraseRenameCommand),
Length(PhraseLengthCommand),
}
#[derive(Clone, PartialEq)]
pub enum PhraseRenameCommand {
Begin,
Backspace,
Append(char),
Set(String),
Confirm,
Cancel,
}
#[derive(Clone, PartialEq)]
pub enum PhraseLengthCommand {
Begin,
Next,
Prev,
Inc,
Dec,
Set(usize),
Confirm,
Cancel,
}
#[derive(Clone, PartialEq)]
pub enum PhraseEditorCommand {
// TODO: 1-9 seek markers that by default start every 8th of the phrase
ToggleDirection,
EnterEditMode,
ExitEditMode,
NoteAppend,
NoteSet,
NoteCursorSet(usize),
NoteLengthSet(usize),
NoteScrollSet(usize),
TimeCursorSet(usize),
TimeScrollSet(usize),
TimeZoomSet(usize),
Go(Direction),
}

View file

@ -1,21 +1,27 @@
pub(crate) use tek_core::*; pub(crate) use tek_core::*;
pub(crate) use tek_core::jack::*;
pub(crate) use tek_core::midly::{*, live::LiveEvent, num::u7}; pub(crate) use tek_core::midly::{*, live::LiveEvent, num::u7};
pub(crate) use std::thread::JoinHandle; pub(crate) use std::thread::JoinHandle;
pub(crate) use std::fmt::{Debug, Formatter, Error}; pub(crate) use std::fmt::{Debug, Formatter, Error};
pub(crate) use tek_core::jack::{
Client, ProcessScope, Control, CycleTimes,
Port, MidiIn, MidiOut, AudioIn, AudioOut, Unowned,
TransportState, MidiIter, RawMidi
};
submod! { submod! {
clock clock
mixer mixer
phrase phrase
plugin plugin
plugin_kind
plugin_lv2
pool pool
sampler sampler
sample sample
scene scene
sequencer sequencer
track track
transport transport transport_cmd
voice voice
api_cmd api_cmd

View file

@ -1,7 +1,7 @@
use crate::*; use crate::*;
/// A MIDI sequence. /// A MIDI sequence.
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone)]
pub struct Phrase { pub struct Phrase {
pub uuid: uuid::Uuid, pub uuid: uuid::Uuid,
/// Name of phrase /// Name of phrase
@ -74,5 +74,11 @@ impl Default for Phrase {
Self::new("(empty)", false, 0, None, Some(ItemColor::from(Color::Rgb(0, 0, 0)).into())) Self::new("(empty)", false, 0, None, Some(ItemColor::from(Color::Rgb(0, 0, 0)).into()))
} }
} }
impl PartialEq for Phrase { fn eq (&self, other: &Self) -> bool { self.uuid == other.uuid } }
impl PartialEq for Phrase {
fn eq (&self, other: &Self) -> bool {
self.uuid == other.uuid
}
}
impl Eq for Phrase {} impl Eq for Phrase {}

View file

@ -46,91 +46,6 @@ impl Plugin {
//} //}
} }
/// Supported plugin formats.
#[derive(Default)]
pub enum PluginKind {
#[default] None,
LV2(LV2Plugin),
VST2 { instance: ::vst::host::PluginInstance },
VST3,
}
impl Debug for PluginKind {
fn fmt (&self, f: &mut Formatter<'_>) -> Result<(), Error> {
write!(f, "{}", match self {
Self::None => "(none)",
Self::LV2(_) => "LV2",
Self::VST2{..} => "VST2",
Self::VST3 => "VST3",
})
}
}
/// A LV2 plugin.
#[derive(Debug)]
pub struct LV2Plugin {
pub world: livi::World,
pub instance: livi::Instance,
pub plugin: livi::Plugin,
pub features: Arc<livi::Features>,
pub port_list: Vec<livi::Port>,
pub input_buffer: Vec<livi::event::LV2AtomSequence>,
pub ui_thread: Option<JoinHandle<()>>,
}
impl LV2Plugin {
const INPUT_BUFFER: usize = 1024;
pub fn new (uri: &str) -> Usually<Self> {
let world = livi::World::with_load_bundle(&uri);
let features = world
.build_features(livi::FeaturesBuilder {
min_block_length: 1,
max_block_length: 65536,
});
let plugin = world
.iter_plugins()
.nth(0)
.expect(&format!("plugin not found: {uri}"));
Ok(Self {
instance: unsafe {
plugin
.instantiate(features.clone(), 48000.0)
.expect(&format!("instantiate failed: {uri}"))
},
port_list: plugin.ports().collect::<Vec<_>>(),
input_buffer: Vec::with_capacity(Self::INPUT_BUFFER),
ui_thread: None,
world,
features,
plugin,
})
}
}
impl LV2Plugin {
pub fn from_edn <'e> (jack: &Arc<RwLock<JackClient>>, args: &[Edn<'e>]) -> Usually<Self> {
let mut name = String::new();
let mut path = String::new();
edn!(edn in args {
Edn::Map(map) => {
if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) {
name = String::from(*n);
}
if let Some(Edn::Str(p)) = map.get(&Edn::Key(":path")) {
path = String::from(*p);
}
},
_ => panic!("unexpected in lv2 '{name}'"),
});
Plugin::new_lv2(jack, &name, &path)
}
}
impl Audio for LV2Plugin {
fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
Control::Continue
}
}
impl Audio for Plugin { impl Audio for Plugin {
fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
match self.plugin.as_mut() { match self.plugin.as_mut() {

View file

@ -0,0 +1,21 @@
use crate::*;
/// Supported plugin formats.
#[derive(Default)]
pub enum PluginKind {
#[default] None,
LV2(LV2Plugin),
VST2 { instance: ::vst::host::PluginInstance },
VST3,
}
impl Debug for PluginKind {
fn fmt (&self, f: &mut Formatter<'_>) -> std::result::Result<(), Error> {
write!(f, "{}", match self {
Self::None => "(none)",
Self::LV2(_) => "LV2",
Self::VST2{..} => "VST2",
Self::VST3 => "VST3",
})
}
}

View file

@ -0,0 +1,67 @@
use crate::*;
/// A LV2 plugin.
#[derive(Debug)]
pub struct LV2Plugin {
pub world: livi::World,
pub instance: livi::Instance,
pub plugin: livi::Plugin,
pub features: Arc<livi::Features>,
pub port_list: Vec<livi::Port>,
pub input_buffer: Vec<livi::event::LV2AtomSequence>,
pub ui_thread: Option<JoinHandle<()>>,
}
impl LV2Plugin {
const INPUT_BUFFER: usize = 1024;
pub fn new (uri: &str) -> Usually<Self> {
let world = livi::World::with_load_bundle(&uri);
let features = world
.build_features(livi::FeaturesBuilder {
min_block_length: 1,
max_block_length: 65536,
});
let plugin = world
.iter_plugins()
.nth(0)
.expect(&format!("plugin not found: {uri}"));
Ok(Self {
instance: unsafe {
plugin
.instantiate(features.clone(), 48000.0)
.expect(&format!("instantiate failed: {uri}"))
},
port_list: plugin.ports().collect::<Vec<_>>(),
input_buffer: Vec::with_capacity(Self::INPUT_BUFFER),
ui_thread: None,
world,
features,
plugin,
})
}
}
impl LV2Plugin {
pub fn from_edn <'e> (jack: &Arc<RwLock<JackClient>>, args: &[Edn<'e>]) -> Usually<Self> {
let mut name = String::new();
let mut path = String::new();
edn!(edn in args {
Edn::Map(map) => {
if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) {
name = String::from(*n);
}
if let Some(Edn::Str(p)) = map.get(&Edn::Key(":path")) {
path = String::from(*p);
}
},
_ => panic!("unexpected in lv2 '{name}'"),
});
Plugin::new_lv2(jack, &name, &path)
}
}
impl Audio for LV2Plugin {
fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
Control::Continue
}
}

View file

@ -2,16 +2,48 @@ use crate::*;
/// Contains all phrases in a project /// Contains all phrases in a project
pub struct PhrasePool { pub struct PhrasePool {
/// Scroll offset
pub scroll: usize,
/// Highlighted phrase
pub phrase: usize,
/// Phrases in the pool /// Phrases in the pool
pub phrases: Vec<Arc<RwLock<Phrase>>>, pub phrases: Vec<Arc<RwLock<Phrase>>>,
/// Mode switch /// Highlighted phrase
pub mode: Option<PhrasePoolMode>, pub phrase: usize,
/// Whether this widget is focused }
pub focused: bool,
/// Whether this widget is entered #[derive(Clone, PartialEq)]
pub entered: bool, pub enum PhrasePoolCommand {
Prev,
Next,
MoveUp,
MoveDown,
Delete,
Append,
Insert,
Duplicate,
RandomColor,
Edit,
Import,
Export,
Rename(PhraseRenameCommand),
Length(PhraseLengthCommand),
}
#[derive(Clone, PartialEq)]
pub enum PhraseRenameCommand {
Begin,
Backspace,
Append(char),
Set(String),
Confirm,
Cancel,
}
#[derive(Clone, PartialEq)]
pub enum PhraseLengthCommand {
Begin,
Next,
Prev,
Inc,
Dec,
Set(usize),
Confirm,
Cancel,
} }

View file

@ -62,13 +62,66 @@ impl Sampler {
output_gain: 0. output_gain: 0.
}) })
} }
} /// Immutable reference to sample at cursor.
pub fn sample (&self) -> Option<&Arc<RwLock<Sample>>> {
/// A currently playing instance of a sample. for (i, sample) in self.mapped.values().enumerate() {
#[derive(Default, Debug, Clone)] if i == self.cursor.0 {
pub struct Voice { return Some(sample)
pub sample: Arc<RwLock<Sample>>, }
pub after: usize, }
pub position: usize, for (i, sample) in self.unmapped.iter().enumerate() {
pub velocity: f32, if i + self.mapped.len() == self.cursor.0 {
return Some(sample)
}
}
None
}
/// Create [Voice]s from [Sample]s in response to MIDI input.
pub fn process_midi_in (&mut self, scope: &ProcessScope) {
for RawMidi { time, bytes } in self.ports.midi_ins.get("midi").unwrap().iter(scope) {
if let LiveEvent::Midi { message, .. } = LiveEvent::parse(bytes).unwrap() {
if let MidiMessage::NoteOn { ref key, ref vel } = message {
if let Some(sample) = self.mapped.get(key) {
self.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 channel_count = self.buffer.len();
self.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
//);
self.buffer[channel % channel_count][index] +=
sample * self.output_gain;
}
} else {
return false
}
}
return true
});
}
/// Write output buffer to output ports.
pub fn write_output_buffer (&mut self, scope: &ProcessScope) {
for (i, port) in self.ports.audio_outs.values_mut().enumerate() {
let buffer = &self.buffer[i];
for (i, value) in port.as_mut_slice(scope).iter_mut().enumerate() {
*value = *buffer.get(i).unwrap_or(&0.0);
}
}
}
} }

View file

@ -1,6 +1,29 @@
use crate::*; use crate::*;
pub enum SequencerCommand {} #[derive(Clone, PartialEq)]
pub enum SequencerCommand {
Focus(FocusCommand),
Transport(TransportCommand),
Phrases(PhrasePoolCommand),
Editor(PhraseEditorCommand),
}
#[derive(Clone, PartialEq)]
pub enum PhraseEditorCommand {
// TODO: 1-9 seek markers that by default start every 8th of the phrase
ToggleDirection,
EnterEditMode,
ExitEditMode,
NoteAppend,
NoteSet,
NoteCursorSet(usize),
NoteLengthSet(usize),
NoteScrollSet(usize),
TimeCursorSet(usize),
TimeScrollSet(usize),
TimeZoomSet(usize),
Go(Direction),
}
#[derive(Debug)] #[derive(Debug)]
pub struct SequencerTrack { pub struct SequencerTrack {

View file

@ -1,19 +1,5 @@
use crate::*; use crate::*;
#[derive(Copy, Clone, PartialEq)]
pub enum TransportCommand {
FocusNext,
FocusPrev,
Play(Option<usize>),
Pause(Option<usize>),
SeekUsec(f64),
SeekSample(f64),
SeekPulse(f64),
SetBpm(f64),
SetQuant(f64),
SetSync(f64),
}
pub struct Transport { pub struct Transport {
pub jack: Arc<RwLock<JackClient>>, pub jack: Arc<RwLock<JackClient>>,
/// JACK transport handle. /// JACK transport handle.

View file

@ -0,0 +1,31 @@
use crate::*;
#[derive(Copy, Clone, PartialEq)]
pub enum TransportCommand {
Play(Option<usize>),
Pause(Option<usize>),
SeekUsec(f64),
SeekSample(f64),
SeekPulse(f64),
SetBpm(f64),
SetQuant(f64),
SetSync(f64),
}
impl Command<Transport> for TransportCommand {
fn execute (self, state: &mut Transport) -> Perhaps<Self> {
use TransportCommand::*;
match self.translate(&state) {
Play(start) => {todo!()},
Pause(start) => {todo!()},
SeekUsec(usec) => {state.clock.current.update_from_usec(usec);},
SeekSample(sample) => {state.clock.current.update_from_sample(sample);},
SeekPulse(pulse) => {state.clock.current.update_from_pulse(pulse);},
SetBpm(bpm) => {return Ok(Some(Self::SetBpm(state.clock.timebase().bpm.set(bpm))))},
SetQuant(quant) => {return Ok(Some(Self::SetQuant(state.clock.quant.set(quant))))},
SetSync(sync) => {return Ok(Some(Self::SetSync(state.clock.sync.set(sync))))},
_ => { unreachable!() }
}
Ok(None)
}
}

View file

@ -0,0 +1,10 @@
use crate::*;
/// A currently playing instance of a sample.
#[derive(Default, Debug, Clone)]
pub struct Voice {
pub sample: Arc<RwLock<Sample>>,
pub after: usize,
pub position: usize,
pub velocity: f32,
}

View file

@ -12,8 +12,16 @@ pub fn delegate <B, C: Command<S>, S> (
Ok(cmd.execute(state)?.map(|x|wrap(x))) Ok(cmd.execute(state)?.map(|x|wrap(x)))
} }
pub trait InputToCommand<E: Engine, S>: Sized { pub trait InputToCommand<E: Engine, S>: Command<S> + Sized {
fn input_to_command (state: &S, input: &E::Input) -> Option<Self>; fn input_to_command (state: &S, input: &E::Input) -> Option<Self>;
fn execute_with_state (state: &mut S, input: &E::Input) -> Perhaps<bool> {
Ok(if let Some(command) = Self::input_to_command(state, input) {
let _undo = command.execute(state)?;
Some(true)
} else {
None
})
}
} }
pub struct MenuBar<E: Engine, S, C: Command<S>> { pub struct MenuBar<E: Engine, S, C: Command<S>> {
pub menus: Vec<Menu<E, S, C>>, pub menus: Vec<Menu<E, S, C>>,

View file

@ -24,12 +24,7 @@ impl Handle<Tui> for Arranger<Tui> {
return Ok(Some(true)) return Ok(Some(true))
} }
} }
Ok(if let Some(command) = ArrangerCommand::input_to_command(self, i) { ArrangerCommand::execute_with_state(self, i)
let _undo = command.execute(self)?;
Some(true)
} else {
None
})
} }
} }
@ -78,12 +73,7 @@ impl InputToCommand<Tui, Arranger<Tui>> for ArrangerCommand {
/// Handle events for arrangement. /// Handle events for arrangement.
impl Handle<Tui> for Arrangement<Tui> { impl Handle<Tui> for Arrangement<Tui> {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> { fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
Ok(if let Some(command) = ArrangementCommand::input_to_command(self, from) { ArrangementCommand::execute_with_state(self, from)
let _undo = command.execute(self)?;
Some(true)
} else {
None
})
} }
} }

View file

@ -1,22 +1,16 @@
use crate::*; use crate::*;
/// The sampler plugin plays sounds. /// The sampler plugin plays sounds.
pub struct Sampler<E: Engine> { pub struct SamplerView<E: Engine> {
_engine: PhantomData<E>, _engine: PhantomData<E>,
pub jack: Arc<RwLock<JackClient>>, pub state: Sampler,
pub name: String, pub cursor: (usize, usize),
pub cursor: (usize, usize), pub editing: Option<Arc<RwLock<Sample>>>,
pub editing: Option<Arc<RwLock<Sample>>>, pub buffer: Vec<Vec<f32>>,
pub mapped: BTreeMap<u7, Arc<RwLock<Sample>>>, pub modal: Arc<Mutex<Option<Box<dyn Exit + Send>>>>,
pub unmapped: Vec<Arc<RwLock<Sample>>>,
pub voices: Arc<RwLock<Vec<Voice>>>,
pub ports: JackPorts,
pub buffer: Vec<Vec<f32>>,
pub modal: Arc<Mutex<Option<Box<dyn Exit + Send>>>>,
pub output_gain: f32
} }
impl<E: Engine> Sampler<E> { impl<E: Engine> SamplerView<E> {
pub fn new ( pub fn new (
jack: &Arc<RwLock<JackClient>>, jack: &Arc<RwLock<JackClient>>,
name: &str, name: &str,
@ -43,68 +37,6 @@ impl<E: Engine> Sampler<E> {
modal: Default::default() modal: Default::default()
})) }))
} }
/// Immutable reference to sample at cursor.
pub fn sample (&self) -> Option<&Arc<RwLock<Sample>>> {
for (i, sample) in self.mapped.values().enumerate() {
if i == self.cursor.0 {
return Some(sample)
}
}
for (i, sample) in self.unmapped.iter().enumerate() {
if i + self.mapped.len() == self.cursor.0 {
return Some(sample)
}
}
None
}
/// Create [Voice]s from [Sample]s in response to MIDI input.
pub fn process_midi_in (&mut self, scope: &ProcessScope) {
for RawMidi { time, bytes } in self.ports.midi_ins.get("midi").unwrap().iter(scope) {
if let LiveEvent::Midi { message, .. } = LiveEvent::parse(bytes).unwrap() {
if let MidiMessage::NoteOn { ref key, ref vel } = message {
if let Some(sample) = self.mapped.get(key) {
self.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 channel_count = self.buffer.len();
self.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
//);
self.buffer[channel % channel_count][index] +=
sample * self.output_gain;
}
} else {
return false
}
}
return true
});
}
/// Write output buffer to output ports.
pub fn write_output_buffer (&mut self, scope: &ProcessScope) {
for (i, port) in self.ports.audio_outs.values_mut().enumerate() {
let buffer = &self.buffer[i];
for (i, value) in port.as_mut_slice(scope).iter_mut().enumerate() {
*value = *buffer.get(i).unwrap_or(&0.0);
}
}
}
} }
/// A sound sample. /// A sound sample.

View file

@ -7,7 +7,7 @@ pub type PhraseMessage = Vec<u8>;
/// Collection of serialized MIDI messages /// Collection of serialized MIDI messages
pub type PhraseChunk = [Vec<PhraseMessage>]; pub type PhraseChunk = [Vec<PhraseMessage>];
/// Root level object for standalone `tek_sequencer` /// Root level object for standalone `tek_sequencer`
pub struct Sequencer<E: Engine> { pub struct SequencerView<E: Engine> {
/// JACK client handle (needs to not be dropped for standalone mode to work). /// JACK client handle (needs to not be dropped for standalone mode to work).
pub jack: Arc<RwLock<JackClient>>, pub jack: Arc<RwLock<JackClient>>,
/// Controls the JACK transport. /// Controls the JACK transport.
@ -25,6 +25,18 @@ pub struct Sequencer<E: Engine> {
/// Whether the currently focused item is entered /// Whether the currently focused item is entered
pub entered: bool, pub entered: bool,
} }
pub struct PhrasePoolView<E: Engine> {
_engine: PhantomData<E>,
state: PhrasePool,
/// Scroll offset
pub scroll: usize,
/// Mode switch
pub mode: Option<PhrasePoolMode>,
/// Whether this widget is focused
pub focused: bool,
/// Whether this widget is entered
pub entered: bool,
}
/// Sections in the sequencer app that may be focused /// Sections in the sequencer app that may be focused
#[derive(Copy, Clone, PartialEq, Eq)] pub enum SequencerFocus { #[derive(Copy, Clone, PartialEq, Eq)] pub enum SequencerFocus {
/// The transport (toolbar) is focused /// The transport (toolbar) is focused
@ -47,28 +59,6 @@ pub enum PhrasePoolMode {
/// Editing the length of a pattern /// Editing the length of a pattern
Length(usize, usize, PhraseLengthFocus), Length(usize, usize, PhraseLengthFocus),
} }
/// A MIDI sequence.
#[derive(Debug, Clone)] pub struct Phrase {
pub uuid: uuid::Uuid,
/// Name of phrase
pub name: String,
/// Temporal resolution in pulses per quarter note
pub ppq: usize,
/// Length of phrase in pulses
pub length: usize,
/// Notes in phrase
pub notes: PhraseData,
/// Whether to loop the phrase or play it once
pub loop_on: bool,
/// Start of loop
pub loop_start: usize,
/// Length of loop
pub loop_length: usize,
/// All notes are displayed with minimum length
pub percussive: bool,
/// Identifying color of phrase
pub color: ItemColorTriplet,
}
/// Contains state for viewing and editing a phrase /// Contains state for viewing and editing a phrase
pub struct PhraseEditor<E: Engine> { pub struct PhraseEditor<E: Engine> {
_engine: PhantomData<E>, _engine: PhantomData<E>,
@ -154,14 +144,22 @@ pub struct PhraseLength<E: Engine> {
/// Focus layout of sequencer app /// Focus layout of sequencer app
impl<E: Engine> FocusGrid for Sequencer<E> { impl<E: Engine> FocusGrid for Sequencer<E> {
type Item = SequencerFocus; type Item = SequencerFocus;
fn cursor (&self) -> (usize, usize) { self.focus_cursor } fn cursor (&self) -> (usize, usize) {
fn cursor_mut (&mut self) -> &mut (usize, usize) { &mut self.focus_cursor } self.focus_cursor
}
fn cursor_mut (&mut self) -> &mut (usize, usize) {
&mut self.focus_cursor
}
fn layout (&self) -> &[&[SequencerFocus]] { &[ fn layout (&self) -> &[&[SequencerFocus]] { &[
&[SequencerFocus::Transport], &[SequencerFocus::Transport],
&[SequencerFocus::PhrasePool, SequencerFocus::PhraseEditor], &[SequencerFocus::PhrasePool, SequencerFocus::PhraseEditor],
] } ] }
fn focus_enter (&mut self) { self.entered = true } fn focus_enter (&mut self) {
fn focus_exit (&mut self) { self.entered = false } self.entered = true
}
fn focus_exit (&mut self) {
self.entered = false
}
fn entered (&self) -> Option<Self::Item> { fn entered (&self) -> Option<Self::Item> {
if self.entered { Some(self.focused()) } else { None } if self.entered { Some(self.focused()) } else { None }
} }
@ -874,36 +872,3 @@ pub(crate) fn keys_vert () -> Buffer {
}); });
buffer buffer
} }
impl Handle<Tui> for PhraseEditor<Tui> {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
if let Some(command) = PhraseEditorCommand::input_to_command(self, from) {
let _undo = command.execute(self)?;
return Ok(Some(true))
}
Ok(None)
}
}
impl InputToCommand<Tui, PhraseEditor<Tui>> for PhraseEditorCommand {
fn input_to_command (_: &PhraseEditor<Tui>, from: &TuiInput) -> Option<Self> {
match from.event() {
key!(KeyCode::Char('`')) => Some(Self::ToggleDirection),
key!(KeyCode::Enter) => Some(Self::EnterEditMode),
key!(KeyCode::Esc) => Some(Self::ExitEditMode),
key!(KeyCode::Char('[')) => Some(Self::NoteLengthDec),
key!(KeyCode::Char(']')) => Some(Self::NoteLengthInc),
key!(KeyCode::Char('a')) => Some(Self::NoteAppend),
key!(KeyCode::Char('s')) => Some(Self::NoteSet),
key!(KeyCode::Char('-')) => Some(Self::TimeZoomOut),
key!(KeyCode::Char('_')) => Some(Self::TimeZoomOut),
key!(KeyCode::Char('=')) => Some(Self::TimeZoomIn),
key!(KeyCode::Char('+')) => Some(Self::TimeZoomIn),
key!(KeyCode::PageUp) => Some(Self::NotePageUp),
key!(KeyCode::PageDown) => Some(Self::NotePageDown),
key!(KeyCode::Up) => Some(Self::GoUp),
key!(KeyCode::Down) => Some(Self::GoDown),
key!(KeyCode::Left) => Some(Self::GoLeft),
key!(KeyCode::Right) => Some(Self::GoRight),
_ => None
}
}
}

View file

@ -255,3 +255,32 @@ impl<E: Engine> Command<PhraseEditor<E>> for PhraseEditorCommand {
Ok(None) Ok(None)
} }
} }
impl Handle<Tui> for PhraseEditor<Tui> {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
PhraseEditorCommand::execute_with_state(self, from)
}
}
impl InputToCommand<Tui, PhraseEditor<Tui>> for PhraseEditorCommand {
fn input_to_command (_: &PhraseEditor<Tui>, from: &TuiInput) -> Option<Self> {
match from.event() {
key!(KeyCode::Char('`')) => Some(Self::ToggleDirection),
key!(KeyCode::Enter) => Some(Self::EnterEditMode),
key!(KeyCode::Esc) => Some(Self::ExitEditMode),
key!(KeyCode::Char('[')) => Some(Self::NoteLengthDec),
key!(KeyCode::Char(']')) => Some(Self::NoteLengthInc),
key!(KeyCode::Char('a')) => Some(Self::NoteAppend),
key!(KeyCode::Char('s')) => Some(Self::NoteSet),
key!(KeyCode::Char('-')) => Some(Self::TimeZoomOut),
key!(KeyCode::Char('_')) => Some(Self::TimeZoomOut),
key!(KeyCode::Char('=')) => Some(Self::TimeZoomIn),
key!(KeyCode::Char('+')) => Some(Self::TimeZoomIn),
key!(KeyCode::PageUp) => Some(Self::NotePageUp),
key!(KeyCode::PageDown) => Some(Self::NotePageDown),
key!(KeyCode::Up) => Some(Self::GoUp),
key!(KeyCode::Down) => Some(Self::GoDown),
key!(KeyCode::Left) => Some(Self::GoLeft),
key!(KeyCode::Right) => Some(Self::GoRight),
_ => None
}
}
}

View file

@ -125,27 +125,3 @@ impl TransportToolbarFocus {
lay!(corners, highlight, *widget) lay!(corners, highlight, *widget)
} }
} }
impl Handle<Tui> for TransportToolbar<Tui> {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
if let Some(command) = TransportCommand::input_to_command(self, from) {
let _undo = command.execute(self)?;
return Ok(Some(true))
}
Ok(None)
}
}
impl InputToCommand<Tui, TransportToolbar<Tui>> for TransportCommand {
fn input_to_command (_: &TransportToolbar<Tui>, input: &TuiInput) -> Option<Self> {
match input.event() {
key!(KeyCode::Char(' ')) => Some(Self::FocusPrev),
key!(Shift-KeyCode::Char(' ')) => Some(Self::FocusPrev),
key!(KeyCode::Left) => Some(Self::FocusPrev),
key!(KeyCode::Right) => Some(Self::FocusNext),
key!(KeyCode::Char('.')) => Some(Self::Increment),
key!(KeyCode::Char(',')) => Some(Self::Decrement),
key!(KeyCode::Char('>')) => Some(Self::FineIncrement),
key!(KeyCode::Char('<')) => Some(Self::FineDecrement),
_ => None
}
}
}

View file

@ -1,103 +1,50 @@
use crate::*; use crate::*;
#[derive(Copy, Clone, PartialEq)] #[derive(Copy, Clone, PartialEq)]
pub enum TransportCommand { pub enum TransportViewCommand {
FocusNext, Focus(FocusCommand),
FocusPrev, Transport(TransportCommand),
Play,
Pause,
PlayToggle,
PlayFromStart,
Increment,
Decrement,
FineIncrement,
FineDecrement,
SeekUsec(f64),
SeekSample(f64),
SeekPulse(f64),
SetBpm(f64),
SetQuant(f64),
SetSync(f64),
} }
impl<E: Engine> Command<TransportView<E>> for TransportCommand { impl Handle<Tui> for TransportView<Tui> {
fn translate (self, state: &TransportView<E>) -> Self { fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
use TransportCommand::*; TransportViewCommand::execute_with_state(self, from)
use TransportViewFocus::*;
match self {
Increment => match state.focus {
Bpm =>
{return SetBpm(state.clock.timebase().bpm.get() + 1.0) },
Quant =>
{return SetQuant(next_note_length(state.clock.quant.get()as usize)as f64)},
Sync =>
{return SetSync(next_note_length(state.clock.sync.get()as usize)as f64+1.)},
PlayPause =>
{/*todo seek*/},
Clock =>
{/*todo seek*/},
},
FineIncrement => match state.focus {
Bpm =>
{return SetBpm(state.clock.timebase().bpm.get() + 0.001)},
Quant =>
{return Increment},
Sync =>
{return Increment},
PlayPause =>
{/*todo seek*/},
Clock =>
{/*todo seek*/},
},
Decrement => match state.focus {
Bpm =>
{return SetBpm(state.clock.timebase().bpm.get() - 1.0)},
Quant =>
{return SetQuant(prev_note_length(state.clock.quant.get()as usize)as f64)},
Sync =>
{return SetSync(prev_note_length(state.clock.sync.get()as usize)as f64)},
PlayPause =>
{/*todo seek*/},
Clock =>
{/*todo seek*/},
},
FineDecrement => match state.focus {
Bpm =>
{return SetBpm(state.clock.timebase().bpm.get() - 0.001)},
Quant =>
{return Decrement},
Sync =>
{return Decrement},
PlayPause =>
{/*todo seek*/},
Clock =>
{/*todo seek*/},
},
_ => {}
};
return self
} }
fn execute (self, state: &mut TransportView<E>) -> Perhaps<Self> { }
use TransportCommand::*; impl InputToCommand<Tui, TransportView<Tui>> for TransportViewCommand {
match self.translate(&state) { fn input_to_command (_: &TransportView<Tui>, input: &TuiInput) -> Option<Self> {
FocusNext => match input.event() {
{ state.focus.next(); }, key!(KeyCode::Char(' ')) => Some(Self::FocusPrev),
FocusPrev => key!(Shift-KeyCode::Char(' ')) => Some(Self::FocusPrev),
{ state.focus.prev(); }, key!(KeyCode::Left) => Some(Self::FocusPrev),
PlayToggle => key!(KeyCode::Right) => Some(Self::FocusNext),
{ state.toggle_play()?; }, key!(KeyCode::Char('.')) => Some(match state.focus {
SeekUsec(usec) => Bpm => SetBpm(state.clock.timebase().bpm.get() + 1.0),
{ state.clock.current.update_from_usec(usec); }, Quant => SetQuant(next_note_length(state.clock.quant.get()as usize)as f64),
SeekSample(sample) => Sync => SetSync(next_note_length(state.clock.sync.get()as usize)as f64+1.),
{ state.clock.current.update_from_sample(sample); }, PlayPause => {todo!()},
SeekPulse(pulse) => Clock => {todo!()}
{ state.clock.current.update_from_pulse(pulse); }, }),
SetBpm(bpm) => key!(KeyCode::Char(',')) => Some(match state.focus {
{ return Ok(Some(Self::SetBpm(state.clock.timebase().bpm.set(bpm)))) }, Bpm => SetBpm(state.clock.timebase().bpm.get() - 1.0),
SetQuant(quant) => Quant => SetQuant(prev_note_length(state.clock.quant.get()as usize)as f64),
{ return Ok(Some(Self::SetQuant(state.clock.quant.set(quant)))) }, Sync => SetSync(prev_note_length(state.clock.sync.get()as usize)as f64+1.),
SetSync(sync) => PlayPause => {todo!()},
{ return Ok(Some(Self::SetSync(state.clock.sync.set(sync)))) }, Clock => {todo!()}
_ => { unreachable!() } }),
key!(KeyCode::Char('>')) => Some(match state.focus {
Bpm => SetBpm(state.clock.timebase().bpm.get() + 0.001),
Quant => SetQuant(next_note_length(state.clock.quant.get()as usize)as f64),
Sync => SetSync(next_note_length(state.clock.sync.get()as usize)as f64+1.),
PlayPause => {todo!()},
Clock => {todo!()}
}),
key!(KeyCode::Char('<')) => Some(match state.focus {
Bpm => SetBpm(state.clock.timebase().bpm.get() - 0.001),
Quant => SetQuant(prev_note_length(state.clock.quant.get()as usize)as f64),
Sync => SetSync(prev_note_length(state.clock.sync.get()as usize)as f64+1.),
PlayPause => {todo!()},
Clock => {todo!()}
}),
_ => None
} }
Ok(None)
} }
} }