1use sim_kernel::{Expr, Symbol};
4use sim_lib_scene::{data_map, node, sym};
5use sim_value::build::{int, list, text, uint};
6
7pub const ARRANGER_OBJECT_ROLL_VIEW_ID: &str = "view:arranger-object-roll";
9
10pub const ARRANGER_OBJECT_ROLL_DEMO_FIXTURE: &str = "arranger-object-roll";
12
13pub const ARRANGER_OBJECT_ROLL_ACTIONS: &[&str] = &[
15 "set-at",
16 "set-duration",
17 "set-stretch",
18 "set-transform",
19 "set-remap-pitch",
20 "set-filter",
21 "set-target",
22 "set-seed",
23 "set-trace-policy",
24 "open-nested",
25 "freeze-to-piano-roll",
26 "freeze-to-midi",
27];
28
29#[derive(Clone, Debug, PartialEq, Eq)]
31pub struct ArrangerLane {
32 pub id: Symbol,
34 pub label: String,
36 pub placements: Vec<ArrangerObjectRollPlacement>,
38}
39
40#[derive(Clone, Debug, PartialEq, Eq)]
42pub struct ArrangerObjectRollPlacement {
43 pub id: Symbol,
45 pub label: String,
47 pub lane: Symbol,
49 pub playable: Symbol,
51 pub at: u64,
53 pub duration: u64,
55 pub stretch: String,
57 pub transpose: i32,
59 pub invert: String,
61 pub retrograde: bool,
63 pub remap_pitch: String,
65 pub filter: Symbol,
67 pub target: Symbol,
69 pub seed: u64,
71 pub trace_policy: String,
73 pub nested: bool,
75}
76
77#[derive(Clone, Copy, Debug, PartialEq, Eq)]
79pub enum ArrangerDiagnosticKind {
80 DroppedEvent,
82 MissingCapability,
84 ImpossibleRemap,
86 ClippedRange,
88}
89
90impl ArrangerDiagnosticKind {
91 fn as_str(self) -> &'static str {
92 match self {
93 Self::DroppedEvent => "dropped-event",
94 Self::MissingCapability => "missing-capability",
95 Self::ImpossibleRemap => "impossible-remap",
96 Self::ClippedRange => "clipped-range",
97 }
98 }
99}
100
101#[derive(Clone, Debug, PartialEq, Eq)]
103pub struct ArrangerDiagnostic {
104 pub placement: Symbol,
106 pub diagnostic_kind: ArrangerDiagnosticKind,
108 pub message: String,
110}
111
112#[derive(Clone, Debug, PartialEq, Eq)]
114pub struct ArrangerObjectRollView {
115 pub target: Symbol,
117 pub arranger: Symbol,
119 pub lanes: Vec<ArrangerLane>,
121 pub diagnostics: Vec<ArrangerDiagnostic>,
123}
124
125pub fn arranger_object_roll_view(view: &ArrangerObjectRollView) -> Expr {
127 node(
128 "object-roll",
129 vec![
130 ("lens", sym(ARRANGER_OBJECT_ROLL_VIEW_ID)),
131 ("role", sym("arranger-object-roll")),
132 ("target", Expr::Symbol(view.target.clone())),
133 ("arranger", Expr::Symbol(view.arranger.clone())),
134 (
135 "actions",
136 list(
137 ARRANGER_OBJECT_ROLL_ACTIONS
138 .iter()
139 .map(|action| text(*action))
140 .collect(),
141 ),
142 ),
143 (
144 "lanes",
145 list(view.lanes.iter().map(arranger_lane_expr).collect()),
146 ),
147 (
148 "diagnostics",
149 list(
150 view.diagnostics
151 .iter()
152 .map(arranger_diagnostic_expr)
153 .collect(),
154 ),
155 ),
156 ],
157 )
158}
159
160pub fn arranger_object_roll_demo_view() -> ArrangerObjectRollView {
162 let melody_lane = Symbol::qualified("music/arranger-lane", "melody");
163 let nested_lane = Symbol::qualified("music/arranger-lane", "nested");
164 let automation_lane = Symbol::qualified("music/arranger-lane", "automation");
165 let motif = ArrangerObjectRollPlacement {
166 id: Symbol::qualified("music/arranger-placement", "motif"),
167 label: "Motif".to_owned(),
168 lane: melody_lane.clone(),
169 playable: Symbol::qualified("music/playable", "motif-roll"),
170 at: 0,
171 duration: 384,
172 stretch: "fit-to-duration".to_owned(),
173 transpose: 12,
174 invert: "pitch:C4".to_owned(),
175 retrograde: true,
176 remap_pitch: "scale:minor-pentatonic".to_owned(),
177 filter: Symbol::qualified("music/filter", "lead-only"),
178 target: Symbol::qualified("audio-synth/instrument", "dx7"),
179 seed: 9001,
180 trace_policy: "full".to_owned(),
181 nested: false,
182 };
183 let nested = ArrangerObjectRollPlacement {
184 id: Symbol::qualified("music/arranger-placement", "nested-arranger"),
185 label: "Nested arranger".to_owned(),
186 lane: nested_lane.clone(),
187 playable: Symbol::qualified("music/arranger", "bridge"),
188 at: 384,
189 duration: 384,
190 stretch: "tempo-ratio:3/2".to_owned(),
191 transpose: 0,
192 invert: "none".to_owned(),
193 retrograde: false,
194 remap_pitch: "vector:modal-axis".to_owned(),
195 filter: Symbol::qualified("music/filter", "none"),
196 target: Symbol::qualified("music/player-chain", "onscreen-keyboard"),
197 seed: 17,
198 trace_policy: "diagnostics".to_owned(),
199 nested: true,
200 };
201 let automation = ArrangerObjectRollPlacement {
202 id: Symbol::qualified("music/arranger-placement", "cutoff-sweep"),
203 label: "Cutoff sweep".to_owned(),
204 lane: automation_lane.clone(),
205 playable: Symbol::qualified("music/playable", "cutoff-curve"),
206 at: 768,
207 duration: 192,
208 stretch: "none".to_owned(),
209 transpose: 0,
210 invert: "none".to_owned(),
211 retrograde: false,
212 remap_pitch: "matrix:ps3300-map".to_owned(),
213 filter: Symbol::qualified("music/filter", "controls"),
214 target: Symbol::qualified("audio-synth/parameter", "cutoff"),
215 seed: 5,
216 trace_policy: "off".to_owned(),
217 nested: false,
218 };
219 ArrangerObjectRollView {
220 target: Symbol::qualified("music/arranger", "song-a"),
221 arranger: Symbol::qualified("music/arranger", "song-a"),
222 lanes: vec![
223 ArrangerLane {
224 id: melody_lane,
225 label: "Melody".to_owned(),
226 placements: vec![motif.clone()],
227 },
228 ArrangerLane {
229 id: nested_lane,
230 label: "Nested".to_owned(),
231 placements: vec![nested.clone()],
232 },
233 ArrangerLane {
234 id: automation_lane,
235 label: "Automation".to_owned(),
236 placements: vec![automation.clone()],
237 },
238 ],
239 diagnostics: vec![
240 ArrangerDiagnostic {
241 placement: motif.id.clone(),
242 diagnostic_kind: ArrangerDiagnosticKind::DroppedEvent,
243 message: "dropped control event".to_owned(),
244 },
245 ArrangerDiagnostic {
246 placement: motif.id,
247 diagnostic_kind: ArrangerDiagnosticKind::MissingCapability,
248 message: "target lacks pitch input".to_owned(),
249 },
250 ArrangerDiagnostic {
251 placement: nested.id,
252 diagnostic_kind: ArrangerDiagnosticKind::ImpossibleRemap,
253 message: "vector remap misses row".to_owned(),
254 },
255 ArrangerDiagnostic {
256 placement: automation.id,
257 diagnostic_kind: ArrangerDiagnosticKind::ClippedRange,
258 message: "placement clipped at loop end".to_owned(),
259 },
260 ],
261 }
262}
263
264pub fn arranger_object_roll_demo_scene() -> Expr {
266 arranger_object_roll_view(&arranger_object_roll_demo_view())
267}
268
269fn arranger_lane_expr(lane: &ArrangerLane) -> Expr {
270 data_map(vec![
271 ("id", Expr::Symbol(lane.id.clone())),
272 ("label", text(lane.label.clone())),
273 (
274 "placements",
275 list(
276 lane.placements
277 .iter()
278 .map(arranger_placement_expr)
279 .collect(),
280 ),
281 ),
282 ])
283}
284
285fn arranger_placement_expr(placement: &ArrangerObjectRollPlacement) -> Expr {
286 data_map(vec![
287 ("id", Expr::Symbol(placement.id.clone())),
288 ("label", text(placement.label.clone())),
289 ("lane", Expr::Symbol(placement.lane.clone())),
290 ("playable", Expr::Symbol(placement.playable.clone())),
291 ("at", uint(placement.at)),
292 ("duration", uint(placement.duration)),
293 ("stretch", text(placement.stretch.clone())),
294 ("transpose", int(i64::from(placement.transpose))),
295 ("invert", text(placement.invert.clone())),
296 ("retrograde", Expr::Bool(placement.retrograde)),
297 ("remap-pitch", text(placement.remap_pitch.clone())),
298 ("filter", Expr::Symbol(placement.filter.clone())),
299 ("target", Expr::Symbol(placement.target.clone())),
300 ("seed", uint(placement.seed)),
301 ("trace-policy", text(placement.trace_policy.clone())),
302 ("nested", Expr::Bool(placement.nested)),
303 (
304 "freeze-targets",
305 list(vec![text("piano-roll"), text("midi")]),
306 ),
307 ])
308}
309
310fn arranger_diagnostic_expr(diagnostic: &ArrangerDiagnostic) -> Expr {
311 data_map(vec![
312 ("placement", Expr::Symbol(diagnostic.placement.clone())),
313 ("diagnostic-kind", text(diagnostic.diagnostic_kind.as_str())),
314 ("message", text(diagnostic.message.clone())),
315 ])
316}