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
|
|
@ -24,12 +24,7 @@ impl Handle<Tui> for Arranger<Tui> {
|
|||
return Ok(Some(true))
|
||||
}
|
||||
}
|
||||
Ok(if let Some(command) = ArrangerCommand::input_to_command(self, i) {
|
||||
let _undo = command.execute(self)?;
|
||||
Some(true)
|
||||
} else {
|
||||
None
|
||||
})
|
||||
ArrangerCommand::execute_with_state(self, i)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -78,12 +73,7 @@ impl InputToCommand<Tui, Arranger<Tui>> for ArrangerCommand {
|
|||
/// Handle events for arrangement.
|
||||
impl Handle<Tui> for Arrangement<Tui> {
|
||||
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
|
||||
Ok(if let Some(command) = ArrangementCommand::input_to_command(self, from) {
|
||||
let _undo = command.execute(self)?;
|
||||
Some(true)
|
||||
} else {
|
||||
None
|
||||
})
|
||||
ArrangementCommand::execute_with_state(self, from)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,22 +1,16 @@
|
|||
use crate::*;
|
||||
|
||||
/// The sampler plugin plays sounds.
|
||||
pub struct Sampler<E: Engine> {
|
||||
_engine: PhantomData<E>,
|
||||
pub jack: Arc<RwLock<JackClient>>,
|
||||
pub name: String,
|
||||
pub cursor: (usize, usize),
|
||||
pub editing: Option<Arc<RwLock<Sample>>>,
|
||||
pub mapped: BTreeMap<u7, Arc<RwLock<Sample>>>,
|
||||
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
|
||||
pub struct SamplerView<E: Engine> {
|
||||
_engine: PhantomData<E>,
|
||||
pub state: Sampler,
|
||||
pub cursor: (usize, usize),
|
||||
pub editing: Option<Arc<RwLock<Sample>>>,
|
||||
pub buffer: Vec<Vec<f32>>,
|
||||
pub modal: Arc<Mutex<Option<Box<dyn Exit + Send>>>>,
|
||||
}
|
||||
|
||||
impl<E: Engine> Sampler<E> {
|
||||
impl<E: Engine> SamplerView<E> {
|
||||
pub fn new (
|
||||
jack: &Arc<RwLock<JackClient>>,
|
||||
name: &str,
|
||||
|
|
@ -43,68 +37,6 @@ impl<E: Engine> Sampler<E> {
|
|||
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.
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ pub type PhraseMessage = Vec<u8>;
|
|||
/// Collection of serialized MIDI messages
|
||||
pub type PhraseChunk = [Vec<PhraseMessage>];
|
||||
/// 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).
|
||||
pub jack: Arc<RwLock<JackClient>>,
|
||||
/// Controls the JACK transport.
|
||||
|
|
@ -25,6 +25,18 @@ pub struct Sequencer<E: Engine> {
|
|||
/// Whether the currently focused item is entered
|
||||
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
|
||||
#[derive(Copy, Clone, PartialEq, Eq)] pub enum SequencerFocus {
|
||||
/// The transport (toolbar) is focused
|
||||
|
|
@ -47,28 +59,6 @@ pub enum PhrasePoolMode {
|
|||
/// Editing the length of a pattern
|
||||
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
|
||||
pub struct PhraseEditor<E: Engine> {
|
||||
_engine: PhantomData<E>,
|
||||
|
|
@ -154,14 +144,22 @@ pub struct PhraseLength<E: Engine> {
|
|||
/// Focus layout of sequencer app
|
||||
impl<E: Engine> FocusGrid for Sequencer<E> {
|
||||
type Item = SequencerFocus;
|
||||
fn cursor (&self) -> (usize, usize) { self.focus_cursor }
|
||||
fn cursor_mut (&mut self) -> &mut (usize, usize) { &mut self.focus_cursor }
|
||||
fn cursor (&self) -> (usize, usize) {
|
||||
self.focus_cursor
|
||||
}
|
||||
fn cursor_mut (&mut self) -> &mut (usize, usize) {
|
||||
&mut self.focus_cursor
|
||||
}
|
||||
fn layout (&self) -> &[&[SequencerFocus]] { &[
|
||||
&[SequencerFocus::Transport],
|
||||
&[SequencerFocus::PhrasePool, SequencerFocus::PhraseEditor],
|
||||
] }
|
||||
fn focus_enter (&mut self) { self.entered = true }
|
||||
fn focus_exit (&mut self) { self.entered = false }
|
||||
fn focus_enter (&mut self) {
|
||||
self.entered = true
|
||||
}
|
||||
fn focus_exit (&mut self) {
|
||||
self.entered = false
|
||||
}
|
||||
fn entered (&self) -> Option<Self::Item> {
|
||||
if self.entered { Some(self.focused()) } else { None }
|
||||
}
|
||||
|
|
@ -874,36 +872,3 @@ pub(crate) fn keys_vert () -> 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)
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
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::*;
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub enum TransportCommand {
|
||||
FocusNext,
|
||||
FocusPrev,
|
||||
Play,
|
||||
Pause,
|
||||
PlayToggle,
|
||||
PlayFromStart,
|
||||
Increment,
|
||||
Decrement,
|
||||
FineIncrement,
|
||||
FineDecrement,
|
||||
SeekUsec(f64),
|
||||
SeekSample(f64),
|
||||
SeekPulse(f64),
|
||||
SetBpm(f64),
|
||||
SetQuant(f64),
|
||||
SetSync(f64),
|
||||
pub enum TransportViewCommand {
|
||||
Focus(FocusCommand),
|
||||
Transport(TransportCommand),
|
||||
}
|
||||
impl<E: Engine> Command<TransportView<E>> for TransportCommand {
|
||||
fn translate (self, state: &TransportView<E>) -> Self {
|
||||
use TransportCommand::*;
|
||||
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
|
||||
impl Handle<Tui> for TransportView<Tui> {
|
||||
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
|
||||
TransportViewCommand::execute_with_state(self, from)
|
||||
}
|
||||
fn execute (self, state: &mut TransportView<E>) -> Perhaps<Self> {
|
||||
use TransportCommand::*;
|
||||
match self.translate(&state) {
|
||||
FocusNext =>
|
||||
{ state.focus.next(); },
|
||||
FocusPrev =>
|
||||
{ state.focus.prev(); },
|
||||
PlayToggle =>
|
||||
{ state.toggle_play()?; },
|
||||
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!() }
|
||||
}
|
||||
impl InputToCommand<Tui, TransportView<Tui>> for TransportViewCommand {
|
||||
fn input_to_command (_: &TransportView<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(match state.focus {
|
||||
Bpm => SetBpm(state.clock.timebase().bpm.get() + 1.0),
|
||||
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() - 1.0),
|
||||
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!()}
|
||||
}),
|
||||
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