mirror of
https://codeberg.org/unspeaker/tek.git
synced 2026-02-21 16:29:04 +01:00
1076 lines
30 KiB
Rust
1076 lines
30 KiB
Rust
use crate::*;
|
||
use clap::{self, Parser, Subcommand};
|
||
use builder_pattern::Builder;
|
||
|
||
/// Wraps [JackState], and through it [jack::Client] when connected.
|
||
///
|
||
/// ```
|
||
/// let jack = tek::Jack::default();
|
||
/// ```
|
||
#[derive(Clone, Debug, Default)] pub struct Jack<'j> (
|
||
pub(crate) Arc<RwLock<JackState<'j>>>
|
||
);
|
||
|
||
/// This is a connection which may be [Inactive], [Activating], or [Active].
|
||
/// In the [Active] and [Inactive] states, [JackState::client] returns a
|
||
/// [jack::Client], which you can use to talk to the JACK API.
|
||
///
|
||
/// ```
|
||
/// let state = tek::JackState::default();
|
||
/// ```
|
||
#[derive(Debug, Default)] pub enum JackState<'j> {
|
||
/// Unused
|
||
#[default] Inert,
|
||
/// Before activation.
|
||
Inactive(Client),
|
||
/// During activation.
|
||
Activating,
|
||
/// After activation. Must not be dropped for JACK thread to persist.
|
||
Active(DynamicAsyncClient<'j>),
|
||
}
|
||
|
||
/// Event enum for JACK events.
|
||
///
|
||
/// ```
|
||
/// let event = tek::JackEvent::XRun;
|
||
/// ```
|
||
#[derive(Debug, Clone, PartialEq)] pub enum JackEvent {
|
||
ThreadInit,
|
||
Shutdown(ClientStatus, Arc<str>),
|
||
Freewheel(bool),
|
||
SampleRate(Frames),
|
||
ClientRegistration(Arc<str>, bool),
|
||
PortRegistration(PortId, bool),
|
||
PortRename(PortId, Arc<str>, Arc<str>),
|
||
PortsConnected(PortId, PortId, bool),
|
||
GraphReorder,
|
||
XRun,
|
||
}
|
||
|
||
/// Generic notification handler that emits [JackEvent]
|
||
///
|
||
/// ```
|
||
/// let notify = tek::JackNotify(|_|{});
|
||
/// ```
|
||
pub struct JackNotify<T: Fn(JackEvent) + Send>(pub T);
|
||
|
||
/// Total state
|
||
///
|
||
/// ```
|
||
/// use tek::{TracksView, ScenesView, AddScene};
|
||
/// let mut app = tek::App::default();
|
||
/// let _ = app.scene_add(None, None).unwrap();
|
||
/// let _ = app.update_clock();
|
||
/// app.project.editor = Some(Default::default());
|
||
/// //let _: Vec<_> = app.project.inputs_with_sizes().collect();
|
||
/// //let _: Vec<_> = app.project.outputs_with_sizes().collect();
|
||
/// let _: Vec<_> = app.project.tracks_with_sizes().collect();
|
||
/// //let _: Vec<_> = app.project.scenes_with_sizes(true, 10, 10).collect();
|
||
/// //let _: Vec<_> = app.scenes_with_colors(true, 10).collect();
|
||
/// //let _: Vec<_> = app.scenes_with_track_colors(true, 10, 10).collect();
|
||
/// let _ = app.project.w();
|
||
/// //let _ = app.project.w_sidebar();
|
||
/// //let _ = app.project.w_tracks_area();
|
||
/// let _ = app.project.h();
|
||
/// //let _ = app.project.h_tracks_area();
|
||
/// //let _ = app.project.h_inputs();
|
||
/// //let _ = app.project.h_outputs();
|
||
/// let _ = app.project.h_scenes();
|
||
/// ```
|
||
#[derive(Default, Debug)] pub struct App {
|
||
/// Base color.
|
||
pub color: ItemTheme,
|
||
/// Must not be dropped for the duration of the process
|
||
pub jack: Jack<'static>,
|
||
/// Display size
|
||
pub size: Measure<TuiOut>,
|
||
/// Performance counter
|
||
pub perf: PerfModel,
|
||
/// Available view modes and input bindings
|
||
pub config: Config,
|
||
/// Currently selected mode
|
||
pub mode: Arc<Mode<Arc<str>>>,
|
||
/// Undo history
|
||
pub history: Vec<(AppCommand, Option<AppCommand>)>,
|
||
/// Dialog overlay
|
||
pub dialog: Dialog,
|
||
/// Contains all recently created clips.
|
||
pub pool: Pool,
|
||
/// Contains the currently edited musical arrangement
|
||
pub project: Arrangement,
|
||
/// Error, if any
|
||
pub error: Arc<RwLock<Option<Arc<str>>>>
|
||
}
|
||
|
||
/// Configuration: mode, view, and bind definitions.
|
||
///
|
||
/// ```
|
||
/// let conf = tek::Config::default();
|
||
/// ```
|
||
#[derive(Default, Debug)] pub struct Config {
|
||
/// XDG base directories of running user.
|
||
pub dirs: BaseDirectories,
|
||
/// Active collection of interaction modes.
|
||
pub modes: Modes,
|
||
/// Active collection of event bindings.
|
||
pub binds: Binds,
|
||
/// Active collection of view definitions.
|
||
pub views: Views,
|
||
}
|
||
|
||
/// Group of view and keys definitions.
|
||
///
|
||
/// ```
|
||
/// let mode = tek::Mode::<std::sync::Arc<str>>::default();
|
||
/// ```
|
||
#[derive(Default, Debug)] pub struct Mode<D: Language + Ord> {
|
||
pub path: PathBuf,
|
||
pub name: Vec<D>,
|
||
pub info: Vec<D>,
|
||
pub view: Vec<D>,
|
||
pub keys: Vec<D>,
|
||
pub modes: Modes,
|
||
}
|
||
|
||
/// An input binding.
|
||
///
|
||
/// ```
|
||
/// let bind = tek::Bind::<(), ()>::default();
|
||
/// ```
|
||
#[derive(Debug)] pub struct Bind<E, C>(
|
||
/// Map of each event (e.g. key combination) to
|
||
/// all command expressions bound to it by
|
||
/// all loaded input layers.
|
||
pub BTreeMap<E, Vec<Binding<C>>>
|
||
);
|
||
|
||
/// An input binding.
|
||
///
|
||
/// ```
|
||
/// let binding: tek::Binding<()> = Default::default();
|
||
/// ```
|
||
#[derive(Debug, Clone)] pub struct Binding<C> {
|
||
pub commands: Arc<[C]>,
|
||
pub condition: Option<Condition>,
|
||
pub description: Option<Arc<str>>,
|
||
pub source: Option<Arc<PathBuf>>,
|
||
}
|
||
|
||
/// Condition that must evaluate to true in order to enable an input layer.
|
||
///
|
||
/// ```
|
||
/// let condition = tek::Condition(std::sync::Arc::new(Box::new(||{true})));
|
||
/// ```
|
||
#[derive(Clone)] pub struct Condition(
|
||
pub Arc<Box<dyn Fn()->bool + Send + Sync>>
|
||
);
|
||
|
||
/// List of menu items.
|
||
///
|
||
/// ```
|
||
/// let items: tek::MenuItems = Default::default();
|
||
/// ```
|
||
#[derive(Debug, Clone, Default, PartialEq)] pub struct MenuItems(
|
||
pub Arc<[MenuItem]>
|
||
);
|
||
|
||
/// An item of a menu.
|
||
///
|
||
/// ```
|
||
/// let item: tek::MenuItem = Default::default();
|
||
/// ```
|
||
#[derive(Clone)] pub struct MenuItem(
|
||
/// Label
|
||
pub Arc<str>,
|
||
/// Callback
|
||
pub Arc<Box<dyn Fn(&mut App)->Usually<()> + Send + Sync>>
|
||
);
|
||
|
||
/// The command-line interface descriptor.
|
||
///
|
||
/// ```
|
||
/// let cli: tek::Cli = Default::default();
|
||
///
|
||
/// use clap::CommandFactory;
|
||
/// tek::Cli::command().debug_assert();
|
||
/// ```
|
||
#[derive(Parser)]
|
||
#[command(name = "tek", version, about = Some(HEADER), long_about = Some(HEADER))]
|
||
#[derive(Debug, Default)] pub struct Cli {
|
||
/// Pre-defined configuration modes.
|
||
///
|
||
/// TODO: Replace these with scripted configurations.
|
||
#[command(subcommand)] pub action: Action,
|
||
}
|
||
|
||
/// Application modes that can be passed to the mommand line interface.
|
||
///
|
||
/// ```
|
||
/// let action: tek::Action = Default::default();
|
||
/// ```
|
||
#[derive(Debug, Clone, Subcommand, Default)] pub enum Action {
|
||
/// Continue where you left off
|
||
#[default] Resume,
|
||
/// Run headlessly in current session.
|
||
Headless,
|
||
/// Show status of current session.
|
||
Status,
|
||
/// List known sessions.
|
||
List,
|
||
/// Continue work in a copy of the current session.
|
||
Fork,
|
||
/// Create a new empty session.
|
||
New {
|
||
/// Name of JACK client
|
||
#[arg(short='n', long)] name: Option<String>,
|
||
/// Whether to attempt to become transport master
|
||
#[arg(short='Y', long, default_value_t = false)] sync_lead: bool,
|
||
/// Whether to sync to external transport master
|
||
#[arg(short='y', long, default_value_t = true)] sync_follow: bool,
|
||
/// Initial tempo in beats per minute
|
||
#[arg(short='b', long, default_value = None)] bpm: Option<f64>,
|
||
/// Whether to include a transport toolbar (default: true)
|
||
#[arg(short='c', long, default_value_t = true)] show_clock: bool,
|
||
/// MIDI outs to connect to (multiple instances accepted)
|
||
#[arg(short='I', long)] midi_from: Vec<String>,
|
||
/// MIDI outs to connect to (multiple instances accepted)
|
||
#[arg(short='i', long)] midi_from_re: Vec<String>,
|
||
/// MIDI ins to connect to (multiple instances accepted)
|
||
#[arg(short='O', long)] midi_to: Vec<String>,
|
||
/// MIDI ins to connect to (multiple instances accepted)
|
||
#[arg(short='o', long)] midi_to_re: Vec<String>,
|
||
/// Audio outs to connect to left input
|
||
#[arg(short='l', long)] left_from: Vec<String>,
|
||
/// Audio outs to connect to right input
|
||
#[arg(short='r', long)] right_from: Vec<String>,
|
||
/// Audio ins to connect from left output
|
||
#[arg(short='L', long)] left_to: Vec<String>,
|
||
/// Audio ins to connect from right output
|
||
#[arg(short='R', long)] right_to: Vec<String>,
|
||
/// Tracks to create
|
||
#[arg(short='t', long)] tracks: Option<usize>,
|
||
/// Scenes to create
|
||
#[arg(short='s', long)] scenes: Option<usize>,
|
||
},
|
||
/// Import media as new session.
|
||
Import,
|
||
/// Show configuration.
|
||
Config,
|
||
/// Show version.
|
||
Version,
|
||
}
|
||
|
||
/// A control axis.
|
||
///
|
||
/// ```
|
||
/// let axis = tek::ControlAxis::X;
|
||
/// ```
|
||
#[derive(Debug, Copy, Clone)] pub enum ControlAxis {
|
||
X, Y, Z, I
|
||
}
|
||
|
||
/// Various possible dialog modes.
|
||
///
|
||
/// ```
|
||
/// let dialog: tek::Dialog = Default::default();
|
||
/// ```
|
||
#[derive(Debug, Clone, Default, PartialEq)] pub enum Dialog {
|
||
#[default] None,
|
||
Help(usize),
|
||
Menu(usize, MenuItems),
|
||
Device(usize),
|
||
Message(Arc<str>),
|
||
Browse(BrowseTarget, Arc<Browse>),
|
||
Options,
|
||
}
|
||
|
||
/// Temporal resolutions: sample rate, tempo, MIDI pulses per quaver (beat)
|
||
///
|
||
/// ```
|
||
///
|
||
/// ```
|
||
#[derive(Debug, Clone)] pub struct Timebase {
|
||
/// Audio samples per second
|
||
pub sr: SampleRate,
|
||
/// MIDI beats per minute
|
||
pub bpm: BeatsPerMinute,
|
||
/// MIDI ticks per beat
|
||
pub ppq: PulsesPerQuaver,
|
||
}
|
||
|
||
/// Iterator that emits subsequent ticks within a range.
|
||
///
|
||
/// ```
|
||
/// let iter = tek::TicksIterator::default();
|
||
/// ```
|
||
#[derive(Debug, Default)] pub struct TicksIterator {
|
||
pub spp: f64,
|
||
pub sample: usize,
|
||
pub start: usize,
|
||
pub end: usize,
|
||
}
|
||
|
||
///
|
||
/// ```
|
||
/// use tek::{TimeRange, NoteRange};
|
||
/// let model = tek::MidiSelection::from((1, false));
|
||
///
|
||
/// let _ = model.get_time_len();
|
||
/// let _ = model.get_time_zoom();
|
||
/// let _ = model.get_time_lock();
|
||
/// let _ = model.get_time_start();
|
||
/// let _ = model.get_time_axis();
|
||
/// let _ = model.get_time_end();
|
||
///
|
||
/// let _ = model.get_note_lo();
|
||
/// let _ = model.get_note_axis();
|
||
/// let _ = model.get_note_hi();
|
||
/// ```
|
||
#[derive(Debug, Clone)] pub struct MidiCursor {
|
||
/// Time coordinate of cursor
|
||
pub time_pos: Arc<AtomicUsize>,
|
||
/// Note coordinate of cursor
|
||
pub note_pos: Arc<AtomicUsize>,
|
||
/// Length of note that will be inserted, in pulses
|
||
pub note_len: Arc<AtomicUsize>,
|
||
}
|
||
|
||
///
|
||
/// ```
|
||
///
|
||
/// ```
|
||
#[derive(Debug, Clone, Default)] pub struct MidiSelection {
|
||
pub time_len: Arc<AtomicUsize>,
|
||
/// Length of visible time axis
|
||
pub time_axis: Arc<AtomicUsize>,
|
||
/// Earliest time displayed
|
||
pub time_start: Arc<AtomicUsize>,
|
||
/// Time step
|
||
pub time_zoom: Arc<AtomicUsize>,
|
||
/// Auto rezoom to fit in time axis
|
||
pub time_lock: Arc<AtomicBool>,
|
||
/// Length of visible note axis
|
||
pub note_axis: Arc<AtomicUsize>,
|
||
// Lowest note displayed
|
||
pub note_lo: Arc<AtomicUsize>,
|
||
}
|
||
|
||
/// A point in time in all time scales (microsecond, sample, MIDI pulse)
|
||
///
|
||
/// ```
|
||
///
|
||
/// ```
|
||
#[derive(Debug, Default, Clone)] pub struct Moment {
|
||
pub timebase: Arc<Timebase>,
|
||
/// Current time in microseconds
|
||
pub usec: Microsecond,
|
||
/// Current time in audio samples
|
||
pub sample: SampleCount,
|
||
/// Current time in MIDI pulses
|
||
pub pulse: Pulse,
|
||
}
|
||
|
||
///
|
||
/// ```
|
||
///
|
||
/// ```
|
||
#[derive(Debug, Clone, Default)] pub enum Moment2 {
|
||
#[default] None,
|
||
Zero,
|
||
Usec(Microsecond),
|
||
Sample(SampleCount),
|
||
Pulse(Pulse),
|
||
}
|
||
|
||
/// MIDI resolution in PPQ (pulses per quarter note)
|
||
///
|
||
/// ```
|
||
///
|
||
/// ```
|
||
#[derive(Debug, Default)] pub struct PulsesPerQuaver (pub(crate) AtomicF64);
|
||
|
||
/// Timestamp in MIDI pulses
|
||
///
|
||
/// ```
|
||
///
|
||
/// ```
|
||
#[derive(Debug, Default)] pub struct Pulse (pub(crate) AtomicF64);
|
||
|
||
/// Tempo in beats per minute
|
||
///
|
||
/// ```
|
||
///
|
||
/// ```
|
||
#[derive(Debug, Default)] pub struct BeatsPerMinute (pub(crate) AtomicF64);
|
||
|
||
/// Quantization setting for launching clips
|
||
///
|
||
/// ```
|
||
///
|
||
/// ```
|
||
#[derive(Debug, Default)] pub struct LaunchSync (pub(crate) AtomicF64);
|
||
|
||
/// Quantization setting for notes
|
||
///
|
||
/// ```
|
||
///
|
||
/// ```
|
||
#[derive(Debug, Default)] pub struct Quantize (pub(crate) AtomicF64);
|
||
|
||
/// Timestamp in audio samples
|
||
///
|
||
/// ```
|
||
///
|
||
/// ```
|
||
#[derive(Debug, Default)] pub struct SampleCount (pub(crate) AtomicF64);
|
||
|
||
/// Audio sample rate in Hz (samples per second)
|
||
///
|
||
/// ```
|
||
///
|
||
/// ```
|
||
#[derive(Debug, Default)] pub struct SampleRate (pub(crate) AtomicF64);
|
||
|
||
/// Timestamp in microseconds
|
||
///
|
||
/// ```
|
||
///
|
||
/// ```
|
||
#[derive(Debug, Default)] pub struct Microsecond (pub(crate) AtomicF64);
|
||
|
||
/// The source of time.
|
||
///
|
||
/// ```
|
||
/// let clock = tek::Clock::default();
|
||
/// ```
|
||
#[derive(Clone, Default)] pub struct Clock {
|
||
/// JACK transport handle.
|
||
pub transport: Arc<Option<Transport>>,
|
||
/// Global temporal resolution (shared by [Moment] fields)
|
||
pub timebase: Arc<Timebase>,
|
||
/// Current global sample and usec (monotonic from JACK clock)
|
||
pub global: Arc<Moment>,
|
||
/// Global sample and usec at which playback started
|
||
pub started: Arc<RwLock<Option<Moment>>>,
|
||
/// Playback offset (when playing not from start)
|
||
pub offset: Arc<Moment>,
|
||
/// Current playhead position
|
||
pub playhead: Arc<Moment>,
|
||
/// Note quantization factor
|
||
pub quant: Arc<Quantize>,
|
||
/// Launch quantization factor
|
||
pub sync: Arc<LaunchSync>,
|
||
/// Size of buffer in samples
|
||
pub chunk: Arc<AtomicUsize>,
|
||
// Cache of formatted strings
|
||
pub view_cache: Arc<RwLock<ClockView>>,
|
||
/// For syncing the clock to an external source
|
||
#[cfg(feature = "port")] pub midi_in: Arc<RwLock<Option<MidiInput>>>,
|
||
/// For syncing other devices to this clock
|
||
#[cfg(feature = "port")] pub midi_out: Arc<RwLock<Option<MidiOutput>>>,
|
||
/// For emitting a metronome
|
||
#[cfg(feature = "port")] pub click_out: Arc<RwLock<Option<AudioOutput>>>,
|
||
}
|
||
|
||
/// Contains memoized renders of clock values.
|
||
///
|
||
/// Performance optimization.
|
||
#[derive(Debug)] pub struct ClockView {
|
||
pub sr: Memo<Option<(bool, f64)>, String>,
|
||
pub buf: Memo<Option<f64>, String>,
|
||
pub lat: Memo<Option<f64>, String>,
|
||
pub bpm: Memo<Option<f64>, String>,
|
||
pub beat: Memo<Option<f64>, String>,
|
||
pub time: Memo<Option<f64>, String>,
|
||
}
|
||
|
||
/// Arranger.
|
||
///
|
||
/// ```
|
||
/// let arranger = tek::Arrangement::default();
|
||
/// ```
|
||
#[derive(Default, Debug)] pub struct Arrangement {
|
||
/// Project name.
|
||
pub name: Arc<str>,
|
||
/// Base color.
|
||
pub color: ItemTheme,
|
||
/// JACK client handle.
|
||
pub jack: Jack<'static>,
|
||
/// FIXME a render of the project arrangement, redrawn on update.
|
||
/// TODO rename to "render_cache" or smth
|
||
pub arranger: Arc<RwLock<Buffer>>,
|
||
/// Display size
|
||
pub size: Measure<TuiOut>,
|
||
/// Display size of clips area
|
||
pub size_inner: Measure<TuiOut>,
|
||
/// Source of time
|
||
#[cfg(feature = "clock")] pub clock: Clock,
|
||
/// Allows one MIDI clip to be edited
|
||
#[cfg(feature = "editor")] pub editor: Option<MidiEditor>,
|
||
/// List of global midi inputs
|
||
#[cfg(feature = "port")] pub midi_ins: Vec<MidiInput>,
|
||
/// List of global midi outputs
|
||
#[cfg(feature = "port")] pub midi_outs: Vec<MidiOutput>,
|
||
/// List of global audio inputs
|
||
#[cfg(feature = "port")] pub audio_ins: Vec<AudioInput>,
|
||
/// List of global audio outputs
|
||
#[cfg(feature = "port")] pub audio_outs: Vec<AudioOutput>,
|
||
/// Selected UI element
|
||
#[cfg(feature = "select")] pub selection: Selection,
|
||
/// Last track number (to avoid duplicate port names)
|
||
#[cfg(feature = "track")] pub track_last: usize,
|
||
/// List of tracks
|
||
#[cfg(feature = "track")] pub tracks: Vec<Track>,
|
||
/// Scroll offset of tracks
|
||
#[cfg(feature = "track")] pub track_scroll: usize,
|
||
/// List of scenes
|
||
#[cfg(feature = "scene")] pub scenes: Vec<Scene>,
|
||
/// Scroll offset of scenes
|
||
#[cfg(feature = "scene")] pub scene_scroll: usize,
|
||
}
|
||
|
||
/// Browses for files to load/save.
|
||
///
|
||
/// ```
|
||
/// let browse = tek::Browse::default();
|
||
/// ```
|
||
#[derive(Debug, Clone, Default, PartialEq)] pub struct Browse {
|
||
pub cwd: PathBuf,
|
||
pub dirs: Vec<(OsString, String)>,
|
||
pub files: Vec<(OsString, String)>,
|
||
pub filter: String,
|
||
pub index: usize,
|
||
pub scroll: usize,
|
||
pub size: Measure<TuiOut>,
|
||
}
|
||
|
||
pub(crate) struct EntriesIterator<'a> {
|
||
pub browser: &'a Browse,
|
||
pub offset: usize,
|
||
pub length: usize,
|
||
pub index: usize,
|
||
}
|
||
|
||
#[derive(Clone, Debug)] pub enum BrowseTarget {
|
||
SaveProject,
|
||
LoadProject,
|
||
ImportSample(Arc<RwLock<Option<Sample>>>),
|
||
ExportSample(Arc<RwLock<Option<Sample>>>),
|
||
ImportClip(Arc<RwLock<Option<MidiClip>>>),
|
||
ExportClip(Arc<RwLock<Option<MidiClip>>>),
|
||
}
|
||
|
||
/// A MIDI sequence.
|
||
///
|
||
/// ```
|
||
/// let clip = tek::MidiClip::default();
|
||
/// ```
|
||
#[derive(Debug, Clone, Default)] pub struct MidiClip {
|
||
pub uuid: uuid::Uuid,
|
||
/// Name of clip
|
||
pub name: Arc<str>,
|
||
/// Temporal resolution in pulses per quarter note
|
||
pub ppq: usize,
|
||
/// Length of clip in pulses
|
||
pub length: usize,
|
||
/// Notes in clip
|
||
pub notes: MidiData,
|
||
/// Whether to loop the clip or play it once
|
||
pub looped: 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 clip
|
||
pub color: ItemTheme,
|
||
}
|
||
|
||
/// A device that can be plugged into the chain.
|
||
///
|
||
/// ```
|
||
/// let device = tek::Device::default();
|
||
/// ```
|
||
#[derive(Debug, Default)] pub enum Device {
|
||
#[default]
|
||
Bypass,
|
||
Mute,
|
||
#[cfg(feature = "sampler")]
|
||
Sampler(Sampler),
|
||
#[cfg(feature = "lv2")] // TODO
|
||
Lv2(Lv2),
|
||
#[cfg(feature = "vst2")] // TODO
|
||
Vst2,
|
||
#[cfg(feature = "vst3")] // TODO
|
||
Vst3,
|
||
#[cfg(feature = "clap")] // TODO
|
||
Clap,
|
||
#[cfg(feature = "sf2")] // TODO
|
||
Sf2,
|
||
}
|
||
|
||
/// Some sort of wrapper?
|
||
pub struct DeviceAudio<'a>(pub &'a mut Device);
|
||
|
||
/// Contains state for viewing and editing a clip.
|
||
///
|
||
/// ```
|
||
/// use std::sync::{Arc, RwLock};
|
||
/// let clip = tek::MidiClip::stop_all();
|
||
/// let mut editor = tek::MidiEditor {
|
||
/// mode: tek::PianoHorizontal::new(Some(&Arc::new(RwLock::new(clip)))),
|
||
/// size: Default::default(),
|
||
/// //keys: Default::default(),
|
||
/// };
|
||
/// let _ = editor.put_note(true);
|
||
/// let _ = editor.put_note(false);
|
||
/// let _ = editor.clip_status();
|
||
/// let _ = editor.edit_status();
|
||
/// ```
|
||
pub struct MidiEditor {
|
||
/// Size of editor on screen
|
||
pub size: Measure<TuiOut>,
|
||
/// View mode and state of editor
|
||
pub mode: PianoHorizontal,
|
||
}
|
||
|
||
/// A clip, rendered as a horizontal piano roll.
|
||
///
|
||
/// ```
|
||
/// let piano = tek::PianoHorizontal::default();
|
||
/// ```
|
||
#[derive(Clone, Default)] pub struct PianoHorizontal {
|
||
pub clip: Option<Arc<RwLock<MidiClip>>>,
|
||
/// Buffer where the whole clip is rerendered on change
|
||
pub buffer: Arc<RwLock<BigBuffer>>,
|
||
/// Size of actual notes area
|
||
pub size: Measure<TuiOut>,
|
||
/// The display window
|
||
pub range: MidiSelection,
|
||
/// The note cursor
|
||
pub point: MidiCursor,
|
||
/// The highlight color palette
|
||
pub color: ItemTheme,
|
||
/// Width of the keyboard
|
||
pub keys_width: u16,
|
||
}
|
||
|
||
/// 12 piano keys, some highlighted.
|
||
///
|
||
/// ```
|
||
/// let keys = tek::OctaveVertical::default();
|
||
/// ```
|
||
#[derive(Copy, Clone)] pub struct OctaveVertical {
|
||
pub on: [bool; 12],
|
||
pub colors: [Color; 3]
|
||
}
|
||
|
||
/// A LV2 plugin.
|
||
#[derive(Debug)] #[cfg(feature = "lv2")] pub struct Lv2 {
|
||
/// JACK client handle (needs to not be dropped for standalone mode to work).
|
||
pub jack: Jack<'static>,
|
||
pub name: Arc<str>,
|
||
pub path: Option<Arc<str>>,
|
||
pub selected: usize,
|
||
pub mapping: bool,
|
||
pub midi_ins: Vec<Port<MidiIn>>,
|
||
pub midi_outs: Vec<Port<MidiOut>>,
|
||
pub audio_ins: Vec<Port<AudioIn>>,
|
||
pub audio_outs: Vec<Port<AudioOut>>,
|
||
|
||
pub lv2_world: livi::World,
|
||
pub lv2_instance: livi::Instance,
|
||
pub lv2_plugin: livi::Plugin,
|
||
pub lv2_features: Arc<livi::Features>,
|
||
pub lv2_port_list: Vec<livi::Port>,
|
||
pub lv2_input_buffer: Vec<livi::event::LV2AtomSequence>,
|
||
pub lv2_ui_thread: Option<JoinHandle<()>>,
|
||
}
|
||
|
||
/// A LV2 plugin's X11 UI.
|
||
#[cfg(feature = "lv2_gui")] pub struct LV2PluginUI {
|
||
pub window: Option<Window>
|
||
}
|
||
|
||
#[derive(Debug, Default)] pub enum MeteringMode {
|
||
#[default] Rms,
|
||
Log10,
|
||
}
|
||
|
||
#[derive(Debug, Default, Clone)] pub struct Log10Meter(pub f32);
|
||
|
||
#[derive(Debug, Default, Clone)] pub struct RmsMeter(pub f32);
|
||
|
||
#[derive(Debug, Default)] pub enum MixingMode {
|
||
#[default] Summing,
|
||
Average,
|
||
}
|
||
|
||
/// A clip pool.
|
||
///
|
||
/// ```
|
||
/// let pool = tek::Pool::default();
|
||
/// ```
|
||
#[derive(Debug)] pub struct Pool {
|
||
pub visible: bool,
|
||
/// Selected clip
|
||
pub clip: AtomicUsize,
|
||
/// Mode switch
|
||
pub mode: Option<PoolMode>,
|
||
/// Embedded file browse
|
||
#[cfg(feature = "browse")] pub browse: Option<Browse>,
|
||
/// Collection of MIDI clips.
|
||
#[cfg(feature = "clip")] pub clips: Arc<RwLock<Vec<Arc<RwLock<MidiClip>>>>>,
|
||
/// Collection of sound samples.
|
||
#[cfg(feature = "sampler")] pub samples: Arc<RwLock<Vec<Arc<RwLock<Sample>>>>>,
|
||
}
|
||
|
||
/// Displays and edits clip length.
|
||
#[derive(Clone, Debug, Default)] pub struct ClipLength {
|
||
/// Pulses per beat (quaver)
|
||
pub ppq: usize,
|
||
/// Beats per bar
|
||
pub bpb: usize,
|
||
/// Length of clip in pulses
|
||
pub pulses: usize,
|
||
/// Selected subdivision
|
||
pub focus: Option<ClipLengthFocus>,
|
||
}
|
||
|
||
/// Some sort of wrapper again?
|
||
pub struct PoolView<'a>(pub &'a Pool);
|
||
|
||
/// Audio input port.
|
||
#[derive(Debug)] pub struct AudioInput {
|
||
/// Handle to JACK client, for receiving reconnect events.
|
||
pub jack: Jack<'static>,
|
||
/// Port name
|
||
pub name: Arc<str>,
|
||
/// Port handle.
|
||
pub port: Port<AudioIn>,
|
||
/// List of ports to connect to.
|
||
pub connections: Vec<Connect>,
|
||
}
|
||
|
||
/// Audio output port.
|
||
#[derive(Debug)] pub struct AudioOutput {
|
||
/// Handle to JACK client, for receiving reconnect events.
|
||
pub jack: Jack<'static>,
|
||
/// Port name
|
||
pub name: Arc<str>,
|
||
/// Port handle.
|
||
pub port: Port<AudioOut>,
|
||
/// List of ports to connect to.
|
||
pub connections: Vec<Connect>,
|
||
}
|
||
|
||
/// MIDI input port.
|
||
#[derive(Debug)] pub struct MidiInput {
|
||
/// Handle to JACK client, for receiving reconnect events.
|
||
pub jack: Jack<'static>,
|
||
/// Port name
|
||
pub name: Arc<str>,
|
||
/// Port handle.
|
||
pub port: Port<MidiIn>,
|
||
/// List of currently held notes.
|
||
pub held: Arc<RwLock<[bool;128]>>,
|
||
/// List of ports to connect to.
|
||
pub connections: Vec<Connect>,
|
||
}
|
||
|
||
/// MIDI output port.
|
||
#[derive(Debug)] pub struct MidiOutput {
|
||
/// Handle to JACK client, for receiving reconnect events.
|
||
pub jack: Jack<'static>,
|
||
/// Port name
|
||
pub name: Arc<str>,
|
||
/// Port handle.
|
||
pub port: Port<MidiOut>,
|
||
/// List of ports to connect to.
|
||
pub connections: Vec<Connect>,
|
||
/// List of currently held notes.
|
||
pub held: Arc<RwLock<[bool;128]>>,
|
||
/// Buffer
|
||
pub note_buffer: Vec<u8>,
|
||
/// Buffer
|
||
pub output_buffer: Vec<Vec<Vec<u8>>>,
|
||
}
|
||
|
||
/// Port connection manager.
|
||
///
|
||
/// ```
|
||
/// let connect = tek::Connect::default();
|
||
/// ```
|
||
#[derive(Clone, Debug, Default)] pub struct Connect {
|
||
pub name: Option<ConnectName>,
|
||
pub scope: Option<ConnectScope>,
|
||
pub status: Arc<RwLock<Vec<(Port<Unowned>, Arc<str>, ConnectStatus)>>>,
|
||
pub info: Arc<str>,
|
||
}
|
||
|
||
/// Plays [Voice]s from [Sample]s.
|
||
///
|
||
/// ```
|
||
/// let sampler = tek::Sampler::default();
|
||
/// ```
|
||
#[derive(Debug, Default)] pub struct Sampler {
|
||
/// Name of sampler.
|
||
pub name: Arc<str>,
|
||
/// Device color.
|
||
pub color: ItemTheme,
|
||
/// Audio input ports. Samples get recorded here.
|
||
#[cfg(feature = "port")] pub audio_ins: Vec<AudioInput>,
|
||
/// Audio input meters.
|
||
#[cfg(feature = "meter")] pub input_meters: Vec<f32>,
|
||
/// Sample currently being recorded.
|
||
pub recording: Option<(usize, Option<Arc<RwLock<Sample>>>)>,
|
||
/// Recording buffer.
|
||
pub buffer: Vec<Vec<f32>>,
|
||
/// Samples mapped to MIDI notes.
|
||
pub samples: Samples<128>,
|
||
/// Samples that are not mapped to MIDI notes.
|
||
pub unmapped: Vec<Arc<RwLock<Sample>>>,
|
||
/// Sample currently being edited.
|
||
pub editing: Option<Arc<RwLock<Sample>>>,
|
||
/// MIDI input port. Triggers sample playback.
|
||
#[cfg(feature = "port")] pub midi_in: Option<MidiInput>,
|
||
/// Collection of currently playing instances of samples.
|
||
pub voices: Arc<RwLock<Vec<Voice>>>,
|
||
/// Audio output ports. Voices get played here.
|
||
#[cfg(feature = "port")] pub audio_outs: Vec<AudioOutput>,
|
||
/// Audio output meters.
|
||
#[cfg(feature = "meter")] pub output_meters: Vec<f32>,
|
||
/// How to mix the voices.
|
||
pub mixing_mode: MixingMode,
|
||
/// How to meter the inputs and outputs.
|
||
pub metering_mode: MeteringMode,
|
||
/// Fixed gain applied to all output.
|
||
pub output_gain: f32,
|
||
/// Currently active modal, if any.
|
||
pub mode: Option<SamplerMode>,
|
||
/// Size of rendered sampler.
|
||
pub size: Measure<TuiOut>,
|
||
/// Lowest note displayed.
|
||
pub note_lo: AtomicUsize,
|
||
/// Currently selected note.
|
||
pub note_pt: AtomicUsize,
|
||
/// Selected note as row/col.
|
||
pub cursor: (AtomicUsize, AtomicUsize),
|
||
}
|
||
|
||
/// Collection of samples, one per slot, fixed number of slots.
|
||
///
|
||
/// TODO: Map more than one sample per slot.
|
||
///
|
||
/// History: Separated to cleanly implement [Default].
|
||
///
|
||
/// ```
|
||
/// let samples = tek::Samples([None, None, None, None]);
|
||
/// ```
|
||
#[derive(Debug)] pub struct Samples<const N: usize>(
|
||
pub [Option<Arc<RwLock<Sample>>>;N]
|
||
);
|
||
|
||
/// A sound cut.
|
||
///
|
||
/// ```
|
||
/// let sample = tek::Sample::default();
|
||
/// let sample = tek::Sample::new("test", 0, 0, vec![]);
|
||
/// ```
|
||
#[derive(Default, Debug)] pub struct Sample {
|
||
pub name: Arc<str>,
|
||
pub start: usize,
|
||
pub end: usize,
|
||
pub channels: Vec<Vec<f32>>,
|
||
pub rate: Option<usize>,
|
||
pub gain: f32,
|
||
pub color: ItemTheme,
|
||
}
|
||
|
||
/// 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,
|
||
}
|
||
|
||
pub struct AddSampleModal {
|
||
pub exited: bool,
|
||
pub dir: PathBuf,
|
||
pub subdirs: Vec<OsString>,
|
||
pub files: Vec<OsString>,
|
||
pub cursor: usize,
|
||
pub offset: usize,
|
||
pub sample: Arc<RwLock<Sample>>,
|
||
pub voices: Arc<RwLock<Vec<Voice>>>,
|
||
pub _search: Option<String>,
|
||
}
|
||
|
||
/// A scene consists of a set of clips to play together.
|
||
///
|
||
/// ```
|
||
/// let scene: tek::Scene = Default::default();
|
||
/// let _ = scene.pulses();
|
||
/// let _ = scene.is_playing(&[]);
|
||
/// ```
|
||
#[derive(Debug, Default)] pub struct Scene {
|
||
/// Name of scene
|
||
pub name: Arc<str>,
|
||
/// Identifying color of scene
|
||
pub color: ItemTheme,
|
||
/// Clips in scene, one per track
|
||
pub clips: Vec<Option<Arc<RwLock<MidiClip>>>>,
|
||
}
|
||
|
||
/// Represents the current user selection in the arranger
|
||
#[derive(PartialEq, Clone, Copy, Debug, Default)] pub enum Selection {
|
||
#[default]
|
||
/// Nothing is selected
|
||
Nothing,
|
||
/// The whole mix is selected
|
||
Mix,
|
||
/// A MIDI input is selected.
|
||
Input(usize),
|
||
/// A MIDI output is selected.
|
||
Output(usize),
|
||
/// A scene is selected.
|
||
#[cfg(feature = "scene")] Scene(usize),
|
||
/// A track is selected.
|
||
#[cfg(feature = "track")] Track(usize),
|
||
/// A clip (track × scene) is selected.
|
||
#[cfg(feature = "track")] TrackClip { track: usize, scene: usize },
|
||
/// A track's MIDI input connection is selected.
|
||
#[cfg(feature = "track")] TrackInput { track: usize, port: usize },
|
||
/// A track's MIDI output connection is selected.
|
||
#[cfg(feature = "track")] TrackOutput { track: usize, port: usize },
|
||
/// A track device slot is selected.
|
||
#[cfg(feature = "track")] TrackDevice { track: usize, device: usize },
|
||
}
|
||
|
||
/// Contains state for playing a clip
|
||
///
|
||
/// ```
|
||
/// let clip = tek::MidiClip::default();
|
||
/// println!("Empty clip: {clip:?}");
|
||
///
|
||
/// let clip = tek::MidiClip::stop_all();
|
||
/// println!("Panic clip: {clip:?}");
|
||
///
|
||
/// let mut clip = tek::MidiClip::new("clip", true, 1, None, None);
|
||
/// clip.set_length(96);
|
||
/// clip.toggle_loop();
|
||
/// clip.record_event(12, midly::MidiMessage::NoteOn { key: 36.into(), vel: 100.into() });
|
||
/// assert!(clip.contains_note_on(36.into(), 6, 18));
|
||
/// assert_eq!(&clip.notes, &clip.duplicate().notes);
|
||
///
|
||
/// let clip = std::sync::Arc::new(clip);
|
||
/// assert_eq!(clip.clone(), clip);
|
||
///
|
||
/// let sequencer = tek::Sequencer::default();
|
||
/// println!("{sequencer:?}");
|
||
/// ```
|
||
pub struct Sequencer {
|
||
/// State of clock and playhead
|
||
#[cfg(feature = "clock")] pub clock: Clock,
|
||
/// Start time and clip being played
|
||
#[cfg(feature = "clip")] pub play_clip: Option<(Moment, Option<Arc<RwLock<MidiClip>>>)>,
|
||
/// Start time and next clip
|
||
#[cfg(feature = "clip")] pub next_clip: Option<(Moment, Option<Arc<RwLock<MidiClip>>>)>,
|
||
/// Record from MIDI ports to current sequence.
|
||
#[cfg(feature = "port")] pub midi_ins: Vec<MidiInput>,
|
||
/// Play from current sequence to MIDI ports
|
||
#[cfg(feature = "port")] pub midi_outs: Vec<MidiOutput>,
|
||
/// Play input through output.
|
||
pub monitoring: bool,
|
||
/// Write input to sequence.
|
||
pub recording: bool,
|
||
/// Overdub input to sequence.
|
||
pub overdub: bool,
|
||
/// Send all notes off
|
||
pub reset: bool, // TODO?: after Some(nframes)
|
||
/// Notes currently held at input
|
||
pub notes_in: Arc<RwLock<[bool; 128]>>,
|
||
/// Notes currently held at output
|
||
pub notes_out: Arc<RwLock<[bool; 128]>>,
|
||
/// MIDI output buffer
|
||
pub note_buf: Vec<u8>,
|
||
/// MIDI output buffer
|
||
pub midi_buf: Vec<Vec<Vec<u8>>>,
|
||
}
|
||
|
||
/// A track consists of a sequencer and zero or more devices chained after it.
|
||
///
|
||
/// ```
|
||
/// let track: tek::Track = Default::default();
|
||
/// ```
|
||
#[derive(Debug, Default)] pub struct Track {
|
||
/// Name of track
|
||
pub name: Arc<str>,
|
||
/// Identifying color of track
|
||
pub color: ItemTheme,
|
||
/// Preferred width of track column
|
||
pub width: usize,
|
||
/// MIDI sequencer state
|
||
pub sequencer: Sequencer,
|
||
/// Device chain
|
||
pub devices: Vec<Device>,
|
||
}
|
||
|
||
// Commands supported by [Browse]
|
||
//#[derive(Debug, Clone, PartialEq)]
|
||
//pub enum BrowseCommand {
|
||
//Begin,
|
||
//Cancel,
|
||
//Confirm,
|
||
//Select(usize),
|
||
//Chdir(PathBuf),
|
||
//Filter(Arc<str>),
|
||
//}
|
||
|
||
/// Modes for clip pool
|
||
#[derive(Debug, Clone)] pub enum PoolMode {
|
||
/// Renaming a pattern
|
||
Rename(usize, Arc<str>),
|
||
/// Editing the length of a pattern
|
||
Length(usize, usize, ClipLengthFocus),
|
||
/// Load clip from disk
|
||
Import(usize, Browse),
|
||
/// Save clip to disk
|
||
Export(usize, Browse),
|
||
}
|
||
|
||
/// Focused field of `ClipLength`
|
||
#[derive(Copy, Clone, Debug)] pub enum ClipLengthFocus {
|
||
/// Editing the number of bars
|
||
Bar,
|
||
/// Editing the number of beats
|
||
Beat,
|
||
/// Editing the number of ticks
|
||
Tick,
|
||
}
|
||
|
||
#[derive(Clone, Debug, PartialEq)] pub enum ConnectName {
|
||
/** Exact match */
|
||
Exact(Arc<str>),
|
||
/** Match regular expression */
|
||
RegExp(Arc<str>),
|
||
}
|
||
|
||
#[derive(Clone, Copy, Debug, PartialEq)] pub enum ConnectScope {
|
||
One,
|
||
All
|
||
}
|
||
|
||
#[derive(Clone, Copy, Debug, PartialEq)] pub enum ConnectStatus {
|
||
Missing,
|
||
Disconnected,
|
||
Connected,
|
||
Mismatch,
|
||
}
|
||
|
||
#[derive(Debug)] pub enum SamplerMode {
|
||
// Load sample from path
|
||
Import(usize, Browse),
|
||
}
|