mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 19:56:42 +01:00
260 lines
9 KiB
Rust
260 lines
9 KiB
Rust
use crate::prelude::*;
|
|
|
|
pub struct Voice {
|
|
sample: Arc<Sample>,
|
|
position: usize,
|
|
}
|
|
impl Voice {
|
|
fn chunk (&mut self, frames: usize) -> Option<Vec<Vec<f32>>> {
|
|
let mut chunk = vec![];
|
|
if self.position < self.sample.end {
|
|
let start = self.position.min(self.sample.end);
|
|
let end = (self.position + frames).min(self.sample.end);
|
|
for channel in self.sample.channels.iter() {
|
|
chunk.push(channel[start..end].into());
|
|
};
|
|
self.position = self.position + frames;
|
|
Some(chunk)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct Sample {
|
|
pub name: String,
|
|
pub start: usize,
|
|
pub end: usize,
|
|
pub channels: Vec<Vec<f32>>,
|
|
}
|
|
impl Sample {
|
|
pub fn new (name: &str, start: usize, end: usize, channels: Vec<Vec<f32>>) -> Arc<Self> {
|
|
Arc::new(Self { name: name.to_string(), start, end, channels })
|
|
}
|
|
fn play (self: &Arc<Self>) -> Voice {
|
|
Voice { sample: self.clone(), position: self.start }
|
|
}
|
|
}
|
|
|
|
pub struct Sampler {
|
|
name: String,
|
|
cursor: (usize, usize),
|
|
samples: BTreeMap<u7, Arc<Sample>>,
|
|
voices: Vec<Voice>,
|
|
midi_in: Port<MidiIn>,
|
|
audio_ins: Vec<Port<AudioIn>>,
|
|
audio_outs: Vec<Port<AudioOut>>,
|
|
}
|
|
|
|
impl Sampler {
|
|
pub fn new (
|
|
name: &str,
|
|
samples: Option<BTreeMap<u7, Arc<Sample>>>,
|
|
) -> Result<DynamicDevice<Self>, Box<dyn Error>> {
|
|
let (client, _) = Client::new(name, ClientOptions::NO_START_SERVER)?;
|
|
DynamicDevice::new(render, handle, Self::process, Self {
|
|
name: name.into(),
|
|
cursor: (0, 0),
|
|
samples: samples.unwrap_or(BTreeMap::new()),
|
|
voices: vec![],
|
|
midi_in: client.register_port("midi", ::jack::MidiIn::default())?,
|
|
audio_ins: vec![
|
|
client.register_port("recL", ::jack::AudioIn::default())?,
|
|
client.register_port("recR", ::jack::AudioIn::default())?,
|
|
],
|
|
audio_outs: vec![
|
|
client.register_port("outL", ::jack::AudioOut::default())?,
|
|
client.register_port("outR", ::jack::AudioOut::default())?,
|
|
],
|
|
}).activate(client)
|
|
}
|
|
|
|
pub fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
|
|
// Output buffer. This will be copied to the audio outs.
|
|
let channel_count = self.audio_outs.len();
|
|
let mut mixed = vec![vec![0.0;scope.n_frames() as usize];channel_count];
|
|
|
|
// Emit next chunk of each currently playing voice,
|
|
// dropping voices that have reached their ends.
|
|
let mut voices = vec![];
|
|
std::mem::swap(&mut voices, &mut self.voices);
|
|
loop {
|
|
if voices.len() < 1 {
|
|
break
|
|
}
|
|
let mut voice = voices.swap_remove(0);
|
|
if let Some(chunk) = voice.chunk(scope.n_frames() as usize) {
|
|
for (i, channel) in chunk.iter().enumerate() {
|
|
let buffer = &mut mixed[i % channel_count];
|
|
for (i, sample) in channel.iter().enumerate() {
|
|
buffer[i] += sample;
|
|
}
|
|
}
|
|
self.voices.push(voice);
|
|
}
|
|
}
|
|
|
|
// process midi in
|
|
// add new voices
|
|
// emit new voices starting from midi event frames
|
|
//
|
|
for (i, port) in self.audio_outs.iter_mut().enumerate() {
|
|
let buffer = &mixed[i];
|
|
for (i, value) in port.as_mut_slice(scope).iter_mut().enumerate() {
|
|
*value = *buffer.get(i).unwrap_or(&0.0);
|
|
}
|
|
}
|
|
Control::Continue
|
|
|
|
//for event in self.midi_in.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 self.samples.iter_mut() {
|
|
//if sample.trigger.0 == channel && sample.trigger.1 == note {
|
|
//sample.play(velocity);
|
|
//}
|
|
//}
|
|
//}
|
|
//for sample in self.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)
|
|
//}
|
|
//}
|
|
//}
|
|
//}
|
|
}
|
|
|
|
fn load_sample (&mut self, _path: &str) {}
|
|
}
|
|
|
|
impl PortList for Sampler {
|
|
fn midi_ins (&self) -> Usually<Vec<String>> {
|
|
Ok(vec![self.midi_in.name()?])
|
|
}
|
|
fn audio_ins (&self) -> Usually<Vec<String>> {
|
|
let mut ports = vec![];
|
|
for port in self.audio_ins.iter() {
|
|
ports.push(port.name()?);
|
|
}
|
|
Ok(ports)
|
|
}
|
|
fn audio_outs (&self) -> Usually<Vec<String>> {
|
|
let mut ports = vec![];
|
|
for port in self.audio_outs.iter() {
|
|
ports.push(port.name()?);
|
|
}
|
|
Ok(ports)
|
|
}
|
|
}
|
|
|
|
pub fn render (state: &Sampler, buf: &mut Buffer, Rect { x, y, height, .. }: Rect)
|
|
-> Usually<Rect>
|
|
{
|
|
let width = 40;
|
|
let style = Style::default().gray();
|
|
format!(" {} ({})", state.name, state.voices.len()).blit(buf, x+1, y, Some(style.white().bold().not_dim()));
|
|
for (i, (note, sample)) in state.samples.iter().enumerate() {
|
|
let style = if i == state.cursor.0 {
|
|
Style::default().green()
|
|
} else {
|
|
Style::default()
|
|
};
|
|
let i = i as u16;
|
|
let y1 = y+1+i;
|
|
if y1 >= y + height {
|
|
break
|
|
}
|
|
if i as usize == state.cursor.0 {
|
|
"⯈".blit(buf, x+1, y1, Some(style.bold()));
|
|
}
|
|
let label1 = format!("{note:3} {:10}", sample.name);
|
|
let label2 = format!("{:>7} {:>7}", sample.start, sample.end);
|
|
label1.blit(buf, x+2, y1, Some(style.bold()));
|
|
label2.blit(buf, x+3+label1.len()as u16, y1, Some(style));
|
|
}
|
|
Ok(Rect { x, y, width, height })
|
|
}
|
|
|
|
//fn render_table (
|
|
//state: &mut Sampler,
|
|
//stdout: &mut Stdout,
|
|
//offset: (u16, u16),
|
|
//) -> Result<(), Box<dyn Error>> {
|
|
//let move_to = |col, row| crossterm::cursor::MoveTo(offset.0 + col, offset.1 + row);
|
|
//stdout.queue(move_to(0, 3))?.queue(
|
|
//Print(" Name Rate Trigger Route")
|
|
//)?;
|
|
//for (i, sample) in state.samples.lock().unwrap().iter().enumerate() {
|
|
//let row = 4 + i as u16;
|
|
//for (j, (column, field)) in [
|
|
//(0, format!(" {:7} ", sample.name)),
|
|
//(9, format!(" {:.1}Hz ", sample.rate)),
|
|
//(18, format!(" MIDI C{} {} ", sample.trigger.0, sample.trigger.1)),
|
|
//(33, format!(" {:.1}dB -> Output ", sample.gain)),
|
|
//(50, format!(" {} ", sample.playing.unwrap_or(0))),
|
|
//].into_iter().enumerate() {
|
|
//stdout.queue(move_to(column, row))?;
|
|
//if state.selected_sample == i && state.selected_column == j {
|
|
//stdout.queue(PrintStyledContent(field.to_string().bold().reverse()))?;
|
|
//} else {
|
|
//stdout.queue(PrintStyledContent(field.to_string().bold()))?;
|
|
//}
|
|
//}
|
|
//}
|
|
//Ok(())
|
|
//}
|
|
|
|
//fn render_meters (
|
|
//state: &mut Sampler,
|
|
//stdout: &mut Stdout,
|
|
//offset: (u16, u16),
|
|
//) -> Result<(), Box<dyn Error>> {
|
|
//let move_to = |col, row| crossterm::cursor::MoveTo(offset.0 + col, offset.1 + row);
|
|
//for (i, sample) in state.samples.lock().iter().enumerate() {
|
|
//let row = 4 + i as u16;
|
|
//stdout.queue(move_to(32, row))?.queue(
|
|
//PrintStyledContent("▁".green())
|
|
//)?;
|
|
//}
|
|
//Ok(())
|
|
//}
|
|
|
|
pub fn handle (state: &mut Sampler, event: &AppEvent) -> Usually<bool> {
|
|
Ok(handle_keymap(state, event, KEYMAP)?)
|
|
}
|
|
pub const KEYMAP: &'static [KeyBinding<Sampler>] = keymap!(Sampler {
|
|
[Up, NONE, "cursor_up", "move cursor up", cursor_up],
|
|
[Down, NONE, "cursor_down", "move cursor down", cursor_down],
|
|
[Enter, NONE, "select", "select item under cursor", select],
|
|
});
|
|
fn cursor_up (state: &mut Sampler) -> Usually<bool> {
|
|
state.cursor.0 = if state.cursor.0 == 0 {
|
|
state.samples.len() - 1
|
|
} else {
|
|
state.cursor.0 - 1
|
|
};
|
|
Ok(true)
|
|
}
|
|
fn cursor_down (state: &mut Sampler) -> Usually<bool> {
|
|
state.cursor.0 = (state.cursor.0 + 1) % state.samples.len();
|
|
Ok(true)
|
|
}
|
|
fn select (state: &mut Sampler) -> Usually<bool> {
|
|
for (i, sample) in state.samples.values().enumerate() {
|
|
if i == state.cursor.0 {
|
|
state.voices.push(sample.play())
|
|
}
|
|
}
|
|
Ok(true)
|
|
}
|