ConfigUnit 模块负责 lessampler 的配置管理,包括全局配置、音源库配置和版本校验。该模块采用 INI 文件格式存储配置,使用 inicpp 库进行解析,并通过 SHA-1 校验和确保配置参数的一致性。
配置系统是歌声合成器的重要组成部分,因为不同的配置参数会产生不同的音频分析结果。如果用户更改配置后使用了旧配置生成的音频模型,会导致合成结果不准确。因此,ConfigUnit 模块设计了完善的版本校验机制来解决这个问题。
模块结构 ConfigUnit/ ├── lessConfigure.h/cpp # 配置参数结构体 ├── ConfigUnit.h/cpp # 全局配置加载 ├── ConfigVoiceBank.h/cpp # 音源库配置 ├── ConfigFileIO.h # 文件读写工具 └── SHA1.h/cpp # SHA-1 校验实现
lessConfigure 是配置参数的核心数据结构,定义了所有可配置的分析参数。
类定义 class lessConfigure {public : std::string get_version () ; public : enum class F0_MODE { F0_MODE_UNKNOWN = 0 , F0_MODE_HARVEST = 1 , F0_MODE_DIO = 2 , }; std::string version; bool debug_mode = false ; double model_amp = 0.85 ; double audio_model_frame_period = (1000.0 * 256 / 44100 ); bool custom_fft_size = false ; int fft_size = 1024 ; F0_MODE f0_mode = F0_MODE::F0_MODE_HARVEST; int f0_speed = 1 ; double f0_dio_floor = 40.0 ; double f0_harvest_floor = 40.0 ; double f0_cheap_trick_floor = 71.0 ; double f0_allow_range = 0.1 ; double ap_threshold = 0.10 ; public : static std::string get_f0_mode_str (F0_MODE f0_mode) ;private : std::string version_data; void make_ver () ; };
参数详解
参数
默认值
作用
影响
model_amp
0.85
AutoAMP 强度
音量归一化
audio_model_frame_period
~5.8ms
分析帧周期
时间分辨率
fft_size
1024
FFT 大小
频谱分辨率
f0_mode
HARVEST
F0 算法
分析精度/速度
f0_speed
1
DIO 降采样
DIO 处理速度
f0_dio_floor
40Hz
DIO F0 下限
低音分析范围
f0_harvest_floor
40Hz
Harvest F0 下限
低音分析范围
f0_cheap_trick_floor
71Hz
CheapTrick F0 下限
频谱包络质量
f0_allow_range
0.1
DIO 允许范围
F0 稳定性
ap_threshold
0.10
D4C 阈值
清浊音判定
make_ver() 版本校验生成 void lessConfigure::make_ver () { version_data = std::to_string (audio_model_frame_period) + std::to_string (fft_size) + std::to_string (model_amp) + std::to_string (f0_speed) + std::to_string (f0_dio_floor) + std::to_string (f0_harvest_floor) + std::to_string (f0_cheap_trick_floor) + std::to_string (f0_allow_range) + std::to_string (ap_threshold) + get_f0_mode_str (f0_mode); SHA1 checksum; checksum.update (version_data); version_data = checksum.final (); }
版本校验机制原理 :
参数串联 :将所有影响音频分析结果的参数连接成字符串
SHA-1 哈希 :生成 160 位(40 字符)的唯一校验码
模型绑定 :音频模型文件存储生成时的校验码
加载验证 :加载模型时比对校验码,确保配置一致
SHA1 类实现 SHA1 类提供了 SHA-1 哈希算法的 C++ 实现,用于生成配置版本校验码。
类定义 class SHA1 {public : SHA1 (); void update (const std::string &s) ; void update (std::istream &is) ; std::string final () ; static std::string from_file (const std::string &filename) ; private : uint32_t digest[5 ]{}; std::string buffer; uint64_t transforms{}; };
SHA-1 算法核心 SHA-1 算法将输入数据分块处理,每块 512 位(64 字节):
void transform (uint32_t digest[], uint32_t block[16 ], uint64_t &transforms) { uint32_t a = digest[0 ]; uint32_t b = digest[1 ]; uint32_t c = digest[2 ]; uint32_t d = digest[3 ]; uint32_t e = digest[4 ]; digest[0 ] += a; digest[1 ] += b; digest[2 ] += c; digest[3 ] += d; digest[4 ] += e; transforms++; }
SHA-1 处理流程 :
初始化 :设置 5 个初始哈希值(魔数)
填充 :将输入数据补齐到 512 位块边界
分块处理 :每块进行 80 轮变换
输出 :拼接 5 个 32 位值,转为 40 字符十六进制
final() 输出函数 std::string SHA1::final () { uint64_t total_bits = (transforms * 64 + buffer.size ()) * 8 ; buffer += (char ) 0x80 ; while (buffer.size () < 64 ) { buffer += (char ) 0x00 ; } uint32_t block[16 ]; buffer_to_block (buffer, block); if (orig_size > 64 - 8 ) { transform (digest, block, transforms); for (size_t i = 0 ; i < 16 - 2 ; i++) { block[i] = 0 ; } } block[16 - 1 ] = (uint32_t ) total_bits; block[16 - 2 ] = (uint32_t ) (total_bits >> 32 ); transform (digest, block, transforms); std::ostringstream result; for (unsigned int i: digest) { result << std::hex << std::setfill ('0' ) << std::setw (8 ); result << i; } return result.str (); }
ConfigUnit 类详解 ConfigUnit 负责全局配置的加载和管理。
类定义 class ConfigUnit {public : explicit ConfigUnit (const std::filesystem::path &exec_path) ; void SetConfig (const std::filesystem::path &exec_path) ; ~ConfigUnit (); lessConfigure GetConfig () const ;private : std::filesystem::path config_file_path; std::string config_file_data_string; inicpp::config config; inicpp::schema config_schema; lessConfigure configure;private : void make_schema () ; void create_default_config () ; void parse_config () ; };
SetConfig() 加载流程 void ConfigUnit::SetConfig (const std::filesystem::path &exec_path) { this ->config_file_path = exec_path / CONFIGFILENAME; make_schema (); if (std::filesystem::exists (config_file_path)) { config_file_data_string = ConfigFileIO::read_config_file (config_file_path); parse_config (); } else { create_default_config (); ConfigFileIO::save_config_file (config_file_path, config_file_data_string); parse_config (); } }
make_schema() 配置结构定义 void ConfigUnit::make_schema () { inicpp::section_schema_params section_config_params{}; section_config_params.name = "config" ; section_config_params.comment = "\n============ Gobal Settings ===========\n" ; section_config_params.requirement = inicpp::item_requirement::mandatory; config_schema.add_section (section_config_params); inicpp::option_schema_params<inicpp::string_ini_t > version{}; version.name = "version" ; version.default_value = PROJECT_GIT_HASH; config_schema.add_option ("config" , version); inicpp::option_schema_params<inicpp::boolean_ini_t > debug{}; debug.name = "debug" ; debug.default_value = "0" ; config_schema.add_option ("config" , debug); inicpp::section_schema_params section_audio_model_params{}; section_audio_model_params.name = "audio_model" ; section_audio_model_params.comment = "\n========= Audio Model Settings ========\n" "Note: modifying any of the parameters here will require remodeling the voice db\n" ; config_schema.add_section (section_audio_model_params); inicpp::option_schema_params<inicpp::float_ini_t > frame_period{}; frame_period.name = "frame_period" ; frame_period.default_value = std::to_string (configure.audio_model_frame_period); config_schema.add_option ("audio_model" , frame_period); inicpp::option_schema_params<inicpp::float_ini_t > fft_size{}; fft_size.name = "fft_size" ; fft_size.default_value = "auto" ; config_schema.add_option ("audio_model" , fft_size); inicpp::option_schema_params<inicpp::float_ini_t > model_amp{}; model_amp.name = "model_amp" ; model_amp.default_value = std::to_string (configure.model_amp); model_amp.comment = "Apply AutoAMP before Modeling..." ; config_schema.add_option ("audio_model" , model_amp); }
parse_config() 解析函数 void ConfigUnit::parse_config () { config = inicpp::parser::load (config_file_data_string); auto config_section = config["config" ]; configure.version = config_section["version" ].get <inicpp::string_ini_t >(); if (configure.version != PROJECT_GIT_HASH) { YALL_WARN_ << "Configure file version does not match software version." ; } configure.debug_mode = config_section["debug" ].get <inicpp::boolean_ini_t >(); auto audio_model_section = config["audio_model" ]; configure.audio_model_frame_period = audio_model_section["frame_period" ].get <inicpp::float_ini_t >(); configure.model_amp = audio_model_section["model_amp" ].get <inicpp::float_ini_t >(); if (audio_model_section["fft_size" ].get <inicpp::string_ini_t >() == "auto" ) { configure.fft_size = 0 ; configure.custom_fft_size = false ; } else { std::stringstream ss; ss << audio_model_section["fft_size" ].get <inicpp::string_ini_t >(); ss >> configure.fft_size; configure.custom_fft_size = true ; } auto f0_section = config["f0" ]; configure.f0_mode = [&]() -> lessConfigure::F0_MODE { auto f0_mode = f0_section["f0_mode" ].get <inicpp::string_ini_t >(); std::transform (f0_mode.begin (), f0_mode.end (), f0_mode.begin (), ::toupper); if (f0_mode == "DIO" ) return lessConfigure::F0_MODE::F0_MODE_DIO; else if (f0_mode == "HARVEST" ) return lessConfigure::F0_MODE::F0_MODE_HARVEST; else return lessConfigure::F0_MODE::F0_MODE_UNKNOWN; }(); configure.f0_speed = static_cast <int >(f0_section["f0_speed" ].get <inicpp::signed_ini_t >()); configure.f0_dio_floor = f0_section["f0_dio_floor" ].get <inicpp::float_ini_t >(); configure.f0_harvest_floor = f0_section["f0_harvest_floor" ].get <inicpp::float_ini_t >(); configure.f0_cheap_trick_floor = f0_section["f0_cheap_trick_floor" ].get <inicpp::float_ini_t >(); configure.f0_allow_range = f0_section["f0_allow_range" ].get <inicpp::float_ini_t >(); auto ap_section = config["ap" ]; configure.ap_threshold = ap_section["ap_threshold" ].get <inicpp::float_ini_t >(); }
配置文件格式 生成的配置文件 lessconfig.ini 格式如下:
============ Gobal Settings ===========[config] version = <git_hash>debug = 0 ========= Audio Model Settings ======== Note: modifying any of the parameters here will require remodeling the voice db[audio_model] frame_period = 5.80499 fft_size = automodel_amp = 0.85 ============= F0 Settings ============= Note: modifying any of the parameters here will require remodeling the voice db[f0] f0_mode = HARVESTf0_speed = 1 f0_dio_floor = 40.0 f0_harvest_floor = 40.0 f0_cheap_trick_floor = 71.0 f0_allow_range = 0.1 ============= AP Settings =============[ap] ap_threshold = 0.10
ConfigVoiceBank 类详解 ConfigVoiceBank 允许为特定的音源库覆盖全局配置。
类定义 class ConfigVoiceBank {public : ConfigVoiceBank (std::filesystem::path _voice_path, lessConfigure _configure); explicit ConfigVoiceBank (lessConfigure _configure) ; void SetVoiceConfig () ;private : lessConfigure configure; std::filesystem::path voice_path; std::filesystem::path voice_config_path; inicpp::config config; inicpp::schema config_schema; std::string config_file_data_string;private : void parse_config () ; };
音源库配置机制 void ConfigVoiceBank::SetVoiceConfig () { voice_config_path = voice_path / VOIICEBANKCONFIGFILENAME; if (voice_config_path.empty ()) { throw file_open_error ("Configure file: " + voice_config_path.string ()); } }void ConfigVoiceBank::parse_config () { config = inicpp::parser::load (config_file_data_string); auto audio_model_section = config["audio_model" ]; configure.audio_model_frame_period = audio_model_section["frame_period" ].get <inicpp::float_ini_t >(); configure.model_amp = audio_model_section["model_amp" ].get <inicpp::float_ini_t >(); }
使用场景 :
不同音源库可能有不同的录制条件:
采样率不同 → 需要调整帧周期
音量不均 → 需要调整 model_amp
低音音源 → 需要降低 f0_floor
配置层次结构 ┌─────────────────────────────┐ │ lessconfig.ini │ 全局配置 │ (exec_path/lessconfig.ini)│ └──────────────────┬──────────┘ │ 默认值 ▼ ┌─────────────────────────────┐ │ lessConfigure │ 配置数据结构 │ - 全局参数 │ │ - get_version() → SHA-1 │ └──────────────────┬──────────┘ │ ▼ ┌─────────────────────────────┐ │ voicebankconfig.ini │ 音源库配置(可选) │ (voice_path/...) │ └──────────────────┬──────────┘ │ 覆盖 ▼ ┌─────────────────────────────┐ │ lessConfigure │ 最终配置 │ - 全局参数 │ │ - 音源库覆盖 │ │ - 版本校验码 │ └─────────────────────────────┘ │ ▼ ┌─────────────────────────────┐ │ AudioModel 生成 │ │ - 存储版本校验码 │ └─────────────────────────────┘ │ ▼ ┌─────────────────────────────┐ │ AudioModel 加载 │ │ - 验证版本校验码 │ │ - 不匹配则重新生成 │ └─────────────────────────────┘
版本校验流程 生成时(AudioModelIO) void AudioModelIO::SaveAudioModel (...) { std::string version = configure.get_version (); file.write ("shine" , 6 ); size_t ver_size = version.size (); file.write (reinterpret_cast <const char *>(&ver_size), sizeof (size_t )); file.write (version.c_str (), ver_size); }
加载时(AudioModelIO) bool AudioModelIO::CheckAudioModelVersion (const lessConfigure &configure) { char header[6 ]; file.read (header, 6 ); if (strcmp (header, "shine" ) != 0 ) { throw header_check_error ("Header: ..." ); } size_t ver_size; file.read (reinterpret_cast <char *>(&ver_size), sizeof (size_t )); std::string stored_version (ver_size, '\0' ) ; file.read (&stored_version[0 ], ver_size); std::string current_version = configure.get_version (); if (stored_version != current_version) { throw file_version_error ("Version Mismatch" ); } return true ; }
使用示例 #include "ConfigUnit/ConfigUnit.h" #include "ConfigUnit/ConfigVoiceBank.h" std::filesystem::path exec_path = std::filesystem::weakly_canonical (argv[0 ]).parent_path ();ConfigUnit configUnit (exec_path) ; lessConfigure configure = configUnit.GetConfig (); std::filesystem::path voice_path = "path/to/voicebank" ;ConfigVoiceBank voiceBank (voice_path, configure) ; std::string version = configure.get_version ();AudioModelIO audioModelIO (input_file) ;if (!audioModelIO.CheckAudioModel (configure)) { GenerateAudioModel (input_file, configure); }