写给设计师的 Processing 编程指南10-媒体加载与事件
Processing 中可以载入许多外部数据。
其中有三类最为常用,分别是图片,音频,视频。
这节我们将结合事件,对音视频的加载进行详细展开。在最后,你可以亲自打造自己的音乐键盘,音乐画板。
读取图片
在开始前,先来回顾一下图片的加载方法。
与图片相关的函数
函数 | 说明 | |
---|---|---|
imageMode(mode) | 用于设置图片位置的现实模式,两个常用模式为,CORNER(将图片的左上角设为绘制坐标),CENTER (居中,图片的中心设为绘制坐标) | |
loadImage(String a) | 读取图片,a 为图片路径 | |
tint(ofColor c) | c 控制颜色分量,有多种重载方式 | |
image(PImage img,float a,float b) | img 是图片对象,参数a,b为图片的横纵坐标 | |
image(PImage img,float a,float b,float c,float d) | img 是图片对象,参数a,b为图片的横纵坐标,c,d 为图片的长宽 |
在使用这些函数前,需要先通过 PImage 创建图片对象。之后就能利用这些函数去设定图片的各类属性
程序运行前,别忘记在 data 文件夹内放入图片素材
音乐的载入,播放与暂定
下面开始正式介绍音乐的调用方法。与载入图片非常类似,开始需要先声明一个音频对象。具体语法可参看以下例子
代码示例(10-1):
import processing.sound.*;
SoundFile sound;
void setup() {
size(640, 360);
background(255);
sound = new SoundFile(this, "1.mp3");
}
void draw() {
}
void keyPressed() {
//播放音频
if (key == 'p') {
sound.play();
}
//暂停音频
if (key == 's') {
sound.stop();
}
}
准备工作:
Processing 本身是不带声音库的。需要自行安装下载。所以在写代码前,请做好以下准备。
在 Processing 中添加库,常规方式如下。在菜单栏选“工具” - “添加工具”,再切换到 - Libraries。在搜索框中输入库的关键字,即能直接下载安装。
但在国内使用此功能,直接联网一般无法下载,需要开启 VPN。但即使开启,也会存在不稳定的情况,需要耐心地尝试几次。通过此方式加载是最便捷的。如果仍是无法安装,就需要用手动的方式来到官网下载。(https://processing.org/reference/libraries/)手动安装的方法由于比较繁琐,会在另一篇文章中展开。
代码说明:
准备工作完成后,声音库便能正常使用。复制上面代码,点 RUN 即能运行。按 P 键音乐播放,S 键音乐暂停。
在程序中使用,需要先导入。在开头先加上一行 import processing.sound.*。import 作为关键字,它的字面意思就是导入。import 后面加上库的名称即可。末尾一般会跟一个 * 号,这样就能把库中的相关类都一并加载进去,而无需逐个手动添加
第二行的“ SoundFile sound; ”声明了一个音频对象。SoundFile 作用类似于 PImage。
setup 函数中的 “ sound = new SoundFile(this, “1.mp3”); ”,作用是创建对象,并指定音频对象的读取路径。这里其实已经开始用到新概念-类。我们可以先不深究。只要知道这是一种固定写法,并且最后一个参数填写音乐素材的地址
keyPressed() 事件中的 “ sound.play() ” 与 “ sound.stop() ” 分别起播放和暂停的作用。中间的 “.” 表示 play 和 stop 是属于音频对象的成员函数。成员函数可理解为这个对象中包含的函数,属于这个对象,是事先定义好的。当以后需要播放多个音频对象,只要在对应的变量名后加上.play()。
音频素材需放在 sketch (后缀为 pde) 文件同目录下的 data 文件夹内,没有的话可以手动新建一个
不要忘记写 draw 函数,尽管没有绘制任何图形,但要成功播放音乐,它是必不可少的。
上面的步骤说明看似繁琐,但其实只要几句代码就能实现播放功能,非常便捷。Processing 中支持常用的音频格式,如 mp3,wav,ogg 等。
音乐速度控制
下面的例子会开始变得有意思起来,Processing 中提供了一些函数可以控制音乐的播放速度。播放速度改变的同时,音调也会同时发生变化。当用鼠标来控制,会产生非常迷幻的效果。
视频地址(
https://v.qq.com/txp/iframe/player.html?vid=y1308mzqsaq&width=500&height=375&auto=0
代码示例(10-2):
import processing.sound.*;
SoundFile sound;
void setup() {
size(640, 360);
background(255);
sound = new SoundFile(this, "1.mp3");
}
void draw() {
float speed = mouseX/(float)width * 3;
sound.rate(speed);
float vol = mouseY/(float)height * 4;
sound.amp(vol);
}
void keyPressed() {
//播放音频
if (key == 'p') {
sound.play();
}
//暂停音频
if (key == 's') {
sound.stop();
}
}
代码说明:
.rate() 函数用于控制音频的播放速度。括号中的数值决定播放速度的大小。数值为 1 时,播放速度正常。数值大于 1 加速,小于 1 减速。
.amp() 函数用于控制音频的音量大小。括号中的数值决定音量值。数值为 1 时,音量值正常。数值大于 1 时音量增大,小于 1 减小。
这里创建了两个局部变量 speed 和 vol 作为参数的传入。因而鼠标的横坐标会改变音乐的音调,纵坐标会改变音量大小。
视频暂停与播放
Processing 中加载视频与加载音频类似,需要先下载 video 库。()
代码示例(10-3):
import processing.video.*;
Movie mov;
void setup() {
size(640, 360);
background(0);
mov = new Movie(this, "1.mov");
}
void movieEvent(Movie movie) {
mov.read();
}
void draw() {
image(mov, 0, 0,640,360);
}
void keyPressed() {
if (key == 'p') {
mov.play();
}
if (key == 's') {
mov.stop();
}
if (key == 'd') {
mov.pause();
}
}
视频截图:
代码说明:
第一行 “ import processing.video.*; ” 作用是导入视频库
第二行 “ Movie mov; ” 用于声明视频对象。其中“ Movie ” 作用类似于 PImage。
setup 中的“ mov = new Movie(this, “1.mov”); ”,作用是创建对象,并指定视频对象的读取路径。最后一个参数填写视频素材的地址
setup 函数后的 movieEvent 代表的是视频事件,用于更新和读取视频信息,事件中的 mov.read() 表示读取。
image 函数除了显示图片,还能显示视频。可以将视频对象看作一个动态的图片。第一个参数填写视频对象的变量名,第二第三个参数是视频绘制的横纵坐标。第四第五个参数决定视频显示的长和宽。
.play() 函数表示播放。.stop() 函数代表停止,并会将视频重置。.pause() 函数代表暂定,它会中断当前的播放,直到.play() 函数被调用,才会继续
视频速度控制
代码示例(10-4):
import processing.video.*;
Movie mov;
void setup() {
size(640, 360);
background(0);
mov = new Movie(this, "transit.mov");
}
void movieEvent(Movie movie) {
mov.read();
}
void draw() {
image(mov, 0, 0, width, height);
float newSpeed = mouseX/(float)width * 4;
mov.speed(newSpeed);
}
void keyPressed() {
if (key == 'p') {
mov.play();
}
if (key == 's') {
mov.stop();
}
if (key == 'd') {
mov.pause();
}
}
代码说明:
.speed() 函数可以控制视频的播放速度。当参数数值为 1 时,播放速度正常。数值大于 1 加速,小于 1 减速。
因为创建了局部变量 newSpeed 并传入了 setSpeed() 函数中,鼠标的横坐标便会直接影响视频的播放速度
有关视频的更多例子,可以参看范例库中的 Libraries - Video
Processing 常用事件一览
之前只介绍了 keyPressed() 事件,当它在键盘按下后就会触发。下面再介绍 Processing 中的其他最常用事件。
鼠标相关函数事件 | 说明 |
---|---|
void mousePressed() | 按下,鼠标点击时执行,只执行一次 |
void mouseDragged() | 拖拽,鼠标按下并移动,移动过程反复执行 |
void mouseMoved() | 移动,鼠标移动时执行,移动过程反复执行 |
void mouseReleased() | 释放,鼠标释放时执行,只执行一次 |
键盘相关函数事件 | 说明 |
---|---|
void keyPressed() | 按下,按下按键执行,只执行一次 |
void keyReleased() | 释放,松开按键执行,只执行一次 |
它们的使用方法和 keyPressed 类似,书写代码时无关先后顺序。换句话说,无论你将某个事件写在 setup 函数的前面还是后面,结果都是一样的。事件的执行顺序只与事件本身的触发条件有关。只有满足条件,才会执行。上面的事件都非常容易理解,只要略微实验一下就能迅速掌握用法。
事件流
我们可以通过一个实例,了解事件的执行顺序。
代码示例(10-5):
void setup() {
frameRate(2);
println(1);
}
void draw() {
println(2);
}
void mousePressed() {
println(3);
}
void mouseMoved() {
println(4);
}
void mouseReleased() {
println(5);
}
void keyPressed() {
println(6);
}
void keyReleased() {
println(7);
}
代码说明:
setup 函数中通过 frameRate() 函数把程序的运行速率设成了 2 帧每秒。降低帧率有助于我们观察控制台的输出。不至于触发事件后就迅速被新数据刷到后面了。
尝试移动鼠标,点击鼠标,释放鼠标。观察输出的结果。通过 println 来了解事件的执行顺序
值得注意的是,绘图函数一般不能写在除了 draw 函数的其他事件中。否则无法显示。若希望通过 keyPressed 之类的事件来控制图形元件的显示或隐藏,可以考虑创建布尔变量来作为中介
事件会按依序执行,只有当前一个事件中的所有代码执行后,才会执行下一个事件中的代码
综合示例-音乐键盘
结合新掌握的事件,我们就可以给程序添加新的交互。下面只要用几分钟的时间,就能简单地模拟一个音乐键盘。
视频地址:(
https://v.qq.com/txp/iframe/player.html?vid=a130872dluv&width=500&height=375&auto=0
代码示例(10-6):
import processing.sound.*;
SoundFile sound1, sound2, sound3, sound4, sound5;
boolean key1, key2, key3, key4, key5;
void setup() {
size(640, 360);
background(255);
noStroke();
sound1 = new SoundFile(this, "do.wav");
sound2 = new SoundFile(this, "re.wav");
sound3 = new SoundFile(this, "mi.wav");
sound4 = new SoundFile(this, "fa.wav");
sound5 = new SoundFile(this, "so.wav");
}
void draw() {
background(255, 214, 79);
rectMode(CENTER);
float w = width * 0.1;
float h = height * 0.8;
if (key1) {
fill(255);
} else {
fill(238, 145, 117);
}
rect(width/6, height/2, w, h);
if (key2) {
fill(255);
} else {
fill(246, 96, 100);
}
rect(width/6 * 2, height/2, w, h);
if (key3) {
fill(255);
} else {
fill(214, 86, 113);
}
rect(width/6 * 3, height/2, w, h);
if (key4) {
fill(255);
} else {
fill(124, 60, 131);
}
rect(width/6 * 4, height/2, w, h);
if (key5) {
fill(255);
} else {
fill(107, 27, 157);
}
rect(width/6 * 5, height/2, w, h);
}
void keyPressed() {
if (key == 'a') {
sound1.play();
key1 = true;
}
if (key == 's') {
sound2.play();
key2 = true;
}
if (key == 'd') {
sound3.play();
key3 = true;
}
if (key == 'f') {
sound4.play();
key4 = true;
}
if (key == 'g') {
sound5.play();
key5 = true;
}
}
void keyReleased() {
if (key == 'a') {
key1 = false;
}
if (key == 's') {
key2 = false;
}
if (key == 'd') {
key3 = false;
}
if (key == 'f') {
key4 = false;
}
if (key == 'g') {
key5 = false;
}
}
代码说明:
需要通过创建多个音频对象,来读取对应的声音信息。以便在触发不同键位时,能播放不同声音。
这里用到一个新事件 keyReleased()。它的作用是将键盘的颜色恢复成原始状态。当松开按钮就能出发。
开头声明的 5 个布尔值。用于检测按键状态。
综合示例-音乐画板1
除了键盘事件外,鼠标事件也是个好东西,得把它灵活运用起来。下面的示例可以打造一个音乐画板,其中用到两个和鼠标相关的事件。
视频地址:(
https://v.qq.com/txp/iframe/player.html?vid=b1308noi6wy&width=500&height=375&auto=0
代码示例(10-7):
import processing.sound.*;
SoundFile sound1, sound2, sound3, sound4, sound5;
boolean isDragging;
void setup() {
size(640, 360);
background(255, 214, 79);
noStroke();
sound1 = new SoundFile(this, "do.wav");
sound2 = new SoundFile(this, "re.wav");
sound3 = new SoundFile(this, "mi.wav");
sound4 = new SoundFile(this, "fa.wav");
sound5 = new SoundFile(this, "so.wav");
}
void draw() {
if (isDragging) {
fill(107, 27, 157, 100);
ellipse(mouseX, mouseY, 16, 16);
}
}
void mouseDragged() {
isDragging = true;
if (mouseX > 100 && mouseX < 105) {
sound1.play();
}
if (mouseX > 200 && mouseX < 205) {
sound2.play();
}
if (mouseX > 300 && mouseX < 305) {
sound3.play();
}
if (mouseX > 400 && mouseX < 405) {
sound4.play();
}
if (mouseX > 500 && mouseX < 505) {
sound5.play();
}
}
void mouseReleased() {
isDragging = false;
}
代码说明:
我们希望当按住鼠标拖动时,才在屏幕上进行绘图。所以需要创建一个布尔变量 isDragging,来获取当前的状态。
当拖动鼠标时,isDragging 就变成 true 值。Draw 函数中的绘图函数才会执行。所以会在屏幕上留下痕迹。当松开鼠标时,isDragging 会变成 false 值,所以 draw 函数中的绘图函数便会停止执行
在鼠标拖动事件中设计了几个触发条件。比如当鼠标的横坐标为 100 到 105 像素之间,音乐会自动播放。这就让屏幕产生了几根隐形的琴弦。只要经过特定的几个区域,便会触发相应的音乐。
综合示例-音乐画板2(改良版)
上面的例子效果已经很不错了。但仔细观察就会发现有不少问题。比如当鼠标移动速度特别快,屏幕上就会残留一个个圆点,并不是连贯的直线。同时还会导致某些音乐漏播。而当鼠标移动速度特别慢,经过横坐标为 100 到 105 的位置时,便会在极短的时间内播放多次音乐,产生卡顿的感觉。这些问题我们都可以通过以下例子来解决
由于微信有视频数量限制,可到以下地址观看:
(
代码示例(10-8):
import processing.sound.*;
SoundFile sound1, sound2, sound3, sound4, sound5;
boolean isDragging;
void setup() {
size(640, 360);
background(255, 214, 79);
noStroke();
sound1 = new SoundFile(this, "do.wav");
sound2 = new SoundFile(this, "re.wav");
sound3 = new SoundFile(this, "mi.wav");
sound4 = new SoundFile(this, "fa.wav");
sound5 = new SoundFile(this, "so.wav");
}
void draw() {
if (isDragging) {
stroke(107, 27, 157, 100);
strokeWeight(10);
line(mouseX, mouseY, pmouseX,pmouseY);
}
}
void mouseDragged() {
isDragging = true;
if ((mouseX - 100) * (pmouseX - 100) < 0) {
sound1.play();
}
if ((mouseX - 200) * (pmouseX - 200) < 0) {
sound2.play();
}
if ((mouseX - 300) * (pmouseX - 300) < 0) {
sound3.play();
}
if ((mouseX - 400) * (pmouseX - 400) < 0) {
sound4.play();
}
if ((mouseX - 500) * (pmouseX - 500) < 0) {
sound5.play();
}
}
void mouseReleased() {
isDragging = false;
}
代码说明:
这里用到了 Processing 系统中自带的两个变量 pmouseX 和 pmouseY。它们和 mouseX,mouseY 类似,但获取的是上一帧鼠标的横纵坐标
draw 函数中使用了 line() 函数来替换原来的 ellipse() 函数。将上一帧的坐标和当前帧的坐标用直接连接起来。也就可以绘制出连续的曲线或直线。
mouseDragged 事件中设计了一个新的触发条件。它通过判断上一帧的坐标和当前帧的坐标是否在同一边,来判断是否有越过某个坐标。以这个条件为例“ if ((mouseX - 100) * (pmouseX - 100) < 0) ”。其中” mouseX - 100 “得出的正负值,可以知道 mouseX 是距离横坐标100 的左侧还是右侧。结果为负数代表左侧,正数代表右侧。” pmouseX - 100 “ 同理。因此,当前后的两个点不是同侧,一正一负相乘得出的便会是负数。也就满足了执行条件。
以上是一种简化的表达,巧妙地运用了某个数学运算规律 - 负负得正。你也可以分两种情况去分别讨论。但判断条件写起来就繁复多了。” if ((mouseX < 100 && pmouseX >= 100) || (mouseX > 100 && pmouseX <= 100)) “ 此判定条件等价于源代码的判定条件。
音视频控制相关函数
以上提到的函数足以应对一般的使用情景。若想深入挖掘,下面整理了一些与音视频相关的常用函数,根据所需可自行探索
音频相关函数事件(SoundFile 对象) | 说明 |
---|---|
.play() | 播放 |
.stop() | 暂停 |
.isPlaying() | 是否正在播放,返回布尔值 |
.amp(float a) | a 值控制音量大小,1 为正常 |
.rate(float a) | a 值控制播放速度 |
.duration() | 返回音频的总长度,单位为秒 |
.loop() | 设置为循环播放 |
.cue(int a) | 从某处播放,a 值单位为秒 |
.jump(float a) | 类似于 cue ,从某处自动播放,a 值单位为秒 |
视频相关函数事件 | 说明 |
---|---|
.play() | 播放 |
.stop() | 停止,并返回到第一帧 |
.pause() | 暂停 |
.speed(float a) | a 值控制播放速度 |
.duration() | 返回视频的总长度,单位为秒,需在 play 函数使用后调用 |
.time() | 返回视频当前的运行时间,单位为秒 |
.jump(float a) | 从某处自动播放,a 值单位为秒 |
更多介绍可参看官网文档
音频(https://processing.org/reference/libraries/sound/index.html)
视频(https://processing.org/reference/libraries/video/index.html)
源文件下载链接:
https://github.com/Wenzy--/OpenFrameWorksTest/tree/master/%E7%AC%AC%E5%8D%81%E7%AF%87%EF%BC%8DProcessing%E5%AE%9E%E4%BE%8B
Processing 系列文章
资源索引