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 section_index(&self, name: &str) -> Option<usize> {
138 self.sections
139 .iter()
140 .position(|section| section.name == name)
141 }
142
143 pub fn first_playing(&self, section: usize) -> u64 {
149 self.timeline
150 .iter()
151 .find(|occurrence| occurrence.section == section)
152 .map_or(0, |occurrence| occurrence.start_cycle)
153 }
154
155 pub fn isolate(&self, section: usize, passes: u32) -> Piece {
172 let kept = self.sections[section].clone();
173 let passes = passes.max(1);
174 let origin = self.first_playing(section);
175 Piece {
176 arrangement: vec![ArrangeItem {
177 section: kept.name.clone(),
178 repeat: passes,
179 }],
180 timeline: (0..passes)
181 .map(|pass| Occurrence {
182 section: 0,
183 start_cycle: origin + u64::from(pass) * u64::from(kept.cycles),
184 })
185 .collect(),
186 sections: vec![kept],
187 throughout: self.throughout.clone(),
188 tail: self.tail,
189 seed: self.seed,
190 unused: Vec::new(),
191 metadata: self.metadata.clone(),
192 }
193 }
194
195 pub fn played_instruments(&self) -> Vec<&str> {
199 let mut played: Vec<usize> = self.timeline.iter().map(|o| o.section).collect();
200 played.sort_unstable();
201 played.dedup();
202 let mut instruments: Vec<&str> = played
203 .into_iter()
204 .flat_map(|index| &self.sections[index].patterns)
205 .chain(&self.throughout)
206 .map(|line| line.instrument.as_str())
207 .collect();
208 instruments.sort_unstable();
209 instruments.dedup();
210 instruments
211 }
212
213 pub fn mute_except(&mut self, keep: &[&str]) {
221 for line in self
222 .sections
223 .iter_mut()
224 .flat_map(|section| section.patterns.iter_mut())
225 .chain(self.throughout.iter_mut())
226 {
227 if !keep.contains(&line.instrument.as_str()) {
228 line.muted = true;
229 }
230 }
231 }
232
233 pub fn walk(&self) -> impl Iterator<Item = PlacedSection<'_>> {
239 let mut at_seconds = 0.0;
240 self.timeline.iter().map(move |occurrence| {
241 let section = &self.sections[occurrence.section];
242 let placed = PlacedSection {
243 section,
244 start_cycle: occurrence.start_cycle,
245 start_seconds: at_seconds,
246 };
247 at_seconds += section.seconds();
248 placed
249 })
250 }
251}
252
253#[derive(Debug, Clone, Copy, PartialEq)]
255pub struct PlacedSection<'a> {
256 pub section: &'a Section,
257 pub start_cycle: u64,
259 pub start_seconds: f64,
261}
262
263pub fn cycle_seconds(bpm: u32, signature: (u8, u8)) -> f64 {
265 let quarters = f64::from(signature.0) * 4.0 / f64::from(signature.1.max(1));
266 quarters * 60.0 / f64::from(bpm.max(1))
267}
268
269pub fn is_piece(program: &Program) -> bool {
271 program
272 .lines
273 .iter()
274 .any(|line| matches!(line, SourceLine::SectionStart { .. }))
275}
276
277fn error(line: usize, message: String) -> CompileError {
278 CompileError {
279 kind: CompileErrorKind::ParseError,
280 location: SourceLocation {
281 line,
282 column: 1,
283 file: None,
284 },
285 message,
286 suggestion: None,
287 }
288}
289
290#[derive(Clone, Copy)]
293struct Ambient {
294 bpm: u32,
295 sig: (u8, u8),
296 scale: Option<(PitchRoot, ScaleMode)>,
297}
298
299pub fn resolve(
308 program: &Program,
309 defaults: (u32, (u8, u8), Option<(PitchRoot, ScaleMode)>),
310) -> (Piece, Vec<CompileError>) {
311 let mut errors = Vec::new();
312 let mut ambient = Ambient {
313 bpm: defaults.0,
314 sig: defaults.1,
315 scale: defaults.2,
316 };
317
318 let mut sections: Vec<Section> = Vec::new();
319 let mut throughout: Vec<PatternDef> = Vec::new();
320 let mut arrangement: Option<Vec<ArrangeItem>> = None;
321 let mut tail = DEFAULT_TAIL;
322 let mut seed = 0u64;
323 let mut metadata: Vec<(String, String)> = Vec::new();
324
325 let mut open: Option<(Section, Ambient)> = None;
327 let mut open_group: Option<(bool, String, usize)> = None;
329
330 for (index, line) in program.lines.iter().enumerate() {
331 let at = index + 1;
332 match line {
333 SourceLine::Bpm(value) => match &mut open {
334 Some((section, _)) => section.bpm = *value,
335 None => ambient.bpm = *value,
336 },
337 SourceLine::Sig(num, den) => match &mut open {
338 Some((section, _)) => section.sig = (*num, *den),
339 None => ambient.sig = (*num, *den),
340 },
341 SourceLine::Scale(root, mode) => match &mut open {
342 Some((section, _)) => section.scale = Some((*root, *mode)),
343 None => ambient.scale = Some((*root, *mode)),
344 },
345 SourceLine::Phrase(_) => {
346 errors.push(error(
347 at,
348 "phrase: a piece is not edited while it plays, so it has no boundary to \
349 land a change on — drop the line (§2.6)"
350 .to_string(),
351 ));
352 }
353 SourceLine::Tail(seconds) => tail = *seconds,
354 SourceLine::Seed(value) => seed = *value,
355 SourceLine::Meta(key, value) => {
356 metadata.retain(|(existing, _)| existing != key);
359 metadata.push((key.clone(), value.clone()));
360 }
361 SourceLine::Arrange(items) => {
362 if arrangement.is_some() {
363 errors.push(error(
364 at,
365 "arrange: the piece already has an arrangement — one line names the \
366 whole order"
367 .to_string(),
368 ));
369 } else {
370 arrangement = Some(items.clone());
371 }
372 }
373 SourceLine::SectionStart {
374 muted,
375 name,
376 cycles,
377 } => {
378 if sections.iter().any(|section| §ion.name == name) {
379 errors.push(error(at, format!("section '{name}' is declared twice")));
380 }
381 open = Some((
382 Section {
383 name: name.clone(),
384 muted: *muted,
385 cycles: *cycles,
386 bpm: ambient.bpm,
387 sig: ambient.sig,
388 scale: ambient.scale,
389 patterns: Vec::new(),
390 groups: Vec::new(),
391 },
392 ambient,
393 ));
394 }
395 SourceLine::SectionEnd => {
396 if let Some((section, restore)) = open.take() {
397 ambient = restore;
399 sections.push(section);
400 }
401 }
402 SourceLine::GroupStart { muted, name } => {
403 open_group = Some((*muted, name.clone(), at));
404 }
405 SourceLine::GroupEnd(transforms) => {
406 if let Some((muted, name, _)) = open_group.take()
407 && let Some((section, _)) = &mut open
408 {
409 section.groups.push(GroupDef {
410 muted,
411 name,
412 transforms: transforms.clone(),
413 });
414 }
415 }
416 SourceLine::Pattern(def) => match &mut open {
417 Some((section, _)) => {
418 if let Some(span) = def.span
419 && let Some(reason) = span.conflict(section.cycles)
420 {
421 errors.push(error(
422 at,
423 format!("'{}': {reason} — the line can never sound", def.name),
424 ));
425 }
426 if section.patterns.iter().any(|other| other.name == def.name) {
427 errors.push(error(
428 at,
429 format!(
430 "'{}' is defined twice in section '{}' — the second would \
431 replace the first",
432 def.name, section.name
433 ),
434 ));
435 }
436 section.patterns.push(def.clone());
437 }
438 None => throughout.push(def.clone()),
439 },
440 _ => {}
441 }
442 }
443
444 errors.extend(check_group_chains(§ions));
445 errors.extend(check_name_collisions(§ions, &throughout));
446
447 let arrangement = arrangement.unwrap_or_else(|| {
449 sections
450 .iter()
451 .map(|section| ArrangeItem {
452 section: section.name.clone(),
453 repeat: 1,
454 })
455 .collect()
456 });
457
458 let (timeline, arrange_errors) = lay_out(§ions, &arrangement);
459 errors.extend(arrange_errors);
460
461 let played: Vec<&str> = timeline
462 .iter()
463 .map(|occurrence| sections[occurrence.section].name.as_str())
464 .collect();
465 let unused = sections
466 .iter()
467 .filter(|section| !played.contains(§ion.name.as_str()))
468 .map(|section| section.name.clone())
469 .collect();
470
471 (
472 Piece {
473 sections,
474 arrangement,
475 timeline,
476 throughout,
477 tail,
478 seed,
479 unused,
480 metadata,
481 },
482 errors,
483 )
484}
485
486fn lay_out(
488 sections: &[Section],
489 arrangement: &[ArrangeItem],
490) -> (Vec<Occurrence>, Vec<CompileError>) {
491 let mut timeline = Vec::new();
492 let mut errors = Vec::new();
493 let mut cycle = 0u64;
494
495 for item in arrangement {
496 let Some(index) = sections
497 .iter()
498 .position(|section| section.name == item.section)
499 else {
500 errors.push(error(
501 1,
502 format!(
503 "arrange: '{}' is not a section in this buffer",
504 item.section
505 ),
506 ));
507 continue;
508 };
509 for _ in 0..item.repeat {
510 timeline.push(Occurrence {
511 section: index,
512 start_cycle: cycle,
513 });
514 cycle += u64::from(sections[index].cycles);
515 }
516 }
517
518 if timeline.is_empty() && !sections.is_empty() {
519 errors.push(error(
520 1,
521 "arrange: the arrangement plays no section, so the piece has no length".to_string(),
522 ));
523 }
524
525 (timeline, errors)
526}
527
528fn check_group_chains(sections: &[Section]) -> Vec<CompileError> {
534 let mut errors = Vec::new();
535 let mut seen: Vec<(&str, &[Transform], &str)> = Vec::new();
536
537 for section in sections {
538 for group in §ion.groups {
539 match seen
540 .iter()
541 .find(|(name, _, _)| *name == group.name.as_str())
542 {
543 Some((_, chain, first)) if *chain != group.transforms.as_slice() => {
544 errors.push(error(
545 1,
546 format!(
547 "group '{}' declares a different chain in '{}' than in '{first}' — \
548 a bus is one bus for the whole piece, so its filters cannot change \
549 between sections",
550 group.name, section.name
551 ),
552 ));
553 }
554 Some(_) => {}
555 None => seen.push((&group.name, &group.transforms, §ion.name)),
556 }
557 }
558 }
559
560 errors
561}
562
563fn check_name_collisions(sections: &[Section], throughout: &[PatternDef]) -> Vec<CompileError> {
569 let mut errors = Vec::new();
570
571 for section in sections {
572 for group in §ion.groups {
573 if section.patterns.iter().any(|pattern| {
574 pattern.name == group.name && pattern.group.as_deref() != Some(&group.name)
575 }) {
576 errors.push(error(
577 1,
578 format!(
579 "'{}' names both a group and a pattern in section '{}'; pick two names",
580 group.name, section.name
581 ),
582 ));
583 }
584 }
585 if let Some(clash) = throughout.iter().find(|line| {
586 section
587 .patterns
588 .iter()
589 .any(|member| member.name == line.name)
590 }) {
591 errors.push(error(
592 1,
593 format!(
594 "'{}' is both a line that sounds throughout the piece and a member of \
595 section '{}' — one of them would silence the other",
596 clash.name, section.name
597 ),
598 ));
599 }
600 if sections
601 .iter()
602 .any(|other| other.patterns.iter().any(|p| p.name == section.name))
603 {
604 errors.push(error(
605 1,
606 format!(
607 "'{}' names both a section and a pattern; pick two names",
608 section.name
609 ),
610 ));
611 }
612 }
613
614 errors
615}
616
617#[cfg(test)]
618mod tests;