mirror of
https://codeberg.org/unspeaker/tek.git
synced 2026-03-13 11:50:44 +01:00
use impl_default, remove some feature annotations
Some checks failed
/ build (push) Has been cancelled
Some checks failed
/ build (push) Has been cancelled
This commit is contained in:
parent
ab90129f4c
commit
4f5c332f48
3 changed files with 373 additions and 426 deletions
|
|
@ -1,4 +1,50 @@
|
|||
|
||||
//pub fn track_counter (cache: &Arc<RwLock<Self>>, track: usize, tracks: usize)
|
||||
//-> Arc<RwLock<String>>
|
||||
//{
|
||||
//let data = (track, tracks);
|
||||
//cache.write().unwrap().trks.update(Some(data), rewrite!(buf, "{}/{}", data.0, data.1));
|
||||
//cache.read().unwrap().trks.view.clone()
|
||||
//}
|
||||
|
||||
//pub fn scene_add (cache: &Arc<RwLock<Self>>, scene: usize, scenes: usize, is_editing: bool)
|
||||
//-> impl Content<TuiOut>
|
||||
//{
|
||||
//let data = (scene, scenes);
|
||||
//cache.write().unwrap().scns.update(Some(data), rewrite!(buf, "({}/{})", data.0, data.1));
|
||||
//button_3("S", "add scene", cache.read().unwrap().scns.view.clone(), is_editing)
|
||||
//}
|
||||
|
||||
|
||||
|
||||
//pub fn view_h2 (&self) -> impl Content<TuiOut> {
|
||||
//let cache = self.project.clock.view_cache.clone();
|
||||
//let cache = cache.read().unwrap();
|
||||
//add(&Fixed::x(15, Align::w(Bsp::s(
|
||||
//FieldH(theme, "Beat", cache.beat.view.clone()),
|
||||
//FieldH(theme, "Time", cache.time.view.clone()),
|
||||
//))));
|
||||
//add(&Fixed::x(13, Align::w(Bsp::s(
|
||||
//Fill::X(Align::w(FieldH(theme, "BPM", cache.bpm.view.clone()))),
|
||||
//Fill::X(Align::w(FieldH(theme, "SR ", cache.sr.view.clone()))),
|
||||
//))));
|
||||
//add(&Fixed::x(12, Align::w(Bsp::s(
|
||||
//Fill::X(Align::w(FieldH(theme, "Buf", cache.buf.view.clone()))),
|
||||
//Fill::X(Align::w(FieldH(theme, "Lat", cache.lat.view.clone()))),
|
||||
//))));
|
||||
//add(&Bsp::s(
|
||||
//Fill::X(Align::w(FieldH(theme, "Selected", Align::w(self.selection().describe(
|
||||
//self.tracks(),
|
||||
//self.scenes()
|
||||
//))))),
|
||||
//Fill::X(Align::w(FieldH(theme, format!("History ({})", self.history.len()),
|
||||
//self.history.last().map(|last|Fill::X(Align::w(format!("{:?}", last.0)))))))
|
||||
//));
|
||||
////if let Some(last) = self.history.last() {
|
||||
////add(&FieldV(theme, format!("History ({})", self.history.len()),
|
||||
////Fill::X(Align::w(format!("{:?}", last.0)))));
|
||||
////}
|
||||
//}
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
//pub fn view_nil (_: &App) -> TuiCb {
|
||||
|
|
@ -1137,3 +1183,6 @@
|
|||
//cols
|
||||
//Thunk::new(|to: &mut TuiOut|{
|
||||
//})
|
||||
//take!(ClipCommand |state: Arrangement, iter|state.selected_clip().as_ref()
|
||||
//.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten()));
|
||||
|
||||
|
|
|
|||
748
app/tek_impls.rs
748
app/tek_impls.rs
|
|
@ -16,20 +16,26 @@ impl <T: TracksView+ScenesView+Send+Sync> Clip
|
|||
|
||||
mod app {
|
||||
use crate::*;
|
||||
impl_has!(Clock: |self: App|self.project.clock);
|
||||
impl_has!(Vec<MidiInput>: |self: App|self.project.midi_ins);
|
||||
impl_has!(Vec<MidiOutput>: |self: App|self.project.midi_outs);
|
||||
impl_as_ref_opt!(MidiEditor: |self: App|self.project.as_ref_opt());
|
||||
impl_as_mut_opt!(MidiEditor: |self: App|self.project.as_mut_opt());
|
||||
impl_has!(Dialog: |self: App|self.dialog);
|
||||
impl_has!(Jack<'static>: |self: App|self.jack);
|
||||
impl_has!(Measure<TuiOut>: |self: App|self.size);
|
||||
impl_has!(Pool: |self: App|self.pool);
|
||||
impl_has!(Selection: |self: App|self.project.selection);
|
||||
has_clips!( |self: App|self.pool.clips);
|
||||
impl_has!(Clock: |self: App|self.project.clock);
|
||||
impl_has!(Vec<MidiInput>: |self: App|self.project.midi_ins);
|
||||
impl_has!(Vec<MidiOutput>: |self: App|self.project.midi_outs);
|
||||
impl_has!(Dialog: |self: App|self.dialog);
|
||||
impl_has!(Jack<'static>: |self: App|self.jack);
|
||||
impl_has!(Measure<TuiOut>: |self: App|self.size);
|
||||
impl_has!(Pool: |self: App|self.pool);
|
||||
impl_has!(Selection: |self: App|self.project.selection);
|
||||
impl_as_ref!(Vec<Scene>: |self: App|self.project.as_ref());
|
||||
impl_as_mut!(Vec<Scene>: |self: App|self.project.as_mut());
|
||||
impl_as_ref_opt!(MidiEditor: |self: App|self.project.as_ref_opt());
|
||||
impl_as_mut_opt!(MidiEditor: |self: App|self.project.as_mut_opt());
|
||||
has_clips!( |self: App|self.pool.clips);
|
||||
impl_audio!(App: tek_jack_process, tek_jack_event);
|
||||
impl_as_ref!(Vec<Scene>: |self: App| self.project.as_ref());
|
||||
impl_as_mut!(Vec<Scene>: |self: App| self.project.as_mut());
|
||||
handle!(TuiIn: |self: App, input|{
|
||||
let commands = collect_commands(self, input)?;
|
||||
let history = execute_commands(self, commands)?;
|
||||
self.history.extend(history.into_iter());
|
||||
Ok(None)
|
||||
});
|
||||
|
||||
impl Draw<TuiOut> for App {
|
||||
fn draw (&self, to: &mut TuiOut) {
|
||||
|
|
@ -45,12 +51,6 @@ mod app {
|
|||
}
|
||||
}
|
||||
|
||||
handle!(TuiIn: |self: App, input|{
|
||||
let commands = collect_commands(self, input)?;
|
||||
let history = execute_commands(self, commands)?;
|
||||
self.history.extend(history.into_iter());
|
||||
Ok(None)
|
||||
});
|
||||
|
||||
impl<'a> Namespace<'a, AppCommand> for App {
|
||||
symbols!('a |app| -> AppCommand {
|
||||
|
|
@ -312,27 +312,18 @@ mod app {
|
|||
|
||||
mod arrange {
|
||||
use crate::*;
|
||||
|
||||
impl_has!(Jack<'static>: |self: Arrangement| self.jack);
|
||||
impl_has!(Measure<TuiOut>: |self: Arrangement| self.size);
|
||||
impl_has!(Vec<Track>: |self: Arrangement| self.tracks);
|
||||
impl_has!(Vec<Scene>: |self: Arrangement| self.scenes);
|
||||
#[cfg(feature = "port")] impl_has!(Vec<MidiInput>: |self: Arrangement| self.midi_ins);
|
||||
#[cfg(feature = "port")] impl_has!(Vec<MidiOutput>: |self: Arrangement| self.midi_outs);
|
||||
#[cfg(feature = "clock")] impl_has!(Clock: |self: Arrangement| self.clock);
|
||||
#[cfg(feature = "select")] impl_has!(Selection: |self: Arrangement| self.selection);
|
||||
#[cfg(feature = "select")] impl_as_ref_opt!(MidiEditor: |self: Arrangement| self.editor.as_ref());
|
||||
#[cfg(feature = "select")] impl_as_mut_opt!(MidiEditor: |self: Arrangement| self.editor.as_mut());
|
||||
#[cfg(feature = "select")] impl_as_ref_opt!(Track: |self: Arrangement| self.selected_track());
|
||||
#[cfg(feature = "select")] impl_as_mut_opt!(Track: |self: Arrangement| self.selected_track_mut());
|
||||
|
||||
#[cfg(feature = "select")] impl Arrangement {
|
||||
#[cfg(feature = "port")] fn selected_midi_in (&self) -> Option<MidiInput> { todo!() }
|
||||
#[cfg(feature = "port")] fn selected_midi_out (&self) -> Option<MidiOutput> { todo!() }
|
||||
fn selected_device (&self) -> Option<Device> { todo!() }
|
||||
fn unselect (&self) -> Selection { Selection::Nothing }
|
||||
}
|
||||
|
||||
impl_has!(Vec<MidiInput>: |self: Arrangement| self.midi_ins);
|
||||
impl_has!(Vec<MidiOutput>: |self: Arrangement| self.midi_outs);
|
||||
impl_has!(Clock: |self: Arrangement| self.clock);
|
||||
impl_has!(Selection: |self: Arrangement| self.selection);
|
||||
impl_as_ref_opt!(MidiEditor: |self: Arrangement| self.editor.as_ref());
|
||||
impl_as_mut_opt!(MidiEditor: |self: Arrangement| self.editor.as_mut());
|
||||
impl_as_ref_opt!(Track: |self: Arrangement| self.selected_track());
|
||||
impl_as_mut_opt!(Track: |self: Arrangement| self.selected_track_mut());
|
||||
impl Arrangement {
|
||||
/// Create a new arrangement.
|
||||
pub fn new (
|
||||
|
|
@ -551,8 +542,7 @@ mod arrange {
|
|||
self.selected_track_mut()?.sampler_mut(0)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "scene")] impl ScenesView for Arrangement {
|
||||
impl ScenesView for Arrangement {
|
||||
fn h_scenes (&self) -> u16 {
|
||||
(self.measure_height() as u16).saturating_sub(20)
|
||||
}
|
||||
|
|
@ -563,7 +553,6 @@ mod arrange {
|
|||
(self.measure_width() as u16).saturating_sub(2 * self.w_side()).max(40)
|
||||
}
|
||||
}
|
||||
|
||||
impl HasClipsSize for Arrangement {
|
||||
fn clips_size (&self) -> &Measure<TuiOut> { &self.size_inner }
|
||||
}
|
||||
|
|
@ -571,21 +560,7 @@ mod arrange {
|
|||
|
||||
mod browse {
|
||||
use crate::*;
|
||||
|
||||
impl PartialEq for BrowseTarget {
|
||||
fn eq (&self, other: &Self) -> bool {
|
||||
match self {
|
||||
Self::ImportSample(_) => false,
|
||||
Self::ExportSample(_) => false,
|
||||
Self::ImportClip(_) => false,
|
||||
Self::ExportClip(_) => false,
|
||||
#[allow(unused)] t => matches!(other, t)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Browse {
|
||||
|
||||
pub fn new (cwd: Option<PathBuf>) -> Usually<Self> {
|
||||
let cwd = if let Some(cwd) = cwd { cwd } else { std::env::current_dir()? };
|
||||
let mut dirs = vec![];
|
||||
|
|
@ -603,19 +578,10 @@ mod browse {
|
|||
}
|
||||
Ok(Self { cwd, dirs, files, ..Default::default() })
|
||||
}
|
||||
|
||||
pub fn len (&self) -> usize {
|
||||
self.dirs.len() + self.files.len()
|
||||
}
|
||||
|
||||
pub fn is_dir (&self) -> bool {
|
||||
self.index < self.dirs.len()
|
||||
}
|
||||
|
||||
pub fn is_file (&self) -> bool {
|
||||
self.index >= self.dirs.len()
|
||||
}
|
||||
|
||||
pub fn chdir (&self) -> Usually<Self> { Self::new(Some(self.path())) }
|
||||
pub fn len (&self) -> usize { self.dirs.len() + self.files.len() }
|
||||
pub fn is_dir (&self) -> bool { self.index < self.dirs.len() }
|
||||
pub fn is_file (&self) -> bool { self.index >= self.dirs.len() }
|
||||
pub fn path (&self) -> PathBuf {
|
||||
self.cwd.join(if self.is_dir() {
|
||||
&self.dirs[self.index].0
|
||||
|
|
@ -625,22 +591,10 @@ mod browse {
|
|||
unreachable!()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn chdir (&self) -> Usually<Self> {
|
||||
Self::new(Some(self.path()))
|
||||
}
|
||||
|
||||
fn _todo_stub_path_buf (&self) -> PathBuf {
|
||||
todo!()
|
||||
}
|
||||
fn _todo_stub_usize (&self) -> usize {
|
||||
todo!()
|
||||
}
|
||||
fn _todo_stub_arc_str (&self) -> Arc<str> {
|
||||
todo!()
|
||||
}
|
||||
fn _todo_stub_path_buf (&self) -> PathBuf { todo!() }
|
||||
fn _todo_stub_usize (&self) -> usize { todo!() }
|
||||
fn _todo_stub_arc_str (&self) -> Arc<str> { todo!() }
|
||||
}
|
||||
|
||||
impl HasContent<TuiOut> for Browse {
|
||||
fn content (&self) -> impl Content<TuiOut> {
|
||||
Map::south(1, ||EntriesIterator {
|
||||
|
|
@ -651,7 +605,6 @@ mod browse {
|
|||
}, |entry, _index|Fill::X(Align::w(entry)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for EntriesIterator<'a> {
|
||||
type Item = Modify<&'a str>;
|
||||
fn next (&mut self) -> Option<Self::Item> {
|
||||
|
|
@ -669,285 +622,261 @@ mod browse {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//take!(ClipCommand |state: Arrangement, iter|state.selected_clip().as_ref()
|
||||
//.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten()));
|
||||
|
||||
|
||||
impl std::fmt::Debug for Clock {
|
||||
fn fmt (&self, f: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
||||
f.debug_struct("Clock")
|
||||
.field("timebase", &self.timebase)
|
||||
.field("chunk", &self.chunk)
|
||||
.field("quant", &self.quant)
|
||||
.field("sync", &self.sync)
|
||||
.field("global", &self.global)
|
||||
.field("playhead", &self.playhead)
|
||||
.field("started", &self.started)
|
||||
.finish()
|
||||
impl PartialEq for BrowseTarget {
|
||||
fn eq (&self, other: &Self) -> bool {
|
||||
match self {
|
||||
Self::ImportSample(_) => false,
|
||||
Self::ExportSample(_) => false,
|
||||
Self::ImportClip(_) => false,
|
||||
Self::ExportClip(_) => false,
|
||||
#[allow(unused)] t => matches!(other, t)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clock {
|
||||
pub fn new (jack: &Jack<'static>, bpm: Option<f64>) -> Usually<Self> {
|
||||
let (chunk, transport) = jack.with_client(|c|(c.buffer_size(), c.transport()));
|
||||
let timebase = Arc::new(Timebase::default());
|
||||
let clock = Self {
|
||||
quant: Arc::new(24.into()),
|
||||
sync: Arc::new(384.into()),
|
||||
transport: Arc::new(Some(transport)),
|
||||
chunk: Arc::new((chunk as usize).into()),
|
||||
global: Arc::new(Moment::zero(&timebase)),
|
||||
playhead: Arc::new(Moment::zero(&timebase)),
|
||||
offset: Arc::new(Moment::zero(&timebase)),
|
||||
started: RwLock::new(None).into(),
|
||||
timebase,
|
||||
midi_in: Arc::new(RwLock::new(Some(MidiInput::new(jack, &"M/clock", &[])?))),
|
||||
midi_out: Arc::new(RwLock::new(Some(MidiOutput::new(jack, &"clock/M", &[])?))),
|
||||
click_out: Arc::new(RwLock::new(Some(AudioOutput::new(jack, &"click", &[])?))),
|
||||
..Default::default()
|
||||
};
|
||||
if let Some(bpm) = bpm {
|
||||
clock.timebase.bpm.set(bpm);
|
||||
}
|
||||
Ok(clock)
|
||||
}
|
||||
pub fn timebase (&self) -> &Arc<Timebase> {
|
||||
&self.timebase
|
||||
}
|
||||
/// Current sample rate
|
||||
pub fn sr (&self) -> &SampleRate {
|
||||
&self.timebase.sr
|
||||
}
|
||||
/// Current tempo
|
||||
pub fn bpm (&self) -> &Bpm {
|
||||
&self.timebase.bpm
|
||||
}
|
||||
/// Current MIDI resolution
|
||||
pub fn ppq (&self) -> &Ppq {
|
||||
&self.timebase.ppq
|
||||
}
|
||||
/// Next pulse that matches launch sync (for phrase switchover)
|
||||
pub fn next_launch_pulse (&self) -> usize {
|
||||
let sync = self.sync.get() as usize;
|
||||
let pulse = self.playhead.pulse.get() as usize;
|
||||
if pulse % sync == 0 {
|
||||
pulse
|
||||
} else {
|
||||
(pulse / sync + 1) * sync
|
||||
mod clock {
|
||||
use crate::*;
|
||||
impl std::fmt::Debug for Clock {
|
||||
fn fmt (&self, f: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
||||
f.debug_struct("Clock")
|
||||
.field("timebase", &self.timebase)
|
||||
.field("chunk", &self.chunk)
|
||||
.field("quant", &self.quant)
|
||||
.field("sync", &self.sync)
|
||||
.field("global", &self.global)
|
||||
.field("playhead", &self.playhead)
|
||||
.field("started", &self.started)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
/// Start playing, optionally seeking to a given location beforehand
|
||||
pub fn play_from (&self, start: Option<u32>) -> Usually<()> {
|
||||
if let Some(transport) = self.transport.as_ref() {
|
||||
if let Some(start) = start {
|
||||
transport.locate(start)?;
|
||||
}
|
||||
transport.start()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
/// Pause, optionally seeking to a given location afterwards
|
||||
pub fn pause_at (&self, pause: Option<u32>) -> Usually<()> {
|
||||
if let Some(transport) = self.transport.as_ref() {
|
||||
transport.stop()?;
|
||||
if let Some(pause) = pause {
|
||||
transport.locate(pause)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
/// Is currently paused?
|
||||
pub fn is_stopped (&self) -> bool {
|
||||
self.started.read().unwrap().is_none()
|
||||
}
|
||||
/// Is currently playing?
|
||||
pub fn is_rolling (&self) -> bool {
|
||||
self.started.read().unwrap().is_some()
|
||||
}
|
||||
/// Update chunk size
|
||||
pub fn set_chunk (&self, n_frames: usize) {
|
||||
self.chunk.store(n_frames, Relaxed);
|
||||
}
|
||||
pub fn update_from_scope (&self, scope: &ProcessScope) -> Usually<()> {
|
||||
// Store buffer length
|
||||
self.set_chunk(scope.n_frames() as usize);
|
||||
|
||||
// Store reported global frame and usec
|
||||
let CycleTimes { current_frames, current_usecs, .. } = scope.cycle_times()?;
|
||||
self.global.sample.set(current_frames as f64);
|
||||
self.global.usec.set(current_usecs as f64);
|
||||
|
||||
let mut started = self.started.write().unwrap();
|
||||
|
||||
// If transport has just started or just stopped,
|
||||
// update starting point:
|
||||
if let Some(transport) = self.transport.as_ref() {
|
||||
match (transport.query_state()?, started.as_ref()) {
|
||||
(TransportState::Rolling, None) => {
|
||||
let moment = Moment::zero(&self.timebase);
|
||||
moment.sample.set(current_frames as f64);
|
||||
moment.usec.set(current_usecs as f64);
|
||||
*started = Some(moment);
|
||||
},
|
||||
(TransportState::Stopped, Some(_)) => {
|
||||
*started = None;
|
||||
},
|
||||
_ => {}
|
||||
impl Clock {
|
||||
pub fn new (jack: &Jack<'static>, bpm: Option<f64>) -> Usually<Self> {
|
||||
let (chunk, transport) = jack.with_client(|c|(c.buffer_size(), c.transport()));
|
||||
let timebase = Arc::new(Timebase::default());
|
||||
let clock = Self {
|
||||
quant: Arc::new(24.into()),
|
||||
sync: Arc::new(384.into()),
|
||||
transport: Arc::new(Some(transport)),
|
||||
chunk: Arc::new((chunk as usize).into()),
|
||||
global: Arc::new(Moment::zero(&timebase)),
|
||||
playhead: Arc::new(Moment::zero(&timebase)),
|
||||
offset: Arc::new(Moment::zero(&timebase)),
|
||||
started: RwLock::new(None).into(),
|
||||
timebase,
|
||||
midi_in: Arc::new(RwLock::new(Some(MidiInput::new(jack, &"M/clock", &[])?))),
|
||||
midi_out: Arc::new(RwLock::new(Some(MidiOutput::new(jack, &"clock/M", &[])?))),
|
||||
click_out: Arc::new(RwLock::new(Some(AudioOutput::new(jack, &"click", &[])?))),
|
||||
..Default::default()
|
||||
};
|
||||
}
|
||||
|
||||
self.playhead.update_from_sample(started.as_ref()
|
||||
.map(|started|current_frames as f64 - started.sample.get())
|
||||
.unwrap_or(0.));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn bbt (&self) -> PositionBBT {
|
||||
let pulse = self.playhead.pulse.get() as i32;
|
||||
let ppq = self.timebase.ppq.get() as i32;
|
||||
let bpm = self.timebase.bpm.get();
|
||||
let bar = (pulse / ppq) / 4;
|
||||
PositionBBT {
|
||||
bar: 1 + bar,
|
||||
beat: 1 + (pulse / ppq) % 4,
|
||||
tick: (pulse % ppq),
|
||||
bar_start_tick: (bar * 4 * ppq) as f64,
|
||||
beat_type: 4.,
|
||||
beats_per_bar: 4.,
|
||||
beats_per_minute: bpm,
|
||||
ticks_per_beat: ppq as f64
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next_launch_instant (&self) -> Moment {
|
||||
Moment::from_pulse(self.timebase(), self.next_launch_pulse() as f64)
|
||||
}
|
||||
|
||||
/// Get index of first sample to populate.
|
||||
///
|
||||
/// Greater than 0 means that the first pulse of the clip
|
||||
/// falls somewhere in the middle of the chunk.
|
||||
pub fn get_sample_offset (&self, scope: &ProcessScope, started: &Moment) -> usize{
|
||||
(scope.last_frame_time() as usize).saturating_sub(
|
||||
started.sample.get() as usize +
|
||||
self.started.read().unwrap().as_ref().unwrap().sample.get() as usize
|
||||
)
|
||||
}
|
||||
|
||||
// Get iterator that emits sample paired with pulse.
|
||||
//
|
||||
// * Sample: index into output buffer at which to write MIDI event
|
||||
// * Pulse: index into clip from which to take the MIDI event
|
||||
//
|
||||
// Emitted for each sample of the output buffer that corresponds to a MIDI pulse.
|
||||
pub fn get_pulses (&self, scope: &ProcessScope, offset: usize) -> Ticker {
|
||||
self.timebase().pulses_between_samples(offset, offset + scope.n_frames() as usize)
|
||||
}
|
||||
}
|
||||
|
||||
impl Clock {
|
||||
fn _todo_provide_u32 (&self) -> u32 {
|
||||
todo!()
|
||||
}
|
||||
fn _todo_provide_opt_u32 (&self) -> Option<u32> {
|
||||
todo!()
|
||||
}
|
||||
fn _todo_provide_f64 (&self) -> f64 {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: HasClock> Command<T> for ClockCommand {
|
||||
fn execute (&self, state: &mut T) -> Perhaps<Self> {
|
||||
self.execute(state.clock_mut()) // awesome
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl ClockView {
|
||||
pub const BEAT_EMPTY: &'static str = "-.-.--";
|
||||
pub const TIME_EMPTY: &'static str = "-.---s";
|
||||
pub const BPM_EMPTY: &'static str = "---.---";
|
||||
|
||||
//pub fn track_counter (cache: &Arc<RwLock<Self>>, track: usize, tracks: usize)
|
||||
//-> Arc<RwLock<String>>
|
||||
//{
|
||||
//let data = (track, tracks);
|
||||
//cache.write().unwrap().trks.update(Some(data), rewrite!(buf, "{}/{}", data.0, data.1));
|
||||
//cache.read().unwrap().trks.view.clone()
|
||||
//}
|
||||
|
||||
//pub fn scene_add (cache: &Arc<RwLock<Self>>, scene: usize, scenes: usize, is_editing: bool)
|
||||
//-> impl Content<TuiOut>
|
||||
//{
|
||||
//let data = (scene, scenes);
|
||||
//cache.write().unwrap().scns.update(Some(data), rewrite!(buf, "({}/{})", data.0, data.1));
|
||||
//button_3("S", "add scene", cache.read().unwrap().scns.view.clone(), is_editing)
|
||||
//}
|
||||
|
||||
pub fn update_clock (cache: &Arc<RwLock<Self>>, clock: &Clock, compact: bool) {
|
||||
let rate = clock.timebase.sr.get();
|
||||
let chunk = clock.chunk.load(Relaxed) as f64;
|
||||
let lat = chunk / rate * 1000.;
|
||||
let delta = |start: &Moment|clock.global.usec.get() - start.usec.get();
|
||||
let mut cache = cache.write().unwrap();
|
||||
cache.buf.update(Some(chunk), rewrite!(buf, "{chunk}"));
|
||||
cache.lat.update(Some(lat), rewrite!(buf, "{lat:.1}ms"));
|
||||
cache.sr.update(Some((compact, rate)), |buf,_,_|{
|
||||
buf.clear();
|
||||
if compact {
|
||||
write!(buf, "{:.1}kHz", rate / 1000.)
|
||||
} else {
|
||||
write!(buf, "{:.0}Hz", rate)
|
||||
if let Some(bpm) = bpm {
|
||||
clock.timebase.bpm.set(bpm);
|
||||
}
|
||||
});
|
||||
if let Some(now) = clock.started.read().unwrap().as_ref().map(delta) {
|
||||
let pulse = clock.timebase.usecs_to_pulse(now);
|
||||
let time = now/1000000.;
|
||||
let bpm = clock.timebase.bpm.get();
|
||||
cache.beat.update(Some(pulse), |buf, _, _|{
|
||||
buf.clear();
|
||||
clock.timebase.format_beats_1_to(buf, pulse)
|
||||
});
|
||||
cache.time.update(Some(time), rewrite!(buf, "{:.3}s", time));
|
||||
cache.bpm.update(Some(bpm), rewrite!(buf, "{:.3}", bpm));
|
||||
} else {
|
||||
cache.beat.update(None, rewrite!(buf, "{}", ClockView::BEAT_EMPTY));
|
||||
cache.time.update(None, rewrite!(buf, "{}", ClockView::TIME_EMPTY));
|
||||
cache.bpm.update(None, rewrite!(buf, "{}", ClockView::BPM_EMPTY));
|
||||
Ok(clock)
|
||||
}
|
||||
pub fn timebase (&self) -> &Arc<Timebase> {
|
||||
&self.timebase
|
||||
}
|
||||
/// Current sample rate
|
||||
pub fn sr (&self) -> &SampleRate {
|
||||
&self.timebase.sr
|
||||
}
|
||||
/// Current tempo
|
||||
pub fn bpm (&self) -> &Bpm {
|
||||
&self.timebase.bpm
|
||||
}
|
||||
/// Current MIDI resolution
|
||||
pub fn ppq (&self) -> &Ppq {
|
||||
&self.timebase.ppq
|
||||
}
|
||||
/// Next pulse that matches launch sync (for phrase switchover)
|
||||
pub fn next_launch_pulse (&self) -> usize {
|
||||
let sync = self.sync.get() as usize;
|
||||
let pulse = self.playhead.pulse.get() as usize;
|
||||
if pulse % sync == 0 {
|
||||
pulse
|
||||
} else {
|
||||
(pulse / sync + 1) * sync
|
||||
}
|
||||
}
|
||||
/// Start playing, optionally seeking to a given location beforehand
|
||||
pub fn play_from (&self, start: Option<u32>) -> Usually<()> {
|
||||
if let Some(transport) = self.transport.as_ref() {
|
||||
if let Some(start) = start {
|
||||
transport.locate(start)?;
|
||||
}
|
||||
transport.start()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
/// Pause, optionally seeking to a given location afterwards
|
||||
pub fn pause_at (&self, pause: Option<u32>) -> Usually<()> {
|
||||
if let Some(transport) = self.transport.as_ref() {
|
||||
transport.stop()?;
|
||||
if let Some(pause) = pause {
|
||||
transport.locate(pause)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
/// Is currently paused?
|
||||
pub fn is_stopped (&self) -> bool {
|
||||
self.started.read().unwrap().is_none()
|
||||
}
|
||||
/// Is currently playing?
|
||||
pub fn is_rolling (&self) -> bool {
|
||||
self.started.read().unwrap().is_some()
|
||||
}
|
||||
/// Update chunk size
|
||||
pub fn set_chunk (&self, n_frames: usize) {
|
||||
self.chunk.store(n_frames, Relaxed);
|
||||
}
|
||||
pub fn update_from_scope (&self, scope: &ProcessScope) -> Usually<()> {
|
||||
// Store buffer length
|
||||
self.set_chunk(scope.n_frames() as usize);
|
||||
|
||||
// Store reported global frame and usec
|
||||
let CycleTimes { current_frames, current_usecs, .. } = scope.cycle_times()?;
|
||||
self.global.sample.set(current_frames as f64);
|
||||
self.global.usec.set(current_usecs as f64);
|
||||
|
||||
let mut started = self.started.write().unwrap();
|
||||
|
||||
// If transport has just started or just stopped,
|
||||
// update starting point:
|
||||
if let Some(transport) = self.transport.as_ref() {
|
||||
match (transport.query_state()?, started.as_ref()) {
|
||||
(TransportState::Rolling, None) => {
|
||||
let moment = Moment::zero(&self.timebase);
|
||||
moment.sample.set(current_frames as f64);
|
||||
moment.usec.set(current_usecs as f64);
|
||||
*started = Some(moment);
|
||||
},
|
||||
(TransportState::Stopped, Some(_)) => {
|
||||
*started = None;
|
||||
},
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
|
||||
self.playhead.update_from_sample(started.as_ref()
|
||||
.map(|started|current_frames as f64 - started.sample.get())
|
||||
.unwrap_or(0.));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn bbt (&self) -> PositionBBT {
|
||||
let pulse = self.playhead.pulse.get() as i32;
|
||||
let ppq = self.timebase.ppq.get() as i32;
|
||||
let bpm = self.timebase.bpm.get();
|
||||
let bar = (pulse / ppq) / 4;
|
||||
PositionBBT {
|
||||
bar: 1 + bar,
|
||||
beat: 1 + (pulse / ppq) % 4,
|
||||
tick: (pulse % ppq),
|
||||
bar_start_tick: (bar * 4 * ppq) as f64,
|
||||
beat_type: 4.,
|
||||
beats_per_bar: 4.,
|
||||
beats_per_minute: bpm,
|
||||
ticks_per_beat: ppq as f64
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next_launch_instant (&self) -> Moment {
|
||||
Moment::from_pulse(self.timebase(), self.next_launch_pulse() as f64)
|
||||
}
|
||||
|
||||
/// Get index of first sample to populate.
|
||||
///
|
||||
/// Greater than 0 means that the first pulse of the clip
|
||||
/// falls somewhere in the middle of the chunk.
|
||||
pub fn get_sample_offset (&self, scope: &ProcessScope, started: &Moment) -> usize{
|
||||
(scope.last_frame_time() as usize).saturating_sub(
|
||||
started.sample.get() as usize +
|
||||
self.started.read().unwrap().as_ref().unwrap().sample.get() as usize
|
||||
)
|
||||
}
|
||||
|
||||
// Get iterator that emits sample paired with pulse.
|
||||
//
|
||||
// * Sample: index into output buffer at which to write MIDI event
|
||||
// * Pulse: index into clip from which to take the MIDI event
|
||||
//
|
||||
// Emitted for each sample of the output buffer that corresponds to a MIDI pulse.
|
||||
pub fn get_pulses (&self, scope: &ProcessScope, offset: usize) -> Ticker {
|
||||
self.timebase().pulses_between_samples(offset, offset + scope.n_frames() as usize)
|
||||
}
|
||||
}
|
||||
|
||||
//pub fn view_h2 (&self) -> impl Content<TuiOut> {
|
||||
//let cache = self.project.clock.view_cache.clone();
|
||||
//let cache = cache.read().unwrap();
|
||||
//add(&Fixed::x(15, Align::w(Bsp::s(
|
||||
//FieldH(theme, "Beat", cache.beat.view.clone()),
|
||||
//FieldH(theme, "Time", cache.time.view.clone()),
|
||||
//))));
|
||||
//add(&Fixed::x(13, Align::w(Bsp::s(
|
||||
//Fill::X(Align::w(FieldH(theme, "BPM", cache.bpm.view.clone()))),
|
||||
//Fill::X(Align::w(FieldH(theme, "SR ", cache.sr.view.clone()))),
|
||||
//))));
|
||||
//add(&Fixed::x(12, Align::w(Bsp::s(
|
||||
//Fill::X(Align::w(FieldH(theme, "Buf", cache.buf.view.clone()))),
|
||||
//Fill::X(Align::w(FieldH(theme, "Lat", cache.lat.view.clone()))),
|
||||
//))));
|
||||
//add(&Bsp::s(
|
||||
//Fill::X(Align::w(FieldH(theme, "Selected", Align::w(self.selection().describe(
|
||||
//self.tracks(),
|
||||
//self.scenes()
|
||||
//))))),
|
||||
//Fill::X(Align::w(FieldH(theme, format!("History ({})", self.history.len()),
|
||||
//self.history.last().map(|last|Fill::X(Align::w(format!("{:?}", last.0)))))))
|
||||
//));
|
||||
////if let Some(last) = self.history.last() {
|
||||
////add(&FieldV(theme, format!("History ({})", self.history.len()),
|
||||
////Fill::X(Align::w(format!("{:?}", last.0)))));
|
||||
////}
|
||||
//}
|
||||
impl Clock {
|
||||
fn _todo_provide_u32 (&self) -> u32 {
|
||||
todo!()
|
||||
}
|
||||
fn _todo_provide_opt_u32 (&self) -> Option<u32> {
|
||||
todo!()
|
||||
}
|
||||
fn _todo_provide_f64 (&self) -> f64 {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
impl<T: HasClock> Command<T> for ClockCommand {
|
||||
fn execute (&self, state: &mut T) -> Perhaps<Self> {
|
||||
self.execute(state.clock_mut()) // awesome
|
||||
}
|
||||
}
|
||||
impl ClockView {
|
||||
pub const BEAT_EMPTY: &'static str = "-.-.--";
|
||||
pub const TIME_EMPTY: &'static str = "-.---s";
|
||||
pub const BPM_EMPTY: &'static str = "---.---";
|
||||
pub fn update_clock (cache: &Arc<RwLock<Self>>, clock: &Clock, compact: bool) {
|
||||
let rate = clock.timebase.sr.get();
|
||||
let chunk = clock.chunk.load(Relaxed) as f64;
|
||||
let lat = chunk / rate * 1000.;
|
||||
let delta = |start: &Moment|clock.global.usec.get() - start.usec.get();
|
||||
let mut cache = cache.write().unwrap();
|
||||
cache.buf.update(Some(chunk), rewrite!(buf, "{chunk}"));
|
||||
cache.lat.update(Some(lat), rewrite!(buf, "{lat:.1}ms"));
|
||||
cache.sr.update(Some((compact, rate)), |buf,_,_|{
|
||||
buf.clear();
|
||||
if compact {
|
||||
write!(buf, "{:.1}kHz", rate / 1000.)
|
||||
} else {
|
||||
write!(buf, "{:.0}Hz", rate)
|
||||
}
|
||||
});
|
||||
if let Some(now) = clock.started.read().unwrap().as_ref().map(delta) {
|
||||
let pulse = clock.timebase.usecs_to_pulse(now);
|
||||
let time = now/1000000.;
|
||||
let bpm = clock.timebase.bpm.get();
|
||||
cache.beat.update(Some(pulse), |buf, _, _|{
|
||||
buf.clear();
|
||||
clock.timebase.format_beats_1_to(buf, pulse)
|
||||
});
|
||||
cache.time.update(Some(time), rewrite!(buf, "{:.3}s", time));
|
||||
cache.bpm.update(Some(bpm), rewrite!(buf, "{:.3}", bpm));
|
||||
} else {
|
||||
cache.beat.update(None, rewrite!(buf, "{}", ClockView::BEAT_EMPTY));
|
||||
cache.time.update(None, rewrite!(buf, "{}", ClockView::TIME_EMPTY));
|
||||
cache.bpm.update(None, rewrite!(buf, "{}", ClockView::BPM_EMPTY));
|
||||
}
|
||||
}
|
||||
}
|
||||
impl_default!(ClockView: {
|
||||
let mut beat = String::with_capacity(16);
|
||||
let _ = write!(beat, "{}", Self::BEAT_EMPTY);
|
||||
let mut time = String::with_capacity(16);
|
||||
let _ = write!(time, "{}", Self::TIME_EMPTY);
|
||||
let mut bpm = String::with_capacity(16);
|
||||
let _ = write!(bpm, "{}", Self::BPM_EMPTY);
|
||||
Self {
|
||||
beat: Memo::new(None, beat),
|
||||
time: Memo::new(None, time),
|
||||
bpm: Memo::new(None, bpm),
|
||||
sr: Memo::new(None, String::with_capacity(16)),
|
||||
buf: Memo::new(None, String::with_capacity(16)),
|
||||
lat: Memo::new(None, String::with_capacity(16)),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
impl Connect {
|
||||
|
|
@ -1114,55 +1043,10 @@ impl<C: Default> Default for Binding<C> {
|
|||
}
|
||||
}
|
||||
}
|
||||
impl Default for AppCommand { fn default () -> Self { Self::Nop } }
|
||||
impl Default for MenuItem { fn default () -> Self { Self("".into(), Arc::new(Box::new(|_|Ok(())))) } }
|
||||
impl Default for Timebase { fn default () -> Self { Self::new(48000f64, 150f64, DEFAULT_PPQ) } }
|
||||
impl Default for MidiEditor { fn default () -> Self { Self { size: Measure::new(0, 0), mode: PianoHorizontal::new(None) } } }
|
||||
impl Default for OctaveVertical {
|
||||
fn default () -> Self {
|
||||
Self { on: [false; 12], colors: [Rgb(255,255,255), Rgb(0,0,0), Rgb(255,0,0)] }
|
||||
}
|
||||
}
|
||||
impl Default for MidiCursor {
|
||||
fn default () -> Self {
|
||||
Self {
|
||||
time_pos: Arc::new(0.into()),
|
||||
note_pos: Arc::new(36.into()),
|
||||
note_len: Arc::new(24.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Default for ClockView {
|
||||
fn default () -> Self {
|
||||
let mut beat = String::with_capacity(16);
|
||||
let _ = write!(beat, "{}", Self::BEAT_EMPTY);
|
||||
let mut time = String::with_capacity(16);
|
||||
let _ = write!(time, "{}", Self::TIME_EMPTY);
|
||||
let mut bpm = String::with_capacity(16);
|
||||
let _ = write!(bpm, "{}", Self::BPM_EMPTY);
|
||||
Self {
|
||||
beat: Memo::new(None, beat),
|
||||
time: Memo::new(None, time),
|
||||
bpm: Memo::new(None, bpm),
|
||||
sr: Memo::new(None, String::with_capacity(16)),
|
||||
buf: Memo::new(None, String::with_capacity(16)),
|
||||
lat: Memo::new(None, String::with_capacity(16)),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Default for Pool {
|
||||
fn default () -> Self {
|
||||
//use PoolMode::*;
|
||||
Self {
|
||||
clip: 0.into(),
|
||||
mode: None,
|
||||
visible: true,
|
||||
#[cfg(feature = "clip")] clips: Arc::from(RwLock::from(vec![])),
|
||||
#[cfg(feature = "sampler")] samples: Arc::from(RwLock::from(vec![])),
|
||||
#[cfg(feature = "browse")] browse: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl_default!(AppCommand: Self::Nop);
|
||||
impl_default!(MenuItem: Self("".into(), Arc::new(Box::new(|_|Ok(())))));
|
||||
impl_default!(Timebase: Self::new(48000f64, 150f64, DEFAULT_PPQ));
|
||||
|
||||
impl Gettable<bool> for AtomicBool { fn get (&self) -> bool { self.load(Relaxed) } }
|
||||
impl InteriorMutable<bool> for AtomicBool { fn set (&self, value: bool) -> bool { self.swap(value, Relaxed) } }
|
||||
|
|
@ -1549,6 +1433,11 @@ mod midi {
|
|||
time_zoom: Arc::new(data.0.into()),
|
||||
time_lock: Arc::new(data.1.into()),
|
||||
});
|
||||
impl_default!(MidiCursor: Self {
|
||||
time_pos: Arc::new(0.into()),
|
||||
note_pos: Arc::new(36.into()),
|
||||
note_len: Arc::new(24.into()),
|
||||
});
|
||||
|
||||
impl NotePoint for MidiCursor {
|
||||
fn note_len (&self) -> &AtomicUsize {
|
||||
|
|
@ -2103,32 +1992,27 @@ mod audio {
|
|||
|
||||
#[cfg(feature = "sequencer")] mod sequencer {
|
||||
use crate::*;
|
||||
impl_has!(Sequencer: |self: Track| self.sequencer);
|
||||
#[cfg(feature = "clock")] impl_has!(Clock: |self: Sequencer|self.clock);
|
||||
#[cfg(feature = "port")] impl_has!(Vec<MidiInput>: |self: Sequencer|self.midi_ins);
|
||||
#[cfg(feature = "port")] impl_has!(Vec<MidiOutput>: |self: Sequencer|self.midi_outs);
|
||||
impl_has!(Sequencer: |self: Track| self.sequencer);
|
||||
impl_has!(Clock: |self: Sequencer| self.clock);
|
||||
impl_has!(Vec<MidiInput>: |self: Sequencer| self.midi_ins);
|
||||
impl_has!(Vec<MidiOutput>: |self: Sequencer| self.midi_outs);
|
||||
impl_has!(Measure<TuiOut>: |self: MidiEditor| self.size);
|
||||
impl_has!(Measure<TuiOut>: |self: PianoHorizontal| self.size);
|
||||
impl Default for Sequencer {
|
||||
fn default () -> Self {
|
||||
Self {
|
||||
#[cfg(feature = "clock")] clock: Clock::default(),
|
||||
#[cfg(feature = "clip")] play_clip: None,
|
||||
#[cfg(feature = "clip")] next_clip: None,
|
||||
#[cfg(feature = "port")] midi_ins: vec![],
|
||||
#[cfg(feature = "port")] midi_outs: vec![],
|
||||
|
||||
recording: false,
|
||||
monitoring: true,
|
||||
overdub: false,
|
||||
notes_in: RwLock::new([false;128]).into(),
|
||||
notes_out: RwLock::new([false;128]).into(),
|
||||
note_buf: vec![0;8],
|
||||
midi_buf: vec![],
|
||||
reset: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl_default!(Sequencer: Self {
|
||||
clock: Clock::default(),
|
||||
play_clip: None,
|
||||
next_clip: None,
|
||||
midi_ins: vec![],
|
||||
midi_outs: vec![],
|
||||
recording: false,
|
||||
monitoring: true,
|
||||
overdub: false,
|
||||
notes_in: RwLock::new([false;128]).into(),
|
||||
notes_out: RwLock::new([false;128]).into(),
|
||||
note_buf: vec![0;8],
|
||||
midi_buf: vec![],
|
||||
reset: true,
|
||||
});
|
||||
impl Sequencer {
|
||||
pub fn new (
|
||||
name: impl AsRef<str>,
|
||||
|
|
@ -2355,6 +2239,12 @@ mod audio {
|
|||
model.redraw();
|
||||
model
|
||||
});
|
||||
impl_default!(MidiEditor: Self {
|
||||
size: Measure::new(0, 0), mode: PianoHorizontal::new(None)
|
||||
});
|
||||
impl_default!(OctaveVertical: Self {
|
||||
on: [false; 12], colors: [Rgb(255,255,255), Rgb(0,0,0), Rgb(255,0,0)]
|
||||
});
|
||||
impl MidiEditor {
|
||||
/// Put note at current position
|
||||
pub fn put_note (&mut self, advance: bool) {
|
||||
|
|
@ -3494,6 +3384,14 @@ mod pool {
|
|||
model.clip.store(1, Relaxed);
|
||||
model
|
||||
});
|
||||
impl_default!(Pool: Self {
|
||||
browse: None,
|
||||
clip: 0.into(),
|
||||
clips: Arc::from(RwLock::from(vec![])),
|
||||
mode: None,
|
||||
samples: Arc::from(RwLock::from(vec![])),
|
||||
visible: true,
|
||||
});
|
||||
impl Pool {
|
||||
pub fn clip_index (&self) -> usize {
|
||||
self.clip.load(Relaxed)
|
||||
|
|
|
|||
2
tengri
2
tengri
|
|
@ -1 +1 @@
|
|||
Subproject commit d1c08df5351ce8c3913723602a05268d593c9a45
|
||||
Subproject commit f1dda6af07b94928481d062c3d3fda5b9e969633
|
||||
Loading…
Add table
Add a link
Reference in a new issue