use std::path::Path;
const EXAMPLE_NAMES: &[&str] = &[
"foundation_loop",
"capability_expansion",
"algorithm_generators",
"synthesis_algorithms",
"effects_chain",
"phrase_arrangement",
"playback",
"solo_piano",
"mini_symphony",
"fresh_pop",
"kaze_ensemble",
"farewell_ensemble",
"hardcore_edm",
"kawaii_bass_3min",
"moonlight_golden_hall",
];
const FULL_SONG_EXAMPLE_NAMES: &[&str] = &[
"solo_piano",
"mini_symphony",
"fresh_pop",
"kaze_ensemble",
"farewell_ensemble",
"hardcore_edm",
"kawaii_bass_3min",
"moonlight_golden_hall",
];
const WIKI_DOCUMENT_FILES: &[&str] = &[
"wiki/00-overview/project-scope.md",
"wiki/00-overview/mental-model.md",
"wiki/00-overview/glossary.md",
"wiki/01-lua-composition/getting-started.md",
"wiki/01-lua-composition/theory.md",
"wiki/01-lua-composition/arrangement.md",
"wiki/01-lua-composition/rhythm-drums.md",
"wiki/01-lua-composition/samples-midi.md",
"wiki/01-lua-composition/full-song-workflows.md",
"wiki/02-sound-design/instruments.md",
"wiki/02-sound-design/synthesis.md",
"wiki/02-sound-design/effects.md",
"wiki/02-sound-design/mixing.md",
"wiki/03-generation/generators.md",
"wiki/03-generation/mapping.md",
"wiki/03-generation/recipes.md",
"wiki/04-export/wav-flac-midi.md",
"wiki/04-export/loudness.md",
"wiki/04-export/pcm-streaming.md",
"wiki/04-export/performance.md",
"wiki/04-export/troubleshooting.md",
"wiki/05-rust-integration/embedding-lua.md",
"wiki/05-rust-integration/rust-builders.md",
"wiki/05-rust-integration/features.md",
"wiki/05-rust-integration/error-handling.md",
"wiki/06-reference/lua-api.md",
"wiki/06-reference/rust-api.md",
"wiki/06-reference/examples.md",
];
const TUTORIAL_WIKI_FILES: &[&str] = &[
"wiki/00-overview/project-scope.md",
"wiki/00-overview/mental-model.md",
"wiki/00-overview/glossary.md",
"wiki/01-lua-composition/getting-started.md",
"wiki/01-lua-composition/theory.md",
"wiki/01-lua-composition/arrangement.md",
"wiki/01-lua-composition/rhythm-drums.md",
"wiki/01-lua-composition/samples-midi.md",
"wiki/01-lua-composition/full-song-workflows.md",
"wiki/02-sound-design/instruments.md",
"wiki/02-sound-design/synthesis.md",
"wiki/02-sound-design/effects.md",
"wiki/02-sound-design/mixing.md",
"wiki/03-generation/generators.md",
"wiki/03-generation/mapping.md",
"wiki/03-generation/recipes.md",
"wiki/04-export/wav-flac-midi.md",
"wiki/04-export/loudness.md",
"wiki/04-export/pcm-streaming.md",
"wiki/04-export/performance.md",
"wiki/04-export/troubleshooting.md",
"wiki/05-rust-integration/embedding-lua.md",
"wiki/05-rust-integration/rust-builders.md",
"wiki/05-rust-integration/features.md",
"wiki/05-rust-integration/error-handling.md",
];
const REFERENCE_WIKI_FILES: &[&str] = &[
"wiki/06-reference/lua-api.md",
"wiki/06-reference/rust-api.md",
"wiki/06-reference/examples.md",
];
const LEGACY_WIKI_ENTRY_FILES: &[&str] = &[
"wiki/lua-composition-guide.md",
"wiki/lua-api.md",
"wiki/rust-api.md",
"wiki/export-loudness.md",
];
fn example_script(name: &str) -> String {
let dir = Path::new("examples").join(name);
let main = dir.join("main.rs");
assert!(main.is_file(), "{name} example should include main.rs");
assert!(
dir.join("script.lua").is_file(),
"{name} example should include script.lua"
);
assert!(
dir.join("output").is_dir(),
"{name} example should include an output directory"
);
assert!(
dir.join("output").join(".gitkeep").is_file(),
"{name} example output directory should be tracked"
);
let runner = std::fs::read_to_string(main).expect("example runner should be readable");
assert!(
runner.contains("register_package_loader"),
"{name} runner should register the pulse package loader"
);
assert!(
runner.contains("include_str!(\"script.lua\")"),
"{name} runner should embed its Lua script"
);
std::fs::read_to_string(dir.join("script.lua")).expect("example script should exist")
}
#[test]
fn examples_are_runnable_directories() {
for name in EXAMPLE_NAMES {
let script = example_script(name);
assert!(
script.contains("require(\"pulse\")"),
"{name} script should require pulse"
);
}
}
#[test]
fn lua_can_require_pulse_and_call_theory_functions() {
let lua = mlua::Lua::new();
mlua_pulse::register_package_loader(&lua).expect("pulse module should register");
lua.load(
r#"
local pulse = require("pulse")
assert(math.abs(pulse.note("A4") - 440.0) < 0.01)
assert(math.abs(pulse.note("C4") - 261.63) < 0.02)
local modes = pulse.modes()
assert(#modes >= 9)
local found_modes = {}
for _, mode in ipairs(modes) do
found_modes[mode] = true
end
assert(found_modes["ionian"])
assert(found_modes["dorian"])
assert(found_modes["aeolian"])
local chord_kinds = pulse.chord_kinds()
assert(#chord_kinds >= 14)
local found_chords = {}
for _, kind in ipairs(chord_kinds) do
found_chords[kind] = true
end
assert(found_chords["maj7"])
assert(found_chords["minor7"])
assert(found_chords["dominant7"])
local scale = pulse.scale("C4", "ionian")
assert(#scale == 8)
assert(scale[1] > 261 and scale[1] < 262)
local chord = pulse.chord("C4", "maj7")
assert(#chord == 4)
local shifted = pulse.transpose(chord, 12)
assert(#shifted == 4)
assert(shifted[1] > chord[1])
local shifted_note = pulse.transpose(chord[1], 12)
assert(shifted_note > chord[1])
"#,
)
.exec()
.expect("lua script should run");
}
#[test]
fn lua_meta_file_documents_public_api_surface() {
let meta = std::fs::read_to_string("types/pulse.meta.lua").expect("Lua meta file should exist");
for expected in [
"---@meta pulse",
"function pulse.note",
"function pulse.modes",
"function pulse.chord_kinds",
"---@class pulse.ExportOptions",
"---@class pulse.Sequence",
"function pulse.sequence",
"function Sequence:notes",
"---@class pulse.Song",
"function Song:export_wav",
"function Song:stream_pcm",
"Song:stream_pcm(options, callback)",
"---@class pulse.PcmChunk",
"function PcmChunk:sample",
"---@class pulse.SampleClip",
"function SampleClip:track",
"function SampleClip:volume",
"function SampleClip:pan",
"function SampleClip:effect",
"---@class pulse.MidiClip",
"function MidiClip:at",
"function MidiClip:repeat_times",
"function MidiClip:effect",
"function Phrase:add_midi",
"function Song:add_midi",
"---@class pulse.PlaybackEngine",
"---@class pulse.ImportedMidi",
"function ImportedMidi:at",
"function ImportedMidi:repeat_times",
"function ImportedMidi:effect",
"function ImportedMidi:stream_pcm",
"ImportedMidi:stream_pcm(options, callback)",
"function pulse.stream_pcm",
"pulse.stream_pcm(playable, options, callback)",
] {
assert!(
meta.contains(expected),
"types/pulse.meta.lua should contain {expected}"
);
}
}
#[test]
fn wiki_documents_midi_and_sample_arrangement() {
let readme = std::fs::read_to_string("README.md").expect("README should exist");
let lua_api = std::fs::read_to_string("wiki/06-reference/lua-api.md")
.expect("Lua reference should exist");
let rust_api = std::fs::read_to_string("wiki/06-reference/rust-api.md")
.expect("Rust reference should exist");
let samples_midi = std::fs::read_to_string("wiki/01-lua-composition/samples-midi.md")
.expect("sample and MIDI chapter should exist");
for expected in [
"MIDI 和采样都可以作为编曲素材",
"wiki/01-lua-composition/samples-midi.md",
"wiki/06-reference/lua-api.md",
"wiki/06-reference/rust-api.md",
] {
assert!(
readme.contains(expected),
"README.md should mention {expected}"
);
}
for expected in [
"`pulse.SampleClip`",
"`pulse.MidiClip`",
"Song:add_midi(midi)",
"MIDI 导出只保留 MIDI 文件能表达的事件",
] {
assert!(
lua_api.contains(expected),
"wiki/06-reference/lua-api.md should mention {expected}"
);
}
for expected in [
"pulse.import_midi(\"input.mid\"):at",
":add_midi(midi)",
"MIDI 导出不会包含采样音频",
] {
assert!(
samples_midi.contains(expected),
"wiki/01-lua-composition/samples-midi.md should mention {expected}"
);
}
for expected in [
"### `PulseMidiClip`",
".add_midi_clip(imported_clip)",
"MIDI 导出不会嵌入采样音频",
] {
assert!(
rust_api.contains(expected),
"wiki/06-reference/rust-api.md should mention {expected}"
);
}
}
#[test]
fn documentation_tree_is_complete_and_linked() {
let readme = std::fs::read_to_string("README.md").expect("README should exist");
let wiki_index = std::fs::read_to_string("wiki/README.md").expect("wiki index should exist");
for expected in [
"wiki/README.md",
"wiki/00-overview/project-scope.md",
"wiki/01-lua-composition/getting-started.md",
"wiki/02-sound-design/instruments.md",
"wiki/03-generation/generators.md",
"wiki/04-export/wav-flac-midi.md",
"wiki/05-rust-integration/embedding-lua.md",
"wiki/06-reference/lua-api.md",
] {
assert!(
readme.contains(expected),
"README.md should link {expected}"
);
}
for path in WIKI_DOCUMENT_FILES {
assert!(Path::new(path).is_file(), "{path} should exist");
assert!(
wiki_index.contains(path),
"wiki/README.md should link every document: {path}"
);
let content = std::fs::read_to_string(path).expect("wiki document should be readable");
assert!(
content.trim().len() >= 1_000,
"{path} should contain a substantial chapter"
);
assert!(
content.contains("## "),
"{path} should contain second-level headings"
);
assert!(
content.contains("## 相关章节"),
"{path} should point readers to related chapters"
);
}
for path in LEGACY_WIKI_ENTRY_FILES {
assert!(
!Path::new(path).exists(),
"{path} should be removed after the split wiki became canonical"
);
assert!(
!readme.contains(path),
"README.md should not link removed legacy wiki entry {path}"
);
}
}
#[test]
fn tutorial_chapters_share_learning_structure() {
for path in TUTORIAL_WIKI_FILES {
let content = std::fs::read_to_string(path).expect("tutorial chapter should be readable");
for heading in [
"## 你会学到什么",
"## 适合谁读",
"## 前置知识",
"## 心智模型",
"## 最小可运行例子",
"## 参数和边界",
"## 常见陷阱",
"## 进阶用法",
"## 相关章节",
] {
assert!(content.contains(heading), "{path} should contain {heading}");
}
}
}
#[test]
fn reference_chapters_cover_api_examples_and_annotations() {
for path in REFERENCE_WIKI_FILES {
let content = std::fs::read_to_string(path).expect("reference chapter should be readable");
for heading in ["## 查阅方式", "## 示例", "## 相关章节"] {
assert!(content.contains(heading), "{path} should contain {heading}");
}
}
let lua_reference = std::fs::read_to_string("wiki/06-reference/lua-api.md")
.expect("Lua reference should exist");
for expected in [
"types/pulse.meta.lua",
"pulse.Sequence",
"pulse.SampleClip",
"pulse.MidiClip",
"pulse.DrumGrid",
"pulse.Phrase",
"pulse.Song",
"pulse.ImportedMidi",
"pulse.PcmChunk",
"userdata",
] {
assert!(
lua_reference.contains(expected),
"Lua reference should mention {expected}"
);
}
let examples_reference = std::fs::read_to_string("wiki/06-reference/examples.md")
.expect("examples reference should exist");
for name in EXAMPLE_NAMES {
assert!(
examples_reference.contains(name),
"examples reference should document {name}"
);
assert!(
examples_reference.contains(&format!("cargo run --release --example {name}")),
"examples reference should show release command for {name}"
);
}
}
#[test]
fn wiki_catalog_chapters_list_runtime_catalogs_and_parameters() {
let lua = mlua::Lua::new();
mlua_pulse::register_package_loader(&lua).expect("pulse module should register");
let pulse: mlua::Table = lua
.load(r#"return require("pulse")"#)
.eval()
.expect("pulse module should load");
let instruments_doc =
std::fs::read_to_string("wiki/02-sound-design/instruments.md").expect("instrument docs");
let drums_doc =
std::fs::read_to_string("wiki/01-lua-composition/rhythm-drums.md").expect("drum docs");
let effects_doc =
std::fs::read_to_string("wiki/02-sound-design/effects.md").expect("effect docs");
let synths_doc =
std::fs::read_to_string("wiki/02-sound-design/synthesis.md").expect("synth docs");
let generators_doc =
std::fs::read_to_string("wiki/03-generation/generators.md").expect("generator docs");
for name in lua_string_catalog(&pulse, "instruments") {
assert!(
instruments_doc.contains(&format!("`{name}`")),
"instrument docs should list `{name}`"
);
}
for name in lua_string_catalog(&pulse, "drums") {
assert!(
drums_doc.contains(&format!("`{name}`")),
"drum docs should list `{name}`"
);
}
for name in lua_string_catalog(&pulse, "effects") {
assert!(
effects_doc.contains(&format!("`{name}`")),
"effect docs should list `{name}`"
);
let info = lua_info_table(&pulse, "effect_info", &name);
for parameter in lua_table_strings(info.get("parameters").expect("parameters table")) {
assert!(
effects_doc.contains(&format!("`{parameter}`")),
"effect docs should describe `{name}.{parameter}`"
);
}
for preset in lua_table_strings(info.get("presets").expect("presets table")) {
assert!(
effects_doc.contains(&format!("`{preset}`")),
"effect docs should list preset `{name}.{preset}`"
);
}
}
for name in lua_string_catalog(&pulse, "synths") {
assert!(
synths_doc.contains(&format!("`{name}`")),
"synth docs should list `{name}`"
);
let info = lua_info_table(&pulse, "synth_info", &name);
for parameter in lua_table_strings(info.get("parameters").expect("parameters table")) {
assert!(
synths_doc.contains(&format!("`{parameter}`")),
"synth docs should describe `{name}.{parameter}`"
);
}
for preset in lua_table_strings(info.get("presets").expect("presets table")) {
assert!(
synths_doc.contains(&format!("`{preset}`")),
"synth docs should list preset `{name}.{preset}`"
);
}
}
for name in lua_string_catalog(&pulse, "generators") {
assert!(
generators_doc.contains(&format!("`{name}`")),
"generator docs should list `{name}`"
);
}
}
fn lua_string_catalog(pulse: &mlua::Table, function_name: &str) -> Vec<String> {
let function: mlua::Function = pulse
.get(function_name)
.expect("catalog function should exist");
let table: mlua::Table = function.call(()).expect("catalog function should run");
lua_table_strings(table)
}
fn lua_info_table(pulse: &mlua::Table, function_name: &str, name: &str) -> mlua::Table {
let function: mlua::Function = pulse
.get(function_name)
.expect("info function should exist");
function.call(name).expect("info function should run")
}
fn lua_table_strings(table: mlua::Table) -> Vec<String> {
table
.sequence_values::<String>()
.collect::<mlua::Result<Vec<_>>>()
.expect("table should contain strings")
}
#[test]
fn lua_meta_file_comments_every_function() {
let meta = std::fs::read_to_string("types/pulse.meta.lua").expect("Lua meta file should exist");
let lines = meta.lines().collect::<Vec<_>>();
let mut undocumented = Vec::new();
for (index, line) in lines.iter().enumerate() {
if !line.starts_with("function ") {
continue;
}
let mut has_comment = false;
let mut cursor = index;
while cursor > 0 {
cursor -= 1;
let previous = lines[cursor].trim();
if previous.is_empty() {
continue;
}
if !previous.starts_with("---") {
break;
}
if previous.starts_with("---@") {
continue;
}
if previous.trim_start_matches("---").trim().is_empty() {
continue;
}
has_comment = true;
break;
}
if !has_comment {
undocumented.push(format!("{}: {}", index + 1, line));
}
}
assert!(
undocumented.is_empty(),
"types/pulse.meta.lua functions missing prose comments:\n{}",
undocumented.join("\n")
);
}
#[test]
fn lua_meta_file_explains_annotations() {
let meta = std::fs::read_to_string("types/pulse.meta.lua").expect("Lua meta file should exist");
let lines = meta.lines().collect::<Vec<_>>();
let mut missing_details = Vec::new();
for (index, line) in lines.iter().enumerate() {
let trimmed = line.trim();
if trimmed.starts_with("---@alias ")
&& !annotation_has_description(trimmed, AnnotationKind::Alias)
{
missing_details.push(format!("{}: {}", index + 1, line));
}
if trimmed.starts_with("---@field ")
&& !annotation_has_description(trimmed, AnnotationKind::FieldOrParam)
{
missing_details.push(format!("{}: {}", index + 1, line));
}
if trimmed.starts_with("---@param ")
&& !annotation_has_description(trimmed, AnnotationKind::FieldOrParam)
{
missing_details.push(format!("{}: {}", index + 1, line));
}
if trimmed.starts_with("---@return ")
&& !annotation_has_description(trimmed, AnnotationKind::Return)
{
missing_details.push(format!("{}: {}", index + 1, line));
}
if trimmed.starts_with("---@class ") && !class_has_prose_comment(&lines, index) {
missing_details.push(format!("{}: {}", index + 1, line));
}
}
assert!(
missing_details.is_empty(),
"types/pulse.meta.lua annotations missing prose details:\n{}",
missing_details.join("\n")
);
}
#[derive(Clone, Copy)]
enum AnnotationKind {
Alias,
FieldOrParam,
Return,
}
fn annotation_has_description(line: &str, kind: AnnotationKind) -> bool {
let required_parts = match kind {
AnnotationKind::Alias | AnnotationKind::FieldOrParam => 4,
AnnotationKind::Return => 3,
};
let Some(description) = line
.splitn(required_parts, char::is_whitespace)
.nth(required_parts - 1)
.map(str::trim)
else {
return false;
};
if description.is_empty() {
return false;
}
match kind {
AnnotationKind::Return => !is_identifier_only(description),
AnnotationKind::Alias | AnnotationKind::FieldOrParam => true,
}
}
fn is_identifier_only(text: &str) -> bool {
text.chars()
.all(|character| character.is_ascii_alphanumeric() || character == '_')
}
fn class_has_prose_comment(lines: &[&str], class_index: usize) -> bool {
lines
.iter()
.skip(class_index + 1)
.map(|line| line.trim())
.find(|line| !line.is_empty())
.is_some_and(|line| {
line.starts_with("---")
&& !line.starts_with("---@")
&& !line.trim_start_matches("---").trim().is_empty()
})
}
#[test]
fn lua_can_build_song_with_chainable_dsl() {
let lua = mlua::Lua::new();
mlua_pulse::register_package_loader(&lua).expect("pulse module should register");
lua.load(
r#"
local pulse = require("pulse")
local notes = pulse.scale("C4", "ionian")
local phrase = pulse.sequence()
:notes(notes)
:durations({0.25, 0.25, 0.25, 0.25, 0.5, 0.5, 0.5, 1.0})
:instrument("electric_piano")
:transpose(12)
local song = pulse.song()
:tempo(120)
:add(phrase)
assert(song ~= nil)
"#,
)
.exec()
.expect("lua dsl should run");
}
#[test]
fn lua_exposes_polyphonic_sequence_and_mix_controls() {
let lua = mlua::Lua::new();
mlua_pulse::register_package_loader(&lua).expect("pulse module should register");
lua.load(
r#"
local pulse = require("pulse")
local harmony = pulse.sequence()
:chords({
pulse.chord("C4", "maj7"),
pulse.chord("F3", "maj7"),
})
:durations({0.5, 0.5})
:instrument("electric_piano")
:at(0.25)
:volume(0.72)
:pan(-0.25)
:velocity(0.62)
local beat = pulse.drum_grid()
:sound("kick_808", {0})
:volume(0.8)
:pan(0.15)
local song = pulse.song()
:add(harmony)
:add_drum_grid(beat)
assert(song ~= nil)
"#,
)
.exec()
.expect("Lua should expose polyphonic and mix-control sequence methods");
}
#[test]
fn foundation_loop_example_script_is_kept_in_sync() {
let script = example_script("foundation_loop");
assert!(script.contains("require(\"pulse\")"));
assert!(script.contains("export_wav"));
assert!(script.contains("electric_piano"));
}
#[test]
fn lua_exposes_midi_conversion_helpers() {
let lua = mlua::Lua::new();
mlua_pulse::register_package_loader(&lua).expect("pulse module should register");
lua.load(
r#"
local pulse = require("pulse")
assert(pulse.frequency_to_midi_note(440.0) == 69)
assert(math.abs(pulse.midi_note_to_frequency(69) - 440.0) < 0.01)
local ok = pcall(function()
pulse.midi_note_to_frequency(200)
end)
assert(ok == false)
local ok_freq = pcall(function()
pulse.frequency_to_midi_note(0/0)
end)
assert(ok_freq == false)
local ok_zero = pcall(function()
pulse.frequency_to_midi_note(0)
end)
assert(ok_zero == false)
local ok_negative = pcall(function()
pulse.frequency_to_midi_note(-440.0)
end)
assert(ok_negative == false)
"#,
)
.exec()
.expect("midi conversion lua script should run");
}
#[test]
fn lua_exposes_export_format_catalog() {
let lua = mlua::Lua::new();
mlua_pulse::register_package_loader(&lua).expect("pulse module should register");
lua.load(
r#"
local pulse = require("pulse")
local formats = pulse.export_formats()
assert(#formats == 4)
local found = {}
for _, format in ipairs(formats) do
found[format] = true
end
assert(found["wav"])
assert(found["flac"])
assert(found["mid"])
assert(found["midi"])
"#,
)
.exec()
.expect("export format catalog should be visible to Lua");
}
#[test]
fn lua_exposes_full_instrument_catalog() {
let lua = mlua::Lua::new();
mlua_pulse::register_package_loader(&lua).expect("pulse module should register");
lua.load(
r#"
local pulse = require("pulse")
local names = pulse.instruments()
assert(#names >= 150)
local found = {}
for _, name in ipairs(names) do
found[name] = true
end
assert(found["electric_piano"])
assert(found["supersaw"])
assert(found["bass_808"])
local phrase = pulse.sequence()
:notes({440.0})
:durations({0.25})
:instrument("supersaw")
local song = pulse.song():add(phrase)
assert(song ~= nil)
"#,
)
.exec()
.expect("instrument catalog lua script should run");
}
#[test]
fn lua_exposes_rhythm_helpers_and_drum_catalog() {
let lua = mlua::Lua::new();
mlua_pulse::register_package_loader(&lua).expect("pulse module should register");
lua.load(
r#"
local pulse = require("pulse")
local four = pulse.euclidean(4, 16)
assert(#four == 4)
assert(four[1] == 0)
assert(four[2] == 4)
assert(four[3] == 8)
assert(four[4] == 12)
local binary = pulse.euclidean_pattern(4, 8)
assert(#binary == 8)
assert(binary[1] == 1)
local tresillo = pulse.pattern("tresillo")
assert(#tresillo == 3)
local drums = pulse.drums()
assert(#drums >= 100)
local found = {}
for _, name in ipairs(drums) do
found[name] = true
end
assert(found["kick_808"])
assert(found["snare_808"])
assert(found["hihat_808_closed"])
assert(found["clap_808"])
assert(found["kick_808_deep_layer"])
assert(found["snare_808_body_layer"])
assert(found["clap_909_wide_layer"])
local ok = pcall(function()
pulse.pattern("vaporwave_backbeat")
end)
assert(ok == false)
local ok_steps, err_steps = pcall(function()
pulse.euclidean(4, 1000001)
end)
assert(ok_steps == false)
assert(tostring(err_steps):find("invalid drum grid steps: 1000001", 1, true))
local ok_binary_steps, err_binary_steps = pcall(function()
pulse.euclidean_pattern(4, 1000001)
end)
assert(ok_binary_steps == false)
assert(tostring(err_binary_steps):find("invalid drum grid steps: 1000001", 1, true))
"#,
)
.exec()
.expect("rhythm and drum catalog lua script should run");
}
#[test]
fn lua_can_build_drum_grid_and_808_machine() {
let lua = mlua::Lua::new();
mlua_pulse::register_package_loader(&lua).expect("pulse module should register");
lua.load(
r#"
local pulse = require("pulse")
local beat = pulse.drum_grid()
:track("drums")
:steps(16)
:step_duration(0.125)
:sound("kick_808", pulse.euclidean(4, 16))
:sound("snare_808", {4, 12})
:sound("hihat_808_closed", "x-x-x-x-x-x-x-x-")
:sound("kick_808_deep_layer", {0, 8})
:accent("x---x---x---x---")
:repeat_times(1)
local machine = pulse.drum_machine("808")
:steps(16)
:kick("x---x---x---x---")
:snare("----x-------x---")
:hihat("x-x-x-x-x-x-x-x-")
:clap("----x-------x---")
:grid()
local song = pulse.song()
:tempo(120)
:add_drum_grid(beat)
:add_drum_grid(machine)
assert(song ~= nil)
local ok_repeat, err_repeat = pcall(function()
pulse.drum_grid():repeat_times(0)
end)
assert(ok_repeat == false)
assert(tostring(err_repeat):find("invalid repeat times: 0", 1, true))
local ok_steps, err_steps = pcall(function()
pulse.drum_grid():steps(18446744073709551616.0)
end)
assert(ok_steps == false)
assert(tostring(err_steps):find("invalid drum grid steps", 1, true), tostring(err_steps))
"#,
)
.exec()
.expect("drum grid lua script should run");
}
#[test]
fn lua_can_build_phrase_arrangements() {
let lua = mlua::Lua::new();
mlua_pulse::register_package_loader(&lua).expect("pulse module should register");
lua.load(
r#"
local pulse = require("pulse")
local lead = pulse.sequence()
:notes({440.0})
:durations({0.25})
:instrument("electric_piano")
local beat = pulse.drum_grid()
:steps(4)
:step_duration(0.25)
:sound("kick_808", {0})
local verse = pulse.phrase()
:add(lead)
:add_drum_grid(beat)
:repeat_times(2)
local song = pulse.song()
:tempo(120)
:add_phrase(verse)
assert(song ~= nil)
local ok_repeat, err_repeat = pcall(function()
pulse.phrase():repeat_times(0)
end)
assert(ok_repeat == false)
assert(tostring(err_repeat):find("invalid repeat times: 0", 1, true))
"#,
)
.exec()
.expect("phrase arrangement lua script should run");
}
#[test]
fn lua_repeat_times_rejects_oversized_numbers_before_rendering() {
let lua = mlua::Lua::new();
mlua_pulse::register_package_loader(&lua).expect("pulse module should register");
lua.load(
r#"
local pulse = require("pulse")
local invalid_repeat_targets = {
function()
pulse.drum_grid():repeat_times(18446744073709551616.0)
end,
function()
pulse.phrase():repeat_times(18446744073709551616.0)
end,
}
for _, target in ipairs(invalid_repeat_targets) do
local ok, err = pcall(target)
assert(ok == false)
assert(tostring(err):find("invalid repeat times", 1, true), tostring(err))
end
"#,
)
.exec()
.expect("oversized Lua repeat_times values should be rejected");
}
#[test]
fn capability_expansion_example_script_is_kept_in_sync() {
let script = example_script("capability_expansion");
assert!(script.contains("export_flac"));
assert!(script.contains("export_midi"));
assert!(script.contains("drum_machine(\"808\")"));
assert!(script.contains("pulse.instruments()"));
assert!(script.contains("pulse.euclidean"));
}
#[test]
fn lua_exposes_playback_constructor_and_methods_without_playing_audio() {
let lua = mlua::Lua::new();
mlua_pulse::register_package_loader(&lua).expect("pulse module should register");
lua.load(
r#"
local pulse = require("pulse")
assert(type(pulse.playback) == "function")
local engine = pulse.playback()
assert(type(engine.play) == "function")
assert(type(engine.play_realtime) == "function")
assert(type(engine.play_looping) == "function")
assert(type(engine.play_at_rate) == "function")
assert(type(engine.play_realtime_at_rate) == "function")
local song = pulse.song()
assert(type(song.play) == "function")
assert(type(song.play_at_rate) == "function")
local ok_rate = pcall(function()
engine:play_at_rate(song, 0)
end)
assert(ok_rate == false)
local ok_realtime_rate = pcall(function()
engine:play_realtime_at_rate(song, 0)
end)
assert(ok_realtime_rate == false)
"#,
)
.exec()
.expect("playback lua API should be visible without real playback");
}
#[test]
fn lua_exposes_sample_clip_dsl_and_validates_options() {
let lua = mlua::Lua::new();
mlua_pulse::register_package_loader(&lua).expect("pulse module should register");
lua.load(
r#"
local pulse = require("pulse")
local clip = pulse.sample("source.wav")
:at(0.25)
:track("loop_bus")
:volume(0.6)
:pan(-0.2)
:effect("delay", "slapback")
:slice(0.1, 0.4)
:reverse()
:loop_for(0.75)
:normalize()
:fade_in(0.01)
:fade_out(0.02)
:pitch_shift(12.0)
:time_stretch(1.5)
:rate(0.75)
:gain(0.8)
local song = pulse.song():add_sample(clip)
assert(song ~= nil)
local phrase = pulse.phrase():add_sample(clip):repeat_times(2)
assert(phrase ~= nil)
local ok_rate, err_rate = pcall(function()
pulse.sample("source.wav"):rate(0)
end)
assert(ok_rate == false)
assert(tostring(err_rate):find("invalid sample option rate: 0", 1, true))
local ok_stretch, err_stretch = pcall(function()
pulse.sample("source.wav"):time_stretch(0)
end)
assert(ok_stretch == false)
assert(tostring(err_stretch):find("invalid sample option time_stretch: 0", 1, true))
local ok_at, err_at = pcall(function()
pulse.sample("source.wav"):at(-0.01)
end)
assert(ok_at == false)
assert(tostring(err_at):find("invalid sample option at: -0.01", 1, true))
local ok_slice, err_slice = pcall(function()
pulse.sample("source.wav"):slice(0.5, 0.25)
end)
assert(ok_slice == false)
assert(tostring(err_slice):find("invalid sample option slice: 0.5..0.25", 1, true))
local ok_fade, err_fade = pcall(function()
pulse.sample("source.wav"):fade_in(-0.1)
end)
assert(ok_fade == false)
assert(tostring(err_fade):find("invalid sample option fade_in: -0.1", 1, true))
local ok_loop, err_loop = pcall(function()
pulse.sample("source.wav"):loop_for(0)
end)
assert(ok_loop == false)
assert(tostring(err_loop):find("invalid sample option loop_for: 0", 1, true))
local ok_volume, err_volume = pcall(function()
pulse.sample("source.wav"):volume(2.1)
end)
assert(ok_volume == false)
assert(tostring(err_volume):find("invalid sample option volume: 2.1", 1, true))
local ok_pan, err_pan = pcall(function()
pulse.sample("source.wav"):pan(-1.1)
end)
assert(ok_pan == false)
assert(tostring(err_pan):find("invalid sample option pan: -1.1", 1, true))
"#,
)
.exec()
.expect("sample clip Lua API should be visible and validate options");
}
#[test]
fn playback_example_script_is_kept_in_sync() {
let script = example_script("playback");
assert!(script.contains("pulse.playback()"));
assert!(script.contains("play_realtime"));
assert!(script.contains("handle:volume"));
assert!(script.contains("handle:stop"));
}
#[test]
fn algorithm_generators_example_script_is_kept_in_sync() {
let script = example_script("algorithm_generators");
assert!(script.contains("pulse.generate"));
assert!(script.contains("pulse.map_to_scale"));
assert!(script.contains("export_wav"));
assert!(script.contains("export_midi"));
}
#[test]
fn lua_exposes_algorithm_generator_catalog() {
let lua = mlua::Lua::new();
mlua_pulse::register_package_loader(&lua).expect("pulse module should register");
lua.load(
r#"
local pulse = require("pulse")
local names = pulse.generators()
assert(#names >= 50)
local found = {}
for _, name in ipairs(names) do
found[name] = true
end
assert(found["fibonacci"])
assert(found["primes"])
assert(found["l_system"])
assert(found["cellular_automaton"])
assert(found["logistic_map"])
assert(found["cantor_rhythm_16"])
local info = pulse.generator_info("fibonacci")
assert(info.name == "fibonacci")
assert(info.category == "mathematical")
assert(info.output == "integers")
assert(info.parameters[1] == "length")
"#,
)
.exec()
.expect("algorithm generator catalog should be visible to Lua");
}
#[test]
fn lua_generates_algorithmic_values_and_reports_stable_errors() {
let lua = mlua::Lua::new();
mlua_pulse::register_package_loader(&lua).expect("pulse module should register");
lua.load(
r#"
local pulse = require("pulse")
local fib = pulse.generate("fibonacci", { length = 8 })
assert(#fib == 8)
assert(fib[1] == 1)
assert(fib[2] == 1)
assert(fib[3] == 2)
assert(fib[8] == 21)
local primes = pulse.generate("primes", { length = 8 })
assert(primes[1] == 2)
assert(primes[8] == 19)
local chaos = pulse.generate("logistic_map", {
r = 3.9,
initial = 0.5,
length = 8,
})
assert(#chaos == 8)
assert(chaos[1] > 0 and chaos[1] < 1)
local ok_unknown, err_unknown = pcall(function()
pulse.generate("vaporwave_sequence", {})
end)
assert(ok_unknown == false)
assert(tostring(err_unknown):find("invalid generator: vaporwave_sequence", 1, true))
local ok_missing, err_missing = pcall(function()
pulse.generate("fibonacci", {})
end)
assert(ok_missing == false)
assert(tostring(err_missing):find("missing generator option: length", 1, true))
local ok_invalid, err_invalid = pcall(function()
pulse.generate("fibonacci", { length = 0 })
end)
assert(ok_invalid == false)
assert(tostring(err_invalid):find("invalid generator option length: 0", 1, true))
local ok_oversized, err_oversized = pcall(function()
pulse.generate("fibonacci", { length = 1000001 })
end)
assert(ok_oversized == false)
assert(tostring(err_oversized):find("invalid generator option length: 1000001", 1, true))
"#,
)
.exec()
.expect("Lua should generate algorithmic values");
}
#[test]
fn lua_generates_text_matrix_rhythm_and_musical_sequences() {
let lua = mlua::Lua::new();
mlua_pulse::register_package_loader(&lua).expect("pulse module should register");
lua.load(
r#"
local pulse = require("pulse")
local pattern = pulse.generate("l_system", {
axiom = "A",
rules = { A = "AB", B = "A" },
iterations = 5,
})
assert(type(pattern) == "string")
assert(#pattern > 5)
local values = pulse.generate("l_system_values", { pattern = pattern })
assert(#values == #pattern)
local grid = pulse.generate("cellular_automaton", {
rule = 30,
width = 16,
generations = 8,
})
assert(#grid == 8)
assert(#grid[1] == 16)
local hits = pulse.generate("cantor_rhythm_16")
assert(#hits > 0)
local markov = pulse.generate("markov")
assert(#markov > 0)
local fifths = pulse.generate("circle_of_fifths", { length = 8, start = 0 })
assert(#fifths == 8)
local harmonics = pulse.generate("harmonic_series", {
fundamental = 110.0,
length = 4,
})
assert(harmonics[1] == 110.0)
local undertones = pulse.generate("undertone_series", { length = 4 })
assert(#undertones == 4)
"#,
)
.exec()
.expect("Lua should generate text, matrix, rhythm, and musical sequences");
}
#[test]
fn lua_maps_generator_values_to_durations_and_scale_notes() {
let lua = mlua::Lua::new();
mlua_pulse::register_package_loader(&lua).expect("pulse module should register");
lua.load(
r#"
local pulse = require("pulse")
local fib = pulse.generate("fibonacci", { length = 8 })
local notes = pulse.map_to_scale(fib, "major", "C4", 2)
assert(#notes == 8)
assert(notes[1] > 261 and notes[1] < 262)
local root = pulse.map_to_scale({0}, "major", "C4", 1)
assert(root[1] > 261 and root[1] < 262)
local raw = pulse.generate("van_der_corput", { length = 8, base = 2 })
local durations = pulse.normalize(raw, 0.125, 0.5)
assert(#durations == 8)
for _, duration in ipairs(durations) do
assert(duration >= 0.125)
assert(duration <= 0.5)
end
local ok_empty = pcall(function()
pulse.normalize({}, 0.0, 1.0)
end)
assert(ok_empty == false)
local ok_range = pcall(function()
pulse.normalize({1, 2, 3}, 1.0, 1.0)
end)
assert(ok_range == false)
local phrase = pulse.sequence()
:notes(notes)
:durations(durations)
:instrument("electric_piano")
assert(phrase ~= nil)
"#,
)
.exec()
.expect("Lua should map generated values to notes and durations");
}
#[test]
fn lua_exposes_effect_catalog_and_info() {
let lua = mlua::Lua::new();
mlua_pulse::register_package_loader(&lua).expect("pulse module should register");
lua.load(
r#"
local pulse = require("pulse")
local names = pulse.effects()
assert(#names == 8)
local found = {}
for _, name in ipairs(names) do
found[name] = true
end
assert(found["delay"])
assert(found["reverb"])
assert(found["filter"])
assert(found["compressor"])
assert(found["limiter"])
assert(found["chorus"])
assert(found["phaser"])
assert(found["eq"])
local info = pulse.effect_info("delay")
assert(info.name == "delay")
assert(info.category == "time")
assert(info.scopes[1] == "track")
assert(info.parameters[1] == "time")
assert(info.presets[1] == "eighth_note")
"#,
)
.exec()
.expect("effect catalog should be visible to Lua");
}
#[test]
fn lua_exposes_synth_catalog_and_info() {
let lua = mlua::Lua::new();
mlua_pulse::register_package_loader(&lua).expect("pulse module should register");
lua.load(
r#"
local pulse = require("pulse")
local names = pulse.synths()
assert(#names == 5)
local found = {}
for _, name in ipairs(names) do
found[name] = true
end
assert(found["fm"])
assert(found["additive"])
assert(found["wavetable"])
assert(found["karplus_strong"])
assert(found["granular"])
local fm = pulse.synth_info("fm")
assert(fm.name == "fm")
assert(fm.category == "frequency_modulation")
assert(fm.parameters[1] == "mod_ratio")
assert(fm.presets[1] == "electric_piano")
assert(fm.aliases[1] == "fm")
local granular = pulse.synth_info("grain")
assert(granular.name == "granular")
assert(granular.category == "sample_granular")
assert(granular.parameters[1] == "source")
assert(granular.parameters[2] == "duration")
assert(granular.presets[1] == "default")
assert(granular.aliases[2] == "grain")
local ok_unknown, err_unknown = pcall(function()
pulse.synth_info("subtractive")
end)
assert(ok_unknown == false)
assert(tostring(err_unknown):find("invalid synth: subtractive", 1, true))
"#,
)
.exec()
.expect("synth catalog should be visible to Lua");
}
#[test]
fn lua_effect_methods_are_chainable_and_validate_errors() {
let lua = mlua::Lua::new();
mlua_pulse::register_package_loader(&lua).expect("pulse module should register");
lua.load(
r#"
local pulse = require("pulse")
local phrase = pulse.sequence()
:notes({440.0})
:durations({0.25})
:instrument("electric_piano")
:effect("filter", { type = "low_pass", cutoff = 1800.0, resonance = 0.4, slope = 24 })
:effect("delay", { time = 0.25, feedback = 0.35, mix = 0.3 })
local beat = pulse.drum_grid()
:sound("kick_808", {0, 4, 8, 12})
:effect("compressor", { threshold = 0.6, ratio = 4.0, attack = 0.01, release = 0.08, makeup_gain = 1.0 })
local song = pulse.song()
:add(phrase)
:add_drum_grid(beat)
:master_effect("reverb", "hall")
:master_effect("limiter", "standard")
assert(song ~= nil)
local ok_effect, err_effect = pcall(function()
pulse.effect_info("shimmer")
end)
assert(ok_effect == false)
assert(tostring(err_effect):find("invalid effect: shimmer", 1, true))
local ok_preset, err_preset = pcall(function()
pulse.sequence():effect("delay", "cathedral")
end)
assert(ok_preset == false)
assert(tostring(err_preset):find("invalid effect preset delay: cathedral", 1, true))
local ok_option, err_option = pcall(function()
pulse.sequence():effect("delay", { feedback = 1.2 })
end)
assert(ok_option == false)
assert(tostring(err_option):find("invalid effect option delay.feedback: 1.2", 1, true))
local ok_scope, err_scope = pcall(function()
pulse.song():master_effect("filter", { type = "low_pass" })
end)
assert(ok_scope == false)
assert(tostring(err_scope):find("invalid effect scope filter: master", 1, true))
"#,
)
.exec()
.expect("effect methods should be visible and validate errors");
}
#[test]
fn lua_exposes_sequence_synth_dsl_and_validates_errors() {
let lua = mlua::Lua::new();
mlua_pulse::register_package_loader(&lua).expect("pulse module should register");
lua.load(
r#"
local pulse = require("pulse")
local fm = pulse.sequence()
:notes({440.0})
:durations({0.25})
:instrument("electric_piano")
:synth("fm", "bell")
local custom_fm = pulse.sequence()
:notes({330.0})
:durations({0.25})
:instrument("electric_piano")
:synth("fm", { mod_ratio = 2.0, mod_index = 3.0 })
local additive = pulse.sequence()
:notes({550.0})
:durations({0.25})
:instrument("electric_piano")
:synth("additive", {1.0, 0.5, 0.25})
local wavetable = pulse.sequence()
:notes({660.0})
:durations({0.25})
:instrument("electric_piano")
:synth("wavetable")
local pluck = pulse.sequence()
:notes({440.0})
:durations({0.25})
:instrument("electric_piano")
:synth("karplus_strong", { decay = 0.996, brightness = 0.7, seed = 42 })
local song = pulse.song()
:add(fm)
:add(custom_fm)
:add(additive)
:add(wavetable)
:add(pluck)
assert(song ~= nil)
local ok_name, err_name = pcall(function()
pulse.sequence():synth("subtractive")
end)
assert(ok_name == false)
assert(tostring(err_name):find("invalid synth: subtractive", 1, true))
local ok_preset, err_preset = pcall(function()
pulse.sequence():synth("fm", "glass")
end)
assert(ok_preset == false)
assert(tostring(err_preset):find("invalid synth preset fm: glass", 1, true))
local ok_option, err_option = pcall(function()
pulse.sequence():synth("additive", {})
end)
assert(ok_option == false)
assert(tostring(err_option):find("invalid synth option additive.harmonics: empty", 1, true))
local ok_decay, err_decay = pcall(function()
pulse.sequence():synth("karplus_strong", { decay = 1.5 })
end)
assert(ok_decay == false)
assert(tostring(err_decay):find("invalid synth option karplus_strong.decay: 1.5", 1, true))
local ok_granular, err_granular = pcall(function()
pulse.sequence():synth("granular", { duration = 0.5 })
end)
assert(ok_granular == false)
assert(tostring(err_granular):find("invalid synth option granular.source: missing", 1, true))
"#,
)
.exec()
.expect("synth methods should be visible and validate errors");
}
#[test]
fn effects_chain_example_script_is_kept_in_sync() {
let script = example_script("effects_chain");
assert!(script.contains("pulse.effects()"));
assert!(script.contains(":effect(\"filter\""));
assert!(script.contains(":effect(\"compressor\""));
assert!(script.contains(":master_effect(\"reverb\""));
assert!(script.contains(":master_effect(\"limiter\""));
assert!(script.contains("export_wav"));
assert!(script.contains("export_flac"));
}
#[test]
fn synthesis_algorithms_example_script_is_kept_in_sync() {
let script = example_script("synthesis_algorithms");
assert!(script.contains(":synth(\"fm\", \"bell\")"));
assert!(script.contains(":synth(\"additive\""));
assert!(script.contains(":synth(\"karplus_strong\""));
assert!(script.contains(":synth(\"granular\""));
assert!(script.contains(":synth(\"wavetable\")"));
assert!(script.contains("export_wav"));
assert!(script.contains("export_flac"));
}
#[test]
fn phrase_arrangement_example_script_is_kept_in_sync() {
let script = example_script("phrase_arrangement");
assert!(script.contains("pulse.phrase()"));
assert!(script.contains(":repeat_times(2)"));
assert!(script.contains(":add_phrase"));
assert!(script.contains("export_wav"));
assert!(script.contains("export_midi"));
}
#[test]
fn full_song_examples_are_kept_in_sync() {
for name in FULL_SONG_EXAMPLE_NAMES {
let script = example_script(name);
assert!(script.contains("require(\"pulse\")"));
assert!(script.contains("pulse.phrase()"));
assert!(script.contains(":add_phrase"));
assert!(script.contains("export_wav"));
}
let solo_piano = example_script("solo_piano");
assert!(solo_piano.contains("solo-piano"));
assert!(solo_piano.contains("export_midi"));
assert!(solo_piano.contains("acoustic_piano"));
assert!(solo_piano.contains("electric_piano"));
let mini_symphony = example_script("mini_symphony");
assert!(mini_symphony.contains("mini-symphony"));
assert!(mini_symphony.contains("export_midi"));
assert!(mini_symphony.contains("strings"));
assert!(mini_symphony.contains("brass_section"));
assert!(mini_symphony.contains("flute"));
let fresh_pop = example_script("fresh_pop");
assert!(fresh_pop.contains("fresh-pop"));
assert!(fresh_pop.contains("export_flac"));
assert!(fresh_pop.contains("electric_piano"));
assert!(fresh_pop.contains("warm_pad"));
let kaze = example_script("kaze_ensemble");
assert!(kaze.contains("kaze-ensemble"));
assert!(kaze.contains("export_flac"));
assert!(kaze.contains("export_midi"));
assert!(kaze.contains("acoustic_guitar"));
assert!(kaze.contains("flute"));
assert!(kaze.contains("pizzicato_strings"));
assert!(kaze.contains("soft_kick"));
let farewell = example_script("farewell_ensemble");
assert!(farewell.contains("farewell-ensemble"));
assert!(farewell.contains("export_flac"));
assert!(farewell.contains("export_midi"));
assert!(farewell.contains("slow_strings"));
assert!(farewell.contains("oboe"));
assert!(farewell.contains("cello"));
assert!(farewell.contains("acoustic_piano"));
assert!(farewell.contains("mellow_filter"));
let hardcore_edm = example_script("hardcore_edm");
assert!(hardcore_edm.contains("hardcore-edm"));
assert!(hardcore_edm.contains("export_flac"));
assert!(hardcore_edm.contains("drum_machine(\"808\")"));
assert!(hardcore_edm.contains("bass_808"));
assert!(hardcore_edm.contains(":synth(\"fm\", \"growl\")"));
let kawaii_bass = example_script("kawaii_bass_3min");
assert!(kawaii_bass.contains("kawaii-bass-3min"));
assert!(kawaii_bass.contains("export_flac"));
assert!(kawaii_bass.contains("export_midi"));
assert!(kawaii_bass.contains("local section_beats = 16.0"));
assert!(kawaii_bass.contains("local section_repeats = 24"));
assert!(kawaii_bass.contains("assert(duration_seconds >= 180.0)"));
let moonlight = example_script("moonlight_golden_hall");
assert!(moonlight.contains("moonlight-golden-hall"));
assert!(moonlight.contains("export_wav"));
assert!(moonlight.contains("export_flac"));
assert!(moonlight.contains("export_midi"));
assert!(moonlight.contains("acoustic_piano"));
assert!(moonlight.contains("slow_strings"));
assert!(moonlight.contains("brass_section"));
assert!(moonlight.contains("choir_aahs"));
assert!(moonlight.contains(":master_effect(\"reverb\", {"));
}