mms-lib 0.2.1__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mms_lib-0.2.1 → mms_lib-0.3.0}/PKG-INFO +1 -1
- {mms_lib-0.2.1 → mms_lib-0.3.0}/pyproject.toml +1 -1
- {mms_lib-0.2.1 → mms_lib-0.3.0}/src/mms/__init__.py +1 -1
- {mms_lib-0.2.1 → mms_lib-0.3.0}/src/mms/core.py +21 -12
- {mms_lib-0.2.1 → mms_lib-0.3.0}/src/mms/dataclass.py +1 -1
- {mms_lib-0.2.1 → mms_lib-0.3.0}/src/mms/midi_reader.py +1 -1
- mms_lib-0.3.0/src/mms/nbs_reader.py +63 -0
- mms_lib-0.3.0/src/mms/pynbs/__init__.py +4 -0
- mms_lib-0.3.0/src/mms/pynbs/file.py +306 -0
- {mms_lib-0.2.1 → mms_lib-0.3.0}/src/mms/tools.py +2 -0
- {mms_lib-0.2.1 → mms_lib-0.3.0}/src/mms_lib.egg-info/PKG-INFO +1 -1
- {mms_lib-0.2.1 → mms_lib-0.3.0}/src/mms_lib.egg-info/SOURCES.txt +3 -0
- {mms_lib-0.2.1 → mms_lib-0.3.0}/LICENSE +0 -0
- {mms_lib-0.2.1 → mms_lib-0.3.0}/README.md +0 -0
- {mms_lib-0.2.1 → mms_lib-0.3.0}/setup.cfg +0 -0
- {mms_lib-0.2.1 → mms_lib-0.3.0}/src/mms/assets/text/features.json +0 -0
- {mms_lib-0.2.1 → mms_lib-0.3.0}/src/mms/assets/text/mapping.json +0 -0
- {mms_lib-0.2.1 → mms_lib-0.3.0}/src/mms/assets/text/profile.json +0 -0
- {mms_lib-0.2.1 → mms_lib-0.3.0}/src/mms/assets/text/settings.json +0 -0
- {mms_lib-0.2.1 → mms_lib-0.3.0}/src/mms/cli.py +0 -0
- {mms_lib-0.2.1 → mms_lib-0.3.0}/src/mms/evaltools.py +0 -0
- {mms_lib-0.2.1 → mms_lib-0.3.0}/src/mms/mido/__init__.py +0 -0
- {mms_lib-0.2.1 → mms_lib-0.3.0}/src/mms/mido/frozen.py +0 -0
- {mms_lib-0.2.1 → mms_lib-0.3.0}/src/mms/mido/messages/__init__.py +0 -0
- {mms_lib-0.2.1 → mms_lib-0.3.0}/src/mms/mido/messages/checks.py +0 -0
- {mms_lib-0.2.1 → mms_lib-0.3.0}/src/mms/mido/messages/decode.py +0 -0
- {mms_lib-0.2.1 → mms_lib-0.3.0}/src/mms/mido/messages/encode.py +0 -0
- {mms_lib-0.2.1 → mms_lib-0.3.0}/src/mms/mido/messages/messages.py +0 -0
- {mms_lib-0.2.1 → mms_lib-0.3.0}/src/mms/mido/messages/specs.py +0 -0
- {mms_lib-0.2.1 → mms_lib-0.3.0}/src/mms/mido/messages/strings.py +0 -0
- {mms_lib-0.2.1 → mms_lib-0.3.0}/src/mms/mido/midifiles/__init__.py +0 -0
- {mms_lib-0.2.1 → mms_lib-0.3.0}/src/mms/mido/midifiles/meta.py +0 -0
- {mms_lib-0.2.1 → mms_lib-0.3.0}/src/mms/mido/midifiles/midifiles.py +0 -0
- {mms_lib-0.2.1 → mms_lib-0.3.0}/src/mms/mido/midifiles/tracks.py +0 -0
- {mms_lib-0.2.1 → mms_lib-0.3.0}/src/mms/mido/midifiles/units.py +0 -0
- {mms_lib-0.2.1 → mms_lib-0.3.0}/src/mms/mido/parser.py +0 -0
- {mms_lib-0.2.1 → mms_lib-0.3.0}/src/mms/mido/tokenizer.py +0 -0
- {mms_lib-0.2.1 → mms_lib-0.3.0}/src/mms/mido/version.py +0 -0
- {mms_lib-0.2.1 → mms_lib-0.3.0}/src/mms_lib.egg-info/dependency_links.txt +0 -0
- {mms_lib-0.2.1 → mms_lib-0.3.0}/src/mms_lib.egg-info/entry_points.txt +0 -0
- {mms_lib-0.2.1 → mms_lib-0.3.0}/src/mms_lib.egg-info/requires.txt +0 -0
- {mms_lib-0.2.1 → mms_lib-0.3.0}/src/mms_lib.egg-info/top_level.txt +0 -0
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import os
|
|
2
2
|
from . import evaltools
|
|
3
3
|
from math import ceil, isclose
|
|
4
|
-
from .tools import round_int, round_45, is_number, limit
|
|
4
|
+
from .tools import round_int, round_45, is_number, limit, is_midi
|
|
5
5
|
from .dataclass import LyricsList, Note, Lyrics, Eval, AverageNumber, Profiles, Settings
|
|
6
|
+
from .nbs_reader import NBSReader
|
|
6
7
|
from .midi_reader import MIDIReader
|
|
7
8
|
from importlib.resources import files
|
|
8
9
|
|
|
@@ -41,7 +42,7 @@ def load_lrc(lines: list[str], time_per_tick: int = 50):
|
|
|
41
42
|
elif tag_type == "offset":
|
|
42
43
|
offset = int(value)
|
|
43
44
|
|
|
44
|
-
def read_midi(midi_reader: MIDIReader, platform_profile: dict, lyrics_data: list[str] | None = None) -> dict[int, set[Note | Lyrics]]:
|
|
45
|
+
def read_midi(midi_reader: MIDIReader | NBSReader, platform_profile: dict, lyrics_data: list[str] | None = None) -> dict[int, set[Note | Lyrics]]:
|
|
45
46
|
if profile.settings.max_time_error:
|
|
46
47
|
tempo = profile.settings.time_per_tick
|
|
47
48
|
min_error = float("INF")
|
|
@@ -105,13 +106,18 @@ def read_midi(midi_reader: MIDIReader, platform_profile: dict, lyrics_data: list
|
|
|
105
106
|
# 歌词数据处理
|
|
106
107
|
if lyrics_data is not None:
|
|
107
108
|
lyrics_buffer.clear()
|
|
109
|
+
|
|
108
110
|
for k, i in load_lrc(lyrics_data, best_tempo):
|
|
109
111
|
if k in lyrics_buffer:
|
|
110
112
|
lyrics_buffer[k] += " " + i
|
|
111
113
|
else:
|
|
112
114
|
lyrics_buffer[k] = i
|
|
113
115
|
|
|
114
|
-
|
|
116
|
+
render_lyrics(lyrics_buffer, result)
|
|
117
|
+
|
|
118
|
+
return result
|
|
119
|
+
|
|
120
|
+
def render_lyrics(lyrics_buffer: dict[int, str], music_data: dict[int, set[Note | Lyrics]]) -> None:
|
|
115
121
|
if lyrics_buffer:
|
|
116
122
|
last_l = None
|
|
117
123
|
for k, l in LyricsList(lyrics_buffer, profile.settings.smooth_progress, profile.settings.auto_join_lyrics):
|
|
@@ -120,25 +126,28 @@ def read_midi(midi_reader: MIDIReader, platform_profile: dict, lyrics_data: list
|
|
|
120
126
|
continue
|
|
121
127
|
|
|
122
128
|
# 将歌词数据合并到结果中
|
|
123
|
-
if k not in
|
|
124
|
-
|
|
129
|
+
if k not in music_data:
|
|
130
|
+
music_data[k] = set()
|
|
125
131
|
|
|
126
|
-
|
|
132
|
+
music_data[k].add(l)
|
|
127
133
|
last_l = l
|
|
128
134
|
|
|
129
|
-
return result
|
|
130
|
-
|
|
131
135
|
def convertor(file_data: bytes, platform: str, lyrics_data: list[str] | None = None):
|
|
132
136
|
platform_profile = profile.profile[platform]
|
|
133
137
|
|
|
134
|
-
|
|
138
|
+
if is_midi(file_data):
|
|
139
|
+
file_data = MIDIReader(file_data)
|
|
140
|
+
else:
|
|
141
|
+
file_data = NBSReader(file_data)
|
|
142
|
+
|
|
143
|
+
music_data = read_midi(file_data, platform_profile, lyrics_data)
|
|
135
144
|
|
|
136
145
|
if profile.settings.skip:
|
|
137
|
-
time_offset = min(
|
|
146
|
+
time_offset = min(music_data.keys())
|
|
138
147
|
else:
|
|
139
148
|
time_offset = 0
|
|
140
149
|
|
|
141
|
-
return cmd_convertor(platform_profile, time_offset,
|
|
150
|
+
return cmd_convertor(platform_profile, time_offset, music_data)
|
|
142
151
|
|
|
143
152
|
def get_notes(midi_file: MIDIReader, platform_profile: dict, tempo: float | int):
|
|
144
153
|
if not profile.settings.enable_extension:
|
|
@@ -240,7 +249,7 @@ def cmd_convertor(platform_profile: dict, start_time: int, note_data: dict[int,
|
|
|
240
249
|
cmd = cmd.replace("{ADDRESS}", str(profile.settings.id))
|
|
241
250
|
cmd = cmd.replace("{ID_SCOREBOARD}", profile.settings.id_scoreboard_name)
|
|
242
251
|
cmd = cmd.replace("{TIMER_SCOREBOARD}", profile.settings.timer_scoreboard_name)
|
|
243
|
-
yield time, cmd
|
|
252
|
+
yield time, cmd.lstrip("/")
|
|
244
253
|
|
|
245
254
|
def cmd_generator(platform_profile: dict, start_time: int, note_data: dict[int, set[Note | Lyrics]]):
|
|
246
255
|
raw_cmd: str = platform_profile["command"][profile.settings.command_type][0]
|
|
@@ -237,7 +237,7 @@ class NoteData:
|
|
|
237
237
|
"type"
|
|
238
238
|
)
|
|
239
239
|
|
|
240
|
-
def __init__(self, time: float, pitch: float, program: tuple[int, int], panning: tuple[float], velocity: float):
|
|
240
|
+
def __init__(self, time: float, pitch: float, program: tuple[int, int], panning: tuple[float, float], velocity: float):
|
|
241
241
|
self.type = "note"
|
|
242
242
|
self.time = time
|
|
243
243
|
self.pitch = pitch
|
|
@@ -211,7 +211,7 @@ class MIDIReader:
|
|
|
211
211
|
elif message.type == "note_off" or (message.type == "note_on" and message.velocity == 0):
|
|
212
212
|
data = stack.get(channel, message.note)
|
|
213
213
|
|
|
214
|
-
#
|
|
214
|
+
# 将音符时间转为绝对时间并返回结果,时间单位为毫秒
|
|
215
215
|
if data is not None: yield tempo_info.compute_time(time), data
|
|
216
216
|
|
|
217
217
|
for data in stack: yield data.time + 0.00001, data
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import io
|
|
2
|
+
import math
|
|
3
|
+
from . import pynbs
|
|
4
|
+
from .tools import round_45
|
|
5
|
+
from .dataclass import NoteData
|
|
6
|
+
|
|
7
|
+
NBS_TO_GM_MAPPING = {
|
|
8
|
+
0: 0,
|
|
9
|
+
1: 43,
|
|
10
|
+
2: 116,
|
|
11
|
+
3: 118,
|
|
12
|
+
4: 115,
|
|
13
|
+
5: 24,
|
|
14
|
+
6: 73,
|
|
15
|
+
7: 14,
|
|
16
|
+
8: 8,
|
|
17
|
+
9: 13,
|
|
18
|
+
10: 9,
|
|
19
|
+
11: 113,
|
|
20
|
+
12: 111,
|
|
21
|
+
13: 80,
|
|
22
|
+
14: 105,
|
|
23
|
+
15: 98,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
class NBSReader:
|
|
27
|
+
def __init__(self, data: bytes):
|
|
28
|
+
self.__nbs_file = pynbs.Parser(io.BytesIO(data)).read_file()
|
|
29
|
+
self.__nodes_cache = []
|
|
30
|
+
self.__instruments_mapping = {}
|
|
31
|
+
|
|
32
|
+
def get_time_accuracy(self, _) -> float:
|
|
33
|
+
return 0.0
|
|
34
|
+
|
|
35
|
+
def override_mapping(self, mapping: dict[int, dict[int, int]]) -> None:
|
|
36
|
+
self.__instruments_mapping = mapping
|
|
37
|
+
|
|
38
|
+
def __iter__(self):
|
|
39
|
+
volume_info = {}
|
|
40
|
+
panning_info = {}
|
|
41
|
+
for layer in self.__nbs_file.layers:
|
|
42
|
+
volume_info[layer.id] = layer.volume
|
|
43
|
+
panning_info[layer.id] = layer.panning
|
|
44
|
+
|
|
45
|
+
for tick, notes in self.__nbs_file:
|
|
46
|
+
for note in notes:
|
|
47
|
+
if panning_info[note.layer] == 0:
|
|
48
|
+
panning = note.panning
|
|
49
|
+
else:
|
|
50
|
+
panning = (note.panning + panning_info[note.layer]) / 2
|
|
51
|
+
|
|
52
|
+
radian = math.radians((panning + 100) * 0.9)
|
|
53
|
+
|
|
54
|
+
note_data = NoteData(
|
|
55
|
+
tick * 50,
|
|
56
|
+
note.key + 21 + note.pitch / 100,
|
|
57
|
+
(NBS_TO_GM_MAPPING.get(note.instrument, 0), ) * 2,
|
|
58
|
+
(round_45(math.cos(radian), 2), round_45(math.sin(radian), 2)),
|
|
59
|
+
note.velocity * volume_info[note.layer]
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
yield tick * 50 + 0.00001, note_data
|
|
63
|
+
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
"read",
|
|
3
|
+
"new_file",
|
|
4
|
+
"Parser",
|
|
5
|
+
"Writer",
|
|
6
|
+
"File",
|
|
7
|
+
"Header",
|
|
8
|
+
"Note",
|
|
9
|
+
"Layer",
|
|
10
|
+
"Instrument",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from struct import Struct
|
|
16
|
+
|
|
17
|
+
CURRENT_NBS_VERSION = 5
|
|
18
|
+
|
|
19
|
+
BYTE = Struct("<B")
|
|
20
|
+
SHORT = Struct("<H")
|
|
21
|
+
SSHORT = Struct("<h")
|
|
22
|
+
INT = Struct("<I")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class Instrument:
|
|
27
|
+
id: int
|
|
28
|
+
name: str
|
|
29
|
+
file: str
|
|
30
|
+
pitch: int = 45
|
|
31
|
+
press_key: bool = True
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class Note:
|
|
36
|
+
tick: int
|
|
37
|
+
layer: int
|
|
38
|
+
instrument: int
|
|
39
|
+
key: int
|
|
40
|
+
velocity: int = 100
|
|
41
|
+
panning: int = 0
|
|
42
|
+
pitch: int = 0
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass
|
|
46
|
+
class Layer:
|
|
47
|
+
id: int
|
|
48
|
+
name: str = ""
|
|
49
|
+
lock: bool = False
|
|
50
|
+
volume: int = 100
|
|
51
|
+
panning: int = 0
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def read(filename):
|
|
55
|
+
with open(filename, "rb") as fileobj:
|
|
56
|
+
return Parser(fileobj).read_file()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def new_file(**header):
|
|
60
|
+
return File(Header(**header), [], [Layer(0, "", False, 100, 0)], [])
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@dataclass
|
|
64
|
+
class Header:
|
|
65
|
+
version: int = CURRENT_NBS_VERSION
|
|
66
|
+
default_instruments: int = 16
|
|
67
|
+
song_length: int = 0
|
|
68
|
+
song_layers: int = 0
|
|
69
|
+
song_name: str = ""
|
|
70
|
+
song_author: str = ""
|
|
71
|
+
original_author: str = ""
|
|
72
|
+
description: str = ""
|
|
73
|
+
tempo: float = 10.0
|
|
74
|
+
auto_save: bool = False
|
|
75
|
+
auto_save_duration: int = 10
|
|
76
|
+
time_signature: int = 4
|
|
77
|
+
minutes_spent: int = 0
|
|
78
|
+
left_clicks: int = 0
|
|
79
|
+
right_clicks: int = 0
|
|
80
|
+
blocks_added: int = 0
|
|
81
|
+
blocks_removed: int = 0
|
|
82
|
+
song_origin: str = ""
|
|
83
|
+
loop: bool = False
|
|
84
|
+
max_loop_count: int = 0
|
|
85
|
+
loop_start: int = 0
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class File:
|
|
89
|
+
def __init__(self, header, notes, layers, instruments):
|
|
90
|
+
self.header = header
|
|
91
|
+
self.notes = notes
|
|
92
|
+
self.layers = layers
|
|
93
|
+
self.instruments = instruments
|
|
94
|
+
|
|
95
|
+
def update_header(self, version):
|
|
96
|
+
self.header.version = version
|
|
97
|
+
if self.notes:
|
|
98
|
+
self.header.song_length = self.notes[-1].tick
|
|
99
|
+
self.header.song_layers = len(self.layers)
|
|
100
|
+
|
|
101
|
+
def save(self, filename, version=CURRENT_NBS_VERSION):
|
|
102
|
+
self.update_header(version)
|
|
103
|
+
with open(filename, "wb") as fileobj:
|
|
104
|
+
Writer(fileobj).encode_file(self, version)
|
|
105
|
+
|
|
106
|
+
def __iter__(self):
|
|
107
|
+
if not self.notes:
|
|
108
|
+
return
|
|
109
|
+
chord = []
|
|
110
|
+
current_tick = self.notes[0].tick
|
|
111
|
+
|
|
112
|
+
for note in sorted(self.notes, key=lambda n: n.tick):
|
|
113
|
+
if note.tick == current_tick:
|
|
114
|
+
chord.append(note)
|
|
115
|
+
else:
|
|
116
|
+
chord.sort(key=lambda n: n.layer)
|
|
117
|
+
yield current_tick, chord
|
|
118
|
+
current_tick, chord = note.tick, [note]
|
|
119
|
+
yield current_tick, chord
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class Parser:
|
|
123
|
+
def __init__(self, fileobj):
|
|
124
|
+
self.fileobj = fileobj
|
|
125
|
+
|
|
126
|
+
def read_file(self):
|
|
127
|
+
header = self.parse_header()
|
|
128
|
+
version = header.version
|
|
129
|
+
return File(
|
|
130
|
+
header,
|
|
131
|
+
list(self.parse_notes(version)),
|
|
132
|
+
list(self.parse_layers(header.song_layers, version)),
|
|
133
|
+
list(self.parse_instruments(version)),
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
def read_numeric(self, fmt):
|
|
137
|
+
return fmt.unpack(self.fileobj.read(fmt.size))[0]
|
|
138
|
+
|
|
139
|
+
def read_string(self):
|
|
140
|
+
length = self.read_numeric(INT)
|
|
141
|
+
return self.fileobj.read(length).decode(encoding="cp1252")
|
|
142
|
+
|
|
143
|
+
def jump(self):
|
|
144
|
+
value = -1
|
|
145
|
+
while True:
|
|
146
|
+
jump = self.read_numeric(SHORT)
|
|
147
|
+
if not jump:
|
|
148
|
+
break
|
|
149
|
+
value += jump
|
|
150
|
+
yield value
|
|
151
|
+
|
|
152
|
+
def parse_header(self):
|
|
153
|
+
song_length = self.read_numeric(SHORT)
|
|
154
|
+
if song_length == 0:
|
|
155
|
+
# A song length of 0 indicates the Open Note Block Studio format
|
|
156
|
+
version = self.read_numeric(BYTE)
|
|
157
|
+
else:
|
|
158
|
+
version = 0
|
|
159
|
+
|
|
160
|
+
return Header(
|
|
161
|
+
version=version,
|
|
162
|
+
default_instruments=self.read_numeric(BYTE) if version > 0 else 10,
|
|
163
|
+
song_length=self.read_numeric(SHORT) if version >= 3 else song_length,
|
|
164
|
+
song_layers=self.read_numeric(SHORT),
|
|
165
|
+
song_name=self.read_string(),
|
|
166
|
+
song_author=self.read_string(),
|
|
167
|
+
original_author=self.read_string(),
|
|
168
|
+
description=self.read_string(),
|
|
169
|
+
tempo=self.read_numeric(SHORT) / 100.0,
|
|
170
|
+
auto_save=self.read_numeric(BYTE) == 1,
|
|
171
|
+
auto_save_duration=self.read_numeric(BYTE),
|
|
172
|
+
time_signature=self.read_numeric(BYTE),
|
|
173
|
+
minutes_spent=self.read_numeric(INT),
|
|
174
|
+
left_clicks=self.read_numeric(INT),
|
|
175
|
+
right_clicks=self.read_numeric(INT),
|
|
176
|
+
blocks_added=self.read_numeric(INT),
|
|
177
|
+
blocks_removed=self.read_numeric(INT),
|
|
178
|
+
song_origin=self.read_string(),
|
|
179
|
+
loop=self.read_numeric(BYTE) == 1 if version >= 4 else False,
|
|
180
|
+
max_loop_count=self.read_numeric(BYTE) if version >= 4 else 0,
|
|
181
|
+
loop_start=self.read_numeric(SHORT) if version >= 4 else 0,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
def parse_notes(self, version):
|
|
185
|
+
for current_tick in self.jump():
|
|
186
|
+
for current_layer in self.jump():
|
|
187
|
+
instrument = self.read_numeric(BYTE)
|
|
188
|
+
key = self.read_numeric(BYTE)
|
|
189
|
+
velocity = self.read_numeric(BYTE) if version >= 4 else 100
|
|
190
|
+
panning = self.read_numeric(BYTE) - 100 if version >= 4 else 0
|
|
191
|
+
pitch = self.read_numeric(SSHORT) if version >= 4 else 0
|
|
192
|
+
yield Note(
|
|
193
|
+
current_tick,
|
|
194
|
+
current_layer,
|
|
195
|
+
instrument,
|
|
196
|
+
key,
|
|
197
|
+
velocity,
|
|
198
|
+
panning,
|
|
199
|
+
pitch,
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
def parse_layers(self, layers_count, version):
|
|
203
|
+
for i in range(layers_count):
|
|
204
|
+
name = self.read_string()
|
|
205
|
+
lock = self.read_numeric(BYTE) == 1 if version >= 4 else False
|
|
206
|
+
volume = self.read_numeric(BYTE)
|
|
207
|
+
panning = self.read_numeric(BYTE) - 100 if version >= 2 else 0
|
|
208
|
+
yield Layer(i, name, lock, volume, panning)
|
|
209
|
+
|
|
210
|
+
def parse_instruments(self, version):
|
|
211
|
+
for i in range(self.read_numeric(BYTE)):
|
|
212
|
+
name = self.read_string()
|
|
213
|
+
sound_file = self.read_string()
|
|
214
|
+
pitch = self.read_numeric(BYTE)
|
|
215
|
+
press_key = self.read_numeric(BYTE) == 1
|
|
216
|
+
yield Instrument(i, name, sound_file, pitch, press_key)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
class Writer:
|
|
220
|
+
def __init__(self, fileobj):
|
|
221
|
+
self.fileobj = fileobj
|
|
222
|
+
|
|
223
|
+
def encode_file(self, nbs_file, version):
|
|
224
|
+
self.write_header(nbs_file, version)
|
|
225
|
+
self.write_notes(nbs_file, version)
|
|
226
|
+
self.write_layers(nbs_file, version)
|
|
227
|
+
self.write_instruments(nbs_file, version)
|
|
228
|
+
|
|
229
|
+
def encode_numeric(self, fmt, value):
|
|
230
|
+
self.fileobj.write(fmt.pack(value))
|
|
231
|
+
|
|
232
|
+
def encode_string(self, value):
|
|
233
|
+
self.encode_numeric(INT, len(value))
|
|
234
|
+
self.fileobj.write(value.encode(encoding="cp1252"))
|
|
235
|
+
|
|
236
|
+
def write_header(self, nbs_file, version):
|
|
237
|
+
header = nbs_file.header
|
|
238
|
+
|
|
239
|
+
if version > 0:
|
|
240
|
+
self.encode_numeric(SHORT, 0)
|
|
241
|
+
self.encode_numeric(BYTE, version)
|
|
242
|
+
self.encode_numeric(BYTE, header.default_instruments)
|
|
243
|
+
else:
|
|
244
|
+
self.encode_numeric(SHORT, header.song_length)
|
|
245
|
+
if version >= 3:
|
|
246
|
+
self.encode_numeric(SHORT, header.song_length)
|
|
247
|
+
self.encode_numeric(SHORT, header.song_layers)
|
|
248
|
+
self.encode_string(header.song_name)
|
|
249
|
+
self.encode_string(header.song_author)
|
|
250
|
+
self.encode_string(header.original_author)
|
|
251
|
+
self.encode_string(header.description)
|
|
252
|
+
|
|
253
|
+
self.encode_numeric(SHORT, int(header.tempo * 100))
|
|
254
|
+
self.encode_numeric(BYTE, int(header.auto_save))
|
|
255
|
+
self.encode_numeric(BYTE, header.auto_save_duration)
|
|
256
|
+
self.encode_numeric(BYTE, header.time_signature)
|
|
257
|
+
|
|
258
|
+
self.encode_numeric(INT, header.minutes_spent)
|
|
259
|
+
self.encode_numeric(INT, header.left_clicks)
|
|
260
|
+
self.encode_numeric(INT, header.right_clicks)
|
|
261
|
+
self.encode_numeric(INT, header.blocks_added)
|
|
262
|
+
self.encode_numeric(INT, header.blocks_removed)
|
|
263
|
+
self.encode_string(header.song_origin)
|
|
264
|
+
|
|
265
|
+
if version >= 4:
|
|
266
|
+
self.encode_numeric(BYTE, int(header.loop))
|
|
267
|
+
self.encode_numeric(BYTE, header.max_loop_count)
|
|
268
|
+
self.encode_numeric(SHORT, header.loop_start)
|
|
269
|
+
|
|
270
|
+
def write_notes(self, nbs_file, version):
|
|
271
|
+
current_tick = -1
|
|
272
|
+
|
|
273
|
+
for tick, chord in nbs_file:
|
|
274
|
+
self.encode_numeric(SHORT, tick - current_tick)
|
|
275
|
+
current_tick = tick
|
|
276
|
+
current_layer = -1
|
|
277
|
+
|
|
278
|
+
for note in chord:
|
|
279
|
+
self.encode_numeric(SHORT, note.layer - current_layer)
|
|
280
|
+
current_layer = note.layer
|
|
281
|
+
self.encode_numeric(BYTE, note.instrument)
|
|
282
|
+
self.encode_numeric(BYTE, note.key)
|
|
283
|
+
if version >= 4:
|
|
284
|
+
self.encode_numeric(BYTE, note.velocity)
|
|
285
|
+
self.encode_numeric(BYTE, note.panning + 100)
|
|
286
|
+
self.encode_numeric(SSHORT, note.pitch)
|
|
287
|
+
|
|
288
|
+
self.encode_numeric(SHORT, 0)
|
|
289
|
+
self.encode_numeric(SHORT, 0)
|
|
290
|
+
|
|
291
|
+
def write_layers(self, nbs_file, version):
|
|
292
|
+
for layer in nbs_file.layers:
|
|
293
|
+
self.encode_string(layer.name)
|
|
294
|
+
if version >= 4:
|
|
295
|
+
self.encode_numeric(BYTE, int(layer.lock))
|
|
296
|
+
self.encode_numeric(BYTE, layer.volume)
|
|
297
|
+
if version >= 2:
|
|
298
|
+
self.encode_numeric(BYTE, layer.panning + 100)
|
|
299
|
+
|
|
300
|
+
def write_instruments(self, nbs_file, version):
|
|
301
|
+
self.encode_numeric(BYTE, len(nbs_file.instruments))
|
|
302
|
+
for instrument in nbs_file.instruments:
|
|
303
|
+
self.encode_string(instrument.name)
|
|
304
|
+
self.encode_string(instrument.file)
|
|
305
|
+
self.encode_numeric(BYTE, instrument.pitch)
|
|
306
|
+
self.encode_numeric(BYTE, int(instrument.press_key))
|
|
@@ -7,6 +7,7 @@ src/mms/core.py
|
|
|
7
7
|
src/mms/dataclass.py
|
|
8
8
|
src/mms/evaltools.py
|
|
9
9
|
src/mms/midi_reader.py
|
|
10
|
+
src/mms/nbs_reader.py
|
|
10
11
|
src/mms/tools.py
|
|
11
12
|
src/mms/assets/text/features.json
|
|
12
13
|
src/mms/assets/text/mapping.json
|
|
@@ -29,6 +30,8 @@ src/mms/mido/midifiles/meta.py
|
|
|
29
30
|
src/mms/mido/midifiles/midifiles.py
|
|
30
31
|
src/mms/mido/midifiles/tracks.py
|
|
31
32
|
src/mms/mido/midifiles/units.py
|
|
33
|
+
src/mms/pynbs/__init__.py
|
|
34
|
+
src/mms/pynbs/file.py
|
|
32
35
|
src/mms_lib.egg-info/PKG-INFO
|
|
33
36
|
src/mms_lib.egg-info/SOURCES.txt
|
|
34
37
|
src/mms_lib.egg-info/dependency_links.txt
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|