mmlabc-to-smf-rust
A library for converting Music Macro Language (MML) to Standard MIDI File (SMF).
Overview
This library converts a Music Macro Language (MML) format string into a Standard MIDI File. It is written in Rust.
Usage
Used as a library by cat-play-mml.
Status
Frequent breaking changes are being made.
The README is outdated. Many more MML commands are actually implemented. The README will be updated later.
To understand the implemented MML, please refer to tree-sitter-mml/grammar.js first (though this will also be subject to breaking changes in the future).
Implemented Features β
- Basic Note Conversion:
cdefgabβ Conversion to MIDI notes - 4-Pass Architecture: Fully implemented
- Pass 1: Tokenization of MML string (simple parser)
- Pass 2: Conversion of tokens to AST (Abstract Syntax Tree)
- Pass 3: Generation of MIDI events from AST
- Pass 4: Creation of Standard MIDI File from MIDI events
- Channel Functionality: Multi-channel support using semicolons (
;) - JSON Debug Output: Output intermediate results of each pass in JSON
- CLI: Basic operations via command-line arguments
- Comprehensive Tests: All 35 test cases are passing
Verification
# Basic scale conversion
cargo run -- "cdefgab"
# Multi-channel
cargo run -- "c;e;g"
# Custom output file
cargo run -- "cde" -o my_song.mid
Future Outlook
Short-term Goals π§
- tree-sitter Integration: For parsing more complex MML syntax
- Repository Setup: Set up formatter, linter, etc.
- Error Handling: More detailed error messages
Long-term Goals π―
- mmlabc Command Implementation: Full mmlabc format support
- Note length specification (quarter note, eighth note, etc.)
- Octave specification (
>,<) - Control commands for tempo, volume, etc.
- Chord functionality expansion
- Performance Optimization: Fast processing of large MML files
References
- For mmlabc, refer to the mml2abc repository.
Features
- 4-Pass Architecture:
- Pass 1: Parses MML string into tokens (Current: simple parser, Future: tree-sitter)
- Pass 2: Converts tokens to an Abstract Syntax Tree (AST)
- Pass 3: Generates MIDI events from the AST
- Pass 4: Creates a Standard MIDI File
- Multi-channel Support: Semicolons (
;) separate concurrent channels - JSON Debug Output: Intermediate results of each pass can be saved and reviewed in JSON format
- Comprehensive Tests: A total of 35 test cases, including unit and integration tests
- Safe Design: Memory safety ensured by Rustβs type system and ownership model
Requirements
- Rust 1.70.0 or later
- Cargo
Installation
Development Version (Current State)
git clone https://github.com/cat2151/mmlabc-to-smf-rust
cd mmlabc-to-smf-rust
cargo build --release
Direct Execution (via Cargo)
cargo run -- "cdefgab"
Usage
Basic Usage
# Convert basic scale (automatically played by cat-play-mml by default)
cargo run -- "cdefgab"
# Multi-channel (concurrent notes)
cargo run -- "c;e;g" # C major chord
# Custom output file
cargo run -- "cde" -o my_song.mid
# Disable auto-playback
cargo run -- "cde" --no-play
Auto-playback Feature
By default, after generating a MIDI file, it will be automatically played using the cat-play-mml command.
This allows you to immediately check the sound during MML development.
- Use the
--no-playoption to disable auto-playback. - If
cat-play-mmlis not installed, a warning message will be displayed, but the MIDI file will still be generated correctly.
Configuring a Custom Player
You can configure a custom MIDI player by creating a mmlabc-to-smf-rust.toml file in the directory where you run the tool.
Example configuration file:
# mmlabc-to-smf-rust.toml
external_smf_player = "timidity"
Common configurable MIDI players:
timidity- TiMidity++ MIDI playerfluidsynth- FluidSynth software synthesizervlc- VLC media playercat-play-mml(default)
If no configuration file exists, cat-play-mml will be used by default.
Refer to mmlabc-to-smf-rust.toml.example for a sample configuration file.
Output Files
Running the command will generate the following files:
pass1_tokens.json- Pass 1 token information (for debugging)pass2_ast.json- Pass 2 AST information (for debugging)pass3_events.json- Pass 3 MIDI event information (for debugging)output.mid- The final MIDI file
Supported MML Notation
Currently supported notation:
- Basic Notes:
c,d,e,f,g,a,b(case-insensitive) - Multi-channel:
;for channel separation (concurrent notes)
Examples:
cdefgab β Consecutive playback of C, D, E, F, G, A, B
c;e;g β Simultaneous playback of C, E, G (C major chord)
Development
Build
cargo build # Debug build
cargo build --release # Release build
Tests
cargo test # Run all tests (35 test cases)
Format & Lint
cargo clippy # Code quality check
cargo fmt --check # Format check
cargo fmt # Apply formatting
tree-sitter Parser Files
The tree-sitter parser files (under tree-sitter-mml/src/) are git-tracked following tree-sitter best practices for reliable distribution on crates.io.
Development Workflow:
- The C source files (
parser.c,grammar.json,node-types.json, and thetree_sitter/directory) are automatically regenerated whengrammar.jsis modified. - The build script checks the file modification times and regenerates them only if necessary.
- Prerequisite: If you update the grammar, Node.js and npx must be installed on your system.
- Regular builds (without grammar changes) use the committed C files and therefore do not require Node.js.
Why commit generated files? This follows best practices in the tree-sitter ecosystem:
- Users installing from crates.io do not need Node.js or
tree-sitter-cli. - It ensures that the grammar and parser versions precisely match.
- It simplifies CI/CD and cross-platform builds.
- It is standard practice for all tree-sitter language crates.
Updating the Grammar:
If you modify tree-sitter-mml/grammar.js:
- Run
cargo build- the build script will detect the change and regenerate the parser files. - Commit both
grammar.jsand the regenerated C files together. - This keeps the grammar and parser synchronized.
To manually regenerate the parser files:
cd tree-sitter-mml
npm install # if tree-sitter-cli is not yet installed
npx tree-sitter generate
Project Structure
src/
βββ main.rs # CLI entry point
βββ lib.rs # Library root
βββ pass1_parser.rs # Pass 1: Token Parsing
βββ pass2_ast.rs # Pass 2: AST Transformation
βββ pass3_events.rs # Pass 3: MIDI Event Generation
βββ pass4_midi.rs # Pass 4: MIDI File Creation
βββ tree_sitter_mml.rs # tree-sitter MML Integration
βββ types.rs # Common Type Definitions
tests/
βββ integration_test.rs # Integration Tests
βββ test_channel.rs # Channel Feature Tests
βββ test_pass1.rs # Pass 1 Tests
βββ test_pass2.rs # Pass 2 Tests
βββ test_pass3.rs # Pass 3 Tests
βββ test_pass4.rs # Pass 4 Tests
License
MIT License - See the LICENSE file for details.
Reference
- Original Python implementation: cat2151/mmlabc-to-smf