Skip to main content

nbs/
lib.rs

1//! This crate provides functionality for decoding & encoding (Open)NoteBlockStudio buffers.
2//!
3//! It supports the original NBS format, aswell as version 1-4 of the unofficial new format introduced in [OpenNoteBlockStudio](https://github.com/HielkeMinecraft/OpenNoteBlockStudio).
4//! Documentation on the NBS format can be found at [NoteBlockStudio](https://www.stuffbydavid.com/mcnbs/format) and [OpenNoteBlockStudio](https://hielkeminecraft.github.io/OpenNoteBlockStudio/nbs).
5//!
6//! ## Example: Editing a NBS file
7//!
8//! ```rust
9//! use nbs::{
10//!     noteblocks::{instrument, note::Note},
11//!     Nbs,
12//! };
13//! use std::fs::File;
14//!
15//! fn main() {
16//!     let mut file = File::open("tests/1.nbs").unwrap();
17//!     let mut nbs = Nbs::decode(&mut file).unwrap();
18//!     nbs.noteblocks.layers[2].name = String::from("Cows"); // Renaming the 3rd layer "Cows".
19//!     nbs.noteblocks.layers[2].volume = 25; // Setting its volume to 25%.
20//!     // Insert a Note in the 3rd layer at tick 0
21//!     nbs.noteblocks.layers[2].notes.insert(
22//!         0,
23//!         Note::new(instrument::COW_BELL, 33, Some(100), Some(100), Some(0)),
24//!     );
25//!     // Write the changes to `out1.nbs`.
26//!     nbs.encode(&mut File::create("out1.nbs").unwrap()).unwrap();
27//! }
28//! ```
29//! ## Example: Creating a NBS file
30//! ```rust
31//! use nbs::{
32//!     header::Header,
33//!     noteblocks::{instrument, instrument::CustomInstruments, layer::Layer, note::Note, NoteBlocks},
34//!     Nbs, NbsFormat,
35//! };
36//! use std::fs::File;
37//!
38//! fn main() {
39//!     let mut file = File::create("out2.nbs").unwrap();
40//!     let mut header = Header::new(NbsFormat::OpenNoteBlockStudio(4)); // Create a header.
41//!     header.song_name = String::from("test"); // Change the name to `test`.
42//!     let mut noteblocks = NoteBlocks::new();
43//!     // Create a new Layer.
44//!     noteblocks
45//!         .layers
46//!         .push(Layer::from_format(NbsFormat::OpenNoteBlockStudio(4)));
47//!     // Insert 20 notes into the first layer
48//!     for i in 0..20 {
49//!         noteblocks.layers[0].notes.insert(
50//!             i,
51//!             Note::new(
52//!                 instrument::PIANO,
53//!                 (33 + i) as i8,
54//!                 Some(100),
55//!                 Some(100),
56//!                 Some(0),
57//!             ),
58//!         );
59//!     }
60//!     let custom_instruments = CustomInstruments::new(); // Create a empty list of custom instruments.
61//!     let mut nbs = Nbs::from_componets(header, noteblocks, custom_instruments); // Assamble everything together.
62//!     nbs.update(); // Update certian fields in the header to match the rest of the file.
63//!     nbs.encode(&mut file); // save!
64//! }
65//! ```
66
67use error::NbsError;
68use header::Header;
69use io::{ReadStringExt, WriteStringExt};
70use noteblocks::{instrument::CustomInstruments, NoteBlocks};
71use std::time::Duration;
72
73pub mod error;
74pub mod header;
75pub mod io;
76pub mod noteblocks;
77
78#[derive(PartialEq, Debug, Clone, Copy)]
79pub enum NbsFormat {
80    NoteBlockStudio,
81    OpenNoteBlockStudio(i8),
82}
83impl NbsFormat {
84    pub fn is_new(&self) -> bool {
85        match self {
86            NbsFormat::NoteBlockStudio => false,
87            NbsFormat::OpenNoteBlockStudio(_) => true,
88        }
89    }
90    pub fn version(&self) -> i8 {
91        match self {
92            NbsFormat::NoteBlockStudio => 0,
93            &NbsFormat::OpenNoteBlockStudio(v) => v,
94        }
95    }
96}
97
98pub struct Nbs {
99    pub header: Header,
100    pub noteblocks: NoteBlocks,
101    pub custom_instruments: CustomInstruments,
102}
103
104impl Nbs {
105    pub fn from_componets(
106        header: Header,
107        noteblocks: NoteBlocks,
108        custom_instruments: CustomInstruments,
109    ) -> Self {
110        Nbs {
111            header,
112            noteblocks,
113            custom_instruments,
114        }
115    }
116
117    /// Decode a NBS buffer.
118    pub fn decode<R>(mut reader: &mut R) -> Result<Nbs, NbsError>
119    where
120        R: ReadStringExt,
121    {
122        let header = Header::decode(&mut reader)?;
123        let noteblocks = NoteBlocks::decode(&mut reader, &header)?;
124        let custom_instruments = CustomInstruments::decode(&mut reader, &header)?;
125        Ok(Nbs {
126            header,
127            noteblocks,
128            custom_instruments,
129        })
130    }
131
132    /// This method updates some parts of the Header to match the rest of the file
133    pub fn update(&mut self) {
134        if self.format().version() >= 3 {
135            self.header.song_length = Some(self.noteblocks.calculate_length());
136        } else if self.format().version() == 0 {
137            self.header.old_song_length = self.noteblocks.calculate_length();
138        }
139        if self.format().version() > 0 {
140            self.header.version_number = Some(self.format().version());
141        }
142        self.header.layer_count = self.noteblocks.layers.len() as i16;
143    }
144
145    /// Enocde a NBS buffer,
146    pub fn encode<W>(&self, mut writer: &mut W) -> Result<(), NbsError>
147    where
148        W: WriteStringExt,
149    {
150        self.header.encode(self.format(), &mut writer)?;
151        self.noteblocks.encode(self.format(), &mut writer)?;
152        self.custom_instruments.encode(&mut writer)?;
153        Ok(())
154    }
155
156    /// Returns the NBS format for this
157    pub fn format(&self) -> NbsFormat {
158        return self.header.format;
159    }
160
161    /// Returns the song ticks.
162    pub fn song_ticks(&self) -> i16 {
163        self.noteblocks.calculate_length()
164    }
165
166    /// Returns the song duration.
167    pub fn song_length(&self) -> Duration {
168        Duration::from_secs_f32(self.song_ticks() as f32 / (self.header.song_tempo as f32 / 100.0))
169    }
170}