minacalc-rs

Crates.iominacalc-rs
lib.rsminacalc-rs
version0.2.2
created_at2025-08-12 20:58:37.782876+00
updated_at2025-09-07 10:27:29.670708+00
descriptionRust bindings for MinaCalc C++ library (Etterna rating calculator)
homepage
repositoryhttps://github.com/Glubus/minacalc-rs
max_upload_size
id1792890
size449,300
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, Etterna, and osu!.

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
  • osu! Integration: Parse and analyze osu! beatmap files directly
  • 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.2.1"

Feature Flags

The crate supports several optional features:

[dependencies]
minacalc-rs = { version = "0.2.1", features = ["hashmap", "thread", "osu", "utils"] }
  • hashmap (default): Provides HashMap conversion for MSD results
  • thread: Provides thread-safe calculator pool
  • osu: Provides osu! beatmap parsing and calculation
  • 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(())
}

osu! Beatmap Analysis

use minacalc_rs::{Calc, OsuCalcExt};
use std::path::PathBuf;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let calc = Calc::new()?;
    
    // Analyze an osu! beatmap file
    let beatmap_path = PathBuf::from("path/to/beatmap.osu");
    let msd_results = calc.calculate_msd_from_osu_file(beatmap_path)?;
    
    // 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
  • osu: osu! beatmap analysis
  • utils_example: Pattern analysis utilities

Run examples with:

# Basic usage
cargo run --example basic_usage

# osu! beatmap analysis (requires osu feature)
cargo run --example osu --features="osu 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
  • OsuError: osu! beatmap 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.2.1

  • Added utils feature with pattern analysis functions

v0.2.0

  • Added osu feature for beatmap 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: 23

cargo fmt