Chat
Ask me anything
Ithy Logo
Sign in to save

Beat Saber Map Generator AI using Transformers

Comprehensive Guide to Building an AI-Powered Beat Saber Map Generator with Transformer Models

beat saber gameplay scene

Key Takeaways

  • Data Preparation is Crucial: Comprehensive data collection and feature extraction are foundational for effective model training.
  • Transformer Architecture Enables Sequence Mapping: Utilizing transformers facilitates the mapping of audio sequences to synchronized note placements.
  • Post-Processing Ensures Playability: Refining generated maps through post-processing guarantees consistency and enjoyable gameplay.

Introduction

Building an AI-powered Beat Saber map generator using transformers involves integrating various components, including audio analysis, data preprocessing, model training, and map formatting. Leveraging state-of-the-art transformer architectures enables the creation of synchronized and engaging Beat Saber maps that align with the rhythm and dynamics of the music.


1. Data Collection and Preparation

1.1 Gathering Beat Saber Maps and Audio Files

To train an effective transformer model, it's essential to compile a large and diverse dataset of Beat Saber maps and their corresponding audio tracks. Sources such as BeatSaver provide a repository of community-created maps that can be utilized for this purpose.

1.2 Feature Extraction from Audio

Extracting meaningful features from audio files is pivotal for the model to understand the rhythm and dynamics. Libraries like librosa can be employed to extract features such as:

  • Tempo detection
  • Beat tracking
  • Mel-frequency cepstral coefficients (MFCCs)
  • Chroma features

1.3 Preprocessing Beat Saber Map Data

Preprocessing involves converting Beat Saber maps into a suitable format for training. This includes representing note placements, directions, and timings as sequences that the transformer can interpret.

2. Transformer Model Architecture

2.1 Sequence-to-Sequence Model

A sequence-to-sequence transformer model is ideal for mapping audio features to Beat Saber maps. The encoder handles the audio feature sequences, while the decoder generates the corresponding map sequences.

2.2 Model Adaptations

Adaptations to the standard transformer architecture may be necessary to account for the rhythmic and spatial aspects of Beat Saber maps, such as temporal alignment and directional cues.

3. Training the Model

3.1 Defining the Loss Function

Using appropriate loss functions, such as cross-entropy loss, is crucial for training the model effectively.

3.2 Hyperparameter Tuning

Selecting and tuning hyperparameters like learning rate, batch size, and model dimensions can significantly impact model performance.

3.3 Training Techniques

Implementing techniques like teacher forcing can enhance convergence and the quality of the generated maps.

4. Map Generation

4.1 Generating Note Sequences

Providing the trained model with new audio features allows the decoder to produce sequences that dictate note timings, directions, and placements.

4.2 Post-Processing for Consistency

Ensuring that generated maps are consistent and playable involves post-processing steps that refine the raw output from the model.

5. Map Formatting and Integration

5.1 Converting to Beat Saber Format

Transforming the model's output into Beat Saber's JSON map format is necessary for integration. This involves mapping the generated sequences into the specific structure required by the game.

5.2 Customization and Refinement Tools

Providing tools for users to customize and refine the generated maps enhances playability and ensures the maps meet community standards.

6. Evaluation and Refinement

6.1 Defining Evaluation Metrics

Metrics such as playability, synchronization accuracy, and map complexity are used to evaluate the quality of generated maps.

6.2 Iterative Improvement

Based on evaluation results, iteratively adjusting the model architecture, training process, and data preparation techniques is essential for continual improvement.

7. Example Implementation

7.1 Environment Setup

# Install required libraries
pip install librosa torch transformers numpy

7.2 Data Preprocessing

import librosa
import numpy as np
from sklearn.preprocessing import MinMaxScaler

def extract_audio_features(audio_path, sr=22050):
    """Extract key audio features for the model."""
    y, sr = librosa.load(audio_path, sr=sr)
    tempo, _ = librosa.beat.beat_track(y, sr=sr)
    mfccs = librosa.feature.mfcc(y, sr=sr, n_mfcc=13)
    chroma = librosa.feature.chroma_stft(y, sr=sr)
    return {
        "tempo": tempo,
        "mfccs": mfccs.T,    # Transpose for sequence
        "chroma": chroma.T   # Transpose for sequence
    }

def process_features_for_model(features):
    """Normalize features and prepare for input."""
    scaler = MinMaxScaler()
    feature_array = np.hstack([features["mfccs"], features["chroma"]])
    scaled_features = scaler.fit_transform(feature_array)
    return scaled_features

# Example pairing of features with labeled map data
input_data = process_features_for_model(extract_audio_features("song.mp3"))
label_data = np.load("beatmap_labels.npy")  # Load preprocessed labels

7.3 Model Definition

import torch
from torch import nn
from transformers import BertConfig, BertModel

class BeatMapGenerator(nn.Module):
    def __init__(self, input_dim, hidden_dim, output_dim):
        super(BeatMapGenerator, self).__init__()
        config = BertConfig(
            hidden_size=hidden_dim,
            num_attention_heads=4,
            intermediate_size=hidden_dim * 2,
            num_hidden_layers=4
        )
        self.transformer = BertModel(config)
        self.input_projection = nn.Linear(input_dim, hidden_dim)
        self.output_projection = nn.Linear(hidden_dim, output_dim)

    def forward(self, x):
        x = self.input_projection(x)
        transformer_output = self.transformer(inputs_embeds=x).last_hidden_state
        output = self.output_projection(transformer_output)
        return output

7.4 Training the Model

import torch
from torch.utils.data import DataLoader, Dataset
from torch.optim import Adam

class BeatMapDataset(Dataset):
    def __init__(self, inputs, labels):
        self.inputs = inputs
        self.labels = labels

    def __len__(self):
        return len(self.inputs)

    def __getitem__(self, idx):
        return torch.tensor(self.inputs[idx], dtype=torch.float32), torch.tensor(self.labels[idx], dtype=torch.long)

def train_model(model, dataloader, optimizer, criterion, epochs=10):
    model.train()
    for epoch in range(epochs):
        for inputs, labels in dataloader:
            optimizer.zero_grad()
            outputs = model(inputs)
            loss = criterion(outputs.view(-1, outputs.shape[-1]), labels.view(-1))
            loss.backward()
            optimizer.step()
        print(f"Epoch {epoch + 1}/{epochs}, Loss: {loss.item()}")

# Load data and create DataLoader
dataset = BeatMapDataset(input_data, label_data)
dataloader = DataLoader(dataset, batch_size=16, shuffle=True)

model = BeatMapGenerator(input_dim=26, hidden_dim=128, output_dim=10)  # Example dimensions
optimizer = Adam(model.parameters(), lr=1e-4)
criterion = nn.CrossEntropyLoss()

train_model(model, dataloader, optimizer, criterion)

7.5 Generating Beat Saber Maps

import json
import torch

def generate_map(model, input_features):
    model.eval()
    with torch.no_grad():
        output = model(torch.tensor(input_features, dtype=torch.float32))
        predictions = torch.argmax(output, dim=-1).numpy()
    return predictions

def save_beat_saber_map(predictions, output_path="map.json"):
    # Simple conversion (extend with block positions and lights)
    beat_map = [{"time": i, "type": int(p)} for i, p in enumerate(predictions)]
    with open(output_path, "w") as f:
        json.dump(beat_map, f)

map_predictions = generate_map(model, input_data)
save_beat_saber_map(map_predictions)

7.6 Complete Integration Example

import torch
import torch.nn as nn
from transformers import Wav2Vec2ForSequenceClassification, Wav2Vec2Processor
import librosa
import numpy as np
import json

class BeatBlockPredictor(nn.Module):
    def __init__(self):
        super().__init__()
        self.wav2vec = Wav2Vec2ForSequenceClassification.from_pretrained(
            "facebook/wav2vec2-base", 
            num_labels=2  # Binary classification for block/no block
        )
        self.dense = nn.Sequential(
            nn.Linear(768, 256),
            nn.ReLU(),
            nn.Dropout(0.1),
            nn.Linear(256, 1),
            nn.Sigmoid()
        )

    def forward(self, audio_features):
        wav2vec_output = self.wav2vec(audio_features).logits
        return self.dense(wav2vec_output)

class BlockPatternGenerator(nn.Module):
    def __init__(self):
        super().__init__()
        self.transformer = nn.TransformerEncoder(
            nn.TransformerEncoderLayer(
                d_model=256,
                nhead=8,
                dim_feedforward=1024
            ),
            num_layers=6
        )
        self.position_embedding = nn.Embedding(512, 256)
        self.output_layer = nn.Linear(256, 12)  # 6 positions * 2 directions

    def forward(self, beat_features):
        positions = torch.arange(beat_features.size(1)).to(beat_features.device)
        pos_embed = self.position_embedding(positions)
        x = beat_features + pos_embed
        transformer_out = self.transformer(x)
        return self.output_layer(transformer_out)

class BeatSaberMapGenerator:
    def __init__(self):
        self.processor = Wav2Vec2Processor.from_pretrained("facebook/wav2vec2-base")
        self.beat_predictor = BeatBlockPredictor()
        self.pattern_generator = BlockPatternGenerator()

    def preprocess_audio(self, audio_path):
        """Load and preprocess audio file"""
        audio, sr = librosa.load(audio_path, sr=16000)
        inputs = self.processor(
            audio, 
            sampling_rate=16000, 
            return_tensors="pt"
        )
        return inputs.input_values

    def generate_map(self, audio_path, difficulty="normal"):
        """Generate a complete Beat Saber map"""
        # Process audio
        audio_features = self.preprocess_audio(audio_path)

        # Predict beat blocks
        beat_probabilities = self.beat_predictor(audio_features)
        beat_mask = (beat_probabilities > 0.5).float()

        # Generate block patterns
        block_patterns = self.pattern_generator(beat_mask)

        # Convert to Beat Saber map format
        map_data = self._convert_to_map_format(
            beat_probabilities, 
            block_patterns,
            difficulty
        )

        return map_data

    def _convert_to_map_format(self, beats, patterns, difficulty):
        """Convert neural network outputs to Beat Saber map format"""
        map_data = {
            "_version": "2.0.0",
            "_notes": [],
            "_events": [],
            "_obstacles": []
        }

        timing_scale = 0.5  # Seconds per beat
        difficulty_speeds = {
            "easy": 1.0,
            "normal": 1.5,
            "hard": 2.0,
            "expert": 2.5
        }

        speed_multiplier = difficulty_speeds.get(difficulty, 1.5)

        # Convert predictions to note placements
        for time_idx, (beat_prob, pattern) in enumerate(zip(beats, patterns)):
            if beat_prob > 0.5:
                pattern_indices = torch.argmax(pattern.reshape(-1, 2), dim=1)
                for block_idx, direction in enumerate(pattern_indices):
                    note = {
                        "_time": time_idx * timing_scale * speed_multiplier,
                        "_lineIndex": block_idx % 4,
                        "_lineLayer": block_idx // 4,
                        "_type": 0,  # 0 for red, 1 for blue
                        "_cutDirection": int(direction)
                    }
                    map_data["_notes"].append(note)

        return map_data

def save_map(map_data, output_path):
    """Save the generated map to a file"""
    with open(output_path, 'w') as f:
        json.dump(map_data, f, indent=2)

# Usage example
if __name__ == "__main__":
    generator = BeatSaberMapGenerator()
    map_data = generator.generate_map("song.mp3", difficulty="expert")
    save_map(map_data, "output_map.dat")

Conclusion

Developing a Beat Saber map generator AI using transformers is a multifaceted process that integrates audio analysis, data preprocessing, transformer-based modeling, and map formatting. By following a structured approach and leveraging state-of-the-art machine learning techniques, it is possible to create sophisticated and synchronized Beat Saber maps that enhance the gaming experience.

References


Last updated January 28, 2025

AI-assisted research. Sources, methodology and corrections · Source-checked guides and tools · Explore published answers

Ask Ithy AI
Download Article
Delete Article