Skip to content

Commit

Permalink
Implement additiona Midi iterator utilities (RustAudio#85)
Browse files Browse the repository at this point in the history
* Add peek and next_if method.
* Make MidiIter cloneable
  • Loading branch information
wmedrano authored Dec 29, 2017
1 parent caa7ef5 commit 583cecc
Show file tree
Hide file tree
Showing 3 changed files with 152 additions and 16 deletions.
2 changes: 1 addition & 1 deletion dummy_jack_server.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/bin/sh
# Start the dummy JACK server

jackd -r -ddummy -r44100 -p1024
exec jackd -r -ddummy -r44100 -p1024
37 changes: 26 additions & 11 deletions src/port/midi.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use std::{mem, slice};
use std::cell::Cell;

use jack_sys as j;
use libc;
Expand Down Expand Up @@ -74,11 +73,10 @@ unsafe impl PortSpec for MidiOutSpec {
}

/// Safely and thinly wrap a `Port<MidiInPort>`.
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct MidiInPort<'a> {
_port: &'a Port<MidiInSpec>,
buffer_ptr: *mut ::libc::c_void,
message: Cell<RawMidi<'a>>,
}

impl<'a> MidiInPort<'a> {
Expand All @@ -92,7 +90,6 @@ impl<'a> MidiInPort<'a> {
MidiInPort {
_port: port,
buffer_ptr: buffer_ptr,
message: Cell::new(RawMidi::default()),
}
}

Expand All @@ -103,11 +100,10 @@ impl<'a> MidiInPort<'a> {
return None;
}
let bytes_slice: &[u8] = unsafe { slice::from_raw_parts(ev.buffer as *const u8, ev.size) };
self.message.set(RawMidi {
Some(RawMidi {
time: ev.time,
bytes: bytes_slice,
});
Some(self.message.get())
})
}

pub fn len(&self) -> usize {
Expand Down Expand Up @@ -192,24 +188,43 @@ impl<'a> MidiOutPort<'a> {
}

/// Iterate through Midi Messages within a `MidiInPort`.
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct MidiIter<'a> {
port: &'a MidiInPort<'a>,
index: usize,
}

impl<'a> MidiIter<'a> {
/// Return the next element without advancing the iterator.
pub fn peek(&self) -> Option<RawMidi<'a>> {
self.port.nth(self.index)
}

/// Return the next element only if the message passes the predicate.
pub fn next_if<P>(&mut self, predicate: P) -> Option<RawMidi<'a>>
where
P: FnOnce(RawMidi) -> bool,
{
if self.peek().map(predicate).unwrap_or(false) {
self.next()
} else {
None
}
}
}

impl<'a> Iterator for MidiIter<'a> {
type Item = RawMidi<'a>;

fn next(&mut self) -> Option<Self::Item> {
let ret = self.port.nth(self.index);
let ret = self.peek();
self.index += 1;
ret
}

fn size_hint(&self) -> (usize, Option<usize>) {
let elements_left = self.port.len() - self.index;
(elements_left, Some(elements_left))
let count = self.port.len() - self.index;
(count, Some(count))
}

fn count(self) -> usize {
Expand Down
129 changes: 125 additions & 4 deletions src/test/test_port_midi.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use prelude::*;

use std::{thread, time};
use std::iter::Iterator;
use std::sync::Mutex;
Expand All @@ -10,6 +11,84 @@ fn open_test_client(name: &str) -> Client {
.0
}

struct Connector {
src: String,
dst: String,
}

impl Connector {
fn connect(&self, c: &Client) {
c.connect_ports_by_name(&self.src, &self.dst).unwrap();
}
}

#[derive(Clone, Debug, PartialEq, Eq)]
struct OwnedRawMidi {
time: JackFrames,
bytes: Vec<u8>,
}

impl OwnedRawMidi {
fn new(m: &RawMidi) -> OwnedRawMidi {
OwnedRawMidi {
time: m.time,
bytes: m.bytes.to_vec(),
}
}

fn unowned<'a>(&'a self) -> RawMidi<'a> {
RawMidi {
time: self.time,
bytes: &self.bytes,
}
}
}

struct IterTest<F: Send + Fn(MidiInPort) -> Vec<OwnedRawMidi>> {
stream: Vec<OwnedRawMidi>,
collected: Vec<OwnedRawMidi>,
collector: F,
midi_in: Port<MidiInSpec>,
midi_out: Port<MidiOutSpec>,
}

impl<F: Send + Fn(MidiInPort) -> Vec<OwnedRawMidi>> IterTest<F> {
fn new(client: &Client, stream: Vec<OwnedRawMidi>, collector: F) -> IterTest<F> {
IterTest {
stream: stream,
collected: Vec::new(),
collector: collector,
midi_in: client.register_port("in", MidiInSpec::default()).unwrap(),
midi_out: client.register_port("out", MidiOutSpec::default()).unwrap(),
}
}

fn connector(&self) -> Connector {
Connector {
src: self.midi_out.name().to_string(),
dst: self.midi_in.name().to_string(),
}
}
}

impl<F: Send + Fn(MidiInPort) -> Vec<OwnedRawMidi>> ProcessHandler for IterTest<F> {
fn process(&mut self, _: &Client, ps: &ProcessScope) -> JackControl {
let (midi_in, mut midi_out) = (
MidiInPort::new(&self.midi_in, ps),
MidiOutPort::new(&mut self.midi_out, ps),
);
// Write to output.
for m in self.stream.iter() {
midi_out.write(&m.unowned()).unwrap();
}
// Collect in input.
if self.collected.is_empty() {
self.collected = (self.collector)(midi_in);
}
JackControl::Continue
}
}

#[test]
fn port_midi_can_read_write() {
// open clients and ports
Expand Down Expand Up @@ -91,7 +170,7 @@ lazy_static! {
}

#[test]
fn port_midi_cant_execeed_max_event_size() {
fn port_midi_cant_exceed_max_event_size() {
// open clients and ports
let c = open_test_client("port_midi_cglc");
let mut out_p = c.register_port("op", MidiOutSpec::default()).unwrap();
Expand Down Expand Up @@ -132,9 +211,9 @@ lazy_static! {
}

#[test]
fn port_midi_has_good_iter() {
fn port_midi_iter() {
// open clients and ports
let c = open_test_client("port_midi_has_good_iter");
let c = open_test_client("port_midi_iter");
let in_p = c.register_port("ip", MidiInSpec::default()).unwrap();
let mut out_p = c.register_port("op", MidiOutSpec::default()).unwrap();

Expand Down Expand Up @@ -163,7 +242,7 @@ fn port_midi_has_good_iter() {

// run
let ac = AsyncClient::new(c, (), ClosureProcessHandler::new(process_callback)).unwrap();
ac.connect_ports_by_name("port_midi_has_good_iter:op", "port_midi_has_good_iter:ip")
ac.connect_ports_by_name("port_midi_iter:op", "port_midi_iter:ip")
.unwrap();
thread::sleep(time::Duration::from_millis(200));
ac.deactivate().unwrap();
Expand All @@ -175,3 +254,45 @@ fn port_midi_has_good_iter() {
assert_eq!(*PMI_LAST.lock().unwrap(), Some((13, [13].to_vec())));
assert_eq!(*PMI_THIRD.lock().unwrap(), Some((12, [12].to_vec())));
}

#[test]
fn port_midi_iter_next_if() {
let c = open_test_client("pmi_nib");
let stream = vec![
OwnedRawMidi {
time: 0,
bytes: vec![1],
},
OwnedRawMidi {
time: 10,
bytes: vec![3, 4, 5],
},
OwnedRawMidi {
time: 11,
bytes: vec![6],
},
OwnedRawMidi {
time: 12,
bytes: vec![7, 8],
},
];
let collect = |midi_in: MidiInPort| {
let mut collected = Vec::with_capacity(midi_in.len());
let mut iter = midi_in.iter();
while let Some(m) = iter.next_if(|m| m.time < 11) {
collected.push(OwnedRawMidi::new(&m));
}
collected
};
let processor = IterTest::new(&c, stream.clone(), collect);
let connector = processor.connector();

let ac = AsyncClient::new(c, (), processor).unwrap();
connector.connect(&ac);
thread::sleep(time::Duration::from_millis(200));

let (_, _, processor) = ac.deactivate().unwrap();
let expected: &[OwnedRawMidi] = &stream[0..2];
let got: &[OwnedRawMidi] = &processor.collected;
assert_eq!(expected, got);
}

0 comments on commit 583cecc

Please sign in to comment.