我的笔记

灵感、随想与新技术
05 六月 2020

音频开发实战(三)制作KONTAKT音源 (附源码) [KSP编程]

 

这节,我们制作一个这样的音源

前言

说起上一节中能自主产生波形的合成器,我们不免想到和它相对的,利用人工录制的采样发声的采样器。

比起开发一个采样器来说,开发Kontakt是更好的选择。

编写采样器有一些底层的,现阶段没有必要了解的内容。

 

开发Kontakt?

可能在我们印象中,Kontakt的作用就是打开它然后选音色用,和编程没什么联系。

但实际上,Kontakt是一个可编程的采样器。两个重点:

  1. 它是一个采样器。
  2. 它可被编程。

 

重新认识Kontakt

打开Kontakt,不加载任何音色

点击那个保存样子的按钮,然后选New instrument新建一个乐器

然后就出现一个新的空乐器,点击扳手

然后就出现了 Kontakt采样器功能的界面, 我们平时用的各种Kontakt音源就是在这里被制作的

先看一下界面的最下面有四排:

| InstrumentBuses | InsertEffects | SendEffects | Modulation |

这四排是可以展开的标签页,点左边的三角就可以展开/关闭。

它们全是效果,用的地方不同。

 

再看界面的最上面有五个按钮:

| Instrument Options | Group Editor | Mapping Editor | Wave Editor | Script Editor |

它们分别是:

乐器设置、群组编辑器、映射编辑器、波形编辑器、脚本编辑器。

 

制作音源(设置背景)

先看看第一个按钮,点一下Instrument Options(乐器设置)

看到有个Instrument Wallpaper(乐器墙纸),那我们找张图

我在网上搜了张图,稍微P了一下,如下

图需要png或tga的格式

设置成Wallpaper,点扳手回到主界面

不管怎么说,背景改变了 (后期可以用代码控制界面大小来让背景显示的多一些)

 

制作音源(映射采样)

接下来点击第三个按钮:Mapping Editor(映射编辑器)

映射的意思是把采样(录音文件)对应到MIDI键盘上,当你某这个键的时候就播放上面对应的那个采样(录音文件)

我们先在左边的文件浏览器里找到我们准备的录音文件,我这里用的是洛天依的.wav格式的呼吸包做例子

从左边按住一个波形文件,拖动到对应的按键,我们看到现在这个音频对应了好几个键(这样的话这几个键按下去都是这同一个声)

没关系,保持拖动不要放开,把鼠标向下移动一些,它就收窄了。(或者松开然后拖拽白块的左右边界也能调节宽窄)

就这样把所有波形拖进来

按下键盘,有声音了!

 

力度分层:一个人大喊出来的声音和他平时说话的声音,除了音量不同,声音内容也是完全不同的,乐器也是如此。为了模拟乐器这种真实自然的效果,我们在音源制作中,常常会让一个按键对应多个录音文件,演奏时由力度决定采样器播放哪个音频文件。

力度体现在KONTAKT的映射编辑器里就是纵轴。刚才我们说过,映射块可以拖动左右边界调整映射到键盘的范围。其实这个映射块还可以上下拖动来调整这个录音文件对应的力度范围。

例如:图中选中的这个块(黄块)对应的力度是43-80

&

群组编辑器与波形编辑器:本例中并不需要使用它们,我简单介绍下用在什么地方。

-> 群组编辑器的一个作用是切换音色,一架钢琴,后盖盖上和打开是不同的音色,如果希望把这两种音色都涵盖到音源里,那么就要录两遍给两个群组。开盖一遍,合盖再一遍,这样我们拥有两组映射。我们用写代码切换群组的方式切换音源当前的音色。

群组还有一个用途是合成,有些音源是好几个麦克风录的,实现多个麦克风的声音合到一起的就是多个群组一起播放。

再有就是管弦音源会通过群组摆位实现一些效果。

-> 波形编辑器:调整波形,告诉采样器这个波形要怎么用。比如我们的录音文件就1s,但我们希望在按着它时它能发出持续的声音,不是1秒就没了。这就要用波形编辑器,在里面设置个Loop。这个编辑器里还有很多细节,影响音源精细程度。

制作音源(开始编程)

介绍完了前四个按钮,就只剩最后一个按钮了,Script Editor(脚本编辑器)

点开什么也没有,灰色的一个条,点这个灰条左下角的Edit打开编辑窗口

我们就在这个白框里写KSP脚本,写好点击右上角Apply应用脚本

 

-> 认识KSP脚本

 

接下来我们一起认识一下KSP脚本语言

 

变量
1
2
3
4
5
6
7
8
9
10
{这是一条注释}

{数值变量}
declare $x := 1                      

{数值数组变量}
declare %myArr[2]           

%myArr[0] := 7
%myArr[1] := -10               
点此查看更多变量

@myText – 字符串变量
!myArray[] – 字符串数组
const $myVariable – 数值常量
polyphonic $i – 也是数值变量。不同的是如果写在on note 里 多个音同时按下时 这个量对每一音都是独立的


界面组件

界面组件声明后会立即出现在界面上。
 

1
2
3
4
5
{旋钮:(0,100,1)的意思是取值0100,每次移动1个单位。这里只是举个例子,可以自由调整}
declare ui_knob $mySlider(0,100,1)

{按钮:有按下和没按下的两种状态,可以直接用$myParam :=1设置它为按下的状态 }
declare ui_button $myParam;
点此查看更多组件
ui_slider – 推子
ui_label – 纯文字
ui_menu – 下拉菜单
ui_switch – 切换开关(还是个按钮)
ui_table – 一个步进器 可以画力度、ARP之类的
ui_file_selector – 加载文件用的
ui_level_meter – 显示音量的电平表
ui_text_edit – 可输入文字的文本框
ui_value_edit – 可输入数值的数值框
ui_waveform – 波形显示器


回调方法

我们在 ‘音频开发技术(三)’里已经提到过,回调方法是框架让你写东西的地方,不同的回调会在不同的条件下被框架执行,你把功能写在里面,就可以在特定条件下执行你写在里面的功能。

KSP中所有代码必须写在回调方法里,外部没有写代码的地方
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
on init
{这里的代码在初始化时执行。你可以把初始化和构造方法理解为这个软件一打开就执行的代码}
end on


on note
{这里的代码在有MIDI音符被按下时执行,类似游戏引擎中的KeyDown}
end on


on release
{这里的代码在有MIDI音符被抬起时执行,类似游戏引擎的KeyUp}
end on


on ui_control
{这里的代码在界面组件和用户交互时(拖动了推子,点了按钮等)执行}
end on


内置变量

内置变量就是可以直接使用的变量,是系统定义并赋值的,我们通过它们从系统获取信息,比如当前哪个键被按着


了解内置变量前我们先了解一下MIDI,每次接收到MIDI信号都会触发on note回调函数,而每个MIDI信号被当成一个事件EVENT处理,每个事件都有自己的事件id,note number(哪一个键)和note velocity(力度),后两者都是0-127的数值。

1
2
3
4
5
6
7
8
{当前MIDI事件是哪个键被按下,返回0-127的数值,多用在on note里}
$EVENT_NOTE

{当前MIDI事件按下的键多少力度,返回0-127的数值,多用在on note里}
$EVENT_VELOCITY

{(常用)用来给set/get_engine_par接口指明调整/获取什么地方的参数 具体参数多是从KSP官方参考手册上查阅得来}
$ENGINE_PAR_相应功能
点此查看更多内置变量
$EVENT_ID – 当前MIDI事件的ID,一些设计中会用到,多用在on note里
$PLAYED_VOICES_INST – 有多少个键在被按下
$NOTE_HELD – 音符正在被按着就是1,没被按着就是0
$ALL_GROUPS – 表示操作全部组,放在接口参数里用来操作全部的组


功能接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{设置界面高度为几个格子}
set_ui_height(4)

{把界面组件放到指定格子的位置}
move_control(界面组件,1,1)

{在主音色窗口显示我们写的界面组件。写代码时预览窗会显示但回到主音色窗口就不显示,需要加这句}
make_perfview

{输出信息到Kontakt最下面的状态栏 一般用来Debug}
message("输出信息")

{设置旋钮显示的单位}
set_knob_unit(界面组件,表示单位类型的内置变量)

{设置旋钮默认的值}
set_knob_defval(界面组件, 默认的值)

{让kontakt自动保存和载入这个组件或变量的值}
make_persistent(界面组件/变量)

{设定指定的系统参数}
set_engine_par(表示要调整的地方的内置变量,数值,作用于第几个Group,第几个效果器,哪个效果条)

{获取指定的系统参数}
get_engine_par(表示要获取的地方的内置变量,作用于第几个Group,第几个效果器,哪个效果条)
点此查看更多功能接口

set_key_color(按键MIDI编号,颜色) – 设置一个MIDI按键的颜色

play_note(哪个键,力度,样本偏移,持续多少毫秒) – 播放一个按键映射的采样,多用在on note里
ignore_event($EVENT_ID) – 无视当前MIDI事件,即按下去不发声,多用在on note里,之后常用play_note重定向

_get_engine_par_disp(表示要获取的地方的内置变量,作用于第几个Group,第几个效果器,哪个效果条) – 获取系统显示的参数

disallow_group(1) – 禁用或mute一个组,如果是全部组可用$ALL_GROUPS
allow_group(0) – 激活一个组

set_script_title("标题") – 设置代码编辑窗口的标题
set_ui_height_px(200) – 用像素设置界面高度
set_text(组件,新名) – 改显示名
set_knob_label() – 为旋钮设置显示内容

inc($myValue) – 自加,等同于$myValue = $myValue + 1
dec($myValue)) – 自减,等同于$myValue = $myValue – 1

wait(1000000) – 等待多少微秒,一秒等于1,000,000微秒

change_note($EVENT_ID, $EVENT_NOTE + 12) – 改变音高 本例中提高八度
change_velo($EVENT_ID, $EVENT_VELOCITY / 2) – 改变力度 本例中力度减半
change_pan($EVENT_ID, -1000, 0) – 移动摆位 本例中极左摆位
change_vol($EVENT_ID, -5000, 0) – 改变MIDI音量 本例中减少5db
change_tune($EVENT_ID, 50000, 0) – 改变音高偏移 本例中音高偏移50个cents



-> 格子
kontakt 的界面规格通常是由‘格子’来衡量的,而不是像素
来自 Toby Pitman 的图,演示了 Kontakt 的 ‘格子’,图中格子的 x, y 颠倒了,比如 1,2 那个位置的格子实际是 2,1

-> 哪个效果条
set_engine_par 和 get_engine_par 中最后一个参数的代表的不同效果条对应的值 Group:-1 Inseret:1 Send:0


流程控制

KSP中用来判断用的是’=’,用来赋值的是’:=’。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
on note

    {  if  }
    if($EVENT_VELOCITY>100)
        message("力度大于100")
    else
        message("力度小于100")
    end if


    {  while  }
    while($NOTE_HELD = 1)
        message("MIDI按键正在被按着")
    end while

end on
点此查看更多流程控制
{ select }
select($EVENT_NOTE)
case 60
message("按了C3")
case 62
message("按了D3")
end select


查阅文档

真实开发中,我们需要一个字典一样的手册用来参考。
点此下载 Kontakt Script 官方参考手册 提取码: 8aqr
(若你所在的区域无法使用百度网盘,点击这里查看NI官网版本
这手册像字典,不是用来看的,而是用来查的
下载下来,打开,然后按下快捷键 Ctrl + F (Mac是 command + F)
输入你要找的,比如 set_key_color,回车,阅读它的用法和各种颜色的代码。


-> 开始写代码

我说过KSP所有代码必须写在回调方法里,外部没有写代码的地方,那我们先写一个回调:

1
2
on init
end on

这个回调里是在音源被加载/打开时被 kontakt 执行的
我们先用 make_perfview 打开音源显示界面组件的功能,然后用 set_ui_height() 调节一下界面高度

1
2
3
4
5
6
on init

make_perfview
set_ui_height(6)

end on

点击代码框右上角Apply提交

再点扳手回到主界面看到,设置高度后背景显示出来了

界面

接着,我们定义一个旋钮,然后用 move_control 功能接口把它移动到 ‘1,3’ 位置的格子上


有个细节,kontakt 不用小数,而是用整数,表示 0% 到 100% 的 0-1 对应在 kontakt 里是 0-1 000 000 。
千分之称之为毫,千分之的千分之称为微,把 0-1 变成(0,1000000,1)这样的参数就达到了微分精度。
201x 年左右,一些银行的金融系统处理小数运算是先乘 100 算完然后除 100,这和编程语言的特性有关。kontakt 这么做可能是同样原因。

1
2
3
4
5
6
7
8
on init

...

declare ui_knob $RevVol (0,1000000,1)
move_control ($RevVol,1,3)

end on

运行,出现了一个旋钮

同样地,定义四个旋钮

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
on init

...

declare ui_knob $RevVol (0,1000000,1)
move_control ($RevVol,1,3)

declare ui_knob $RevTime (0,1000000,1)
move_control ($RevTime,1,5)

declare ui_knob $DelayVol (0,1000000,1)
move_control ($DelayVol,2,3)

declare ui_knob $DelTime (0,1000000,1)
move_control ($DelTime,2,5)

end on

我们看到,这四个旋钮下方的数值都没有单位,用 set_knob_unit 功能接口为它们加上单位。
这接口怎么用呢,文档里搜一下,找到 $KNOB_UNIT_DB 和 $KNOB_UNIT_MS 参数 是我们需要的。

1
2
3
4
5
6
7
8
9
10
11
12
on init

...

set_knob_unit ($RevVol,$KNOB_UNIT_DB)
set_knob_unit ($DelayVol,$KNOB_UNIT_DB)

set_knob_unit ($RevTime,$KNOB_UNIT_HZ)
set_knob_unit ($DelTime,$KNOB_UNIT_HZ)


end on

好了,现在就显示单位了

接下来我希望让 kontakt 保存这些旋钮,否则当用户关闭再打开这个工程时,这些旋钮全部会自动清零,无法保存在工程中。使用 make_persistent() 的功能接口,可以让某个组件被自动保存在工程中 (在编程中,这个步骤叫做 数据持久化)

1
2
3
4
5
6
7
8
9
10
11
on init

...

make_persistent($RevVol)
make_persistent($DelayVol)

make_persistent($RevTime)
make_persistent($DelTime)

end on

好了,现在我们定义了控制混响音量和时长的旋钮,以及控制延迟音量和时长的旋钮,接下来要实现混响和延迟的功能。

Kontakt Script 实现音频效果的方式主要是 -> 挂上kontakt内置的效果器 -> 用代码控制内置效果器参数

我们先挂上效果器吧,有三个位置可以挂,Group, Insert, Send。自己根据需求挂不同地方,这里我把效果挂在 Insert 里。挂在不同的位置会影响到后面使用 set_engine_par 和 get_engine_par 的最后一个参数的不同。

set_engine_par 和 get_engine_par 中最后一个参数的代表的不同效果条对应的值 Group:-1 Inseret:1 Send:0



我们来到InsertEffects标签页,点第一个效果框右下角的加号,挂上一个 Delay


然后同理,在第二个效果框上挂一个 Reverb


然后点击 Delay, 我们看一下,下面这些 Delay 的参数也就是在代码中用 get_engine_par 和 set_engine_par 交互到的参数。


Reverb也是同理。
我们根据自己的混音经验预先调整好一些,然后选几个常用的让我们写的界面组件控制。

用界面控制效果器

现在编写让界面组件和效果器的参数交互的代码。

那么,要把代码写在什么地方呢?我之前说过,ksp 的代码只能写在回调方法里,之前的代码都是写在 on init 回调里的,这个回调只在一开始打开 kontakt 时执行。
如果要让一段代码在每次旋钮转动时就设定效果器的参数,那显然不能写在 on init 里。因为 on init 里的代码只在音源一打开的时候执行一次,之后就不再执行了。
细心的同学应该看到,在上面的‘ -> 认识KSP脚本 的 回调方法 里’,还有 on ui_control 这个回调,它在界面组件和用户发生交互时执行,那就把代码写在这个回调里。

1
2
3
on ui_control($RevVol)
    set_engine_par($ENGINE_PAR_SEND_EFFECT_OUTPUT_GAIN, $RevVol, -1, 1, 1)
end on

首先,我们用 on ui_control() 后面的括号指定了是当 $RevVol 这个旋钮转动时才执行它里面的代码

然后我们用了

set_engine_par

来看一下这个接口的参数:


1.第一个参数是指定设置系统哪一种参数,这里的 ‘$ENGINE_PAR_SEND_EFFECT_OUTPUT_GAIN’ 它告诉 kontakt,我要控制的是效果器 wet 量,也就是效果器的效果音的成分。

2.第二个参数,就是指把要设置的系统参数改成什么值,同样也是 0-1000000 的范围,这个范围在 kontakt 里代表 0% 到 100%。这里我们直接把旋钮扭到的值赋给 wet 量,也就是效果音的量。(这就之前为什么把旋钮的取值设定为从0-1000000 : declare ui_knob $RevVol (0,1000000,1)

3.第三个参数为操作哪一个组,0代表第一个组,1代表第二个组,以此类推。这里我们的效果器是放在 Insert 里的,它作用于全局,不属于任何一个组,所以这里填上 -1。

4.第四个参数就是第几个效果块,英文管这叫slot中文是插槽的意思。看一下上面那张图,在 InsertEffects 标签页中,我们之在第一个插槽上挂的是Delay,第二个插槽上挂的Reverb。所以Reverb在第二个,那这里就写1。(0 代表第一个,1 代表第二个)

5.最后一个参数就是指在哪个效果条(标签页)里了,下面这张图在本文是第三次出现了。我是把效果器挂在InsertEffect标签页里的,这个标签页对应的的是1。这里就写1。

set_engine_par 和 get_engine_par 中最后一个参数的代表的不同效果条标签页所对应的值 Group: -1 Inseret: 1 Send: 0


好,以此类推,我们再连接第二个旋钮

1
2
3
on ui_control($RevTime)
    set_engine_par($ENGINE_PAR_RV2_TIME, $RevTime, -1, 1, 1)
end on

和第一个按钮的代码一样,只是把参数换成了 $ENGINE_PAR_RV2_TIME,这是我在操作手册中查到对应混响时间的参数。

接着连接控制Delay的两个旋钮。

1
2
3
4
5
6
7
on ui_control($DelayVol)
    set_engine_par($ENGINE_PAR_SEND_EFFECT_OUTPUT_GAIN, $DelayVol, -1, 0, 1)
end on

on ui_control($DelTime)
    set_engine_par($ENGINE_PAR_RDL_TIME, $DelTime, -1, 0, 1)
end on

试一下,起效果了!


显示数值

等等,我们发现旋钮下方显示的值不太对,显示的是 0 – 1000000 的值,之前提到过,这个值在 kontakt 里 代表 0% 到 100%。

但是在 InsertEffects 里 Reverb 参数中的显示却是正确的。

于是我想把这里显示的值拿到旋钮下面显示,就用到这个接口:_get_engine_par_disp,它是获取系统参数显示出来的值,而不是对应 kontakt 的 0% 到 100% 的 0-1000000 的微分值。
获取到以后 用 set_knob_label 放到旋钮上显示

1
2
3
on ui_control($RevVol)
    set_knob_label($RevVol, _get_engine_par_disp($ENGINE_PAR_SEND_EFFECT_OUTPUT_GAIN,-1,1,1))
end on

如此类推,应用到四个旋钮上,界面就全部搞定了。


练习:
已知如下接口可以设置效果器的旁通 bypass
set_engine_par($ENGINE_PAR_SEND_EFFECT_BYPASS, 0, -1, 1或0, 1) :关
set_engine_par($ENGINE_PAR_SEND_EFFECT_BYPASS, 1, -1, 1或0, 1) :开
你能否设计两个按钮来控制 reverb 和 delay 的旁通效果?

(答案见源码)


键盘色彩

我听了一下这个呼吸包,发现里面有一些呼吸声比较甜,还有一些则像是吸面条般的声音,我不知道是用在哪的。
然后我想把比较甜的声音标记出来,在键盘上面,把它们的位置标红。

这里用到 set_key_color 接口,打开手册查一下它

根据这些参数。我设置了一下整体键盘的色彩,之前在Ksp中更多功能接口里写了,inc($x) 是 $x 自加的意思 $x = $x + 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
on init
...
declare $keys
set_key_color(36, $KEY_COLOR_RED)
set_key_color(38, $KEY_COLOR_MAGENTA)
$keys := 40
while ($keys<52)
    set_key_color($keys, $KEY_COLOR_NONE)
    inc($keys)
end while

set_key_color(52, $KEY_COLOR_RED)
set_key_color(53, $KEY_COLOR_MAGENTA)
$keys := 54
while ($keys<72)
    set_key_color($keys, $KEY_COLOR_GREEN)
    inc($keys)
end while

set_key_color(72, $KEY_COLOR_RED)
set_key_color(74, $KEY_COLOR_MAGENTA)
$keys := 75
while ($keys<92)
    set_key_color($keys, $KEY_COLOR_CYAN)
    inc($keys)
end while
end on

Apply后,得到这样的效果

到这我们的音源就完成了。


扩展阅读


-> 如何录制多个Group 实现多套映射

我们之前在介绍 Group Editor 的时候提到过,多个 Group 可以实现音色切换、合成多个摆位的麦克风等功能。
这都是非常实用的功能,我们来看看是怎么做的。

1.先打开 Group Editor, 然后点击 Create Empty Group


 

2.选中新的 Group,双击可以重命名。

3.打开 Mapping Editor,先取消勾选 ‘AutoSel. Grp'(是默认选中的),然后勾选 ‘Selected Groups only’。

现在映射窗口里就是新的组了。
点击 Group1 再点新创建的 Group, 就看见映射窗的内容不同了(如果做过映射)。


-> Wave Editor 怎么用

同样地,之前在介绍 Wave Editor 时,我提到过,这个编辑器可以实现让一段不持续的音无限持续延长的功能。
现代作品中,这个技术在弦乐和人声比较常见,我们来看一下具体怎么做。

1.先把其他Editor关了,就打开 Mapping Editor 和 Wave Editor 两个窗口。

2.在Mapping Editor 映射窗里中选中一个音频块,然后就在Wave Editor里看到它了。
接下来开启 Sample Loop 的开关,然后就出现了设置Loop范围的黄色的区域,接下来拖动黄色区域调整Loop的范围就行了。

工欲善其事,必先利其器

在 kontakt 的 Script Editor 里写代码,除了最后 Apply 时知道对错之外,中途就完全没有任何信息。
选用一款合适的文本编辑器,可以大大提升我们的效率和体验。它可以像个助理一样告诉你代码的对错,提示你各种接口的用法,以及帮你自动输入变量和接口。
我们可以在外界用文本编辑器写好代码,写完再放到 kontakt 的 Script Editor 里运行。

打开 Sublime Text官网 下载对应你的系统的 Sublime Text -> 安装 -> 打开

然后在 Sublime Text 中 按下 Shift + Ctrl + P (Mac是 shift + command + P) 出现一个输入框,输入 install,选中这个 回车 (或点击它)

等一会 出现这个界面,

点确定,然后再次按下 Shift + Ctrl + P,输入 install, 选择 Install Package
等一会,就会出现了一个新的空的输入框,在新的输入框里输入 ksp,选这个,回车(或点击它)

等一会弹出这个界面,整个就安装好了。

用的时候在右下角这里选上 KSP 类型

就有代码提示和自动补全了

 

 

后记

一切技术在理解了以后都是简单的。如果我们觉得一个东西很厉害很高端,那只说明我们不够了解它。

WE CAN KEEP IT SIMPLE BABY, LETS NO MAKE IT COMPLICATED.
LABELS ARE SO OVERRATED, LETS NO MAKE IT COMPLICATED.

Complicated – Dimitri Vegas & Like Mike / David Guetta / Kiiara
  1. 1. Complicated – Dimitri Vegas & Like Mike / David Guetta / Kiiara

 

点击这里下载源码

课程进度60%

上海外滩 – StudioEIM // MapleStory
  1. 上海外滩 – StudioEIM // MapleStory
  2. 神木村 – StudioEIM // MapleStory
  3. MapleStory – StudioEIM // MapleStory
  4. Pantheon – StudioEIM // MapleStory
  5. 逐梦飞翔 – StudioEIM // MapleStory
  6. 魔法密林 – StudioEIM // MapleStory