mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 12:16:42 +01:00
scene and track colors; random_color_near
This commit is contained in:
parent
f500c717a2
commit
6bee5b0bcd
5 changed files with 80 additions and 38 deletions
|
|
@ -47,11 +47,17 @@ pub struct ArrangementTrack<E: Engine> {
|
||||||
pub outputs: Vec<()>,
|
pub outputs: Vec<()>,
|
||||||
/// Preferred width of track column
|
/// Preferred width of track column
|
||||||
pub width: usize,
|
pub width: usize,
|
||||||
|
/// Identifying color of track
|
||||||
|
pub color: Color,
|
||||||
}
|
}
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Scene {
|
pub struct Scene {
|
||||||
|
/// Name of scene
|
||||||
pub name: Arc<RwLock<String>>,
|
pub name: Arc<RwLock<String>>,
|
||||||
|
/// Clips in scene, one per track
|
||||||
pub clips: Vec<Option<Arc<RwLock<Phrase>>>>,
|
pub clips: Vec<Option<Arc<RwLock<Phrase>>>>,
|
||||||
|
/// Identifying color of scene
|
||||||
|
pub color: Color,
|
||||||
}
|
}
|
||||||
#[derive(PartialEq, Clone, Copy)]
|
#[derive(PartialEq, Clone, Copy)]
|
||||||
/// Represents the current user selection in the arranger
|
/// Represents the current user selection in the arranger
|
||||||
|
|
@ -199,8 +205,8 @@ impl<E: Engine> Arrangement<E> {
|
||||||
}
|
}
|
||||||
pub fn track_add (&mut self, name: Option<&str>) -> Usually<&mut ArrangementTrack<E>> {
|
pub fn track_add (&mut self, name: Option<&str>) -> Usually<&mut ArrangementTrack<E>> {
|
||||||
self.tracks.push(name.map_or_else(
|
self.tracks.push(name.map_or_else(
|
||||||
|| ArrangementTrack::new(&self.track_default_name()),
|
|| ArrangementTrack::new(&self.track_default_name(), None),
|
||||||
|name| ArrangementTrack::new(name),
|
|name| ArrangementTrack::new(name, None),
|
||||||
));
|
));
|
||||||
let index = self.tracks.len() - 1;
|
let index = self.tracks.len() - 1;
|
||||||
Ok(&mut self.tracks[index])
|
Ok(&mut self.tracks[index])
|
||||||
|
|
@ -240,8 +246,8 @@ impl<E: Engine> Arrangement<E> {
|
||||||
pub fn scene_add (&mut self, name: Option<&str>) -> Usually<&mut Scene> {
|
pub fn scene_add (&mut self, name: Option<&str>) -> Usually<&mut Scene> {
|
||||||
let clips = vec![None;self.tracks.len()];
|
let clips = vec![None;self.tracks.len()];
|
||||||
self.scenes.push(match name {
|
self.scenes.push(match name {
|
||||||
Some(name) => Scene::new(name, clips),
|
Some(name) => Scene::new(name, clips, None),
|
||||||
None => Scene::new(&self.scene_default_name(), clips),
|
None => Scene::new(&self.scene_default_name(), clips, None),
|
||||||
});
|
});
|
||||||
let index = self.scenes.len() - 1;
|
let index = self.scenes.len() - 1;
|
||||||
Ok(&mut self.scenes[index])
|
Ok(&mut self.scenes[index])
|
||||||
|
|
@ -344,13 +350,14 @@ impl<E: Engine> Arrangement<E> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<E: Engine> ArrangementTrack<E> {
|
impl<E: Engine> ArrangementTrack<E> {
|
||||||
pub fn new (name: &str) -> Self {
|
pub fn new (name: &str, color: Option<Color>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
name: Arc::new(RwLock::new(name.into())),
|
name: Arc::new(RwLock::new(name.into())),
|
||||||
inputs: vec![],
|
inputs: vec![],
|
||||||
player: PhrasePlayer::new(name),
|
player: PhrasePlayer::new(),
|
||||||
outputs: vec![],
|
outputs: vec![],
|
||||||
width: name.len() + 2,
|
width: name.len() + 2,
|
||||||
|
color: color.unwrap_or_else(random_color)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn longest_name (tracks: &[Self]) -> usize {
|
pub fn longest_name (tracks: &[Self]) -> usize {
|
||||||
|
|
@ -491,11 +498,13 @@ impl ArrangementViewMode {
|
||||||
impl Scene {
|
impl Scene {
|
||||||
pub fn new (
|
pub fn new (
|
||||||
name: impl AsRef<str>,
|
name: impl AsRef<str>,
|
||||||
clips: impl AsRef<[Option<Arc<RwLock<Phrase>>>]>
|
clips: impl AsRef<[Option<Arc<RwLock<Phrase>>>]>,
|
||||||
|
color: Option<Color>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
name: Arc::new(RwLock::new(name.as_ref().into())),
|
name: Arc::new(RwLock::new(name.as_ref().into())),
|
||||||
clips: clips.as_ref().iter().map(|x|x.clone()).collect(),
|
clips: clips.as_ref().iter().map(|x|x.clone()).collect(),
|
||||||
|
color: color.unwrap_or_else(random_color),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Returns the pulse length of the longest phrase in the scene
|
/// Returns the pulse length of the longest phrase in the scene
|
||||||
|
|
|
||||||
|
|
@ -169,14 +169,16 @@ impl<'a> Content for VerticalArranger<'a, Tui> {
|
||||||
//let height = rows.last().map(|(w,y)|(y+w)/PPQ).unwrap_or(16);
|
//let height = rows.last().map(|(w,y)|(y+w)/PPQ).unwrap_or(16);
|
||||||
let tracks: &[ArrangementTrack<Tui>] = state.tracks.as_ref();
|
let tracks: &[ArrangementTrack<Tui>] = state.tracks.as_ref();
|
||||||
let scenes: &[Scene] = state.scenes.as_ref();
|
let scenes: &[Scene] = state.scenes.as_ref();
|
||||||
let offset = 4 + Scene::longest_name(scenes) as u16;
|
let offset = 3 + Scene::longest_name(scenes) as u16;
|
||||||
let content = Layers::new(move |add|{
|
let content = Layers::new(move |add|{
|
||||||
let rows: &[(usize, usize)] = rows.as_ref();
|
let rows: &[(usize, usize)] = rows.as_ref();
|
||||||
let cols: &[(usize, usize)] = cols.as_ref();
|
let cols: &[(usize, usize)] = cols.as_ref();
|
||||||
|
|
||||||
let track_titles = row!((track, (w, _)) in tracks.iter().zip(cols) =>
|
let track_titles = row!((track, (w, _)) in tracks.iter().zip(cols) =>
|
||||||
(&track.name.read().unwrap().as_str() as &dyn Widget<Engine = Tui>)
|
(&track.name.read().unwrap().as_str() as &dyn Widget<Engine = Tui>)
|
||||||
.min_xy(*w as u16, 2).push_x(offset));
|
.min_xy(*w as u16, 2)
|
||||||
|
.bg(track.color)
|
||||||
|
.push_x(offset));
|
||||||
|
|
||||||
let scene_name = |scene, playing: bool, height|row!(
|
let scene_name = |scene, playing: bool, height|row!(
|
||||||
if playing { "▶ " } else { " " },
|
if playing { "▶ " } else { " " },
|
||||||
|
|
@ -186,7 +188,7 @@ impl<'a> Content for VerticalArranger<'a, Tui> {
|
||||||
let scene_clip = |scene, track: usize, w: u16, h: u16|Layers::new(move |add|{
|
let scene_clip = |scene, track: usize, w: u16, h: u16|Layers::new(move |add|{
|
||||||
let mut color = Color::Rgb(40, 50, 30);
|
let mut color = Color::Rgb(40, 50, 30);
|
||||||
match (tracks.get(track), (scene as &Scene).clips.get(track)) {
|
match (tracks.get(track), (scene as &Scene).clips.get(track)) {
|
||||||
(Some(track), Some(Some(phrase))) => {
|
(Some(_), Some(Some(phrase))) => {
|
||||||
let name = &(phrase as &Arc<RwLock<Phrase>>).read().unwrap().name;
|
let name = &(phrase as &Arc<RwLock<Phrase>>).read().unwrap().name;
|
||||||
let name = format!("{}", name);
|
let name = format!("{}", name);
|
||||||
add(&name.as_str().push_x(1).fixed_x(w))?;
|
add(&name.as_str().push_x(1).fixed_x(w))?;
|
||||||
|
|
@ -206,7 +208,7 @@ impl<'a> Content for VerticalArranger<'a, Tui> {
|
||||||
let height = 1.max((pulses / PPQ) as u16);
|
let height = 1.max((pulses / PPQ) as u16);
|
||||||
let playing = scene.is_playing(tracks);
|
let playing = scene.is_playing(tracks);
|
||||||
Stack::right(move |add| {
|
Stack::right(move |add| {
|
||||||
add(&scene_name(scene, playing, height))?;
|
add(&scene_name(scene, playing, height).bg(scene.color))?;
|
||||||
for (track, (w, _)) in cols.iter().enumerate() {
|
for (track, (w, _)) in cols.iter().enumerate() {
|
||||||
add(&scene_clip(scene, track, *w as u16, height))?;
|
add(&scene_clip(scene, track, *w as u16, height))?;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,3 +34,47 @@ tui_style!(STYLE_LABEL =
|
||||||
Some(Color::Reset), None, None, Modifier::empty(), Modifier::BOLD);
|
Some(Color::Reset), None, None, Modifier::empty(), Modifier::BOLD);
|
||||||
tui_style!(STYLE_VALUE =
|
tui_style!(STYLE_VALUE =
|
||||||
Some(Color::White), None, None, Modifier::BOLD, Modifier::DIM);
|
Some(Color::White), None, None, Modifier::BOLD, Modifier::DIM);
|
||||||
|
|
||||||
|
pub fn random_color () -> Color {
|
||||||
|
let mut rng = thread_rng();
|
||||||
|
let color: Okhsl<f32> = Okhsl::new(
|
||||||
|
rng.gen::<f32>() * 360f32 - 180f32,
|
||||||
|
rng.gen::<f32>(),
|
||||||
|
rng.gen::<f32>() * 0.5,
|
||||||
|
);
|
||||||
|
let color: Srgb<f32> = Srgb::from_color_unclamped(color);
|
||||||
|
Color::Rgb(
|
||||||
|
(color.red * 255.0) as u8,
|
||||||
|
(color.green * 255.0) as u8,
|
||||||
|
(color.blue * 255.0) as u8,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn random_color_near (color: Color, distance: f32) -> Color {
|
||||||
|
let (r, g, b) = if let Color::Rgb(r, g, b) = color {
|
||||||
|
(r, g, b)
|
||||||
|
} else {
|
||||||
|
panic!("random_color_near works only with Color::Rgb")
|
||||||
|
};
|
||||||
|
if distance > 1.0 {
|
||||||
|
panic!("random_color_near requires distance between 0.0 and 1.0");
|
||||||
|
}
|
||||||
|
let old: Okhsl<f32> = Okhsl::from([
|
||||||
|
r as f32 / 255.0,
|
||||||
|
g as f32 / 255.0,
|
||||||
|
b as f32 / 255.0,
|
||||||
|
]);
|
||||||
|
let mut rng = thread_rng();
|
||||||
|
let new: Okhsl<f32> = Okhsl::new(
|
||||||
|
rng.gen::<f32>() * 360f32 - 180f32,
|
||||||
|
rng.gen::<f32>(),
|
||||||
|
rng.gen::<f32>() * 0.66,
|
||||||
|
);
|
||||||
|
let mixed = new.mix(old, 0.5);
|
||||||
|
let color: Srgb<f32> = Srgb::from_color_unclamped(mixed);
|
||||||
|
Color::Rgb(
|
||||||
|
(color.red * 255.0) as u8,
|
||||||
|
(color.green * 255.0) as u8,
|
||||||
|
(color.blue * 255.0) as u8,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -158,20 +158,6 @@ impl<E: Engine> PhraseEditor<E> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn random_color () -> Color {
|
|
||||||
let mut rng = thread_rng();
|
|
||||||
let color: Okhsl<f32> = Okhsl::new(
|
|
||||||
rng.gen::<f32>() * 360f32 - 180f32,
|
|
||||||
rng.gen::<f32>() * 0.5 + 0.4,
|
|
||||||
rng.gen::<f32>() * 0.5 + 0.15,
|
|
||||||
);
|
|
||||||
let color: Srgb<f32> = Srgb::from_color_unclamped(color);
|
|
||||||
Color::Rgb(
|
|
||||||
(color.red * 255.0) as u8,
|
|
||||||
(color.green * 255.0) as u8,
|
|
||||||
(color.blue * 255.0) as u8,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
impl Phrase {
|
impl Phrase {
|
||||||
pub fn new (
|
pub fn new (
|
||||||
name: impl AsRef<str>,
|
name: impl AsRef<str>,
|
||||||
|
|
@ -257,19 +243,19 @@ impl std::cmp::PartialEq for Phrase {
|
||||||
}
|
}
|
||||||
impl Eq for Phrase {}
|
impl Eq for Phrase {}
|
||||||
impl<E: Engine> PhrasePlayer<E> {
|
impl<E: Engine> PhrasePlayer<E> {
|
||||||
pub fn new (name: &str) -> Self {
|
pub fn new () -> Self {
|
||||||
Self {
|
Self {
|
||||||
_engine: Default::default(),
|
_engine: Default::default(),
|
||||||
phrase: None,
|
phrase: None,
|
||||||
notes_in: Arc::new(RwLock::new([false;128])),
|
notes_in: Arc::new(RwLock::new([false;128])),
|
||||||
notes_out: Arc::new(RwLock::new([false;128])),
|
notes_out: Arc::new(RwLock::new([false;128])),
|
||||||
monitoring: false,
|
monitoring: false,
|
||||||
recording: false,
|
recording: false,
|
||||||
overdub: true,
|
overdub: true,
|
||||||
midi_out: None,
|
midi_out: None,
|
||||||
midi_out_buf: vec![Vec::with_capacity(16);16384],
|
midi_out_buf: vec![Vec::with_capacity(16);16384],
|
||||||
reset: true,
|
reset: true,
|
||||||
now: 0,
|
now: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn toggle_monitor (&mut self) {
|
pub fn toggle_monitor (&mut self) {
|
||||||
|
|
|
||||||
|
|
@ -120,7 +120,8 @@ impl Handle<Tui> for PhrasePool<Tui> {
|
||||||
self.phrase += 1;
|
self.phrase += 1;
|
||||||
},
|
},
|
||||||
key!(KeyCode::Char('d')) => { // insert duplicate
|
key!(KeyCode::Char('d')) => { // insert duplicate
|
||||||
let phrase = (*self.phrases[self.phrase].read().unwrap()).clone();
|
let mut phrase = (*self.phrases[self.phrase].read().unwrap()).clone();
|
||||||
|
phrase.color = random_color_near(phrase.color, 0.1);
|
||||||
self.phrases.insert(self.phrase + 1, Arc::new(RwLock::new(phrase)));
|
self.phrases.insert(self.phrase + 1, Arc::new(RwLock::new(phrase)));
|
||||||
self.phrase += 1;
|
self.phrase += 1;
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue