Skip to content

Commit

Permalink
add new notes_iter method to Lua Scale
Browse files Browse the repository at this point in the history
  • Loading branch information
emuell committed Sep 30, 2024
1 parent 6e7eec0 commit 5707c2d
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 1 deletion.
86 changes: 86 additions & 0 deletions src/bindings/scale.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,46 @@ impl LuaUserData for Scale {
},
);

methods.add_method(
"notes_iter",
|lua, this, count_value: LuaValue| -> LuaResult<LuaFunction> {
let mut max_notes = usize::MAX;
if !count_value.is_nil() {
if let Some(value) = count_value.as_integer() {
if value <= 0 {
return Err(bad_argument_error(
"notes_iter",
"count",
1,
"expecting a number > 0",
));
}
max_notes = value as usize;
} else {
return Err(bad_argument_error(
"notes_iter",
"count",
1,
"expecting a number or nil",
));
}
}
lua.create_function_mut({
let mut iter = this.notes_iter().enumerate();
move |_lua: &Lua, _args: LuaMultiValue| -> LuaResult<LuaValue> {
if let Some((index, note)) = iter.next() {
if index < max_notes {
Ok(LuaValue::Integer(note as LuaInteger))
} else {
Ok(LuaNil)
}
} else {
Ok(LuaNil)
}
}
})
},
);
methods.add_method(
"degree",
|_lua, this, args: LuaMultiValue| -> LuaResult<LuaMultiValue> {
Expand Down Expand Up @@ -224,6 +264,52 @@ mod test {
Ok(())
}

#[test]
fn scale_notes_iter() -> LuaResult<()> {
let lua = new_test_engine()?;

assert!(lua
.load(r#"scale("c4", "minor"):notes_iter(0)"#)
.exec()
.is_err());
assert!(lua
.load(r#"scale("c4", "minor"):notes_iter(1)"#)
.exec()
.is_ok());

assert_eq!(
lua.load(
r#"local cmin = scale("c4", "minor")
local iter = cmin:notes_iter(3)
return iter(), iter(), iter(), iter()
"#
)
.eval::<LuaMultiValue>()?
.into_vec()
.iter()
.map(|v| v.as_i32().unwrap_or(0))
.collect::<Vec<i32>>(),
vec![48, 50, 51, 0]
);

assert_eq!(
lua.load(
r#"local cmin = scale("f10", "minor")
local iter = cmin:notes_iter()
return iter(), iter(), iter()
"#
)
.eval::<LuaMultiValue>()?
.into_vec()
.iter()
.map(|v| v.as_i32().unwrap_or(0))
.collect::<Vec<i32>>(),
vec![125, 127, 0]
);

Ok(())
}

#[test]
fn scale_fit() -> LuaResult<()> {
let lua = new_test_engine()?;
Expand Down
24 changes: 23 additions & 1 deletion types/nerdo/library/scale.lua
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ function Scale:chord(degree, note_count) end
---
---### examples:
---```lua
---local cmmaj = scale("c4", "major")
---local cmin = scale("c4", "minor")
---cmin:degree(1) --> 48 ("c4")
---cmin:degree(5) --> 55
---cmin:degree("i", "iii", "v") --> 48, 50, 55
Expand All @@ -47,6 +47,28 @@ function Scale:chord(degree, note_count) end
---@nodiscard
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.
---
---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.
---
---### examples:
---```lua
-----collect 16 notes of a c major scale
---local cmaj = scale("c4", "major")
---local notes = {}
---for note in cmin:notes_iter(16) do
--- table.insert(notes, note)
---end
----- same using the `pattern` library
---local notes = pattern.new(16):init(cmaj.notes_iter())
---```
---@param count integer?
---@return fun():integer|nil
function Scale:notes_iter(count) end

---Fit given note value(s) into scale by moving them to the nearest note in the scale.
---
---### examples:
Expand Down

0 comments on commit 5707c2d

Please sign in to comment.