diff --git a/src/bindings.rs b/src/bindings.rs index dc2b49c..1b22124 100644 --- a/src/bindings.rs +++ b/src/bindings.rs @@ -22,6 +22,7 @@ use self::{ }; use crate::{ + chord::unique_chord_names, event::InstrumentId, rhythm::{beat_time::BeatTimeRhythm, second_time::SecondTimeRhythm, Rhythm}, time::BeatTimeBase, @@ -218,6 +219,13 @@ fn register_global_bindings( )?, )?; + globals.raw_set( + "scale_names", + lua.create_function(|lua, _args: ()| -> LuaResult { + lua.create_sequence_from(Scale::mode_names()) + })?, + )?; + // function note(args...) globals.raw_set( "note", @@ -236,6 +244,14 @@ fn register_global_bindings( )?, )?; + // function chord_names() + globals.raw_set( + "chord_names", + lua.create_function(|lua, _args: LuaMultiValue| -> LuaResult { + lua.create_sequence_from(unique_chord_names()) + })?, + )?; + // function sequence(args...) globals.raw_set( "sequence", diff --git a/src/bindings/note.rs b/src/bindings/note.rs index e7b5fd8..bf8df89 100644 --- a/src/bindings/note.rs +++ b/src/bindings/note.rs @@ -354,6 +354,11 @@ mod test { assert!(evaluate_note_userdata(&lua, r#"note("c4'maj'")"#).is_err()); assert!(evaluate_note_userdata(&lua, r#"note("c4'maj xx")"#).is_err()); + assert!( + evaluate_note_userdata(&lua, r#"note(string.format("c4'%s", chord_names()[1]))"#) + .is_ok() + ); + assert_eq!( evaluate_note_userdata(&lua, r#"note("c'maj")"#)?.notes, vec![new_note("c4"), new_note("e4"), new_note("g4"),] diff --git a/src/bindings/scale.rs b/src/bindings/scale.rs index bc90cf6..48df54c 100644 --- a/src/bindings/scale.rs +++ b/src/bindings/scale.rs @@ -153,6 +153,12 @@ mod test { fn scale() -> LuaResult<()> { let lua = new_test_engine()?; + // scale_names () + assert!(lua + .load(r#"scale_names()[1]"#) + .eval::() + .is_ok_and(|v| v.to_str().is_ok_and(|s| s == "chromatic"))); + // Scale (note, mode_name) assert!(lua .load(r#"scale("c", "wurst")"#) diff --git a/src/chord.rs b/src/chord.rs index fd794a1..aa26ded 100644 --- a/src/chord.rs +++ b/src/chord.rs @@ -162,7 +162,7 @@ lazy_static! { ("minorMajor7", Vec::from(MINOR_MAJOR7)), ("minMaj7", Vec::from(MINOR_MAJOR7)), ("mM7", Vec::from(MINOR_MAJOR7)), - ("−^7", Vec::from(MINOR_MAJOR7)), + ("-^7", Vec::from(MINOR_MAJOR7)), ("five", Vec::from(FIVE)), ("5", Vec::from(FIVE)), ("sus2", Vec::from(SUS2)), @@ -183,12 +183,32 @@ pub fn chords() -> HashMap<&'static str, Vec> { } /// return list of all known chord names. -pub fn chord_names() -> String { - CHORD_TABLE +pub fn chord_names() -> Vec { + let mut chords = CHORD_TABLE .keys() .map(|name| String::from(*name)) - .collect::>() - .join(", ") + .collect::>(); + chords.sort(); + chords +} + +/// return list of all known chord names with unique intervals. +pub fn unique_chord_names() -> Vec { + let mut unique_chords = CHORD_TABLE.iter().collect::>(); + // prefere longer names, then dedup + unique_chords.sort_by(|(an, _), (bn, _)| bn.len().cmp(&an.len())); + unique_chords.sort_by(|(_, ai), (_, bi)| ai.cmp(bi)); + // dedup, but keep add/dom duplicates + unique_chords.dedup_by(|(an, ai), (_, bi)| { + ai == bi && !(an.starts_with("dom") || an.starts_with("add")) + }); + // get names and sort + let mut chords = unique_chords + .into_iter() + .map(|(name, _)| String::from(*name)) + .collect::>(); + chords.sort(); + chords } /// return chord intervals for the given chord string or [] @@ -241,8 +261,8 @@ impl TryFrom<&str> for Chord { } let note = Note::try_from(note_part)?; let intervals = CHORD_TABLE.get(chord_part).ok_or(format!( - "invalid mode, valid modes are: {}", - chord_names() + "invalid chord mode, valid modes are: {}", + chord_names().join(",") ))?; return Ok(Self::new(note, intervals.clone())); } @@ -263,7 +283,7 @@ where fn try_from((note, mode): (N, &str)) -> Result { let intervals = CHORD_TABLE.get(mode).ok_or(format!( "Invalid chord mode, valid chords are: {}", - chord_names() + chord_names().join(",") ))?; Ok(Self::new(note, intervals.clone())) } diff --git a/types/nerdo/library/chord.lua b/types/nerdo/library/chord.lua index f60f748..498e4b9 100644 --- a/types/nerdo/library/chord.lua +++ b/types/nerdo/library/chord.lua @@ -7,12 +7,12 @@ error("Do not try to execute this file. It's just a type definition file.") ---------------------------------------------------------------------------------------------------- ---Available chords. ----@alias ChordName "major"|"augmented"|"six"|"sixNine"|"major7"|"major9"|"add9"|"major11"|"add11"|"major13"|"add13"|"dom7"|"dom9"|"dom11"|"dom13"|"7b5"|"7#5"|"7b9"|"9"|"nine"|"eleven"|"thirteen"|"minor"|"diminished"|"dim"|"minor#5"|"minor6"|"minor69"|"minor7b5"|"minor7"|"minor7#5"|"minor7b9"|"minor7#9"|"diminished7"|"minor9"|"minor11"|"minor13"|"minorMajor7"|"five"|"sus2"|"sus4"|"7sus2"|"7sus4"|"9sus2"|"9sus4"|string +---@alias ChordName "major"|"major7"|"major9"|"major11"|"major13"|"minor"|"minor#5"|"minor6"|"minor69"|"minor7b5"|"minor7"|"minor7#5"|"minor7b9"|"minor7#9"|"minor9"|"minor11"|"minor13"|"minorMajor7"|"add9"|"add11"|"add13"|"dom7"|"dom9"|"dom11"|"dom13"|"7b5"|"7#5"|"7b9"|"five"|"six"|"sixNine"|"nine"|"eleven"|"thirteen"|"augmented"|"diminished"|"diminished7"|"sus2"|"sus4"|"7sus2"|"7sus4"|"9sus2"|"9sus4"|string ---Create a new chord from the given key notes and a chord name or an array of custom intervals. --- ----NB: Chords also can also be defined via strings in function `note` and via the a scale's ----`chord` function. See examples below. +---NB: Chords also can also be defined via strings in function `note` and via the a scale's +---`chord` function. See examples below. --- ---Chord names also can be shortened by using the following synonyms: ---- "min" | "m" | "-" -> "minor" @@ -46,3 +46,7 @@ error("Do not try to execute this file. It's just a type definition file.") ---@nodiscard ---@overload fun(key: NoteValue, intervals: integer[]): Note function chord(key, mode) end + +---Return supported chord names. +---@return string[] +function chord_names() end diff --git a/types/nerdo/library/scale.lua b/types/nerdo/library/scale.lua index e8d41f5..0dcb2c5 100644 --- a/types/nerdo/library/scale.lua +++ b/types/nerdo/library/scale.lua @@ -49,7 +49,7 @@ function Scale:degree(...) end ---Create an iterator function that returns up to `count` notes from the scale. ---If the count exceeds the number of notes in the scale, then notes from the next ----octave are taken. +---octave are taken. --- ---The iterator function returns nil when the maximum number of MIDI notes has been ---reached, or when the given optional `count` parameter has been exceeded. @@ -83,12 +83,12 @@ function Scale:fit(...) end ---------------------------------------------------------------------------------------------------- ----Available scales. +---Available scale mode names. ---@alias ScaleMode "chromatic"|"major"|"minor"|"natural major"|"natural minor"|"pentatonic major"|"pentatonic minor"|"pentatonic egyptian"|"blues major"|"blues minor"|"whole tone"|"augmented"|"prometheus"|"tritone"|"harmonic major"|"harmonic minor"|"melodic minor"|"all minor"|"dorian"|"phrygian"|"phrygian dominant"|"lydian"|"lydian augmented"|"mixolydian"|"locrian"|"locrian major"|"super locrian"|"neapolitan major"|"neapolitan minor"|"romanian minor"|"spanish gypsy"|"hungarian gypsy"|"enigmatic"|"overtone"|"diminished half"|"diminished whole"|"spanish eight-tone"|"nine-tone"|string ---Create a new scale from the given key notes and a mode name. --- ----Scale names can also be shortened by using the following synonyms: +---Mode names can also be shortened by using the following synonyms: ---- "8-tone" -> "eight-tone" ---- "9-tone" -> "nine-tone" ---- "aug" -> "augmented" @@ -125,3 +125,7 @@ function scale(key, mode) end ---@return Scale ---@nodiscard function scale(key, intervals) end + +---Return supported scale mode names. +---@return string[] +function scale_names() end \ No newline at end of file