Text-to-Audio / utils /data_utils.py
yuancwang
init
5548515
# Copyright (c) 2023 Amphion.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
import json
import os
import numpy as np
from scipy.interpolate import interp1d
from tqdm import tqdm
from sklearn.preprocessing import StandardScaler
def load_content_feature_path(meta_data, processed_dir, feat_dir):
utt2feat_path = {}
for utt_info in meta_data:
utt = utt_info["Dataset"] + "_" + utt_info["Uid"]
feat_path = os.path.join(
processed_dir, utt_info["Dataset"], feat_dir, f'{utt_info["Uid"]}.npy'
)
utt2feat_path[utt] = feat_path
return utt2feat_path
def load_source_content_feature_path(meta_data, feat_dir):
utt2feat_path = {}
for utt in meta_data:
feat_path = os.path.join(feat_dir, f"{utt}.npy")
utt2feat_path[utt] = feat_path
return utt2feat_path
def get_spk_map(spk2id_path, utt2spk_path):
utt2spk = {}
with open(spk2id_path, "r") as spk2id_file:
spk2id = json.load(spk2id_file)
with open(utt2spk_path, encoding="utf-8") as f:
for line in f.readlines():
utt, spk = line.strip().split("\t")
utt2spk[utt] = spk
return spk2id, utt2spk
def get_target_f0_median(f0_dir):
total_f0 = []
for utt in os.listdir(f0_dir):
if not utt.endswith(".npy"):
continue
f0_feat_path = os.path.join(f0_dir, utt)
f0 = np.load(f0_feat_path)
total_f0 += f0.tolist()
total_f0 = np.array(total_f0)
voiced_position = np.where(total_f0 != 0)
return np.median(total_f0[voiced_position])
def get_conversion_f0_factor(source_f0, target_median, source_median=None):
"""Align the median between source f0 and target f0
Note: Here we use multiplication, whose factor is target_median/source_median
Reference: Frequency and pitch interval
http://blog.ccyg.studio/article/be12c2ee-d47c-4098-9782-ca76da3035e4/
"""
if source_median is None:
voiced_position = np.where(source_f0 != 0)
source_median = np.median(source_f0[voiced_position])
factor = target_median / source_median
return source_median, factor
def transpose_key(frame_pitch, trans_key):
# Transpose by user's argument
print("Transpose key = {} ...\n".format(trans_key))
transed_pitch = frame_pitch * 2 ** (trans_key / 12)
return transed_pitch
def pitch_shift_to_target(frame_pitch, target_pitch_median, source_pitch_median=None):
# Loading F0 Base (median) and shift
source_pitch_median, factor = get_conversion_f0_factor(
frame_pitch, target_pitch_median, source_pitch_median
)
print(
"Auto transposing: source f0 median = {:.1f}, target f0 median = {:.1f}, factor = {:.2f}".format(
source_pitch_median, target_pitch_median, factor
)
)
transed_pitch = frame_pitch * factor
return transed_pitch
def load_frame_pitch(
meta_data,
processed_dir,
pitch_dir,
use_log_scale=False,
return_norm=False,
interoperate=False,
utt2spk=None,
):
utt2pitch = {}
utt2uv = {}
if utt2spk is None:
pitch_scaler = StandardScaler()
for utt_info in meta_data:
utt = utt_info["Dataset"] + "_" + utt_info["Uid"]
pitch_path = os.path.join(
processed_dir, utt_info["Dataset"], pitch_dir, f'{utt_info["Uid"]}.npy'
)
pitch = np.load(pitch_path)
assert len(pitch) > 0
uv = pitch != 0
utt2uv[utt] = uv
if use_log_scale:
nonzero_idxes = np.where(pitch != 0)[0]
pitch[nonzero_idxes] = np.log(pitch[nonzero_idxes])
utt2pitch[utt] = pitch
pitch_scaler.partial_fit(pitch.reshape(-1, 1))
mean, std = pitch_scaler.mean_[0], pitch_scaler.scale_[0]
if return_norm:
for utt_info in meta_data:
utt = utt_info["Dataset"] + "_" + utt_info["Uid"]
pitch = utt2pitch[utt]
normalized_pitch = (pitch - mean) / std
utt2pitch[utt] = normalized_pitch
pitch_statistic = {"mean": mean, "std": std}
else:
spk2utt = {}
pitch_statistic = []
for utt_info in meta_data:
utt = utt_info["Dataset"] + "_" + utt_info["Uid"]
if not utt2spk[utt] in spk2utt:
spk2utt[utt2spk[utt]] = []
spk2utt[utt2spk[utt]].append(utt)
for spk in spk2utt:
pitch_scaler = StandardScaler()
for utt in spk2utt[spk]:
dataset = utt.split("_")[0]
uid = "_".join(utt.split("_")[1:])
pitch_path = os.path.join(
processed_dir, dataset, pitch_dir, f"{uid}.npy"
)
pitch = np.load(pitch_path)
assert len(pitch) > 0
uv = pitch != 0
utt2uv[utt] = uv
if use_log_scale:
nonzero_idxes = np.where(pitch != 0)[0]
pitch[nonzero_idxes] = np.log(pitch[nonzero_idxes])
utt2pitch[utt] = pitch
pitch_scaler.partial_fit(pitch.reshape(-1, 1))
mean, std = pitch_scaler.mean_[0], pitch_scaler.scale_[0]
if return_norm:
for utt in spk2utt[spk]:
pitch = utt2pitch[utt]
normalized_pitch = (pitch - mean) / std
utt2pitch[utt] = normalized_pitch
pitch_statistic.append({"spk": spk, "mean": mean, "std": std})
return utt2pitch, utt2uv, pitch_statistic
# discard
def load_phone_pitch(
meta_data,
processed_dir,
pitch_dir,
utt2dur,
use_log_scale=False,
return_norm=False,
interoperate=True,
utt2spk=None,
):
print("Load Phone Pitch")
utt2pitch = {}
utt2uv = {}
if utt2spk is None:
pitch_scaler = StandardScaler()
for utt_info in tqdm(meta_data):
utt = utt_info["Dataset"] + "_" + utt_info["Uid"]
pitch_path = os.path.join(
processed_dir, utt_info["Dataset"], pitch_dir, f'{utt_info["Uid"]}.npy'
)
frame_pitch = np.load(pitch_path)
assert len(frame_pitch) > 0
uv = frame_pitch != 0
utt2uv[utt] = uv
phone_pitch = phone_average_pitch(frame_pitch, utt2dur[utt], interoperate)
if use_log_scale:
nonzero_idxes = np.where(phone_pitch != 0)[0]
phone_pitch[nonzero_idxes] = np.log(phone_pitch[nonzero_idxes])
utt2pitch[utt] = phone_pitch
pitch_scaler.partial_fit(remove_outlier(phone_pitch).reshape(-1, 1))
mean, std = pitch_scaler.mean_[0], pitch_scaler.scale_[0]
max_value = np.finfo(np.float64).min
min_value = np.finfo(np.float64).max
if return_norm:
for utt_info in meta_data:
utt = utt_info["Dataset"] + "_" + utt_info["Uid"]
pitch = utt2pitch[utt]
normalized_pitch = (pitch - mean) / std
max_value = max(max_value, max(normalized_pitch))
min_value = min(min_value, min(normalized_pitch))
utt2pitch[utt] = normalized_pitch
phone_normalized_pitch_path = os.path.join(
processed_dir,
utt_info["Dataset"],
"phone_level_" + pitch_dir,
f'{utt_info["Uid"]}.npy',
)
pitch_statistic = {
"mean": mean,
"std": std,
"min_value": min_value,
"max_value": max_value,
}
else:
spk2utt = {}
pitch_statistic = []
for utt_info in tqdm(meta_data):
utt = utt_info["Dataset"] + "_" + utt_info["Uid"]
if not utt2spk[utt] in spk2utt:
spk2utt[utt2spk[utt]] = []
spk2utt[utt2spk[utt]].append(utt)
for spk in spk2utt:
pitch_scaler = StandardScaler()
for utt in spk2utt[spk]:
dataset = utt.split("_")[0]
uid = "_".join(utt.split("_")[1:])
pitch_path = os.path.join(
processed_dir, dataset, pitch_dir, f"{uid}.npy"
)
frame_pitch = np.load(pitch_path)
assert len(frame_pitch) > 0
uv = frame_pitch != 0
utt2uv[utt] = uv
phone_pitch = phone_average_pitch(
frame_pitch, utt2dur[utt], interoperate
)
if use_log_scale:
nonzero_idxes = np.where(phone_pitch != 0)[0]
phone_pitch[nonzero_idxes] = np.log(phone_pitch[nonzero_idxes])
utt2pitch[utt] = phone_pitch
pitch_scaler.partial_fit(remove_outlier(phone_pitch).reshape(-1, 1))
mean, std = pitch_scaler.mean_[0], pitch_scaler.scale_[0]
max_value = np.finfo(np.float64).min
min_value = np.finfo(np.float64).max
if return_norm:
for utt in spk2utt[spk]:
pitch = utt2pitch[utt]
normalized_pitch = (pitch - mean) / std
max_value = max(max_value, max(normalized_pitch))
min_value = min(min_value, min(normalized_pitch))
utt2pitch[utt] = normalized_pitch
pitch_statistic.append(
{
"spk": spk,
"mean": mean,
"std": std,
"min_value": min_value,
"max_value": max_value,
}
)
return utt2pitch, utt2uv, pitch_statistic
def phone_average_pitch(pitch, dur, interoperate=False):
pos = 0
if interoperate:
nonzero_ids = np.where(pitch != 0)[0]
interp_fn = interp1d(
nonzero_ids,
pitch[nonzero_ids],
fill_value=(pitch[nonzero_ids[0]], pitch[nonzero_ids[-1]]),
bounds_error=False,
)
pitch = interp_fn(np.arange(0, len(pitch)))
phone_pitch = np.zeros(len(dur))
for i, d in enumerate(dur):
d = int(d)
if d > 0 and pos < len(pitch):
phone_pitch[i] = np.mean(pitch[pos : pos + d])
else:
phone_pitch[i] = 0
pos += d
return phone_pitch
def load_energy(
meta_data,
processed_dir,
energy_dir,
use_log_scale=False,
return_norm=False,
utt2spk=None,
):
utt2energy = {}
if utt2spk is None:
for utt_info in meta_data:
utt = utt_info["Dataset"] + "_" + utt_info["Uid"]
energy_path = os.path.join(
processed_dir, utt_info["Dataset"], energy_dir, f'{utt_info["Uid"]}.npy'
)
if not os.path.exists(energy_path):
continue
energy = np.load(energy_path)
assert len(energy) > 0
if use_log_scale:
nonzero_idxes = np.where(energy != 0)[0]
energy[nonzero_idxes] = np.log(energy[nonzero_idxes])
utt2energy[utt] = energy
if return_norm:
with open(
os.path.join(
processed_dir, utt_info["Dataset"], energy_dir, "statistics.json"
)
) as f:
stats = json.load(f)
mean, std = (
stats[utt_info["Dataset"] + "_" + utt_info["Singer"]][
"voiced_positions"
]["mean"],
stats["LJSpeech_LJSpeech"]["voiced_positions"]["std"],
)
for utt in utt2energy.keys():
energy = utt2energy[utt]
normalized_energy = (energy - mean) / std
utt2energy[utt] = normalized_energy
energy_statistic = {"mean": mean, "std": std}
else:
spk2utt = {}
energy_statistic = []
for utt_info in meta_data:
utt = utt_info["Dataset"] + "_" + utt_info["Uid"]
if not utt2spk[utt] in spk2utt:
spk2utt[utt2spk[utt]] = []
spk2utt[utt2spk[utt]].append(utt)
for spk in spk2utt:
energy_scaler = StandardScaler()
for utt in spk2utt[spk]:
dataset = utt.split("_")[0]
uid = "_".join(utt.split("_")[1:])
energy_path = os.path.join(
processed_dir, dataset, energy_dir, f"{uid}.npy"
)
if not os.path.exists(energy_path):
continue
frame_energy = np.load(energy_path)
assert len(frame_energy) > 0
if use_log_scale:
nonzero_idxes = np.where(frame_energy != 0)[0]
frame_energy[nonzero_idxes] = np.log(frame_energy[nonzero_idxes])
utt2energy[utt] = frame_energy
energy_scaler.partial_fit(frame_energy.reshape(-1, 1))
mean, std = energy_scaler.mean_[0], energy_scaler.scale_[0]
if return_norm:
for utt in spk2utt[spk]:
energy = utt2energy[utt]
normalized_energy = (energy - mean) / std
utt2energy[utt] = normalized_energy
energy_statistic.append({"spk": spk, "mean": mean, "std": std})
return utt2energy, energy_statistic
def load_frame_energy(
meta_data,
processed_dir,
energy_dir,
use_log_scale=False,
return_norm=False,
interoperate=False,
utt2spk=None,
):
utt2energy = {}
if utt2spk is None:
energy_scaler = StandardScaler()
for utt_info in meta_data:
utt = utt_info["Dataset"] + "_" + utt_info["Uid"]
energy_path = os.path.join(
processed_dir, utt_info["Dataset"], energy_dir, f'{utt_info["Uid"]}.npy'
)
frame_energy = np.load(energy_path)
assert len(frame_energy) > 0
if use_log_scale:
nonzero_idxes = np.where(frame_energy != 0)[0]
frame_energy[nonzero_idxes] = np.log(frame_energy[nonzero_idxes])
utt2energy[utt] = frame_energy
energy_scaler.partial_fit(frame_energy.reshape(-1, 1))
mean, std = energy_scaler.mean_[0], energy_scaler.scale_[0]
if return_norm:
for utt_info in meta_data:
utt = utt_info["Dataset"] + "_" + utt_info["Uid"]
energy = utt2energy[utt]
normalized_energy = (energy - mean) / std
utt2energy[utt] = normalized_energy
energy_statistic = {"mean": mean, "std": std}
else:
spk2utt = {}
energy_statistic = []
for utt_info in meta_data:
utt = utt_info["Dataset"] + "_" + utt_info["Uid"]
if not utt2spk[utt] in spk2utt:
spk2utt[utt2spk[utt]] = []
spk2utt[utt2spk[utt]].append(utt)
for spk in spk2utt:
energy_scaler = StandardScaler()
for utt in spk2utt[spk]:
dataset = utt.split("_")[0]
uid = "_".join(utt.split("_")[1:])
energy_path = os.path.join(
processed_dir, dataset, energy_dir, f"{uid}.npy"
)
frame_energy = np.load(energy_path)
assert len(frame_energy) > 0
if use_log_scale:
nonzero_idxes = np.where(frame_energy != 0)[0]
frame_energy[nonzero_idxes] = np.log(frame_energy[nonzero_idxes])
utt2energy[utt] = frame_energy
energy_scaler.partial_fit(frame_energy.reshape(-1, 1))
mean, std = energy_scaler.mean_[0], energy_scaler.scale_[0]
if return_norm:
for utt in spk2utt[spk]:
energy = utt2energy[utt]
normalized_energy = (energy - mean) / std
utt2energy[utt] = normalized_energy
energy_statistic.append({"spk": spk, "mean": mean, "std": std})
return utt2energy, energy_statistic
def align_length(feature, target_len, pad_value=0.0):
feature_len = feature.shape[-1]
dim = len(feature.shape)
# align 1-D data
if dim == 2:
if target_len > feature_len:
feature = np.pad(
feature,
((0, 0), (0, target_len - feature_len)),
constant_values=pad_value,
)
else:
feature = feature[:, :target_len]
# align 2-D data
elif dim == 1:
if target_len > feature_len:
feature = np.pad(
feature, (0, target_len - feature_len), constant_values=pad_value
)
else:
feature = feature[:target_len]
else:
raise NotImplementedError
return feature
def align_whisper_feauture_length(
feature, target_len, fast_mapping=True, source_hop=320, target_hop=256
):
factor = np.gcd(source_hop, target_hop)
source_hop //= factor
target_hop //= factor
# print(
# "Mapping source's {} frames => target's {} frames".format(
# target_hop, source_hop
# )
# )
max_source_len = 1500
target_len = min(target_len, max_source_len * source_hop // target_hop)
width = feature.shape[-1]
if fast_mapping:
source_len = target_len * target_hop // source_hop + 1
feature = feature[:source_len]
else:
source_len = max_source_len
# const ~= target_len * target_hop
const = source_len * source_hop // target_hop * target_hop
# (source_len * source_hop, dim)
up_sampling_feats = np.repeat(feature, source_hop, axis=0)
# (const, dim) -> (const/target_hop, target_hop, dim) -> (const/target_hop, dim)
down_sampling_feats = np.average(
up_sampling_feats[:const].reshape(-1, target_hop, width), axis=1
)
assert len(down_sampling_feats) >= target_len
# (target_len, dim)
feat = down_sampling_feats[:target_len]
return feat
def align_content_feature_length(feature, target_len, source_hop=320, target_hop=256):
factor = np.gcd(source_hop, target_hop)
source_hop //= factor
target_hop //= factor
# print(
# "Mapping source's {} frames => target's {} frames".format(
# target_hop, source_hop
# )
# )
# (source_len, 256)
source_len, width = feature.shape
# const ~= target_len * target_hop
const = source_len * source_hop // target_hop * target_hop
# (source_len * source_hop, dim)
up_sampling_feats = np.repeat(feature, source_hop, axis=0)
# (const, dim) -> (const/target_hop, target_hop, dim) -> (const/target_hop, dim)
down_sampling_feats = np.average(
up_sampling_feats[:const].reshape(-1, target_hop, width), axis=1
)
err = abs(target_len - len(down_sampling_feats))
if err > 4: ## why 4 not 3?
print("target_len:", target_len)
print("raw feature:", feature.shape)
print("up_sampling:", up_sampling_feats.shape)
print("down_sampling_feats:", down_sampling_feats.shape)
exit()
if len(down_sampling_feats) < target_len:
# (1, dim) -> (err, dim)
end = down_sampling_feats[-1][None, :].repeat(err, axis=0)
down_sampling_feats = np.concatenate([down_sampling_feats, end], axis=0)
# (target_len, dim)
feat = down_sampling_feats[:target_len]
return feat
def remove_outlier(values):
values = np.array(values)
p25 = np.percentile(values, 25)
p75 = np.percentile(values, 75)
lower = p25 - 1.5 * (p75 - p25)
upper = p75 + 1.5 * (p75 - p25)
normal_indices = np.logical_and(values > lower, values < upper)
return values[normal_indices]