mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 12:16:42 +01:00
sampler buzzes
This commit is contained in:
parent
faac61180b
commit
edc363c55b
6 changed files with 79 additions and 30 deletions
|
|
@ -47,7 +47,7 @@ impl Engine {
|
||||||
if exited.fetch_and(true, Ordering::Relaxed) {
|
if exited.fetch_and(true, Ordering::Relaxed) {
|
||||||
Control::Quit
|
Control::Quit
|
||||||
} else {
|
} else {
|
||||||
sender.send(Event::Update);
|
sender.send(Event::Update).unwrap();
|
||||||
Control::Continue
|
Control::Continue
|
||||||
}
|
}
|
||||||
}) as BoxedProcessHandler
|
}) as BoxedProcessHandler
|
||||||
|
|
|
||||||
|
|
@ -178,7 +178,7 @@ fn run_all () -> Result<(), Box<dyn Error>> {
|
||||||
if key.modifiers == KeyModifiers::SHIFT {
|
if key.modifiers == KeyModifiers::SHIFT {
|
||||||
state.transport.play_from_start_or_stop_and_rewind();
|
state.transport.play_from_start_or_stop_and_rewind();
|
||||||
} else {
|
} else {
|
||||||
state.transport.play_or_pause();
|
state.transport.play_or_pause().unwrap();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
KeyCode::Tab => match state.mode {
|
KeyCode::Tab => match state.mode {
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,12 @@ pub fn handle (
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
KeyCode::Down => {
|
KeyCode::Down => {
|
||||||
state.selected_sample = (state.selected_sample + 1) % state.samples.len();
|
state.selected_sample = (state.selected_sample + 1) % state.samples.lock().unwrap().len();
|
||||||
println!("{}", state.selected_sample);
|
println!("{}", state.selected_sample);
|
||||||
},
|
},
|
||||||
KeyCode::Up => {
|
KeyCode::Up => {
|
||||||
if state.selected_sample == 0 {
|
if state.selected_sample == 0 {
|
||||||
state.selected_sample = state.samples.len() - 1;
|
state.selected_sample = state.samples.lock().unwrap().len() - 1;
|
||||||
} else {
|
} else {
|
||||||
state.selected_sample = state.selected_sample - 1;
|
state.selected_sample = state.selected_sample - 1;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,67 +12,114 @@ pub const ACTIONS: [(&'static str, &'static str);2] = [
|
||||||
];
|
];
|
||||||
|
|
||||||
pub struct Sampler {
|
pub struct Sampler {
|
||||||
exited: bool,
|
exited: Arc<AtomicBool>,
|
||||||
jack: Jack<Notifications>,
|
jack: Jack<Notifications>,
|
||||||
samples: Vec<Sample>,
|
samples: Arc<Mutex<Vec<Sample>>>,
|
||||||
selected_sample: usize,
|
selected_sample: usize,
|
||||||
selected_column: usize,
|
selected_column: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sampler {
|
impl Sampler {
|
||||||
pub fn new () -> Result<Self, Box<dyn Error>> {
|
pub fn new () -> Result<Self, Box<dyn Error>> {
|
||||||
|
let exited = Arc::new(AtomicBool::new(false));
|
||||||
let (client, status) = Client::new(
|
let (client, status) = Client::new(
|
||||||
"bloop-sampler",
|
"blinkenlive-sampler",
|
||||||
ClientOptions::NO_START_SERVER
|
ClientOptions::NO_START_SERVER
|
||||||
)?;
|
)?;
|
||||||
let jack = client.activate_async(
|
let samples = vec![
|
||||||
Notifications,
|
Sample::new("Kick", &client, 1, 35)?,
|
||||||
ClosureProcessHandler::new(Box::new(
|
Sample::new("Snare", &client, 1, 38)?,
|
||||||
move |_: &Client, _: &ProcessScope| -> Control {
|
];
|
||||||
|
let samples = Arc::new(Mutex::new(samples));
|
||||||
|
let input = client.register_port("trigger", ::jack::MidiIn::default())?;
|
||||||
|
let handler: BoxedProcessHandler = Box::new({
|
||||||
|
let exited = exited.clone();
|
||||||
|
let samples = samples.clone();
|
||||||
|
Box::new(move |_: &Client, scope: &ProcessScope| -> Control {
|
||||||
|
if exited.fetch_and(true, Ordering::Relaxed) {
|
||||||
|
return Control::Quit
|
||||||
|
}
|
||||||
|
let mut samples = samples.lock().unwrap();
|
||||||
|
for event in input.iter(scope) {
|
||||||
|
let len = 3.min(event.bytes.len());
|
||||||
|
let mut data = [0; 3];
|
||||||
|
data[..len].copy_from_slice(&event.bytes[..len]);
|
||||||
|
if (data[0] >> 4) == 0b1001 { // note on
|
||||||
|
let channel = data[0] & 0b00001111;
|
||||||
|
let note = data[1];
|
||||||
|
let velocity = data[2];
|
||||||
|
for sample in samples.iter_mut() {
|
||||||
|
if /*sample.trigger.0 == channel &&*/ sample.trigger.1 == note {
|
||||||
|
sample.play(velocity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for sample in samples.iter_mut() {
|
||||||
|
if let Some(playing) = sample.playing {
|
||||||
|
for (index, value) in sample.port.as_mut_slice(scope).iter_mut().enumerate() {
|
||||||
|
*value = *sample.data[0].get(playing + index).unwrap_or(&0f32);
|
||||||
|
}
|
||||||
|
if playing + scope.n_frames() as usize > sample.data[0].len() {
|
||||||
|
sample.playing = None
|
||||||
|
} else {
|
||||||
|
sample.playing = Some(playing + scope.n_frames() as usize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Control::Continue
|
Control::Continue
|
||||||
}) as Box<dyn FnMut(&Client, &ProcessScope)->Control + Send>
|
})
|
||||||
)
|
});
|
||||||
)?;
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
exited: false,
|
exited,
|
||||||
selected_sample: 0,
|
selected_sample: 0,
|
||||||
selected_column: 0,
|
selected_column: 0,
|
||||||
samples: vec![
|
samples,
|
||||||
Sample::new("Kick")?,
|
jack: client.activate_async(
|
||||||
Sample::new("Snare")?,
|
self::jack::Notifications,
|
||||||
],
|
ClosureProcessHandler::new(handler)
|
||||||
jack,
|
)?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Sample {
|
pub struct Sample {
|
||||||
|
port: Port<AudioOut>,
|
||||||
name: String,
|
name: String,
|
||||||
rate: u32,
|
rate: u32,
|
||||||
gain: f64,
|
gain: f64,
|
||||||
channels: u8,
|
channels: u8,
|
||||||
data: Vec<Vec<u8>>,
|
data: Vec<Vec<f32>>,
|
||||||
|
trigger: (u8, u8),
|
||||||
|
playing: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sample {
|
impl Sample {
|
||||||
|
|
||||||
pub fn new (name: &str) -> Result<Self, Box<dyn Error>> {
|
pub fn new (name: &str, client: &Client, channel: u8, note: u8) -> Result<Self, Box<dyn Error>> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
|
port: client.register_port(name, ::jack::AudioOut::default())?,
|
||||||
name: name.into(),
|
name: name.into(),
|
||||||
rate: 44100,
|
rate: 44100,
|
||||||
channels: 1,
|
channels: 1,
|
||||||
gain: 0.0,
|
gain: 0.0,
|
||||||
data: vec![vec![]]
|
data: vec![vec![1.0, 0.0, 0.0, 0.0]],
|
||||||
|
trigger: (channel, note),
|
||||||
|
playing: None
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn play (&mut self, velocity: u8) {
|
||||||
|
self.playing = Some(0)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Exitable for Sampler {
|
impl Exitable for Sampler {
|
||||||
fn exit (&mut self) {
|
fn exit (&mut self) {
|
||||||
self.exited = true
|
self.exited.store(true, Ordering::Relaxed)
|
||||||
}
|
}
|
||||||
fn exited (&self) -> bool {
|
fn exited (&self) -> bool {
|
||||||
self.exited
|
self.exited.fetch_and(true, Ordering::Relaxed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,13 +20,14 @@ fn render_table (
|
||||||
stdout.queue(move_to(0, 3))?.queue(
|
stdout.queue(move_to(0, 3))?.queue(
|
||||||
Print(" Name Rate Trigger Route")
|
Print(" Name Rate Trigger Route")
|
||||||
)?;
|
)?;
|
||||||
for (i, sample) in state.samples.iter().enumerate() {
|
for (i, sample) in state.samples.lock().unwrap().iter().enumerate() {
|
||||||
let row = 4 + i as u16;
|
let row = 4 + i as u16;
|
||||||
for (j, (column, field)) in [
|
for (j, (column, field)) in [
|
||||||
(0, format!(" {:7} ", sample.name)),
|
(0, format!(" {:7} ", sample.name)),
|
||||||
(9, format!(" {:.1}Hz ", sample.rate)),
|
(9, format!(" {:.1}Hz ", sample.rate)),
|
||||||
(18, format!(" MIDI C10 36 ")),
|
(18, format!(" MIDI C{} {} ", sample.trigger.0, sample.trigger.1)),
|
||||||
(33, format!(" {:.1}dB -> Output ", sample.gain)),
|
(33, format!(" {:.1}dB -> Output ", sample.gain)),
|
||||||
|
(50, format!(" {} ", sample.playing.unwrap_or(0))),
|
||||||
].into_iter().enumerate() {
|
].into_iter().enumerate() {
|
||||||
stdout.queue(move_to(column, row))?;
|
stdout.queue(move_to(column, row))?;
|
||||||
if state.selected_sample == i && state.selected_column == j {
|
if state.selected_sample == i && state.selected_column == j {
|
||||||
|
|
@ -45,7 +46,7 @@ fn render_meters (
|
||||||
offset: (u16, u16),
|
offset: (u16, u16),
|
||||||
) -> Result<(), Box<dyn Error>> {
|
) -> Result<(), Box<dyn Error>> {
|
||||||
let move_to = |col, row| crossterm::cursor::MoveTo(offset.0 + col, offset.1 + row);
|
let move_to = |col, row| crossterm::cursor::MoveTo(offset.0 + col, offset.1 + row);
|
||||||
for (i, sample) in state.samples.iter().enumerate() {
|
for (i, sample) in state.samples.lock().iter().enumerate() {
|
||||||
let row = 4 + i as u16;
|
let row = 4 + i as u16;
|
||||||
stdout.queue(move_to(32, row))?.queue(
|
stdout.queue(move_to(32, row))?.queue(
|
||||||
PrintStyledContent("▁".green())
|
PrintStyledContent("▁".green())
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,8 @@ pub use self::jack::*;
|
||||||
pub use self::render::*;
|
pub use self::render::*;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
pub const ACTIONS: [(&'static str, &'static str);3] = [
|
pub const ACTIONS: [(&'static str, &'static str);4] = [
|
||||||
|
("?", "Toggle help"),
|
||||||
("(Shift-)Tab", "Switch pane"),
|
("(Shift-)Tab", "Switch pane"),
|
||||||
("Arrows", "Navigate"),
|
("Arrows", "Navigate"),
|
||||||
("(Shift-)Space", "⯈ Play/pause"),
|
("(Shift-)Space", "⯈ Play/pause"),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue