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

use error::NbsError;
use header::Header;
use io::{ReadStringExt, WriteStringExt};
use noteblocks::{instrument::CustomInstruments, NoteBlocks};
use std::time::Duration;

pub mod error;
pub mod header;
pub mod io;
pub mod noteblocks;

#[derive(PartialEq, Debug, Clone, Copy)]
pub enum NbsFormat {
    NoteBlockStudio,
    OpenNoteBlockStudio(i8),
}
impl NbsFormat {
    pub fn is_new(&self) -> bool {
        match self {
            NbsFormat::NoteBlockStudio => false,
            NbsFormat::OpenNoteBlockStudio(_) => true,
        }
    }
    pub fn version(&self) -> i8 {
        match self {
            NbsFormat::NoteBlockStudio => 0,
            &NbsFormat::OpenNoteBlockStudio(v) => v,
        }
    }
}

pub struct Nbs {
    pub header: Header,
    pub noteblocks: NoteBlocks,
    pub custom_instruments: CustomInstruments,
}

impl Nbs {
    pub fn from_componets(
        header: Header,
        noteblocks: NoteBlocks,
        custom_instruments: CustomInstruments,
    ) -> Self {
        Nbs {
            header,
            noteblocks,
            custom_instruments,
        }
    }

    /// Decode a NBS buffer.
    pub fn decode<R>(mut reader: &mut R) -> Result<Nbs, NbsError>
    where
        R: ReadStringExt,
    {
        let header = Header::decode(&mut reader)?;
        let noteblocks = NoteBlocks::decode(&mut reader, &header)?;
        let custom_instruments = CustomInstruments::decode(&mut reader, &header)?;
        Ok(Nbs {
            header,
            noteblocks,
            custom_instruments,
        })
    }

    /// This method updates some parts of the Header to match the rest of the file
    pub fn update(&mut self) {
        if self.format().version() >= 3 {
            self.header.song_length = Some(self.noteblocks.calculate_length());
        } else if self.format().version() == 0 {
            self.header.old_song_length = self.noteblocks.calculate_length();
        }
        if self.format().version() > 0 {
            self.header.version_number = Some(self.format().version());
        }
        self.header.layer_count = self.noteblocks.layers.len() as i16;
    }

    /// Enocde a NBS buffer,
    pub fn encode<W>(&self, mut writer: &mut W) -> Result<(), NbsError>
    where
        W: WriteStringExt,
    {
        self.header.encode(self.format(), &mut writer)?;
        self.noteblocks.encode(self.format(), &mut writer)?;
        self.custom_instruments.encode(&mut writer)?;
        Ok(())
    }

    /// Returns the NBS format for this
    pub fn format(&self) -> NbsFormat {
        return self.header.format;
    }

    /// Returns the song ticks.
    pub fn song_ticks(&self) -> i16 {
        self.noteblocks.calculate_length()
    }

    /// Returns the song duration.
    pub fn song_length(&self) -> Duration {
        Duration::from_secs_f32(self.song_ticks() as f32 / (self.header.song_tempo as f32 / 100.0))
    }
}