UltraSingerUI / src /modules /Ultrastar /ultrastar_writer.py
TIMBOVILL's picture
Upload 4 files
e880ecc verified
"""Ultrastar writer module"""
import re
import langcodes
from packaging import version
from modules.console_colors import ULTRASINGER_HEAD
from modules.Ultrastar.ultrastar_converter import (
real_bpm_to_ultrastar_bpm,
second_to_beat,
beat_to_second,
)
from modules.Ultrastar.ultrastar_txt import UltrastarTxtValue, UltrastarTxtTag, UltrastarTxtNoteTypeTag, \
FILE_ENCODING
from modules.Speech_Recognition.TranscribedData import TranscribedData
from modules.Ultrastar.ultrastar_score_calculator import Score
def get_thirtytwo_note_second(real_bpm: float):
"""Converts a beat to a 1/32 note in second"""
return 60 / real_bpm / 8
def get_sixteenth_note_second(real_bpm: float):
"""Converts a beat to a 1/16 note in second"""
return 60 / real_bpm / 4
def get_eighth_note_second(real_bpm: float):
"""Converts a beat to a 1/8 note in second"""
return 60 / real_bpm / 2
def get_quarter_note_second(real_bpm: float):
"""Converts a beat to a 1/4 note in second"""
return 60 / real_bpm
def get_half_note_second(real_bpm: float):
"""Converts a beat to a 1/2 note in second"""
return 60 / real_bpm * 2
def get_whole_note_second(real_bpm: float):
"""Converts a beat to a 1/1 note in second"""
return 60 / real_bpm * 4
def get_multiplier(real_bpm: float) -> int:
"""Calculates the multiplier for the BPM"""
if real_bpm == 0:
raise Exception("BPM is 0")
multiplier = 1
result = 0
while result < 400:
result = real_bpm * multiplier
multiplier += 1
return multiplier - 2
def get_language_name(language: str) -> str:
"""Creates the language name from the language code"""
return langcodes.Language.make(language=language).display_name()
def create_ultrastar_txt_from_automation(
transcribed_data: list[TranscribedData],
note_numbers: list[int],
ultrastar_file_output: str,
ultrastar_class: UltrastarTxtValue,
real_bpm=120,
) -> None:
"""Creates an Ultrastar txt file from the automation data"""
print(
f"{ULTRASINGER_HEAD} Creating {ultrastar_file_output} from transcription."
)
ultrastar_bpm = real_bpm_to_ultrastar_bpm(real_bpm)
multiplication = get_multiplier(ultrastar_bpm)
ultrastar_bpm = ultrastar_bpm * get_multiplier(ultrastar_bpm)
silence_split_duration = calculate_silent_beat_length(transcribed_data)
with open(ultrastar_file_output, "w", encoding=FILE_ENCODING) as file:
gap = transcribed_data[0].start
if version.parse(ultrastar_class.version) >= version.parse("1.0.0"):
file.write(f"#{UltrastarTxtTag.VERSION}:{ultrastar_class.version}\n"),
file.write(f"#{UltrastarTxtTag.ARTIST}:{ultrastar_class.artist}\n")
file.write(f"#{UltrastarTxtTag.TITLE}:{ultrastar_class.title}\n")
if ultrastar_class.year is not None:
file.write(f"#{UltrastarTxtTag.YEAR}:{ultrastar_class.year}\n")
if ultrastar_class.language is not None:
file.write(f"#{UltrastarTxtTag.LANGUAGE}:{get_language_name(ultrastar_class.language)}\n")
if ultrastar_class.genre:
file.write(f"#{UltrastarTxtTag.GENRE}:{ultrastar_class.genre}\n")
if ultrastar_class.cover is not None:
file.write(f"#{UltrastarTxtTag.COVER}:{ultrastar_class.cover}\n")
file.write(f"#{UltrastarTxtTag.MP3}:{ultrastar_class.mp3}\n")
if version.parse(ultrastar_class.version) >= version.parse("1.1.0"):
file.write(f"#{UltrastarTxtTag.AUDIO}:{ultrastar_class.audio}\n")
if ultrastar_class.vocals is not None:
file.write(f"#{UltrastarTxtTag.VOCALS}:{ultrastar_class.vocals}\n")
if ultrastar_class.instrumental is not None:
file.write(f"#{UltrastarTxtTag.INSTRUMENTAL}:{ultrastar_class.instrumental}\n")
if ultrastar_class.tags is not None:
file.write(f"#{UltrastarTxtTag.TAGS}:{ultrastar_class.tags}\n")
file.write(f"#{UltrastarTxtTag.VIDEO}:{ultrastar_class.video}\n")
file.write(f"#{UltrastarTxtTag.BPM}:{round(ultrastar_bpm, 2)}\n") # not the real BPM!
file.write(f"#{UltrastarTxtTag.GAP}:{int(gap * 1000)}\n")
file.write(f"#{UltrastarTxtTag.CREATOR}:{ultrastar_class.creator}\n")
file.write(f"#{UltrastarTxtTag.COMMENT}:{ultrastar_class.comment}\n")
# Write the singing part
previous_end_beat = 0
separated_word_silence = [] # This is a workaround for separated words that get his ends to far away
for i, data in enumerate(transcribed_data):
start_time = (data.start - gap) * multiplication
end_time = (
data.end - data.start
) * multiplication
start_beat = round(second_to_beat(start_time, real_bpm))
duration = round(second_to_beat(end_time, real_bpm))
# Fix the round issue, so the beats don’t overlap
start_beat = max(start_beat, previous_end_beat)
previous_end_beat = start_beat + duration
# Calculate the silence between the words
if i < len(transcribed_data) - 1:
silence = (transcribed_data[i + 1].start - data.end)
else:
silence = 0
# : 10 10 10 w
# ':' start midi part
# 'n1' start at real beat
# 'n2' duration at real beat
# 'n3' pitch where 0 == C4
# 'w' lyric
line = f"{UltrastarTxtNoteTypeTag.NORMAL} " \
f"{str(start_beat)} " \
f"{str(duration)} " \
f"{str(note_numbers[i])} " \
f"{data.word}\n"
file.write(line)
# detect silence between words
if not transcribed_data[i].is_word_end:
separated_word_silence.append(silence)
continue
if silence_split_duration != 0 and silence > silence_split_duration or any(
s > silence_split_duration for s in separated_word_silence) and i != len(transcribed_data) - 1:
# - 10
# '-' end of current sing part
# 'n1' show next at time in real beat
show_next = (
second_to_beat(data.end - gap, real_bpm)
* multiplication
)
linebreak = f"{UltrastarTxtTag.LINEBREAK} " \
f"{str(round(show_next))}\n"
file.write(linebreak)
separated_word_silence = []
file.write(f"{UltrastarTxtTag.FILE_END}")
def deviation(silence_parts):
"""Calculates the deviation of the silence parts"""
if len(silence_parts) < 5:
return 0
# Remove the longest part so the deviation is not that high
sorted_parts = sorted(silence_parts)
filtered_parts = [part for part in sorted_parts if part < sorted_parts[-1]]
sum_parts = sum(filtered_parts)
if sum_parts == 0:
return 0
mean = sum_parts / len(filtered_parts)
return mean
def calculate_silent_beat_length(transcribed_data: list[TranscribedData]):
print(f"{ULTRASINGER_HEAD} Calculating silence parts for linebreaks.")
silent_parts = []
for i, data in enumerate(transcribed_data):
if i < len(transcribed_data) - 1:
silent_parts.append(transcribed_data[i + 1].start - data.end)
return deviation(silent_parts)
def create_repitched_txt_from_ultrastar_data(
input_file: str, note_numbers: list[int], output_repitched_ultrastar: str
) -> None:
"""Creates a repitched ultrastar txt file from the original one"""
# todo: just add '_repitched' to input_file
print(
"{PRINT_ULTRASTAR} Creating repitched ultrastar txt -> {input_file}_repitch.txt"
)
# todo: to reader
with open(input_file, "r", encoding=FILE_ENCODING) as file:
txt = file.readlines()
i = 0
# todo: just add '_repitched' to input_file
with open(output_repitched_ultrastar, "w", encoding=FILE_ENCODING) as file:
for line in txt:
if line.startswith(f"{UltrastarTxtNoteTypeTag.NORMAL} "):
parts = re.findall(r"\S+|\s+", line)
# between are whitespaces
# [0] :
# [2] start beat
# [4] duration
# [6] pitch
# [8] word
parts[6] = str(note_numbers[i])
delimiter = ""
file.write(delimiter.join(parts))
i += 1
else:
file.write(line)
def add_score_to_ultrastar_txt(ultrastar_file_output: str, score: Score) -> None:
"""Adds the score to the ultrastar txt file"""
with open(ultrastar_file_output, "r", encoding=FILE_ENCODING) as file:
text = file.read()
text = text.split("\n")
for i, line in enumerate(text):
if line.startswith(f"#{UltrastarTxtTag.COMMENT}:"):
text[
i
] = f"{line} | Score: total: {score.score}, notes: {score.notes} line: {score.line_bonus}, golden: {score.golden}"
break
if line.startswith((
f"{UltrastarTxtNoteTypeTag.FREESTYLE} ",
f"{UltrastarTxtNoteTypeTag.NORMAL} ",
f"{UltrastarTxtNoteTypeTag.GOLDEN} ",
f"{UltrastarTxtNoteTypeTag.RAP} ",
f"{UltrastarTxtNoteTypeTag.RAP_GOLDEN} ")):
text.insert(
i,
f"#{UltrastarTxtTag.COMMENT}: UltraSinger [GitHub] | Score: total: {score.score}, notes: {score.notes} line: {score.line_bonus}, golden: {score.golden}",
)
break
text = "\n".join(text)
with open(ultrastar_file_output, "w", encoding=FILE_ENCODING) as file:
file.write(text)
class UltraStarWriter:
"""Docstring"""