1use 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
15pub trait BuiltinFunction {
20 fn name(&self) -> &'static str;
22
23 fn call(&self, args: &[Value], kwargs: &[(String, Value)], span: &Span) -> Result<Value>;
36}
37
38pub 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
58pub 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
78pub 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
131pub 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 if let Some(axis_pc) = <Value as ValueExtractor<PitchClass>>::extract(&values[1]) {
156 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 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
200pub 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 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
246pub 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
295pub 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
333pub 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
386pub 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
410pub 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
460pub 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 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 fn register(&mut self, func: Box<dyn BuiltinFunction>) {
500 let name = func.name().to_string();
501 self.functions.insert(name, func);
502 }
503
504 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 pub fn get_function_names(&self) -> Vec<&str> {
536 self.functions.keys().map(|s| s.as_str()).collect()
537 }
538}