赛博歌搭:探索记录

最近需要一个搭子一起唱歌,但是在全民k歌冲浪了很久都没有找到搭子,于是想了想打算自己用AI做一个。

其中主要涉及到的技术就是SVC/SVS (Singing voice conversion/synthesis)。SVC是把一个人的歌声换成另一个人的;SVS是从曲谱和歌词(以及其他的唱法参数)生成一段歌声。

选择技术

从定义就可以很明显的看出SVS是老一代基于声库的虚拟歌姬的AI-ify版本11 知名的开源方案包括 diffsinger,在制作音乐的时候需要更多的手动调参(包括f0曲线、音量、气声,甚至口型),当然也更加可控;但是我是一个懒人,而且我还会点唱歌,我觉得这些手动调的唱法参数其实都可以用自己的嗓子做到(通过唱出不一样的输入音频来实现)。因此我选择了SVC这个方向。

让LLM Agent进行一波调研之后,发现现在研究的重点逐渐从2021~2023年的每个音色一个模型(主要例子有so-vits-svcddsp-svc);转换到现在追求zero-shot,也就是只需要给一段参考音频,就可以用参考音频的声音生成歌声(例子有seed-vc, yingmusic-svc)。

So-vits-svc

经过一个个的demo试听,一开始我实际上是认为音色和模型绑定的路线是更好的,因为b站上有大量的用(sovits/ddsp)-svc做AI翻唱的,听起来听感也不错。但是在把模型拉下来跑推理之后,我发现一个比较大的问题是,就算是质量更好的so-vits-svc,用GAN Generator生成出的音频电音比较严重[todo:audio]。我又换了好多个checkpoint,其中还有OpenCpop这种有一个人大量高质量干音数据集训练的,但是结果的电音都是相对比较严重的。

通过进一步阅读readme,了解到shallow diffusion model可能可以缓解电音问题。其具体工作方式是:

但是初看这个方案就有一个很明显的训推不一致问题:

实际跑完训练推理后,听感是diffusion带来了咬字不清的问题:在所有的辅音音节上,原本GAN输出的音频咬字比较清晰的,经过Diffusion之后就变得相当模糊,以至于让人经常听不清这一句唱了什么。

补丁

由于我是先意识到训推不一致再听到辅音咬字不清的,因此我就下意识认为这个问题是和训推不一致有关的(现在我又再次无法确定这个问题是什么导致的了,希望有一天我能有空把这个问题捡起来研究清楚)。

基于[训推不一致]这个问题,我想找的解决方法至少需要满足以下条件:

因此,上述离散时间步的Diffusion model肯定没有办法用了,因为我们没有从GAN生成的noisy mel到GT mel的完整加噪路径的,因此无法获取每个训练step的训练数据对(noisy mel, noise)。

但是一个补丁正好就可以打在这个点上:基于Rectified flow的Flow Matching正好不需要整个加噪路径,其只需要采样一个时间t,并按照这个时间在noisy和GT之间插值,就可以作为训练对。使用(加噪的声音,GT)作为配对就可以直接训练。

推导

目标:将 Shallow Diffusion 在第 个扩散步得到的加噪 GAN mel 映射到Flow Matching 的全局时间轴 上的中间时刻 .

为GT mel, 为相同条件下GAN生成的mel, 表示shallow diffusion的加噪步数。定义:

原 Shallow Diffusion 在第 个扩散步得到的状态为

采用rectified flow坐标:

线性flow matching路径中,source 分量与 target 分量的系数分别为 ,其对应的SNR(signao-to-noise ratio)和Shallow Diffusion 在第 个diffusion step的 SNR相等:

由于 均为正数,有

但是仅匹配 SNR 还不足以使 diffusion 状态直接成为线性flow matching状态,因为 中两个系数之和通常不等于1。因此定义归一化后的入口状态

代入 并利用

因此, 精确对应线性flow matching的 轴上的 噪声强度。它不仅与diffusion step 具有相同的SNR,而且信号与噪声的绝对系数也与线性flow matching坐标一致。训练和推理的起始状态均取为

在采样路径上还有更多推导以将flow matching匹配于原diffusion model,这里就不展开了。

效果

Zero-shot SVC

Zero-shot这个方向就能找到近至四五个月之前的很前沿的开源工作。经过多个例子的试听,我感觉Yingmusic-SVC的效果是最好的,而且尤其是如果参考timbre的音频质量足够好(没有过载的干音直出),输出的音频效果是很不错的。但是其仍然存在几个比较严重的问题:

  1. 断音。听感上一个长音中间会出现一至多个“断裂”,可以从mel-spectrogram里面看出来;
  2. aliasing(这里似乎翻译成伪影更合适?):从听感上来说,就是声音有的部分的谐波和基频好像在两个图层。

改造

对于这个任务,我实际上是极其缺数据的,我手上只有60h左右的包括开源和自己b站爬的干净干音数据。基于此,经过一系列调研,尝试了以下方案:

  1. 更换vocoder

    • 在试听vocoder在歌声任务的大横评之后,发现其实BigVGAN的表现极其一般且已被各种新的SOTA超过,会出现和我前面听到的类似的aliasing,怀疑是BigVGAN引入的;
    • 效果:换成SOTA的Pupu-Vocoder之后有明显改善,aliasing的问题几乎被完全解决。
  2. 部分层添加conv模块,并且进行微调

    • 考虑整个网络目前只有attention,没有能显式建模局部连续性的模块
    • 实际上没啥用,也可能是数据太少,但是后来一想觉得数据量多了也没啥用,transformer里面已经加了RoPE了应当能辨识位置信息
  3. 换成使用带有explicit f0输入的vocoder(PC-NSF-HiFiGAN)

    • 断音的地方从Mel-spectrogram可以看到能量偏移了f0,考虑可能引入explicit f0可以在mel->waveform的步骤缓解这个问题
    • 需要微调主模型,因为PC-NSF-HiFiGAN使用的mel-spectrogram的配置和原本的BigVGAN不同
    • 用我有限的数据进行微调之后效果不尽如人意,比较嘶哑;
    • 这个方案还有其他变体:

      1. 给当前SOTA的Vocoder添加上explicit f0输入,但是这个也需要重新训练Vocoder;
      2. 训练一个mel格式转换的模型,从pseudo-inverse开始迭代,训练一个将BigVGAN格式的Mel转换成HiFiGAN格式的Mel。
    • 上述方案都给出了可以辨识雏形的声音结果,但是各种细节都很不尽人意。我初步判断是数据量不足的的问题,于是许多问题又回到了去哪找数据……
  4. 一种免训练的方案:

    1. 先试用Pupu-Vocoder合成波形
    2. 然后提取HiFiGAN格式的Mel,再使用PC-NSF-HiFiGAN使用explicit f0 condition重新合成。

    经过实验,这种方法是效果最好的,断音数量大幅减少,而且由于Pupu-Vocoder的参数量非常大,导致实际上二次分析的音频质量和PC-NSF-HiFiGAN的生成质量没有可以感知的差别。

最终敲定了方案4,并让AI将所有模型的推理移植到了Metal平台,并使用Rust实现以避免对Python的依赖。最终架构如图所示:

推理 & UI

由于我需要在Mac电脑上使用,推理仍然是使用一个我自己写的skill(可以在这里找到)让Agent将PyTorch代码移植到mlx,并且使用Rust以避免Python环境管理的混乱。

虽说LLM Agent的出现让写UI的成本确实低了非常多,但是如果UI出bug了,要去调整UI的细节还是一件非常痛苦的事情,尤其是我这种不是专业客户端开发的,甚至有时候连把问题用专业术语描述清楚都比较难做到。基于我在开发这个音高调节工具这个显存分析可视化工具的时候的比较痛苦的经历,我决定还是回归web技术栈,使用electron。

用了web技术栈就方便多了!你只需要一个前端艺术素养比较好的模型22 比如:Claude Fable5, Kimi K3;我使用的是Kimi K3。As of 2026.08, DO NOT USE GPT!!!,然后告诉他你想要复刻哪个软件的UI设计风格就可以了。我这里想要简约一点,就直接复刻VS Code的设计风格,然后Agent就自动生成出了app:

这次UI部分的工作比之前都舒服太多了,在比electron更好的方案出现之前我应该没有兴趣再使用各种原生方案了。

Misc

Source-Filter理论、NSF架构

后来查阅资料后发现,BigVGAN的不足很可能是没有使用NSF的架构,而Pupu-Vocoder和PC-NSF-HiFiGAN都使用了这个架构。试听了大量vocoder的横评之后我认为这个猜想是合理的。