Skip to content

Commit

Permalink
Improve timing accuracy and reduce cpu usage
Browse files Browse the repository at this point in the history
  • Loading branch information
Dave Poulter committed Mar 11, 2018
1 parent 54d5001 commit 5b3f64b
Show file tree
Hide file tree
Showing 11 changed files with 426 additions and 454 deletions.
26 changes: 10 additions & 16 deletions src/api.rs
Expand Up @@ -6,7 +6,7 @@ use serde_json;

use err::Error;
use lang::{assemble, parser, Directive};
use vm::{millis_to_dur, Clock, Command, Instr, Machine as VmMachine, Status, TimeEvent};
use vm::{millis_to_dur, Clock, Command, Instr, Machine as VmMachine, Schedule, Status};
use sinks::{factory, Backend, CompositeSink, Device, Sink as SinkTrait, ThreadedSink};

pub struct Sink {
Expand Down Expand Up @@ -54,7 +54,7 @@ pub struct Program {
pub struct Machine {
clock: Option<Clock>,
machine: VmMachine,
channel: Receiver<TimeEvent<Command>>,
channel: Receiver<Schedule<Command>>,
}

impl Program {
Expand All @@ -66,23 +66,17 @@ impl Program {
}

impl Machine {
pub fn new(prog: &Program, mut input: Input, output: Output) -> Result<Machine, Error> {
let (host_to_mach_send, host_to_mach_recv) = channel();
pub fn new(prog: &Program, input: Input, output: Output) -> Result<Machine, Error> {
let (clock_to_mach_send, clock_to_mach_recv) = channel();
let (mach_to_clock_send, mach_to_clock_recv) = channel();

let mut clock = Clock::new(clock_to_mach_send, mach_to_clock_recv);
clock.interval(2.0, Command::MidiClock);
clock.interval(0.5, Command::Clock);
clock.interval(1000.0, Command::Clock);

let machine = try!(VmMachine::new(
input,
output,
Box::new(move |evt| mach_to_clock_send.send(evt).unwrap_or(())),
Box::new(move |cmd| host_to_mach_send.send(cmd).unwrap_or(())),
Box::new(move || match host_to_mach_recv.try_recv() {
Ok(cmd) => Some(cmd),
Err(_) => input(),
}),
&prog.instrs,
));

Expand All @@ -109,8 +103,8 @@ impl Machine {
};

while let Ok(event) = self.channel.try_recv() {
if let TimeEvent::Timer(delta, cmd) = event {
let status = try!(self.machine.process(cmd, &delta));
if let Schedule::At(_, cmd) = event {
let status = try!(self.machine.process(cmd));
match status {
Status::Continue => (),
Status::Stop | Status::Reload => return Ok(status),
Expand All @@ -120,7 +114,7 @@ impl Machine {
}
}

clock.tick(&delta);
clock.tick(delta);
Ok(Status::Continue)
}

Expand All @@ -133,8 +127,8 @@ impl Machine {
thread::spawn(move || clock.run_forever());

while let Ok(event) = self.channel.recv() {
if let TimeEvent::Timer(delta, cmd) = event {
let status = try!(self.machine.process(cmd, &delta));
if let Schedule::At(_, cmd) = event {
let status = try!(self.machine.process(cmd));
match status {
Status::Continue => (),
Status::Stop | Status::Reload => return Ok(status),
Expand Down
95 changes: 95 additions & 0 deletions src/vm/handler.rs
@@ -0,0 +1,95 @@
use super::math::{point_on_curve, Curve};
use super::time::Schedule;
use super::types::{Command, Destination, Event, EventValue};

type Clock = Box<FnMut(Schedule<Command>)>;
type Out = Box<FnMut(Command)>;

pub struct EventHandler;

pub struct NoteInterceptor {
output: Out,
pending: Vec<(u8, u8)>,
}

impl NoteInterceptor {
pub fn new(output: Out) -> NoteInterceptor {
NoteInterceptor {
output: output,
pending: vec![],
}
}

pub fn filter(&mut self, cmd: Command) {
match cmd {
Command::MidiNoteOn(channel, pitch, _) => {
self.pending.push((channel, pitch));
(self.output)(cmd);
}
Command::MidiNoteOff(channel, pitch) => {
self.pending
.retain(|&evt| !(evt.0 == channel && evt.1 == pitch));
(self.output)(cmd);
}
Command::Stop => {
for &(channel, pitch) in &self.pending {
(self.output)(Command::MidiNoteOff(channel, pitch));
}
(self.output)(cmd);
}
_ => (self.output)(cmd),
}
}
}

impl EventHandler {
pub fn new() -> EventHandler {
EventHandler {}
}

pub fn handle(&mut self, output: &mut Clock, event: Event) {
match event.value {
EventValue::Trigger(val) => self.handle_trigger(output, event, val as u8),
EventValue::Curve(curve) => self.handle_control(output, event, curve),
};
}

fn handle_trigger(&mut self, output: &mut Clock, event: Event, val: u8) {
let (chan, vel) = match event.dest {
Destination::Midi(chan, vel) => (chan, vel),
};

let cmd = Command::Event(event);
output(Schedule::At(event.onset, cmd));
let cmd = Command::MidiNoteOn(chan, val, vel);
output(Schedule::At(event.onset, cmd));
let cmd = Command::MidiNoteOff(chan, val);
output(Schedule::At(event.onset + event.dur, cmd));
}

fn handle_control(&mut self, output: &mut Clock, event: Event, val: Curve) {
let cmd = Command::Event(event);
output(Schedule::At(event.onset, cmd));

let (chan, ctl) = match event.dest {
Destination::Midi(chan, ctl) => (chan, ctl),
};

let mut elapsed = 0.0;
let mut previous = None;
let delta = 1000.0 / 125.0; // target messages per second (roughly)

while elapsed <= event.dur {
let t = elapsed / event.dur;
let cc = point_on_curve(t, &val)[1].round() as u8;

if previous != Some(cc) {
let cmd = Command::MidiCtl(chan, ctl, cc);
output(Schedule::At(event.onset + elapsed, cmd));
previous = Some(cc);
}

elapsed += delta;
}
}
}
21 changes: 0 additions & 21 deletions src/vm/math.rs
@@ -1,5 +1,3 @@
use std::time::Duration;

pub type Point = [f64; 2];
pub type Curve = [f64; 8];

Expand Down Expand Up @@ -40,18 +38,6 @@ pub fn point_on_curve(t: f64, curve: &Curve) -> Point {
[x, y]
}

pub fn millis_to_dur(millis: f64) -> Duration {
let secs = (millis / 1000.0).floor();
let nanos = (millis - (secs * 1000.0)) * 1000000.0;
Duration::new(secs as u64, nanos as u32)
}

pub fn dur_to_millis(dur: &Duration) -> f64 {
let secs = dur.as_secs() as f64 * 1000.0;
let nanos = f64::from(dur.subsec_nanos()) / 1000000.0;
secs + nanos
}

#[cfg(test)]
mod tests {
use super::*;
Expand All @@ -65,11 +51,4 @@ mod tests {
assert_eq!(point_on_curve(-1.5, &curve), [0.0, 0.0]);
assert_eq!(point_on_curve(0.5, &curve), [0.5, 64.0]);
}

#[test]
fn test_time_fns() {
let dur = millis_to_dur(2500.0);
assert_eq!(dur, Duration::new(2, 500000000));
assert_eq!(dur_to_millis(&dur), 2500.0);
}
}
157 changes: 0 additions & 157 deletions src/vm/midi.rs

This file was deleted.

0 comments on commit 5b3f64b

Please sign in to comment.