340 lines
15 KiB
C#
340 lines
15 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.ComponentModel;
|
||
using System.Linq;
|
||
using System.Reflection;
|
||
using System.Text;
|
||
using System.Threading.Tasks;
|
||
|
||
namespace CloudBuilder.AI.Policy
|
||
{
|
||
public enum SoVITSTtsVoice
|
||
{
|
||
[System.ComponentModel.Description("老年人(男性)-1-0")]
|
||
OLD_MAN1,
|
||
[System.ComponentModel.Description("老年人(男性)-2-1-80")]
|
||
OLD_MAN2,
|
||
[System.ComponentModel.Description("老年人(男性)-3-2")]
|
||
OLD_MAN3,
|
||
[System.ComponentModel.Description("老年人(男性)-4-3")]
|
||
OLD_MAN4,
|
||
[System.ComponentModel.Description("老年人(男性)-5-4-80")]
|
||
OLD_MAN5,
|
||
[System.ComponentModel.Description("老年人(男性)-6-5")]
|
||
OLD_MAN6,
|
||
[System.ComponentModel.Description("老年人(男性)-7-6")]
|
||
OLD_MAN7,
|
||
[System.ComponentModel.Description("男中年(男性)-1-7")]
|
||
MIDDLE_AGED_MAN1,
|
||
[System.ComponentModel.Description("男中年(男性)-2-8")]
|
||
MIDDLE_AGED_MAN2,
|
||
[System.ComponentModel.Description("男中年(男性)-3-9-80")]
|
||
MIDDLE_AGED_MAN3,
|
||
[System.ComponentModel.Description("男中年(男性)-4-10")]
|
||
MIDDLE_AGED_MAN4,
|
||
[System.ComponentModel.Description("男中年(男性)-5-11-80")]
|
||
MIDDLE_AGED_MAN5,
|
||
[System.ComponentModel.Description("男中年(男性)-6-12")]
|
||
MIDDLE_AGED_MAN6,
|
||
[System.ComponentModel.Description("男中年(男性)-7-13-80")]
|
||
MIDDLE_AGED_MAN7,
|
||
[System.ComponentModel.Description("男青年(男性)-1-14")]
|
||
YOUNG_MALE1,
|
||
[System.ComponentModel.Description("男青年(男性)-2-15-80")]
|
||
YOUNG_MALE2,
|
||
[System.ComponentModel.Description("男青年(男性)-3-16")]
|
||
YOUNG_MALE3,
|
||
[System.ComponentModel.Description("男青年(男性)-4-17")]
|
||
YOUNG_MALE4,
|
||
[System.ComponentModel.Description("男青年(男性)-5-18")]
|
||
YOUNG_MALE5,
|
||
[System.ComponentModel.Description("男青年(男性)-6-19-0")]
|
||
YOUNG_MALE6,
|
||
[System.ComponentModel.Description("男青年(男性)-7-20-80")]
|
||
YOUNG_MALE7,
|
||
[System.ComponentModel.Description("男儿童(男性)-1-21-80")]
|
||
MALE_CHILD1,
|
||
[System.ComponentModel.Description("男儿童(男性)-2-22")]
|
||
MALE_CHILD2,
|
||
[System.ComponentModel.Description("男儿童(男性)-3-23-80")]
|
||
MALE_CHILD3,
|
||
[System.ComponentModel.Description("男儿童(男性)-4-24")]
|
||
MALE_CHILD4,
|
||
[System.ComponentModel.Description("老年人(女性)-1-25-80")]
|
||
ELDERLY_FEMALE1,
|
||
[System.ComponentModel.Description("老年人(女性)-2-26")]
|
||
ELDERLY_FEMALE2,
|
||
[System.ComponentModel.Description("老年人(女性)-3-27-80")]
|
||
ELDERLY_FEMALE3,
|
||
[System.ComponentModel.Description("女中年(女性)-1-28")]
|
||
MIDDLE_AGED_FEMALE1,
|
||
[System.ComponentModel.Description("女中年(女性)-2-29")]
|
||
MIDDLE_AGED_FEMALE2,
|
||
[System.ComponentModel.Description("女中年(女性)-3-30-80")]
|
||
MIDDLE_AGED_FEMALE3,
|
||
[System.ComponentModel.Description("女中年(女性)-4-31")]
|
||
MIDDLE_AGED_FEMALE4,
|
||
[System.ComponentModel.Description("女中年(女性)-5-32-80")]
|
||
MIDDLE_AGED_FEMALE5,
|
||
[System.ComponentModel.Description("女中年(女性)-6-33")]
|
||
MIDDLE_AGED_FEMALE6,
|
||
[System.ComponentModel.Description("女中年(女性)-7-34")]
|
||
MIDDLE_AGED_FEMALE7,
|
||
[System.ComponentModel.Description("女青年(女性)-1-35-80")]
|
||
YOUNG_FEMALE1,
|
||
[System.ComponentModel.Description("女青年(女性)-2-36")]
|
||
YOUNG_FEMALE2,
|
||
[System.ComponentModel.Description("女青年(女性)-3-37")]
|
||
YOUNG_FEMALE3,
|
||
[System.ComponentModel.Description("女青年(女性)-4-38")]
|
||
YOUNG_FEMALE4,
|
||
[System.ComponentModel.Description("女青年(女性)-5-39")]
|
||
YOUNG_FEMALE5,
|
||
[System.ComponentModel.Description("女青年(女性)-6-40")]
|
||
YOUNG_FEMALE6,
|
||
[System.ComponentModel.Description("女青年(女性)-7-41-80")]
|
||
YOUNG_FEMALE7,
|
||
[System.ComponentModel.Description("女儿童(女性)-1-42-80")]
|
||
FEMALE_CHILD1,
|
||
[System.ComponentModel.Description("女儿童(女性)-2-43")]
|
||
FEMALE_CHILD2,
|
||
[System.ComponentModel.Description("女儿童(女性)-3-44")]
|
||
FEMALE_CHILD3,
|
||
[System.ComponentModel.Description("女儿童(女性)-4-45-80")]
|
||
FEMALE_CHILD4,
|
||
}
|
||
|
||
// 音色条目结构
|
||
public class VoiceEntry
|
||
{
|
||
public SoVITSTtsVoice Voice { get; set; }
|
||
public string AgeGroup { get; set; } // "老年人", "中年", "青年", "儿童"
|
||
public string Gender { get; set; } // "男性", "女性"
|
||
public int Weight { get; set; } // 权重,默认为 50
|
||
}
|
||
/// <summary>
|
||
/// SoVITS 音色随机工具,根据枚举描述中的年龄段、性别和权重进行加权随机
|
||
/// </summary>
|
||
public static class SoVITSTtsVoiceRandom
|
||
{
|
||
// 所有音色数据(只读,线程安全)
|
||
private static readonly IReadOnlyList<VoiceEntry> _allEntries;
|
||
|
||
// 预分组缓存,提高效率
|
||
private static readonly IReadOnlyDictionary<string, IReadOnlyList<VoiceEntry>> _byGender;
|
||
private static readonly IReadOnlyDictionary<string, IReadOnlyList<VoiceEntry>> _byAge;
|
||
private static readonly IReadOnlyDictionary<(string Gender, string Age), IReadOnlyList<VoiceEntry>> _byGenderAndAge;
|
||
|
||
private static readonly Random _random = new Random();
|
||
|
||
static SoVITSTtsVoiceRandom()
|
||
{
|
||
// 枚举所有 SoVITSTtsVoice 值
|
||
var enumType = typeof(SoVITSTtsVoice);
|
||
var values = Enum.GetValues(enumType).Cast<SoVITSTtsVoice>();
|
||
|
||
var entries = new List<VoiceEntry>();
|
||
|
||
foreach (var voice in values)
|
||
{
|
||
var field = enumType.GetField(voice.ToString());
|
||
var descAttr = field?.GetCustomAttribute<DescriptionAttribute>();
|
||
if (descAttr == null) continue;
|
||
|
||
string desc = descAttr.Description;
|
||
// 解析格式:年龄段(性别)-数字-数字-权重? 如 "老年人(男性)-2-1-80"
|
||
// 提取年龄段和性别
|
||
int ageEnd = desc.IndexOf('(');
|
||
int genderEnd = desc.IndexOf(')');
|
||
if (ageEnd < 0 || genderEnd < 0 || genderEnd <= ageEnd) continue;
|
||
|
||
string ageGroup = desc.Substring(0, ageEnd).Trim();
|
||
string gender = desc.Substring(ageEnd + 1, genderEnd - ageEnd - 1).Trim();
|
||
|
||
// 提取权重(最后一个 '-' 后面的数字,如果有)
|
||
int weight = 50; // 默认权重
|
||
int lastDash = desc.LastIndexOf('-');
|
||
if (lastDash >= 0 && lastDash < desc.Length - 1)
|
||
{
|
||
string weightStr = desc.Substring(lastDash + 1);
|
||
if (int.TryParse(weightStr, out int parsedWeight))
|
||
{
|
||
weight = parsedWeight;
|
||
}
|
||
}
|
||
|
||
entries.Add(new VoiceEntry
|
||
{
|
||
Voice = voice,
|
||
AgeGroup = ageGroup,
|
||
Gender = gender,
|
||
Weight = weight
|
||
});
|
||
}
|
||
|
||
_allEntries = entries.AsReadOnly();
|
||
|
||
// 建立分组字典
|
||
_byGender = _allEntries
|
||
.GroupBy(e => e.Gender)
|
||
.ToDictionary(g => g.Key, g => (IReadOnlyList<VoiceEntry>)g.ToList().AsReadOnly());
|
||
|
||
_byAge = _allEntries
|
||
.GroupBy(e => e.AgeGroup)
|
||
.ToDictionary(g => g.Key, g => (IReadOnlyList<VoiceEntry>)g.ToList().AsReadOnly());
|
||
|
||
_byGenderAndAge = _allEntries
|
||
.GroupBy(e => (e.Gender, e.AgeGroup))
|
||
.ToDictionary(g => g.Key, g => (IReadOnlyList<VoiceEntry>)g.ToList().AsReadOnly());
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从指定列表中按权重随机选择一个音色
|
||
/// </summary>
|
||
private static SoVITSTtsVoice PickRandom(IReadOnlyList<VoiceEntry> entries)
|
||
{
|
||
if (entries == null || entries.Count == 0)
|
||
throw new InvalidOperationException("没有可用的音色");
|
||
|
||
// 计算总权重
|
||
int totalWeight = entries.Sum(e => e.Weight);
|
||
if (totalWeight <= 0)
|
||
return entries[_random.Next(entries.Count)].Voice; // 所有权重为0时退化为均匀随机
|
||
|
||
int roll = _random.Next(totalWeight);
|
||
int cumulative = 0;
|
||
foreach (var entry in entries)
|
||
{
|
||
cumulative += entry.Weight;
|
||
if (roll < cumulative)
|
||
return entry.Voice;
|
||
}
|
||
|
||
// 保险
|
||
return entries.Last().Voice;
|
||
}
|
||
|
||
// -------------------- 公开随机方法 --------------------
|
||
|
||
/// <summary>
|
||
/// 从所有音色中随机(加权)
|
||
/// </summary>
|
||
public static SoVITSTtsVoice GetRandom()
|
||
{
|
||
return PickRandom(_allEntries);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 随机男性音色(加权)
|
||
/// </summary>
|
||
public static SoVITSTtsVoice GetRandomMale()
|
||
{
|
||
if (_byGender.TryGetValue("男性", out var list))
|
||
return PickRandom(list);
|
||
throw new InvalidOperationException("没有男性音色");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 随机女性音色(加权)
|
||
/// </summary>
|
||
public static SoVITSTtsVoice GetRandomFemale()
|
||
{
|
||
if (_byGender.TryGetValue("女性", out var list))
|
||
return PickRandom(list);
|
||
throw new InvalidOperationException("没有女性音色");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 随机指定性别的音色(加权)
|
||
/// </summary>
|
||
public static SoVITSTtsVoice GetRandomByGender(string gender)
|
||
{
|
||
if (_byGender.TryGetValue(gender, out var list))
|
||
return PickRandom(list);
|
||
throw new ArgumentException($"未知性别: {gender}");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 随机指定年龄段的音色(加权)
|
||
/// </summary>
|
||
public static SoVITSTtsVoice GetRandomByAge(string ageGroup)
|
||
{
|
||
if (_byAge.TryGetValue(ageGroup, out var list))
|
||
return PickRandom(list);
|
||
throw new ArgumentException($"未知年龄段: {ageGroup}");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 随机指定性别和年龄段的音色(加权)
|
||
/// </summary>
|
||
public static SoVITSTtsVoice GetRandomByGenderAndAge(string gender, string ageGroup)
|
||
{
|
||
var key = (gender, ageGroup);
|
||
if (_byGenderAndAge.TryGetValue(key, out var list))
|
||
return PickRandom(list);
|
||
throw new ArgumentException($"未找到性别 '{gender}' 和年龄段 '{ageGroup}' 的组合");
|
||
}
|
||
|
||
// -------------------- 常用组合快捷方法 --------------------
|
||
public static SoVITSTtsVoice GetRandomElderlyMale() => GetRandomByGenderAndAge("男性", "老年人");
|
||
public static SoVITSTtsVoice GetRandomElderlyFemale() => GetRandomByGenderAndAge("女性", "老年人");
|
||
public static SoVITSTtsVoice GetRandomMiddleAgedMale() => GetRandomByGenderAndAge("男性", "中年");
|
||
public static SoVITSTtsVoice GetRandomMiddleAgedFemale() => GetRandomByGenderAndAge("女性", "中年");
|
||
public static SoVITSTtsVoice GetRandomYoungMale() => GetRandomByGenderAndAge("男性", "青年");
|
||
public static SoVITSTtsVoice GetRandomYoungFemale() => GetRandomByGenderAndAge("女性", "青年");
|
||
public static SoVITSTtsVoice GetRandomChildMale() => GetRandomByGenderAndAge("男性", "儿童");
|
||
public static SoVITSTtsVoice GetRandomChildFemale() => GetRandomByGenderAndAge("女性", "儿童");
|
||
|
||
// -------------------- 排除指定音色的扩展(可选)--------------------
|
||
|
||
/// <summary>
|
||
/// 随机男性音色,排除某个指定音色(加权)
|
||
/// </summary>
|
||
public static SoVITSTtsVoice GetRandomMaleExclude(SoVITSTtsVoice exclude)
|
||
{
|
||
if (_byGender.TryGetValue("男性", out var list))
|
||
{
|
||
var filtered = list.Where(e => e.Voice != exclude).ToList();
|
||
if (filtered.Count == 0)
|
||
throw new InvalidOperationException("排除后没有可用的男性音色");
|
||
return PickRandom(filtered);
|
||
}
|
||
throw new InvalidOperationException("没有男性音色");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 随机女性音色,排除某个指定音色(加权)
|
||
/// </summary>
|
||
public static SoVITSTtsVoice GetRandomFemaleExclude(SoVITSTtsVoice exclude)
|
||
{
|
||
if (_byGender.TryGetValue("女性", out var list))
|
||
{
|
||
var filtered = list.Where(e => e.Voice != exclude).ToList();
|
||
if (filtered.Count == 0)
|
||
throw new InvalidOperationException("排除后没有可用的女性音色");
|
||
return PickRandom(filtered);
|
||
}
|
||
throw new InvalidOperationException("没有女性音色");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从所有音色中随机,排除某个指定音色(加权)
|
||
/// </summary>
|
||
public static SoVITSTtsVoice GetRandomExclude(SoVITSTtsVoice exclude)
|
||
{
|
||
var filtered = _allEntries.Where(e => e.Voice != exclude).ToList();
|
||
if (filtered.Count == 0)
|
||
throw new InvalidOperationException("排除后没有可用的音色");
|
||
return PickRandom(filtered);
|
||
}
|
||
|
||
// -------------------- 获取所有分组信息(便于调试)--------------------
|
||
|
||
public static IReadOnlyList<SoVITSTtsVoice> GetAllVoices() => _allEntries.Select(e => e.Voice).ToList().AsReadOnly();
|
||
public static IReadOnlyList<SoVITSTtsVoice> GetMaleVoices() => _byGender["男性"].Select(e => e.Voice).ToList().AsReadOnly();
|
||
public static IReadOnlyList<SoVITSTtsVoice> GetFemaleVoices() => _byGender["女性"].Select(e => e.Voice).ToList().AsReadOnly();
|
||
public static IReadOnlyList<SoVITSTtsVoice> GetVoicesByAge(string ageGroup) => _byAge[ageGroup].Select(e => e.Voice).ToList().AsReadOnly();
|
||
public static IReadOnlyList<SoVITSTtsVoice> GetVoicesByGenderAndAge(string gender, string ageGroup) => _byGenderAndAge[(gender, ageGroup)].Select(e => e.Voice).ToList().AsReadOnly();
|
||
}
|
||
}
|