Skip to content

Commit 6d8120b

Browse files
authored
Merge pull request #24 from RaphiMC/rewrite/3.0.0
Rewrite/3.0.0
2 parents 12ed0ca + 8422a69 commit 6d8120b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

74 files changed

+3807
-3655
lines changed

README.md

Lines changed: 64 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,17 @@ To use NoteBlockLib in your application, check out the [Usage](#usage) section.
55
For a reference implementation of NoteBlockLib, check out [NoteBlockTool](https://github.com/RaphiMC/NoteBlockTool).
66

77
## Features
8-
- Reads .nbs, .mcsp2, .mid, .txt and .notebot files
9-
- Can convert all of the above to .nbs
8+
- Supports reading .nbs, .mid, .txt, .mcsp, .mcsp2 and .notebot files
9+
- Supports writing .nbs, .txt and .mcsp2 files
10+
- Can convert all formats to .nbs
1011
- Offers an easy way to play note block songs in your application
1112
- Good MIDI importer
1213
- Supports most MIDI files
1314
- Supports velocity and panning
1415
- Can handle Black MIDI files
1516
- Supports all NBS versions
1617
- Version 0 - 5
17-
- Supports undocumented features like Tempo Changers
18+
- Supports Tempo Changers
1819
- Many tools for manipulating songs
1920
- Optimize songs for use in Minecraft (Transposing, Resampling)
2021
- Resampling songs with a different TPS
@@ -34,29 +35,14 @@ If you just want the latest jar file you can download it from [GitHub Actions](h
3435
## Usage
3536
### Concepts and terminology
3637
The main class of NoteBlockLib is the ``NoteBlockLib`` class. It contains all the methods for reading, writing and creating songs.
37-
The utils for manipulating songs are located in the ``util`` package.
38+
Some utils for manipulating and getting metrics about songs are located in the ``util`` package.
3839

39-
#### Song
40-
Song is a wrapper class around the Header, Data and the View of a song.
41-
The Header and Data classes are the low level representation of a song. They are used by I/O operations.
42-
The View class is a high level representation of a song and is generated from the Header and Data classes.
40+
The ``Song`` class is the main data structure for parsed songs. It contains generalized and format specific data.
41+
The generalized data only includes the most important data of songs like the format, the title, the description, the author, the notes and the tempo.
42+
To access format specific data you have to cast the song instance to a specific format (``NbsSong`` for example).
43+
Most of the time you will only need the generalized data and all the methods for manipulating, playing and converting songs will work with the generalized data.
4344

44-
#### Header
45-
The header usually contains the metadata of a song. This includes the author, the original author, the description, the tempo, the delay and the length of the song.
46-
47-
#### Data
48-
The data contains all the notes of a song. This includes the tick at which the note should be played, the instrument and the key.
49-
50-
#### SongView
51-
The SongView is a high level and generalized representation of a song. It contains only the most important information of a song.
52-
The view is used for most operations like playing a song or manipulating it. Due to the fact that the view is a high level representation of a song, it is not suitable for I/O operations directly.
53-
To create a low level representation (Song) from the view again you can use the ``NoteBlockLib.createSong(view, format)`` method.
54-
The returned song only has the bare minimum of data required to be written to a file. You can use the setter methods of the Header and Data class to add more data to the song.
55-
The view is generated by default only once when the Song class is created. If you want to refresh the view you can use the ``Song.refreshView()`` method.
56-
57-
#### Note
58-
The Note class is a wrapper class around the instrument and key of a note. Each format has its own Note class which can have additional data like volume or panning.
59-
One way of accessing that data is through the use of the ``NoteWithVolume`` and ``NoteWithPanning`` classes.
45+
All data structures in NoteBlockLib are mutable and can be modified at any time. All data structures also have a ``copy`` method to create a deep copy of the data structure.
6046

6147
### Reading a song
6248
To read a song you can use the ``NoteBlockLib.readSong(<input>, [format])`` method.
@@ -67,13 +53,13 @@ The format is optional and can be used to specify the format of the input. If th
6753
To write a song you can use the ``NoteBlockLib.writeSong(<song>, <output>)`` method.
6854

6955
### Creating a song
70-
The easiest way to create a song is to create a SongView and then use the ``NoteBlockLib.createSongFromView(<view>, [format])`` method to create a Song from it.
71-
Alternatively you can create a Song directly by using the ``new Song(null, <header>, <data>)`` constructor. This requires you to create the Header and Data yourself.
56+
The easiest way to create a song is to create a ``new GenericSong()``, fill in the data and then use the ``NoteBlockLib.convertSong(<song>, <format>)`` method to create a format specific song from it.
57+
Alternatively you can create a format specific Song directly by using for example the ``new NbsSong()`` constructor. This requires you to fill in all the format specific data yourself.
7258

7359
### Playing a song
7460
To play a song you can use the ``SongPlayer`` class. The SongPlayer provides basic controls like play, pause, stop and seek.
75-
To instantiate it you can use the ``new SongPlayer(<songView>, <callback>)`` constructor.
76-
The callback contains basic methods like ``onFinished`` and ``playNote`` to handle the playback of the song.
61+
To create a SongPlayer implementation, you have to create a class which extends the ``SongPlayer`` class.
62+
The SongPlayer class requires you to implement the ``playNotes`` method, but also offers several optional methods like ``onFinished``.
7763

7864
### Manipulating a song
7965
There are multiple utils for manipulating a song.
@@ -84,104 +70,107 @@ This is very useful if you want to export the song as a schematic and play it in
8470

8571
#### MinecraftDefinitions
8672
The MinecraftDefinitions class contains definitions and formulas for Minecraft related manipulations.
87-
This includes multiple methods for getting notes within the Minecraft octave range, converting between Minecraft and NBS id systems and more.
73+
This for example includes multiple methods for getting notes within the Minecraft octave range, such as transposing them.
8874

8975
#### SongUtil
90-
This class has some general utils for manipulating songs like applying a modification to all notes of a song.
76+
This class has some general utils for getting various metrics about a song.
9177

9278
## Examples
9379
**Reading a song, transposing its notes and writing it back**
9480
```java
95-
Song<?, ?, ?> song = NoteBlockLib.readSong(new File("input.nbs"));
81+
Song song = NoteBlockLib.readSong(new File("input.nbs"));
9682

9783
// Clamp the note key
98-
// SongUtil.applyToAllNotes(song.getView(), MinecraftDefinitions::clampNoteKey);
84+
// song.getNotes().forEach(MinecraftDefinitions::clampNoteKey);
9985

10086
// Transpose the note key
101-
//SongUtil.applyToAllNotes(song.getView(), MinecraftDefinitions::transposeNoteKey);
87+
// song.getNotes().forEach(MinecraftDefinitions::transposeNoteKey);
10288

10389
// Shift the instrument of out of range notes to a higher/lower one. Sounds better than all above.
104-
SongUtil.applyToAllNotes(song.getView(), MinecraftDefinitions::instrumentShiftNote);
90+
song.getNotes().forEach(MinecraftDefinitions::instrumentShiftNote);
10591
// Clamp the remaining out of range notes
106-
SongUtil.applyToAllNotes(song.getView(), MinecraftDefinitions::clampNoteKey);
107-
108-
NoteBlockLib.writeSong(song, new File("output.nbs"));
92+
song.getNotes().forEach(MinecraftDefinitions::clampNoteKey);
93+
94+
// The operations above work with the generalized song model. If you want to write it back to a specific format, you need to convert it first.
95+
Song convertedSong = NoteBlockLib.convertSong(song, SongFormat.NBS);
96+
97+
NoteBlockLib.writeSong(convertedSong, new File("output.nbs"));
10998
```
11099
**Reading a MIDI, and writing it as NBS**
111100
```java
112-
Song<?, ?, ?> midiSong = NoteBlockLib.readSong(new File("input.mid"));
113-
Song<?, ?, ?> nbsSong = NoteBlockLib.createSongFromView(midiSong.getView(), SongFormat.NBS);
101+
Song midiSong = NoteBlockLib.readSong(new File("input.mid"));
102+
Song nbsSong = NoteBlockLib.convertSong(midiSong, SongFormat.NBS);
114103
NoteBlockLib.writeSong(nbsSong, new File("output.nbs"));
115104
```
116-
**Reading a song, changing its sample rate to 10 TPS and writing it back**
105+
**Reading a song, changing its tempo to 10 TPS and writing it back**
117106
```java
118-
Song<?, ?, ?> song = NoteBlockLib.readSong(new File("input.nbs"));
119-
SongResampler.changeTickSpeed(song.getView(), 10F);
120-
Song<?, ?, ?> newSong = NoteBlockLib.createSongFromView(song.getView(), SongFormat.NBS);
107+
Song song = NoteBlockLib.readSong(new File("input.nbs"));
108+
SongResampler.changeTickSpeed(song, 10F);
109+
// The operations above work with the generalized song model. If you want to write it back to a specific format, you need to convert it first.
110+
Song newSong = NoteBlockLib.convertSong(song, SongFormat.NBS);
121111
NoteBlockLib.writeSong(newSong, new File("output.nbs"));
122112
```
123113
**Creating a new song and saving it as NBS**
124114
```java
125-
// tick -> list of notes
126-
Map<Integer, List<Note>> notes = new TreeMap<>();
127-
// Add the notes to the song
128-
notes.put(0, Lists.newArrayList(new NbsNote(Instrument.HARP, (byte) 46)));
129-
notes.put(5, Lists.newArrayList(new NbsNote(Instrument.BASS, (byte) 60)));
130-
notes.put(8, Lists.newArrayList(new NbsNote(Instrument.BIT, (byte) 84)));
131-
SongView<Note> mySong = new SongView<>("My song" /*title*/, 10F /*ticks per second*/, notes);
132-
Song<?, ?, ?> nbsSong = NoteBlockLib.createSongFromView(mySong, SongFormat.NBS);
133-
NoteBlockLib.writeSong(nbsSong, new File("C:\\Users\\User\\Desktop\\output.nbs"));
115+
Song mySong = new GenericSong();
116+
mySong.setTitle("My song");
117+
mySong.getTempoEvents().set(0, 10F); // set the tempo to 10 ticks per second
118+
mySong.getNotes().add(0, new Note().setInstrument(MinecraftInstrument.HARP).setNbsKey((byte) 46));
119+
mySong.getNotes().add(5, new Note().setInstrument(MinecraftInstrument.BASS).setNbsKey((byte) 60));
120+
mySong.getNotes().add(8, new Note().setInstrument(MinecraftInstrument.BIT).setNbsKey((byte) 84));
121+
Song nbsSong = NoteBlockLib.convertSong(mySong, SongFormat.NBS);
122+
NoteBlockLib.writeSong(nbsSong, new File("output.nbs"));
134123
```
135124
**Playing a song**
136125

137-
Define a callback class
126+
Create the custom SongPlayer implementation:
138127
```java
139-
// Default callback. This callback has a method which receives the already calculated pitch, volume and panning.
140-
// Note: The FullNoteConsumer interface may change over time when new note data is added by one of the formats.
141-
public class MyCallback implements SongPlayerCallback, FullNoteConsumer {
142-
@Override
143-
public void playNote(final Instrument instrument, final float pitch, final float volume, final float panning) {
144-
// This method gets called in real time as the song is played.
145-
System.out.println(instrument + " " + pitch + " " + volume + " " + panning);
128+
public class MySongPlayer extends SongPlayer {
129+
130+
public MySongPlayer(Song song) {
131+
super(song);
146132
}
147-
148-
// There are other methods like playCustomNote, onFinished which can be overridden.
149-
}
150133

151-
// Raw callback. This callback receives the raw Note class. Data like pitch, volume or panning have to be calculated/accessed manually.
152-
public class MyRawCallback implements SongPlayerCallback {
153134
@Override
154-
public void playNote(Note note) {
155-
// This method gets called in real time as the song is played.
156-
// For an example to calculate the various note data see the FullNoteConsumer class.
157-
System.out.println(note.getInstrument() + " " + note.getKey());
135+
protected void playNotes(List<Note> notes) {
136+
for (Note note : notes) {
137+
// This method gets called in real time as the song is played.
138+
// Make sure to check the javadoc of the various methods from the Note class to see how you should use the returned values.
139+
System.out.println(note.getInstrument() + " " + note.getPitch() + " " + note.getVolume() + " " + note.getPanning());
140+
}
158141
}
159-
142+
160143
// There are other methods like onFinished which can be overridden.
161144
}
162145
```
163146

164-
Start playing the song
147+
Start playing the song;
165148
```java
166-
Song<?, ?, ?> song = NoteBlockLib.readSong(new File("input.nbs"));
149+
Song song = NoteBlockLib.readSong(new File("input.nbs"));
167150

168151
// Optionally apply a modification to all notes here (For example to transpose the note keys)
169152

170153
// Create a song player
171-
SongPlayer player = new SongPlayer(song.getView(), new MyCallback());
154+
SongPlayer player = new MySongPlayer(song);
172155

173156
// Start playing
174-
player.play();
157+
player.start();
175158

176159
// Pause
177160
player.setPaused(true);
178161

179162
// Resume
180163
player.setPaused(false);
181164

182-
// Seek
165+
// Seek to a specific tick
183166
player.setTick(50);
184167

168+
// Seek to a specific time
169+
player.setMillisecondPosition(1000);
170+
171+
// Get the current millisecond position
172+
player.getMillisecondPosition();
173+
185174
// Stop
186175
player.stop();
187176
```

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ org.gradle.configureondemand=true
44

55
maven_group=net.raphimc
66
maven_name=NoteBlockLib
7-
maven_version=2.1.5-SNAPSHOT
7+
maven_version=3.0.0-SNAPSHOT

src/main/java/net/raphimc/noteblocklib/NoteBlockLib.java

Lines changed: 54 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,22 @@
1717
*/
1818
package net.raphimc.noteblocklib;
1919

20-
import com.google.common.io.ByteStreams;
2120
import net.raphimc.noteblocklib.format.SongFormat;
22-
import net.raphimc.noteblocklib.format.future.FutureParser;
23-
import net.raphimc.noteblocklib.format.mcsp.McSpParser;
24-
import net.raphimc.noteblocklib.format.midi.MidiParser;
25-
import net.raphimc.noteblocklib.format.nbs.NbsParser;
26-
import net.raphimc.noteblocklib.format.nbs.NbsSong;
27-
import net.raphimc.noteblocklib.format.nbs.model.NbsData;
28-
import net.raphimc.noteblocklib.format.nbs.model.NbsHeader;
29-
import net.raphimc.noteblocklib.format.txt.TxtParser;
30-
import net.raphimc.noteblocklib.format.txt.TxtSong;
21+
import net.raphimc.noteblocklib.format.futureclient.FutureClientIo;
22+
import net.raphimc.noteblocklib.format.mcsp.McSpIo;
23+
import net.raphimc.noteblocklib.format.mcsp2.McSp2Converter;
24+
import net.raphimc.noteblocklib.format.mcsp2.McSp2Io;
25+
import net.raphimc.noteblocklib.format.mcsp2.model.McSp2Song;
26+
import net.raphimc.noteblocklib.format.midi.MidiIo;
27+
import net.raphimc.noteblocklib.format.nbs.NbsConverter;
28+
import net.raphimc.noteblocklib.format.nbs.NbsIo;
29+
import net.raphimc.noteblocklib.format.nbs.model.NbsSong;
30+
import net.raphimc.noteblocklib.format.txt.TxtConverter;
31+
import net.raphimc.noteblocklib.format.txt.TxtIo;
32+
import net.raphimc.noteblocklib.format.txt.model.TxtSong;
3133
import net.raphimc.noteblocklib.model.Song;
32-
import net.raphimc.noteblocklib.model.SongView;
3334

35+
import java.io.ByteArrayInputStream;
3436
import java.io.File;
3537
import java.io.InputStream;
3638
import java.io.OutputStream;
@@ -41,86 +43,88 @@
4143

4244
public class NoteBlockLib {
4345

44-
public static Song<?, ?, ?> readSong(final File file) throws Exception {
46+
public static Song readSong(final File file) throws Exception {
4547
return readSong(file.toPath());
4648
}
4749

48-
public static Song<?, ?, ?> readSong(final Path path) throws Exception {
50+
public static Song readSong(final Path path) throws Exception {
4951
return readSong(path, getFormat(path));
5052
}
5153

52-
public static Song<?, ?, ?> readSong(final Path path, final SongFormat format) throws Exception {
53-
return readSong(Files.readAllBytes(path), format, path.getFileName().toString());
54+
public static Song readSong(final Path path, final SongFormat format) throws Exception {
55+
return readSong(Files.newInputStream(path), format, path.getFileName().toString());
5456
}
5557

56-
public static Song<?, ?, ?> readSong(final InputStream is, final SongFormat format) throws Exception {
57-
return readSong(ByteStreams.toByteArray(is), format, null);
58+
public static Song readSong(final byte[] bytes, final SongFormat format) throws Exception {
59+
return readSong(new ByteArrayInputStream(bytes), format);
5860
}
5961

60-
public static Song<?, ?, ?> readSong(final byte[] bytes, final SongFormat format) throws Exception {
61-
return readSong(bytes, format, null);
62+
public static Song readSong(final InputStream is, final SongFormat format) throws Exception {
63+
return readSong(is, format, null);
6264
}
6365

64-
public static Song<?, ?, ?> readSong(final byte[] bytes, final SongFormat format, final String fileName) throws Exception {
66+
public static Song readSong(final InputStream is, final SongFormat format, final String fileName) throws Exception {
6567
try {
66-
if (format == null) throw new IllegalArgumentException("Unknown format");
67-
6868
switch (format) {
6969
case NBS:
70-
return NbsParser.read(bytes, fileName);
70+
return NbsIo.readSong(is, fileName);
7171
case MCSP:
72-
return McSpParser.read(bytes, fileName);
72+
return McSpIo.readSong(is, fileName);
73+
case MCSP2:
74+
return McSp2Io.readSong(is, fileName);
7375
case TXT:
74-
return TxtParser.read(bytes, fileName);
75-
case FUTURE:
76-
return FutureParser.read(bytes, fileName);
76+
return TxtIo.readSong(is, fileName);
77+
case FUTURE_CLIENT:
78+
return FutureClientIo.readSong(is, fileName);
7779
case MIDI:
78-
return MidiParser.read(bytes, fileName);
80+
return MidiIo.readSong(is, fileName);
7981
default:
8082
throw new IllegalStateException("Unknown format");
8183
}
8284
} catch (Throwable e) {
8385
throw new Exception("Failed to read song", e);
86+
} finally {
87+
is.close();
8488
}
8589
}
8690

87-
public static void writeSong(final Song<?, ?, ?> song, final File file) throws Exception {
91+
public static void writeSong(final Song song, final File file) throws Exception {
8892
writeSong(song, file.toPath());
8993
}
9094

91-
public static void writeSong(final Song<?, ?, ?> song, final Path path) throws Exception {
92-
Files.write(path, writeSong(song));
93-
}
94-
95-
public static void writeSong(final Song<?, ?, ?> song, final OutputStream os) throws Exception {
96-
os.write(writeSong(song));
95+
public static void writeSong(final Song song, final Path path) throws Exception {
96+
writeSong(song, Files.newOutputStream(path));
9797
}
9898

99-
public static byte[] writeSong(final Song<?, ?, ?> song) throws Exception {
100-
byte[] bytes = null;
99+
public static void writeSong(final Song song, final OutputStream os) throws Exception {
101100
try {
102101
if (song instanceof NbsSong) {
103-
bytes = NbsParser.write((NbsSong) song);
102+
NbsIo.writeSong((NbsSong) song, os);
103+
} else if (song instanceof McSp2Song) {
104+
McSp2Io.writeSong((McSp2Song) song, os);
104105
} else if (song instanceof TxtSong) {
105-
bytes = TxtParser.write((TxtSong) song);
106+
TxtIo.writeSong((TxtSong) song, os);
107+
} else {
108+
throw new Exception("Unsupported song format for writing: " + song.getClass().getSimpleName());
106109
}
107110
} catch (Throwable e) {
108111
throw new Exception("Failed to write song", e);
112+
} finally {
113+
os.close();
109114
}
110-
111-
if (bytes == null) {
112-
throw new Exception("Unsupported song type for writing: " + song.getClass().getSimpleName());
113-
}
114-
115-
return bytes;
116115
}
117116

118-
public static Song<?, ?, ?> createSongFromView(final SongView<?> songView, final SongFormat format) {
119-
if (format != SongFormat.NBS) {
120-
throw new IllegalArgumentException("Only NBS is supported for creating songs from views");
117+
public static Song convertSong(final Song song, final SongFormat targetFormat) {
118+
switch (targetFormat) {
119+
case NBS:
120+
return NbsConverter.createSong(song);
121+
case MCSP2:
122+
return McSp2Converter.createSong(song);
123+
case TXT:
124+
return TxtConverter.createSong(song);
125+
default:
126+
throw new IllegalStateException("Unsupported target format: " + targetFormat);
121127
}
122-
123-
return new NbsSong(null, new NbsHeader(songView), new NbsData(songView));
124128
}
125129

126130
public static SongFormat getFormat(final Path path) {

0 commit comments

Comments
 (0)