diff --git a/README.md b/README.md index 925e681d..3c26566d 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,9 @@ See `demos/project.edn` for the initial contents of the session. * Save project: * [ ] Preserve EDN layout * Samples: - * [ ] Sample browser + * [x] Sample browser + * [ ] Resample + * [ ] Repitch * [ ] Sample editor * [ ] Envelope * [ ] Stretch sample to BPM diff --git a/src/devices/sampler.rs b/src/devices/sampler.rs index 2dfc9424..4f8653c7 100644 --- a/src/devices/sampler.rs +++ b/src/devices/sampler.rs @@ -6,9 +6,9 @@ use crate::{core::*, view::*, model::MODAL}; pub struct Sampler { pub name: String, pub cursor: (usize, usize), - pub editing: Option>, - pub mapped: BTreeMap>, - pub unmapped: Vec>, + pub editing: Option>>, + pub mapped: BTreeMap>>, + pub unmapped: Vec>>, pub voices: Vec, pub ports: JackPorts, pub buffer: Vec>, @@ -31,7 +31,7 @@ render!(Sampler |self, buf, area| { break } let active = j == self.cursor.0; - width = width.max(draw_sample(buf, x, y + y1, note, sample, active)?); + width = width.max(draw_sample(buf, x, y + y1, note, &*sample.read().unwrap(), active)?); y1 = y1 + 1; j = j + 1; } @@ -75,7 +75,7 @@ pub const KEYMAP_SAMPLER: &'static [KeyBinding] = keymap!(Sampler { }], [Char('t'), NONE, "sample_play", "play current sample", |state: &mut Sampler| { if let Some(sample) = state.sample() { - state.voices.push(sample.play(0, &100.into())) + state.voices.push(Sample::play(sample, 0, &100.into())) } Ok(true) }], @@ -96,7 +96,7 @@ pub const KEYMAP_SAMPLER: &'static [KeyBinding] = keymap!(Sampler { process!(Sampler = Sampler::process); impl Sampler { - pub fn new (name: &str, mapped: Option>>) -> Usually { + pub fn new (name: &str, mapped: Option>>>) -> Usually { Jack::new(name)? .midi_in("midi") .audio_in("recL") @@ -117,12 +117,17 @@ impl Sampler { } /// Immutable reference to sample at cursor. - pub fn sample (&self) -> Option<&Arc> { + pub fn sample (&self) -> Option<&Arc>> { for (i, sample) in self.mapped.values().enumerate() { if i == self.cursor.0 { return Some(sample) } } + for (i, sample) in self.unmapped.iter().enumerate() { + if i + self.mapped.len() == self.cursor.0 { + return Some(sample) + } + } None } @@ -140,7 +145,7 @@ impl Sampler { if let LiveEvent::Midi { message, .. } = LiveEvent::parse(bytes).unwrap() { if let MidiMessage::NoteOn { ref key, ref vel } = message { if let Some(sample) = self.mapped.get(key) { - self.voices.push(sample.play(time as usize, vel)); + self.voices.push(Sample::play(sample, time as usize, vel)); } } } @@ -223,14 +228,16 @@ pub struct Sample { } impl Sample { - pub fn new (name: &str, start: usize, end: usize, channels: Vec>) -> Arc { - Arc::new(Self { name: name.to_string(), start, end, channels }) + pub fn new ( + name: &str, start: usize, end: usize, channels: Vec> + ) -> Arc> { + Arc::new(RwLock::new(Self { name: name.to_string(), start, end, channels })) } - pub fn play (self: &Arc, after: usize, velocity: &u7) -> Voice { + pub fn play (sample: &Arc>, after: usize, velocity: &u7) -> Voice { Voice { - sample: self.clone(), + sample: sample.clone(), after, - position: self.start, + position: sample.read().unwrap().start, velocity: velocity.as_int() as f32 / 127.0 } } @@ -238,7 +245,7 @@ impl Sample { /// A currently playing instance of a sample. pub struct Voice { - pub sample: Arc, + pub sample: Arc>, pub after: usize, pub position: usize, pub velocity: f32, @@ -251,12 +258,13 @@ impl Iterator for Voice { self.after = self.after - 1; return Some([0.0, 0.0]) } - if self.position < self.sample.end { + let sample = self.sample.read().unwrap(); + if self.position < sample.end { let position = self.position; self.position = self.position + 1; return Some([ - self.sample.channels[0][position] * self.velocity, - self.sample.channels[0][position] * self.velocity, + sample.channels[0][position] * self.velocity, + sample.channels[0][position] * self.velocity, ]) } None @@ -270,10 +278,12 @@ pub struct AddSampleModal { files: Vec, cursor: usize, offset: usize, - sample: Arc, + sample: Arc>, search: Option, } + exit!(AddSampleModal); + render!(AddSampleModal |self,buf,area|{ make_dim(buf); let area = center_box(area, 64, 20); @@ -300,14 +310,16 @@ render!(AddSampleModal |self,buf,area|{ } Lozenge(Style::default()).draw(buf, area) }); + handle!(AddSampleModal |self,e|{ if handle_keymap(self, e, KEYMAP_ADD_SAMPLE)? { return Ok(true) } Ok(true) }); + impl AddSampleModal { - fn new (sample: &Arc) -> Usually { + fn new (sample: &Arc>) -> Usually { let dir = std::env::current_dir()?; let (subdirs, files) = scan(&dir)?; Ok(Self { @@ -321,22 +333,6 @@ impl AddSampleModal { search: None }) } -} -fn scan (dir: &PathBuf) -> Usually<(Vec, Vec)> { - Ok(read_dir(dir)?.fold( - (vec!["..".into()], vec![]), - |(mut subdirs, mut files), entry|{ - let entry = entry.expect("failed to read drectory entry"); - let meta = entry.metadata().expect("failed to read entry metadata"); - if meta.is_file() { - files.push(entry.file_name()); - } else if meta.is_dir() { - subdirs.push(entry.file_name()); - } - (subdirs, files) - })) -} -impl AddSampleModal { fn rescan (&mut self) -> Usually<()> { scan(&self.dir).map(|(subdirs, files)|{ self.subdirs = subdirs; @@ -349,22 +345,35 @@ impl AddSampleModal { fn next (&mut self) { self.cursor = self.cursor + 1; } - fn pick (&mut self) -> Usually<()> { + fn pick (&mut self) -> Usually { if self.cursor == 0 { if let Some(parent) = self.dir.parent() { self.dir = parent.into(); self.rescan()?; + self.cursor = 0; + return Ok(false) } - } else if self.cursor < self.subdirs.len() { + } + if self.cursor < self.subdirs.len() { self.dir = self.dir.join(&self.subdirs[self.cursor]); self.rescan()?; - } else if (self.cursor - self.subdirs.len()) < self.files.len() { - - } else { + self.cursor = 0; + return Ok(false) } - Ok(()) + if (self.cursor - self.subdirs.len()) < self.files.len() { + let file = &self.files[self.cursor - self.subdirs.len()]; + let path = self.dir.join(file); + let (end, channels) = read_sample_data(&path.to_string_lossy())?; + let mut sample = self.sample.write().unwrap(); + sample.name = file.to_string_lossy().into(); + sample.end = end; + sample.channels = channels; + return Ok(true) + } + return Ok(false) } } + pub const KEYMAP_ADD_SAMPLE: &'static [KeyBinding] = keymap!(AddSampleModal { [Esc, NONE, "add_sample_close", "close help dialog", |modal: &mut AddSampleModal|{ modal.exit(); @@ -379,7 +388,24 @@ pub const KEYMAP_ADD_SAMPLE: &'static [KeyBinding] = keymap!(Add Ok(true) }], [Enter, NONE, "add_sample_enter", "activate selected entry", |modal: &mut AddSampleModal|{ - modal.pick()?; + if modal.pick()? { + modal.exit(); + } Ok(true) }], }); + +fn scan (dir: &PathBuf) -> Usually<(Vec, Vec)> { + Ok(read_dir(dir)?.fold( + (vec!["..".into()], vec![]), + |(mut subdirs, mut files), entry|{ + let entry = entry.expect("failed to read drectory entry"); + let meta = entry.metadata().expect("failed to read entry metadata"); + if meta.is_file() { + files.push(entry.file_name()); + } else if meta.is_dir() { + subdirs.push(entry.file_name()); + } + (subdirs, files) + })) +} diff --git a/src/edn.rs b/src/edn.rs index 42de374f..10200ed1 100644 --- a/src/edn.rs +++ b/src/edn.rs @@ -260,7 +260,7 @@ impl Sampler { if let Some(midi) = midi { samples.insert(midi, sample); } else { - panic!("sample without midi binding: {}", sample.name); + panic!("sample without midi binding: {}", sample.read().unwrap().name); } }, _ => panic!("unexpected in sampler {name}: {args:?}") @@ -272,7 +272,7 @@ impl Sampler { } impl Sample { - fn load_edn <'e> (dir: &str, args: &[Edn<'e>]) -> Usually<(Option, Arc)> { + fn load_edn <'e> (dir: &str, args: &[Edn<'e>]) -> Usually<(Option, Arc>)> { let mut name = String::new(); let mut file = String::new(); let mut midi = None;