Emacs中的那些动画效果
Table of Contents
Emacs被设计成可以快速且高可配置地操作文本. ASCII动画需要以一定的速度修改文本使之看上去就好像是动画一样. 很多表演者还使用Emacs来进行live code musical & visual(及其他类似)的表演. 在这些表演中,观众不仅可以在emacs中看到代码还能听到代码.
在我的现场编码表演(Repl Electric)中, 我为Emacs增加了很多动画效果,收到了很多表演者的反馈,并为emacs提供了一个机会来摧毁程序员所搭建起的表演建筑中的秩序与结构.
请记住,我们能看到通过代码来表达的想法,这看起来好像是变魔术一样,但是本质上这也不过是文本而已.
有点类似于沙曼陀罗那样不断的重复毁灭与重构.
1 Emacs动画的框架
Zone Mode是一个Emacs插件,它是一个屏保的框架提供了类似动画的效果.
http://www.emacswiki.org/emacs/ZoneMode
重要的是,它允许我们以当前buffer的内容作为输入开启动画,然后按下任意键后会停止动画并还原原始的内容. 这使得我们可以随意更改文文本而无需担心会损坏原内容. 到目前为止zone总是正确的恢复了原内容但是在zone的代码中有那么一段警告:
(message "...here's hoping we didn't hose your buffer!")
A nice property of taking our buffer as input is we are never quite sure what text will be there and hence the properties of the animation.
1.1 案例: 将所有字母转换为大写形式
下面这个函数会随机寻找buffer中的非空格字符,并尝试将其转换为对应的大写形式. 这个函数并不需要了解zone框架,这只是个很平常的操作active buffer的函数.
(defun zone-upper-case-text () (zone-fill-out-screen (window-width) (window-height)) (random t) (goto-char (point-min)) (while (not (input-pending-p)) (let ((wbeg (window-start)) (wend (window-end))) ;;Keep moving the char cursor until its not whitespace (while (looking-at "[ \n\f]") (goto-char (+ wbeg (random (- wend wbeg)))))) ;;If we are at the end of the buffer go to the last char (when (eobp) (goto-char (point-min))) ;;Read the char at the cursor (let ((c (char-after (point)))) (delete-char 1) ;; Remove the char (insert-char (upcase c))) ;; Reinsert with caps ;;Sleep (zone-park/sit-for (point-min) 0.1)))
1.2 Zoning Setup
我们可以覆盖所有其他的zone程序而只使用我们自己定义的zone函数. 当我们运行zone时,我们自定义的动画效果将会启动.
(eval-after-load "zone" '(unless (memq 'zone-upper-case-text (append zone-programs nil)) (setq zone-programs [zone-upper-case-text])))
1.3 Zoning Examples:
Zoning mode 自带了许多动画方式的例子:
http://www.opensource.apple.com/source/emacs/emacs-51/emacs/lisp/play/zone.el
- zone-pgm-jitter
- zone-pgm-putz-with-case
- zone-pgm-dissolve
- zone-pgm-whack-chars
- zone-pgm-rotate
- zone-pgm-rotate-LR-lockstep
- zone-pgm-rotate-RL-lockstep
- zone-pgm-rotate-LR-variable
- zone-pgm-rotate-RL-variable
- zone-pgm-drip
- zone-pgm-drip-fretfully
- zone-pgm-five-oclock-swan-dive
- zone-pgm-martini-swan-dive
- zone-pgm-paragraph-spaz
- zone-pgm-stress
2 基于Open Sound Control协议的动画
OSC是一种在网络设备之间传输数据的协议. Emacs有一个插件可以用来运行一个OSC服务器(http://delysid.org/emacs/osc.html). 因此,当我们收到某种打击信号时,我们可以发送个消息給Emacs,这样Emacs就能根据我们的音乐节拍而做出改变.
我在Repl-Electric的配置中设置了如下的OSC消息传送流:
[Supercollider] -> OSC -> [Clojure] -> OSC -> [Emacs]
Emacs中启动了一个OSC服务器,并且定义了两个回调函数用来更改window face number的颜色
(require 'osc) (require 'cl) (defvar osc-server nil "Connection to receive msgs" (defvar flip-state t) (defun osc-connect ( "Create an OSC server and bind our fallback functions" (when (not osc-server) (setq osc-s (osc-make-s "localhost" 4 (lambda (path &rest args) (c ((string-match "/beat" path) (progn (if flip-state (on-beat) (off-beat) (setq flip-state (not flip-s (defun osc-make-server (host port default-handler) "Settings for OSC server" (make-network-p :name "emacs OSC server" :host h :server t :service p :filter #'osc-f :type ' :family ' :plist (list :generic default-handler) (defun on-beat ( (custom-set-f '(window-number-face ((t (:foreground "deeppink" (defun off-beat ( (custom-set-f '(window-number-face ((t (:foreground "#FDDD0C" (osc-connect) (provide 'osc-server)
在Overtone/Clojure中发送信号:
(defonce emacs-client (osc-client "localhost" 4558)) (def emacs-trigger (on-beat-trigger 8 #(do (osc-send emacs-client "/beat"))))
下面是一个演示,随着Overtone beat更改括号和window number的颜色.
2.1 Synchronisation
考虑到存在细微的本地延时,我们还需要在 Supercollider, Overtone 与 Emacs之间使用一个定时信号.
这意味着,一切完成之后,Emacs动画可以随着音乐的节拍而变化了.
3 Sound in ASCII
现在我们可以通过Emacs获取音频数据并展示动画效果了. 我们还可以更进一步,使用ASCII来可视化这些音频数据.
通过 Overtone 或 SuperCollider,我们可以创建一个合成器,这个合成器会追踪音频信号的峰值和强度并数据发回给我们,然后我们再把数据以OSC信息的方式发送給Emacs.
#Triggers a Sin Wave Oscillator and sends signals about power/peak SynthDef(\pulse,{ var sig, chain, onsets; sig = SinOsc.ar(Rand(220.0,440.0)) *EnvGen.ar(Env.perc(releaseTime:0.5),Dust.ar(0.5))*0.7; Out.ar(0,sig !2); // chain = FFT({LocalBuf(512, 1)}, sig); onsets = Onsets.kr(chain,0.1,\power); SendTrig.kr(onsets); SendPeakRMS.kr(sig, 20, 3, "/replyAddress"); }).add; #Run the crazy synth above Synth(\pulse) #Forward the data on as an OSC message #to emacs ~host = NetAddr("localhost", 4859); p = OSCFunc({ |msg| ~host.sendMsg("/peakpower",msg[3], msg[4]); "peak: %, rms: %".format(msg[3], msg[4]).postln }, '/replyAddress');
在我们的Emacs OSC服务器上,还需要添加如下代码:
((string-match "/peakpower" path) (progn (with-current-buffer "flatiron.clj" (let ((sig (round (* 100.0 (first args))))) (message (format "%f" (first args))) (dotimes (n sig) (insert "▓")) (insert "▒░")) (insert "\n"))))
4 Repl Electric Emacs animations
我所有的Emacs动画都被用于该表演中了. 下面会列出表演所用到的源代码,一些截屏以及一些动画化时的一些技巧
下面是Repl Electic动画的一个演示:
4.1 End of Buffer
https://github.com/repl-electric/view-pane/blob/master/animations/end-of-buffer.el
在该动画中,文本慢慢地被打散,然后像有一阵风吹过一样,将字符吹散. 不时的还有些单词会被风吹散.
该动画有两个主要的阶段:
- 空间注入. 该阶段在保持文本可读的情况下对文本进行变形.它还能增强下一阶段扩展空格时的效果.
- 将空格转换为多个空格. 使用一个正则表达式匹配空格然后将匹配出来的空格替换为随机个空格. 这产生的效果就是代码中的字符好像随风飘散一样. 我试了很多方法改进这一步的速度,最后发现使用正则表达式才是最快的方式.
若移动文本的速度足够快,在开启了soft word wrap(这时Emacs会以word边界自动换行)的情况下,文本看起来就像是从屏幕左边重返并最终消失于buffer之外. 在未开启soft word wrap的情况下,动画效果就很糟糕了,Emacs会不断的在buffer左右边界之间跳动
其他用到的技巧包括:
- 持续增加的整数. 模拟注射运动(injecting movement)时很有用,也可以作为一个连续值传递給sin/cos函数.
- 保持源代码的语法高亮,以试图保留代码的意义.
4.2 The Stars
https://github.com/repl-electric/view-pane/blob/master/animations/the-stars.el
这是我实现的第一个动画,它严重依赖于zone-pgm-drip-fretfully.
它随机选择一个字符,然后把它屏幕下发哪个移动,知道它撞倒另一个字符或者移动到屏幕外面
当同时运行Emacs + Overtone + OpenGL时, Emacs开始变得很慢,因此实现该动画效果所面临的一个挑战是如何让Emacs运行的经可能的快
当关闭OpenGL锐化效果后,动画的速度会开始变快. 这样代码自毁的就更快了.
4.3 Waves
https://github.com/repl-electric/view-pane/blob/master/animations/waves.el
该动画尝试模拟波浪的效果,方法是开启line wrap功能,然后通过增加删除随机长度的字符来让各行看起来是以不同的速度在移动着.
5 Breaking Tools
虽说强迫Emacs去作它不擅长的事情看起来很可笑,但是你要善于发掘工具的潜力,这一点很重要. 不要被他们原本的设计所限制,发掘你自己的使用方式吧.