上海首家 Ableton 官方认证教育中心 GateMusic 門音乐正式上线

Steinberg UR-C 细节全知道!第三期:驱动

Tegeler Audio Manufaktur VTRC 评测:「昂贵」的声音长这样吗?

喜大普奔:midifan.fun 音乐人欢乐社区 iOS 和 Android 应用下载起来!

2020年10月号《Midifan 月刊》技术刊物上线,戳这里阅读

Reaktor Core Cell搭建日记(二)

分享到微信朋友圈

· 曾照南 添加于 2017-01-13 · 共有 1 条评论

作者:曾照南

Reaktor的事件模块也是非常有趣的,我总是很喜欢去搭建这么一些非常基础的模块,因为它能让我了解的更多,像今天我要来搭建一个大家都非常常见的模块,它就是Accumulator模块(图1.1),它是用来累计事件数值的总和。

首先,需要先了解一下这个模块的功能,从图1.1可以看它有两个输入和一个输出,In输入是用来接收数值事件的,而Set输入则是用来改变其模块内部的数值状态,举个例子,假设模块先接收到一个数值1的事件,接着又接收到一个数值2的事件,那么模块最终输出的是一个数值3的事件,如果这个时候Set输入接收到一个数值0的事件的话,那么模块将会输出一个数值0的事件。



图1.1

通过上面的举例,现在我们已经知道Accumulator模块的具体运作原理了,因此,让我们开始在Core Cell里搭建一个Accumulator模块吧,首先我们需要添加一个Core Cell,并将其命名好,然后在其内部添加相应的输入和输出。(图1.2)


图1.2

好的,我们现在要来考虑怎么搭建好一个Accumulator模块,我们知道Accumulator模块是用来累计事件数值的总和,因此我们需要累计的模块,顾名思义肯定是加法模块(注意如果没有特别指明,我这里的加法模块指的是Core Cell内的加法模块);但除了加法模块之外还不行,因为我们要将前面累计的数值加上最新的数值,因此我们需要把累计的数值保存起来,所以一个非常好的选择当然就是Read和Write模块,这样我们就可以这样来搭建了。(图1.3)



图1.3

这个搭建非常简单,In输入接收到的数值事件不仅到达加法模块的第一个输入,并且还触发了Read模块读取Write模块写入的数值,接着加法模块将其两个输入累加后的数值事件发送给Merge模块,最终Merge模块再发送最终的数值事件到达最终输出,注意Merge模块不仅仅发送了最终的数值事件,而且它还把最终的数值事件发送给Write模块执行重新写入,因此Write模块此时的数值状态是累计后的数值状态,这样一来反复触发,Write模块就会把最新事件的数值跟前面事件累计后的数值加在一起,当然这里别忽略了Set的作用。

其实很多人认为搭建到上面那样已经算是完成了Accumulator模块的具体功能了,但我知道它还没有真正完成,我们来看下下面这张图。(图1.4)


图1.4

上面这张图显示的是搭建后模块和原有模块在初始化后的最终输出状态,我们可以看到,我们搭建的Accumulator模块跟原始的Accumulator模块最终输出的数值不一定,也就是说,我们还需要对刚刚搭建Accumulator模块考虑初始化的情况。

好的,我们来考虑一下它的初始化状态,因为我们知道原有的Accumulator模块输出的是3,这意味着说结果是In和Set两个事件数值的和,那么我们来假设一下怎么样才能得到这种结果,因为对于Primary Level的事件处理,它们是没办法同步,也就是事件的处理是一个接着一个,所以我们能够来假设两种情况:In先处理然后Set处理和Set先处理然后In处理。
首先我们来考虑第一种情况:In先处理然后Set处理。
假设In的事件先处理,那么可想结果肯定是输出数值2事件,接着Set再处理的话,那么可想结果肯定是输出数值1事件,所以最终输出事件数值是1,显然这个不是我们想要的结果。

接着我们再来考虑第二种情况:Set先处理然后In处理。
假设Set的事件先处理,那么可想结果肯定是输出数值1事件,接着In再处理的话,那么可想结果肯定是输出3事件,所以最终输出事件数值是3,显然这个是我们想要的结果。

等等,这里的第二种情况很多人可能会看懵了,会疑问为什么会是3,其实很简单,因为Set数值事件改变了Write模块一开始的数值状态,所以在In事件到达后,Read模块读取的事件数值不是0,而是1,并跟In事件的数值相加,因此结果输出的事件数值肯定是3,所以我们现在要想想怎么让Set处理发生在In处理之前。

可是怎么才能让Set处理发生在In处理之前呢?可实际上我忽略了在Core Cell不能这样来考虑问题,尽管Core Cell也可以认为是依次处理事件,但在理论上确实可以认为是同步的,这也是Core Level和Primary Level不一样的地方,所以说尽管两个事件我们可以考虑是先后发送到Accumulator模块,但这两个事件在Core Cell的处理是同步的,因此我们上面假设的两种情况完全是认为处理事件是有时间的顺序来理解的,如果你还没看明白我在说什么,那么我来看下面这张图:(图1.5)


图1.5

是不是觉得上面这张图跟图1.3很相似的,确实它们可以说是一模一样的,唯一的差别在于Merge模块上的不同,所以结果也会有所不同,我们知道Merge模块是无论你同时接收到多少事件,最终只能输出一个事件,而这个事件一般都是Merge模块最底下输入的事件,所以细心的你要是自己测试一下,你就会知道这两次的结果是不一样的,图1.3的结果是1,而图1.5的结果则是2,也就是说从图1.3和图1.5我们得出的结论是加法模块发送的事件跟Set发送的事件是同时到达Merge模块,因此对于图1.5的情况,我们只要让加法模块发出的事件数值是3就可以了,那么怎么才能让它发送的事件数值为3呢?显然这个跟Read模块是有关系的,因为一开始Read模块读取的数值是0,这是Write模块初始化的结果,我们可以这样来想,如果我们能够改变这个初始化的状态,就能够改变最终的结果,也就是说我们是否能改变Write一开始被写入的数值是1呢,如果可以的话,Read模块一开始读取的数值就会是1,那么它跟In发送的数值事件相加就会得到我们要的结果3,这样最终的结果也将会是3。

其实这个对我来说一点难度都没有,我只需要在添加一个Write模块,一是让它跟Read模块连接,二是用来提前写入Set的数值,结果搭建如下图显示(图1.6)。

图1.6


我们可以看到这里有两个Write模块,很多人在这里往往会被搞混掉,但注意,它们其实共享都是同一个内存块,对于第一个Write模块,由于它比Read模块先处理,所以它会先存储Set发送的事件数值并改变其共享内存的状态,接着Read模块将会从内存中读取数值,由于读取的是Set发送的事件数值,所以加法模块相加的就是In和Set的数值之和,因为在上面我们提到可以认为加法模块和Set发送的是同步到达Merge模块,因此,对于Merge模块处理同步事件仅能发送一个事件到其输出,我们可以知道Merge模块将会发送加法模块的结果,而这个刚好就是我要的结果。

好的,从图1.6我们可以知道,一个Accumulator模块算是真正完成了。

第二天

有了Accumulator模块的搭建经验,我们可以把它扩展到Counter,所以我们接下来要来搭建Counter。(图2.1)



图2.1


一开始我们还是要来分析下Counter模块的特性,首先我们从图上可以看出Counter模块跟Accumulator模块是很相似的,唯一的不同在于Counter模块把In的部分更细化了,分成了Up和Down(模块简称Dn),意思就是说Up输入只要有事件发生将会触发Counter输出一个数值增加的事件,而每次数值增加的最小刻度是1,同理,Dn输入则是数值减小,但有个问题是,Up和Dn输入对到达的事件有什么具体要求吗?答案当然是有的,到达的事件数值必须大于0,也就是说如果到达事件数值小于0的话,Counter模块是不会产生任何事件。

好的,了解Counter具体原理之后,接下来要来搭建就没有什么大问题了,所以最终的搭建如下图所示:(图2.2)

图2.2


我们来从左到右分析一下,首先我们用到了一个判断事件数值是否大于0的模块,注意这个模块不是最基本的模块,它实际上是一个Macro,利用用到了Compare比较模块和Router路由模块;接着,用到了Latch模块,它也是一个Macro,用来当有事件发生才能去触发其输出事件,注意Up和Dn连接的Latch模块上方输入的数值是不同的,Up是用来累加的,而Dn是用来减少的,所以它们分别针对的数值是1和-1;然后我们还需要把两个Latch的输出通过Merge模块连接在一块,最后连接到我们最熟悉的部分,也就是我们已经有搭建过的Accumulator模块,所以由此可以看出Counter模块实际上跟Accumulator模块原理是非常相似的,既然如此的话,为了让整个搭建看来更直观,我们可以把Accumulator部分用Macro封装起来,然后再把触发数数也用Macro封装起来,结果整理如下图所示。(图2.3)

图2.3


其实对于我个人的习惯,我是比较喜欢图2.3整理后的,这样一来我能更明白每个搭建具体的作用,而且对于理解搭建也会显得比较直观,不会因为模块一多,连线一杂,就降低了对自己搭建模块的可读性。


第三天

除了Accumulator模块和Counter模块之外,还有一类事件是用作逻辑运算,通常性这类就是用来判断事件的真假,也就是True和False,如果具体到数字的表达,那么True就是1,而False就是0,当然这是最清楚的表示方式,但实际它们是针对整个实数的,也就是说只要是大于0的都是True,而小于等于0的都是False。

一般基础的逻辑运算包括四种:AND(与)、OR(或)、EXOR(异或)、NOT(非)。(图3.1)

图3.1


从上图我们可以看到Primary提供了四种逻辑运算模块,它们有一个共同的地方,那就是Not输出,这个实际上就是NOT逻辑运算,那么再来看看Core为我们提供了什么。(图3.2)

图3.2

很好的是,Core也为我们提供这四种运算,不过相比Primary提供的,Core提供是更为基础的,我们可以看出Core的每个逻辑运算模块的输入和输出都是整数的,它们实际上就是位的运算,对于每一个整数,它都会转换成二进制再来进行位的运算,那么这里我们就要开始考虑了,对于逻辑运算,我们的数值是要针对整个整数集合,还是说限制在某个数值上,比如0和1;其实在上面我们开始就说表示事件真假最直接的数字是0和1,所以说无论我们处理的事件数值是多少,我们都要先把它转换成0和1,然后再来进行位运算,这样我们就能省去很多不必要的麻烦。

那么怎么才能把事件的数值转换成0或者1呢?这个就需要对事件的数值进行判断,我们上面也提到了大于0的代表True,而小于等于0的代表False;好的,有这样的一个思路,我们就能很好地来搭建一个逻辑运算模块,比如先从AND模块开始搭建。

首先我们要判断,具体的做法就是新建一个Macro,并命名为Gate,然后利用Compare、Router、Latch等模块来实现一个Gate的功能,搭建结果如下图所示:

图3.3


从图上我们可以看出Gate的思路很简单,就是先要判断事件数值是否大于0,如果大于0的话,就触发一个数值1的事件,如果小于等于0的话,就触发一个数值0的事件,这样我们就可以很轻松地搭建出AND模块了。(图3.4)

图3.4


OK,接下来我们还要再解决一个问题,那就是Not,它是跟结果相反的,也就是说如果两个逻辑事件结果是True的话,那么Not就是False,反之亦然。可能这里大家会想到NOT模块,因为它刚好是能用来对位取反,所以结果搭建如下图所示:(图3.5)

图3.5

但实际上上面的搭建最终会导致错误的,问题就出在NOT模块,尽管它是能把二进制数取反,可我们忽略了一个重要的信息,那就是计算机是以补码的形式来存储数值的,我们来举个简单的例子,我们假设是最简单的8位运算,AND模块的结果是1,那么对于1取反的结果就是11111110,那么它究竟是个什么数?其实它是-2的补码,因为-2的补码是先对-2取正取反再加1,而2的二进制数是00000010,取反后就是11111101,然后加1,就得到结果11111110,这个二进制数刚好跟我们上面1取反的结果是一样的,所以显然结果不是我们想要的;一个很好的解决方法就是我们可以再进行一次逻辑运算,刚好Core也为我们提供一个封装的NOT模块。(图3.6)

图3.6


从图3.6我们可以看到这里依然回归到0和1的逻辑运算,不同的是用到了EXOR模块,它是异或运算,它的运算原理很简单,如果1和1或者0和0得到的就是0,而如果0和1得到的结果就是1,因此对于上面NOT模块,我们就能清楚地知道,当输入是一个数值1的事件的话,跟下面数值1的异或运算就是0,而如果输入是一个数值0的事件的话,那么跟下面数值1的异或运算结果就是1,显然这个就是我们想要的结果。

好的,有了AND搭建的基础,其他逻辑运算模块的搭建就迎刃而解了。


第四天(小总结)

在搭建了Accumulator、Counter和几个逻辑运算模块后,我们来回想一下,所有这些搭建都有什么相似的东西?我们会发现所有这些都用到一些相同的模块,比如Compare模块、Router模块,还有Latch模块,而这几个模块可以说是非常重要的,在以后的搭建中都会经常碰到,比如在事件处理中,我们会用到Value和Separator这两个模块,一个是用来触发事件什么时候发送,另外一个是用来分离事件的,让事件有界限地通过,而恰巧这两个模块就能用到Compare模块、Router模块,还有Latch模块这三个模块来实现,比如Value模块就可以用到Latch模块来实现,而Separator就可以用到Compare模块、Router模块。(图4.1)


图4.1



第五天

如果上面的搭建对你来说都是小儿科的话,那么接下来要搭建或许就有点难度了,我们先来看看要搭建的是一个什么样的模块。(图5.1)

图5.1


看到这个模块,尤其是看到模块中间的图形,有没有觉得在哪里见过?是的,我相信每个学过数学的人都有画过函数吧,尤其是直线函数,而这个模块实际上就是一个分段函数,它能用来对事件数值进行重新定义,更确切地说,这是一个转换函数。那么问题来了,它具体的工作原理是什么?

首先对于Ctrl. Shp 1模块,它有四个输入和一个输出,In输入不用说了,但标签为0、50和100的输入它们代表什么呢?可能很多人一开始会认为标签就是它们具体的数值,如果我们在XY轴上来标识的话,实际上标签0、50和100指的是X轴的0、0.5和1,而它们接收到事件数值则是它们对应Y轴的数值,我们来看一下最简单的情况,也是对In输入事件数值不做任何改变的情况。(图5.2)

图5.2


图5.2上的A、B、C三点分别就是代表标签0、50、100对应的具体Y值,由于AB线段和BC线段在延长线上是重合的,并且X轴和Y轴对应的数值是一致的,因此我们可以理解它们是同一条直线,所以无论In输入的事件数值是多少,输出的结果将等于本身;那么我们再来看另外一种情况。(图5.3)

图5.3


我们看到线段已经发生了变化了,C点Y轴的数值不再是1了,而是0,这样一来,我们就可以知道一旦In输入的事件数值大于0.5后,输出的结果将不再等于本身,而是根据BC线段具体情况做变化,也就是说我们要具体知道BC线段的公式,已知两点求直线公式对我们来说是一件非常简单的事情,我们只要把已知两点的数值代入到直线公式即可求出。

好吧,让我们来求一下a和b,假设我们已知两点(x1, y1)、(x2, y2),把它们代入公式即可得到:

我想求一元一次方程组应该对大多数人来说不是一件很苦难的事吧,所以从上面我们可以得到:

紧接着我们不妨在Core Level搭建实现它,我们可以添加一个Macro,并命名为y=ax+b,然后增加x、x1、y1、x2、y2五个输入和y一个输出,结果搭建如下图所示:(图5.4)

图5.4


对于上面的搭建应该是没有太多问题,可能唯一有疑问的是x1-x2的部分,我们知道作为分母它是不能等于0的,如果等于等于0是无意义的,所以我们要确保的是x1-x2不能等于0,当然x1-x2永远都不会等于0,因为前面的描述我们能确定的是它们的数值已经确定了,它们差值的绝对值永远等于0.5。

实现了y=ax+b还不够,我们要需要对In输入进行数值分离,从图5.3我们能够知道这个分离点是0.5对应的Y值,所以说我们可以让大于0.5的用0.5和1对应Y值两点得出的直线来处理,让小于等于0.5的用0和0.5对应Y值两点得出的直线来处理。(图5.5)

图5.5


尽管上面的搭建已经实现了最终的效果,但连线让我们看起来有点混乱,所以其实我们可以整理下,让搭建看起来更直观明了些,实际上我可以把不变的数值放到一个Macro用来调用,比如可以搭建一个名为Const的Macro,用Pack把所有不变的数值打包起来用来以后的调用。(图5.6)

图5.6


好的,这样我们就可以重新把图5.5搭建重新整理如下图所示:(图5.6)

图5.6


可能很多人会觉得上面的搭建跟图5.5的搭建有差别吗?我觉得肯定是有差别的,尽管图5.5的搭建换成别人来看的话,在看懂所需要的时间不会很长,但相比图5.6的搭建来说,显然是比较吃力的,而图5.6搭建的好处在于我们把一些可能造成搭建可读性的东西独立起来,这对我们在搭建其他不同情况的模块是有帮助,比如我们把可控的点变成四个,不再是0、0.5和1,而是0、0.33、0.66和1,所以我们要考虑的是它的通用性,让搭建对可能增加的情况进行扩展,比如我们把所有情况都列出来。(图5.7)

图5.7



可下载Midifan for iOS应用在手机或平板上阅读(直接在App Store里搜索Midifan即可找到,或扫描下面的二维码直接下载),在iPad或iPhone上下载并阅读。

文章出处:http://www.midifan.com做人要厚道,转载文章请注明出自 midifan.com,谢谢

共有 1 条评论

添加评论