Skip to main content

sim_lib_music_core/
player_chain.rs

1use sim_kernel::{Error, Result, Symbol};
2use sim_lib_stream_core::{ClockDomain, StreamValue};
3
4use crate::freeze::{context_hash, freeze_meta, stable_hash};
5use crate::{
6    ChainDevice, ChainPlacementPlan, ChainTraceRecord, DirectRecording, FreezeMeta, FrozenPlayable,
7    FrozenPlayerChain, LaneDescriptor, LaneId, LaneKind, Music, PlacedChainDevice, PlayContext,
8    PlayEvent, PlayStream, Playable, PlayableDescriptor, PlayableShape, PlayerDeviceId, PlayerMode,
9    PlayerTargetDescriptor, SourceRecording, TraceAction, render_music_events, stable_event_order,
10};
11
12/// Result of rendering a chain: the output events and their trace records.
13#[derive(Clone, Debug, PartialEq, Eq)]
14pub struct ChainRender {
15    /// Rendered output events in stable order.
16    pub events: Vec<PlayEvent>,
17    /// Trace records describing how each device acted.
18    pub traces: Vec<ChainTraceRecord>,
19}
20
21/// A source music object plus an ordered chain of devices and an output target.
22#[derive(Clone, Debug)]
23pub struct PlayerChain {
24    /// Symbol identifying the source.
25    pub source_id: Symbol,
26    /// Source music rendered at the head of the chain.
27    pub source: Music,
28    /// Devices applied in order to the source events.
29    pub devices: Vec<ChainDevice>,
30    /// Descriptor of the chain's output target.
31    pub target: PlayerTargetDescriptor,
32}
33
34impl PlayerChain {
35    /// Creates a chain from a source and its devices and target.
36    pub fn new(
37        source_id: Symbol,
38        source: Music,
39        devices: Vec<ChainDevice>,
40        target: PlayerTargetDescriptor,
41    ) -> Self {
42        Self {
43            source_id,
44            source,
45            devices,
46            target,
47        }
48    }
49
50    /// Returns the devices in stable order by `order` then id.
51    pub fn stable_devices(&self) -> Vec<&ChainDevice> {
52        let mut devices = self.devices.iter().collect::<Vec<_>>();
53        devices.sort_by(|left, right| {
54            left.order
55                .cmp(&right.order)
56                .then_with(|| left.id.cmp(&right.id))
57        });
58        devices
59    }
60
61    /// Builds the placement plan for the chain's devices.
62    pub fn placement_plan(&self) -> ChainPlacementPlan {
63        ChainPlacementPlan::new(
64            self.stable_devices()
65                .into_iter()
66                .map(|device| PlacedChainDevice {
67                    device_id: device.id.clone(),
68                    placement: device.placement.clone(),
69                })
70                .collect(),
71        )
72    }
73
74    /// Renders the source through every active device into events and traces.
75    pub fn render_chain(&self, cx: &PlayContext) -> Result<ChainRender> {
76        let mut events = render_music_events(&self.source, cx)?;
77        let mut traces = Vec::new();
78        let mut sequence = 0;
79        for device in active_devices(self.stable_devices()) {
80            events = process_device(device, events, cx, &mut traces, &mut sequence);
81            stable_event_order(&mut events);
82        }
83        Ok(ChainRender { events, traces })
84    }
85
86    /// Records source metadata (hashes, seed, placement) without rendering output.
87    pub fn record_source(&self, cx: &PlayContext) -> SourceRecording {
88        SourceRecording::new(
89            self.source_id.clone(),
90            self.chain_hash(),
91            context_hash(cx),
92            cx.seed,
93            self.placement_plan(),
94        )
95    }
96
97    /// Freezes the chain and captures it as a direct recording.
98    pub fn record_direct(&self, cx: &PlayContext) -> Result<DirectRecording> {
99        let frozen = self.freeze_chain(cx)?;
100        Ok(DirectRecording {
101            meta: frozen.meta,
102            events: frozen.events,
103            traces: frozen.traces,
104        })
105    }
106
107    /// Renders the chain and freezes it with metadata into a [`FrozenPlayerChain`].
108    pub fn freeze_chain(&self, cx: &PlayContext) -> Result<FrozenPlayerChain> {
109        let render = self.render_chain(cx)?;
110        let meta = self.freeze_meta(cx, &render.events, &render.traces);
111        Ok(FrozenPlayerChain {
112            meta,
113            events: render.events,
114            traces: render.traces,
115        })
116    }
117
118    /// Builds freeze metadata for already-rendered events and traces.
119    pub fn freeze_meta(
120        &self,
121        cx: &PlayContext,
122        events: &[PlayEvent],
123        traces: &[ChainTraceRecord],
124    ) -> FreezeMeta {
125        freeze_meta(
126            self.source_id.clone(),
127            self.chain_hash(),
128            cx,
129            self.placement_plan(),
130            events,
131            traces,
132        )
133    }
134
135    /// Returns a stable hash over the source id, devices, and target.
136    pub fn chain_hash(&self) -> String {
137        stable_hash(
138            "player-chain",
139            &(&self.source_id, &self.devices, &self.target),
140        )
141    }
142}
143
144impl Playable for PlayerChain {
145    fn describe(&self) -> Result<PlayableDescriptor> {
146        Ok(PlayableDescriptor {
147            id: Symbol::qualified("music/player-chain", self.source_id.to_string()),
148            lanes: vec![
149                LaneDescriptor::new(
150                    LaneId::new("chain-output"),
151                    LaneKind::Note,
152                    self.target.target.clone(),
153                    0,
154                )
155                .map_err(|err| Error::Eval(err.to_string()))?,
156            ],
157            clock_domain: self.target.clock_domain(),
158            latency_class: self.target.latency_class(),
159            shape: PlayableShape::music_object(),
160        })
161    }
162
163    fn render_range(&self, cx: &PlayContext) -> Result<PlayStream> {
164        let render = self.render_chain(cx)?;
165        let items = render
166            .events
167            .iter()
168            .map(|event| event.to_stream_item(ClockDomain::MidiTick.symbol()))
169            .collect::<Result<Vec<_>>>()?;
170        let metadata = cx.stream_metadata(
171            Symbol::qualified("music/play-stream", "player-chain"),
172            items.len(),
173        )?;
174        Ok(StreamValue::pull(metadata, items))
175    }
176
177    fn freeze(&self, cx: &PlayContext) -> Result<FrozenPlayable> {
178        let frozen = self.freeze_chain(cx)?;
179        Ok(FrozenPlayable {
180            descriptor: self.describe()?,
181            events: frozen.events,
182            content_hash: frozen.meta.output_hash,
183        })
184    }
185}
186
187fn active_devices(devices: Vec<&ChainDevice>) -> Vec<&ChainDevice> {
188    let soloed = devices
189        .iter()
190        .any(|device| device.enabled && device.solo && !device.mute);
191    devices
192        .into_iter()
193        .filter(|device| device.enabled && !device.mute)
194        .filter(|device| !soloed || device.solo)
195        .collect()
196}
197
198fn process_device(
199    device: &ChainDevice,
200    input: Vec<PlayEvent>,
201    cx: &PlayContext,
202    traces: &mut Vec<ChainTraceRecord>,
203    sequence: &mut u64,
204) -> Vec<PlayEvent> {
205    if device.bypass {
206        return input;
207    }
208
209    let generated = generated_events(device, cx);
210    let mut output = match device.mode {
211        PlayerMode::Through => {
212            let mut output = routed_events(device, input, traces, sequence);
213            trace_generated(device, &generated, traces, sequence);
214            output.extend(generated);
215            output
216        }
217        PlayerMode::Replace => {
218            trace_dropped(device, input, traces, sequence);
219            trace_generated(device, &generated, traces, sequence);
220            generated
221        }
222        PlayerMode::Filter => filter_events(device, input, traces, sequence),
223        PlayerMode::Sidechain => {
224            trace_generated(device, &generated, traces, sequence);
225            routed_events(device, input, traces, sequence)
226        }
227        PlayerMode::SelfClocked => {
228            trace_dropped(device, input, traces, sequence);
229            trace_generated(device, &generated, traces, sequence);
230            generated
231        }
232    };
233    stable_event_order(&mut output);
234    output
235}
236
237fn generated_events(device: &ChainDevice, cx: &PlayContext) -> Vec<PlayEvent> {
238    device
239        .generated
240        .iter()
241        .filter_map(|event| clip_event(event, cx))
242        .map(|event| route_event(device, event))
243        .collect()
244}
245
246fn filter_events(
247    device: &ChainDevice,
248    input: Vec<PlayEvent>,
249    traces: &mut Vec<ChainTraceRecord>,
250    sequence: &mut u64,
251) -> Vec<PlayEvent> {
252    let mut output = Vec::new();
253    for event in input {
254        if device.filter_lanes.binary_search(event.lane_id()).is_ok() {
255            push_trace(
256                device,
257                TraceAction::Dropped,
258                event,
259                "filtered",
260                traces,
261                sequence,
262            );
263        } else {
264            let routed = route_event(device, event);
265            let action = if device.route_lane.is_some() {
266                TraceAction::Rewritten
267            } else {
268                TraceAction::Routed
269            };
270            push_trace(
271                device,
272                action,
273                routed.clone(),
274                "filtered-through",
275                traces,
276                sequence,
277            );
278            output.push(routed);
279        }
280    }
281    output
282}
283
284fn routed_events(
285    device: &ChainDevice,
286    input: Vec<PlayEvent>,
287    traces: &mut Vec<ChainTraceRecord>,
288    sequence: &mut u64,
289) -> Vec<PlayEvent> {
290    input
291        .into_iter()
292        .map(|event| {
293            let routed = route_event(device, event);
294            let action = if device.route_lane.is_some() {
295                TraceAction::Rewritten
296            } else {
297                TraceAction::Routed
298            };
299            push_trace(device, action, routed.clone(), "routed", traces, sequence);
300            routed
301        })
302        .collect()
303}
304
305fn trace_generated(
306    device: &ChainDevice,
307    generated: &[PlayEvent],
308    traces: &mut Vec<ChainTraceRecord>,
309    sequence: &mut u64,
310) {
311    for event in generated {
312        push_trace(
313            device,
314            TraceAction::Generated,
315            event.clone(),
316            "generated",
317            traces,
318            sequence,
319        );
320    }
321}
322
323fn trace_dropped(
324    device: &ChainDevice,
325    input: Vec<PlayEvent>,
326    traces: &mut Vec<ChainTraceRecord>,
327    sequence: &mut u64,
328) {
329    for event in input {
330        push_trace(
331            device,
332            TraceAction::Dropped,
333            event,
334            "dropped",
335            traces,
336            sequence,
337        );
338    }
339}
340
341fn push_trace(
342    device: &ChainDevice,
343    action: TraceAction,
344    event: PlayEvent,
345    detail: &'static str,
346    traces: &mut Vec<ChainTraceRecord>,
347    sequence: &mut u64,
348) {
349    traces.push(ChainTraceRecord::new(
350        *sequence,
351        PlayerDeviceId::new(device.id.0.clone()),
352        action,
353        event,
354        detail,
355    ));
356    *sequence += 1;
357}
358
359fn clip_event(event: &PlayEvent, cx: &PlayContext) -> Option<PlayEvent> {
360    match event {
361        PlayEvent::Note(note) => {
362            let (time, duration) = cx.range.clip_span(note.time, note.duration)?;
363            let mut note = note.clone();
364            note.time = time;
365            note.duration = duration;
366            Some(PlayEvent::Note(note))
367        }
368        _ if cx.range.contains(event.time()) => Some(event.clone()),
369        _ => None,
370    }
371}
372
373fn route_event(device: &ChainDevice, event: PlayEvent) -> PlayEvent {
374    let Some(lane) = &device.route_lane else {
375        return event;
376    };
377    with_lane(event, lane.clone())
378}
379
380fn with_lane(event: PlayEvent, lane_id: LaneId) -> PlayEvent {
381    match event {
382        PlayEvent::Note(mut event) => {
383            event.lane_id = lane_id;
384            PlayEvent::Note(event)
385        }
386        PlayEvent::Midi(mut event) => {
387            event.lane_id = lane_id;
388            PlayEvent::Midi(event)
389        }
390        PlayEvent::Pitch(mut event) => {
391            event.lane_id = lane_id;
392            PlayEvent::Pitch(event)
393        }
394        PlayEvent::Control(mut event) => {
395            event.lane_id = lane_id;
396            PlayEvent::Control(event)
397        }
398        PlayEvent::Audio(mut event) => {
399            event.lane_id = lane_id;
400            PlayEvent::Audio(event)
401        }
402        PlayEvent::Playable(mut event) => {
403            event.lane_id = lane_id;
404            PlayEvent::Playable(event)
405        }
406        PlayEvent::Performance(mut event) => {
407            event.lane_id = lane_id;
408            PlayEvent::Performance(event)
409        }
410        PlayEvent::Diagnostic(mut event) => {
411            event.lane_id = lane_id;
412            PlayEvent::Diagnostic(event)
413        }
414        PlayEvent::Trace(mut event) => {
415            event.lane_id = lane_id;
416            PlayEvent::Trace(event)
417        }
418    }
419}