mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
basic midi import example
This commit is contained in:
parent
35944cf684
commit
f5b1f495ad
8 changed files with 114 additions and 66 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -2,3 +2,4 @@ target
|
||||||
perf.data*
|
perf.data*
|
||||||
flamegraph*.svg
|
flamegraph*.svg
|
||||||
vgcore*
|
vgcore*
|
||||||
|
example.mid
|
||||||
|
|
|
||||||
18
crates/tek_api/examples/midi_import.rs
Normal file
18
crates/tek_api/examples/midi_import.rs
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
use tek_api::*;
|
||||||
|
|
||||||
|
struct ExamplePhrases(Vec<Arc<RwLock<Phrase>>>);
|
||||||
|
|
||||||
|
impl HasPhrases for ExamplePhrases {
|
||||||
|
fn phrases (&self) -> &Vec<Arc<RwLock<Phrase>>> {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
fn phrases_mut (&mut self) -> &mut Vec<Arc<RwLock<Phrase>>> {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main () -> Usually<()> {
|
||||||
|
let mut phrases = ExamplePhrases(vec![]);
|
||||||
|
PhrasePoolCommand::Import(0, String::from("./example.mid")).execute(&mut phrases)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
use tek_core::midly::Smf;
|
||||||
|
|
||||||
pub trait HasPhrases {
|
pub trait HasPhrases {
|
||||||
fn phrases (&self) -> &Vec<Arc<RwLock<Phrase>>>;
|
fn phrases (&self) -> &Vec<Arc<RwLock<Phrase>>>;
|
||||||
|
|
@ -10,11 +11,11 @@ pub enum PhrasePoolCommand {
|
||||||
Add(usize, Phrase),
|
Add(usize, Phrase),
|
||||||
Delete(usize),
|
Delete(usize),
|
||||||
Swap(usize, usize),
|
Swap(usize, usize),
|
||||||
Color(usize, ItemColor),
|
|
||||||
Import(usize, String),
|
Import(usize, String),
|
||||||
Export(usize, String),
|
Export(usize, String),
|
||||||
SetName(usize, String),
|
SetName(usize, String),
|
||||||
SetLength(usize, usize),
|
SetLength(usize, usize),
|
||||||
|
SetColor(usize, ItemColor),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: HasPhrases> Command<T> for PhrasePoolCommand {
|
impl<T: HasPhrases> Command<T> for PhrasePoolCommand {
|
||||||
|
|
@ -39,12 +40,26 @@ impl<T: HasPhrases> Command<T> for PhrasePoolCommand {
|
||||||
model.phrases_mut().swap(index, other);
|
model.phrases_mut().swap(index, other);
|
||||||
return Ok(Some(Self::Swap(index, other)))
|
return Ok(Some(Self::Swap(index, other)))
|
||||||
},
|
},
|
||||||
Self::Color(index, color) => {
|
|
||||||
let mut color = ItemColorTriplet::from(color);
|
|
||||||
std::mem::swap(&mut color, &mut model.phrases()[index].write().unwrap().color);
|
|
||||||
return Ok(Some(Self::Color(index, color.base)))
|
|
||||||
},
|
|
||||||
Self::Import(index, path) => {
|
Self::Import(index, path) => {
|
||||||
|
let bytes = std::fs::read(&path)?;
|
||||||
|
let smf = Smf::parse(bytes.as_slice())?;
|
||||||
|
println!("{:?}", &smf.header);
|
||||||
|
let mut t = 0u32;
|
||||||
|
let mut events = vec![];
|
||||||
|
for (i, track) in smf.tracks.iter().enumerate() {
|
||||||
|
for (j, event) in track.iter().enumerate() {
|
||||||
|
t += event.delta.as_int();
|
||||||
|
if let TrackEventKind::Midi { channel, message } = event.kind {
|
||||||
|
events.push((t, channel.as_int(), message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut phrase = Phrase::new(&path, true, t as usize + 1, None, None);
|
||||||
|
for event in events.iter() {
|
||||||
|
println!("{event:?}");
|
||||||
|
phrase.notes[event.0 as usize].push(event.2);
|
||||||
|
}
|
||||||
|
return Self::Add(index, phrase).execute(model)
|
||||||
},
|
},
|
||||||
Self::Export(index, path) => {
|
Self::Export(index, path) => {
|
||||||
},
|
},
|
||||||
|
|
@ -52,6 +67,11 @@ impl<T: HasPhrases> Command<T> for PhrasePoolCommand {
|
||||||
},
|
},
|
||||||
Self::SetLength(index, length) => {
|
Self::SetLength(index, length) => {
|
||||||
},
|
},
|
||||||
|
Self::SetColor(index, color) => {
|
||||||
|
let mut color = ItemColorTriplet::from(color);
|
||||||
|
std::mem::swap(&mut color, &mut model.phrases()[index].write().unwrap().color);
|
||||||
|
return Ok(Some(Self::SetColor(index, color.base)))
|
||||||
|
},
|
||||||
}
|
}
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,9 @@ pub trait HasPhrase: ClockApi {
|
||||||
fn reset (&self) -> bool;
|
fn reset (&self) -> bool;
|
||||||
fn reset_mut (&mut self) -> &mut bool;
|
fn reset_mut (&mut self) -> &mut bool;
|
||||||
|
|
||||||
fn phrase (&self)
|
fn play_phrase (&self)
|
||||||
-> &Option<(Instant, Option<Arc<RwLock<Phrase>>>)>;
|
-> &Option<(Instant, Option<Arc<RwLock<Phrase>>>)>;
|
||||||
fn phrase_mut (&mut self)
|
fn play_phrase_mut (&mut self)
|
||||||
-> &mut Option<(Instant, Option<Arc<RwLock<Phrase>>>)>;
|
-> &mut Option<(Instant, Option<Arc<RwLock<Phrase>>>)>;
|
||||||
|
|
||||||
fn next_phrase (&self)
|
fn next_phrase (&self)
|
||||||
|
|
@ -29,7 +29,7 @@ pub trait HasPhrase: ClockApi {
|
||||||
*self.reset_mut() = true;
|
*self.reset_mut() = true;
|
||||||
}
|
}
|
||||||
fn pulses_since_start (&self) -> Option<f64> {
|
fn pulses_since_start (&self) -> Option<f64> {
|
||||||
if let Some((started, Some(_))) = self.phrase().as_ref() {
|
if let Some((started, Some(_))) = self.play_phrase().as_ref() {
|
||||||
Some(self.current().pulse.get() - started.pulse.get())
|
Some(self.current().pulse.get() - started.pulse.get())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|
@ -71,7 +71,7 @@ pub trait MidiInputApi: ClockApi + HasPhrase {
|
||||||
let sample0 = scope.last_frame_time() as usize;
|
let sample0 = scope.last_frame_time() as usize;
|
||||||
// For highlighting keys and note repeat
|
// For highlighting keys and note repeat
|
||||||
let notes_in = self.notes_in().clone();
|
let notes_in = self.notes_in().clone();
|
||||||
if let (true, Some((started, ref phrase))) = (self.is_rolling(), self.phrase().clone()) {
|
if let (true, Some((started, ref phrase))) = (self.is_rolling(), self.play_phrase().clone()) {
|
||||||
let start = started.sample.get() as usize;
|
let start = started.sample.get() as usize;
|
||||||
let quant = self.quant().get();
|
let quant = self.quant().get();
|
||||||
let timebase = self.timebase().clone();
|
let timebase = self.timebase().clone();
|
||||||
|
|
@ -166,8 +166,8 @@ pub trait MidiOutputApi: ClockApi + HasPhrase {
|
||||||
let sample0 = scope.last_frame_time() as usize;
|
let sample0 = scope.last_frame_time() as usize;
|
||||||
let samples = scope.n_frames() as usize;
|
let samples = scope.n_frames() as usize;
|
||||||
// If no phrase is playing, prepare for switchover immediately
|
// If no phrase is playing, prepare for switchover immediately
|
||||||
next = self.phrase().is_none();
|
next = self.play_phrase().is_none();
|
||||||
let phrase = self.phrase();
|
let phrase = self.play_phrase();
|
||||||
let started0 = self.transport_offset();
|
let started0 = self.transport_offset();
|
||||||
let timebase = self.timebase();
|
let timebase = self.timebase();
|
||||||
let notes_out = self.notes_out();
|
let notes_out = self.notes_out();
|
||||||
|
|
@ -241,7 +241,7 @@ pub trait MidiOutputApi: ClockApi + HasPhrase {
|
||||||
let skipped = sample0 - start;
|
let skipped = sample0 - start;
|
||||||
// Switch over to enqueued phrase
|
// Switch over to enqueued phrase
|
||||||
let started = Instant::from_sample(&self.timebase(), start as f64);
|
let started = Instant::from_sample(&self.timebase(), start as f64);
|
||||||
*self.phrase_mut() = Some((started, phrase.clone()));
|
*self.play_phrase_mut() = Some((started, phrase.clone()));
|
||||||
// Unset enqueuement (TODO: where to implement looping?)
|
// Unset enqueuement (TODO: where to implement looping?)
|
||||||
*self.next_phrase_mut() = None
|
*self.next_phrase_mut() = None
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@ pub trait ArrangerSceneApi: Sized {
|
||||||
Some(clip) => tracks
|
Some(clip) => tracks
|
||||||
.get(track_index)
|
.get(track_index)
|
||||||
.map(|track|{
|
.map(|track|{
|
||||||
if let Some((_, Some(phrase))) = track.phrase() {
|
if let Some((_, Some(phrase))) = track.play_phrase() {
|
||||||
*phrase.read().unwrap() == *clip.read().unwrap()
|
*phrase.read().unwrap() == *clip.read().unwrap()
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
|
|
|
||||||
|
|
@ -57,10 +57,10 @@ macro_rules! impl_midi_player {
|
||||||
fn reset_mut (&mut self) -> &mut bool {
|
fn reset_mut (&mut self) -> &mut bool {
|
||||||
&mut self$(.$field)*.reset
|
&mut self$(.$field)*.reset
|
||||||
}
|
}
|
||||||
fn phrase (&self) -> &Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
|
fn play_phrase (&self) -> &Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
|
||||||
&self$(.$field)*.play_phrase
|
&self$(.$field)*.play_phrase
|
||||||
}
|
}
|
||||||
fn phrase_mut (&mut self) -> &mut Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
|
fn play_phrase_mut (&mut self) -> &mut Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
|
||||||
&mut self$(.$field)*.play_phrase
|
&mut self$(.$field)*.play_phrase
|
||||||
}
|
}
|
||||||
fn next_phrase (&self) -> &Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
|
fn next_phrase (&self) -> &Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
|
||||||
|
|
|
||||||
|
|
@ -97,18 +97,20 @@ fn to_sequencer_command (state: &SequencerTui, input: &TuiInput) -> Option<Seque
|
||||||
AppFocus::Menu => {
|
AppFocus::Menu => {
|
||||||
todo!()
|
todo!()
|
||||||
},
|
},
|
||||||
AppFocus::Content(SequencerFocus::Transport(_)) => {
|
AppFocus::Content(focused) => match focused {
|
||||||
match TransportCommand::input_to_command(state, input)? {
|
SequencerFocus::Transport(_) => {
|
||||||
TransportCommand::Clock(_) => { todo!() },
|
match TransportCommand::input_to_command(state, input)? {
|
||||||
TransportCommand::Focus(command) => Cmd::Focus(command),
|
TransportCommand::Clock(_) => { todo!() },
|
||||||
}
|
TransportCommand::Focus(command) => Cmd::Focus(command),
|
||||||
},
|
}
|
||||||
AppFocus::Content(SequencerFocus::Phrases) => {
|
},
|
||||||
Cmd::Phrases(PhrasesCommand::input_to_command(state, input)?)
|
SequencerFocus::Phrases => {
|
||||||
},
|
Cmd::Phrases(PhrasesCommand::input_to_command(state, input)?)
|
||||||
AppFocus::Content(SequencerFocus::PhraseEditor) => {
|
},
|
||||||
Cmd::Editor(PhraseCommand::input_to_command(state, input)?)
|
SequencerFocus::PhraseEditor => {
|
||||||
},
|
Cmd::Editor(PhraseCommand::input_to_command(state, input)?)
|
||||||
|
},
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -118,43 +120,47 @@ fn to_arranger_command (state: &ArrangerTui, input: &TuiInput) -> Option<Arrange
|
||||||
return None
|
return None
|
||||||
}
|
}
|
||||||
Some(match state.focused() {
|
Some(match state.focused() {
|
||||||
AppFocus::Menu => todo!(),
|
AppFocus::Menu => {
|
||||||
AppFocus::Content(ArrangerFocus::Transport(_)) => {
|
todo!()
|
||||||
use TransportCommand::{Clock, Focus};
|
|
||||||
match TransportCommand::input_to_command(state, input)? {
|
|
||||||
Clock(_) => { todo!() },
|
|
||||||
Focus(command) => Cmd::Focus(command)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
AppFocus::Content(ArrangerFocus::PhraseEditor) => {
|
AppFocus::Content(focused) => match focused {
|
||||||
Cmd::Editor(PhraseCommand::input_to_command(state, input)?)
|
ArrangerFocus::Transport(_) => {
|
||||||
},
|
use TransportCommand::{Clock, Focus};
|
||||||
AppFocus::Content(ArrangerFocus::Phrases) => match input.event() {
|
match TransportCommand::input_to_command(state, input)? {
|
||||||
key!(KeyCode::Char('e')) => {
|
Clock(_) => { todo!() },
|
||||||
Cmd::EditPhrase(state.phrase_editing().clone())
|
Focus(command) => Cmd::Focus(command)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
_ => {
|
ArrangerFocus::PhraseEditor => {
|
||||||
Cmd::Phrases(PhrasesCommand::input_to_command(state, input)?)
|
Cmd::Editor(PhraseCommand::input_to_command(state, input)?)
|
||||||
}
|
},
|
||||||
},
|
ArrangerFocus::Phrases => match input.event() {
|
||||||
AppFocus::Content(ArrangerFocus::Arranger) => {
|
key!(KeyCode::Char('e')) => {
|
||||||
use ArrangerSelection::*;
|
Cmd::EditPhrase(state.phrase_editing().clone())
|
||||||
use KeyCode::Char;
|
},
|
||||||
match input.event() {
|
_ => {
|
||||||
key!(Char('e')) => Cmd::EditPhrase(state.phrase_editing().clone()),
|
Cmd::Phrases(PhrasesCommand::input_to_command(state, input)?)
|
||||||
key!(Char('l')) => Cmd::Clip(ArrangerClipCommand::SetLoop(false)),
|
}
|
||||||
key!(Char('+')) => Cmd::Zoom(0), // TODO
|
},
|
||||||
key!(Char('=')) => Cmd::Zoom(0), // TODO
|
ArrangerFocus::Arranger => {
|
||||||
key!(Char('_')) => Cmd::Zoom(0), // TODO
|
use ArrangerSelection::*;
|
||||||
key!(Char('-')) => Cmd::Zoom(0), // TODO
|
use KeyCode::Char;
|
||||||
key!(Char('`')) => { todo!("toggle state mode") },
|
match input.event() {
|
||||||
key!(Ctrl-Char('a')) => Cmd::Scene(ArrangerSceneCommand::Add),
|
key!(Char('e')) => Cmd::EditPhrase(state.phrase_editing().clone()),
|
||||||
key!(Ctrl-Char('t')) => Cmd::Track(ArrangerTrackCommand::Add),
|
key!(Char('l')) => Cmd::Clip(ArrangerClipCommand::SetLoop(false)),
|
||||||
_ => match state.selected() {
|
key!(Char('+')) => Cmd::Zoom(0), // TODO
|
||||||
Mix => to_arranger_mix_command(input)?,
|
key!(Char('=')) => Cmd::Zoom(0), // TODO
|
||||||
Track(t) => to_arranger_track_command(input, t)?,
|
key!(Char('_')) => Cmd::Zoom(0), // TODO
|
||||||
Scene(s) => to_arranger_scene_command(input, s)?,
|
key!(Char('-')) => Cmd::Zoom(0), // TODO
|
||||||
Clip(t, s) => to_arranger_clip_command(input, t, s)?,
|
key!(Char('`')) => { todo!("toggle state mode") },
|
||||||
|
key!(Ctrl-Char('a')) => Cmd::Scene(ArrangerSceneCommand::Add),
|
||||||
|
key!(Ctrl-Char('t')) => Cmd::Track(ArrangerTrackCommand::Add),
|
||||||
|
_ => match state.selected() {
|
||||||
|
Mix => to_arranger_mix_command(input)?,
|
||||||
|
Track(t) => to_arranger_track_command(input, t)?,
|
||||||
|
Scene(s) => to_arranger_scene_command(input, s)?,
|
||||||
|
Clip(t, s) => to_arranger_clip_command(input, t, s)?,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -288,7 +294,10 @@ impl<T: PhrasesControl> InputToCommand<Tui, T> for PhrasesCommand {
|
||||||
index + 1,
|
index + 1,
|
||||||
state.phrases()[index].read().unwrap().duplicate()
|
state.phrases()[index].read().unwrap().duplicate()
|
||||||
)),
|
)),
|
||||||
key!(Char('c')) => Self::Phrase(Pool::Color(index, ItemColor::random())),
|
key!(Char('c')) => Self::Phrase(Pool::SetColor(
|
||||||
|
index,
|
||||||
|
ItemColor::random()
|
||||||
|
)),
|
||||||
key!(Char('n')) => Self::Rename(Rename::Begin),
|
key!(Char('n')) => Self::Rename(Rename::Begin),
|
||||||
key!(Char('t')) => Self::Length(Length::Begin),
|
key!(Char('t')) => Self::Length(Length::Begin),
|
||||||
_ => match state.phrases_mode() {
|
_ => match state.phrases_mode() {
|
||||||
|
|
|
||||||
|
|
@ -222,7 +222,7 @@ pub fn arranger_content_vertical (
|
||||||
let name = format!("▎{}", &name[0..max_w]);
|
let name = format!("▎{}", &name[0..max_w]);
|
||||||
let name = TuiStyle::bold(name, true);
|
let name = TuiStyle::bold(name, true);
|
||||||
// beats elapsed
|
// beats elapsed
|
||||||
let elapsed = if let Some((_, Some(phrase))) = track.phrase().as_ref() {
|
let elapsed = if let Some((_, Some(phrase))) = track.play_phrase().as_ref() {
|
||||||
let length = phrase.read().unwrap().length;
|
let length = phrase.read().unwrap().length;
|
||||||
let elapsed = track.pulses_since_start().unwrap();
|
let elapsed = track.pulses_since_start().unwrap();
|
||||||
let elapsed = timebase.format_beats_1_short(
|
let elapsed = timebase.format_beats_1_short(
|
||||||
|
|
@ -282,7 +282,7 @@ pub fn arranger_content_vertical (
|
||||||
let color = phrase.read().unwrap().color;
|
let color = phrase.read().unwrap().color;
|
||||||
add(&name.as_str()[0..max_w].push_x(1).fixed_x(w as u16))?;
|
add(&name.as_str()[0..max_w].push_x(1).fixed_x(w as u16))?;
|
||||||
bg = color.dark.rgb;
|
bg = color.dark.rgb;
|
||||||
if let Some((_, Some(ref playing))) = track.phrase() {
|
if let Some((_, Some(ref playing))) = track.play_phrase() {
|
||||||
if *playing.read().unwrap() == *phrase.read().unwrap() {
|
if *playing.read().unwrap() == *phrase.read().unwrap() {
|
||||||
bg = color.light.rgb
|
bg = color.light.rgb
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue