Beat Saber Mapping Framework
Project description
Beat Saber Mapping Framework (bsmap)
⚠️ Warning: When loading beatmaps, bsmap will attempt to convert them to v3 if not already. Try to make all maps in v3 to avoid data loss!
Table of Contents
- Introduction
- Installation
- Core Concepts
- Getting Started
- Module Reference
- Advanced Features
- Best Practices
- Troubleshooting
- Contributing
1. Introduction
The Beat Saber Mapping Framework is a comprehensive Python library designed to simplify the creation, manipulation, and analysis of Beat Saber maps. Built on Pydantic for strong type validation, the framework provides an intuitive and object-oriented approach to Beat Saber mapping, supporting all official characteristics and various customization options through mods like Chroma and Noodle Extensions.
Key Features
- Complete Data Model: Full support for all Beat Saber map components with type safety
- Multi-Characteristic Support: Support for all official characteristics (Standard, OneSaber, 360Degree, etc.)
- Pattern Generation: Built-in utilities for common note patterns
- Map Analysis: Automated difficulty estimation and issue detection
- Lighting Automation: Tools to generate synchronized lighting effects
- Template Management: Save and reuse mapping patterns
- Map Operations: Mirror, copy, shift, and manipulate map sections
- Command-Line Interface: Scriptable operations for automation
2. Installation
Requirements
- Python 3.9 or higher
- Pydantic 2.0 or higher
Installation via pip
pip install bsmap
Manual Installation
git clone https://github.com/CodeSoftGit/bsmap.git
cd bsmap
pip install -e .
3. Core Concepts
Map Structure
A Beat Saber map consists of:
- Info.dat: Contains metadata about the song and available difficulties
- Beatmap Files: Contains notes, obstacles, events, and other gameplay elements
- Audio File: The song audio file
- Cover Image: The map's cover art
Key Components
- Notes: The red and blue cubes that players hit
- Bombs: Black spheres with points that must be avoided
- Walls: Obstacles that players must dodge
- Events: Lighting and environment effects
- Characteristics: Different play modes (Standard, OneSaber, etc.)
- Difficulties: Different difficulty levels (Easy, Normal, Hard, etc.)
Data Flow
[Disk: Input Files]
- Info.dat (JSON)
- <Difficulty>.dat (JSON, e.g., Expert.dat)
- template.json (JSON, for bsmap.templates)
|
| (File Read, JSON Parsing)
V
+-------------------------------------------------------------------------------------------------+
| bsmap.mapper.BeatSaberMapper |
|-------------------------------------------------------------------------------------------------|
| - load_info_dat(filePath) |
| - load_beatmap(filePath) |
| - load_map_folder(folderPath) |
| - create_empty_map(...) |
| (These methods parse JSON and instantiate Pydantic models from bsmap.models) |
+-------------------------------------------------------------------------------------------------+
|
| (Data converted to In-Memory Pydantic Model Instances)
V
+-------------------------------------------------------------------------------------------------+
| In-Memory Data Representation (Defined in bsmap.models & bsmap.custom_data) |
|-------------------------------------------------------------------------------------------------|
| 1. info_dat_object: bsmap.models.InfoDat |
| - Contains: _version, _songName, _bpm, _difficultyBeatmapSets (list of DifficultyBeatmapSet),|
| _customData (can hold bsmap.custom_data.Settings), etc. |
| |
| 2. beatmap_files_dict: Dict[str, bsmap.models.BeatmapFile] |
| - Key: beatmapFilename (e.g., "Expert.dat") |
| - Value: beatmap_object: bsmap.models.BeatmapFile |
| - Contains: version, bpmEvents, rotationEvents, waypoints, |
| colorNotes: List[bsmap.models.ColorNote] |
| bombNotes: List[bsmap.models.BombNote] |
| obstacles: List[bsmap.models.Obstacle] |
| sliders: List[bsmap.models.Slider] |
| basicBeatmapEvents: List[bsmap.models.BasicBeatmapEvent] |
| customData (can hold bsmap.custom_data.NoodleExtensions, |
| bsmap.custom_data.ChromaData, bsmap.custom_data.Animation), etc. |
+-------------------------------------------------------------------------------------------------+
| ^ | ^ | ^
| (Read) | (Modify/Create) | (Read) | (Modify) | (R/W) | (Create from/to file)
V | V | V |
+---------------------------+ +---------------------------------+ +--------------------------------+
| bsmap.analysis | | bsmap.autolights | | bsmap.templates |
| (MapAnalysis class) | | (LightingAutomation class) | | (TemplateManager class) |
|---------------------------| |---------------------------------| |--------------------------------|
| - get_note_statistics() |---->| (Reads BeatmapFile object) |<----| - save_template() |
| (Input: BeatmapFile) | | | | (Input: BeatmapFile section) |
| (Output: Dict stats) | | - generate_advanced_lighting() |--+ | (Output: template.json file) |
| | | (Input: BeatmapFile) | | | |
| - identify_map_issues() | | (Modifies BeatmapFile by | | | - (load_template()) |
| (Input: BeatmapFile) | | adding BasicBeatmapEvents) | | | (Input: template.json file) |
| (Output: List issues) | +---------------------------------+ | | (Output: BeatmapFile object) |
+---------------------------+ ^ |+--------------------------------+
| (Returns |
| modified |
| BeatmapFile |
| object) |
+----------------+
(bsmap.operations.MapOperations - also interacts here, modifying BeatmapFile objects)
(bsmap.utils - provides helper functions)
|
| (Processed/Modified In-Memory Pydantic Model Instances)
V
+-------------------------------------------------------------------------------------------------+
| bsmap.mapper.BeatSaberMapper |
|-------------------------------------------------------------------------------------------------|
| - save_info_dat(InfoDat, filePath) |
| - save_beatmap(BeatmapFile, filePath) |
| - save_map_folder(folderPath, InfoDat, beatmap_files_dict) |
| - validate_map(InfoDat, beatmap_files_dict) (Reads models, returns validation warnings) |
| (These methods serialize Pydantic models to JSON and write to files) |
+-------------------------------------------------------------------------------------------------+
|
| (File Write, Pydantic Model Serialization to JSON)
V
[Disk: Output Files]
- Info.dat (JSON)
- <Difficulty>.dat (JSON)
- (Potentially new/modified template.json files via TemplateManager)
4. Getting Started
Creating a New Map
from bsmap import BeatSaberMapper, Characteristic, Difficulty
# Create a new map
info, beatmaps = BeatSaberMapper.create_empty_map(
song_name="My First Map",
song_author="Artist Name",
level_author="Your Name",
bpm=120.0,
audio_filename="song.ogg",
cover_filename="cover.jpg",
characteristics=[Characteristic.STANDARD.value],
difficulties=[Difficulty.EASY.value, Difficulty.NORMAL.value]
)
# Save the map
BeatSaberMapper.save_map_folder("./my_first_map", info, beatmaps)
Adding Notes
from bsmap import ColorNote, NoteDirection, NoteColor
# Get the Easy difficulty beatmap
easy_beatmap = beatmaps["Easy.dat"]
# Add a red note
easy_beatmap.colorNotes.append(
ColorNote(
b=1.0, # Beat time
x=1, # X position (0-3)
y=0, # Y position (0-2)
c=NoteColor.RED.value, # Color
d=NoteDirection.UP.value # Direction
)
)
# Add a blue note
easy_beatmap.colorNotes.append(
ColorNote(
b=2.0,
x=2,
y=0,
c=NoteColor.BLUE.value,
d=NoteDirection.UP.value
)
)
# Sort notes by beat time
easy_beatmap.sort_objects()
Adding Lighting
from bsmap.autolights import LightingAutomation
# Generate basic lighting
LightingAutomation.generate_basic_lighting(
beatmap=easy_beatmap,
beat_divisor=0.5, # Eighth notes
intensity=0.8
)
5. Module Reference
Models Module
The models module contains Pydantic models representing Beat Saber map structures:
InfoDat: Represents the main Info.dat fileBeatmapFile: Represents a beatmap file (e.g., Expert.dat)ColorNote: Represents a red/blue noteBombNote: Represents a bombObstacle: Represents a wall/obstacleRotationEvent: Represents a rotation event for 360/90 maps- Enums:
Difficulty,Characteristic,NoteColor,NoteDirection
Mapper Module
The mapper module provides utilities for working with Beat Saber maps:
BeatSaberMapper.create_empty_map(): Create a new mapBeatSaberMapper.load_map_folder(): Load a map from a folderBeatSaberMapper.save_map_folder(): Save a map to a folderBeatSaberMapper.validate_map(): Validate a map for issues
Operations Module
The operations module provides utilities for map operations:
MapOperations.mirror_map(): Create a mirrored version of a mapMapOperations.copy_section(): Copy a section of a mapMapOperations.paste_section(): Paste a section into a mapMapOperations.shift_section(): Shift a section of a mapMapOperations.adjust_difficulty(): Adjust difficulty parameters
Analysis Module
The analysis module provides utilities for analyzing maps:
MapAnalysis.get_note_statistics(): Calculate statistics about a mapMapAnalysis.identify_mapping_issues(): Identify potential issuesMapAnalysis.compare_maps(): Compare two maps and calculate differences
AutoLights Module
The autolights module provides utilities for lighting automation:
LightingAutomation.generate_basic_lighting(): Generate basic lightingLightingAutomation.generate_note_sync_lighting(): Generate note-synchronized lightingLightingAutomation.generate_advanced_lighting(): Generate advanced lighting
Templates Module
The templates module provides utilities for template management:
TemplateManager.save_template(): Save a section as a templateTemplateManager.load_template(): Load a templateTemplateManager.list_templates(): List available templatesTemplateManager.search_templates(): Search templates
6. Advanced Features
Working with Custom Data
from bsmap.custom_data import NoodleExtensions, ChromaData
# Add Noodle Extensions custom data
note.customData = {
"track": "example_track",
"coordinates": [1.5, 0.0],
"disableNoteLook": True
}
# Using the structured models
noodle_data = NoodleExtensions(
track="example_track",
coordinates=[1.5, 0.0],
disableNoteLook=True
)
note.customData = noodle_data.model_dump(exclude_none=True)
Working with 360/90 Degree Maps
# Create a 360 degree map
info, beatmaps = BeatSaberMapper.create_empty_map(
# ... other parameters ...
characteristics=[Characteristic.DEGREE_360.value],
difficulties=[Difficulty.EXPERT.value]
)
# Get the Expert difficulty beatmap
expert_beatmap = beatmaps["Expert360Degree.dat"]
# Add rotation events
expert_beatmap.rotationEvents.append(
RotationEvent(
b=1.0, # Beat time
e=0, # Event type
r=90 # Rotation in degrees
)
)
Creating a Custom Pattern Template
from bsmap import BeatmapFile, ColorNote, NoteColor, NoteDirection
from bsmap.operations import MapOperations
from bsmap.templates import TemplateManager
# --- 1. Define the Pattern ---
# Create a temporary BeatmapFile to hold the pattern.
# Notes within this pattern should be timed starting from beat 0 relative to the pattern itself.
pattern_bm = BeatmapFile(version="3.3.0") # Or match your map's specific version
# Example: A short, fast stream of 4 alternating notes
# (e.g., Red on left, Blue on right, all down-cuts)
pattern_notes_data = [
ColorNote(b=0.0, x=1, y=1, c=NoteColor.RED.value, d=NoteDirection.DOWN.value), # Beat 0 of pattern
ColorNote(b=0.25, x=2, y=1, c=NoteColor.BLUE.value, d=NoteDirection.DOWN.value), # Beat 0.25 of pattern
ColorNote(b=0.5, x=1, y=1, c=NoteColor.RED.value, d=NoteDirection.UP.value), # Beat 0.5 of pattern
ColorNote(b=0.75, x=2, y=1, c=NoteColor.BLUE.value, d=NoteDirection.UP.value), # Beat 0.75 of pattern
]
pattern_bm.colorNotes.extend(pattern_notes_data)
pattern_bm.sort_objects() # Good practice after adding or modifying map objects
# --- 2. Save the Pattern as a Template ---
template_name = "MyShortFastStream"
TemplateManager.save_template(
beatmap_section=pattern_bm, # The BeatmapFile instance containing the pattern
name=template_name,
description="A short, fast stream of 4 alternating notes (R-B-R-B)",
tags=["stream", "fast", "short", "alternating"]
)
# You can confirm by checking if a file like "MyShortFastStream.json" (or similar)
# is created in your templates directory.
# --- 3. Load and Use the Template in an Existing Map ---
# Assume 'target_map_difficulty' is an existing BeatmapFile object you are working on
# (e.g., for an ExpertPlus difficulty).
# It might be loaded like this:
# info, beatmaps = BeatSaberMapper.load_map_folder("./my_map_project")
# target_map_difficulty = beatmaps["ExpertPlusStandard.dat"]
#
# For this example, we'll initialize a new BeatmapFile instance to demonstrate the functionality.
target_map_difficulty = BeatmapFile(version="3.3.0")
# Ensure it's sorted if it was empty or newly created
target_map_difficulty.sort_objects()
# Load the template you saved earlier
# - 'loaded_pattern_bm' will be a BeatmapFile containing the notes from the template.
# - 'metadata' will hold the template's name, description, tags, etc.
loaded_pattern_bm, metadata = TemplateManager.load_template(template_name)
# Specify the beat in your 'target_map_difficulty' where the pattern should start
paste_at_beat = 32.0
# Paste the loaded pattern into your target map.
# The notes from 'loaded_pattern_bm' (timed from beat 0 within that template)
# will be offset by 'paste_at_beat' and added to 'target_map_difficulty'.
MapOperations.paste_section(
target=target_map_difficulty, # Your main BeatmapFile object to modify
section=loaded_pattern_bm, # The BeatmapFile loaded from the template
target_beat=paste_at_beat # Beat in 'target' where the pattern begins
)
target_map_difficulty.sort_objects() # Re-sort after adding new objects from the pattern
# Now, 'target_map_difficulty' contains the notes from the pasted pattern.
7. Best Practices
Map Organization
- Consistent Spacing: Use consistent spacing between notes for readability
- Pattern Recognition: Create recognizable patterns that flow well
- Visual Clarity: Avoid vision blocks and excessive density
- Map Progression: Gradually increase difficulty throughout the map
- Sync to Music: Ensure notes match the rhythm and energy of the song
Code Organization
- Sort Objects: Always call
beatmap.sort_objects()after adding notes - Use Enums: Use provided enums instead of raw values
- Validate Maps: Run
BeatSaberMapper.validate_map()to check for issues - Model Validation: Ensure all objects have required fields properly set
- Clear Custom Data: Use the provided models for custom data
Performance Optimization
- Batch Processing: Process notes in batches rather than one at a time
- Reuse Templates: Use templates for repeated patterns
- Efficient Lighting: Limit the number of lighting events
- Memory Management: Clear large collections when no longer needed
- Object Pooling: Reuse objects when processing large maps
8. Troubleshooting
Common Errors
-
AttributeError: 'dict' object has no attribute 'x'
- Cause: Using a dictionary instead of a proper model object
- Solution: Use the appropriate model class instead of raw dictionaries
-
TypeError: Object of type x is not JSON serializable
- Cause: Custom Python objects in customData
- Solution: Ensure all data is JSON serializable
-
ValueError: field required
- Cause: Missing required field when creating a model
- Solution: Check model definition to see which fields are required
Validation Issues
-
Note Position Outside Grid
- Cause: Invalid x or y position for notes
- Solution: Ensure x is 0-3 and y is 0-2
-
Same Hand Double Notes
- Cause: Multiple notes of the same color at the same time
- Solution: Adjust timing or use different colors
-
Missing Rotation Events in 360/90 Maps
- Cause: 360/90 degree map without rotation events
- Solution: Add appropriate rotation events
9. Contributing
We welcome contributions to the Beat Saber Mapping Framework! Here's how you can help:
- Report Issues: Submit bug reports and feature requests
- Submit Pull Requests: Contribute code improvements and new features (i work for free, help is greatly appreciated!)
- Improve Documentation: Help clarify and expand the documentation so others can get started easier
- Share Templates: Contribute reusable templates and patterns
- Spread the Word: Tell others about the framework
License
The Beat Saber Mapping Framework (bsmap) is released under the MIT License. See the LICENSE file for details.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file bsmap-1.1.3.tar.gz.
File metadata
- Download URL: bsmap-1.1.3.tar.gz
- Upload date:
- Size: 33.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.11.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
20f005959a671da4d24cd57fe3a706cdf0a1d990e9c51ca3db34b1fb5dace11c
|
|
| MD5 |
9b3d8b66acbf98603f6920fbf938f82c
|
|
| BLAKE2b-256 |
db8e5de51c9cb9f958dc495ab4f4ead927668725e1217d3a6c5984b055c3262c
|
File details
Details for the file bsmap-1.1.3-py3-none-any.whl.
File metadata
- Download URL: bsmap-1.1.3-py3-none-any.whl
- Upload date:
- Size: 31.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.11.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
161b8293e96a72b0f546157f8ac57e77f1a806b0a6ca2c2ab6c060929c414416
|
|
| MD5 |
d62d768fe239b492219cbccbb029c772
|
|
| BLAKE2b-256 |
ff3eca5b5949fa7482114dd81e4abc0a6c3e524991bbc30e0e572497f2455b84
|