搜索

iVocaloid论坛

查看: 2500|回复: 8
打印 上一主题 下一主题

【Rocaloid经验总述】【一】关于CybervoiceEngine和TDPSM [复制链接]

Sleepwalking

我不是技术宅!

Lv.5-章鱼须

Rank: 5Rank: 5Rank: 5

0
9
0


UID: 111156
权限: 40
属性: 宇宙人
发帖: 201 (1精)
积分: 540
章鱼: 3
大葱: 14
茄子: 2688
注册:2012/8/18
存在感:476
跳转到指定楼层
[1L]楼主
Zleepwalking 发表于 2013/5/27 07:07:23 |只看该作者 |倒序浏览
本帖最后由 Zleepwalking 于 2015/3/22 18:25 编辑

2015.3
本贴所包含信息时间过于久远,已废弃。出于保留项目历史原因在此搁置。

        这篇文章讲解Rocaloid使用的CVE引擎和TDPSM算法。

        另:TDPSOLA是上世纪90年代发明的时域语音合成算法,PSM和它较为相似。故在此比较异同,可以跳过。

         基本的合成概念


        看了这个,再去理解下面的东西以及以后的几章就容易多了:
        CVE对于每个字的合成是通过音素之间的过渡实现的。
        比如窗“Chuang”,就是从ch->u,u->a,a->ng这样实现的。
        比如腾讯“Teng Xun”,多个字的合成,先拆成单个字, t->e, e->ng; x->v, v->e, e->n。然后把两个字衔接起来。TDPSM就是用来过渡的算法。



算法

信号域

窗口大小

基本运算

优势

劣势

TDPSOLA

时域

2-4个周期

叠加

速度快、还原度高

变调音质损伤大

TDPSM

时域

1个周期

过渡(混合)

速度快、还原度高

数据量需求大


        TDPSOLA = Time Domain Pitch Sychronize and OverLap Add

        时域基音同步叠加
        TDPSM = Time Domain Pitch Sychronize and Mix
        时域基音同步混合
        从名字上说两个算法四分之三都差不多。

        其实TDPSM……我一开始不知道有个东西叫PSOLA,然后自己摸索摸索着就把PSM设计出来了,后来才发现跟PSOLA很像……反正这是个很简单的算法,算不上什么吧。

        嗯,很简单的算法。CVE1.6其实只有1500行代码(除去开头GPL的注释)。


        TD-PSOLA


        我就不详细解释了。参见维基叔叔的简述:
PSOLA works by dividing the speech waveform in small overlapping segments. To change the pitch of the signal, the segments are moved further apart (to decrease the pitch) or closer together (to increase the pitch). To change the duration of the signal, the segments are then repeated multiple times (to increase the duration) or some are eliminated (to decrease the duration). The segments are then combined using the overlap add technique.

        渣翻译:
          PSOLA通过将语音波形分解成小的重叠部分工作。为了改变信号的音高,这些部分被移动得分散(降调)或者更紧密(升调)。为了改变信号的持续时长,这些部分被重复多次(延时)或者去除一些部分(缩短)。这些部分通过重叠相加技术组合。

      TD - PSM


       TDPSM的作用:拆分初音的语音数据,转化成Rocaloid音源库,合成时把音源库的数据进行时间拉伸、变调、过渡,再重组成波形数据。
       原理概述:两步。1. 分解波形(拆帧)2.重组帧。
       优点:速度快,还原度高。
       缺点:输出语音可能会模糊,无法变调(只能靠过渡实现),需要大量音源库数据。

       PSM第一步也将语音波形分解成很多小部分,只不过这些部分不重叠,但首位相接,如下:

Orig.png


这是原始波形



Framed.png


这是分解后的波形


        这种分解的波形,每一段可以称为一个周期或者一个语音帧。拿1秒除以帧的长度就是信号的基频(实际上是采样率/帧的采样数量)。


        分解波形的过程称为拆帧,会在音源库一章里详解。分解后的波形以CBV后缀存在音源库中(但CBV不仅仅是改了后缀的wav,它有文件头)。


        第二步,也就是真正的合成操作,是对这些小部分的组合。

        关于【组合】操作,有以下几种组合方法:


  • 顺序连接:通过直接按顺序连接语音帧,还原出原来的波形。
  • 逆序连接:通过按相反的顺序连接语音帧,产生出听上去和原来相同的波形。
  • 伸缩:直接拉长或者压缩语音帧的长度,伸缩的长度基本在10个采样(Rocaloid音源库是96000HZ)以内就不会有什么影响。过度伸缩会产生大叔音或娃娃音= =
  • 过渡连接:此链接方式有两个CBV文件参与,在顺序或逆序连接的基础上,将两个CBV中对应的语音帧伸缩使其长度保持一致,然后混音,随时间推移使其中一组语音帧在混音中所占的比例增大或减小。

        这四种方法还可以组合起来用。PSM有几种方法可以实现拉伸波形:
  • 先顺序连接,到达最后一个语音帧时转成逆序连接,到达第一个语音帧时再转回顺序连接,如此反复。
  • 先顺序连接,快要到达最后一个语音帧时开始过渡到头几个语音帧,到达最后一个语音帧时正好完全过渡到头几个语音帧,然后从头开始顺序连接,如此反复。
  • 如果你够无聊还可以逆序连接再按②过渡。。。

        问题在于逆序连接容易产生杂音,因为你不能保证把连接顺序倒过来,语音帧的首尾还接得上。
        注:逆序连接不同于将波形反向。逆序仅仅是语音帧排列顺序上的反向,而语音帧本身还是正向的。

        CVE0.6中使用了顺序连接,逆序连接,过渡连接三种基本方法。CVE1.6中使用了顺序连接,伸缩,过渡连接三种方法,为了提高音质。

        然而硬伤是PSM无法像PSOLA一样变调。好在PSM有过渡,要实现变调,可以从一个声调的语音帧组合,过渡到另一个声调的语音帧组合。只是这两个声调的跨度不能太大,否则会崩得很厉害……
        而且要实现这种变调,音源库里必须提供所有发音记号在所有音高下的CBV数据……我也只能向算法妥协。于是Rocaloid Renaissance的1.6.1版音源库中有3441个文件……关于如此大的工作量问题如何解决,我会在音源库一章中说明。


        CVE 2.0 框架


        概述:这是讲TDPSM算法在CVE中以代码形式实现的方式。如果你加入CVE引擎开发应该阅读以下内容。
        大致原理:由合成器、缓冲器、效果器构成,面向对象,各部分层层相套,从CVS文件中读取的语音参数层层向下传递。一个一个字合成好再拼接起来。

        准确地说,现在CVE1.6其实是一个实现了基本功能的CVE 2,原计划发布的时候CVE 2仅是一个添加了很多功能的CVE 1.6。
        CVE 1.6的结构和之前的版本有了很大的改进,甚至相对于CVE 1.5都是翻天覆地的变化。
        下面是CVE 1.6在设计阶段的一张结构图:
        (Rocaloid项目中所有规划图用GNU Dia绘制)


CVEChart.png


CVE1.6 即按此图编写,结构上有细微调整


        这张结构图上的方块主要有三种颜色,分别代表合成器(红色),效果器(绿色),缓冲器(黑色)。数据存储和数据流用黑色箭头表示;参数存储、计算器和参数流用蓝色箭头表示。
        *数据指音源数据,即CBV文件中存贮的东西;参数指合成参数,即CVS中存贮的东西。
        左上角的蓝色方块是加载到内存中的合成参数。不过现在这种结构没有被采用。

        我把所有Synthesizer, Effector, Buffer都抽象化成了类(不过Effector的所有成员都是Shared)。
        CVE合成时先把一个字的波形合成出来(图中右上的一半部分),然后给它加上包络和效果(图中中心和下方部分),再和上一个字的结尾处混音(图中下方),最后输出到文件。之所以和上一个字的结尾混音,是因为这样听上去更加连贯(会在Vocaloid和反向工程以及语音学研究章中详细说明)。


        细节




        下面是非常操蛋的细节描述。。。。。。。。。

        在Synthesizer的基类里有这样一段代码,这是TDPSM的Mix算法,有编程经验的话很容易就能理解:
  1. Public Function FrameMix(ByVal Frame1 As FrameBuffer, ByVal Frame2 As FrameBuffer) As FrameBuffer
  2.         'This function adds two framebuffers together, each is multiplied by a certain ratio, which relates to MixRatio.
  3.         'Returns the result of calculation.
  4.         Dim i As Integer
  5.         Dim Len1 As Integer, Len2 As Integer, Len As Integer
  6.         Dim TransitionRatio As Double
  7.         Dim InstantaneousRatio As Double
  8.         Len1 = Frame1.Length
  9.         Len2 = Frame2.Length
  10.         Len = CInt(Len1 * (1 - MixRatio) + Len2 * MixRatio)
  11.         If Len = 0 Then
  12.             Throw New Exception("Overflow. Len = 0 in FrameMix procedure.")
  13.         End If
  14.         Dim Frame As FrameBuffer = New FrameBuffer(Len)
  15.         For i = 0 To Len
  16.             TransitionRatio = i / Len
  17.             InstantaneousRatio = MixRatio * (1 - TransitionRatio) + MixRatio2 * TransitionRatio
  18.             Frame.Data(i) = Frame1.Data(CInt(TransitionRatio * Len1)) * (1 - InstantaneousRatio) _
  19.                                     + Frame2.Data(CInt(TransitionRatio * Len2)) * InstantaneousRatio
  20.         Next
  21.         Return Frame
  22. End Function
复制代码

(顺便说一下,虽然是Public Function但这个函数其实是提供给Synthesizer内部的。。。)


        对于每一个音节(字)的合成,ConsecutivePreSynthesizer会合成一系列连贯的语音帧(存储于一系列FrameBuffer中),这些FrameBuffer被递交给ConsecutivePreSynthesizer上一级的PitchPreSynthesizer,再和另一个相同发音不同音高的FrameBuffer混合成一个新的,特定频率的FrameBuffer;这个FrameBuffer又被递交给上一级的SpeechSynthesizer,再和由另一个PitchPreSynthesizer生成的相同音高不同发音的FrameBuffer混合,实现汉语的转韵。
        意思就是,PitchPreSynthesizer负责生成特定频率的FrameBuffer;SpeechSynthesizer负责生成特定频率下特定发音的FrameBuffer。


        在这张图上有一个结构没有画出来,它是Scheduler。Scheduler是一个总的控制器,负责调度SpeechSynthesizer、效果器、波形输出和混音。

        PitchCalculator是一个用来控制频率控制的类。在Scheduler把需要合成的音节传给SpeechSynthesizer,SpeechSynthesizer调用PitchCalculator计算两个PitchPreSynthesizer的音高等参数,PitchPreSynthesizer也依靠PitchCalculator计算每一帧的周期长度。


        对于音源库文件的读取,并没有刻意的控制。PitchPreSynthesizer在音高变化或发音记号变化时会让ConsecutivePreSynthesizer读取指定的cbv文件。

        Effector包括:
  •    控制点包络生成器(EnvelopeListRender):根据n个时间、振幅的控制点控制音节中音量变化。
  •    ADSR包络生成器(ADSREnvelope):根据Attack\Decline\Release\Amplitude控制音节中音量变化。
  •    口型大小控制的包络生成器(OpennessListRender):根据设定的不同音节的口型大小控制音节中音量变化。口型大音量大,口型小音量小。(也可以用来作音源库中某个发音普遍音量过大或过小的补救)
  •    前向剪切器(ForwardCutter):用于缩短辅音。用得过猛会吞辅音。
  •    尾音平滑模糊器(Blur):在音节结束前的一段时间,通过平滑处理让声音模糊。
  •    呼吸声生成器(BreathNoise):通过差分处理后的波形与原波形按权叠加实现类似呼吸噪音的效果。(可能会加强拼接不当产生的引擎噪音)
  •    辅音缩短器(Shrink):类似前向剪切器,吞辅音不会很严重。
        反正都是时域算法,代码应很容易理解。


        有一个小细节,在SpeechSynthesizer合成音节辅音部分时,由于辅音部分可能没有周期性,SpeechSynthesizer\PitchPreSynthesizer\ConsecutivePreSynthesizer都会直接把辅音不做处理读出来,写进WaveBuffer里去。同时会改变一个全局变量:GlobalSendBack。这个SendBack目前只包含了辅音部分的长度,是给辅音缩短器用的。

        对于高于C5或低于C2的频率,CVE会直接伸缩其长度,虽然会导致大叔或娃娃音出现,但是音高本身就很极端也听不太出来。目前辅音部分还没有实行伸缩算法,所以只有用滑音才能飙出C2-C5的范围。滑音频率的范围是65.4HZ(C1) - 1975.5HZ(B5)。



        在本章最后,附上一张CVE 1.6合成时的截图,现在你应该可以看懂这些调试日志:

Synlog.png



          另:如果CVE在合成时崩溃,你可以修改调用的命令行参数:

        CybervoiceEngine -cvs xxx.cvs -wav xxx.wav -q > log.txt

        这样就可以把调试信息存入log.txt。你可以把它传上论坛或者发给我。








知识共享许可协议 除非另有声明,本帖内容采用 署名-非商业-相同方式共享 3.0 许可协议 授权,且需注明出处,所有权利归发帖人。

使用道具 举报

Rank: 3

0
3
0


UID: 93325
权限: 20
属性: 難燃性
发帖: 74 (0精)
积分: 100
章鱼: 1
大葱: 2
茄子: 433
注册:2011/10/22
存在感:84
[2L]沙发
rgwan 发表于 2013/5/30 20:07:10 |只看该作者
我的想法是暑假用gcc/vc把engine重写一次。然后用动态库(DSO或者DLL)方式被程序引用、
目前我想好的接口部分类似MIDI协议。使用Command+控制码配置音源。然后API做音频输出等等。
系统码的方式比较方便扩充。

使用道具 举报

Sleepwalking

我不是技术宅!

Lv.5-章鱼须

Rank: 5Rank: 5Rank: 5

0
9
0


UID: 111156
权限: 40
属性: 宇宙人
发帖: 201 (1精)
积分: 540
章鱼: 3
大葱: 14
茄子: 2688
注册:2012/8/18
存在感:476
[3L]板凳
Zleepwalking 发表于 2013/5/30 20:11:38 |只看该作者
rgwan 发表于 2013/5/30 20:07
我的想法是暑假用gcc/vc把engine重写一次。然后用动态库(DSO或者DLL)方式被程序引用、
目前我想好的接口 ...

重写是必须的【我不会c++。。。
你可以专门写个midi的接口
但是RSC和CVS必须被保留
RDL的好处就是纯文本,如果不能纯文本编辑了,Rocaloid就丧失了她的灵活性;
CVS、RSC、CDT的统一性符合Rocaloid的设计思想;
另外RDL可以按原来格式被直接拷贝进Excel里去,这是个我自己设计时都没预料到的方便功能!

使用道具 举报

Rank: 3

0
3
0


UID: 93325
权限: 20
属性: 難燃性
发帖: 74 (0精)
积分: 100
章鱼: 1
大葱: 2
茄子: 433
注册:2011/10/22
存在感:84
[4L]地板
rgwan 发表于 2013/5/31 10:02:02 |只看该作者
Zleepwalking 发表于 2013/5/30 20:11
重写是必须的【我不会c++。。。
你可以专门写个midi的接口
但是RSC和CVS必须被保留

我的意思是说。操作就直接用类似MIDI的四字控制码发给引擎。至于选音的话,我想了两种方式:1、专门函数来取,2、兼容MIDI PROGRAM CHANGE。
然后音频输出可以用配置函数定位到WAVE文件或者DX SOUND、PULSEAUDIO.
封装成动态库。然后APP引用就可以了。
现在我纠结是用C写还是用CPP写。感觉CPP写可能可以提供重入。操作方便,BUT问题就是封装类比较麻烦。
看吧。一些不同平台的函数得封装,然后按需引用。

使用道具 举报

Sleepwalking

我不是技术宅!

Lv.5-章鱼须

Rank: 5Rank: 5Rank: 5

0
9
0


UID: 111156
权限: 40
属性: 宇宙人
发帖: 201 (1精)
积分: 540
章鱼: 3
大葱: 14
茄子: 2688
注册:2012/8/18
存在感:476
[5L]萝莉
Zleepwalking 发表于 2013/5/31 10:36:47 |只看该作者
rgwan 发表于 2013/5/31 10:02
我的意思是说。操作就直接用类似MIDI的四字控制码发给引擎。至于选音的话,我想了两种方式:1、专门函数 ...

嗯。控制引擎的话MIDI应该是可以的。
那么应该有个把CVS的数据结构转到MIDI的东西,外加一个用MIDI发信号协调引擎的东西。
音频输出编程我没经验的……
------
我个人想法当然是C。。。。。。。

使用道具 举报

Rank: 3

0
3
0


UID: 93325
权限: 20
属性: 難燃性
发帖: 74 (0精)
积分: 100
章鱼: 1
大葱: 2
茄子: 433
注册:2011/10/22
存在感:84
rgwan 发表于 2013/5/31 17:18:19 |只看该作者
Zleepwalking 发表于 2013/5/31 10:36
嗯。控制引擎的话MIDI应该是可以的。
那么应该有个把CVS的数据结构转到MIDI的东西,外加一个用MIDI发信号 ...

同意、另外频域算法我会暑假恶补数学&森势WORLD。
刚才在Q上的讨论结果让我觉得照搬MIDI绝对不靠谱。但是MIDI灵活,我准备综合下目前的CVS和MIDI的描述方式,做一个类似MIDI的系统。第一可以减少CVS的大小,第二可以让API简洁。这样只要发几个字节给引擎就搞定了。

使用道具 举报

Rank: 3

0
3
0


UID: 93325
权限: 20
属性: 難燃性
发帖: 74 (0精)
积分: 100
章鱼: 1
大葱: 2
茄子: 433
注册:2011/10/22
存在感:84
[7L]大姐姐
rgwan 发表于 2013/6/3 18:44:00 |只看该作者
后鼻音怎么办?-ng……

使用道具 举报

Sleepwalking

我不是技术宅!

Lv.5-章鱼须

Rank: 5Rank: 5Rank: 5

0
9
0


UID: 111156
权限: 40
属性: 宇宙人
发帖: 201 (1精)
积分: 540
章鱼: 3
大葱: 14
茄子: 2688
注册:2012/8/18
存在感:476
[8L]实妹
Zleepwalking 发表于 2013/6/3 19:59:10 |只看该作者
rgwan 发表于 2013/5/31 17:18
同意、另外频域算法我会暑假恶补数学&森势WORLD。
刚才在Q上的讨论结果让我觉得照搬MIDI绝对不靠谱。但是 ...

字节码无法兼备易于修改的特点。
就CVS读取的效率似乎不会造成什么性能瓶颈。这种事情现在用不着讨论,同样录制音源的事情还早呢。。。

使用道具 举报

Rank: 3

0
3
0


UID: 93325
权限: 20
属性: 難燃性
发帖: 74 (0精)
积分: 100
章鱼: 1
大葱: 2
茄子: 433
注册:2011/10/22
存在感:84
rgwan 发表于 2013/6/4 14:55:34 |只看该作者
Zleepwalking 发表于 2013/6/3 19:59
字节码无法兼备易于修改的特点。
就CVS读取的效率似乎不会造成什么性能瓶颈。这种事情现在用不着讨论,同 ...


那么API接口就得成CVE.Execute(CString str[])……
还有我貌似没看到chang的-ng音怎么发 ……

使用道具 举报

您需要登录后才可以回帖 登录 | 注册/sign up

申请友链|Archiver|iVocaloid - 自由,开放,合作,共享    | 版权持有者点击这里进行举报

GMT+8, 2025/6/7 22:35

Powered by Discuz! X2

© 2001-2011 Comsenz Inc.

回顶部