录音/制作/创作 吉他 扩声技术 视频技术 作品展示 生活 信息 更多... | 音频应用专卖店
Sonar / Cakewalk

[教程] CAL编写教程(打造自己独特的MIDI系统)

( 90 )
 
[收藏]
-  第 6 页  -

231
#76 15-3-4 20:20
LZ辛苦,这帖一直看下来有种进了看雪学院的感觉

1158
#77 15-3-6 18:01

CAL教程11(实例讲解:中级四)

本篇我们来看看如何在一个CAL脚本中钳入多个CAL脚本。在讲解这个功能之前,我们先来讲一个十分有用,大家在MIDI制作中不可或缺的CAL功能。
那就是选取功能,虽然在我们的软件中,Cakewalk 历代版本中都有一个叫作查找替代的插件(也有查找插件),
但它使用起来相当麻烦,并且有很多相当重要的查找功能,它是不具备的。
下面,
我们就用CAL钳入指令,以及前边的实例中提到过的指令来完成一个很重要的,但软件本身并不具备的功能。
即:每小节中指定位置的选取


—————————————————————————————————————————————————


我们有了这样一个需求后,首先就是要尽可能的分析这个工具需要设计出来的各个功能,
当然,这也可以在使用过程中慢慢完善。那么,我们至少要设计出常用的各个方面。


MIDI制作老手都清楚(特别是一些从事扒带后期检测工作,或是带学生的老师),
在曲子大概完成后,其实曲中依然存在大量问题,或是错误,或是有可以提升的地方,
而这些错误与提升,往往又出现在十分规律的位置,
比如我们发现某段(甚至全曲)吉他扫弦中,每小节的第3拍都少了一个节奏,或是多了一个节奏,
或是音符时值效果不佳,我们要对其进行处理,那么首先就是要选中它们才行,
如果一小节一小节的去选(很多制作人就是这样做的),毫无疑问,这是十分痛苦的。


有些制作人就会想,如果可以批量的,直接选取到这些很规律的位置就好了。
我们在之前说过多次,在MIDI方面,CAL是无所不能的……


现在,我们就来设计一个CAL,它需要有以下功能:
1、选取每拍中,某TK位置的事件
2、选取每小节中,某拍的事件,或某几拍事件。
3、每间隔一小节选取事件
有了它,我们的后期调整与修改,将会变得十分方便简单了。


但是这里有一个问题,我们的CAL语句中,并没有选取指令,怎么才能实现选取这个目的呢?
其实使用过删除与插入功能的朋友就不难发现,
我们插入的事件,在CAL完成操作后,是处于未被选中状态的,
而我们未进行过删除与插入操作的事件,则依然保持着被选中的状态,
这就为我们实现选取这一效果提供了可能性。


是的,我们要做的,就是将需要选取的事件,作(不进行任何操作)的操作,
而对不需要选取的事件,执行两个操作:
1、插入一个与当前被扫描事件一模一样的事件。
2、删除这个正在被扫描的事件。
由此,我们就可以实现选取这个目的了。


—————————————————————————————————————————————————


例1:定位选取工具(母CAL)


(do
(int KG)
(getInt KG "+TK / -P" -234 32767)
(switch KG
  0 (include "定位选取_0.cal")
  -1 (include "定位选取_1.cal")
  -2 (include "定位选取_1.cal")
  -3 (include "定位选取_1.cal")
  -4 (include "定位选取_1.cal")
  -12 (include "定位选取_12.cal")
  -13 (include "定位选取_13.cal")
  -14 (include "定位选取_14.cal")
  -23 (include "定位选取_23.cal")
  -24 (include "定位选取_24.cal")
  -34 (include "定位选取_34.cal")
  -123 (include "定位选取_123.cal")
  -134 (include "定位选取_134.cal")
  -124 (include "定位选取_124.cal")
  -234 (include "定位选取_234.cal")
  KG (include "定位选取_TK.cal")
)
)
这个CAL在执行后,会弹出一个对话框,要求输入具体数值,
即:
1、输入正值:进行对每拍指定TK位置的选取。
2、输入负值:进行对每小节某拍,或某几拍位置的选取。
3、输入零值:进行间隔小节选取。


大家可以看出,这个CAL并没有删除指令与插入指令,是的,它并不具备任何功能,
它只是一个母CAL,当我们输入相应的数值时,程序将执行相对应的子CAL文件。
而这些子CAL才是具体功能的执行程序。
接下来,我们就要编写一系列子CAL,并将它们与母CAL放在同一个地址下,
如此,我们就可以实现具体的操作。

—————————————————————————————————————————————————


例2:间隔小节选取(子CAL)


(do
(dword T)
(forEachEvent (if (== T 0) (= T Event.Time) NIL))
(forEachEvent
  (if (== (% (- (meas Event.Time) (meas T)) 2) 1)
   (switch Event.Kind
    NOTE (do (insert Event.Time Event.Chan NOTE Note.Key Note.Vel Note.Dur) (delete))
    PATCH (do (insert Event.Time Event.Chan PATCH Patch.Num Patch.Bank) (delete))
    CONTROL (do (insert Event.Time Event.Chan CONTROL Control.Num Control.Val) (delete))
    WHEEL (do (insert Event.Time Event.Chan WHEEL Wheel.Val) (delete))
   )
   NIL
  )
)
)


例3:每拍指定TK位置选取(子CAL)


(forEachEvent
(if (== (tick Event.Time) KG)
  NIL
  (switch Event.Kind
   NOTE (do (insert Event.Time Event.Chan NOTE Note.Key Note.Vel Note.Dur) (delete))
   PATCH (do (insert Event.Time Event.Chan PATCH Patch.Num Patch.Bank) (delete))
   CONTROL (do (insert Event.Time Event.Chan CONTROL Control.Num Control.Val) (delete))
   WHEEL (do (insert Event.Time Event.Chan WHEEL Wheel.Val) (delete))
  )
)
)


例4:每小节指定某一拍的选取(子CAL)

(forEachEvent
(if (== (beat Event.Time) (* KG -1))
  NIL
  (switch Event.Kind
   NOTE (do (insert Event.Time Event.Chan NOTE Note.Key Note.Vel Note.Dur) (delete))
   PATCH (do (insert Event.Time Event.Chan PATCH Patch.Num Patch.Bank) (delete))
   CONTROL (do (insert Event.Time Event.Chan CONTROL Control.Num Control.Val) (delete))
   WHEEL (do (insert Event.Time Event.Chan WHEEL Wheel.Val) (delete))
  )
)
)


例5:每小节第一二拍的选取(子CAL)

(forEachEvent
(if (|| (== (beat Event.Time) 1) (== (beat Event.Time) 2))
  NIL
  (switch Event.Kind
   NOTE (do (insert Event.Time Event.Chan NOTE Note.Key Note.Vel Note.Dur) (delete))
   PATCH (do (insert Event.Time Event.Chan PATCH Patch.Num Patch.Bank) (delete))
   CONTROL (do (insert Event.Time Event.Chan CONTROL Control.Num Control.Val) (delete))
   WHEEL (do (insert Event.Time Event.Chan WHEEL Wheel.Val) (delete))
  )
)
)

例6:每小节第一二三拍的选取(子CAL)

(forEachEvent
(if (|| (== (beat Event.Time) 1) (|| (== (beat Event.Time) 2) (== (beat Event.Time) 3)))
  NIL
  (switch Event.Kind
   NOTE (do (insert Event.Time Event.Chan NOTE Note.Key Note.Vel Note.Dur) (delete))
   PATCH (do (insert Event.Time Event.Chan PATCH Patch.Num Patch.Bank) (delete))
   CONTROL (do (insert Event.Time Event.Chan CONTROL Control.Num Control.Val) (delete))
   WHEEL (do (insert Event.Time Event.Chan WHEEL Wheel.Val) (delete))
  )
)
)

以上,对两个拍子的选取,与对三个拍子的选取,都只举了一个例子,大家可以轻意的旁通,
只需要将母CAL中所对应的选项(子条件)都满足到即可。

—————————————————————————————————————————————————


完成了上诉一系列子CAL后,请一定要小心文件名,必须是母CAL中对应的文件名,
比如《0 (include "定位选取_0.cal")》本段语句,意思是当KG的值为0时,将执行“定位选取_0.cal“这个CAL脚本,
而我们已经设计了,当输入0时,我们需要的功能是间隔小节选取。
所以,我们编写的,间隔小节选取事件的CAL文件名,就必须是——定位选取_0.cal
其它功能所对应的文件,也必须如此为CAL文件命名。


完成后,我们只需要将母CAL指派给一个热键,当我们需要对事件进行某位置的选取时,
按下热键,输入相应的值即可进行操作。


旁通思路:
我们可以将事件的位置作为条件,从而对事件进行有针对性的选取,
当然也可以将事件的其它属性,作为选取的条件来进行此操作,
指定力度,力度范围,指定时值长度,时值长度范围……
甚至是特定的事件结构,比如在大量扫弦中,将刮弦结构选取出来,
比如将某种分解结构选取出来……等等,太多,只要能想出来的,只要是业务上需要的,
都能做到。


—————————————————————————————————————————————————


在此,说一些题外话,希望学习CAL编写的朋友,应该对这样一个概念充满希望与期待,
即——:改变软件环境,从而使软件来适应我们自己的需要,而不是我们去适应软件的功能。


CAL对这种需要,提供了最大的可能性,我们不用等着软件开发商来提供他们新出的功能,
他们只会考虑最大程度的兼容性操作,不可能对各种不同的业务进行操作上的细分与提升,
另外,对伸手党来说,等着会编写的老手放出CAL,本质上与等着软件商提升版本号一样,
都是很被动的……

—————————————————————————————————————————————————


中级篇就讲到这里,相信认真仔细学习到这里的童鞋们,已经可以编写自己所希望实现的功能,
对CAL的编写精髓,也已经有了一定的掌握。多练,多编写,将这种思维变成习惯,
只有在这种习惯中,火花才会爆发……


下一篇,我们将进入CAL的高级篇,在高级篇中,可能不再出现CAL的实例,
更多的,应该是从设计思路与MIDI编写经验上出发,来扩展大家对鼠标输入的认识。


待续……




[ 本帖最后由 溺水鱼 于 15-3-6 18:08 编辑 ]
观众反应
mao

1158
#78 15-3-14 04:38

CAL教程12(实例讲解:高级一)

本篇开始进入高级教程,之前的初级与中级教程,主要讲解CAL的编写方法与格式,

如果感觉已经掌握,就可以继续学习下面的教程了,
高级篇中主要讲解思路与创意,就不对具体的编写方法进行实例解释了。


—————————————————————————————————————————————————


其实说起来,我们在原创;编曲;或是扒带时,可以对一些操作方式稍作留意,
只有对软件操作产生了不满足的心态,才是CAL真正发挥威力的时候。


比如音符的写入,初学者都是老老实实的一个音一个音的用鼠标点写,然后拖动音尾到需要的时值长度,
在没有见过快速操作的情况下,大家都只能一样,也就是看谁的鼠标点得快了。
这样由鼠标去拖动音尾,不但费眼力,费手力,也十分费心力……
但是通过精心的CAL编写,这种写音方式直接被颠覆,这个CAL就是大名鼎鼎的《Legato.cal》音符粘合,
编写它的人明显是一个MIDI制作老手,只有真正经常用鼠标点写音符的人才会产生这种想法,
而软件开发团队,是很难直接产生这种想法的,因为他们不做MIDI,自然不会知道做MIDI的人的需要。


做音乐的人都知道,绝大多数音乐中,音符与音符之间,多数情况下都是一个紧跟着一个的,
只有在一些马口,或是齐奏等特殊位置,音符与音符才会因为情绪的需要,而有所缩短。
所以,高效率的操作,就应该首先满足这种最常出现的情况,如果我们只管点出音符的音高与起点,
那么鼠标的点写效率将大大提升,而且不再那么费眼力,手力,心力了。


有了这样一个想法,我们就可以去判断一下,这个功能都会涉及到哪些音乐元素,
这些音乐元素在CAL中有没有对应的指令语句,只要有与音乐元素所对应的指令,哪怕是间接的,
那么这个功能就多半可以实现。


—————————————————————————————————————————————————


接下来,我们就可以来思考音符粘合这个CAL的编写思路了:


首先找出所需要对应的元素,既然是音符与音符之间的操作,那么就只与音符这个元素相关,
音符在音乐中都存在哪些元素呢?做MIDI当然都知道,
那就是:音高,力度,时值,再加上所有事件都必须涉及的元素,起始位置。
那么几乎可以肯定,编写这个功能需要用到的指令,就是围绕着这四个元素来思考的。


进一步的思考:我们希望实现的功能是将前一个音符的音尾,自动粘合到下一个音符开头的位置,
这就涉及到两个音符,本质上就是两个音符的起始位置,如果我们打开一个MIDI文件,点一串音符,将更直观。


既然是希望前一个音符的音尾自动拉伸到下一个音头位置,最直接的方法就是这样思考:
当程序扫描到第一个音符时,因为前边没有音符,所以不需要执行音尾改变这个操作,
但是为了在之后进行拉伸操作,我们必须记录下这扫描的第一个音符的所有相关元素,


所以操作就成了这样:当程序扫描第一个音符时,执行:
将起始位置记录到自定义变量;
将音高记录到自定义变量;
将力度记录到自定义变量;
将时值记录到自定义变量(其实这个元素是可以不作记录的,大家最后理解了这个编写思路后就明白了)
将当前被扫描的这个音符删除掉。


由此,当程序扫描第一个音符时,这个音符的所有属性就被记录下来了,并且,它已经被删除,
所以我们在之后,就一定要触发一个写入这个音符的操作,
因为它的属性都被记录了,所以要再写入这个音符就很简单了。


继续思考,当程序扫描到第二个音符时,我们就触发写入之前第一个音符的操作:
执行插入一个音符:
音符的起始位置就用这前记录下来的用户自定义变量(这个变量记录了第一个音符的起点);
音符的音高,也用之前记录下来的自定义变量;
力度也是用这前用于记录的自定义变量;


只有时值长度要稍作改变,目前的事件起点位置,正好是所扫描的第二个音符的起始位置,
而我们需要的功能,就是要把第一个音符的音尾拉伸到第二个音符的音首处,
所以在插入第一个音符时,它的时值长度,就应该是:
当前正在被扫描的音符起始位置,减去之前记录下来的第一个音符的起始位置。


如此,第一个被删除的音符又被插入回来了,音高,力度,起点都没变,
只有长度变成了后一个音符减去第一个音符的距离。


注意,目前程序扫描只是进行到了第二个音符,还没有过去,
所以我们在插入了第一个音符后,应该继续将之前记录第一个音符属性的自定义变量,
统统用于 记录第二个音符。


到此,我们的CAL基本就编写完成了,效果就是在扫描后一个音符时,插入前一个音符,
并将音符长度通过两个音符起点相减的方式,使前一个音符长度变成粘合状态。
然后记录下这后一个音符的属性。


总结:
程序事实上等于是在不停的重复这样一个操作,
当扫描后一个音符时,触发前一个音符的写入,
并且将被扫描的音符属性记录下来,同时删除掉这个音符。


以上就是音符粘合功能的CAL编写思路,有点绕,不过如果能看着钢琴窗中的音符进行思考,就会清楚很多。


当这个CAL被编写出来后,我们写音符的速度就会相当高效了,
我们只管写音头,点完后,将这段音符框选起来,执行CAL,所有的音符就连在一起了。


—————————————————————————————————————————————————


这个CAL还有两个可以提升的地方,其实就是它的两个缺陷,
1、它只能处理单旋律(可以提升为处理双音以上的多音处理)
2、最后一个音符未作处理(可以提升为将最后一个音符的长度以光标为结束位置来处理)


[ 本帖最后由 溺水鱼 于 15-3-14 04:41 编辑 ]
观众反应
mao

4098
#79 15-3-14 07:50
赞!希望楼主共享一些编写好的cal命令。毕竟多数人不会编写

1158
#80 15-3-14 12:55

回复 海潮 在 #49 的 pid=4417203 的贴子

其实在本版挖挖坟,是可以收获很多CAL文件的,我在本篇结束后,也会将所有收集的CAL发放出来,大家可以选择自己需要的来使用。

1158
#81 15-3-14 15:06

CAL教程13(实例讲解:高级二)

本篇我们就来了解最著名的精典CAL——扫弦处理!在这个CAL没有诞生之前,编写吉他扫弦如恶梦般令人心生畏惧,单单一轨的扫弦音符就有数千上万个,
如果是多个扫弦轨,那将是不可思议的工作量,我以及我所知道的很多老手们,在初接触MIDI制作时,
就是一个个音符处理的!
虽然也掌握了许多利用复制粘贴来完成操作的高效手段,但那又怎么能与CAL相提并论呢!


这个CAL的编写难度近乎是所有CAL中最高的,所以只要我们能看懂这个CAL的编写思路,
那么几乎所有的CAL,我们也能从容分析了。


我之前以在高级篇中首先讲了“音符粘合”这个CAL,主要原因就是,扫弦CAL在思路上与前者相当类似,
只是在插入时有所不同,且因为是和弦的处理,所以需要多编写一段分析和弦音高的语句。
下面,我们就走进这个CAL的编写思路,掌握了它,相信任何功能,我们只要能想得出来,就能实现它。


—————————————————————————————————————————————————


首先,我们依然是分析功能与音乐元素之间的关系:
我们处理的事件只有一种,就是音符事件,所以要涉及的元素,也就只有四个:起点;音高;力度;时值。
但因为我们需要记录的是一个柱式和弦中所有的音符,那么在设计自定义变量时,就要考虑充分,
我们需要估计吉他在扫弦时,出现最多和弦数的情况,即——6根弦同时演奏。
那么我们的变量设定,就要有6组:
音高1;力度1;时值1;
音高2;力度2;时值2;
…… …… ……
音高6;力度6;时值6;

起点变量,与音符粘合的设定差不多,因为只涉及到前一个和弦与后一个和弦两个起点,
所以不用像另外三个变量那样设定6组。
另外,因为我们要判断一个柱式和弦有多少个和弦内音,并且要在判断的同时,将它们的属性记录到变量中去,
所以一个取值为1-6的开关变量必不可少。
其它还有什么变量,其实并不用在一开始就去设定,我们可以在中途碰上需要的时候,再在开始设定它们。


下面,来分析这个扫弦CAL的结构,它主要由4大块组成:
1、自定义变量设定。
2、和弦内音记录。
3、重置已经记录了音符属性的自定义变量,使其从大到小,或从小到大排列。
      总之就是要使用户明确,哪个变量记录的是第几根弦。
4、插入用户希望得到的目标音符。


其中,第3步是一串相当庞大的语句段,之所以需要重置,是因为我们的程序在扫描每一个事件时,
虽然是从左向右逐一扫描的,但对于同时出现的事件,程序是依照写入的先后顺序来取得优先扫描权的,
也就是说,对于柱式和弦,程序并不一定是从高向低扫描,也不一定是从低向高扫描,
这意味着扫描的顺序,是类似随机乱序的,因为我们不可能记得成千上万的音符中,我们写入的先后顺序,
就算在小范围内记得,这也无法被CAL利用,因为CAL词句编写出来是死的,而我们写入音符时的顺序,
在每首曲子,或是每一段,甚至是每一小节都是有所不同的!一个死的操作顺序,是无法应对乱序操作的。


下面是这个CAL在进程上的逻辑:
首先使程序记录下前一组柱式和弦,并删除之,
然后对记录了音符属性的变量进行重置,使变量从大到小,或从小到大的排列起来,
我们不用知道具体每个变量所记录的具体值,但必须知道哪个变量记录的是第几根弦的属性,特别是音高!
当程序扫描到后一组柱式和弦时,插入前一组和弦,
在插入时,我们不用修改插入音高,就照记录下来的变量进行插入即可,
但是我们可以控制插入的起点位置与时值长度,这是产生扫弦效果的关键,
因为已经知道了哪个变量记录的是最高或最低弦,所以我们就可以从高到低,或从低到高的插入音符,
并将起点依次向后移动所需要的位置,时值作相应减短即可。


然后这个CAL程序会对每一组柱式和弦都使用上诉逻辑进行操作。
这就是扫弦CAL的进程思路。


总结:
当扫描后一组和弦时,插入前一组和弦,并记录下后一组和弦,且删除之。


—————————————————————————————————————————————————


大家都看出来了,是不是与之前那个音符粘合的概念很像!
这种记录下当前扫描对象属性的同时,插入前一个扫描对象的方式,
在高级的CAL的中是相当常见的,逻辑思维强一些的人甚至会编写出扫描后两个对象时才插入第一个对象的操作,
当然,不管这种编写思路再复杂,本质上都是同一种处理概念:
就是先记录前一个对象,当扫描并记录后一个对象时再触发对前一个对象的处理。


[ 本帖最后由 溺水鱼 于 15-3-14 15:08 编辑 ]

1158
#82 15-3-15 20:33

回复 溺水鱼 在 #1 的 pid=4401232 的贴子

40楼:CAL教程07(实例讲解:中级一)上
42楼:CAL教程08(实例讲解:中级一)下
44楼:CAL教程09(实例讲解:中级二)
45楼:CAL教程10(实例讲解:中级三)
47楼:CAL教程11(实例讲解:中级四)
48楼:CAL教程12(实例讲解:高级一)
50楼:CAL教程13(实例讲解:高级二)

1158
#83 15-3-16 23:13

视频教程:《音符粘合》

好吧,应大家要求,我终于还是录了一个视频,这种一边录,一边讲解,一边编写CAL,真的很头痛,希望这种方式真的可以帮到童鞋们!

注意本CAL的进程原理:
记录第一个音符的属性,然后是扫描后一个音符,插入前一个音符并记录当前音符的属性。

视频发放:
http://v.youku.com/v_show/id_XOTEyNDg0MDM2.html

[ 本帖最后由 溺水鱼 于 15-3-16 23:18 编辑 ]

1158
#84 15-3-19 02:06

回复 溺水鱼 在 #1 的 pid=4401232 的贴子

51楼:视频教程《音符粘合》

23
#85 15-3-25 17:22
着实看不懂

1565
#86 15-3-26 07:48

回复 溺水鱼 在 #50 的 pid=4417391 的贴子

请教一下老师,在你的箱琴扫弦视频演示里,似乎是用一组同样音高的音符先做成一个节奏型。然后在开始扫弦输入根音,生成和弦,再调整音高与数量。用之前的节奏型作为模板,把和弦做成那个节奏型是这样吗?上面的思路就直接把和弦做成节奏型。不知道我想的对不对。

1158
#87 15-3-28 02:50

回复 溺水鱼 在 #50 的 pid=4417391 的贴子

处理扫弦的CAL,只能针对已经做好节奏的柱式和弦进行处理。我在视频中展示的,还多了两个功能,即:
1、横向节奏克隆:将选区内最前边一小节的节奏,克隆给后面的多个单音,
视频中框选最左边的节奏,再框选那些根音,然后将所有的根音变成前边那个节奏,就是用到了这个功能。
2、纵向节奏克隆:将节奏放在和弦的上方,然后拷贝足够多的位置,
再同时框选上方的节奏与下方的和弦,执行CAL后,下方的和弦就会变成上方的节奏。
最后一步才是执行扫弦CAL。
我在教程中就没有讲节奏克隆这个CAL的设计思路了,主要是大家反应看不懂,可能是我的口才有限,
不过,如果前边这些已经出现看不懂的情况,再强大的设计思路,可能就更难理解了,只好停止了。

1158
#88 15-3-28 02:54

回复 杨飞宇 在 #52 的 pid=4425655 的贴子

这个,我也确实尽力了,没把大家讲明白,十分抱歉,在与之对应的另一个贴中,有本教程对应的CAL下载,
大家可以直接下载那些CAL程序,再在SONAR中将之设置为热键使用即可。

307
#89 15-4-19 23:49
感谢感谢再感谢!力争今年能看明白

!!!!

176
#90 15-5-10 07:32
马克以备学习!
您需要登录后才可以回帖 登录 | 注册

本版积分规则

搜索