Krill Architecture
July 30, 2026 · View on GitHub
Overview
Krill is a live-coding music notation system with a dual-implementation strategy: both C++ and JavaScript implement the same parsing, AST transformation, and rendering pipeline to ensure platform flexibility and performance optimization.
The project is structured into two main components:
/core/- Playback infrastructure (both C++ and JS implementations)/app/- Web application frontend (Hapi server + live editor)
Core Infrastructure (/core/)
The core contains the complete playback pipeline implemented in parallel languages:
Shared Components
test-cases.json- Shared test fixtures for pattern evaluationtest-cases-ast.json- Shared AST snapshots for parity validationgrammar.txt- PEG grammar (used by both implementations)
C++ Implementation (/core/cpp/)
Purpose: High-performance parser and renderer for real-time MIDI playback
Components:
-
src/parser/- PEG-based parser using cpp-peglibKrillParser.hpp- Generated parser from grammar.txtParser.hpp- Parser wrapper with error handlingContext.hpp- Parsing context and state managementTypes.hpp- AST node definitions
-
src/renderer/- AST-to-render-tree transformationRenderTreeBuilder.hpp- Main orchestrator for renderingfactories/- Operator-specific node factoriesnodes/- Render node implementations for all operatorsRenderNode.hpp- Base class for all render nodes
-
src/harmony/- Music theory utilitiestheory/Scale.hpp- Scale operationstheory/Roman.hpp- Roman numeral analysiscore/NoteMidi.hpp- MIDI note conversioncore/Interval.hpp- Interval calculations
Build System: CMake with MinGW64 toolchain
cd core/cpp
bash prepare_build.sh
cd build
cmake --build .
Tests: Catch2 framework in tests/ directory
- Parser tests:
tst_parser.cpp - AST parity:
tst_ast_cases.cpp - Render contracts:
tst_run_cases.cpp - Operator nodes:
tst_render_node_*.cpp - Music theory:
tst_harmony_*.cpp
JavaScript Implementation (/core/js/)
Purpose: Node.js runtime implementation for scripting and web environments
Components:
-
input-evaluator.js- Main entry point- Reads
grammar.txtand generates PEGjs parser - Wraps parser with error handling
- Exports
Evaluatorclass
- Reads
-
application.js- Hapi application factory- Initializes web server with routes
- Configures MIDI device detection
- Provides
/grammar.txtand/commandendpoints
-
type.js- AST node type definitions- Mirrors C++
Types.hpp - Type checking and validation
- Mirrors C++
-
patterns/- Pattern evaluationpattern.js- Core pattern implementationpattern-event.js- Event schedulingweaving.js- Timeline weaving
-
renderer/- AST-to-render-tree transformationfactory.js- Node factory dispatcheroperator-nodes/- Operator implementationsrender-node.js- Base render node class
-
playback/- Audio and MIDI playbackengine.js- Main playback orchestratorplayback-device.js- Audio device managementrendering-tree-player.js- Render tree executionsync-device.js- Synchronization utilities
-
music/- Music theory utilitiesharmony.js- Harmony and scale operationsconversion.js- MIDI/note conversion
Tests: Node.js test files with assertion library
npm test # Run all JS tests
npm run update-ast-cases # Regenerate AST snapshots
Data Flow
The pipeline transforms text notation into scheduled MIDI events:
Input Text
↓
Parser (PEG grammar)
↓
Abstract Syntax Tree (AST)
↓
Renderer (Operator evaluation)
↓
Render Tree (Operator nodes with state)
↓
Player (Timeline execution)
↓
MIDI Events
Example
Input: "add(scale(notes(C D E), 0.25), 2)"
AST: AddNode { patterns: [ScaleNode{...}, 2] }
Render: Renders patterns in series with fade-in/out
Output: MIDI note-on/note-off events on timeline
Operator Sets
Both implementations support identical operator sets organized by category:
Time Operators:
add()- Sequential compositionhorizontal()- Parallel horizontal arrangementvertical()- Parallel vertical stacking
Pattern Operators:
pattern()- Named pattern definitionweave()- Timeline interleaving
Transformations:
scale()- Proportional time scalingshift()- Time offsetstretch()- Non-proportional time stretchingtrunc()- Duration truncation
Structural:
struct()- Structural containmentelement()- Atomic element (notes, rests)bjorklund()- Euclidean rhythm generation
Parity Strategy
Goal: Ensure C++ and JavaScript produce identical AST and render trees for identical input
Validation Method:
- Load shared
test-cases.jsonandtest-cases-ast.json - Parse each case in both implementations
- Compare AST structures (must be identical)
- Compare rendered output (must be identical)
- Snapshot comparison catches unintended divergence
Running Parity Tests:
npm run test-parity-contract-all
This command:
- Runs JS tests
- Rebuilds C++ (if needed)
- Runs C++ tests
- Validates AST snapshots match
Exit code 0 = Complete parity.
Test Structure
Shared Fixtures
test-cases.json- 100+ pattern evaluation test casestest-cases-ast.json- 50+ AST validation snapshots
JavaScript Tests (core/js/tests/)
test-runner.js- Main test aggregatortest-evaluator.js- Input parsing and evaluationtest-ast-cases.js- AST parity validationtest-parser-canonicalization.js- Grammar canonicalizationtest-run-cases.js- Full pattern executiontest-harmony.js- Music theory validationtest-render-nodes.js- Operator node verificationtest-render-query-contract.js- Renderer contractstest-player-state-machine.js- Playback state validation
C++ Tests (core/cpp/tests/)
test.cpp- Test framework setuptst_parser.cpp- Parser validationtst_ast_cases.cpp- AST parity (loaded from shared JSON)tst_run_cases.cpp- Pattern evaluation (loaded from shared JSON)tst_render_node_*.cpp- Individual operator nodestst_harmony_*.cpp- Music theory modulestst_peglib_*.cpp- Grammar smoke tests
Web Application (/app/)
Purpose: Live-coding interface for real-time music notation
Entry Point: app/main.js (Hapi server)
Endpoints:
GET /- Main editor interfaceGET /{file*}- Static file serving (CSS, JS, HTML)GET /grammar.txt- Grammar endpoint (for parser updates)GET /command?input=...- Execute pattern and return ASTGET /reporter- Live MIDI device reporter
Configuration:
- Loads core JS implementation via
require('../core/js/application.js') - Detects available MIDI output devices
- Serves static assets from
public/directory
Build and Run
Quick Start:
# Install dependencies
npm install
# Start web server (port 3000)
npm start
# Run all tests
npm test
# Full parity validation
npm run test-parity-contract-all
# Regenerate AST snapshots (when grammar intentionally changes)
npm run update-ast-cases
Development:
- JavaScript: Edit files in
core/js/, tests auto-detect changes - C++: Edit files in
core/cpp/src/, rebuild withcmake --build core/cpp/build - Both: Grammar changes in
grammar.txtrequire AST snapshot regeneration
Directory Structure
krill/
├── core/ # Playback infrastructure
│ ├── cpp/ # C++ implementation
│ │ ├── src/
│ │ │ ├── parser/ # PEG parser
│ │ │ ├── renderer/ # AST renderer
│ │ │ └── harmony/ # Music theory
│ │ ├── tests/ # C++ test suite
│ │ ├── third_party/ # External dependencies
│ │ ├── build/ # CMake build directory
│ │ └── CMakeLists.txt
│ ├── js/ # JavaScript implementation
│ │ ├── input-evaluator.js
│ │ ├── application.js
│ │ ├── type.js
│ │ ├── patterns/
│ │ ├── renderer/
│ │ ├── playback/
│ │ ├── music/
│ │ └── tests/ # JS test suite
│ ├── test-cases.json # Shared test fixtures
│ ├── test-cases-ast.json # Shared AST snapshots
│ ├── grammar.txt # PEG grammar (shared)
│ └── README.md
├── app/ # Web application
│ ├── main.js # Hapi server entry
│ ├── public/
│ │ ├── index-edit.html
│ │ ├── css/
│ │ └── js/ # Browser JS and libs
│ └── README.md
├── tests/ # Integration tests
│ ├── test-parity-contract.js
│ ├── test-parity-contract-all.sh
│ └── README.md
├── docs/
│ ├── ARCHITECTURE.md # This file
│ ├── render-operator-parity.md
│ └── ...
├── package.json # npm configuration
└── README.md # Top-level overview
Contributing
When modifying the architecture:
- Grammar changes → Update
core/grammar.txt→ Runnpm run update-ast-cases - New operators → Implement in both
core/cpp/src/renderer/andcore/js/renderer/→ Add tests to both suites - Parser changes → Update parser logic in both implementations → Ensure parity tests pass
- Playback changes → Modify rendering/player pipeline → Validate with
npm run test-parity-contract-all
See CONTRIBUTING.md for detailed guidelines.