Explainable counterpoint rule engine for MoonBit
moon check --target all
moon test --target all
moon run cmd/main///|
test {
let cantus = [
Note::new(midi=60, onset=0, duration=2, label="C"),
Note::new(midi=62, onset=2, duration=2, label="D"),
Note::new(midi=64, onset=4, duration=2, label="E"),
Note::new(midi=65, onset=6, duration=2, label="F"),
Note::new(midi=67, onset=8, duration=2, label="G"),
]
let counter = [
Note::new(midi=67, onset=0, duration=2, label="G"),
Note::new(midi=65, onset=2, duration=2, label="F"),
Note::new(midi=72, onset=4, duration=2, label="C"),
Note::new(midi=81, onset=6, duration=2, label="A"),
Note::new(midi=79, onset=8, duration=2, label="G"),
]
let result = validate_pair(cantus, counter, RuleProfileConfig::classroom())
inspect(result.passed(), content="true")
}| Module | Responsibility |
|---|---|
| types.mbt | Public note, rule, finding, species, and option types |
| intervals.mbt | Melodic/harmonic interval and motion primitives |
| melody.mbt | Range, leap, and recovery diagnostics |
| harmony.mbt | Vertical sonority, parallels, hidden perfects, and cadence |
| species.mbt | Species rhythm and suspension-shape rules |
| profiles.mbt | Reusable rule policy presets and classification helpers |
| timeline.mbt | Sequence validation and note-time utilities |
| pitch.mbt / voices.mbt | Pitch classes, registers, voice ranges, and alignment |
| metrics.mbt / reports.mbt | Scores, summaries, CSV, and Markdown output |
| workflow.mbt | End-to-end pair validation facade |
| rhythm.mbt / contour.mbt / statistics.mbt | Meter, contour, distribution, and motion statistics |
| candidates.mbt / phrase_planner.mbt | Bounded candidate generation and phrase planning |
| phrase_windows.mbt / candidate_evidence.mbt | Local phrase partitioning and explainable candidate constraints |
| analysis_triage.mbt / repair_suggestions.mbt | Repair prioritization and bounded single-event edits |
| harmony_tools.mbt / voice_leading.mbt | Sonority profiles and motion constraints |
| rule_catalog.mbt / exercise_catalog.mbt / teaching.mbt | Explainable rule and practice content |
| pipeline.mbt / quality_gate.mbt / scorecards.mbt | Production analysis orchestration and quality gates |
| interchange.mbt / report_sections.mbt / analysis_exports.mbt | Interchange and composable report artifacts |
moon fmt --check
moon check --target all
moon test --target all
moon coverage report -f summary
moon coverage analyzepub(all) struct AnalysisHistory {
snapshots : Array[AnalysisSnapshot]
best_score : Int
latest_score : Int
improvement : Int
} derive(Eq, ToJson, Debug)pub(all) struct AnalysisTriage {
band : TriageBand
items : Array[TriageItem]
total_findings : Int
repair_score : Int
} derive(Eq, ToJson, Debug)pub(all) struct BeatPoint {
index : Int
time : Int
measure : Int
beat : Int
strength : BeatStrength
} derive(Eq, ToJson, Debug)pub(all) struct CandidateEvaluation {
candidate : PitchCandidate
constraints : Array[ConstraintEvidence]
score : Int
accepted : Bool
} derive(Eq, ToJson, Debug)pub(all) struct ConstraintEvidence {
constraint : CandidateConstraint
passed : Bool
score : Int
detail : String
} derive(Eq, ToJson, Debug)pub(all) struct LessonStep {
index : Int
stage : LessonStage
title : String
prompt : String
expected_rule : RuleKind?
} derive(Eq, ToJson, Debug)pub(all) struct MotionResult {
motion : MotionKind
accepted : Bool
cost : Int
message : String
} derive(Eq, ToJson, Debug)pub(all) struct PairValidation {
cantus : SequenceReport
counterpoint : SequenceReport
analysis : Analysis
summary : AnalysisSummary
profile : String
} derive(Eq, ToJson, Debug)pub(all) struct PhrasePartition {
windows : Array[PhraseWindow]
total_notes : Int
total_duration : Int
window_duration : Int
} derive(Eq, ToJson, Debug)pub(all) struct PhrasePlan {
segments : Array[PhraseSegment]
cadence : CadencePlan
total_notes : Int
balanced : Bool
} derive(Eq, ToJson, Debug)pub(all) struct PhraseSegment {
start : Int
end : Int
role : PhraseRole
notes : Int
pitch_range : Int
density : Int
} derive(Eq, ToJson, Debug)fn PipelineInput::new(cantus~ : Array[Note], counterpoint~ : Array[Note], meter~ : Meter, profile~ : RuleProfileConfig) -> PipelineInputpub(all) struct PipelineOutput {
validation : PairValidation
rhythm : RhythmSummary
contour : ContourSummary
harmony : HarmonySummary
motion : VoiceLeadingSummary
report : ReportDocument
recommendations : Array[Recommendation]
} derive(Eq, ToJson, Debug)pub(all) struct PitchCandidate {
note : Note
vertical_interval : Int
melodic_interval : Int
consonant : Bool
motion : MotionKind
score : Int
} derive(Eq, ToJson, Debug)pub(all) struct PracticePlan {
title : String
objective : String
steps : Array[LessonStep]
estimated_minutes : Int
difficulty : String
} derive(Eq, ToJson, Debug)pub(all) struct PracticeResult {
completed : Int
total : Int
score : Int
next_stage : LessonStage
feedback : String
} derive(Eq, ToJson, Debug)pub(all) struct QualityGate {
name : String
status : GateStatus
score : Int
evidence : String
action : String
} derive(Eq, ToJson, Debug)pub(all) struct QualityGateReport {
status : GateStatus
gates : Array[QualityGate]
score : Int
} derive(Eq, ToJson, Debug)pub(all) struct RepairSuggestion {
index : Int
kind : RepairKind
replacement_midi : Int
score : Int
rationale : String
} derive(Eq, ToJson, Debug)pub(all) struct ReportDocument {
title : String
sections : Array[ReportSection]
format : String
} derive(Eq, ToJson, Debug)fn ReportSection::new(id~ : String, title~ : String, body~ : String, priority~ : Int) -> ReportSectionpub(all) struct RuleProfileConfig {
profile : RuleProfile
allow_hidden_perfect : Bool
allow_weak_beat_dissonance : Bool
require_cadence : Bool
max_span : Int
max_leap : Int
max_consecutive_perfect : Int
} derive(Eq, ToJson, Debug)fn RuleProfileConfig::custom(allow_hidden_perfect~ : Bool, allow_weak_beat_dissonance~ : Bool, require_cadence~ : Bool, max_span~ : Int, max_leap~ : Int, max_consecutive_perfect~ : Int) -> RuleProfileConfigfn RuleProfileConfig::with_profile(self : RuleProfileConfig, profile : RuleProfile) -> RuleProfileConfigpub(all) struct SequenceReport {
note_count : Int
duration : Int
issues : Array[SequenceIssue]
} derive(Eq, ToJson, Debug)pub(all) struct Transformation {
kind : TransformationKind
amount : Int
description : String
} derive(Eq, ToJson, Debug)pub(all) struct TriageItem {
rule : RuleKind
count : Int
errors : Int
warnings : Int
infos : Int
priority : Int
band : TriageBand
action : String
} derive(Eq, ToJson, Debug)pub(all) struct VerticalEvent {
index : Int
lower : Int
upper : Int
semitones : Int
interval_class : Int
class : ConsonanceClass
inversion : Bool
} derive(Eq, ToJson, Debug)pub(all) struct VoiceAssessment {
role : VoiceRole
range : VoiceRange
notes : Int
violations : Int
lowest : Int
highest : Int
} derive(Eq, ToJson, Debug)fn analyze_batch(cantus : Array[Note], variants : Array[Array[Note]], profile : RuleProfileConfig) -> Array[AnalysisSummary]fn batch_report(cantus : Array[Note], variants : Array[Array[Note]], labels : Array[String], profile : RuleProfileConfig) -> BatchReportfn cadence_candidates(cantus : Note, final_pitch : Int, policy : CandidatePolicy) -> Array[PitchCandidate]fn candidate_evaluation_has(evaluation : CandidateEvaluation, constraint : CandidateConstraint) -> Boolfn candidate_evaluation_passes(evaluation : CandidateEvaluation, constraint : CandidateConstraint) -> Boolfn candidate_is_allowed(cantus : Note, previous : Note?, candidate : Note, policy : CandidatePolicy) -> Boolfn candidate_motion(cantus_before : Note, cantus_after : Note, counter_before : Note, counter_after : Note) -> MotionKindfn candidate_score(vertical : Int, melodic : Int, motion_value : MotionKind, policy : CandidatePolicy) -> Intfn constrained_quality(lower : Array[Note], upper : Array[Note], constraint : MotionConstraint) -> Intfn evaluate_candidate(cantus_before : Note, cantus_after : Note, counter_before : Note, counter_after : Note, candidate : Note, policy : CandidatePolicy) -> CandidateEvaluationfn evaluate_candidates(cantus_before : Note, cantus_after : Note, counter_before : Note, counter_after : Note, candidates : Array[Note], policy : CandidatePolicy) -> Array[CandidateEvaluation]fn evaluate_motion(lower_before : Note, lower_after : Note, upper_before : Note, upper_after : Note, constraint : MotionConstraint) -> MotionResultfn evaluate_voice_leading(lower : Array[Note], upper : Array[Note], constraint : MotionConstraint) -> Array[MotionResult]fn generate_candidates(cantus : Note, previous : Note?, policy : CandidatePolicy) -> Array[PitchCandidate]fn generate_voice_candidates(cantus_before : Note, cantus_after : Note, counter_before : Note?, policy : CandidatePolicy) -> CandidateBatchfn history_from_validations(labels : Array[String], validations : Array[PairValidation]) -> AnalysisHistoryfn lesson_step(index : Int, stage : LessonStage, title : String, prompt : String, rule : RuleKind?) -> LessonStepfn make_analysis_export(title : String, profile : RuleProfileConfig, analysis : Analysis) -> AnalysisExportfn partition_phrases(notes : Array[Note], meter : Meter, measures_per_window : Int) -> PhrasePartitionfn quality_gate_report(validation : PairValidation, motion : VoiceLeadingSummary) -> QualityGateReportfn repair_suggestion_all_in_range(suggestions : Array[RepairSuggestion], low : Int, high : Int) -> Boolfn repair_suggestion_for_index(suggestions : Array[RepairSuggestion], index : Int) -> Array[RepairSuggestion]fn repair_suggestions(finding : Finding, notes : Array[Note], max_leap : Int, low : Int, high : Int) -> Array[RepairSuggestion]fn report_with_manifest(document : ReportDocument, profile : RuleProfileConfig, meter : Meter) -> ReportDocumentfn summarize_voice_leading(lower : Array[Note], upper : Array[Note], constraint : MotionConstraint) -> VoiceLeadingSummaryfn validate_pair(cantus : Array[Note], counter : Array[Note], profile : RuleProfileConfig) -> PairValidationfn voices_fit(lower : Array[Note], upper : Array[Note], lower_range : VoiceRange, upper_range : VoiceRange) -> BoolInstall
Download zipExplainable counterpoint rule engine for MoonBit