mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
wip: edn project format
This commit is contained in:
parent
14d9116c7c
commit
e2a842492e
12 changed files with 401 additions and 65 deletions
19
Cargo.lock
generated
19
Cargo.lock
generated
|
|
@ -212,6 +212,15 @@ version = "0.7.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
|
||||
|
||||
[[package]]
|
||||
name = "clojure-reader"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fe72db90a90a91de4a9fbd79542538caa0445ebdebcd3112589cab4c1e0e10b"
|
||||
dependencies = [
|
||||
"ordered-float",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cmake"
|
||||
version = "0.1.50"
|
||||
|
|
@ -680,6 +689,15 @@ version = "1.19.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||
|
||||
[[package]]
|
||||
name = "ordered-float"
|
||||
version = "4.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19ff2cf528c6c03d9ed653d6c4ce1dc0582dc4af309790ad92f07c1cd551b0be"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.11.2"
|
||||
|
|
@ -1032,6 +1050,7 @@ dependencies = [
|
|||
"backtrace",
|
||||
"better-panic",
|
||||
"clap",
|
||||
"clojure-reader",
|
||||
"crossterm",
|
||||
"fraction",
|
||||
"jack",
|
||||
|
|
|
|||
|
|
@ -23,3 +23,4 @@ atomic_float = "1.0.0"
|
|||
fraction = "0.15.3"
|
||||
rlsf = "0.2.1"
|
||||
r8brain-rs = "0.3.5"
|
||||
clojure-reader = "0.1.0"
|
||||
|
|
|
|||
13
README.md
13
README.md
|
|
@ -0,0 +1,13 @@
|
|||
TODO:
|
||||
|
||||
* Focus transport to set BPM/sync/quant with `.,`
|
||||
* Sample browser
|
||||
* Envelope
|
||||
* Stretch sample to PPM
|
||||
* Move clip/track/scene
|
||||
* Fix next/prev clip
|
||||
* needs_update
|
||||
* Buffer sequencer
|
||||
* Buffer chain view
|
||||
* Device ports
|
||||
* LV2 GUI
|
||||
67
demos/project.edn
Normal file
67
demos/project.edn
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
(track { :name "Drums" :gain +0.0 }
|
||||
|
||||
(sampler { :name "DrumKit1" :dir "/home/user/Lab/Music/pak" }
|
||||
(sample { :midi 34 :name "808" :file "808.wav" })
|
||||
(sample { :midi 35 :name "KC1" :file "kik.wav" })
|
||||
(sample { :midi 36 :name "KC2" :file "kik2.wav" })
|
||||
(sample { :midi 38 :name "SN1" :file "sna.wav" })
|
||||
(sample { :midi 40 :name "SN2" :file "sna2.wav" })
|
||||
(sample { :midi 42 :name "HH1" :file "chh.wav" })
|
||||
(sample { :midi 44 :name "HH2" :file "chh2.wav" }))
|
||||
|
||||
(phrase { :name "4 kicks" :beats 4 :steps 16 }
|
||||
(:00 (36 128))
|
||||
(:04 (36 128))
|
||||
(:08 (36 128))
|
||||
(:12 (36 128)))
|
||||
|
||||
)
|
||||
|
||||
;(phrase {
|
||||
;:name "5 kicks"
|
||||
;:beats 4
|
||||
;} (:00 (36 128))
|
||||
;(:04 (36 128))
|
||||
;(:08 (36 128))
|
||||
;(:12 (36 128))
|
||||
;(:14 (36 128)))
|
||||
|
||||
;(phrase {
|
||||
;:name "D Beat"
|
||||
;:beats 4
|
||||
;} (:00 (:44 :100) (:34 :100) (:35 :100))
|
||||
;(:02 (:42 :100) )
|
||||
;(:04 (:42 :080) (:38 :100) )
|
||||
;(:06 (:44 :120) )
|
||||
;(:08 (:42 :100) (:34 :100) (:35 :100))
|
||||
;(:10 (:42 :100) (:34 :100) (:35 :100))
|
||||
;(:12 (:44 :100) (:40 :100) )
|
||||
;(:13 (:44 :100) )
|
||||
;(:14 (:44 :100) )
|
||||
;(:15 (:42 :100) ))
|
||||
|
||||
;(phrase {
|
||||
;:name "Garage"
|
||||
;:beats 4
|
||||
;} (:00 (44 100) (36 100) (35 100))
|
||||
;(:01 (44 100))
|
||||
;(:02 (44 100) (35 100))
|
||||
;(:03 (44 100))
|
||||
;(:04 (44 100) (40 100))
|
||||
;(:06 (44 100))
|
||||
;(:07 (44 100) (34 100))
|
||||
;(:09 (44 100))
|
||||
;(:10 (44 100))
|
||||
;(:11 (35 100) (36 100))
|
||||
;(:12 (44 100) (40 100))
|
||||
;(:14 (44 100))))
|
||||
|
||||
;(track "Bass"
|
||||
;(lv2-plugin "Odin2")
|
||||
;(phrase { :name "Empty" :beats 4 })
|
||||
;(phrase { :name "Empty" :beats 4 }))
|
||||
|
||||
;(track "Lead"
|
||||
;(lv2-plugin "Odin2")
|
||||
;(phrase { :name "Empty" :beats 4 })
|
||||
;(phrase { :name "Empty" :beats 4 }))
|
||||
|
|
@ -34,13 +34,13 @@ handle!(App |self, e| {
|
|||
});
|
||||
|
||||
const KEYMAP_FOCUS: &'static [KeyBinding<App>] = keymap!(App {
|
||||
[Tab, NONE, "focus_next", "focus next area", focus_next],
|
||||
[Tab, SHIFT, "focus_prev", "focus previous area", focus_prev],
|
||||
[Esc, NONE, "focus_exit", "unfocus", |app: &mut App|{
|
||||
[Tab, NONE, "focus_next", "focus next area", focus_next],
|
||||
[Tab, SHIFT, "focus_prev", "focus previous area", focus_prev],
|
||||
[Esc, NONE, "focus_exit", "unfocus", |app: &mut App|{
|
||||
app.entered = false;
|
||||
Ok(true)
|
||||
}],
|
||||
[Enter, NONE, "focus_enter", "activate item at cursor", |app: &mut App|{
|
||||
[Enter, NONE, "focus_enter", "activate item at cursor", |app: &mut App|{
|
||||
app.entered = true;
|
||||
Ok(true)
|
||||
}],
|
||||
|
|
@ -50,8 +50,8 @@ const KEYMAP: &'static [KeyBinding<App>] = keymap!(App {
|
|||
|
||||
[F(1), NONE, "help_toggle", "toggle help", |_: &mut App| {Ok(true)}],
|
||||
|
||||
[Up, NONE, "focus_prev", "focus previous area", focus_prev],
|
||||
[Down, NONE, "focus_next", "focus next area", focus_next],
|
||||
[Up, NONE, "focus_prev", "focus previous area", focus_prev],
|
||||
[Down, NONE, "focus_next", "focus next area", focus_next],
|
||||
|
||||
[Char(' '), NONE, "play_toggle", "play or pause", |app: &mut App| {
|
||||
app.toggle_play()?;
|
||||
|
|
@ -175,13 +175,52 @@ const KEYMAP_GRID: &'static [KeyBinding<App>] = keymap!(App {
|
|||
true
|
||||
}
|
||||
)],
|
||||
[Char('.'), NONE, "grid_increment", "increment item at cursor", clip_next],
|
||||
[Char(','), NONE, "grid_decrement", "decrement item at cursor", clip_prev],
|
||||
[Char('.'), NONE, "grid_increment", "set next clip at cursor", |app: &mut App| {
|
||||
Ok(true)
|
||||
}],
|
||||
[Char(','), NONE, "grid_decrement", "set previous clip at cursor", |app: &mut App| {
|
||||
Ok(true)
|
||||
}],
|
||||
[Char('`'), NONE, "grid_mode_switch", "switch the display mode", |app: &mut App| {
|
||||
app.grid_mode = !app.seq_mode;
|
||||
Ok(true)
|
||||
}],
|
||||
});
|
||||
fn clip_next (_: &mut App) -> Usually<bool> { Ok(true) }
|
||||
//fn clip_next (state: &mut Launcher) -> Usually<bool> {
|
||||
//if state.cursor.0 >= 1 && state.cursor.1 >= 1 {
|
||||
//let scene_id = state.cursor.1 - 1;
|
||||
//let clip_id = state.cursor.0 - 1;
|
||||
//let scene = &mut state.scenes[scene_id];
|
||||
//scene.clips[clip_id] = match scene.clips[clip_id] {
|
||||
//None => Some(0),
|
||||
//Some(i) => if i >= state.tracks[clip_id].sequencer.phrases.len().saturating_sub(1) {
|
||||
//None
|
||||
//} else {
|
||||
//Some(i + 1)
|
||||
//}
|
||||
//};
|
||||
//}
|
||||
//Ok(true)
|
||||
//}
|
||||
|
||||
fn clip_prev (_: &mut App) -> Usually<bool> { Ok(true) }
|
||||
//fn clip_prev (state: &mut Launcher) -> Usually<bool> {
|
||||
//if state.cursor.0 >= 1 && state.cursor.1 >= 1 {
|
||||
//let scene_id = state.cursor.1 - 1;
|
||||
//let clip_id = state.cursor.0 - 1;
|
||||
//let scene = &mut state.scenes[scene_id];
|
||||
//scene.clips[clip_id] = match scene.clips[clip_id] {
|
||||
//None => Some(state.tracks[clip_id].sequencer.phrases.len().saturating_sub(1)),
|
||||
//Some(i) => if i == 0 {
|
||||
//None
|
||||
//} else {
|
||||
//Some(i - 1)
|
||||
//}
|
||||
//};
|
||||
//}
|
||||
//Ok(true)
|
||||
//}
|
||||
|
||||
const KEYMAP_SEQUENCER: &'static [KeyBinding<App>] = keymap!(App {
|
||||
[Up, NONE, "seq_cursor_up", "move cursor up", |app: &mut App| {
|
||||
|
|
@ -257,46 +296,11 @@ fn increment (app: &mut App) -> Usually<bool> {
|
|||
Ok(false)
|
||||
}
|
||||
|
||||
fn clip_next (_: &mut App) -> Usually<bool> { Ok(true) }
|
||||
//fn clip_next (state: &mut Launcher) -> Usually<bool> {
|
||||
//if state.cursor.0 >= 1 && state.cursor.1 >= 1 {
|
||||
//let scene_id = state.cursor.1 - 1;
|
||||
//let clip_id = state.cursor.0 - 1;
|
||||
//let scene = &mut state.scenes[scene_id];
|
||||
//scene.clips[clip_id] = match scene.clips[clip_id] {
|
||||
//None => Some(0),
|
||||
//Some(i) => if i >= state.tracks[clip_id].sequencer.phrases.len().saturating_sub(1) {
|
||||
//None
|
||||
//} else {
|
||||
//Some(i + 1)
|
||||
//}
|
||||
//};
|
||||
//}
|
||||
//Ok(true)
|
||||
//}
|
||||
|
||||
fn decrement (app: &mut App) -> Usually<bool> {
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn clip_prev (_: &mut App) -> Usually<bool> { Ok(true) }
|
||||
//fn clip_prev (state: &mut Launcher) -> Usually<bool> {
|
||||
//if state.cursor.0 >= 1 && state.cursor.1 >= 1 {
|
||||
//let scene_id = state.cursor.1 - 1;
|
||||
//let clip_id = state.cursor.0 - 1;
|
||||
//let scene = &mut state.scenes[scene_id];
|
||||
//scene.clips[clip_id] = match scene.clips[clip_id] {
|
||||
//None => Some(state.tracks[clip_id].sequencer.phrases.len().saturating_sub(1)),
|
||||
//Some(i) => if i == 0 {
|
||||
//None
|
||||
//} else {
|
||||
//Some(i - 1)
|
||||
//}
|
||||
//};
|
||||
//}
|
||||
//Ok(true)
|
||||
//}
|
||||
|
||||
fn delete (app: &mut App) -> Usually<bool> {
|
||||
match app.section {
|
||||
AppSection::Grid => delete_track(app),
|
||||
|
|
|
|||
222
src/edn.rs
Normal file
222
src/edn.rs
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
use crate::{core::*, model::*, App};
|
||||
|
||||
use clojure_reader::edn::Edn;
|
||||
|
||||
impl App {
|
||||
pub fn load_edn (&mut self, mut src: &str) {
|
||||
loop {
|
||||
match clojure_reader::edn::read(src) {
|
||||
Ok((edn, rest)) => {
|
||||
self.load_edn_one(edn);
|
||||
if rest.len() > 0 {
|
||||
src = rest;
|
||||
} else {
|
||||
break
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
panic!("{e:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fn load_edn_one <'e> (&mut self, edn: Edn<'e>) {
|
||||
match edn {
|
||||
Edn::List(items) => {
|
||||
let head = items.get(0);
|
||||
match head {
|
||||
Some(Edn::Symbol("track")) => Track::load_edn(self, &items[1..]),
|
||||
_ => panic!("unexpected edn: {head:?}")
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
panic!("unexpected edn: {edn:?}");
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl Track {
|
||||
fn load_edn <'a, 'e> (app: &'a mut App, items: &[Edn<'e>]) -> Usually<&'a mut Self> {
|
||||
let ppq = app.timebase.ppq() as usize;
|
||||
let mut name = String::new();
|
||||
let mut gain = 0.0f64;
|
||||
let mut devices: Vec<JackDevice> = vec![];
|
||||
let mut phrases: Vec<Phrase> = vec![];
|
||||
for edn in items[1..].iter() {
|
||||
match edn {
|
||||
Edn::Map(map) => {
|
||||
if let Some(Edn::Str(n)) = map.get(&Edn::Key("name")) {
|
||||
name = String::from(*n);
|
||||
}
|
||||
if let Some(Edn::Double(g)) = map.get(&Edn::Key("gain")) {
|
||||
gain = f64::from(*g)
|
||||
}
|
||||
},
|
||||
Edn::List(items) => match items.get(0) {
|
||||
Some(Edn::Symbol("phrase")) => {
|
||||
phrases.push(Phrase::load_edn(ppq, items)?)
|
||||
},
|
||||
Some(Edn::Symbol("sampler")) => {
|
||||
devices.push(Sampler::load_edn(items)?)
|
||||
},
|
||||
Some(Edn::Symbol("lv2")) => {
|
||||
devices.push(LV2Plugin::load_edn(items)?)
|
||||
},
|
||||
None => panic!("empty list track {name}"),
|
||||
_ => panic!("unexpected in track {name}: {:?}", items.get(0).unwrap())
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
app.add_track_with_cb(Some(name.as_str()), move|_, track|{
|
||||
for phrase in phrases {
|
||||
track.phrases.push(phrase);
|
||||
}
|
||||
for device in devices {
|
||||
track.add_device(device);
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Phrase {
|
||||
fn load_edn <'e> (ppq: usize, items: &[Edn<'e>]) -> Usually<Self> {
|
||||
let mut name = String::new();
|
||||
let mut beats = 0usize;
|
||||
let mut steps = 0usize;
|
||||
let mut data = BTreeMap::new();
|
||||
for edn in items[1..].iter() {
|
||||
match edn {
|
||||
Edn::Map(map) => {
|
||||
if let Some(Edn::Str(n)) = map.get(&Edn::Key("name")) {
|
||||
name = String::from(*n);
|
||||
}
|
||||
if let Some(Edn::Int(b)) = map.get(&Edn::Key("beats")) {
|
||||
beats = *b as usize;
|
||||
}
|
||||
if let Some(Edn::Int(s)) = map.get(&Edn::Key("steps")) {
|
||||
steps = *s as usize;
|
||||
}
|
||||
},
|
||||
Edn::List(items) => {
|
||||
let time = (match items.get(0) {
|
||||
Some(Edn::Symbol(text)) => text.parse::<f64>()?,
|
||||
Some(Edn::Int(i)) => *i as f64,
|
||||
Some(Edn::Double(f)) => f64::from(*f),
|
||||
_ => panic!("unexpected in phrase {name}: {:?}", items.get(0)),
|
||||
} * beats as f64 * ppq as f64 / steps as f64) as usize;
|
||||
for edn in items[1..].iter() {
|
||||
match edn {
|
||||
Edn::List(items) => if let (
|
||||
Some(Edn::Int(key)),
|
||||
Some(Edn::Int(vel)),
|
||||
) = (
|
||||
items.get(0),
|
||||
items.get(1),
|
||||
) {
|
||||
if !data.contains_key(&time) {
|
||||
data.insert(time, vec![]);
|
||||
}
|
||||
data.get_mut(&time).unwrap().push(MidiMessage::NoteOn {
|
||||
key: u7::from(*key as u8),
|
||||
vel: u7::from(*vel as u8),
|
||||
});
|
||||
} else {
|
||||
panic!("unexpected list in phrase {name}")
|
||||
},
|
||||
_ => panic!("unexpected in phrase {name}: {edn:?}")
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => panic!("unexpected in phrase {name}: {edn:?}"),
|
||||
}
|
||||
}
|
||||
Ok(Self::new(&name, beats * ppq, Some(data)))
|
||||
}
|
||||
}
|
||||
|
||||
impl Sampler {
|
||||
fn load_edn <'e> (items: &[Edn<'e>]) -> Usually<JackDevice> {
|
||||
let mut name = String::new();
|
||||
let mut dir = String::new();
|
||||
let mut samples = BTreeMap::new();
|
||||
for edn in items[1..].iter() {
|
||||
match edn {
|
||||
Edn::Map(map) => {
|
||||
if let Some(Edn::Str(n)) = map.get(&Edn::Key("name")) {
|
||||
name = String::from(*n);
|
||||
}
|
||||
if let Some(Edn::Str(n)) = map.get(&Edn::Key("dir")) {
|
||||
dir = String::from(*n);
|
||||
}
|
||||
},
|
||||
Edn::List(items) => match items.get(0) {
|
||||
Some(Edn::Symbol("sample")) => {
|
||||
let (midi, sample) = Sample::load_edn(&items[1..])?;
|
||||
if let Some(midi) = midi {
|
||||
samples.insert(midi, sample);
|
||||
} else {
|
||||
panic!("sample without midi binding: {}", sample.name);
|
||||
}
|
||||
},
|
||||
_ => panic!("unexpected in sampler {name}: {items:?}")
|
||||
},
|
||||
_ => panic!("unexpected in sampler {name}: {edn:?}")
|
||||
}
|
||||
}
|
||||
Self::new(&name, Some(samples))
|
||||
}
|
||||
}
|
||||
|
||||
impl Sample {
|
||||
fn load_edn <'e> (items: &[Edn<'e>]) -> Usually<(Option<u7>, Arc<Self>)> {
|
||||
let mut name = String::new();
|
||||
let mut file = String::new();
|
||||
let mut midi = None;
|
||||
let mut start = 0usize;
|
||||
for edn in items[1..].iter() {
|
||||
match edn {
|
||||
Edn::Map(map) => {
|
||||
if let Some(Edn::Str(n)) = map.get(&Edn::Key("name")) {
|
||||
name = String::from(*n);
|
||||
}
|
||||
if let Some(Edn::Str(f)) = map.get(&Edn::Key("file")) {
|
||||
file = String::from(*f);
|
||||
}
|
||||
if let Some(Edn::Int(i)) = map.get(&Edn::Key("start")) {
|
||||
start = *i as usize;
|
||||
}
|
||||
if let Some(Edn::Int(m)) = map.get(&Edn::Key("midi")) {
|
||||
midi = Some(u7::from(*m as u8));
|
||||
}
|
||||
},
|
||||
_ => panic!("unexpected in sample {name}"),
|
||||
}
|
||||
}
|
||||
let (end, data) = read_sample_data(&file)?;
|
||||
Ok((midi, Self::new(&name, start, end, data)))
|
||||
}
|
||||
}
|
||||
|
||||
impl LV2Plugin {
|
||||
fn load_edn <'e> (items: &[Edn<'e>]) -> Usually<JackDevice> {
|
||||
let mut name = String::new();
|
||||
let mut path = String::new();
|
||||
for edn in items[1..].iter() {
|
||||
match edn {
|
||||
Edn::Map(map) => {
|
||||
if let Some(Edn::Str(n)) = map.get(&Edn::Key("name")) {
|
||||
name = String::from(*n);
|
||||
}
|
||||
if let Some(Edn::Str(p)) = map.get(&Edn::Key("path")) {
|
||||
path = String::from(*p);
|
||||
}
|
||||
},
|
||||
_ => panic!("unexpected in sample {name}"),
|
||||
}
|
||||
}
|
||||
Plugin::lv2(&name, &path)
|
||||
}
|
||||
}
|
||||
|
|
@ -16,11 +16,13 @@ pub mod core;
|
|||
pub mod model;
|
||||
pub mod view;
|
||||
pub mod jack;
|
||||
pub mod edn;
|
||||
|
||||
use crate::{core::*, model::*};
|
||||
|
||||
/// Application entrypoint.
|
||||
pub fn main () -> Usually<()> {
|
||||
|
||||
let mut app = App::default();
|
||||
|
||||
// Load config
|
||||
|
|
@ -75,6 +77,8 @@ pub fn main () -> Usually<()> {
|
|||
|
||||
state.jack = Some(jack);
|
||||
|
||||
state.load_edn(include_str!("../demos/project.edn"));
|
||||
|
||||
state.add_track_with_cb(Some("Drums"), |_, track|{
|
||||
|
||||
track.add_device_with_cb(Sampler::new("Sampler", Some(BTreeMap::from([
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ pub mod track;
|
|||
pub use self::phrase::Phrase;
|
||||
pub use self::scene::Scene;
|
||||
pub use self::track::Track;
|
||||
pub use self::sampler::{Sampler, Sample};
|
||||
pub use self::sampler::{Sampler, Sample, read_sample_data};
|
||||
pub use self::mixer::Mixer;
|
||||
pub use self::plugin::{Plugin, PluginKind, lv2::LV2Plugin};
|
||||
|
||||
|
|
@ -158,7 +158,7 @@ impl App {
|
|||
pub fn add_track_with_cb (
|
||||
&mut self,
|
||||
name: Option<&str>,
|
||||
init: impl Fn(&Client, &mut Track)->Usually<()>,
|
||||
init: impl FnOnce(&Client, &mut Track)->Usually<()>,
|
||||
) -> Usually<&mut Track> {
|
||||
let name = name.ok_or_else(||format!("Track {}", self.tracks.len() + 1))?;
|
||||
let mut track = Track::new(&name, self.client(), None, None)?;
|
||||
|
|
|
|||
|
|
@ -114,22 +114,28 @@ impl Sampler {
|
|||
|
||||
/// Load sample from WAV and assign to MIDI note.
|
||||
#[macro_export] macro_rules! sample {
|
||||
($note:expr, $name:expr, $src:expr) => {
|
||||
{
|
||||
let mut channels: Vec<wavers::Samples<f32>> = vec![];
|
||||
for channel in wavers::Wav::from_path($src)?.channels() {
|
||||
channels.push(channel);
|
||||
}
|
||||
let mut end = 0;
|
||||
let mut data: Vec<Vec<f32>> = vec![];
|
||||
for samples in channels.iter() {
|
||||
let channel = Vec::from(samples.as_ref());
|
||||
end = end.max(channel.len());
|
||||
data.push(channel);
|
||||
}
|
||||
(u7::from_int_lossy($note).into(), Sample::new($name, 0, end, data).into())
|
||||
}
|
||||
};
|
||||
($note:expr, $name:expr, $src:expr) => {{
|
||||
let (end, data) = read_sample_data($src)?;
|
||||
(
|
||||
u7::from_int_lossy($note).into(),
|
||||
Sample::new($name, 0, end, data).into()
|
||||
)
|
||||
}};
|
||||
}
|
||||
|
||||
pub fn read_sample_data (src: &str) -> Usually<(usize, Vec<Vec<f32>>)> {
|
||||
let mut channels: Vec<wavers::Samples<f32>> = vec![];
|
||||
for channel in wavers::Wav::from_path(src)?.channels() {
|
||||
channels.push(channel);
|
||||
}
|
||||
let mut end = 0;
|
||||
let mut data: Vec<Vec<f32>> = vec![];
|
||||
for samples in channels.iter() {
|
||||
let channel = Vec::from(samples.as_ref());
|
||||
end = end.max(channel.len());
|
||||
data.push(channel);
|
||||
}
|
||||
Ok((end, data))
|
||||
}
|
||||
|
||||
pub struct Sample {
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ render!(App |self, buf, area| {
|
|||
Style::default().green()
|
||||
} else {
|
||||
Style::default().green().dim()
|
||||
}).draw(buf, Rect { x, y, width, height: chain.height })
|
||||
}).draw(buf, chain)
|
||||
}
|
||||
let phrase = self.draw_phrase(buf, Rect {
|
||||
x, y, width, height: height - height / 3
|
||||
|
|
|
|||
|
|
@ -131,7 +131,7 @@ impl<'a> SceneGridViewVertical<'a> {
|
|||
(0 == self.cursor.0) && (index + 1 == self.cursor.1)
|
||||
).bold());
|
||||
"⯈".blit(self.buf, x, y, style);
|
||||
scene.name.blit(self.buf, x + 1, y, style);
|
||||
scene.name.blit(self.buf, x + 2, y, style);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -155,11 +155,11 @@ mod horizontal {
|
|||
cell_bg_tick.set_style(bw);
|
||||
|
||||
let mut cell_a = Cell::default();
|
||||
cell_a.set_char('▀');
|
||||
cell_a.set_char('▄');
|
||||
cell_a.set_style(wh);
|
||||
|
||||
let mut cell_b = Cell::default();
|
||||
cell_b.set_char('▄');
|
||||
cell_b.set_char('▀');
|
||||
cell_b.set_style(wh);
|
||||
|
||||
let mut cell_ab = Cell::default();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue