Skip to content

Commit 6650878

Browse files
author
stephen
committed
macros AND scenes use single Plug with optional duration
1 parent fa89102 commit 6650878

8 files changed

Lines changed: 147 additions & 131 deletions

File tree

src/animation.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::time::{Duration, SystemTime};
22

3+
use egui::Color32;
34
use tween::{Tween, Tweener};
45

56
type StoredTweener = Tweener<f32, usize, Box<dyn Tween<f32>>>;
@@ -40,3 +41,17 @@ impl Animation {
4041
(self.get_value(), self.tweener.is_finished())
4142
}
4243
}
44+
45+
pub fn animate_colour(start_colour: &Color32, end_colour: &Color32, progress: f32) -> Color32 {
46+
// TODO: could just use an array here
47+
let r = linear_interpolate_u8(start_colour.r(), end_colour.r(), progress);
48+
let g = linear_interpolate_u8(start_colour.g(), end_colour.g(), progress);
49+
let b = linear_interpolate_u8(start_colour.b(), end_colour.b(), progress);
50+
let a = linear_interpolate_u8(start_colour.a(), end_colour.a(), progress);
51+
Color32::from_rgba_unmultiplied(r, g, b, a)
52+
}
53+
54+
fn linear_interpolate_u8(a: u8, b: u8, t: f32) -> u8 {
55+
let v = a as f32 + t * (b as f32 - a as f32);
56+
v as u8
57+
}

src/artnet.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ use std::{
44
};
55

66
use artnet_protocol::{ArtCommand, Output};
7-
use rand::{rngs::ThreadRng, Rng};
7+
use rand::Rng;
88

99
use crate::{
10-
project::{CMYChannels, ChannelMacro, FixtureInstance, RGBWChannels},
10+
project::{CMYChannels, FixtureInstance, RGBWChannels},
1111
settings::CHANNELS_PER_UNIVERSE,
1212
};
1313

src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::{net::SocketAddr, sync::mpsc};
22

33
use env_logger::Env;
4-
use log::{debug, error, info};
4+
use log::{debug, info};
55

66
use clap::Parser;
77

src/model.rs

Lines changed: 86 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
11
use std::{
2-
ops::Deref,
32
sync::mpsc::Receiver,
43
time::{Duration, SystemTime},
54
};
65

7-
use log::{debug, error, info, warn};
6+
use log::{debug, error, info};
87
use tween::SineInOut;
98

109
use crate::{
11-
animation::Animation,
10+
animation::{animate_colour, Animation},
1211
artnet::{random, zero, ArtNetInterface},
13-
project::{FixtureInstance, Project, Scene},
12+
project::{FixtureInstance, Project},
1413
settings::{Cli, CHANNELS_PER_UNIVERSE},
1514
tether_interface::{
16-
RemoteAnimationMessage, RemoteControlMessage, RemoteMacroMessage, RemoteMacroValue,
17-
RemoteSceneMessage, TetherControlChangePayload, TetherMidiMessage, TetherNotePayload,
15+
RemoteControlMessage, RemoteMacroMessage, RemoteMacroValue, RemoteSceneMessage,
16+
TetherMidiMessage, TetherNotePayload,
1817
},
1918
ui::{render_gui, ViewMode},
2019
};
@@ -95,11 +94,8 @@ impl Model {
9594
RemoteControlMessage::Midi(midi_msg) => {
9695
self.handle_midi_message(midi_msg);
9796
}
98-
RemoteControlMessage::MacroDirect(macro_msg) => {
99-
self.handle_macro_message(macro_msg);
100-
}
10197
RemoteControlMessage::MacroAnimation(animation_msg) => {
102-
self.handle_animation_message(animation_msg);
98+
self.handle_macro_message(animation_msg);
10399
}
104100
RemoteControlMessage::SceneAnimation(scene_msg) => {
105101
self.handle_scene_message(scene_msg);
@@ -152,8 +148,20 @@ impl Model {
152148
}
153149
}
154150
}
155-
crate::project::FixtureMacro::Colour(_) => {
156-
// Cannot animate Colour Macros (yet)
151+
crate::project::FixtureMacro::Colour(colour_macro) => {
152+
if let Some((animation, start_colour, end_colour)) =
153+
&mut colour_macro.animation
154+
{
155+
let (progress, is_done) = animation.get_value_and_done();
156+
colour_macro.current_value =
157+
animate_colour(start_colour, end_colour, progress);
158+
159+
// NB: Check if done AFTER applying value
160+
if is_done {
161+
debug!("Animation done; delete");
162+
colour_macro.animation = None;
163+
}
164+
}
157165
}
158166
}
159167
}
@@ -162,7 +170,7 @@ impl Model {
162170

163171
fn handle_midi_message(&mut self, m: TetherMidiMessage) {
164172
match m {
165-
TetherMidiMessage::Raw(_) => todo!(),
173+
// TetherMidiMessage::Raw(_) => todo!(),
166174
TetherMidiMessage::NoteOn(note) => {
167175
let TetherNotePayload {
168176
note,
@@ -174,13 +182,13 @@ impl Model {
174182
debug!("Note {} => macro group index {}", note, index);
175183
self.selected_macro_group_index = index as usize;
176184
}
177-
TetherMidiMessage::NoteOff(_) => todo!(),
178-
TetherMidiMessage::ControlChange(cc) => {
179-
let TetherControlChangePayload {
180-
channel: _,
181-
controller,
182-
value,
183-
} = cc;
185+
// TetherMidiMessage::NoteOff(_) => todo!(),
186+
TetherMidiMessage::ControlChange(_cc) => {
187+
// let TetherControlChangePayload {
188+
// channel: _,
189+
// controller,
190+
// value,
191+
// } = cc;
184192

185193
todo!();
186194

@@ -229,50 +237,7 @@ impl Model {
229237
}
230238
}
231239

232-
fn handle_macro_message(&mut self, msg: RemoteMacroMessage) {
233-
let target_fixtures = get_target_fixtures_list(&self.project.fixtures, &msg.fixture_label);
234-
235-
for (i, fixture) in self.project.fixtures.iter_mut().enumerate() {
236-
if target_fixtures.contains(&i) {
237-
if let Some(target_macro) =
238-
fixture
239-
.config
240-
.active_mode
241-
.macros
242-
.iter_mut()
243-
.find(|m| match m {
244-
crate::project::FixtureMacro::Control(control_macro) => {
245-
control_macro.label.eq_ignore_ascii_case(&msg.macro_label)
246-
}
247-
crate::project::FixtureMacro::Colour(colour_macro) => {
248-
colour_macro.label.eq_ignore_ascii_case(&msg.macro_label)
249-
}
250-
})
251-
{
252-
match target_macro {
253-
crate::project::FixtureMacro::Control(control_macro) => match msg.value {
254-
RemoteMacroValue::ControlValue(control_value) => {
255-
control_macro.current_value = control_value;
256-
}
257-
RemoteMacroValue::ColourValue(_) => {
258-
error!("Remote message targets Control Macro, but provided Colour Value");
259-
}
260-
},
261-
crate::project::FixtureMacro::Colour(colour_macro) => match msg.value {
262-
RemoteMacroValue::ColourValue(colour_value) => {
263-
colour_macro.current_value = colour_value;
264-
}
265-
RemoteMacroValue::ControlValue(_) => {
266-
error!("Remote message targets Colour Macro, but provided Control Value")
267-
}
268-
},
269-
}
270-
}
271-
}
272-
}
273-
}
274-
275-
pub fn handle_animation_message(&mut self, msg: RemoteAnimationMessage) {
240+
pub fn handle_macro_message(&mut self, msg: RemoteMacroMessage) {
276241
let target_fixtures = get_target_fixtures_list(&self.project.fixtures, &msg.fixture_label);
277242

278243
debug!(
@@ -299,34 +264,74 @@ impl Model {
299264
{
300265
match target_macro {
301266
crate::project::FixtureMacro::Control(control_macro) => {
302-
match msg.target_value {
267+
match msg.value {
303268
RemoteMacroValue::ControlValue(target_value) => {
304-
let start_value = control_macro.current_value as f32 / 255.0;
305-
let end_value = target_value as f32 / 255.0;
306-
let duration = Duration::from_millis(msg.duration);
269+
if let Some(ms) = msg.duration {
270+
let duration = Duration::from_millis(ms);
271+
let start_value =
272+
control_macro.current_value as f32 / 255.0;
273+
let end_value = target_value as f32 / 255.0;
274+
275+
control_macro.animation = Some(Animation::new(
276+
duration,
277+
start_value,
278+
end_value,
279+
Box::new(SineInOut),
280+
));
281+
282+
debug!(
283+
"Added Control Value animation with duration {}ms, {} -> {}",
284+
duration.as_millis(),
285+
start_value,
286+
end_value
287+
);
288+
} else {
289+
debug!(
290+
"No animation; immediately go to Control Macro value"
291+
);
292+
control_macro.animation = None; // cancel first
293+
control_macro.current_value = target_value;
294+
}
295+
}
296+
RemoteMacroValue::ColourValue(_) => {
297+
error!("Remote Animation Message targets Control Macro, but provides Colour Value instead");
298+
}
299+
}
300+
}
301+
crate::project::FixtureMacro::Colour(colour_macro) => match msg.value {
302+
RemoteMacroValue::ControlValue(_) => {
303+
error!("Remote Animation Message targets Colour Macro, but provices Control Value instead");
304+
}
305+
RemoteMacroValue::ColourValue(target_colour) => {
306+
if let Some(ms) = msg.duration {
307+
let duration = Duration::from_millis(ms);
308+
let start_value = 0.;
309+
let end_value = 1.0;
307310

308-
control_macro.animation = Some(Animation::new(
311+
let animation = Animation::new(
309312
duration,
310313
start_value,
311314
end_value,
312315
Box::new(SineInOut),
313-
));
316+
);
317+
let start_colour = colour_macro.current_value;
318+
let end_colour = target_colour;
314319

315320
debug!(
316-
"Added animation with duration {}ms, {} -> {}",
321+
"Added Colour animation with duration {}ms, {:?} => {:?}",
317322
duration.as_millis(),
318-
start_value,
319-
end_value
323+
start_colour,
324+
end_colour
320325
);
321-
}
322-
RemoteMacroValue::ColourValue(_) => {
323-
error!("Remote Animation Message targets Control Macro, but provides Colour Value");
326+
327+
colour_macro.animation =
328+
Some((animation, start_colour, end_colour));
329+
} else {
330+
debug!("No animation; immediately go to Colour Macro value");
331+
colour_macro.current_value = target_colour;
324332
}
325333
}
326-
}
327-
crate::project::FixtureMacro::Colour(_) => {
328-
warn!("Colour animations are not yet implemented!");
329-
}
334+
},
330335
}
331336
}
332337
}
@@ -476,7 +481,7 @@ fn get_target_fixtures_list(
476481
fixtures
477482
.iter()
478483
.enumerate()
479-
.filter(|(i, f)| {
484+
.filter(|(_i, f)| {
480485
if let Some(label) = label_search_string {
481486
f.label.eq_ignore_ascii_case(&label)
482487
} else {

src/project.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ pub struct ColourMacro {
107107
#[serde(skip, default = "default_rgb")]
108108
pub current_value: Color32,
109109
#[serde(skip)]
110-
pub animation: Option<Animation>,
110+
pub animation: Option<(Animation, Color32, Color32)>,
111111
}
112112

113113
impl Clone for ColourMacro {

src/tether_interface.rs

Lines changed: 5 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -31,30 +31,21 @@ pub enum RemoteMacroValue {
3131
#[derive(Serialize, Deserialize, Debug)]
3232
#[serde(rename_all = "camelCase")]
3333
pub struct RemoteMacroMessage {
34-
/// If no fixture specified, assume all
35-
pub fixture_label: Option<String>,
36-
pub macro_label: String,
37-
pub value: RemoteMacroValue,
38-
}
39-
40-
#[derive(Serialize, Deserialize, Debug)]
41-
#[serde(rename_all = "camelCase")]
42-
pub struct RemoteAnimationMessage {
4334
/// If no fixture specified, assume all
4435
pub fixture_label: Option<String>,
4536
pub macro_label: String,
4637
/// Start value will be "whatever the current value is";
4738
/// so `target_value` is the End value
48-
pub target_value: RemoteMacroValue,
39+
pub value: RemoteMacroValue,
4940
/// Animation duration in ms
50-
pub duration: u64,
41+
pub duration: Option<u64>,
5142
}
5243
#[derive(Debug)]
5344
pub enum TetherMidiMessage {
5445
/// Already-encoded payload
55-
Raw(Vec<u8>),
46+
// Raw(Vec<u8>),
5647
NoteOn(TetherNotePayload),
57-
NoteOff(TetherNotePayload),
48+
// NoteOff(TetherNotePayload),
5849
ControlChange(TetherControlChangePayload),
5950
}
6051

@@ -68,8 +59,7 @@ pub struct RemoteSceneMessage {
6859

6960
pub enum RemoteControlMessage {
7061
Midi(TetherMidiMessage),
71-
MacroDirect(RemoteMacroMessage),
72-
MacroAnimation(RemoteAnimationMessage),
62+
MacroAnimation(RemoteMacroMessage),
7363
SceneAnimation(RemoteSceneMessage),
7464
}
7565

@@ -90,10 +80,6 @@ pub fn start_tether_thread(tx: Sender<RemoteControlMessage>) -> JoinHandle<()> {
9080
.build(&tether_agent)
9181
.expect("failed to create Input Plug");
9282

93-
let input_animations = PlugOptionsBuilder::create_input("animations")
94-
.build(&tether_agent)
95-
.expect("failed to create Input Plug");
96-
9783
let input_scenes = PlugOptionsBuilder::create_input("scenes")
9884
.build(&tether_agent)
9985
.expect("failed to create Input Plug");
@@ -118,13 +104,6 @@ pub fn start_tether_thread(tx: Sender<RemoteControlMessage>) -> JoinHandle<()> {
118104
if input_macros.matches(&topic) {
119105
debug!("Macro (direct) control message");
120106
let m = rmp_serde::from_slice::<RemoteMacroMessage>(&message.payload()).unwrap();
121-
tx.send(RemoteControlMessage::MacroDirect(m))
122-
.expect("failed to send from Tether Interface thread");
123-
}
124-
if input_animations.matches(&topic) {
125-
debug!("Macro Animation control message");
126-
let m =
127-
rmp_serde::from_slice::<RemoteAnimationMessage>(&message.payload()).unwrap();
128107
tx.send(RemoteControlMessage::MacroAnimation(m))
129108
.expect("failed to send from Tether Interface thread");
130109
}

0 commit comments

Comments
 (0)