Skip to content

Commit

Permalink
Cleanup: unify time types
Browse files Browse the repository at this point in the history
  • Loading branch information
PetrGlad committed Nov 7, 2023
1 parent a113034 commit f1d2ffb
Show file tree
Hide file tree
Showing 8 changed files with 121 additions and 162 deletions.
9 changes: 4 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,9 @@ I use Pianoteq, but that is a commercial product.

## TODO

- [ ] Persist bookmarks in project.
- [ ] Location history navigation (e.g. go to a bookmark that was visited recently), with Alt + LeftArrow / RightArrow
- [ ] When start playing send current CC values (will help damper to take effect immediately, not on next change).
- [ ] Time marks on stave ("minute:second" from the beginning).
- [ ] Consider TransportTime to be signed (see also StaveTime). There are too many conversions forth and back.
- [ ] When start playing send current CC values (will help damper to take effect immediately, not on next change).
- [ ] Find a way to separate actions from view logic with egui. It looks too messy now.
- [ ] Location history navigation (e.g. go to a bookmark that was visited recently), with Alt + LeftArrow / RightArrow
- [ ] Minimize use of unwrap. The biggest contention currently is event data shared between engine and stave.
- [ ] Multi-track UI (for snippets, flight recorder, and copy/paste buffer). Can show only one at a time, though. Use
tabs?
Expand All @@ -52,6 +49,8 @@ I use Pianoteq, but that is a commercial product.
- [ ] Copy/cut/paste.
- [ ] Adjust tempo for selection.
- [ ] Optimize undo history 2: save only minimal diff instead of the whole track.
- [x] Consider TransportTime to be signed (see also StaveTime). There are too many conversions forth and back.
- [x] Persist bookmarks in project.
- [x] Have a separate edit-position and play-start cursors (time bookmarks), so it is easier to jump back and listen to
the modified version.
- [x] Optimize undo history: avoid O(N) algos; batch fast similar commands (e.g. tail or note shifts) saving at most
Expand Down
21 changes: 11 additions & 10 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@ use std::fs;
use std::path::PathBuf;
use std::sync::mpsc;

use crate::common::Time;
use eframe::egui::{Modifiers, Vec2};
use eframe::{self, egui, CreationContext};
use egui_extras::{Size, StripBuilder};

use crate::engine::{Engine, EngineCommand, StatusEvent, TransportTime};
use crate::engine::{Engine, EngineCommand, StatusEvent};
use crate::project::Project;
use crate::stave::{Bookmarks, Stave, StaveTime};
use crate::stave::{Bookmarks, Stave};

enum Message {
UpdateTransportTime(TransportTime),
UpdateTime(Time),
}

pub struct EmApp {
Expand Down Expand Up @@ -50,8 +51,8 @@ impl EmApp {
// TODO (optimization?) Throttle updates (30..60 times per second should be enough).
// Should not miss one-off updates, maybe skip only in same-event-type runs.
match ev {
StatusEvent::TransportTime(t) => {
match message_sender.send(Message::UpdateTransportTime(t)) {
StatusEvent::Time(t) => {
match message_sender.send(Message::UpdateTime(t)) {
Ok(_) => engine_receiver_ctx.request_repaint(),
_ => (), // Will try next time.
}
Expand Down Expand Up @@ -88,9 +89,9 @@ impl EmApp {
self.stave.save_to(&PathBuf::from(path));
}

fn engine_seek(&self, to: StaveTime) {
fn engine_seek(&self, to: Time) {
self.engine_command_send
.send(Box::new(move |engine| engine.seek(to as TransportTime)))
.send(Box::new(move |engine| engine.seek(to)))
.unwrap();
}
}
Expand All @@ -100,11 +101,11 @@ impl eframe::App for EmApp {
self.stave.history.do_pending();
if let Some(message) = self.message_receiver.try_iter().last() {
match message {
Message::UpdateTransportTime(t) => {
self.stave.cursor_position = t as StaveTime;
Message::UpdateTime(t) => {
self.stave.cursor_position = t;
if self.follow_playback {
let at = self.stave.cursor_position;
self.stave.scroll_to(at as StaveTime, 0.1);
self.stave.scroll_to(at, 0.1);
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions src/common.rs
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
/// Track time in microseconds.
pub type Time = i64;

/// A data revision identifier.
pub type VersionId = i64;
22 changes: 10 additions & 12 deletions src/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::sync::{mpsc, Arc, Mutex};
use std::thread;
use std::time::{Duration, Instant};

use crate::common::Time;
use midly::live::LiveEvent;
use midly::MidiMessage;
use vst::event::Event;
Expand All @@ -12,19 +13,16 @@ use vst::plugin::Plugin;
use crate::midi_vst::Vst;
use crate::track::MIDI_CC_SUSTAIN_ID;

/// uSecs from the start.
pub type TransportTime = u64;

/** Event that is produced by engine. */
#[derive(Clone, Debug)]
pub enum StatusEvent {
TransportTime(TransportTime),
Time(Time),
}

/// A sound event to be rendered by the engine at given time.
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
pub struct EngineEvent {
pub at: TransportTime,
pub at: Time,
pub event: LiveEvent<'static>,
}

Expand All @@ -49,10 +47,10 @@ pub trait EventSource {
Use this to detach it from the engine. */
fn is_running(&self) -> bool;
/** Reset current source's time to this moment. */
fn seek(&mut self, at: &TransportTime);
fn seek(&mut self, at: &Time);
/** The next event to be played at the instant. On subsequent
calls instants must not decrease unless a reset call sets another time. */
fn next(&mut self, at: &TransportTime, queue: &mut BinaryHeap<EngineEvent>);
fn next(&mut self, at: &Time, queue: &mut BinaryHeap<EngineEvent>);
}

type EventSourceHandle = dyn EventSource + Send;
Expand All @@ -62,7 +60,7 @@ pub type EngineCommand = dyn FnOnce(&mut Engine) + Send;
pub struct Engine {
vst: Vst,
sources: Vec<Box<EventSourceHandle>>,
running_at: TransportTime,
running_at: Time,
reset_at: Instant,
paused: bool,
status_receiver: Option<Box<StatusEventReceiver>>,
Expand Down Expand Up @@ -152,13 +150,13 @@ impl Engine {
}

fn update_track_time(&mut self) {
self.running_at = Instant::now().duration_since(self.reset_at).as_micros() as u64;
self.running_at = Instant::now().duration_since(self.reset_at).as_micros() as Time;
self.status_receiver
.as_mut()
.map(|recv| recv(StatusEvent::TransportTime(self.running_at)));
.map(|recv| recv(StatusEvent::Time(self.running_at)));
}

pub fn seek(&mut self, at: TransportTime) {
pub fn seek(&mut self, at: Time) {
for s in self.sources.iter_mut() {
s.seek(&at);
}
Expand All @@ -183,7 +181,7 @@ impl Engine {
}

pub fn update_realtime(&mut self) {
self.reset_at = Instant::now() - Duration::from_micros(self.running_at);
self.reset_at = Instant::now() - Duration::from_micros(self.running_at as u64);
}

pub fn add(&mut self, source: Box<EventSourceHandle>) {
Expand Down
11 changes: 6 additions & 5 deletions src/midi.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
use std::collections::BinaryHeap;
use std::time::Duration;

use crate::common::Time;
use midly::io::WriteResult;
use midly::live::LiveEvent;
use midly::num::u15;
use midly::MidiMessage::Controller;
use midly::{Format, Header, MidiMessage, Smf, Timing, Track, TrackEvent};

use crate::engine::{EngineEvent, EventSource, TransportTime};
use crate::engine::{EngineEvent, EventSource};
use crate::track::{ChannelId, ControllerId, Level, Pitch};

pub struct SmfSource {
events: Vec<TrackEvent<'static>>,
tick: Duration,
current_idx: usize,
running_at: TransportTime,
running_at: Time,
}

pub fn load_smf(smf_data: &Vec<u8>) -> (Vec<TrackEvent<'static>>, u32) {
Expand Down Expand Up @@ -103,20 +104,20 @@ impl EventSource for SmfSource {
self.current_idx < self.events.len()
}

fn seek(&mut self, at: &TransportTime) {
fn seek(&mut self, at: &Time) {
assert!(
self.running_at > *at,
"SmfSource back reset is not supported."
);
self.running_at = at.to_owned();
}

fn next(&mut self, at: &TransportTime, queue: &mut BinaryHeap<EngineEvent>) {
fn next(&mut self, at: &Time, queue: &mut BinaryHeap<EngineEvent>) {
let track = &self.events;
while self.is_running() {
let event = track[self.current_idx];
let running_at =
self.running_at + self.tick.as_micros() as u64 * event.delta.as_int() as u64;
self.running_at + self.tick.as_micros() as Time * event.delta.as_int() as Time;
if running_at > *at {
return;
}
Expand Down
Loading

0 comments on commit f1d2ffb

Please sign in to comment.