summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml2
-rw-r--r--gens/test.gen10
-rw-r--r--gens/test_voice.gen2
-rw-r--r--src/client.rs12
-rw-r--r--src/lib.rs3
-rw-r--r--src/main.rs20
-rw-r--r--src/proto.rs17
-rw-r--r--src/seq/file/iv.rs112
-rw-r--r--src/seq/file/mod.rs4
-rw-r--r--src/seq/mod.rs222
-rw-r--r--src/seq/sequencer.rs59
-rw-r--r--src/types.rs1
12 files changed, 459 insertions, 5 deletions
diff --git a/Cargo.toml b/Cargo.toml
index ea5b5b8..441fcc1 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -19,6 +19,8 @@ byteorder = "1.1.0"
rand = "0.3"
unicode-xid = "0.1.0"
portaudio = "0.7.0"
+xml-rs = "0.8.0"
+failure = "0.1"
#[dependencies.glium]
##version = "0.17.1"
diff --git a/gens/test.gen b/gens/test.gen
index 4613c0c..e891ebe 100644
--- a/gens/test.gen
+++ b/gens/test.gen
@@ -19,5 +19,15 @@
#gens/test_basic.gen#,
#gens/test_basic.gen#,
#gens/test_basic.gen#,
+ #gens/test_basic.gen#,
+ #gens/test_basic.gen#,
+ #gens/test_basic.gen#,
+ #gens/test_basic.gen#,
+ #gens/test_basic.gen#,
+ #gens/test_basic.gen#,
+ #gens/test_basic.gen#,
+ #gens/test_basic.gen#,
+ #gens/test_basic.gen#,
+ #gens/test_basic.gen#,
#gens/test_basic.gen#
]
diff --git a/gens/test_voice.gen b/gens/test_voice.gen
index 41684c1..d4ae66a 100644
--- a/gens/test_voice.gen
+++ b/gens/test_voice.gen
@@ -1 +1 @@
-(lutgen(sine(lut_freq), 128, v_freq) * controlrate(dahdsr(v_frame < v_deadline, 0, 4 / samplerate(), 0, 1, 1, 8 / samplerate()))) * v_amp
+(lutgen(saw(lut_freq), 128, v_freq) * dahdsr(v_frame < v_deadline, 0, 8 / samplerate(), 0, 0, 1, 4 / samplerate())) * v_amp
diff --git a/src/client.rs b/src/client.rs
index b8518b3..5cf941a 100644
--- a/src/client.rs
+++ b/src/client.rs
@@ -1,5 +1,5 @@
use std::net::{UdpSocket, SocketAddr};
-use std::{io, mem};
+use std::{io, mem, iter};
use synth::*;
use proto::*;
@@ -111,6 +111,16 @@ impl Client {
self.socket.send_to(&reply_buffer, sender);
},
Command::PCM{..} => { /* TODO */ },
+ Command::PCMSyn{..} => { /* TODO */},
+ Command::ArtParam{voice, index, value} => {
+ dprintln!("Articulation parameter voice {:?} index {} value {}", voice, index, value);
+ for vidx in match voice {
+ Some(vidx) => ((vidx as usize)..((vidx+1) as usize)),
+ None => (0..self.voices.len()),
+ } {
+ *self.voices[vidx].params.vars.entry(format!("artp{}", index)).or_insert_with(Default::default) = value;
+ }
+ },
Command::Unknown{data} => {
dprintln!("Dropping packet: unknown data {:?}", (&data as &[u8]));
},
diff --git a/src/lib.rs b/src/lib.rs
index 28560f4..0e9672a 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,6 +1,8 @@
extern crate byteorder;
extern crate rand;
extern crate unicode_xid;
+extern crate xml;
+#[macro_use] extern crate failure;
pub mod types;
pub use types::*;
@@ -10,6 +12,7 @@ pub mod proto;
pub mod lang;
pub mod client;
pub mod monitor;
+pub mod seq;
#[cfg(feature = "graphics")]
pub mod graphics;
diff --git a/src/main.rs b/src/main.rs
index e4e2a20..9c61733 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,4 +1,4 @@
-use std::{io, env, thread, iter, time, mem};
+use std::{io, env, thread, iter, time, mem, ffi};
use std::io::*;
use std::fs::*;
use std::net::*;
@@ -20,12 +20,26 @@ use synfone::lang::*;
use synfone::proto::*;
use synfone::client::*;
+fn main() {
+ let cmd = env::args_os().nth(1).expect("Please pass a command as the first argument; use `help` as a command for more information.");
+ let cmds = cmd.into_string().expect("Couldn't parse command");
+
+ let new_args: Vec<ffi::OsString> = env::args_os().skip(1).collect();
+
+ match &*cmds {
+ "help" => eprintln!("TODO! Commands are help, client."),
+ "client" => main_client(new_args),
+ _ => eprintln!("Unknown command; `help` for help."),
+ }
+}
+
+
const GFX: bool = false;
-fn main() {
+fn main_client(args: Vec<ffi::OsString>) {
let env = Environment::default();
- let mut genfile = File::open(env::args_os().nth(1).expect("Need first argument to be a file with a generator vector")).expect("Failed to open file");
+ let mut genfile = File::open(args.iter().nth(1).expect("Need first argument to be a file with a generator vector")).expect("Failed to open file");
let mut genstr = String::new();
genfile.read_to_string(&mut genstr);
diff --git a/src/proto.rs b/src/proto.rs
index 279cd3f..21e8903 100644
--- a/src/proto.rs
+++ b/src/proto.rs
@@ -14,6 +14,8 @@ pub enum Command {
Play{sec: u32, usec: u32, freq: u32, amp: f32, voice: u32},
Caps{voices: u32, tp: [u8; 4], ident: [u8; 24]},
PCM{samples: [i16; 16]},
+ PCMSyn{buffered: u32},
+ ArtParam{voice: Option<u32>, index: u32, value: f32},
Unknown{data: [u8; Command::SIZE]},
}
@@ -60,6 +62,13 @@ impl Command {
NetworkEndian::write_u32(&mut ret[..4], 5);
NetworkEndian::write_i16_into(&samples, &mut ret[4..]);
},
+ Command::PCMSyn{buffered} => {
+ NetworkEndian::write_u32_into(&[6u32, buffered], &mut ret[..8]);
+ },
+ Command::ArtParam{voice, index, value} => {
+ NetworkEndian::write_u32_into(&[7u32, voice.unwrap_or(OBLIGATE_POLYPHONE), index], &mut ret[..12]);
+ NetworkEndian::write_f32(&mut ret[12..16], value);
+ },
Command::Unknown{data} => {
ret.copy_from_slice(&data);
},
@@ -79,6 +88,8 @@ impl fmt::Debug for Command {
Command::Play{sec, usec, freq, amp, voice} => f.debug_struct("Play").field("sec", &sec).field("usec", &usec).field("freq", &freq).field("amp", &amp).field("voice", &voice).finish(),
Command::Caps{voices, tp, ident} => f.debug_struct("Caps").field("voices", &voices).field("tp", &tp).field("ident", &ident).finish(),
Command::PCM{samples} => f.debug_struct("PCM").field("samples", &samples).finish(),
+ Command::PCMSyn{buffered} => f.debug_struct("PCMSyn").field("buffered", &buffered).finish(),
+ Command::ArtParam{voice, index, value} => f.debug_struct("ArtParam").field("voice", &voice).field("index", &index).field("value", &value).finish(),
Command::Unknown{data} => f.debug_struct("Unknown").field("data", &(&data as &[u8])).finish(),
}
}
@@ -122,6 +133,12 @@ impl<'a> From<&'a [u8; Command::SIZE]> for Command {
::byteorder::LittleEndian::read_i16_into(&packet[4..], &mut samples);
Command::PCM{samples: samples}
},
+ 6 => Command::PCMSyn{buffered: fields_u32[1]},
+ 7 => Command::ArtParam {
+ voice: if fields_u32[1] == OBLIGATE_POLYPHONE { None } else { Some(fields_u32[1]) },
+ index: fields_u32[2],
+ value: fields_f32[3],
+ },
_ => {
let mut data: [u8; Command::SIZE] = unsafe { mem::uninitialized() };
data.copy_from_slice(packet);
diff --git a/src/seq/file/iv.rs b/src/seq/file/iv.rs
new file mode 100644
index 0000000..570f36d
--- /dev/null
+++ b/src/seq/file/iv.rs
@@ -0,0 +1,112 @@
+use std::io;
+use std::collections::HashMap;
+use std::borrow::Borrow;
+
+use xml::reader;
+use xml::reader::{EventReader, XmlEvent};
+use xml::attribute::OwnedAttribute;
+use std::hash::Hash;
+use std::cmp::Eq;
+use std::str::FromStr;
+use std::fmt::Display;
+use failure::Error;
+use super::*;
+
+struct AttrMapping(HashMap<String, String>);
+
+impl AttrMapping {
+ pub fn make(attrs: Vec<OwnedAttribute>) -> AttrMapping {
+ let mut output = HashMap::new();
+
+ for attr in attrs {
+ output.insert(attr.name.local_name.clone(), attr.value.clone());
+ }
+
+ AttrMapping(output)
+ }
+
+ pub fn get_str<'a, 'b, 'c: 'a, Q: Hash+Eq+Display+?Sized>(&'a self, key: &'b Q, default: &'c str) -> &'a str where String: Borrow<Q> {
+ self.0.get(key).map(|x| &**x).unwrap_or(default)
+ }
+
+ pub fn req<V: FromStr, Q: Hash+Eq+Display+?Sized>(&self, key: &Q) -> Result<V, Error> where String: Borrow<Q>, V::Err: failure::Fail {
+ match self.0.get(key){
+ Some(x) => Ok(x.parse()?),
+ None => bail!("{} not found in attrs", key)
+ }
+ }
+
+ pub fn req_midi_pitch<Q: Hash+Eq+Display+?Sized>(&self, key: &Q) -> Result<Pitch, Error> where String: Borrow<Q> {
+ Ok(Pitch::MIDI(self.req::<f32, Q>(key)?))
+ }
+}
+
+fn parse_note(ev: XmlEvent, into: &mut Vec<Note>) -> Result<bool, Error> {
+ match ev {
+ XmlEvent::StartElement{name, attributes, ..} => {
+ if name.local_name.as_ref() != "note" { bail!("malformed iv: non-note attr in note stream"); }
+ let attrs = AttrMapping::make(attributes);
+ into.push(Note {
+ time: attrs.req("time")?,
+ ampl: attrs.req("ampl")?,
+ dur: attrs.req("dur")?,
+ pitch: attrs.req_midi_pitch("pitch")?,
+ start_tick: None,
+ dur_ticks: None
+ });
+ Ok(false)
+ },
+ _ => Ok(true)
+ }
+}
+
+pub fn read<R: io::Read>(source: R) -> Result<IV, Error> {
+ let mut output: IV = Default::default();
+ let mut event_reader = EventReader::new(source);
+
+ #[derive(Debug)]
+ enum ReadState<'a> {
+ Idle,
+ InStreams,
+ InBPMs,
+ InNoteStream(&'a mut NoteStream),
+ InAuxStream(&'a mut AuxStream),
+ }
+
+ let mut state = ReadState::Idle;
+
+ loop {
+ match event_reader.next()? {
+ XmlEvent::StartElement{name, attributes, ..} => {
+ let attrmap = AttrMapping::make(attributes);
+
+ match name.local_name.as_ref() {
+ "bpms" => { }
+ "streams" => {
+ match attrmap.get_str("type", "") {
+ "ns" => {
+ let mut notes = Vec::new();
+
+ loop {
+ if !parse_note(event_reader.next()?, &mut notes)? { break; }
+ }
+
+ },
+ _ => unimplemented!()
+ }
+ },
+ _ => unimplemented!()
+ }
+ }
+ XmlEvent::EndElement{name} => match (name.local_name.as_ref(), &state) {
+ ("bpms", _) => { state = ReadState::Idle; },
+ ("streams", _) => { state = ReadState::Idle; },
+ _ => (),
+ },
+ _ => (),
+ }
+ }
+
+
+ Ok(output)
+}
diff --git a/src/seq/file/mod.rs b/src/seq/file/mod.rs
new file mode 100644
index 0000000..12662f8
--- /dev/null
+++ b/src/seq/file/mod.rs
@@ -0,0 +1,4 @@
+//pub mod iv;
+//pub mod midi;
+
+use super::*;
diff --git a/src/seq/mod.rs b/src/seq/mod.rs
new file mode 100644
index 0000000..e53d64d
--- /dev/null
+++ b/src/seq/mod.rs
@@ -0,0 +1,222 @@
+pub mod sequencer;
+pub use self::sequencer::*;
+pub mod file;
+
+use std::{cmp, iter, ops};
+use std::collections::{hash_map, HashMap};
+
+use super::*;
+
+#[derive(Debug,Clone,Copy,PartialEq)]
+pub struct Seconds(pub f32);
+
+impl Eq for Seconds {}
+
+impl PartialOrd for Seconds {
+ fn partial_cmp(&self, other: &Seconds) -> Option<cmp::Ordering> {
+ self.0.partial_cmp(&other.0)
+ }
+}
+
+impl Ord for Seconds {
+ fn cmp(&self, other: &Seconds) -> cmp::Ordering {
+ self.partial_cmp(other).expect("Encountered NaN Seconds")
+ }
+}
+
+impl ops::Add for Seconds {
+ type Output = Seconds;
+ fn add(self, rhs: Seconds) -> Seconds { Seconds(self.0 + rhs.0) }
+}
+
+impl ops::Sub for Seconds {
+ type Output = Seconds;
+ fn sub(self, rhs: Seconds) -> Seconds { Seconds(self.0 - rhs.0) }
+}
+
+impl<RHS> ops::Mul<RHS> for Seconds where f32: ops::Mul<RHS, Output=f32> {
+ type Output = Seconds;
+ fn mul(self, rhs: RHS) -> Seconds { Seconds(self.0.mul(rhs)) }
+}
+
+impl<RHS> ops::Div<RHS> for Seconds where f32: ops::Div<RHS, Output=f32> {
+ type Output = Seconds;
+ fn div(self, rhs: RHS) -> Seconds { Seconds(self.0.div(rhs)) }
+}
+
+pub type Ticks = u64;
+
+#[derive(Debug,Clone)]
+pub enum Time {
+ Seconds(Seconds),
+ Ticks(Ticks),
+}
+
+impl From<Seconds> for Time {
+ fn from(s: Seconds) -> Time {
+ Time::Seconds(s)
+ }
+}
+
+impl From<Ticks> for Time {
+ fn from(t: Ticks) -> Time {
+ Time::Ticks(t)
+ }
+}
+
+#[derive(Debug,Clone,Copy,PartialEq)]
+pub struct BPM(pub f32);
+
+impl Eq for BPM {}
+
+impl PartialOrd for BPM {
+ fn partial_cmp(&self, other: &BPM) -> Option<cmp::Ordering> {
+ self.0.partial_cmp(&other.0)
+ }
+}
+
+impl Ord for BPM {
+ fn cmp(&self, other: &BPM) -> cmp::Ordering {
+ self.partial_cmp(other).expect("Encountered NaN BPM")
+ }
+}
+
+impl<RHS> ops::Mul<RHS> for BPM where f32: ops::Mul<RHS, Output=f32> {
+ type Output = BPM;
+ fn mul(self, rhs: RHS) -> BPM { BPM(self.0.mul(rhs)) }
+}
+impl<RHS> ops::Div<RHS> for BPM where f32: ops::Div<RHS, Output=f32> {
+ type Output = BPM;
+ fn div(self, rhs: RHS) -> BPM { BPM(self.0.div(rhs)) }
+}
+
+#[derive(Debug,Clone)]
+pub struct Note {
+ pub time: Seconds,
+ pub dur: Seconds,
+ pub start_tick: Option<Ticks>,
+ pub dur_ticks: Option<Ticks>,
+ pub ampl: f32,
+ pub pitch: Pitch,
+}
+
+#[derive(Debug,Clone)]
+pub struct Aux {
+ pub time: Seconds,
+ pub data: String,
+}
+
+pub type NoteStream = Vec<Note>;
+pub type AuxStream = Vec<Aux>;
+
+#[derive(Debug,Clone)]
+pub enum Stream {
+ Note(NoteStream),
+ Aux(AuxStream),
+}
+
+impl Stream {
+ pub fn note_stream(&self) -> Option<&NoteStream> {
+ match self {
+ &Stream::Note(ref ns) => Some(ns),
+ _ => None,
+ }
+ }
+}
+
+pub type Group = Vec<NoteStream>;
+
+#[derive(Debug,Clone,Copy)]
+pub struct BPMEntry {
+ pub abstick: Ticks,
+ pub bpm: BPM,
+ pub realtime: Option<Seconds>,
+}
+
+#[derive(Debug,Clone)]
+pub struct BPMTableInput {
+ pub entries: Vec<BPMEntry>,
+ pub resolution: f32,
+}
+
+impl From<BPMTableInput> for BPMTable {
+ fn from(input: BPMTableInput) -> BPMTable {
+ let mut range = input.entries.clone();
+ range.sort_unstable_by_key(|&ent| ent.abstick);
+ for ent in range.iter_mut() {
+ ent.realtime = Some(Seconds(0.0));
+ }
+ for idx in 1 .. (range.len() - 1) {
+ let tick = range[idx].abstick;
+ let BPMEntry {abstick: ptick, bpm: pbpm, realtime: ptm} = range[idx - 1];
+ range[idx].realtime = Some(ptm.unwrap() + Seconds((60.0 * ((tick - ptick) as f32)) / (pbpm * input.resolution).0));
+ }
+ BPMTable {
+ range: range,
+ resolution: input.resolution,
+ }
+ }
+}
+
+pub struct BPMTable {
+ range: Vec<BPMEntry>,
+ resolution: f32,
+}
+
+impl BPMTable {
+ pub fn to_seconds(&self, tm: Time) -> Seconds {
+ match tm {
+ Time::Seconds(s) => s,
+ Time::Ticks(t) => match self.range.binary_search_by_key(&t, |&ent| ent.abstick) {
+ Ok(idx) => self.range[idx].realtime.unwrap(),
+ Err(idx) => {
+ let effidx = cmp::max(0, idx - 1);
+ let BPMEntry {abstick: tick, bpm, realtime: sec} = self.range[effidx];
+ sec.unwrap() + Seconds((60.0 * ((t - tick) as f32)) / (bpm * self.resolution).0)
+ },
+ },
+ }
+ }
+
+ pub fn to_seconds_time(&self, tm: Time) -> Time {
+ Time::Seconds(self.to_seconds(tm))
+ }
+
+ pub fn to_ticks(&self, tm: Time) -> Ticks {
+ match tm {
+ Time::Ticks(t) => t,
+ Time::Seconds(s) => match self.range.binary_search_by_key(&s, |&ent| ent.realtime.unwrap()) {
+ Ok(idx) => self.range[idx].abstick,
+ Err(idx) => {
+ let effidx = cmp::max(0, idx - 1);
+ let BPMEntry {abstick: tick, bpm, realtime: sec} = self.range[effidx];
+ tick + ((((s - sec.unwrap()).0 * bpm.0 * self.resolution) / 60.0) as Ticks)
+ },
+ },
+ }
+ }
+
+ pub fn to_ticks_time(&self, tm: Time) -> Time {
+ Time::Ticks(self.to_ticks(tm))
+ }
+}
+
+#[derive(Default)]
+pub struct IVMeta {
+ pub bpms: Option<BPMTable>,
+ pub args: Option<String>,
+ pub app: Option<String>,
+}
+
+#[derive(Default)]
+pub struct IV {
+ pub default_group: Group,
+ pub groups: HashMap<String, Group>,
+ pub meta: IVMeta,
+}
+
+impl IV {
+ fn iter_streams(&self) -> impl Iterator<Item=&NoteStream> {
+ self.groups.values().chain(iter::once(&self.default_group)).flat_map(|x| x.iter())
+ }
+}
diff --git a/src/seq/sequencer.rs b/src/seq/sequencer.rs
new file mode 100644
index 0000000..2284656
--- /dev/null
+++ b/src/seq/sequencer.rs
@@ -0,0 +1,59 @@
+use super::*;
+
+pub fn coalesce<'a, I: Iterator<Item=&'a NoteStream>>(stream_iter: I) -> NoteStream {
+ let mut output = NoteStream::new();
+
+ for ns in stream_iter {
+ output.extend(ns.iter().cloned());
+ }
+
+ output
+}
+
+pub struct SchedParams {
+ pub epsilon: f32,
+}
+
+impl Default for SchedParams {
+ fn default() -> SchedParams {
+ SchedParams {
+ epsilon: 0.0,
+ }
+ }
+}
+
+pub fn schedule<'a, 'b: 'a, I: Iterator<Item=&'a Note>, F: FnMut(&'a Note) -> Option<&'b str>>(notes: I, mut classifier: F, params: &SchedParams) -> IV {
+ let mut output: IV = Default::default();
+
+ for note in notes {
+ let grp_name = classifier(note);
+ let grp = if let Some(name) = grp_name {
+ if !output.groups.contains_key(name) {
+ output.groups.insert(name.into(), Vec::new());
+ }
+ output.groups.get_mut(name).unwrap()
+ } else {
+ &mut output.default_group
+ };
+
+ let mut found: Option<usize> = None;
+ for (idx, ns) in grp.iter().enumerate() {
+ if ns.len() > 0 {
+ let nt = &ns[ns.len() - 1];
+ if note.time.0 < nt.time.0 + nt.dur.0 + params.epsilon {
+ continue
+ }
+ }
+ found = Some(idx);
+ break;
+ }
+
+ if let Some(nidx) = found {
+ grp[nidx].push(note.clone());
+ } else {
+ grp.push(vec![note.clone()]);
+ }
+ }
+
+ output
+}
diff --git a/src/types.rs b/src/types.rs
index dd450bb..b54eb42 100644
--- a/src/types.rs
+++ b/src/types.rs
@@ -1,5 +1,6 @@
pub type Sample = f32;
+#[derive(Debug,Clone)]
pub enum Pitch {
Freq(f32),
MIDI(f32),