Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jack transport #29

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,213 changes: 788 additions & 425 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions loopers-common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ license = "MIT OR Apache-2.0"
[dependencies]
bytes = "1.0"
serde = { version = "1.0", features = ["derive", "rc"] }

csv = "1.1"
bytemuck = { version = "1", features = ["derive"] }
csv = "1.3"

crossbeam-queue = "0.3"
crossbeam-channel = "0.5"
Expand Down
66 changes: 33 additions & 33 deletions loopers-common/src/api.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::gui_channel::WAVEFORM_DOWNSAMPLE;
use crate::music::{SavedMetricStructure};
use crate::music::SavedMetricStructure;
use derive_more::{Add, Div, Mul, Sub};
use serde::{Deserialize, Serialize};
use std::ops::{Index, IndexMut};
Expand All @@ -15,7 +15,7 @@ mod tests {
#[test]
fn test_from_str() {
assert_eq!(
Command::Start,
Command::Start(false),
Command::from_str("Start", &[][..]).unwrap()(CommandData { data: 0 })
);

Expand Down Expand Up @@ -140,7 +140,9 @@ impl LooperCommand {
use Command::Looper;
use LooperCommand::*;

let target_type = args.get(0).ok_or(format!("{} expects a target", command))?;
let target_type = args
.first()
.ok_or(format!("{} expects a target", command))?;

let target = match *target_type {
"All" => LooperTarget::All,
Expand Down Expand Up @@ -173,7 +175,7 @@ impl LooperCommand {
} else {
let f = f32::from_str(v)
.map_err(|_| format!("Invalid value for SetPan: '{}'", v))?;
if f < -1.0 || f > 1.0 {
if !(-1.0..=1.0).contains(&f) {
return Err("Value for SetPan must be between -1 and 1".to_string());
}
Some(f)
Expand All @@ -185,7 +187,7 @@ impl LooperCommand {
target,
)
})
},
}

"SetLevel" => {
let v = args.get(1).ok_or(
Expand All @@ -197,21 +199,15 @@ impl LooperCommand {
} else {
let f = f32::from_str(v)
.map_err(|_| format!("Invalid value for SetLevel: '{}'", v))?;
if f < 0.0 || f > 1.0 {
if !(0.0..=1.0).contains(&f) {
return Err("Value for SetLevel must be between 0 and 1".to_string());
}
Some(f)
};

Box::new(move |d| {
Looper(
SetLevel(arg.unwrap_or(d.data as f32 / 127.0)),
target,
)
})
Box::new(move |d| Looper(SetLevel(arg.unwrap_or(d.data as f32 / 127.0)), target))
}


"1/2x" => Box::new(move |_| Looper(SetSpeed(LooperSpeed::Half), target)),
"1x" => Box::new(move |_| Looper(SetSpeed(LooperSpeed::One), target)),
"2x" => Box::new(move |_| Looper(SetSpeed(LooperSpeed::Double), target)),
Expand All @@ -228,12 +224,12 @@ impl LooperCommand {
pub enum Command {
Looper(LooperCommand, LooperTarget),

Start,
Stop,
Pause,
Start(bool), // set_transport
Stop(bool), // set_transport
Pause(bool), // set_transport

StartStop,
PlayPause,
StartStop(bool), // set_transport
PlayPause(bool), // set_transport

Reset,
SetTime(FrameTime),
Expand All @@ -248,6 +244,8 @@ pub enum Command {
PreviousPart,
NextPart,
GoToPart(Part),
SetTransportPosition(FrameTime),
StartTransport,

SetQuantizationMode(QuantizationMode),

Expand All @@ -266,26 +264,26 @@ impl Command {
args: &[&str],
) -> Result<Box<dyn Fn(CommandData) -> Command + Send>, String> {
Ok(match command {
"Start" => Box::new(|_| Command::Start),
"Stop" => Box::new(|_| Command::Stop),
"Pause" => Box::new(|_| Command::Pause),
"StartStop" => Box::new(|_| Command::StartStop),
"PlayPause" => Box::new(|_| Command::PlayPause),
"Start" => Box::new(|_| Command::Start(false)),
"Stop" => Box::new(|_| Command::Stop(false)),
"Pause" => Box::new(|_| Command::Pause(false)),
"StartStop" => Box::new(|_| Command::StartStop(false)),
"PlayPause" => Box::new(|_| Command::PlayPause(false)),
"Reset" => Box::new(|_| Command::Reset),

"SetTime" => {
let arg = args
.get(0)
.first()
.and_then(|s| i64::from_str(s).ok())
.map(|t| FrameTime(t))
.map(FrameTime)
.ok_or("SetTime expects a single numeric argument, time".to_string())?;
Box::new(move |_| Command::SetTime(arg))
}

"AddLooper" => Box::new(|_| Command::AddLooper),
"SelectLooperById" => {
let arg = args
.get(0)
.first()
.and_then(|s| u32::from_str(s).ok())
.ok_or(
"SelectLooperById expects a single numeric argument, the looper id"
Expand All @@ -297,7 +295,7 @@ impl Command {
}

"SelectLooperByIndex" => {
let arg = args.get(0).and_then(|s| u8::from_str(s).ok()).ok_or(
let arg = args.first().and_then(|s| u8::from_str(s).ok()).ok_or(
"SelectLooperByIndex expects a single numeric argument, the looper index"
.to_string(),
)?;
Expand All @@ -311,8 +309,8 @@ impl Command {
"NextPart" => Box::new(|_| Command::NextPart),
"GoToPart" => {
let arg = args
.get(0)
.and_then(|s| match s.as_ref() {
.first()
.and_then(|s| match *s {
"A" => Some(Part::A),
"B" => Some(Part::B),
"C" => Some(Part::C),
Expand All @@ -326,8 +324,8 @@ impl Command {

"SetQuantizationMode" => {
let arg = args
.get(0)
.and_then(|s| match s.as_ref() {
.first()
.and_then(|s| match *s {
"Free" => Some(QuantizationMode::Free),
"Beat" => Some(QuantizationMode::Beat),
"Measure" => Some(QuantizationMode::Measure),
Expand All @@ -341,7 +339,7 @@ impl Command {
}

"SetMetronomeLevel" => {
let arg = args.get(0).and_then(|s| u8::from_str(s).ok()).ok_or(
let arg = args.first().and_then(|s| u8::from_str(s).ok()).ok_or(
"SetMetronomeLevel expects a single numeric argument, the level between 0-100"
.to_string(),
)?;
Expand Down Expand Up @@ -404,6 +402,7 @@ impl PartSet {
}

pub fn is_empty(&self) -> bool {
#[allow(clippy::nonminimal_bool)]
!(self.a || self.b || self.c || self.c)
}
}
Expand Down Expand Up @@ -440,7 +439,8 @@ impl IndexMut<Part> for PartSet {

pub static PARTS: [Part; 4] = [Part::A, Part::B, Part::C, Part::D];

#[derive(Serialize, Deserialize, Clone, Copy, Debug, Eq, PartialEq, Hash)]
#[derive(bytemuck::NoUninit, Serialize, Deserialize, Clone, Copy, Debug, Eq, PartialEq, Hash)]
#[repr(u8)]
pub enum LooperMode {
Recording,
Overdubbing,
Expand Down
17 changes: 5 additions & 12 deletions loopers-common/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ mod tests {

let mapping = MidiMapping::from_file(
&file.path().to_string_lossy(),
&File::open(&file.path()).unwrap(),
&File::open(file.path()).unwrap(),
)
.unwrap();

Expand All @@ -58,7 +58,7 @@ mod tests {
assert_eq!(24, mapping[2].controller);
assert_eq!(DataValue::Value(6), mapping[2].data);
assert_eq!(
Command::Start,
Command::Start(false),
(mapping[2].command)(CommandData { data: 39 })
);

Expand All @@ -74,18 +74,11 @@ mod tests {

pub static FILE_HEADER: &str = "Channel\tController\tData\tCommand\tArg1\tArg2\tArg3";

#[derive(Default)]
pub struct Config {
pub midi_mappings: Vec<MidiMapping>,
}

impl Config {
pub fn new() -> Config {
Config {
midi_mappings: vec![],
}
}
}

#[derive(Debug, PartialEq)]
pub enum DataValue {
Any,
Expand All @@ -105,7 +98,7 @@ impl DataValue {
}
}

let split: Vec<u8> = s.split("-").filter_map(|s| u8::from_str(s).ok()).collect();
let split: Vec<u8> = s.split('-').filter_map(|s| u8::from_str(s).ok()).collect();

if split.len() == 2 && split[0] <= 127 && split[1] <= 127 && split[0] < split[1] {
return Some(DataValue::Range(split[0], split[1]));
Expand Down Expand Up @@ -180,7 +173,7 @@ impl MidiMapping {
u8::from_str(c)
.map_err(|_| "Channel must be * or a number".to_string())
.and_then(|c| {
if c >= 1 && c <= 16 {
if (1..=16).contains(&c) {
Ok(c)
} else {
Err("Channel must be between 1 and 16".to_string())
Expand Down
19 changes: 6 additions & 13 deletions loopers-common/src/gui_channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,15 @@ pub enum GuiCommand {
AddGlobalTrigger(FrameTime, Command),
}

#[derive(Clone)]
#[derive(Clone, Default)]
pub enum LogLevel {
#[default]
Info,
Warn,
Error,
}

#[derive(Clone)]
#[derive(Clone, Default)]
pub struct LogMessage {
buffer: ArrayVec<u8, 256>,
len: usize,
Expand All @@ -81,14 +82,6 @@ pub struct LogMessage {
}

impl LogMessage {
pub fn new() -> Self {
LogMessage {
buffer: ArrayVec::new(),
len: 0,
level: LogLevel::Info,
}
}

pub fn error() -> Self {
LogMessage {
buffer: ArrayVec::new(),
Expand Down Expand Up @@ -131,7 +124,7 @@ impl GuiSender {

let sender = GuiSender {
cmd_channel: Some(tx),
cur_message: LogMessage::new(),
cur_message: LogMessage::default(),
log_channel: Some(log_tx),
};

Expand All @@ -146,7 +139,7 @@ impl GuiSender {
pub fn disconnected() -> GuiSender {
GuiSender {
cmd_channel: None,
cur_message: LogMessage::new(),
cur_message: LogMessage::default(),
log_channel: None,
}
}
Expand All @@ -165,7 +158,7 @@ impl GuiSender {
}
}

pub fn send_log(&mut self, message: LogMessage) -> () {
pub fn send_log(&mut self, message: LogMessage) {
if let Err(e) = self.send_log_with_result(message) {
warn!("Failed to send message to gui: {}", e);
}
Expand Down
9 changes: 9 additions & 0 deletions loopers-common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
extern crate log;
extern crate derive_more;

use crate::api::FrameTime;
use crate::music::MetricStructure;

pub mod api;
pub mod config;
pub mod gui_channel;
Expand All @@ -25,11 +28,17 @@ pub fn f32_to_i16(v: f32) -> i16 {
(v * 32768.0).floor() as i16
}

#[allow(unused_variables)]
pub trait Host<'a> {
fn add_looper(&mut self, id: u32) -> Result<(), String>;
fn remove_looper(&mut self, id: u32) -> Result<(), String>;

fn output_for_looper<'b>(&'b mut self, id: u32) -> Option<[&'b mut [f32]; 2]>
where
'a: 'b;

fn start_transport(&mut self) {}
fn stop_transport(&mut self) {}
fn set_transport_position(&mut self, time: FrameTime, metric_structure: MetricStructure) {}
fn set_transport_bpm(&mut self, bpm: f32) {}
}
26 changes: 12 additions & 14 deletions loopers-common/src/music.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,10 @@ mod tests {
let next_beat_time = tempo.next_full_beat(FrameTime(time));

assert!(
next_beat_time.0 <= time + frames as i64,
next_beat_time.0 <= time + frames,
"{} > {} (time = {})",
next_beat_time.0,
time + frames as i64,
time + frames,
time
);

Expand Down Expand Up @@ -143,9 +143,7 @@ pub struct Tempo {
impl Tempo {
pub fn new(bpm: u64) -> Tempo {
assert!(bpm > 0, "bpm must be positive");
Tempo {
bpm
}
Tempo { bpm }
}

pub fn from_bpm(bpm: f32) -> Tempo {
Expand Down Expand Up @@ -192,17 +190,17 @@ impl SavedMetricStructure {
pub fn to_ms(&self) -> Result<MetricStructure, String> {
let bpm = match self.tempo {
SavedTempo { bpm: Some(bpm), .. } => Ok(Tempo::new(bpm)),
SavedTempo { samples_per_beat: Some(spb), ..} =>
Ok(Tempo::from_bpm(
((get_sample_rate() as f64) / spb as f64 * 60.0) as f32)),
_ => Err("Neither bpm nor samples_per_beat supplied".to_string())
SavedTempo {
samples_per_beat: Some(spb),
..
} => Ok(Tempo::from_bpm(
((get_sample_rate() as f64) / spb as f64 * 60.0) as f32,
)),
_ => Err("Neither bpm nor samples_per_beat supplied".to_string()),
}?;

MetricStructure::new(
self.time_signature.upper,
self.time_signature.lower,
bpm,
).ok_or("Invalid time signature".to_string())
MetricStructure::new(self.time_signature.upper, self.time_signature.lower, bpm)
.ok_or("Invalid time signature".to_string())
}
}

Expand Down
Loading