1use crate::ast::{
15 ArrangeItem, GroupDef, PatternDef, PitchRoot, Program, ScaleMode, SourceLine, Transform,
16};
17use crate::error::{CompileError, CompileErrorKind, SourceLocation};
18
19#[derive(Debug, Clone, PartialEq)]
25pub struct Section {
26 pub name: String,
27 pub muted: bool,
29 pub cycles: u32,
31 pub bpm: u32,
33 pub sig: (u8, u8),
35 pub scale: Option<(PitchRoot, ScaleMode)>,
37 pub patterns: Vec<PatternDef>,
39 pub groups: Vec<GroupDef>,
41}
42
43impl Section {
44 pub fn cycle_seconds(&self) -> f64 {
46 cycle_seconds(self.bpm, self.sig)
47 }
48
49 pub fn seconds(&self) -> f64 {
51 self.cycle_seconds() * f64::from(self.cycles)
52 }
53
54 pub fn audible_on(&self, cycle: u32) -> impl Iterator<Item = &PatternDef> {
57 let cycles = self.cycles;
58 let muted = self.muted;
59 self.patterns.iter().filter(move |pattern| {
60 !muted && !pattern.muted && pattern.span.is_none_or(|span| span.contains(cycle, cycles))
61 })
62 }
63}
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67pub struct Occurrence {
68 pub section: usize,
70 pub start_cycle: u64,
73}
74
75#[derive(Debug, Clone, PartialEq)]
77pub struct Piece {
78 pub sections: Vec<Section>,
80 pub arrangement: Vec<ArrangeItem>,
83 pub timeline: Vec<Occurrence>,
85 pub throughout: Vec<PatternDef>,
88 pub tail: f64,
90 pub seed: u64,
92 pub unused: Vec<String>,
95 pub metadata: Vec<(String, String)>,
98}
99
100pub const DEFAULT_TAIL: f64 = 2.0;
102
103impl Piece {
104 pub fn total_cycles(&self) -> u64 {
106 self.timeline
107 .iter()
108 .map(|occurrence| u64::from(self.sections[occurrence.section].cycles))
109 .sum()
110 }
111
112 pub fn seconds(&self) -> f64 {
117 self.timeline
118 .iter()
119 .map(|occurrence| self.sections[occurrence.section].seconds())
120 .sum()
121 }
122
123 pub fn meta(&self, key: &str) -> Option<&str> {
125 self.metadata
126 .iter()
127 .find(|(name, _)| name == key)
128 .map(|(_, value)| value.as_str())
129 }
130
131 pub fn render_seconds(&self) -> f64 {
133 self.seconds() + self.tail
134 }
135
136 pub fn walk(&self) -> impl Iterator<Item = PlacedSection<'_>> {
142 let mut at_seconds = 0.0;
143 self.timeline.iter().map(move |occurrence| {
144 let section = &self.sections[occurrence.section];
145 let placed = PlacedSection {
146 section,
147 start_cycle: occurrence.start_cycle,
148 start_seconds: at_seconds,
149 };
150 at_seconds += section.seconds();
151 placed
152 })
153 }
154}
155
156#[derive(Debug, Clone, Copy, PartialEq)]
158pub struct PlacedSection<'a> {
159 pub section: &'a Section,
160 pub start_cycle: u64,
162 pub start_seconds: f64,
164}
165
166pub fn cycle_seconds(bpm: u32, signature: (u8, u8)) -> f64 {
168 let quarters = f64::from(signature.0) * 4.0 / f64::from(signature.1.max(1));
169 quarters * 60.0 / f64::from(bpm.max(1))
170}
171
172pub fn is_piece(program: &Program) -> bool {
174 program
175 .lines
176 .iter()
177 .any(|line| matches!(line, SourceLine::SectionStart { .. }))
178}
179
180fn error(line: usize, message: String) -> CompileError {
181 CompileError {
182 kind: CompileErrorKind::ParseError,
183 location: SourceLocation {
184 line,
185 column: 1,
186 file: None,
187 },
188 message,
189 suggestion: None,
190 }
191}
192
193#[derive(Clone, Copy)]
196struct Ambient {
197 bpm: u32,
198 sig: (u8, u8),
199 scale: Option<(PitchRoot, ScaleMode)>,
200}
201
202pub fn resolve(
211 program: &Program,
212 defaults: (u32, (u8, u8), Option<(PitchRoot, ScaleMode)>),
213) -> (Piece, Vec<CompileError>) {
214 let mut errors = Vec::new();
215 let mut ambient = Ambient {
216 bpm: defaults.0,
217 sig: defaults.1,
218 scale: defaults.2,
219 };
220
221 let mut sections: Vec<Section> = Vec::new();
222 let mut throughout: Vec<PatternDef> = Vec::new();
223 let mut arrangement: Option<Vec<ArrangeItem>> = None;
224 let mut tail = DEFAULT_TAIL;
225 let mut seed = 0u64;
226 let mut metadata: Vec<(String, String)> = Vec::new();
227
228 let mut open: Option<(Section, Ambient)> = None;
230 let mut open_group: Option<(bool, String, usize)> = None;
232
233 for (index, line) in program.lines.iter().enumerate() {
234 let at = index + 1;
235 match line {
236 SourceLine::Bpm(value) => match &mut open {
237 Some((section, _)) => section.bpm = *value,
238 None => ambient.bpm = *value,
239 },
240 SourceLine::Sig(num, den) => match &mut open {
241 Some((section, _)) => section.sig = (*num, *den),
242 None => ambient.sig = (*num, *den),
243 },
244 SourceLine::Scale(root, mode) => match &mut open {
245 Some((section, _)) => section.scale = Some((*root, *mode)),
246 None => ambient.scale = Some((*root, *mode)),
247 },
248 SourceLine::Phrase(_) => {
249 errors.push(error(
250 at,
251 "phrase: a piece is not edited while it plays, so it has no boundary to \
252 land a change on — drop the line (§2.6)"
253 .to_string(),
254 ));
255 }
256 SourceLine::Tail(seconds) => tail = *seconds,
257 SourceLine::Seed(value) => seed = *value,
258 SourceLine::Meta(key, value) => {
259 metadata.retain(|(existing, _)| existing != key);
262 metadata.push((key.clone(), value.clone()));
263 }
264 SourceLine::Arrange(items) => {
265 if arrangement.is_some() {
266 errors.push(error(
267 at,
268 "arrange: the piece already has an arrangement — one line names the \
269 whole order"
270 .to_string(),
271 ));
272 } else {
273 arrangement = Some(items.clone());
274 }
275 }
276 SourceLine::SectionStart {
277 muted,
278 name,
279 cycles,
280 } => {
281 if sections.iter().any(|section| §ion.name == name) {
282 errors.push(error(at, format!("section '{name}' is declared twice")));
283 }
284 open = Some((
285 Section {
286 name: name.clone(),
287 muted: *muted,
288 cycles: *cycles,
289 bpm: ambient.bpm,
290 sig: ambient.sig,
291 scale: ambient.scale,
292 patterns: Vec::new(),
293 groups: Vec::new(),
294 },
295 ambient,
296 ));
297 }
298 SourceLine::SectionEnd => {
299 if let Some((section, restore)) = open.take() {
300 ambient = restore;
302 sections.push(section);
303 }
304 }
305 SourceLine::GroupStart { muted, name } => {
306 open_group = Some((*muted, name.clone(), at));
307 }
308 SourceLine::GroupEnd(transforms) => {
309 if let Some((muted, name, _)) = open_group.take()
310 && let Some((section, _)) = &mut open
311 {
312 section.groups.push(GroupDef {
313 muted,
314 name,
315 transforms: transforms.clone(),
316 });
317 }
318 }
319 SourceLine::Pattern(def) => match &mut open {
320 Some((section, _)) => {
321 if let Some(span) = def.span
322 && let Some(reason) = span.conflict(section.cycles)
323 {
324 errors.push(error(
325 at,
326 format!("'{}': {reason} — the line can never sound", def.name),
327 ));
328 }
329 if section.patterns.iter().any(|other| other.name == def.name) {
330 errors.push(error(
331 at,
332 format!(
333 "'{}' is defined twice in section '{}' — the second would \
334 replace the first",
335 def.name, section.name
336 ),
337 ));
338 }
339 section.patterns.push(def.clone());
340 }
341 None => throughout.push(def.clone()),
342 },
343 _ => {}
344 }
345 }
346
347 errors.extend(check_group_chains(§ions));
348 errors.extend(check_name_collisions(§ions, &throughout));
349
350 let arrangement = arrangement.unwrap_or_else(|| {
352 sections
353 .iter()
354 .map(|section| ArrangeItem {
355 section: section.name.clone(),
356 repeat: 1,
357 })
358 .collect()
359 });
360
361 let (timeline, arrange_errors) = lay_out(§ions, &arrangement);
362 errors.extend(arrange_errors);
363
364 let played: Vec<&str> = timeline
365 .iter()
366 .map(|occurrence| sections[occurrence.section].name.as_str())
367 .collect();
368 let unused = sections
369 .iter()
370 .filter(|section| !played.contains(§ion.name.as_str()))
371 .map(|section| section.name.clone())
372 .collect();
373
374 (
375 Piece {
376 sections,
377 arrangement,
378 timeline,
379 throughout,
380 tail,
381 seed,
382 unused,
383 metadata,
384 },
385 errors,
386 )
387}
388
389fn lay_out(
391 sections: &[Section],
392 arrangement: &[ArrangeItem],
393) -> (Vec<Occurrence>, Vec<CompileError>) {
394 let mut timeline = Vec::new();
395 let mut errors = Vec::new();
396 let mut cycle = 0u64;
397
398 for item in arrangement {
399 let Some(index) = sections
400 .iter()
401 .position(|section| section.name == item.section)
402 else {
403 errors.push(error(
404 1,
405 format!(
406 "arrange: '{}' is not a section in this buffer",
407 item.section
408 ),
409 ));
410 continue;
411 };
412 for _ in 0..item.repeat {
413 timeline.push(Occurrence {
414 section: index,
415 start_cycle: cycle,
416 });
417 cycle += u64::from(sections[index].cycles);
418 }
419 }
420
421 if timeline.is_empty() && !sections.is_empty() {
422 errors.push(error(
423 1,
424 "arrange: the arrangement plays no section, so the piece has no length".to_string(),
425 ));
426 }
427
428 (timeline, errors)
429}
430
431fn check_group_chains(sections: &[Section]) -> Vec<CompileError> {
437 let mut errors = Vec::new();
438 let mut seen: Vec<(&str, &[Transform], &str)> = Vec::new();
439
440 for section in sections {
441 for group in §ion.groups {
442 match seen
443 .iter()
444 .find(|(name, _, _)| *name == group.name.as_str())
445 {
446 Some((_, chain, first)) if *chain != group.transforms.as_slice() => {
447 errors.push(error(
448 1,
449 format!(
450 "group '{}' declares a different chain in '{}' than in '{first}' — \
451 a bus is one bus for the whole piece, so its filters cannot change \
452 between sections",
453 group.name, section.name
454 ),
455 ));
456 }
457 Some(_) => {}
458 None => seen.push((&group.name, &group.transforms, §ion.name)),
459 }
460 }
461 }
462
463 errors
464}
465
466fn check_name_collisions(sections: &[Section], throughout: &[PatternDef]) -> Vec<CompileError> {
472 let mut errors = Vec::new();
473
474 for section in sections {
475 for group in §ion.groups {
476 if section.patterns.iter().any(|pattern| {
477 pattern.name == group.name && pattern.group.as_deref() != Some(&group.name)
478 }) {
479 errors.push(error(
480 1,
481 format!(
482 "'{}' names both a group and a pattern in section '{}'; pick two names",
483 group.name, section.name
484 ),
485 ));
486 }
487 }
488 if let Some(clash) = throughout.iter().find(|line| {
489 section
490 .patterns
491 .iter()
492 .any(|member| member.name == line.name)
493 }) {
494 errors.push(error(
495 1,
496 format!(
497 "'{}' is both a line that sounds throughout the piece and a member of \
498 section '{}' — one of them would silence the other",
499 clash.name, section.name
500 ),
501 ));
502 }
503 if sections
504 .iter()
505 .any(|other| other.patterns.iter().any(|p| p.name == section.name))
506 {
507 errors.push(error(
508 1,
509 format!(
510 "'{}' names both a section and a pattern; pick two names",
511 section.name
512 ),
513 ));
514 }
515 }
516
517 errors
518}
519
520#[cfg(test)]
521mod tests;