rosu_map/lib.rs
1//! Library to de- and encode `.osu` files from [osu!].
2//!
3//! ## What
4//!
5//! At it's core, `rosu-map` provides the [`DecodeBeatmap`] trait. The trait is responsible for
6//! decoding the file itself, error handling, and section parsing. All that's left to do for
7//! implementators of the trait is to keep a state of parsed data and, given a section and a line
8//! of text, to modify that state.
9//!
10//! `rosu-map` also provides multiple types that already implement this trait, namely one for each
11//! section (see [`Editor`], [`TimingPoints`], ...) and one for the (almost) full content, [`Beatmap`].
12//!
13//! ## Why
14//!
15//! Exposing functionality through the trait allows for flexibility when deciding which content to
16//! parse and thus make it more efficient when not all data is needed.
17//!
18//! If only the difficulty attributes are required, parsing via the [`Difficulty`] struct will discard
19//! everything except for the few lines within the `[Difficulty]` section of the `.osu` file.
20//! Similarly, if only the artist, title, and version is of interest, the [`Metadata`] struct can be
21//! used.
22//!
23//! Additionally, it's worth noting that [`Beatmap`] parses (almost) *everything* which might be
24//! overkill for many use-cases. The work-around would be to define a new custom type, copy-paste
25//! [`Beatmap`]'s [`DecodeBeatmap`] implementation, and then throw out everything that's not needed.
26//!
27//! ## How
28//!
29//! The simplest way to make use of a type's [`DecodeBeatmap`] implementation is by using
30//! `rosu-map`s functions [`from_bytes`], [`from_path`], and [`from_str`].
31//!
32//! ```
33//! # use rosu_map::Beatmap;
34//! use rosu_map::section::difficulty::Difficulty;
35//!
36//! let content = "[Difficulty]
37//! ApproachRate: 9.2
38//! SliderMultiplier: 1.9
39//!
40//! [Metadata]
41//! Creator: peppy";
42//!
43//! let difficulty = rosu_map::from_str::<Difficulty>(content).unwrap();
44//! assert_eq!(difficulty.approach_rate, 9.2);
45//!
46//! let path = "./resources/Soleily - Renatus (Gamu) [Insane].osu";
47//! let map = rosu_map::from_path::<Beatmap>(path).unwrap();
48//! assert_eq!(map.audio_file, "03. Renatus - Soleily 192kbps.mp3");
49//! ```
50//!
51//! For information on implementing the [`DecodeBeatmap`] trait on a new type, check out the
52//! trait's documentation. For examples, check how types like [`General`] or [`HitObjects`]
53//! implement the trait.
54//!
55//! ## Encoding
56//!
57//! The [`Beatmap`] struct provides a built-in way to turn itself into the content of a `.osu` file
58//! through its `encode*` methods.
59//!
60//! ```no_run
61//! # use rosu_map::Beatmap;
62//! let path = "./resources/Within Temptation - The Unforgiving (Armin) [Marathon].osu";
63//! let mut map: Beatmap = rosu_map::from_path(path).unwrap();
64//!
65//! map.approach_rate = 10.0;
66//!
67//! map.encode_to_path("./new_file.osu").unwrap();
68//!
69//! let metadata = rosu_map::section::metadata::Metadata {
70//! title: "song title".to_string(),
71//! artist: "artist name".to_string(),
72//! ..Default::default()
73//! };
74//!
75//! let content = Beatmap::from(metadata).encode_to_string().unwrap();
76//! assert!(content.contains("Title: song title"));
77//! ```
78//!
79//! ## Features
80//!
81//! | Flag | Description | Dependencies
82//! | - | - | -
83//! | `default` | No features |
84//! | `tracing` | Any error encountered during decoding will be logged through `tracing::error`. If this features is not enabled, errors will be ignored. | [`tracing`]
85//!
86//! ## Misc
87//!
88//! #### Internals
89//!
90//! A sizable section of `rosu-map` is a port of [osu!lazer]'s beatmap
91//! {de/en}coding. Not only does its functionality mirror osu!, but many test cases were
92//! translated too, providing a solid degree of correctness even on fringe edge cases.
93//!
94//! Lazer commit on last port: `8bd65d9938a10fc42e6409501b0282f0fa4a25ef`
95//!
96//! #### Async
97//!
98//! After some testing and benchmarking, it turns out that async IO does not provide any improvements
99//! or performance gains even in a concurrent context. In fact, regular sequential IO consistently
100//! outperformed its async counterpart. As such `rosu-map` does not provide an async interface.
101//!
102//! #### Storyboard
103//!
104//! `rosu-map` does not provide types that parse storyboards, but the crate [`rosu-storyboard`] does.
105//!
106//! [osu!]: https://osu.ppy.sh/
107//! [osu!lazer]: https://github.com/ppy/osu
108//! [`DecodeBeatmap`]: crate::decode::DecodeBeatmap
109//! [`Beatmap`]: crate::beatmap::Beatmap
110//! [`from_bytes`]: crate::decode::from_bytes
111//! [`from_str`]: crate::decode::from_str
112//! [`from_path`]: crate::decode::from_path
113//! [`General`]: crate::section::general::decode::General
114//! [`Editor`]: crate::section::editor::Editor
115//! [`Metadata`]: crate::section::metadata::Metadata
116//! [`Difficulty`]: crate::section::difficulty::Difficulty
117//! [`TimingPoints`]: crate::section::timing_points::decode::TimingPoints
118//! [`HitObjects`]: crate::section::hit_objects::decode::HitObjects
119//! [`tracing`]: https://docs.rs/tracing
120//! [`rosu-storyboard`]: https://github.com/MaxOhn/rosu-storyboard/
121
122#![deny(rustdoc::broken_intra_doc_links, rustdoc::missing_crate_level_docs)]
123#![warn(clippy::missing_const_for_fn, clippy::pedantic)]
124#![allow(
125 clippy::missing_errors_doc,
126 clippy::module_name_repetitions,
127 clippy::must_use_candidate,
128 clippy::struct_excessive_bools,
129 clippy::match_same_arms,
130 clippy::cast_possible_truncation,
131 clippy::cast_precision_loss,
132 clippy::cast_sign_loss,
133 clippy::explicit_iter_loop
134)]
135
136#[macro_use]
137mod macros;
138
139mod beatmap;
140mod decode;
141mod encode;
142mod format_version;
143mod reader;
144
145/// Section-specific types.
146pub mod section;
147
148/// Various utility types for usage in and around this library.
149pub mod util;
150
151pub use crate::{
152 beatmap::{Beatmap, BeatmapState, ParseBeatmapError},
153 decode::{from_bytes, from_path, from_str, DecodeBeatmap, DecodeState},
154 format_version::LATEST_FORMAT_VERSION,
155};