Skip to content

Commit ee5148c

Browse files
committed
feat(nodes/timecode): add ltc decode node
1 parent 60ccbd5 commit ee5148c

File tree

7 files changed

+136
-2
lines changed

7 files changed

+136
-2
lines changed

Cargo.lock

Lines changed: 10 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/runtime/clock/src/lib.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,15 @@ impl Timecode {
241241
let frames = frames.round() as u64;
242242
Self::build(frames, false, fps as u64)
243243
}
244+
245+
pub fn to_duration(&self, fps: f64) -> Duration {
246+
let frames = self.frames as f64 / fps;
247+
let seconds = self.seconds as f64;
248+
let minutes = self.minutes as f64 * 60f64;
249+
let hours = self.hours as f64 * 3600f64;
250+
251+
Duration::from_secs_f64(hours + minutes + seconds + frames)
252+
}
244253

245254
const fn build(frames: u64, negative: bool, fps: u64) -> Self {
246255
let seconds = frames / fps;

crates/runtime/pipeline/node/src/introspection.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ node_type_name! {
160160
TimecodeControl,
161161
TimecodeOutput,
162162
TimecodeRecorder,
163+
LtcDecoder,
163164
AudioFile,
164165
AudioOutput,
165166
AudioVolume,

crates/runtime/pipeline/nodes/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ pub use mizer_sequencer_nodes::SequencerNode;
5454
pub use mizer_step_sequencer_nodes::StepSequencerNode;
5555
pub use mizer_surface_nodes::SurfaceMappingNode;
5656
pub use mizer_text_nodes::VideoTextNode;
57-
pub use mizer_timecode_nodes::{TimecodeControlNode, TimecodeOutputNode, TimecodeRecorderNode};
57+
pub use mizer_timecode_nodes::{TimecodeControlNode, TimecodeOutputNode, TimecodeRecorderNode, LtcDecoderNode};
5858
pub use mizer_timing_nodes::{CountdownNode, DelayNode, TimeTriggerNode};
5959
pub use mizer_traktor_kontrol_nodes::{TraktorKontrolX1InputNode, TraktorKontrolX1OutputNode};
6060
pub use mizer_transport_nodes::{BeatsNode, TransportNode};
@@ -320,6 +320,7 @@ node_impl! {
320320
TimecodeControl(TimecodeControlNode),
321321
TimecodeOutput(TimecodeOutputNode),
322322
TimecodeRecorder(TimecodeRecorderNode),
323+
LtcDecoder(LtcDecoderNode),
323324
AudioFile(AudioFileNode),
324325
AudioOutput(AudioOutputNode),
325326
AudioVolume(AudioVolumeNode),

crates/runtime/pipeline/nodes/timecode/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,7 @@ anyhow = "1"
99
tracing = "0.1"
1010
serde = { version = "1", features = ["derive"] }
1111
mizer-node = { path = "../../node" }
12+
mizer-audio-nodes = { path = "../audio" }
1213
mizer-timecode = { path = "../../../../components/timecode" }
14+
mizer-clock = { path = "../../../clock" }
15+
ltc = "0.2"
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
pub use self::control::TimecodeControlNode;
22
pub use self::output::TimecodeOutputNode;
33
pub use self::recorder::TimecodeRecorderNode;
4+
pub use self::ltc_decoder::LtcDecoderNode;
45

56
mod control;
67
mod output;
78
mod recorder;
9+
mod ltc_decoder;
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
use ltc::LTCDecoder;
2+
use serde::{Deserialize, Serialize};
3+
use mizer_audio_nodes::{AudioContext, Signal};
4+
use mizer_clock::Timecode;
5+
use mizer_node::*;
6+
7+
const AUDIO_INPUT: &str = "LTC";
8+
const TIMECODE_OUTPUT: &str = "Clock";
9+
10+
const FPS_SETTING: &str = "FPS";
11+
12+
const SAMPLE_RATE: u32 = 44_100;
13+
14+
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
15+
pub struct LtcDecoderNode {
16+
pub fps: f64,
17+
}
18+
19+
impl Default for LtcDecoderNode {
20+
fn default() -> Self {
21+
Self {
22+
fps: 30.0,
23+
}
24+
}
25+
}
26+
27+
impl ConfigurableNode for LtcDecoderNode {
28+
fn settings(&self, _injector: &Injector) -> Vec<NodeSetting> {
29+
vec![
30+
setting!(FPS_SETTING, self.fps),
31+
]
32+
}
33+
34+
fn update_setting(&mut self, setting: NodeSetting) -> anyhow::Result<()> {
35+
update!(float setting, FPS_SETTING, self.fps);
36+
37+
update_fallback!(setting)
38+
}
39+
}
40+
41+
impl PipelineNode for LtcDecoderNode {
42+
fn details(&self) -> NodeDetails {
43+
NodeDetails {
44+
node_type_name: "LTC Decoder".into(),
45+
preview_type: PreviewType::Timecode,
46+
category: NodeCategory::Standard,
47+
}
48+
}
49+
50+
fn display_name(&self, _injector: &Injector) -> String {
51+
format!("LTC Decoder ({} FPS)", self.fps)
52+
}
53+
54+
fn list_ports(&self, _injector: &Injector) -> Vec<(PortId, PortMetadata)> {
55+
vec![
56+
input_port!(AUDIO_INPUT, PortType::Multi),
57+
output_port!(TIMECODE_OUTPUT, PortType::Clock),
58+
]
59+
}
60+
61+
fn node_type(&self) -> NodeType {
62+
NodeType::LtcDecoder
63+
}
64+
}
65+
66+
impl ProcessingNode for LtcDecoderNode {
67+
type State = LTCDecoder;
68+
69+
fn process(&self, context: &impl NodeContext, state: &mut Self::State) -> anyhow::Result<()> {
70+
let Some(mut signal) = context.input_signal(AUDIO_INPUT) else {
71+
return Ok(());
72+
};
73+
74+
let frames = context.transfer_size_per_channel();
75+
76+
let frames = (0..frames).map(|_| {
77+
let [mono, _] = signal.next();
78+
79+
mono as f32
80+
}).collect::<Vec<_>>();
81+
82+
83+
let mut timecode_frame = None;
84+
if state.write_samples(&frames) {
85+
for frame in state {
86+
timecode_frame = Some(frame);
87+
}
88+
}
89+
90+
if let Some(frame) = timecode_frame {
91+
let timecode = Timecode {
92+
hours: frame.hour as u64,
93+
minutes: frame.minute as u64,
94+
seconds: frame.second as u64,
95+
frames: frame.frame as u64,
96+
negative: false
97+
};
98+
context.write_timecode_preview(timecode);
99+
let timestamp = timecode.to_duration(context.fps());
100+
context.write_port(TIMECODE_OUTPUT, timestamp);
101+
}
102+
103+
Ok(())
104+
}
105+
106+
fn create_state(&self) -> Self::State {
107+
LTCDecoder::new(SAMPLE_RATE as f32 / self.fps as f32, Default::default())
108+
}
109+
}

0 commit comments

Comments
 (0)