Skip to main content
Beatmaps are the core content of osu!, containing the gameplay patterns, metadata, and difficulty settings for each playable map. The beatmap system handles everything from importing and storing beatmaps to managing difficulty calculations and online status.

Beatmap Structure

Each beatmap consists of several key components that define its identity and gameplay characteristics.

BeatmapInfo Model

The BeatmapInfo class represents a single difficulty within a beatmap set. Key properties include:

Core Properties

  • ID: Unique GUID identifier
  • DifficultyName: The name of this specific difficulty (e.g., “Hard”, “Expert”)
  • Ruleset: The game mode (osu!, osu!taiko, osu!catch, osu!mania)
  • Difficulty: Difficulty settings object
  • Metadata: Song title, artist, mapper information
  • Hash: MD5 hash for version tracking
public class BeatmapInfo : RealmObject
{
    public Guid ID { get; set; }
    public string DifficultyName { get; set; }
    public RulesetInfo Ruleset { get; set; }
    public BeatmapDifficulty Difficulty { get; set; }
    public BeatmapMetadata Metadata { get; set; }
    public double StarRating { get; set; } = -1;
    public double Length { get; set; }
    public double BPM { get; set; }
}

Difficulty Settings

Each beatmap has configurable difficulty parameters that affect gameplay:
Controls how quickly health depletes and how much health is recovered from hits. Default value is 5.
public float DrainRate { get; set; } = 5;
Determines the size of hit objects. Higher values mean smaller circles. Default value is 5.
public float CircleSize { get; set; } = 5;
Affects the timing window for hitting objects accurately. Default value is 5.
public float OverallDifficulty { get; set; } = 5;
Controls how long hit objects are visible before they need to be hit. Default value is 5.
public float ApproachRate { get; set; } = 5;
  • SliderMultiplier: Base slider velocity (default 1.4)
  • SliderTickRate: Frequency of slider ticks (default 1)

Beatmap Status

Beatmaps can have different online statuses that affect their visibility and whether they grant performance points:
1

LocallyModified

Beatmap has been edited locally via the editor. Online status changes are ignored.
2

Pending

Beatmap is uploaded but not yet ranked.
3

Qualified

Beatmap is in the qualification process for ranking.
4

Ranked

Official ranked status - grants performance points (PP).
5

Approved

Approved status - also grants performance points.
6

Loved

Community-loved beatmap - does not grant PP but appears in rankings.
Only Ranked and Approved beatmaps grant performance points:
public static bool GrantsPerformancePoints(this BeatmapOnlineStatus status)
    => status == BeatmapOnlineStatus.Ranked || status == BeatmapOnlineStatus.Approved;

Beatmap Management

The BeatmapManager class handles all operations related to beatmap storage and retrieval.

Creating New Beatmaps

Creating a New Beatmap Set

public WorkingBeatmap CreateNew(RulesetInfo ruleset, APIUser user)
{
    var metadata = new BeatmapMetadata
    {
        Author = new RealmUser
        {
            OnlineID = user.OnlineID,
            Username = user.Username,
        }
    };

    var beatmapSet = new BeatmapSetInfo
    {
        DateAdded = DateTimeOffset.UtcNow,
        Beatmaps = { new BeatmapInfo(ruleset, new BeatmapDifficulty(), metadata) }
    };
    
    return GetWorkingBeatmap(imported.Beatmaps.First());
}

Adding Difficulties

You can add new difficulties to existing beatmap sets in two ways:
Creates a fresh difficulty with default settings but preserves metadata and timing points from a reference beatmap.
public virtual WorkingBeatmap CreateNewDifficulty(
    BeatmapSetInfo targetBeatmapSet,
    WorkingBeatmap referenceWorkingBeatmap,
    RulesetInfo rulesetInfo)
This method is useful when starting a new difficulty from scratch while keeping the song’s timing.
Creates a nearly-exact copy of an existing difficulty, including all hit objects and settings.
public virtual WorkingBeatmap CopyExistingDifficulty(
    BeatmapSetInfo targetBeatmapSet,
    WorkingBeatmap referenceWorkingBeatmap)
The copy gets a new ID and “(copy)” suffix added to its difficulty name.

Hiding and Restoring Difficulties

Difficulty Visibility Management

Difficulties can be hidden from song select while keeping them in the database:
// Hide a difficulty (requires at least one other visible difficulty)
public bool Hide(BeatmapInfo beatmapInfo)

// Check if a difficulty can be hidden
public bool CanHide(BeatmapInfo beatmapInfo)
    => beatmapInfo.BeatmapSet!.Beatmaps.Count(b => !b.Hidden) > 1;

// Restore a hidden difficulty
public void Restore(BeatmapInfo beatmapInfo)

// Restore all hidden difficulties
public void RestoreAll()
You cannot hide all difficulties in a beatmap set. At least one difficulty must remain visible.

Importing and Exporting

The beatmap system supports importing beatmaps from various sources and exporting them for sharing.

Import Operations

1

Import from File

Import beatmaps from .osz or .osu files:
public Task Import(params string[] paths)
2

Import as Update

Update an existing beatmap with a new version:
public Task<Live<BeatmapSetInfo>?> ImportAsUpdate(
    ProgressNotification notification,
    ImportTask importTask,
    BeatmapSetInfo original)
3

External Editing

Begin editing a beatmap with an external editor:
public Task<ExternalEditOperation<BeatmapSetInfo>> BeginExternalEditing(
    BeatmapSetInfo model)

Export Operations

Export Formats

  • Standard Export: Modern .osz format
    public Task Export(BeatmapSetInfo beatmapSet)
    
  • Legacy Export: Legacy format for compatibility
    public Task ExportLegacy(BeatmapSetInfo beatmapSet)
    public Task ExportLegacy(BeatmapInfo beatmap)
    

Score Association

Beatmaps maintain relationships with scores through hash matching:
public void UpdateLocalScores(Realm realm)
{
    // Disassociate scores that no longer match
    foreach (var score in Scores)
        score.BeatmapInfo = null;

    // Associate scores matching the current hash
    foreach (var score in realm.All<ScoreInfo>().Where(s => s.BeatmapHash == Hash))
        score.BeatmapInfo = this;
}
Scores are matched to beatmaps via MD5 hash rather than ID, allowing scores to persist even when beatmaps are updated or reimported.

Collection Management

When beatmaps are updated, their hashes change. The system automatically transfers collection references:
public void TransferCollectionReferences(Realm realm, string previousMD5Hash)
{
    var collections = realm.All<BeatmapCollection>()
        .Where(c => c.BeatmapMD5Hashes.Contains(previousMD5Hash));

    foreach (var c in collections)
    {
        c.BeatmapMD5Hashes.Remove(previousMD5Hash);
        c.BeatmapMD5Hashes.Add(MD5Hash);
    }
}

Working Beatmaps

The WorkingBeatmap represents a beatmap ready for gameplay, with loaded audio and textures:

Working Beatmap Cache

public WorkingBeatmap GetWorkingBeatmap(BeatmapInfo? beatmapInfo, bool refetch = false)
{
    if (beatmapInfo != null && refetch)
        workingBeatmapCache.Invalidate(beatmapInfo);
    
    return workingBeatmapCache.GetWorkingBeatmap(beatmapInfo);
}
The cache ensures that frequently-accessed beatmaps are kept in memory for better performance.

Video Management

Beatmap sets may include background videos. The system provides tools to manage storage:
// Delete all videos from all beatmap sets
public void DeleteAllVideos()

// Delete videos from specific beatmap sets
public void DeleteVideos(List<BeatmapSetInfo> items, bool silent = false)
Deleting videos can significantly reduce storage usage while keeping the beatmaps playable.

Build docs developers (and LLMs) love