Skip to content

Commit 889db5e

Browse files
committed
Create style_shifter.py
スタイルシフトの拡張機能として作成。動作確認済み。
1 parent 0406cd7 commit 889db5e

File tree

1 file changed

+141
-0
lines changed

1 file changed

+141
-0
lines changed

synthesis/extensions/style_shifter.py

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) 2022 oatsu
3+
"""
4+
USTの音高をずらして読み込んで、合成時にf0を本体の高さに戻すことで歌い癖をずらす。
5+
6+
1個のファイルでUST編集とf0編集ができるようにしたい。
7+
UST読み込んで加工する際に、各ノートに独自エントリを書き込む。
8+
USTにの [#SETTING] にすでに独自エントリがある場合はf0ファイルを編集する。
9+
"""
10+
import re
11+
from argparse import ArgumentParser
12+
from copy import copy
13+
from math import log2
14+
from pprint import pprint
15+
16+
import utaupy
17+
18+
STYLE_SHIFT_FLAG_PATTERN = re.compile(r'S(\d+|\+\d+|-\d+)')
19+
20+
21+
def shift_ust_notes(ust) -> utaupy.ust.Ust:
22+
"""フラグに基づいてUST内のノート番号をずらし、その分を独自エントリに追加する。
23+
"""
24+
ust = copy(ust)
25+
key = '$EnunuStyleShift'
26+
ust.setting[key] = True
27+
for note in ust.notes:
28+
# フラグ内のスタイルシフトのパラメータを取得する
29+
style_shift = re.search(STYLE_SHIFT_FLAG_PATTERN, note.flags)
30+
# フラグにスタイルシフトのパラメータがあるとき
31+
if style_shift is not None:
32+
# フルラベルにするときの不具合の原因にならないように、フラグのスタイルシフト部分を削除する。
33+
note.flags = note.flags.replace(style_shift.group(), '')
34+
# 数値部分を取り出す
35+
style_shift_amount = int(style_shift.group(1))
36+
# スタイルシフト設定値の分だけノートの音高を下げる。
37+
note.notenum += int(style_shift_amount)
38+
# フラグのスタイルシフト値を独自エントリとしてノートに登録する。
39+
note[key] = '{:+}'.format(style_shift_amount)
40+
else:
41+
note[key] = 0
42+
43+
return ust
44+
45+
46+
def shift_f0(ust, full_timing, f0_list: list) -> list:
47+
"""f0をいい感じに編集する
48+
"""
49+
ust_notes = ust.notes
50+
hts_notes = full_timing.song.all_notes
51+
# ノート数が一致することを確認しておく
52+
if len(ust_notes) != len(hts_notes):
53+
raise ValueError(
54+
f'USTのノート数({len(ust_notes)}) と フルラベルのノート数({len(hts_notes)}) が一致していません。')
55+
56+
# 各ノートのf0開始スライスと終了スライス
57+
f0_point_slices = [
58+
(round(note.start / 50000), round(note.end / 50000)) for note in hts_notes]
59+
60+
# スタイルシフトの量をUSTのノートから取り出してリストにする
61+
style_shift_list = [
62+
int(note.get('$EnunuStyleShift', 0)) for note in ust_notes]
63+
64+
# 計算しやすいように対数に変換
65+
log2_f0_list = [log2(hz) if hz > 0 else 0 for hz in f0_list]
66+
67+
# f0のリストをノートごとに区切って2次元にする
68+
log2_f0_list_2d = [
69+
log2_f0_list[slice_start: slice_end] for (slice_start, slice_end) in f0_point_slices
70+
]
71+
72+
# ノート区切りごとにf0を調製して、新しいf0のリストを作る
73+
# このとき一番最初の開始時刻が0出ない時にf0点数が合わなくなるのを回避する。
74+
offset = round(hts_notes[0].start / 50000)
75+
new_log2_f0_list = log2_f0_list[0:offset]
76+
for f0_list_for_note, shift_amount in zip(log2_f0_list_2d, style_shift_list):
77+
delta_log2_f0 = shift_amount / (-12)
78+
new_log2_f0_list += [f0 + delta_log2_f0 if f0 >
79+
0 else 0 for f0 in f0_list_for_note]
80+
# 書き換えたやつ対数から元に戻す
81+
new_f0_list = [
82+
(2 ** log2_f0 if log2_f0 > 0 else 0) for log2_f0 in new_log2_f0_list]
83+
return new_f0_list
84+
85+
86+
def switch_mode(ust) -> str:
87+
"""どのタイミングで起動されたかを、USTから調べて動作モードを切り替える。
88+
"""
89+
if '$EnunuStyleShift' in ust.setting:
90+
return 'f0_editor'
91+
return 'ust_editor'
92+
93+
94+
def main():
95+
parser = ArgumentParser()
96+
parser.add_argument('--ust', help='選択部分のノートのUSTファイルのパス')
97+
parser.add_argument('--f0', help='f0の情報を持ったCSVファイルのパス')
98+
parser.add_argument('--full_timing', help='タイミング推定済みのフルラベルファイルのパス')
99+
100+
# 使わない引数は無視して、必要な情報だけ取り出す。
101+
args, _ = parser.parse_known_args()
102+
path_ust = args.ust
103+
104+
ust = utaupy.ust.load(path_ust)
105+
106+
# ust_editor として起動されたか、acoustic_editor として起動されたかを取得して動作切り替える
107+
mode = switch_mode(ust)
108+
109+
# ust編集のステップで実行された場合、ustの音高操作などをする。
110+
if mode == 'ust_editor':
111+
print('USTの音高を加工します。/ Shifting notes in UST.')
112+
ust = shift_ust_notes(ust)
113+
ust.write(path_ust)
114+
print('USTの音高を加工しました。/ Shifted notes in UST.')
115+
116+
# f0加工用に呼び出された場合、f0加工をする。
117+
elif mode == 'f0_editor':
118+
print('f0を加工します。/ Shifting f0.')
119+
# f0のファイルを読み取る
120+
path_f0 = args.f0
121+
with open(path_f0, 'r', encoding='utf-8') as f:
122+
f0_list = list(map(float, f.read().splitlines()))
123+
# フルラベルファイルを読み取る
124+
full_timing = utaupy.hts.load(args.full_timing)
125+
# f0を編集する
126+
new_f0_list = shift_f0(ust, full_timing, f0_list)
127+
new_f0_list = list(map(str, new_f0_list))
128+
s_f0 = '\n'.join(new_f0_list) + '\n'
129+
with open(path_f0, 'w', encoding='utf-8') as f:
130+
f.write(s_f0)
131+
print('f0を加工しました。/ Shifted f0.')
132+
133+
# それ以外
134+
else:
135+
raise Exception('動作モードを判別できませんでした。')
136+
137+
138+
if __name__ == "__main__":
139+
print('style_shifter.py (2022-09-24) -------------------------')
140+
main()
141+
print('-------------------------------------------------------')

0 commit comments

Comments
 (0)