devalang_wasm/engine/functions/
note.rs1use super::{FunctionContext, FunctionExecutor};
17use crate::language::syntax::ast::nodes::Value;
18use anyhow::{Result, anyhow};
19
20pub struct NoteFunction;
21
22impl FunctionExecutor for NoteFunction {
23 fn name(&self) -> &str {
24 "note"
25 }
26
27 fn execute(&self, context: &mut FunctionContext, args: &[Value]) -> Result<()> {
28 if args.is_empty() {
29 return Err(anyhow!("note() requires at least 1 argument (note name)"));
30 }
31
32 let note_name = match &args[0] {
34 Value::String(s) | Value::Identifier(s) => s.clone(),
35 _ => return Err(anyhow!("note() first argument must be a note name")),
36 };
37
38 context.set("note", Value::String(note_name.clone()));
40
41 Ok(())
42 }
43}
44
45pub fn parse_note_to_midi(note: &str) -> Result<u8> {
48 let note = note.to_uppercase();
49
50 let base_char = note.chars().next().ok_or_else(|| anyhow!("Empty note"))?;
52 let base_note = match base_char {
53 'C' => 0,
54 'D' => 2,
55 'E' => 4,
56 'F' => 5,
57 'G' => 7,
58 'A' => 9,
59 'B' => 11,
60 _ => return Err(anyhow!("Invalid note: {}", base_char)),
61 };
62
63 let mut offset = 0;
65 let mut octave_start = 1;
66
67 if note.len() > 1 {
68 match note.chars().nth(1) {
69 Some('#') => {
70 offset = 1;
71 octave_start = 2;
72 }
73 Some('b') | Some('B') => {
74 offset = -1;
75 octave_start = 2;
76 }
77 _ => {}
78 }
79 }
80
81 let octave_str = ¬e[octave_start..];
83 let octave: i32 = octave_str
84 .parse()
85 .map_err(|_| anyhow!("Invalid octave: {}", octave_str))?;
86
87 let midi = ((octave + 1) * 12) + base_note + offset;
89
90 if midi < 0 || midi > 127 {
91 return Err(anyhow!("MIDI note out of range: {}", midi));
92 }
93
94 Ok(midi as u8)
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100
101 #[test]
102 fn test_parse_note_to_midi() {
103 assert_eq!(parse_note_to_midi("C4").unwrap(), 60);
104 assert_eq!(parse_note_to_midi("C#4").unwrap(), 61);
105 assert_eq!(parse_note_to_midi("D4").unwrap(), 62);
106 assert_eq!(parse_note_to_midi("A4").unwrap(), 69);
107 assert_eq!(parse_note_to_midi("C5").unwrap(), 72);
108 assert_eq!(parse_note_to_midi("Bb4").unwrap(), 70);
109 }
110}