TIMBOVILL commited on
Commit
e880ecc
1 Parent(s): 0932341

Upload 4 files

Browse files
src/modules/Ultrastar/ultrastar_parser.py ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Ultrastar txt parser"""
2
+
3
+ from modules.console_colors import ULTRASINGER_HEAD
4
+ from modules.Ultrastar.ultrastar_converter import (
5
+ get_end_time_from_ultrastar,
6
+ get_start_time_from_ultrastar,
7
+ )
8
+ from modules.Ultrastar.ultrastar_txt import UltrastarTxtValue, UltrastarTxtTag, UltrastarTxtNoteTypeTag, FILE_ENCODING
9
+
10
+ def parse_ultrastar_txt(input_file: str) -> UltrastarTxtValue:
11
+ """Parse ultrastar txt file to UltrastarTxt class"""
12
+ print(f"{ULTRASINGER_HEAD} Parse ultrastar txt -> {input_file}")
13
+
14
+ with open(input_file, "r", encoding=FILE_ENCODING) as file:
15
+ txt = file.readlines()
16
+
17
+ ultrastar_class = UltrastarTxtValue()
18
+ count = 0
19
+
20
+ # Strips the newline character
21
+ for line in txt:
22
+ count += 1
23
+ if line.startswith("#"):
24
+ if line.startswith(f"#{UltrastarTxtTag.ARTIST}"):
25
+ ultrastar_class.artist = line.split(":")[1].replace("\n", "")
26
+ elif line.startswith(f"#{UltrastarTxtTag.TITLE}"):
27
+ ultrastar_class.title = line.split(":")[1].replace("\n", "")
28
+ elif line.startswith(f"#{UltrastarTxtTag.MP3}"):
29
+ ultrastar_class.mp3 = line.split(":")[1].replace("\n", "")
30
+ elif line.startswith(f"#{UltrastarTxtTag.AUDIO}"):
31
+ ultrastar_class.audio = line.split(":")[1].replace("\n", "")
32
+ elif line.startswith(f"#{UltrastarTxtTag.VIDEO}"):
33
+ ultrastar_class.video = line.split(":")[1].replace("\n", "")
34
+ elif line.startswith(f"#{UltrastarTxtTag.GAP}"):
35
+ ultrastar_class.gap = line.split(":")[1].replace("\n", "")
36
+ elif line.startswith(f"#{UltrastarTxtTag.BPM}"):
37
+ ultrastar_class.bpm = line.split(":")[1].replace("\n", "")
38
+ elif line.startswith((
39
+ f"{UltrastarTxtNoteTypeTag.FREESTYLE} ",
40
+ f"{UltrastarTxtNoteTypeTag.NORMAL} ",
41
+ f"{UltrastarTxtNoteTypeTag.GOLDEN} ",
42
+ f"{UltrastarTxtNoteTypeTag.RAP} ",
43
+ f"{UltrastarTxtNoteTypeTag.RAP_GOLDEN} ")):
44
+ parts = line.split()
45
+ # [0] F : * R G
46
+ # [1] start beat
47
+ # [2] duration
48
+ # [3] pitch
49
+ # [4] word
50
+
51
+ ultrastar_class.noteType.append(parts[0])
52
+ ultrastar_class.startBeat.append(parts[1])
53
+ ultrastar_class.durations.append(parts[2])
54
+ ultrastar_class.pitches.append(parts[3])
55
+ ultrastar_class.words.append(parts[4] if len(parts) > 4 else "")
56
+
57
+ # do always as last
58
+ pos = len(ultrastar_class.startBeat) - 1
59
+ ultrastar_class.startTimes.append(
60
+ get_start_time_from_ultrastar(ultrastar_class, pos)
61
+ )
62
+ ultrastar_class.endTimes.append(
63
+ get_end_time_from_ultrastar(ultrastar_class, pos)
64
+ )
65
+ # todo: Progress?
66
+
67
+ return ultrastar_class
src/modules/Ultrastar/ultrastar_score_calculator.py ADDED
@@ -0,0 +1,168 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Ultrastar score calculator."""
2
+
3
+ import librosa
4
+
5
+ from modules.console_colors import (
6
+ ULTRASINGER_HEAD,
7
+ blue_highlighted,
8
+ cyan_highlighted,
9
+ gold_highlighted,
10
+ light_blue_highlighted,
11
+ underlined,
12
+ )
13
+ from modules.Midi.midi_creator import create_midi_note_from_pitched_data
14
+ from modules.Ultrastar.ultrastar_converter import (
15
+ get_end_time_from_ultrastar,
16
+ get_start_time_from_ultrastar,
17
+ ultrastar_note_to_midi_note,
18
+ )
19
+ from modules.Ultrastar.ultrastar_txt import UltrastarTxtValue
20
+ from modules.Pitcher.pitched_data import PitchedData
21
+
22
+ MAX_SONG_SCORE = 10000
23
+ MAX_SONG_LINE_BONUS = 1000
24
+
25
+
26
+ class Points:
27
+ """Docstring"""
28
+
29
+ notes = 0
30
+ golden_notes = 0
31
+ rap = 0
32
+ golden_rap = 0
33
+ line_bonus = 0
34
+ parts = 0
35
+
36
+
37
+ def add_point(note_type: str, points: Points) -> Points:
38
+ """Add calculated points to the points object."""
39
+
40
+ if note_type == ":":
41
+ points.notes += 1
42
+ elif note_type == "*":
43
+ points.golden_notes += 2
44
+ elif note_type == "R":
45
+ points.rap += 1
46
+ elif note_type == "G":
47
+ points.golden_rap += 2
48
+ return points
49
+
50
+
51
+ class Score:
52
+ """Docstring"""
53
+
54
+ max_score = 0
55
+ notes = 0
56
+ golden = 0
57
+ line_bonus = 0
58
+ score = 0
59
+
60
+
61
+ def get_score(points: Points) -> Score:
62
+ """Score calculation."""
63
+
64
+ score = Score()
65
+ score.max_score = (
66
+ MAX_SONG_SCORE
67
+ if points.line_bonus == 0
68
+ else MAX_SONG_SCORE - MAX_SONG_LINE_BONUS
69
+ )
70
+ score.notes = round(
71
+ score.max_score * (points.notes + points.rap) / points.parts
72
+ )
73
+ score.golden = round(points.golden_notes + points.golden_rap)
74
+ score.score = round(score.notes + points.line_bonus + score.golden)
75
+ score.line_bonus = round(points.line_bonus)
76
+ return score
77
+
78
+
79
+ def print_score(score: Score) -> None:
80
+ """Print score."""
81
+
82
+ print(
83
+ f"{ULTRASINGER_HEAD} Total: {cyan_highlighted(str(score.score))}, notes: {blue_highlighted(str(score.notes))}, line bonus: {light_blue_highlighted(str(score.line_bonus))}, golden notes: {gold_highlighted(str(score.golden))}"
84
+ )
85
+
86
+
87
+ def calculate_score(pitched_data: PitchedData, ultrastar_class: UltrastarTxtValue) -> (Score, Score):
88
+ """Calculate score."""
89
+
90
+ print(ULTRASINGER_HEAD + " Calculating Ultrastar Points")
91
+
92
+ simple_points = Points()
93
+ accurate_points = Points()
94
+
95
+ reachable_line_bonus_per_word = MAX_SONG_LINE_BONUS / len(
96
+ ultrastar_class.words
97
+ )
98
+
99
+ for i in enumerate(ultrastar_class.words):
100
+ pos = i[0]
101
+ if ultrastar_class.words == "":
102
+ continue
103
+
104
+ if ultrastar_class.noteType[pos] == "F":
105
+ continue
106
+
107
+ start_time = get_start_time_from_ultrastar(ultrastar_class, pos)
108
+ end_time = get_end_time_from_ultrastar(ultrastar_class, pos)
109
+ duration = end_time - start_time
110
+ step_size = 0.09 # Todo: Whats is the step size of the game? Its not 1/bps -> one beat in seconds s = 60/bpm
111
+ parts = int(duration / step_size)
112
+ parts = 1 if parts == 0 else parts
113
+
114
+ accurate_part_line_bonus_points = 0
115
+ simple_part_line_bonus_points = 0
116
+
117
+ ultrastar_midi_note = ultrastar_note_to_midi_note(
118
+ int(ultrastar_class.pitches[pos])
119
+ )
120
+ ultrastar_note = librosa.midi_to_note(ultrastar_midi_note)
121
+
122
+ for part in range(parts):
123
+ start = start_time + step_size * part
124
+ end = start + step_size
125
+ if end_time < end or part == parts - 1:
126
+ end = end_time
127
+ pitch_note = create_midi_note_from_pitched_data(
128
+ start, end, pitched_data
129
+ )
130
+
131
+ if pitch_note[:-1] == ultrastar_note[:-1]:
132
+ # Ignore octave high
133
+ simple_points = add_point(
134
+ ultrastar_class.noteType[pos], simple_points
135
+ )
136
+ simple_part_line_bonus_points += 1
137
+
138
+ if pitch_note == ultrastar_note:
139
+ # Octave high must be the same
140
+ accurate_points = add_point(
141
+ ultrastar_class.noteType[pos], accurate_points
142
+ )
143
+ accurate_part_line_bonus_points += 1
144
+
145
+ accurate_points.parts += 1
146
+ simple_points.parts += 1
147
+
148
+ if accurate_part_line_bonus_points >= parts:
149
+ accurate_points.line_bonus += reachable_line_bonus_per_word
150
+
151
+ if simple_part_line_bonus_points >= parts:
152
+ simple_points.line_bonus += reachable_line_bonus_per_word
153
+
154
+ return get_score(simple_points), get_score(accurate_points)
155
+
156
+
157
+ def print_score_calculation(simple_points: Score, accurate_points: Score) -> None:
158
+ """Print score calculation."""
159
+
160
+ print(
161
+ f"{ULTRASINGER_HEAD} {underlined('Simple (octave high ignored)')} points"
162
+ )
163
+ print_score(simple_points)
164
+
165
+ print(
166
+ f"{ULTRASINGER_HEAD} {underlined('Accurate (octave high matches)')} points:"
167
+ )
168
+ print_score(accurate_points)
src/modules/Ultrastar/ultrastar_txt.py ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Ultrastar TXT"""
2
+
3
+ from enum import Enum
4
+
5
+ FILE_ENCODING = "utf-8"
6
+
7
+
8
+ class UltrastarTxtTag(str, Enum):
9
+ """Tags for Ultrastar TXT files."""
10
+
11
+ # 0.2.0
12
+ VERSION = 'VERSION' # Version of the file format: See https://usdx.eu/format/
13
+ ARTIST = 'ARTIST'
14
+ TITLE = 'TITLE'
15
+ MP3 = 'MP3' # Removed in v2.0.0
16
+ GAP = 'GAP'
17
+ BPM = 'BPM'
18
+ LANGUAGE = 'LANGUAGE' # Multi-language support since v1.1.0
19
+ GENRE = 'GENRE' # Multi-language support since v1.1.0
20
+ YEAR = 'YEAR' # Multi-language support since v1.1.0
21
+ COVER = 'COVER' # Path to cover. Should end with `*[CO].jpg`
22
+ CREATOR = 'CREATOR' # Multi-language support since v1.1.0
23
+ COMMENT = 'COMMENT'
24
+ VIDEO = 'VIDEO'
25
+ FILE_END = 'E'
26
+ LINEBREAK = '-'
27
+
28
+ # 1.1.0
29
+ AUDIO = 'AUDIO' # Its instead of MP3. Just renamed
30
+ VOCALS = 'VOCALS' # Vocals only audio
31
+ INSTRUMENTAL = 'INSTRUMENTAL' # Instrumental only audio
32
+ TAGS = 'TAGS' # Tags for the song. Can be used for filtering
33
+
34
+ # Unused 0.2.0
35
+ BACKGROUND = 'BACKGROUND' # Path to background. Is shown when there is no video. Should end with `*[BG].jpg`
36
+ VIDEOGAP = 'VIDEOGAP'
37
+ EDITION = 'EDITION' # Multi-language support since v1.1.0
38
+ START = 'START'
39
+ END = 'END'
40
+ PREVIEWSTART = 'PREVIEWSTART'
41
+ MEDLEYSTARTBEAT = 'MEDLEYSTARTBEAT' # Removed in 2.0.0
42
+ MEDLEYENDBEAT = 'MEDLEYENDBEAT' # Removed in v2.0.0
43
+ CALCMEDLEY = 'CALCMEDLEY'
44
+ P1 = 'P1' # Only for UltraStar Deluxe
45
+ P2 = 'P2' # Only for UltraStar Deluxe
46
+ DUETSINGERP1 = 'DUETSINGERP1' # Removed in 1.0.0 (Used by UltraStar WorldParty)
47
+ DUETSINGERP2 = 'DUETSINGERP2' # Removed in 1.0.0 (Used by UltraStar WorldParty)
48
+ RESOLUTION = 'RESOLUTION' # Changes the grid resolution of the editor. Only for the editor and nothing for singing. # Removed in 1.0.0
49
+ NOTESGAP = 'NOTESGAP' # Removed in 1.0.0
50
+ RELATIVE = 'RELATIVE' # Removed in 1.0.0
51
+ ENCODING = 'ENCODING' # Removed in 1.0.0
52
+
53
+ # (Unused) 1.1.0
54
+ PROVIDEDBY = 'PROVIDEDBY' # Should the URL from hoster server
55
+
56
+ # (Unused) New in (unreleased) 1.2.0
57
+ AUDIOURL = 'AUDIOURL' # URL to the audio file
58
+ COVERURL = 'COVERURL' # URL to the cover file
59
+ BACKGROUNDURL = 'BACKGROUNDURL' # URL to the background file
60
+ VIDEOURL = 'VIDEOURL' # URL to the video file
61
+
62
+ # (Unused) New in (unreleased) 2.0.0
63
+ MEDLEYSTART = 'MEDLEYSTART' # Rename of MEDLEYSTARTBEAT
64
+ MEDLEYEND = 'MEDLEYEND' # Renmame of MEDLEYENDBEAT
65
+
66
+
67
+ class UltrastarTxtNoteTypeTag(str, Enum):
68
+ """Note types for Ultrastar TXT files."""
69
+ NORMAL = ':'
70
+ RAP = 'R'
71
+ RAP_GOLDEN = 'G'
72
+ FREESTYLE = 'F'
73
+ GOLDEN = '*'
74
+
75
+
76
+ class UltrastarTxtValue:
77
+ """Vaules for Ultrastar TXT files."""
78
+
79
+ version = "1.0.0"
80
+ artist = ""
81
+ title = ""
82
+ year = None
83
+ genre = ""
84
+ mp3 = ""
85
+ audio = ""
86
+ video = None
87
+ gap = ""
88
+ bpm = ""
89
+ language = None
90
+ cover = None
91
+ vocals = None
92
+ instrumental = None
93
+ tags = None
94
+ creator = "UltraSinger [GitHub]"
95
+ comment = "UltraSinger [GitHub]"
96
+ startBeat = []
97
+ startTimes = []
98
+ endTimes = []
99
+ durations = []
100
+ pitches = []
101
+ words = []
102
+ noteType = [] # F, R, G, *, :
src/modules/Ultrastar/ultrastar_writer.py ADDED
@@ -0,0 +1,268 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Ultrastar writer module"""
2
+
3
+ import re
4
+ import langcodes
5
+ from packaging import version
6
+
7
+ from modules.console_colors import ULTRASINGER_HEAD
8
+ from modules.Ultrastar.ultrastar_converter import (
9
+ real_bpm_to_ultrastar_bpm,
10
+ second_to_beat,
11
+ beat_to_second,
12
+ )
13
+ from modules.Ultrastar.ultrastar_txt import UltrastarTxtValue, UltrastarTxtTag, UltrastarTxtNoteTypeTag, \
14
+ FILE_ENCODING
15
+ from modules.Speech_Recognition.TranscribedData import TranscribedData
16
+ from modules.Ultrastar.ultrastar_score_calculator import Score
17
+
18
+
19
+ def get_thirtytwo_note_second(real_bpm: float):
20
+ """Converts a beat to a 1/32 note in second"""
21
+ return 60 / real_bpm / 8
22
+
23
+
24
+ def get_sixteenth_note_second(real_bpm: float):
25
+ """Converts a beat to a 1/16 note in second"""
26
+ return 60 / real_bpm / 4
27
+
28
+
29
+ def get_eighth_note_second(real_bpm: float):
30
+ """Converts a beat to a 1/8 note in second"""
31
+ return 60 / real_bpm / 2
32
+
33
+
34
+ def get_quarter_note_second(real_bpm: float):
35
+ """Converts a beat to a 1/4 note in second"""
36
+ return 60 / real_bpm
37
+
38
+
39
+ def get_half_note_second(real_bpm: float):
40
+ """Converts a beat to a 1/2 note in second"""
41
+ return 60 / real_bpm * 2
42
+
43
+
44
+ def get_whole_note_second(real_bpm: float):
45
+ """Converts a beat to a 1/1 note in second"""
46
+ return 60 / real_bpm * 4
47
+
48
+
49
+ def get_multiplier(real_bpm: float) -> int:
50
+ """Calculates the multiplier for the BPM"""
51
+
52
+ if real_bpm == 0:
53
+ raise Exception("BPM is 0")
54
+
55
+ multiplier = 1
56
+ result = 0
57
+ while result < 400:
58
+ result = real_bpm * multiplier
59
+ multiplier += 1
60
+ return multiplier - 2
61
+
62
+
63
+ def get_language_name(language: str) -> str:
64
+ """Creates the language name from the language code"""
65
+
66
+ return langcodes.Language.make(language=language).display_name()
67
+
68
+
69
+ def create_ultrastar_txt_from_automation(
70
+ transcribed_data: list[TranscribedData],
71
+ note_numbers: list[int],
72
+ ultrastar_file_output: str,
73
+ ultrastar_class: UltrastarTxtValue,
74
+ real_bpm=120,
75
+ ) -> None:
76
+ """Creates an Ultrastar txt file from the automation data"""
77
+
78
+ print(
79
+ f"{ULTRASINGER_HEAD} Creating {ultrastar_file_output} from transcription."
80
+ )
81
+
82
+ ultrastar_bpm = real_bpm_to_ultrastar_bpm(real_bpm)
83
+ multiplication = get_multiplier(ultrastar_bpm)
84
+ ultrastar_bpm = ultrastar_bpm * get_multiplier(ultrastar_bpm)
85
+ silence_split_duration = calculate_silent_beat_length(transcribed_data)
86
+
87
+ with open(ultrastar_file_output, "w", encoding=FILE_ENCODING) as file:
88
+ gap = transcribed_data[0].start
89
+
90
+ if version.parse(ultrastar_class.version) >= version.parse("1.0.0"):
91
+ file.write(f"#{UltrastarTxtTag.VERSION}:{ultrastar_class.version}\n"),
92
+ file.write(f"#{UltrastarTxtTag.ARTIST}:{ultrastar_class.artist}\n")
93
+ file.write(f"#{UltrastarTxtTag.TITLE}:{ultrastar_class.title}\n")
94
+ if ultrastar_class.year is not None:
95
+ file.write(f"#{UltrastarTxtTag.YEAR}:{ultrastar_class.year}\n")
96
+ if ultrastar_class.language is not None:
97
+ file.write(f"#{UltrastarTxtTag.LANGUAGE}:{get_language_name(ultrastar_class.language)}\n")
98
+ if ultrastar_class.genre:
99
+ file.write(f"#{UltrastarTxtTag.GENRE}:{ultrastar_class.genre}\n")
100
+ if ultrastar_class.cover is not None:
101
+ file.write(f"#{UltrastarTxtTag.COVER}:{ultrastar_class.cover}\n")
102
+ file.write(f"#{UltrastarTxtTag.MP3}:{ultrastar_class.mp3}\n")
103
+ if version.parse(ultrastar_class.version) >= version.parse("1.1.0"):
104
+ file.write(f"#{UltrastarTxtTag.AUDIO}:{ultrastar_class.audio}\n")
105
+ if ultrastar_class.vocals is not None:
106
+ file.write(f"#{UltrastarTxtTag.VOCALS}:{ultrastar_class.vocals}\n")
107
+ if ultrastar_class.instrumental is not None:
108
+ file.write(f"#{UltrastarTxtTag.INSTRUMENTAL}:{ultrastar_class.instrumental}\n")
109
+ if ultrastar_class.tags is not None:
110
+ file.write(f"#{UltrastarTxtTag.TAGS}:{ultrastar_class.tags}\n")
111
+ file.write(f"#{UltrastarTxtTag.VIDEO}:{ultrastar_class.video}\n")
112
+ file.write(f"#{UltrastarTxtTag.BPM}:{round(ultrastar_bpm, 2)}\n") # not the real BPM!
113
+ file.write(f"#{UltrastarTxtTag.GAP}:{int(gap * 1000)}\n")
114
+ file.write(f"#{UltrastarTxtTag.CREATOR}:{ultrastar_class.creator}\n")
115
+ file.write(f"#{UltrastarTxtTag.COMMENT}:{ultrastar_class.comment}\n")
116
+
117
+ # Write the singing part
118
+ previous_end_beat = 0
119
+ separated_word_silence = [] # This is a workaround for separated words that get his ends to far away
120
+
121
+ for i, data in enumerate(transcribed_data):
122
+ start_time = (data.start - gap) * multiplication
123
+ end_time = (
124
+ data.end - data.start
125
+ ) * multiplication
126
+ start_beat = round(second_to_beat(start_time, real_bpm))
127
+ duration = round(second_to_beat(end_time, real_bpm))
128
+
129
+ # Fix the round issue, so the beats don’t overlap
130
+ start_beat = max(start_beat, previous_end_beat)
131
+ previous_end_beat = start_beat + duration
132
+
133
+ # Calculate the silence between the words
134
+ if i < len(transcribed_data) - 1:
135
+ silence = (transcribed_data[i + 1].start - data.end)
136
+ else:
137
+ silence = 0
138
+
139
+ # : 10 10 10 w
140
+ # ':' start midi part
141
+ # 'n1' start at real beat
142
+ # 'n2' duration at real beat
143
+ # 'n3' pitch where 0 == C4
144
+ # 'w' lyric
145
+ line = f"{UltrastarTxtNoteTypeTag.NORMAL} " \
146
+ f"{str(start_beat)} " \
147
+ f"{str(duration)} " \
148
+ f"{str(note_numbers[i])} " \
149
+ f"{data.word}\n"
150
+
151
+ file.write(line)
152
+
153
+ # detect silence between words
154
+ if not transcribed_data[i].is_word_end:
155
+ separated_word_silence.append(silence)
156
+ continue
157
+
158
+ if silence_split_duration != 0 and silence > silence_split_duration or any(
159
+ s > silence_split_duration for s in separated_word_silence) and i != len(transcribed_data) - 1:
160
+ # - 10
161
+ # '-' end of current sing part
162
+ # 'n1' show next at time in real beat
163
+ show_next = (
164
+ second_to_beat(data.end - gap, real_bpm)
165
+ * multiplication
166
+ )
167
+ linebreak = f"{UltrastarTxtTag.LINEBREAK} " \
168
+ f"{str(round(show_next))}\n"
169
+ file.write(linebreak)
170
+ separated_word_silence = []
171
+ file.write(f"{UltrastarTxtTag.FILE_END}")
172
+
173
+
174
+ def deviation(silence_parts):
175
+ """Calculates the deviation of the silence parts"""
176
+
177
+ if len(silence_parts) < 5:
178
+ return 0
179
+
180
+ # Remove the longest part so the deviation is not that high
181
+ sorted_parts = sorted(silence_parts)
182
+ filtered_parts = [part for part in sorted_parts if part < sorted_parts[-1]]
183
+
184
+ sum_parts = sum(filtered_parts)
185
+ if sum_parts == 0:
186
+ return 0
187
+
188
+ mean = sum_parts / len(filtered_parts)
189
+ return mean
190
+
191
+
192
+ def calculate_silent_beat_length(transcribed_data: list[TranscribedData]):
193
+ print(f"{ULTRASINGER_HEAD} Calculating silence parts for linebreaks.")
194
+
195
+ silent_parts = []
196
+ for i, data in enumerate(transcribed_data):
197
+ if i < len(transcribed_data) - 1:
198
+ silent_parts.append(transcribed_data[i + 1].start - data.end)
199
+
200
+ return deviation(silent_parts)
201
+
202
+
203
+ def create_repitched_txt_from_ultrastar_data(
204
+ input_file: str, note_numbers: list[int], output_repitched_ultrastar: str
205
+ ) -> None:
206
+ """Creates a repitched ultrastar txt file from the original one"""
207
+ # todo: just add '_repitched' to input_file
208
+ print(
209
+ "{PRINT_ULTRASTAR} Creating repitched ultrastar txt -> {input_file}_repitch.txt"
210
+ )
211
+
212
+ # todo: to reader
213
+ with open(input_file, "r", encoding=FILE_ENCODING) as file:
214
+ txt = file.readlines()
215
+
216
+ i = 0
217
+ # todo: just add '_repitched' to input_file
218
+ with open(output_repitched_ultrastar, "w", encoding=FILE_ENCODING) as file:
219
+ for line in txt:
220
+ if line.startswith(f"{UltrastarTxtNoteTypeTag.NORMAL} "):
221
+ parts = re.findall(r"\S+|\s+", line)
222
+ # between are whitespaces
223
+ # [0] :
224
+ # [2] start beat
225
+ # [4] duration
226
+ # [6] pitch
227
+ # [8] word
228
+ parts[6] = str(note_numbers[i])
229
+ delimiter = ""
230
+ file.write(delimiter.join(parts))
231
+ i += 1
232
+ else:
233
+ file.write(line)
234
+
235
+
236
+ def add_score_to_ultrastar_txt(ultrastar_file_output: str, score: Score) -> None:
237
+ """Adds the score to the ultrastar txt file"""
238
+ with open(ultrastar_file_output, "r", encoding=FILE_ENCODING) as file:
239
+ text = file.read()
240
+ text = text.split("\n")
241
+
242
+ for i, line in enumerate(text):
243
+ if line.startswith(f"#{UltrastarTxtTag.COMMENT}:"):
244
+ text[
245
+ i
246
+ ] = f"{line} | Score: total: {score.score}, notes: {score.notes} line: {score.line_bonus}, golden: {score.golden}"
247
+ break
248
+
249
+ if line.startswith((
250
+ f"{UltrastarTxtNoteTypeTag.FREESTYLE} ",
251
+ f"{UltrastarTxtNoteTypeTag.NORMAL} ",
252
+ f"{UltrastarTxtNoteTypeTag.GOLDEN} ",
253
+ f"{UltrastarTxtNoteTypeTag.RAP} ",
254
+ f"{UltrastarTxtNoteTypeTag.RAP_GOLDEN} ")):
255
+ text.insert(
256
+ i,
257
+ f"#{UltrastarTxtTag.COMMENT}: UltraSinger [GitHub] | Score: total: {score.score}, notes: {score.notes} line: {score.line_bonus}, golden: {score.golden}",
258
+ )
259
+ break
260
+
261
+ text = "\n".join(text)
262
+
263
+ with open(ultrastar_file_output, "w", encoding=FILE_ENCODING) as file:
264
+ file.write(text)
265
+
266
+
267
+ class UltraStarWriter:
268
+ """Docstring"""