CloudBuilder/CloudBuilder.AI/Utility/SoVITSTtsVoiceRandom.cs
owenchen 996bb243df ow
2026-06-22 17:02:13 +08:00

245 lines
10 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using CloudBuilder.AI.Policy;
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.Utility
{
// 音色条目结构
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();
}
}