mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
load scenes from project
This commit is contained in:
parent
f1f812d0fb
commit
dff42ca5a7
5 changed files with 202 additions and 301 deletions
15
README.md
15
README.md
|
|
@ -1,3 +1,18 @@
|
|||
# tek
|
||||
|
||||
Tek is a MIDI sequencer, sampler, and plugin host for the Linux terminal.
|
||||
|
||||
## Requirements
|
||||
|
||||
* Linux
|
||||
* Rust toolchain
|
||||
* JACK or Pipewire
|
||||
|
||||
## Recommended
|
||||
|
||||
* MIDI controller
|
||||
* Samples
|
||||
|
||||
TODO:
|
||||
|
||||
* Focus transport to set BPM/sync/quant with `.,`
|
||||
|
|
|
|||
|
|
@ -1,5 +1,12 @@
|
|||
(bpm 150)
|
||||
|
||||
(scene { :name "Intro" } _ 0 _ _)
|
||||
(scene { :name "Hook" } 0 1 0 _)
|
||||
(scene { :name "Verse" } 2 2 1 _)
|
||||
(scene { :name "Chorus" } 1 3 2 _)
|
||||
(scene { :name "Bridge" } 3 4 3 _)
|
||||
(scene { :name "Outro" } _ 1 4 _)
|
||||
|
||||
(track { :name "Drums" :gain +0.0 }
|
||||
(phrase { :name "4 kicks" :beats 4 :steps 16 }
|
||||
(:00 (36 128))
|
||||
|
|
|
|||
84
src/edn.rs
84
src/edn.rs
|
|
@ -2,6 +2,17 @@ use crate::{core::*, model::*, App};
|
|||
|
||||
use clojure_reader::{edn::{read, Edn}, error::Error as EdnError};
|
||||
|
||||
macro_rules! edn {
|
||||
($edn:ident { $($pat:pat => $expr:expr),* $(,)? }) => {
|
||||
match $edn { $($pat => $expr),* }
|
||||
};
|
||||
($edn:ident in $args:ident { $($pat:pat => $expr:expr),* $(,)? }) => {
|
||||
for $edn in $args {
|
||||
edn!($edn { $($pat => $expr),* })
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl App {
|
||||
pub fn load_edn (&mut self, mut src: &str) -> Usually<()> {
|
||||
loop {
|
||||
|
|
@ -27,11 +38,7 @@ impl App {
|
|||
fn load_edn_one <'e> (&mut self, edn: Edn<'e>) -> Usually<()> {
|
||||
match edn {
|
||||
Edn::List(items) => {
|
||||
let head = items.get(0);
|
||||
match head {
|
||||
Some(Edn::Symbol("track")) => {
|
||||
Track::load_edn(self, &items[1..])?;
|
||||
},
|
||||
match items.get(0) {
|
||||
Some(Edn::Symbol("bpm")) => {
|
||||
match items.get(1) {
|
||||
Some(Edn::Int(b)) => self.timebase.set_bpm(*b as f64),
|
||||
|
|
@ -39,7 +46,13 @@ impl App {
|
|||
_ => panic!("unspecified bpm")
|
||||
}
|
||||
},
|
||||
_ => panic!("unexpected edn: {head:?}")
|
||||
Some(Edn::Symbol("scene")) => {
|
||||
Scene::load_edn(self, &items[1..])?;
|
||||
},
|
||||
Some(Edn::Symbol("track")) => {
|
||||
Track::load_edn(self, &items[1..])?;
|
||||
},
|
||||
_ => panic!("unexpected edn: {:?}", items.get(0))
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
|
|
@ -50,6 +63,35 @@ impl App {
|
|||
}
|
||||
}
|
||||
|
||||
impl Scene {
|
||||
fn load_edn <'a, 'e> (app: &'a mut App, args: &[Edn<'e>]) -> Usually<&'a mut Self> {
|
||||
let mut name = None;
|
||||
let mut clips = vec![];
|
||||
edn!(edn in args {
|
||||
Edn::Map(map) => {
|
||||
let key = map.get(&Edn::Key(":name"));
|
||||
if let Some(Edn::Str(n)) = key {
|
||||
name = Some(String::from(*n));
|
||||
} else {
|
||||
panic!("unexpected key in scene '{name:?}': {key:?}")
|
||||
}
|
||||
},
|
||||
Edn::Symbol("_") => {
|
||||
clips.push(None);
|
||||
},
|
||||
Edn::Int(i) => {
|
||||
clips.push(Some(*i as usize));
|
||||
},
|
||||
_ => panic!("unexpected in scene '{name:?}': {edn:?}")
|
||||
});
|
||||
app.add_scene_with_clips(name.as_deref(), &clips)
|
||||
//for edn in args {
|
||||
//match end {
|
||||
//}
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
impl Track {
|
||||
fn load_edn <'a, 'e> (app: &'a mut App, args: &[Edn<'e>]) -> Usually<&'a mut Self> {
|
||||
let ppq = app.timebase.ppq() as usize;
|
||||
|
|
@ -57,8 +99,7 @@ impl Track {
|
|||
let mut gain = 0.0f64;
|
||||
let mut devices: Vec<JackDevice> = vec![];
|
||||
let mut phrases: Vec<Phrase> = vec![];
|
||||
for edn in args {
|
||||
match edn {
|
||||
edn!(edn in args {
|
||||
Edn::Map(map) => {
|
||||
if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) {
|
||||
name = String::from(*n);
|
||||
|
|
@ -81,8 +122,7 @@ impl Track {
|
|||
_ => panic!("unexpected in track {name}: {:?}", args.get(0).unwrap())
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
});
|
||||
let (left, right) = (app.audio_out(0), app.audio_out(1));
|
||||
app.add_track_with_cb(Some(name.as_str()), move|_, track|{
|
||||
for phrase in phrases {
|
||||
|
|
@ -113,8 +153,7 @@ impl Phrase {
|
|||
let mut beats = 0usize;
|
||||
let mut steps = 0usize;
|
||||
let mut data = BTreeMap::new();
|
||||
for edn in args {
|
||||
match edn {
|
||||
edn!(edn in args {
|
||||
Edn::Map(map) => {
|
||||
if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) {
|
||||
name = String::from(*n);
|
||||
|
|
@ -159,8 +198,7 @@ impl Phrase {
|
|||
}
|
||||
},
|
||||
_ => panic!("unexpected in phrase '{name}': {edn:?}"),
|
||||
}
|
||||
}
|
||||
});
|
||||
Ok(Self::new(&name, beats * ppq, Some(data)))
|
||||
}
|
||||
}
|
||||
|
|
@ -170,8 +208,7 @@ impl Sampler {
|
|||
let mut name = String::new();
|
||||
let mut dir = String::new();
|
||||
let mut samples = BTreeMap::new();
|
||||
for edn in args {
|
||||
match edn {
|
||||
edn!(edn in args {
|
||||
Edn::Map(map) => {
|
||||
if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) {
|
||||
name = String::from(*n);
|
||||
|
|
@ -192,8 +229,7 @@ impl Sampler {
|
|||
_ => panic!("unexpected in sampler {name}: {args:?}")
|
||||
},
|
||||
_ => panic!("unexpected in sampler {name}: {edn:?}")
|
||||
}
|
||||
}
|
||||
});
|
||||
Self::new(&name, Some(samples))
|
||||
}
|
||||
}
|
||||
|
|
@ -204,8 +240,7 @@ impl Sample {
|
|||
let mut file = String::new();
|
||||
let mut midi = None;
|
||||
let mut start = 0usize;
|
||||
for edn in args.iter() {
|
||||
match edn {
|
||||
edn!(edn in args {
|
||||
Edn::Map(map) => {
|
||||
if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) {
|
||||
name = String::from(*n);
|
||||
|
|
@ -221,8 +256,7 @@ impl Sample {
|
|||
}
|
||||
},
|
||||
_ => panic!("unexpected in sample {name}"),
|
||||
}
|
||||
}
|
||||
});
|
||||
let (end, data) = read_sample_data(&format!("{dir}/{file}"))?;
|
||||
Ok((midi, Self::new(&name, start, end, data)))
|
||||
}
|
||||
|
|
@ -232,8 +266,7 @@ impl LV2Plugin {
|
|||
fn load_edn <'e> (args: &[Edn<'e>]) -> Usually<JackDevice> {
|
||||
let mut name = String::new();
|
||||
let mut path = String::new();
|
||||
for edn in args.iter() {
|
||||
match edn {
|
||||
edn!(edn in args {
|
||||
Edn::Map(map) => {
|
||||
if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) {
|
||||
name = String::from(*n);
|
||||
|
|
@ -243,8 +276,7 @@ impl LV2Plugin {
|
|||
}
|
||||
},
|
||||
_ => panic!("unexpected in lv2 '{name}'"),
|
||||
}
|
||||
}
|
||||
});
|
||||
Plugin::lv2(&name, &path)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
172
src/main.rs
172
src/main.rs
|
|
@ -22,9 +22,8 @@ use crate::{core::*, model::*};
|
|||
|
||||
/// Application entrypoint.
|
||||
pub fn main () -> Usually<()> {
|
||||
|
||||
// Construct app
|
||||
let mut app = App::default();
|
||||
|
||||
// Load config
|
||||
let xdg = Arc::new(microxdg::XdgApp::new("tek")?);
|
||||
app.xdg = Some(xdg.clone());
|
||||
|
|
@ -33,9 +32,7 @@ pub fn main () -> Usually<()> {
|
|||
}
|
||||
let midi_from = ["nanoKEY Studio.*capture.*"];
|
||||
let audio_into = ["Komplete.+:playback_FL", "Komplete.+:playback_FR"];
|
||||
|
||||
// Init
|
||||
let ppq = app.timebase.ppq() as usize;
|
||||
// Init view
|
||||
app.track_cursor = 1;
|
||||
app.scene_cursor = 1;
|
||||
app.note_start = 12;
|
||||
|
|
@ -45,13 +42,11 @@ pub fn main () -> Usually<()> {
|
|||
// Start main loop
|
||||
app.run(Some(|app: Arc<RwLock<App>>|{
|
||||
let mut state = app.write().unwrap();
|
||||
|
||||
// Start JACK and setup device graph
|
||||
let jack = jack_run("tek", &app)?;
|
||||
let client = jack.as_client();
|
||||
state.transport = Some(client.transport());
|
||||
state.midi_in = Some(client.register_port("midi-in", MidiIn)?);
|
||||
|
||||
let _ = midi_from
|
||||
.iter()
|
||||
.map(|name|client
|
||||
|
|
@ -65,7 +60,6 @@ pub fn main () -> Usually<()> {
|
|||
})
|
||||
.collect::<Usually<()>>())
|
||||
.collect::<Usually<()>>()?;
|
||||
|
||||
state.audio_outs = audio_into
|
||||
.iter()
|
||||
.map(|name|client
|
||||
|
|
@ -76,169 +70,9 @@ pub fn main () -> Usually<()> {
|
|||
.filter_map(|x|x)
|
||||
.map(Arc::new)
|
||||
.collect();
|
||||
|
||||
state.jack = Some(jack);
|
||||
|
||||
// Load project
|
||||
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([
|
||||
//sample!(34, "808", "/home/user/Lab/Music/pak/808.wav"),
|
||||
//sample!(35, "Kick1", "/home/user/Lab/Music/pak/kik.wav"),
|
||||
//sample!(36, "Kick2", "/home/user/Lab/Music/pak/kik2.wav"),
|
||||
//sample!(38, "Snare1", "/home/user/Lab/Music/pak/sna.wav"),
|
||||
//sample!(40, "Snare2", "/home/user/Lab/Music/pak/sna2.wav"),
|
||||
//sample!(42, "Hihat", "/home/user/Lab/Music/pak/chh.wav"),
|
||||
//sample!(44, "Hihat", "/home/user/Lab/Music/pak/chh2.wav"),
|
||||
//])))?, |track, device|{
|
||||
//device.connect_midi_in(0, &track.midi_out.clone_unowned())?;
|
||||
//if let Some(Some(left)) = audio_outs.get(0) {
|
||||
//device.connect_audio_out(0, left)?;
|
||||
//}
|
||||
//if let Some(Some(right)) = audio_outs.get(0) {
|
||||
//device.connect_audio_out(1, right)?;
|
||||
//}
|
||||
//Ok(())
|
||||
//})?;
|
||||
|
||||
////track.add_device_with_cb(Plugin::lv2(
|
||||
////"Panagement",
|
||||
////"file:///home/user/.lv2/Auburn Sounds Panagement 2.lv2"
|
||||
////)?, |track, device|{
|
||||
////device.connect_audio_in(0, &track.devices[0].audio_outs()?[0])?;
|
||||
////device.connect_audio_in(0, &track.devices[0].audio_outs()?[1])?;
|
||||
////if let Some(Some(left)) = audio_outs.get(0) {
|
||||
////device.connect_audio_out(0, left)?;
|
||||
////}
|
||||
////if let Some(Some(right)) = audio_outs.get(0) {
|
||||
////device.connect_audio_out(1, right)?;
|
||||
////}
|
||||
////Ok(())
|
||||
////})?;
|
||||
|
||||
//track.sequence = Some(1); // FIXME
|
||||
|
||||
//track.add_phrase("4 kicks", ppq * 4, Some(phrase! {
|
||||
//00 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
|
||||
//04 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
|
||||
//08 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
|
||||
//12 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
|
||||
//}));
|
||||
//track.add_phrase("5 kicks", ppq * 4, Some(phrase! {
|
||||
//00 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
|
||||
//04 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
|
||||
//08 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
|
||||
//12 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
|
||||
//14 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
|
||||
//}));
|
||||
//track.add_phrase("D-Beat", ppq * 4, Some(phrase! {
|
||||
//00 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
|
||||
//02 * ppq/4 => MidiMessage::NoteOn { key: 42.into(), vel: 100.into() },
|
||||
//04 * ppq/4 => MidiMessage::NoteOn { key: 42.into(), vel: 100.into() },
|
||||
//06 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
|
||||
//08 * ppq/4 => MidiMessage::NoteOn { key: 42.into(), vel: 100.into() },
|
||||
//10 * ppq/4 => MidiMessage::NoteOn { key: 42.into(), vel: 100.into() },
|
||||
//12 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
|
||||
//13 * ppq/4 => MidiMessage::NoteOn { key: 42.into(), vel: 100.into() },
|
||||
//14 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
|
||||
//15 * ppq/4 => MidiMessage::NoteOn { key: 42.into(), vel: 100.into() },
|
||||
//00 * ppq/4 => MidiMessage::NoteOn { key: 34.into(), vel: 100.into() },
|
||||
//04 * ppq/4 => MidiMessage::NoteOn { key: 38.into(), vel: 100.into() },
|
||||
//08 * ppq/4 => MidiMessage::NoteOn { key: 34.into(), vel: 100.into() },
|
||||
//10 * ppq/4 => MidiMessage::NoteOn { key: 35.into(), vel: 100.into() },
|
||||
//12 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() },
|
||||
//}));
|
||||
//track.add_phrase("Garage", ppq * 4, Some(phrase! {
|
||||
//00 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
|
||||
//01 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
|
||||
//02 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
|
||||
//03 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
|
||||
//04 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
|
||||
//06 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
|
||||
//07 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
|
||||
//09 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
|
||||
//10 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
|
||||
//12 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
|
||||
//14 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
|
||||
|
||||
//00 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
|
||||
//00 * ppq/4 => MidiMessage::NoteOn { key: 35.into(), vel: 100.into() },
|
||||
//02 * ppq/4 => MidiMessage::NoteOn { key: 34.into(), vel: 100.into() },
|
||||
//07 * ppq/4 => MidiMessage::NoteOn { key: 34.into(), vel: 100.into() },
|
||||
//04 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() },
|
||||
//11 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
|
||||
//11 * ppq/4 => MidiMessage::NoteOn { key: 35.into(), vel: 100.into() },
|
||||
//12 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() },
|
||||
//}));
|
||||
//Ok(())
|
||||
//})?;
|
||||
|
||||
//state.add_track_with_cb(Some("Bass"), |_, track|{
|
||||
//track.add_device_with_cb(Plugin::lv2(
|
||||
//"Odin2",
|
||||
//"file:///home/user/.lv2/Odin2.lv2"
|
||||
//)?, |track, device|{
|
||||
//device.connect_midi_in(0, &track.midi_out.clone_unowned())?;
|
||||
//if let Some(Some(left)) = audio_outs.get(0) {
|
||||
//device.connect_audio_out(0, left)?;
|
||||
//}
|
||||
//if let Some(Some(right)) = audio_outs.get(0) {
|
||||
//device.connect_audio_out(1, right)?;
|
||||
//}
|
||||
//Ok(())
|
||||
//})?;
|
||||
//track.sequence = Some(0); // FIXME
|
||||
//track.add_phrase("Offbeat", ppq * 4, Some(phrase! {
|
||||
////00 * ppq/4 => MidiMessage::NoteOff { key: 40.into(), vel: 100.into() },
|
||||
////02 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() },
|
||||
////04 * ppq/4 => MidiMessage::NoteOff { key: 40.into(), vel: 100.into() },
|
||||
////06 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() },
|
||||
////08 * ppq/4 => MidiMessage::NoteOff { key: 40.into(), vel: 100.into() },
|
||||
////10 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() },
|
||||
////12 * ppq/4 => MidiMessage::NoteOff { key: 40.into(), vel: 100.into() },
|
||||
////14 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() },
|
||||
//}));
|
||||
//track.add_phrase("Custom1", ppq * 4, None);
|
||||
//track.add_phrase("Custom2", ppq * 4, None);
|
||||
//track.add_phrase("Custom3", ppq * 4, None);
|
||||
//track.add_phrase("Custom4", ppq * 4, None);
|
||||
//Ok(())
|
||||
//})?;
|
||||
|
||||
//state.add_track_with_cb(Some("Lead"), |_, track|{
|
||||
//track.add_device_with_cb(Plugin::lv2(
|
||||
//"Odin2",
|
||||
//"file:///home/user/.lv2/Odin2.lv2"
|
||||
//)?, |track, device|{
|
||||
//device.connect_midi_in(0, &track.midi_out.clone_unowned())?;
|
||||
//if let Some(Some(left)) = audio_outs.get(0) {
|
||||
//device.connect_audio_out(0, left)?;
|
||||
//}
|
||||
//if let Some(Some(right)) = audio_outs.get(0) {
|
||||
//device.connect_audio_out(1, right)?;
|
||||
//}
|
||||
//Ok(())
|
||||
//})?;
|
||||
//track.sequence = Some(0); // FIXME
|
||||
//track.add_phrase("Custom0", ppq * 4, None);
|
||||
//track.add_phrase("Custom1", ppq * 4, None);
|
||||
//track.add_phrase("Custom2", ppq * 4, None);
|
||||
//track.add_phrase("Custom3", ppq * 4, None);
|
||||
//track.add_phrase("Custom4", ppq * 4, None);
|
||||
//Ok(())
|
||||
//})?;
|
||||
|
||||
state.scenes = vec![
|
||||
Scene::new("Intro", vec![None, Some(0), None, None]),
|
||||
Scene::new("Hook", vec![Some(0), Some(1), Some(0), None]),
|
||||
Scene::new("Verse", vec![Some(2), Some(2), Some(1), None]),
|
||||
Scene::new("Chorus", vec![Some(1), Some(3), Some(2), None]),
|
||||
Scene::new("Bridge", vec![Some(3), Some(4), Some(3), None]),
|
||||
Scene::new("Outro", vec![None, Some(1), Some(4), None]),
|
||||
];
|
||||
|
||||
Ok(())
|
||||
}))
|
||||
|
||||
}
|
||||
|
|
|
|||
15
src/model.rs
15
src/model.rs
|
|
@ -185,12 +185,25 @@ impl App {
|
|||
self.tracks.get_mut(id).map(|t|(id, t))
|
||||
} }
|
||||
}
|
||||
pub fn new_scene_name (&self) -> String {
|
||||
format!("Scene {}", self.scenes.len() + 1)
|
||||
}
|
||||
pub fn add_scene (&mut self, name: Option<&str>) -> Usually<&mut Scene> {
|
||||
let name = name.ok_or_else(||format!("Scene {}", self.scenes.len() + 1))?;
|
||||
let name = name.ok_or_else(||self.new_scene_name())?;
|
||||
self.scenes.push(Scene::new(&name, vec![]));
|
||||
self.scene_cursor = self.scenes.len();
|
||||
Ok(&mut self.scenes[self.scene_cursor - 1])
|
||||
}
|
||||
pub fn add_scene_with_clips (
|
||||
&mut self,
|
||||
name: Option<&str>,
|
||||
clips: &[Option<usize>]
|
||||
) -> Usually<&mut Scene> {
|
||||
let name = name.ok_or_else(||self.new_scene_name())?;
|
||||
self.scenes.push(Scene::new(&name, Vec::from(clips)));
|
||||
self.scene_cursor = self.scenes.len();
|
||||
Ok(&mut self.scenes[self.scene_cursor - 1])
|
||||
}
|
||||
pub fn scene (&self) -> Option<(usize, &Scene)> {
|
||||
match self.scene_cursor { 0 => None, _ => {
|
||||
let id = self.scene_cursor as usize - 1;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue