minacalc-rs

Crates.iominacalc-rs
lib.rsminacalc-rs
version0.4.2
created_at2025-08-12 20:58:37.782876+00
updated_at2026-01-19 11:20:07.800025+00
descriptionRust bindings for MinaCalc C++ library (Etterna rating calculator)
homepage
repositoryhttps://github.com/Glubus/minacalc-rs
max_upload_size
id1792890
size764,434
Osef (Glubus)

documentation

README

MinaCalc Rust Bindings

Crates.io Documentation License: MIT

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.

Features

  • Core Difficulty Calculation: Calculate MSD (MinaCalc Skill Difficulty) scores for rhythm game charts
  • Multi-rate Support: Get difficulty scores for music rates from 0.7x to 2.0x
  • Pattern Analysis: Analyze specific skillsets like stream, jumpstream, handstream, stamina, and more
  • Thread-safe: Multi-threaded calculator pool for high-performance applications
  • ROX Integration: Parse and analyze rhythm game chart files using the rhythm-open-exchange format
  • Utility Functions: Helper functions for pattern analysis and difficulty comparison
  • HashMap Conversion: Easy conversion to HashMap format for flexible data handling

Prerequisites

  • Rust: Edition 2021 or later
  • C++ Compiler: C++17 compatible compiler (MSVC on Windows, GCC/Clang on Unix)
  • Build Tools: cc and bindgen (automatically handled by Cargo)

Installation

Add this to your Cargo.toml:

[dependencies]
minacalc-rs = "0.4.0"

Feature Flags

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 results
  • thread: Provides thread-safe calculator pool
  • rox: Provides chart parsing via rhythm-open-exchange (supports .sm, .osu, .rox formats)
  • utils: Provides utility functions for pattern analysis

Quick Start

Basic Usage

use 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(&notes)?;
    
    // 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(())
}

HashMap Conversion

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(&notes)?;
    
    // 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(())
}

Chart File Analysis with ROX

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(())
}

Pattern Analysis with Utils

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(&notes)?;
    
    // 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(())
}

Thread-safe Calculator Pool

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(&notes)?;
    
    println!("Overall difficulty: {:.2}", msd_results.msds[3].overall);
    
    Ok(())
}

API Reference

Core Types

  • Calc: Main calculator instance
  • Note: Represents a note in a rhythm game chart
  • AllRates: Contains MSD scores for all music rates (0.7x to 2.0x)
  • SkillsetScores: Individual skillset scores (stream, jumpstream, etc.)

Skillsets

The calculator provides scores for these skillsets:

  • Overall: General difficulty rating
  • Stream: Consecutive note patterns
  • Jumpstream: Jump patterns in streams
  • Handstream: Two-handed patterns
  • Stamina: Endurance requirements
  • Jackspeed: Jack pattern speed
  • Chordjack: Chord jack patterns
  • Technical: Technical complexity

Music Rates

Scores are calculated for 14 different music rates:

  • 0.7x, 0.8x, 0.9x, 1.0x, 1.1x, 1.2x, 1.3x, 1.4x, 1.5x, 1.6x, 1.7x, 1.8x, 1.9x, 2.0x

Examples

The crate includes several examples demonstrating different use cases:

  • basic_usage: Basic MSD calculation
  • rox: Chart file analysis with ROX
  • utils_example: Pattern analysis utilities

Run 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"

Error Handling

The crate uses custom error types for different failure modes:

  • MinaCalcError: General calculation errors
  • RoxError: Chart file parsing errors

All functions return Result<T, E> for proper error handling.

Thread Safety

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.

Performance

  • Single-threaded: Optimized for single-threaded applications
  • Multi-threaded: Use CalcPool for concurrent calculations
  • Memory: Efficient memory usage with minimal allocations
  • Caching: Calculator instances can be reused for multiple calculations

Contributing

Contributions 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.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Acknowledgments

  • MinaCalc: The original C++ difficulty calculator
  • Etterna: The rhythm game that inspired this project
  • Rust Community: For the excellent tooling and ecosystem

Changelog

v0.4.0

  • Unified API: Consolidated calculation methods into generic calculate_at_rate and calculate_all_rates.
  • Capped Parameter: Introduced capped parameter to toggle between SSR (capped) and MSD (uncapped/skillset rating) calculations. Defaults to false (MSD).
  • Breaking Change: Removed legacy aliases (calc_msd, calc_ssr, CalculateSsr, CalculateSsrFromFile) in favor of the new generic API.

v0.3.5

  • Fixed score_goal parameter (now uses 0.93 instead of 93.0)
  • Synchronized all binding versions

v0.3.1

  • Added C# bindings (bindings/csharp)
  • Added Python bindings (bindings/python)
  • Added api feature for FFI exports (file path and string content support)
  • Added GitHub Actions release workflow (PyPI, Crates.io, NuGet)

v0.3.0

  • Removed deprecated osu feature (use rox instead for chart parsing)
  • Updated rhythm-open-exchange dependency
  • Updated MinaCalc to 515 version

v0.2.1

  • Added utils feature with pattern analysis functions

v0.2.0

  • Added rox feature for universal chart parsing
  • Added thread feature for thread-safe calculator pools
  • Improved HashMap conversion utilities
  • Enhanced documentation and examples
  • Improved type naming (AllRates instead of MsdForAllRates)
  • Enhanced error handling and documentation
  • Fixed namespace conflicts in bindings

v0.1.0

  • Initial release with basic MSD calculation
  • Core calculator functionality
  • Basic Rust bindings for MinaCalc
Commit count: 48

cargo fmt