mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 12:16:42 +01:00
wip: refactor pt.5, no translate
This commit is contained in:
parent
8c37c95cc6
commit
5df08409e5
19 changed files with 389 additions and 457 deletions
|
|
@ -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),
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 {}
|
||||||
|
|
|
||||||
|
|
@ -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() {
|
||||||
|
|
|
||||||
21
crates/tek_api/src/plugin_kind.rs
Normal file
21
crates/tek_api/src/plugin_kind.rs
Normal 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",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
67
crates/tek_api/src/plugin_lv2.rs
Normal file
67
crates/tek_api/src/plugin_lv2.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
31
crates/tek_api/src/transport_cmd.rs
Normal file
31
crates/tek_api/src/transport_cmd.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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,
|
||||||
|
}
|
||||||
|
|
@ -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>>,
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue