1mod ratio;
4mod read;
5mod value;
6
7pub use ratio::Ratio;
8pub(crate) use read::is_old_score;
9pub use read::parse_score;
10pub use value::{Amount, Field, Kind, MOVING, Reading, Refused, Value, evaluate};
11
12use crate::diag::{ByteSpan, Diag, DiagCode};
13use crate::expr::{Address, BinOp, DEGREE, Expr, Literal, children};
14use crate::filename::SpanUnit;
15use crate::graph::VARIABLES;
16
17pub const EXTENSION: &str = ".jsonl";
18
19#[derive(Clone, Debug, PartialEq)]
20pub struct Score {
21 pub annotation: Option<String>,
22 pub unit: SpanUnit,
23 pub lines: Vec<Line>,
24}
25
26impl Score {
27 pub fn items(&self) -> impl Iterator<Item = (&Line, &Item)> {
28 self.lines
29 .iter()
30 .flat_map(|line| line.items.iter().map(move |item| (line, item)))
31 }
32
33 pub(crate) fn reads(&self, path: &str) -> Expr {
37 let root = "../".repeat(path.matches('/').count());
38 let mut named: Vec<(String, usize, bool)> = Vec::new();
39 for (_, item) in self.items() {
40 match item {
41 Item::Event(e) => named.push((e.node.name.clone(), e.node.at, true)),
42 Item::Move(m) => match &m.node {
43 Some(node) => named.push((node.name.clone(), node.at, false)),
44 None => {
45 let variable = format!("{root}{VARIABLES}/{}", m.lane.name);
46 named.push((variable, m.lane.at, false));
47 if let Some(scale) = m.to.text.strip_prefix('@') {
48 named.push((scale.to_string(), m.to.inner, false));
49 }
50 }
51 },
52 Item::Mark(_) | Item::Comment(_) => {}
53 }
54 }
55 let mut seen = std::collections::BTreeSet::new();
56 named.retain(|(name, _, played)| seen.insert((name.clone(), *played)));
57 let read = |(path, at, played): (String, usize, bool)| Expr::Ref {
58 path,
59 arg: Box::new(match played {
60 true => Expr::Var("t".to_string()),
61 false => Expr::Lit(Literal::Num(0.0)),
62 }),
63 binds: Vec::new(),
64 address: Address::Time,
65 span: ByteSpan::new(at, at),
66 };
67 named
68 .into_iter()
69 .map(read)
70 .reduce(|a, b| Expr::Bin(BinOp::Add, Box::new(a), Box::new(b)))
71 .unwrap_or(Expr::Lit(Literal::Num(0.0)))
72 }
73
74 pub(crate) fn reads_degrees(&self) -> bool {
75 fn calls(e: &Expr) -> bool {
76 matches!(e, Expr::Call { name, .. } if name == DEGREE)
77 || children(e, crate::expr::Binds::Substitute)
78 .into_iter()
79 .any(calls)
80 }
81 self.items().any(|(_, item)| match item {
82 Item::Event(e) => e.deg.is_some() || e.binds.iter().any(|b| calls(&b.value)),
83 Item::Move(m) => m.to.text.contains("deg("),
84 _ => false,
85 })
86 }
87}
88
89#[derive(Clone, Debug, PartialEq)]
90pub struct Line {
91 pub number: usize,
92 pub span: ByteSpan,
93 pub items: Vec<Item>,
94}
95
96#[derive(Clone, Debug, PartialEq)]
97pub enum Item {
98 Event(Event),
99 Mark(Mark),
100 Comment(Time),
101 Move(Move),
102}
103
104impl Item {
105 pub fn at(&self) -> Time {
106 match self {
107 Item::Event(e) => e.at,
108 Item::Mark(m) => m.at,
109 Item::Comment(at) => *at,
110 Item::Move(m) => m.at,
111 }
112 }
113
114 pub fn kind(&self) -> &'static str {
115 match self {
116 Item::Event(_) => "event",
117 Item::Mark(_) => "mark",
118 Item::Comment(_) => "comment",
119 Item::Move(_) => "move",
120 }
121 }
122}
123
124#[derive(Clone, Copy, Debug, PartialEq)]
126pub struct Time {
127 pub amount: Ratio,
128 pub at: usize,
129}
130
131#[derive(Clone, Debug, PartialEq)]
132pub struct Named {
133 pub name: String,
134 pub at: usize,
135}
136
137#[derive(Clone, Debug, PartialEq)]
138pub struct Event {
139 pub at: Time,
140 pub node: Named,
141 pub deg: Option<(i64, usize)>,
142 pub len: Option<Time>,
143 pub binds: Vec<Bind>,
144}
145
146#[derive(Clone, Debug, PartialEq)]
148pub struct Bind {
149 pub name: String,
150 pub value: Expr,
151 pub key: usize,
152 pub at: usize,
153}
154
155#[derive(Clone, Debug, PartialEq)]
156pub struct Mark {
157 pub at: Time,
158 pub name: String,
159}
160
161#[derive(Clone, Debug, PartialEq)]
162pub struct Move {
163 pub at: Time,
164 pub node: Option<Named>,
165 pub lane: Named,
166 pub to: To,
167 pub ramp: Option<Ramp>,
168}
169
170#[derive(Clone, Debug, PartialEq)]
172pub struct To {
173 pub text: String,
174 pub at: usize,
176 pub inner: usize,
177}
178
179#[derive(Clone, Copy, Debug, PartialEq)]
180pub struct Ramp {
181 pub len: Time,
182 pub shape: Shape,
183}
184
185#[derive(Clone, Copy, Debug, PartialEq, Eq)]
186pub enum Shape {
187 Lin,
188 Exp,
189}
190
191impl Shape {
192 pub fn name(self) -> &'static str {
193 match self {
194 Shape::Lin => "lin",
195 Shape::Exp => "exp",
196 }
197 }
198}
199
200pub fn spelled(amount: f64, unit: SpanUnit) -> String {
201 match unit {
202 SpanUnit::Bars => format!("{amount}b"),
203 SpanUnit::Seconds => format!("{amount}s"),
204 }
205}
206
207pub fn line_column(text: &str, at: usize) -> (usize, usize) {
209 let before = &text[..at.min(text.len())];
210 let line = before.matches('\n').count() + 1;
211 let column = before.len() - before.rfind('\n').map_or(0, |n| n + 1) + 1;
212 (line, column)
213}
214
215pub fn refused(
216 file: String,
217 text: &str,
218 at: usize,
219 code: DiagCode,
220 message: &str,
221) -> crate::Refusal {
222 let place = line_column(text, at);
223 let diag = located(text, at, message);
224 crate::Refusal::new(file, diag.span, code, diag.message).placed(place)
225}
226
227pub fn located(text: &str, at: usize, message: &str) -> Diag {
228 let (line, column) = line_column(text, at);
229 Diag::new(
230 DiagCode::BadScore,
231 ByteSpan::at(at),
232 format!("line {line}, column {column}: {message}"),
233 )
234}