1mod lanes;
4mod lower;
5mod place;
6mod reach;
7mod tempo;
8
9use std::collections::BTreeSet;
10use std::rc::Rc;
11
12pub use lanes::{Held, Hold, Home, Lane, LaneId, Swept};
13pub use place::parameters;
14pub use place::{Chain, Instance, PlacedMark, Pos, Reached};
15pub use tempo::Curve;
16
17use crate::diag::{ByteSpan, DiagCode};
18use crate::expr::Expr;
19use crate::graph::{Defined, Graph, VARIABLES, variable_text};
20use crate::refusal::Refusal;
21use crate::scale::Scale;
22use crate::score::{EXTENSION, Item, Ratio, Shape, located};
23
24use lanes::Sweeping;
25use place::{Container, Offset, Placing, at_line, shifted};
26use reach::Reach;
27use tempo::TempoMove;
28
29#[derive(Clone, Copy, Debug, PartialEq)]
32pub struct Span {
33 pub start: Option<f64>,
34 pub end: Option<f64>,
35}
36
37#[derive(Clone, Debug, PartialEq)]
39pub struct Warning {
40 pub code: &'static str,
41 pub file: String,
42 pub span: ByteSpan,
43 pub start: (usize, usize),
44 pub message: String,
45}
46
47pub struct Song {
48 root: String,
49 curve: Curve,
50 tree: Rc<Container>,
51 instances: Vec<Instance>,
52 lanes: Vec<Lane>,
53 marks: Vec<PlacedMark>,
54 instruments: Vec<(String, Defined)>,
55 asked: Vec<(usize, Expr)>,
56 warnings: Vec<Warning>,
57 key: Option<f64>,
58 scale: Option<Scale>,
59 passed: Vec<(usize, LaneId)>,
60 containers: BTreeSet<String>,
61}
62
63pub struct Lowered {
65 pub nodes: Vec<(String, Defined)>,
66}
67
68fn refused(refusals: Vec<Refusal>) -> Result<(), Vec<Refusal>> {
69 match refusals.is_empty() {
70 true => Ok(()),
71 false => Err(refusals),
72 }
73}
74
75impl Graph {
76 pub fn song(&self, root: &str) -> Result<Song, Vec<Refusal>> {
79 let values = self.values(None)?;
80 let (bpm, beats) = match &values.tempo {
81 Some(tempo) => (tempo.bpm, tempo.beats()),
82 None => (60.0, 1.0),
83 };
84 let steady = Curve::new(bpm, beats, &[]);
85 let tempo = {
86 let mut first = Placing::new(self, &steady);
87 first.song(root);
88 refused(std::mem::take(&mut first.refusals))?;
89 let reached: Vec<Reached> = first
90 .moves
91 .into_iter()
92 .filter(|m| m.lane.is_variable("bpm"))
93 .collect();
94 let mut refusals = Vec::new();
95 for m in reached
96 .iter()
97 .filter(|m| matches!(m.start, Pos::Seconds(_)))
98 {
99 refusals.push(self.through_seconds(m, &steady, "a `bpm` move"));
100 }
101 refused(refusals)?;
102 let sweeping = Sweeping {
103 graph: self,
104 curve: &steady,
105 per_bar: values.tempo.as_ref().map(|t| t.seconds_per_bar()),
106 degree: &|_, _| Err("a `bpm` move reads no degree".to_string()),
107 };
108 let lanes = sweeping.sweep(reached)?;
109 lanes
110 .first()
111 .map(|lane| {
112 lane.moves
113 .iter()
114 .map(|m| {
115 let Pos::Bars(start) = m.reached.start else {
116 unreachable!("refused above")
117 };
118 let len = match m.reached.end {
119 Some(Pos::Bars(end)) => end.minus(start).expect("a bar span"),
120 _ => Ratio::ZERO,
121 };
122 TempoMove {
123 start,
124 len,
125 shape: m.shape.unwrap_or(Shape::Lin),
126 from: m.from.as_ref().and_then(Held::number).unwrap_or(bpm),
127 to: m.to.number().expect("bpm is a number"),
128 }
129 })
130 .collect::<Vec<_>>()
131 })
132 .unwrap_or_default()
133 };
134 let curve = Curve::new(bpm, beats, &tempo);
135 let mut placing = Placing::new(self, &curve);
136 let tree = placing.song(root);
137 let mut refusals = std::mem::take(&mut placing.refusals);
138 if curve.moves() {
139 for m in placing
140 .moves
141 .iter()
142 .filter(|m| matches!(m.start, Pos::Seconds(_)))
143 {
144 refusals.push(self.through_seconds(m, &curve, "a move"));
145 }
146 for (score, line, item, lost) in &placing.bars_lost {
147 let at = self.score(score).expect("a score").lines[*line].items[*item].at();
148 refusals.push(at_line(
149 self,
150 score,
151 at.at,
152 DiagCode::BadLane,
153 &format!(
154 "this line counts in bars, and the tempo moves, so a bar is a place on \
155 the tempo curve; `{}` reads what places it at a time that is no \
156 constant shift of `t`, so it has no song time: place it at `t - d`",
157 lost.path
158 ),
159 ));
160 }
161 }
162 refused(refusals)?;
163 let key = self.key_hz();
164 let scale = values.scale.clone();
165 let degree = |at: f64, n: i64| -> Result<f64, String> {
166 let _ = at;
167 let ratio = scale.as_ref().and_then(|s| s.ratio(n)).ok_or_else(|| {
168 "`deg` counts in `variables/scale`, and there is none".to_string()
169 })?;
170 key.map(|k| k * ratio)
171 .ok_or_else(|| "`deg` counts from `variables/key`, a constant note".to_string())
172 };
173 let sweeping = Sweeping {
174 graph: self,
175 curve: &curve,
176 per_bar: values.tempo.as_ref().map(|t| t.seconds_per_bar()),
177 degree: °ree,
178 };
179 let placed_moves = std::mem::take(&mut placing.moves);
180 let lanes = sweeping.sweep(placed_moves)?;
181 let marks = std::mem::take(&mut placing.marks);
182 let containers = std::mem::take(&mut placing.placed);
183 let instances_lost = std::mem::take(&mut placing.instances_lost);
184 let mut instances = std::mem::take(&mut placing.instances);
185 let moved: BTreeSet<LaneId> = lanes.iter().map(|l| l.id.clone()).collect();
186 let mut reach = Reach::new(self, &moved, &values);
187 for instance in &mut instances {
188 let bound: BTreeSet<&str> = instance.bound.iter().map(String::as_str).collect();
189 let mut held = reach.sound(&instance.node, &bound);
190 let read = instance.node.strip_prefix(&format!("{VARIABLES}/"));
191 if !read.is_some_and(|name| moved.contains(&LaneId::Variable(name.to_string()))) {
192 held.extend(reach.of(&instance.node, &bound));
193 }
194 held.retain(|l| !l.is_variable("scale"));
195 held.sort();
196 held.dedup();
197 instance.lanes = held;
198 }
199 let mut lanes = lanes;
200 if let Some(scale) = lanes.iter().find(|l| l.id.is_variable("scale")) {
201 let read: BTreeSet<i64> = instances
202 .iter()
203 .flat_map(|i| &i.lanes)
204 .filter_map(|l| match l {
205 LaneId::Degree(n) => Some(*n),
206 _ => None,
207 })
208 .collect();
209 let derived: Vec<Lane> = read
210 .into_iter()
211 .filter_map(|n| lanes::degree_lane(scale, n))
212 .collect();
213 lanes.extend(derived);
214 }
215 let mut song = Song {
216 root: root.to_string(),
217 curve,
218 tree,
219 instances,
220 lanes,
221 marks,
222 instruments: Vec::new(),
223 asked: Vec::new(),
224 warnings: Vec::new(),
225 key,
226 scale: values.scale.clone(),
227 passed: Vec::new(),
228 containers,
229 };
230 let mut refusals = song.unbound(self);
231 for (k, lost) in instances_lost {
232 let instance = &song.instances[k];
233 if instance.lanes.is_empty() {
234 continue;
235 }
236 let lanes: Vec<String> = instance.lanes.iter().map(LaneId::name).collect();
237 let (file, span) = lost.unwrap_or((song.root.clone(), ByteSpan::at(0)));
238 refusals.push(Refusal::new(
239 file.clone(),
240 span,
241 DiagCode::BadLane,
242 format!(
243 "`@{}` has no song time: `{file}` reads it at a time that is no constant \
244 shift of `t`, and the lanes `{}` move it; a lane reaches an instance at its \
245 song position, so read it at `t - d`",
246 instance.node,
247 lanes.join("`, `")
248 ),
249 ));
250 }
251 refused(refusals)?;
252 let mut lowered = BTreeSet::new();
253 for instance in &song.instances {
254 let mut open: Vec<String> = vec![instance.node.clone()];
255 while let Some(node) = open.pop() {
256 if reach.of(&node, &BTreeSet::new()).is_empty() || !lowered.insert(node.clone()) {
257 continue;
258 }
259 let mut sites = Vec::new();
260 if let Some(defined) = self.source(&node) {
261 for e in std::iter::once(&defined.body)
262 .chain(defined.defaults.iter().map(|(_, v)| v))
263 {
264 place::sites(self, &node, e, &mut sites);
265 }
266 }
267 open.extend(
268 sites
269 .iter()
270 .filter_map(|e| reach::invoked(self, &node, e).map(|(n, ..)| n)),
271 );
272 }
273 }
274 let mut refusals = Vec::new();
275 for node in lowered {
276 match reach.lowered(&node) {
277 Ok(defined) => song.instruments.push((node, defined)),
278 Err(refusal) => refusals.push(refusal),
279 }
280 }
281 refused(refusals)?;
282 song.asked = song.asked_spans(self)?;
283 song.warnings = song.warned(self);
284 Ok(song)
285 }
286
287 fn through_seconds(&self, m: &Reached, curve: &Curve, what: &str) -> Refusal {
288 let at = match &self.score(&m.score).expect("a score").lines[m.line].items[m.item] {
289 Item::Move(mv) => mv.at.at,
290 _ => 0,
291 };
292 at_line(
293 self,
294 &m.score,
295 at,
296 DiagCode::BadLane,
297 &format!(
298 "{what} on `{}` is reached through seconds ({}), and while the tempo moves a \
299 move sits on a bar, so a lane compares exact positions; place it from a file \
300 counting in bars",
301 m.lane.name(),
302 m.chain.text(curve)
303 ),
304 )
305 }
306
307 fn key_hz(&self) -> Option<f64> {
309 let text = variable_text(self, "key")?;
310 let reading = crate::score::Reading::<f64> {
311 field: crate::score::Field::Value,
312 mark: &|_| None,
313 degree: &|_| Err(String::new()),
314 own: None,
315 };
316 crate::score::evaluate(text.trim(), &reading)
317 .ok()
318 .map(|v| v.amount)
319 }
320}
321
322impl Song {
323 pub fn curve(&self) -> &Curve {
324 &self.curve
325 }
326
327 pub fn lanes(&self) -> &[Lane] {
328 &self.lanes
329 }
330
331 pub fn marks(&self) -> &[PlacedMark] {
332 &self.marks
333 }
334
335 pub fn instances(&self) -> &[Instance] {
336 &self.instances
337 }
338
339 pub fn warnings(&self) -> &[Warning] {
340 &self.warnings
341 }
342
343 pub fn instruments(&self) -> &[(String, Defined)] {
345 &self.instruments
346 }
347
348 pub fn asked(&self) -> Vec<Expr> {
351 self.asked.iter().map(|(_, e)| e.clone()).collect()
352 }
353
354 pub fn lane(&self, id: &LaneId) -> Option<&Lane> {
355 self.lanes.iter().find(|l| &l.id == id)
356 }
357
358 pub(crate) fn own_lanes(&self, graph: &Graph, node: &str) -> Vec<LaneId> {
361 parameters(graph, node)
362 .into_iter()
363 .map(|p| LaneId::Sound(node.to_string(), p))
364 .filter(|l| self.lane(l).is_some())
365 .collect()
366 }
367
368 pub fn ends(&self, graph: &Graph, i: &Instance) -> Option<Pos> {
370 let (score, li, ii) = i.line.as_ref()?;
371 let file = graph.score(score)?;
372 let Item::Event(event) = &file.lines[*li].items[*ii] else {
373 return None;
374 };
375 let len = event.len?.amount;
376 let by = match file.unit {
377 crate::filename::SpanUnit::Bars => Offset::Bars(len),
378 crate::filename::SpanUnit::Seconds => Offset::Seconds(len.to_f64()),
379 };
380 shifted(i.at, by, &self.curve)
381 }
382
383 pub fn key_at(&self, at: f64) -> Option<f64> {
385 match self.lane(&LaneId::Variable("key".to_string())) {
386 Some(lane) => lane.at(at).and_then(|h| h.number()),
387 None => self.key,
388 }
389 }
390
391 pub fn scale_at(&self, at: f64) -> Option<Scale> {
392 match self.lane(&LaneId::Variable("scale".to_string())) {
393 Some(lane) => match lane.at(at) {
394 Some(Held::Scale(scale)) => Some(scale),
395 _ => None,
396 },
397 None => self.scale.clone(),
398 }
399 }
400
401 fn unbound(&self, graph: &Graph) -> Vec<Refusal> {
404 let mut out = Vec::new();
405 let mut seen = BTreeSet::new();
406 for instance in &self.instances {
407 let Some((score, li, ii)) = &instance.line else {
408 continue;
409 };
410 let Item::Event(event) = &graph.score(score).expect("a score").lines[*li].items[*ii]
411 else {
412 continue;
413 };
414 let params = parameters(graph, &instance.node);
415 let read: Vec<&str> = params.iter().map(String::as_str).collect();
416 for bound in &instance.bound {
417 if read.contains(&bound.as_str()) {
418 continue;
419 }
420 let (key, at) = match bound.as_str() {
421 "f0" if event.deg.is_some() => ("deg", event.deg.expect("a deg").1),
422 "len" => ("len", event.len.expect("a len").at),
423 name => (
424 name,
425 event
426 .binds
427 .iter()
428 .find(|b| b.name == name)
429 .map_or(event.node.at, |b| b.key),
430 ),
431 };
432 if !seen.insert((score.clone(), *li, key.to_string())) {
433 continue;
434 }
435 let set = match key == bound {
436 true => format!("`{key}`"),
437 false => format!("`{key}`, which sets `{bound}`,"),
438 };
439 let chord = match key {
440 "chord" => "; a chord is a `deg` list, one event per voice",
441 _ => "",
442 };
443 out.push(at_line(
444 graph,
445 score,
446 at,
447 DiagCode::BadScore,
448 &format!(
449 "{set} is no parameter `@{}` reads; it reads `{}`{chord}",
450 event.node.name,
451 read.join("`, `")
452 ),
453 ));
454 }
455 let defaults: Vec<&str> = graph
456 .source(&instance.node)
457 .map(|d| d.defaults.iter().map(|(k, _)| k.as_str()).collect())
458 .unwrap_or_default();
459 let at = instance.at.map(|p| p.seconds(&self.curve));
460 for p in ¶ms {
461 if defaults.contains(&p.as_str()) || instance.bound.contains(p) {
462 continue;
463 }
464 let lane = LaneId::Sound(instance.node.clone(), p.clone());
465 let set = self
466 .lane(&lane)
467 .is_some_and(|l| at.is_some_and(|at| l.at(at).is_some()));
468 if set || !seen.insert((score.clone(), *li, format!("unbound {p}"))) {
469 continue;
470 }
471 out.push(at_line(
472 graph,
473 score,
474 event.node.at,
475 DiagCode::BadScore,
476 &format!(
477 "`@{}` reads `{p}`, which nothing binds here: bind it on this line, set \
478 the lane `{}` at or before this line, or give it a default, `{p} = ...`",
479 event.node.name,
480 lane.name()
481 ),
482 ));
483 }
484 }
485 out
486 }
487
488 fn asked_spans(&self, graph: &Graph) -> Result<Vec<(usize, Expr)>, Vec<Refusal>> {
490 let mut out = Vec::new();
491 for (k, instance) in self.instances.iter().enumerate() {
492 let Some(at) = instance.at else { continue };
493 let at = at.seconds(&self.curve);
494 let moving = instance
495 .lanes
496 .iter()
497 .filter_map(|l| self.lane(l))
498 .any(|l| l.changes_after(at));
499 if moving {
500 out.push((k, self.probe(graph, k, at)?));
501 }
502 }
503 Ok(out)
504 }
505
506 fn warned(&self, graph: &Graph) -> Vec<Warning> {
507 let mut out = Vec::new();
508 let curve = &self.curve;
509 for instance in &self.instances {
510 let (Some((score, li, ii)), Some(at)) = (&instance.line, instance.at) else {
511 continue;
512 };
513 let Item::Event(event) = &graph.score(score).expect("a score").lines[*li].items[*ii]
514 else {
515 continue;
516 };
517 let start = at.seconds(curve);
518 let end = start + self.len_seconds(graph, score, at, event.len.map(|l| l.amount));
519 for bound in &instance.bound {
520 let lane = LaneId::Sound(instance.node.clone(), bound.clone());
521 let Some(l) = self.lane(&lane) else { continue };
522 let ramp = l.moves.iter().find(|m| {
523 m.end > m.start && m.end > start && (m.start < end || m.start <= start)
524 });
525 if let Some(ramp) = ramp {
526 let key_at = event
527 .binds
528 .iter()
529 .find(|b| &b.name == bound)
530 .map_or(event.node.at, |b| b.key);
531 out.push(warn(
532 graph,
533 score,
534 key_at,
535 "line-overrides-lane",
536 &format!(
537 "this line binds `{bound}`, and the lane `{}` ramps during its event \
538 ({}); the line wins over the lane",
539 lane.name(),
540 self.line_of(graph, &ramp.reached)
541 ),
542 ));
543 }
544 }
545 }
546 for lane in &self.lanes {
547 let reached = lane.id.is_variable("bpm")
548 || self
549 .instances
550 .iter()
551 .any(|i| i.lanes.contains(&lane.id) || i.node == lane.id.name())
552 || matches!(&lane.id, LaneId::Sound(node, _) if self.containers.contains(node))
553 || self.degrees_read(graph, &lane.id);
554 if !reached {
555 let first = &lane.moves[0].reached;
556 let at = match &graph.score(&first.score).expect("a score").lines[first.line].items
557 [first.item]
558 {
559 Item::Move(m) => m.lane.at,
560 _ => 0,
561 };
562 out.push(warn(
563 graph,
564 &first.score,
565 at,
566 "lane-reaches-nothing",
567 &format!(
568 "the lane `{}` has moves, and no instance in the song reads it: its \
569 node is never placed, or every invoker passes the parameter",
570 lane.id.name()
571 ),
572 ));
573 }
574 }
575 out
576 }
577
578 fn degrees_read(&self, graph: &Graph, id: &LaneId) -> bool {
580 let own = |i: &Instance| i.lanes.iter().any(|l| matches!(l, LaneId::Degree(_)));
581 if id.is_variable("scale") && self.instances.iter().any(own) {
582 return true;
583 }
584 (id.is_variable("key") || id.is_variable("scale"))
585 && self.instances.iter().any(|i| {
586 i.line.as_ref().is_some_and(|(score, li, ii)| {
587 match &graph.score(score).expect("a score").lines[*li].items[*ii] {
588 Item::Event(e) => {
589 e.deg.is_some()
590 || e.binds
591 .iter()
592 .any(|b| crate::render_expr(&b.value).contains("deg("))
593 }
594 _ => false,
595 }
596 })
597 })
598 }
599
600 fn line_of(&self, graph: &Graph, r: &Reached) -> String {
601 let number = graph.score(&r.score).expect("a score").lines[r.line].number;
602 format!("{}{EXTENSION}:{number}", r.score)
603 }
604
605 fn len_seconds(&self, graph: &Graph, score: &str, at: Pos, len: Option<Ratio>) -> f64 {
607 let Some(len) = len else { return 0.0 };
608 match graph.score(score).expect("a score").unit {
609 crate::filename::SpanUnit::Seconds => len.to_f64(),
610 crate::filename::SpanUnit::Bars => match at {
611 Pos::Bars(b) => self.curve.duration(b, len),
612 Pos::Seconds(s) => self
613 .curve
614 .after(s, len)
615 .unwrap_or(len.to_f64() * self.curve.seconds_per_bar()),
616 },
617 }
618 }
619
620 pub fn lowered(&mut self, graph: &Graph, spans: &[Span]) -> Result<Lowered, Vec<Refusal>> {
623 let (lowered, passed) = lower::Lowering::new(graph, self, spans).lowered()?;
624 self.passed = passed;
625 Ok(lowered)
626 }
627
628 pub fn passed(&self) -> &[(usize, LaneId)] {
630 &self.passed
631 }
632
633 pub fn moved_under<'g>(
636 &self,
637 graph: &'g Graph,
638 k: usize,
639 lane: &LaneId,
640 ) -> Option<(String, usize, &'g str)> {
641 let at = self.instances[k].at?.seconds(&self.curve);
642 let l = self.lane(lane)?;
643 let m = l
644 .moves
645 .iter()
646 .find(|m| m.end > at || m.start >= at)
647 .or(l.moves.last())?;
648 let r = &m.reached;
649 let Item::Move(mv) = &graph.score(&r.score)?.lines[r.line].items[r.item] else {
650 return None;
651 };
652 Some((
653 format!("{}{EXTENSION}", r.score),
654 mv.to.at,
655 graph.text(&r.score)?,
656 ))
657 }
658}
659
660fn warn(graph: &Graph, score: &str, at: usize, code: &'static str, message: &str) -> Warning {
661 let text = graph.text(score).unwrap_or("");
662 let diag = located(text, at, message);
663 Warning {
664 code,
665 file: format!("{score}{EXTENSION}"),
666 span: diag.span,
667 start: crate::score::line_column(text, at),
668 message: diag.message,
669 }
670}
671
672impl Graph {
673 pub fn apply(&mut self, lowered: Lowered) {
675 for (path, defined) in lowered.nodes {
676 self.derive(&path, defined);
677 }
678 }
679}