| Crates.io | minacalc-rs |
| lib.rs | minacalc-rs |
| version | 0.4.2 |
| created_at | 2025-08-12 20:58:37.782876+00 |
| updated_at | 2026-01-19 11:20:07.800025+00 |
| description | Rust bindings for MinaCalc C++ library (Etterna rating calculator) |
| homepage | |
| repository | https://github.com/Glubus/minacalc-rs |
| max_upload_size | |
| id | 1792890 |
| size | 764,434 |
Safe and ergonomic Rust bindings for the MinaCalc rhythm game difficulty calculator. This crate provides high-level Rust APIs for calculating difficulty scores in rhythm games like StepMania and Etterna.
cc and bindgen (automatically handled by Cargo)Add this to your Cargo.toml:
[dependencies]
minacalc-rs = "0.4.0"
The crate supports several optional features:
[dependencies]
minacalc-rs = { version = "0.4.0", features = ["hashmap", "thread", "rox", "utils"] }
hashmap (default): Provides HashMap conversion for MSD resultsthread: Provides thread-safe calculator poolrox: Provides chart parsing via rhythm-open-exchange (supports .sm, .osu, .rox formats)utils: Provides utility functions for pattern analysisuse minacalc_rs::{Calc, Note};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a calculator instance
let calc = Calc::new()?;
// Define some notes (4K chart)
let notes = vec![
Note { notes: 1, row_time: 0.0 }, // Left column at 0s
Note { notes: 8, row_time: 1.0 }, // Right column at 1s
Note { notes: 15, row_time: 2.0 }, // All columns at 2s
];
// Calculate MSD scores for all rates
let msd_results = calc.calc_msd(¬es)?;
// Access scores for specific rates
let overall_1x = msd_results.msds[3].overall; // 1.0x rate
let stream_2x = msd_results.msds[13].stream; // 2.0x rate
println!("1.0x Overall: {:.2}", overall_1x);
println!("2.0x Stream: {:.2}", stream_2x);
Ok(())
}
use minacalc_rs::{Calc, Note, HashMapCalcExt};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let calc = Calc::new()?;
let notes = vec![
Note { notes: 1, row_time: 0.0 },
Note { notes: 8, row_time: 1.0 },
];
let msd_results = calc.calc_msd(¬es)?;
// Convert to HashMap for easy access
let hashmap = msd_results.as_hashmap()?;
// Access by rate string
if let Some(scores) = hashmap.get("1.0") {
println!("1.0x: Overall={:.2}, Stream={:.2}",
scores.overall, scores.stream);
}
Ok(())
}
use minacalc_rs::{Calc, RoxCalcExt};
use std::path::PathBuf;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let calc = Calc::new()?;
// Analyze a chart file (supports .sm, .osu, .rox formats)
let chart_path = PathBuf::from("path/to/chart.sm");
let msd_results = calc.calc_msd_from_rox_file(&chart_path, 1.0)?;
// Access scores for different rates
let rates = [0.7, 1.0, 1.5, 2.0];
let rate_indices = [0, 3, 8, 13];
for (rate, &index) in rates.iter().zip(rate_indices.iter()) {
if index < msd_results.msds.len() {
let scores = msd_results.msds[index];
println!("{:.1}x: Overall={:.2}, Stream={:.2}, Tech={:.2}",
rate, scores.overall, scores.stream, scores.technical);
}
}
Ok(())
}
use minacalc_rs::{Calc, Note, utils::*};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let calc = Calc::new()?;
let notes = vec![
Note { notes: 1, row_time: 0.0 },
Note { notes: 8, row_time: 1.0 },
];
let msd_results = calc.calc_msd(¬es)?;
// Get top 3 patterns for 1.0x rate
let top_patterns = calculate_highest_patterns(&msd_results.msds[3], 3);
println!("Top 3 patterns: {:?}", top_patterns);
// Get all patterns ranked by difficulty
let all_patterns = calculate_highest_patterns(&msd_results.msds[3], 7);
println!("All patterns ranked: {:?}", all_patterns);
Ok(())
}
use minacalc_rs::thread::{CalcPool, Note};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a pool of calculators
let pool = CalcPool::new(4)?; // 4 calculator instances
let notes = vec![
Note { notes: 1, row_time: 0.0 },
Note { notes: 8, row_time: 1.0 },
];
// Calculate MSD using the pool
let msd_results = pool.calc_msd(¬es)?;
println!("Overall difficulty: {:.2}", msd_results.msds[3].overall);
Ok(())
}
Calc: Main calculator instanceNote: Represents a note in a rhythm game chartAllRates: Contains MSD scores for all music rates (0.7x to 2.0x)SkillsetScores: Individual skillset scores (stream, jumpstream, etc.)The calculator provides scores for these skillsets:
Scores are calculated for 14 different music rates:
The crate includes several examples demonstrating different use cases:
basic_usage: Basic MSD calculationrox: Chart file analysis with ROXutils_example: Pattern analysis utilitiesRun examples with:
# Basic usage
cargo run --example basic_usage
# ROX chart analysis (requires rox feature)
cargo run --example rox --features="rox hashmap"
# Utils example (requires utils feature)
cargo run --example utils_example --features="utils"
The crate uses custom error types for different failure modes:
MinaCalcError: General calculation errorsRoxError: Chart file parsing errorsAll functions return Result<T, E> for proper error handling.
The Calc type is not Send or Sync by default. For multi-threaded applications, use the CalcPool from the thread feature, which provides a thread-safe pool of calculator instances.
CalcPool for concurrent calculationsContributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
This project is licensed under the MIT License - see the LICENSE file for details.
calculate_at_rate and calculate_all_rates.capped parameter to toggle between SSR (capped) and MSD (uncapped/skillset rating) calculations. Defaults to false (MSD).calc_msd, calc_ssr, CalculateSsr, CalculateSsrFromFile) in favor of the new generic API.bindings/csharp)bindings/python)api feature for FFI exports (file path and string content support)osu feature (use rox instead for chart parsing)utils feature with pattern analysis functionsrox feature for universal chart parsingthread feature for thread-safe calculator poolsAllRates instead of MsdForAllRates)