use super::{FunctionContext, FunctionExecutor};
use crate::language::syntax::ast::nodes::Value;
use anyhow::{Result, anyhow};
pub struct NoteFunction;
impl FunctionExecutor for NoteFunction {
fn name(&self) -> &str {
"note"
}
fn execute(&self, context: &mut FunctionContext, args: &[Value]) -> Result<()> {
if args.is_empty() {
return Err(anyhow!("note() requires at least 1 argument (note name)"));
}
let note_name = match &args[0] {
Value::String(s) | Value::Identifier(s) => s.clone(),
_ => return Err(anyhow!("note() first argument must be a note name")),
};
context.set("note", Value::String(note_name.clone()));
Ok(())
}
}
pub fn parse_note_to_midi(note: &str) -> Result<u8> {
let note = note.to_uppercase();
let base_char = note.chars().next().ok_or_else(|| anyhow!("Empty note"))?;
let base_note = match base_char {
'C' => 0,
'D' => 2,
'E' => 4,
'F' => 5,
'G' => 7,
'A' => 9,
'B' => 11,
_ => return Err(anyhow!("Invalid note: {}", base_char)),
};
let mut offset = 0;
let mut octave_start = 1;
if note.len() > 1 {
match note.chars().nth(1) {
Some('#') => {
offset = 1;
octave_start = 2;
}
Some('b') | Some('B') => {
offset = -1;
octave_start = 2;
}
_ => {}
}
}
let octave_str = ¬e[octave_start..];
let octave: i32 = octave_str
.parse()
.map_err(|_| anyhow!("Invalid octave: {}", octave_str))?;
let midi = ((octave + 1) * 12) + base_note + offset;
if midi < 0 || midi > 127 {
return Err(anyhow!("MIDI note out of range: {}", midi));
}
Ok(midi as u8)
}
#[cfg(test)]
#[path = "test_note.rs"]
mod tests;