Skip to main content

carmen_lang/interpreter/
builtins.rs

1//! Builtin functions for the Carmen interpreter.
2//!
3//! This module provides the core library of musical analysis and transformation
4//! functions available to Carmen programs. Functions include pitch operations,
5//! set theory analysis, transposition, inversion, and other music-theoretical
6//! computations.
7
8use super::Value;
9use crate::core::traits::{Invert as CoreInvert, Transpose as CoreTranspose};
10use crate::core::*;
11use crate::errors::{AddSpan, ErrorSource, Result, Span};
12use crate::interpreter::arg_extraction::*;
13use crate::{extract_args, extract_two};
14
15/// Trait defining the interface for builtin functions.
16///
17/// All builtin functions must implement this trait to be callable from Carmen code.
18/// The trait provides a uniform interface for function dispatch and error handling.
19pub trait BuiltinFunction {
20    /// Returns the function's name.
21    fn name(&self) -> &'static str;
22
23    /// Executes the function with the given arguments.
24    ///
25    /// # Arguments
26    /// * `args` - Positional arguments passed to the function
27    /// * `kwargs` - Named arguments (keyword arguments) passed to the function
28    /// * `span` - Source location for error reporting
29    ///
30    /// # Returns
31    /// The result of the function execution as a `Value`
32    ///
33    /// # Errors
34    /// Returns an error if argument validation fails or the operation cannot be completed
35    fn call(&self, args: &[Value], kwargs: &[(String, Value)], span: &Span) -> Result<Value>;
36}
37
38/// Calculates the interval in semitones between two pitches.
39///
40/// # Arguments
41/// * `pitch1` - First pitch
42/// * `pitch2` - Second pitch
43/// * `ordered` (optional) - If `true`, returns signed interval; if `false`, returns absolute interval
44pub struct PitchInterval;
45impl BuiltinFunction for PitchInterval {
46    fn name(&self) -> &'static str {
47        "pitch_interval"
48    }
49
50    fn call(&self, args: &[Value], kwargs: &[(String, Value)], span: &Span) -> Result<Value> {
51        let (p1, p2) = extract_two!(args, span, Pitch);
52        let ordered = extract_kwarg(kwargs, "ordered", false);
53        let interval = crate::core::pitch_interval(&p1, &p2, ordered);
54        Ok(Value::Number(interval as f64))
55    }
56}
57
58/// Calculates the interval in semitones between two pitch classes.
59///
60/// # Arguments
61/// * `pc1` - First pitch class (0-11)
62/// * `pc2` - Second pitch class (0-11)
63/// * `ordered` (optional) - If `true`, returns signed interval; if `false`, returns absolute interval
64pub struct PitchClassInterval;
65impl BuiltinFunction for PitchClassInterval {
66    fn name(&self) -> &'static str {
67        "pitch_class_interval"
68    }
69
70    fn call(&self, args: &[Value], kwargs: &[(String, Value)], span: &Span) -> Result<Value> {
71        let (pc1, pc2) = extract_two!(args, span, PitchClass);
72        let ordered = extract_kwarg(kwargs, "ordered", false);
73        let interval = crate::core::pitch_class_interval(&pc1, &pc2, ordered);
74        Ok(Value::Number(interval as f64))
75    }
76}
77
78/// Transposes musical objects by a specified number of semitones.
79///
80/// This function can transpose pitches, pitch classes, pitch class sets, chords,
81/// musical events, and lists of musical objects.
82///
83/// # Arguments
84/// * `object` - The musical object to transpose
85/// * `semitones` - Number of semitones to transpose (positive = up, negative = down)
86pub struct Transpose;
87impl BuiltinFunction for Transpose {
88    fn name(&self) -> &'static str {
89        "transpose"
90    }
91
92    fn call(&self, args: &[Value], _kwargs: &[(String, Value)], span: &Span) -> Result<Value> {
93        let values = extract_mixed_args(args, span)?;
94
95        if values.len() < 2 {
96            return Err(
97                ErrorSource::Argument("transpose requires 2 arguments".to_string())
98                    .with_span(*span),
99            );
100        }
101
102        let semitones = match <Value as ValueExtractor<f64>>::extract(&values[1]) {
103            Some(n) => n as i32,
104            None => {
105                return Err(
106                    ErrorSource::Argument("Second argument must be a number".to_string())
107                        .with_span(*span),
108                )
109            }
110        };
111
112        match &values[0] {
113            Value::Pitch(p) => Ok(Value::Pitch(p.transpose(semitones))),
114            Value::PitchClass(pc) => Ok(Value::PitchClass(pc.transpose(semitones))),
115            Value::PitchClassSet(pcs) => Ok(Value::PitchClassSet(pcs.transpose(semitones))),
116            Value::Chord(chord) => Ok(Value::Chord(chord.transpose(semitones))),
117            Value::MusicalEvent(event) => Ok(Value::MusicalEvent(event.transpose(semitones))),
118            Value::Rest => Ok(Value::Rest),
119            Value::List(events) => {
120                let transposed: Result<Vec<Value>> = events
121                    .iter()
122                    .map(|event| self.call(&[event.clone(), values[1].clone()], &[], span))
123                    .collect();
124                Ok(Value::List(transposed?))
125            }
126            _ => Err(ErrorSource::Type("Cannot transpose this type".to_string()).with_span(*span)),
127        }
128    }
129}
130
131/// Inverts musical objects around a specified axis pitch class.
132///
133/// Inversion reflects pitches or pitch classes around an axis, preserving
134/// interval relationships but reversing their direction.
135///
136/// # Arguments
137/// * `object` - The musical object to invert
138/// * `axis` - The pitch class to use as the inversion axis
139pub struct Invert;
140impl BuiltinFunction for Invert {
141    fn name(&self) -> &'static str {
142        "invert"
143    }
144
145    fn call(&self, args: &[Value], _kwargs: &[(String, Value)], span: &Span) -> Result<Value> {
146        let values = extract_mixed_args(args, span)?;
147
148        if values.len() < 2 {
149            return Err(
150                ErrorSource::Argument("invert requires 2 arguments".to_string()).with_span(*span),
151            );
152        }
153
154        // Try to extract axis as PitchClass first, then as Pitch
155        if let Some(axis_pc) = <Value as ValueExtractor<PitchClass>>::extract(&values[1]) {
156            // Chromatic inversion around pitch class axis
157            match &values[0] {
158                Value::Number(n) => {
159                    if *n >= 0.0 && *n <= 11.0 && n.fract() == 0.0 {
160                        let pc = PitchClass::new(*n as u8);
161                        let inverted = pc.invert(&axis_pc);
162                        Ok(Value::Number(inverted.0 as f64))
163                    } else {
164                        Err(ErrorSource::Type(
165                            "Number must be integer 0-11 for pitch class inversion".to_string(),
166                        )
167                        .with_span(*span))
168                    }
169                }
170                Value::PitchClass(pc) => Ok(Value::PitchClass(pc.invert(&axis_pc))),
171                Value::PitchClassSet(pcs) => Ok(Value::PitchClassSet(pcs.invert(&axis_pc))),
172                Value::Pitch(p) => {
173                    let inverted_pc = p.pitch_class.invert(&axis_pc);
174                    let inverted_pitch = Pitch {
175                        pitch_class: inverted_pc,
176                        octave: p.octave,
177                    };
178                    Ok(Value::Pitch(inverted_pitch))
179                }
180                _ => Err(ErrorSource::Type("Cannot invert this type".to_string()).with_span(*span)),
181            }
182        } else if let Some(axis_pitch) = <Value as ValueExtractor<Pitch>>::extract(&values[1]) {
183            // Pitch inversion around specific pitch axis
184            match &values[0] {
185                Value::Pitch(p) => Ok(Value::Pitch(p.invert(&axis_pitch))),
186                _ => Err(ErrorSource::Type(
187                    "When using a pitch as axis, can only invert pitches".to_string(),
188                )
189                .with_span(*span)),
190            }
191        } else {
192            Err(
193                ErrorSource::Argument("Second argument must be a pitch class or pitch".to_string())
194                    .with_span(*span),
195            )
196        }
197    }
198}
199
200/// Computes the normal form of a pitch class set.
201///
202/// Normal form is the most compact representation of a pitch class set,
203/// starting from the pitch class that produces the smallest intervals.
204///
205/// # Arguments
206/// * `set` - A pitch class set or collection of pitch classes
207pub struct NormalForm;
208impl BuiltinFunction for NormalForm {
209    fn name(&self) -> &'static str {
210        "normal_form"
211    }
212
213    fn call(&self, args: &[Value], _kwargs: &[(String, Value)], span: &Span) -> Result<Value> {
214        if args.len() != 1 {
215            return Err(
216                ErrorSource::Argument("normal_form requires 1 argument".to_string())
217                    .with_span(*span),
218            );
219        }
220
221        match &args[0] {
222            Value::PitchClassSet(pcs) => {
223                let normal = pcs.normal_form();
224                Ok(Value::PitchClassSet(normal))
225            }
226            Value::Set(values) => {
227                // Convert set of numbers to pitch class set
228                let mut classes = Vec::new();
229                for val in values {
230                    if let Value::PitchClass(n) = val {
231                        classes.push(*n);
232                    }
233                }
234                let pcs = PitchClassSet::new(classes);
235                let normal = pcs.normal_form();
236                Ok(Value::PitchClassSet(normal))
237            }
238            _ => Err(
239                ErrorSource::Type("normal_form requires a pitch class set".to_string())
240                    .with_span(*span),
241            ),
242        }
243    }
244}
245
246/// Computes the prime form of a pitch class set.
247///
248/// Prime form is the most compact representation of a set class,
249/// considering both the set and its inversion, transposed to start at 0.
250///
251/// # Arguments
252/// * `set` - A pitch class set or collection of pitch classes/numbers
253pub struct PrimeForm;
254impl BuiltinFunction for PrimeForm {
255    fn name(&self) -> &'static str {
256        "prime_form"
257    }
258
259    fn call(&self, args: &[Value], _kwargs: &[(String, Value)], span: &Span) -> Result<Value> {
260        if args.len() != 1 {
261            return Err(
262                ErrorSource::Argument("prime_form requires 1 argument".to_string())
263                    .with_span(*span),
264            );
265        }
266
267        match &args[0] {
268            Value::PitchClassSet(pcs) => {
269                let prime = pcs.prime_form();
270                Ok(Value::PitchClassSet(prime))
271            }
272            Value::Set(values) => {
273                let mut classes = Vec::new();
274                for val in values {
275                    match val {
276                        Value::PitchClass(pc) => classes.push(*pc),
277                        Value::Number(n) if *n >= 0.0 && *n <= 11.0 && n.fract() == 0.0 => {
278                            classes.push(PitchClass::new(*n as u8));
279                        }
280                        _ => {}
281                    }
282                }
283                let pcs = PitchClassSet::new(classes);
284                let prime = pcs.prime_form();
285                Ok(Value::PitchClassSet(prime))
286            }
287            _ => Err(
288                ErrorSource::Type("prime_form requires a pitch class set".to_string())
289                    .with_span(*span),
290            ),
291        }
292    }
293}
294
295/// Computes the interval class vector of a pitch class set.
296///
297/// The interval class vector shows the frequency of each interval class (1-6)
298/// within the set, providing a compact representation of its intervallic content.
299///
300/// # Arguments
301/// * `set` - A pitch class set
302///
303/// # Returns
304/// A list of 6 numbers representing the count of each interval class
305pub struct IntervalClassVector;
306impl BuiltinFunction for IntervalClassVector {
307    fn name(&self) -> &'static str {
308        "interval_class_vector"
309    }
310
311    fn call(&self, args: &[Value], _kwargs: &[(String, Value)], span: &Span) -> Result<Value> {
312        if args.len() != 1 {
313            return Err(ErrorSource::Argument(
314                "interval_class_vector requires 1 argument".to_string(),
315            )
316            .with_span(*span));
317        }
318
319        match &args[0] {
320            Value::PitchClassSet(pcs) => {
321                let icv = pcs.interval_class_vector();
322                let values: Vec<Value> = icv.into_iter().map(|n| Value::Number(n as f64)).collect();
323                Ok(Value::List(values))
324            }
325            _ => Err(ErrorSource::Type(
326                "interval_class_vector requires a pitch class set".to_string(),
327            )
328            .with_span(*span)),
329        }
330    }
331}
332
333/// Computes the set class of a pitch class set.
334///
335/// A set class contains all transpositions and inversions of a given pitch class set.
336/// This function returns all members of the set class.
337///
338/// # Arguments
339/// * `set` - A pitch class set or collection of pitch classes/numbers
340///
341/// # Returns
342/// A list of pitch class sets representing all members of the set class
343pub struct SetClass;
344impl BuiltinFunction for SetClass {
345    fn name(&self) -> &'static str {
346        "set_class"
347    }
348
349    fn call(&self, args: &[Value], _kwargs: &[(String, Value)], span: &Span) -> Result<Value> {
350        if args.len() != 1 {
351            return Err(
352                ErrorSource::Argument("set_class requires 1 argument".to_string()).with_span(*span),
353            );
354        }
355
356        match &args[0] {
357            Value::PitchClassSet(pcs) => {
358                let set_class = pcs.set_class();
359                let values: Vec<Value> = set_class.into_iter().map(Value::PitchClassSet).collect();
360                Ok(Value::List(values))
361            }
362            Value::Set(values) => {
363                let mut classes = Vec::new();
364                for val in values {
365                    match val {
366                        Value::PitchClass(pc) => classes.push(*pc),
367                        Value::Number(n) if *n >= 0.0 && *n <= 11.0 && n.fract() == 0.0 => {
368                            classes.push(PitchClass::new(*n as u8));
369                        }
370                        _ => {}
371                    }
372                }
373                let pcs = PitchClassSet::new(classes);
374                let set_class = pcs.set_class();
375                let values: Vec<Value> = set_class.into_iter().map(Value::PitchClassSet).collect();
376                Ok(Value::List(values))
377            }
378            _ => Err(
379                ErrorSource::Type("set_class requires a pitch class set".to_string())
380                    .with_span(*span),
381            ),
382        }
383    }
384}
385
386/// Calculates the interval class between two pitch classes.
387///
388/// Interval class reduces all intervals to their smallest form (1-6 semitones),
389/// treating intervals and their inversions as equivalent.
390///
391/// # Arguments
392/// * `pc1` - First pitch class
393/// * `pc2` - Second pitch class
394///
395/// # Returns
396/// An interval class number from 1 to 6
397pub struct IntervalClass;
398impl BuiltinFunction for IntervalClass {
399    fn name(&self) -> &'static str {
400        "interval_class"
401    }
402
403    fn call(&self, args: &[Value], _kwargs: &[(String, Value)], span: &Span) -> Result<Value> {
404        let (pc1, pc2) = extract_two!(args, span, PitchClass);
405        let ic = pc1.interval_class_to(&pc2);
406        Ok(Value::Number(ic as f64))
407    }
408}
409
410/// Computes the interval class content of a pitch class set.
411///
412/// This is similar to interval class vector but manually calculates the count
413/// of each interval class by examining all pairs within the set.
414///
415/// # Arguments
416/// * `set` - A pitch class set
417///
418/// # Returns
419/// A list of 6 numbers representing the count of each interval class (1-6)
420pub struct IntervalClassContent;
421impl BuiltinFunction for IntervalClassContent {
422    fn name(&self) -> &'static str {
423        "interval_class_content"
424    }
425
426    fn call(&self, args: &[Value], _kwargs: &[(String, Value)], span: &Span) -> Result<Value> {
427        if args.len() != 1 {
428            return Err(ErrorSource::Argument(
429                "interval_class_content requires 1 argument".to_string(),
430            )
431            .with_span(*span));
432        }
433
434        match &args[0] {
435            Value::PitchClassSet(pcs) => {
436                let mut content = std::collections::HashMap::new();
437                let classes: Vec<PitchClass> = pcs.classes.iter().cloned().collect();
438
439                for i in 0..classes.len() {
440                    for j in (i + 1)..classes.len() {
441                        let ic = classes[i].interval_class_to(&classes[j]);
442                        *content.entry(ic).or_insert(0) += 1;
443                    }
444                }
445
446                let result: Vec<Value> = (1..=6)
447                    .map(|i| Value::Number(*content.get(&i).unwrap_or(&0) as f64))
448                    .collect();
449
450                Ok(Value::List(result))
451            }
452            _ => Err(ErrorSource::Type(
453                "interval_class_content requires a pitch class set".to_string(),
454            )
455            .with_span(*span)),
456        }
457    }
458}
459
460/// Registry for all builtin functions available in the Carmen interpreter.
461///
462/// The registry manages function registration, lookup, and dispatch. It provides
463/// a centralized way to access all builtin functions and their aliases.
464pub struct BuiltinRegistry {
465    functions: std::collections::HashMap<String, Box<dyn BuiltinFunction>>,
466}
467
468impl Default for BuiltinRegistry {
469    fn default() -> Self {
470        Self::new()
471    }
472}
473
474impl BuiltinRegistry {
475    /// Creates a new registry with all builtin functions registered.
476    pub fn new() -> Self {
477        let mut registry = Self {
478            functions: std::collections::HashMap::new(),
479        };
480
481        registry.register(Box::new(PitchInterval));
482        registry.register(Box::new(Transpose));
483        registry.register(Box::new(Invert));
484        registry.register(Box::new(PitchClassInterval));
485        registry.register(Box::new(NormalForm));
486        registry.register(Box::new(PrimeForm));
487        registry.register(Box::new(IntervalClassVector));
488        registry.register(Box::new(SetClass));
489        registry.register(Box::new(IntervalClass));
490        registry.register(Box::new(IntervalClassContent));
491
492        registry
493    }
494
495    /// Registers a builtin function in the registry.
496    ///
497    /// # Arguments
498    /// * `func` - The function implementation to register
499    fn register(&mut self, func: Box<dyn BuiltinFunction>) {
500        let name = func.name().to_string();
501        self.functions.insert(name, func);
502    }
503
504    /// Calls a builtin function by name with the provided arguments.
505    ///
506    /// # Arguments
507    /// * `name` - The function name to call
508    /// * `args` - Positional arguments to pass to the function
509    /// * `kwargs` - Named arguments to pass to the function
510    /// * `span` - Source location for error reporting
511    ///
512    /// # Returns
513    /// The result of the function call
514    ///
515    /// # Errors
516    /// Returns an error if the function is not found or the call fails
517    pub fn call(
518        &self,
519        name: &str,
520        args: &[Value],
521        kwargs: &[(String, Value)],
522        span: &Span,
523    ) -> Result<Value> {
524        self.functions
525            .get(name)
526            .ok_or_else(|| {
527                ErrorSource::Runtime(format!("Unknown builtin function: {name}")).with_span(*span)
528            })?
529            .call(args, kwargs, span)
530    }
531
532    /// Returns a list of all registered function names.
533    ///
534    /// This is useful for auto-completion and help systems.
535    pub fn get_function_names(&self) -> Vec<&str> {
536        self.functions.keys().map(|s| s.as_str()).collect()
537    }
538}