This repository has been archived by the owner on Nov 24, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ust2hts.py
202 lines (176 loc) · 6.76 KB
/
ust2hts.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
#! /usr/bin/env python3
# coding: utf-8
# Copyright (c) 2020 oatsu
"""
USTファイルをHTSフルラベルに変換する。
コンテキストは最低限。
Musicオブジェクトを経由せずにOneLineオブジェクトを直接生成していく。
適用するコンテキストは
[jp_qst_crazy_mono_005.hed](https://github.com/oatsu-gh/nnsvs-custom-stripts/tree/master/hed)
を参考とする。
対象
CQS:
p11, p12, p13, p14
a1, a2, a3,
b1, b2, b3,
c1, c2, c3,
d1, d2, d3, (d5), d6, d7, d8,
e1, e2, e3, (e5), e6, e7, e8, e57, e58,
f1, f2, f3, (f5), f6, f7, f8
QS:
p1, p3, p4, p5
d2, d3, e2, e3 はスケール判定が必要なため、
d2, e2, f2 には USTのNoteNumのmod12を代入し、
# d3, e3, f3 はSinsyで出現しえない値である '99' を代入する。
d3, e3, f3 には 'xx' を代入する。休符の学習データ引っ張ってきそうな気はする。
"""
from os.path import basename, splitext
import utaupy as up
from hts2json import hts2json
# from pprint import pprint
def language_independent_phoneme_identity(phoneme):
"""
音素の分類 (c, v, p, s)
"""
vowels = ('a', 'i', 'u', 'e', 'o', 'N', 'A', 'I', 'U', 'E', 'O')
breaks = ('br', 'cl')
if phoneme in vowels:
return 'v'
if phoneme in breaks:
return 'b'
if phoneme == 'pau':
return 'p'
if phoneme == 'sil':
return 's'
# どれも当てはまらない場合は子音とみなす
return 'c'
def pitch_difference_of_notes(note_1, note_2):
"""
ノートの音程差を、フルラベルに合うようなフォーマットで返す。
note_1.lyric が UST上の歌詞であることに注意
"""
pauses = ('sil', 'pau', 'R', 'r')
if note_1.lyric in pauses or note_2.lyric in pauses:
return 'xx'
# いずれも休符ではないとき
pitch_difference = note_1.notenum - note_2.notenum
# 正負の符号をつける
if pitch_difference >= 0:
return f'p{pitch_difference}'
return str(pitch_difference).replace('-', 'm')
def convert_ustobj_to_htsfulllabelobj(
ust: up.ust.Ust, d_table: dict, key_of_the_note: int = 120) -> up.hts.HTSFullLabel:
"""
Ustオブジェクトをノートごとに処理して、HTS用に変換する。
日本語歌詞を想定するため、音節数は1とする。促音に注意。
ust: Ustオブジェクト
d_table: 日本語→ローマ字変換テーブル
key_of_the_note:
曲のキーだが、USTからは判定できない。
12の倍数かつSinsyではあり得ない120を設定している。
Sinsyでは 0 ~ 11 または 'xx' である。
"""
# TODO: 促音が入っている(音節が複数になる)ときの処理を追加
# e3 に対応する数値で、曲ごとに決まっている。スケール判定の結果っぽい。
full_label = up.hts.HTSFullLabel()
t_start = 0
t_end = 0
notes = ust.notes
for idx_n, note in enumerate(notes):
t_end = t_start + (note.length_ms * 10000)
lyric = note.lyric
notenum = note.notenum
if lyric.endswith('っ'):
phonemes = d_table[lyric[:-1]].append('cl')
try:
phonemes = d_table[lyric]
except KeyError as ke:
phonemes = lyric.split()
print('KeyError:', ke)
for idx_ph, phoneme in enumerate(phonemes):
ol = up.hts.OneLine()
# 時刻の処理
ol.start = int(t_start)
ol.end = int(t_end)
# oneline.p: 音素の処理-------------
temp_p = ol.p
# 音素分類
temp_p[0] = language_independent_phoneme_identity(phoneme)
# 音素記号
temp_p[3] = phoneme
# 音素の音節内位置
temp_p[11] = idx_ph + 1
temp_p[12] = len(phonemes) - idx_ph
temp_p[13] = 'xx'
temp_p[14] = 'xx' if idx_ph == 0 else len(phonemes) - idx_ph - 1
ol.p = temp_p
# oneline.b: 音節の処理-------------
# 音節内音素数
ol.b[0] = len(phonemes)
# ノート内音節位置
ol.b[1] = 1
ol.b[2] = 1
# oneline.e: ノートの処理-----------
# 音程C0-G9
ol.e[0] = up.ust.notenum_as_abc(notenum)
# relative pitch
ol.e[1] = (notenum - key_of_the_note) % 12
# key
ol.e[2] = key_of_the_note
# テンポ
ol.e[4] = int(note.tempo)
# ノート内音節数
ol.e[5] = 1
# ノート長(0.01s)
ol.e[6] = int(note.length_ms // 10)
# ノート長(32分音符の1/3, つまり96分音符いくつぶんか, 4分音符なら8*3=24)
# utaupy.ust.Note.length は4分音符で480なので、20で割ればよい。
ol.e[7] = note.length // 20
# ピッチ変化①
try:
ol.e[56] = pitch_difference_of_notes(notes[idx_n - 1], note)
except IndexError:
ol.e[56] = 'xx'
# ピッチ変化②
try:
ol.e[57] = pitch_difference_of_notes(notes[idx_n + 1], note)
except IndexError:
ol.e[57] = 'xx'
full_label.append(ol)
t_start = t_end
return full_label
def main():
"""
USTファイルをLABファイルおよびJSONファイルに変換する。
"""
path_table = 'dic/kana2romaji_utf-8_for_oto2lab .table'
d_table = up.table.load(path_table, encoding='sjis')
path_ust = input('path_ust: ').strip('"')
path_hts = 'test/' + splitext(basename(path_ust))[0] + '_ust2hts.lab'
path_json = 'test/' + splitext(basename(path_ust))[0] + '_ust2hts.json'
ust = up.ust.load(path_ust)
# Ust → HTSFullLabel
full_label = convert_ustobj_to_htsfulllabelobj(ust, d_table)
# HTSFullLabel中の重複データを削除して整理
full_label.generate_songobj()
full_label.fill_contexts_from_songobj()
# 音素数などの整合性をチェック
full_label.song.check()
# ファイル出力
full_label.write(path_hts, strict_hts_style=True)
hts2json(path_hts, path_json)
def test():
"""
LABファイルを読み取ってLABファイルとJSONファイルに再出力する。
"""
path_in = input('path_in: ').strip('"')
hts2json(path_in, path_in.replace('.lab', '.json'))
path_out = path_in.replace('.lab', '_out.lab')
full_label = up.hts.load(path_in)
full_label.song.check()
full_label.write(path_out, strict_hts_style=True)
hts2json(path_out, path_out.replace('.lab', '.json'))
if __name__ == '__main__':
main()
test()
input('press enter')