写给设计师的 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 中提供了一些函数可以控制音乐的播放速度。播放速度改变的同时,音调也会同时发生变化。当用鼠标来控制,会产生非常迷幻的效果。

视频地址(http://v.qq.com/x/page/e0322yw5el0.html)

代码示例(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 库。(https://processing.org/reference/libraries/video/index.html)

代码示例(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 之类的事件来控制图形元件的显示或隐藏,可以考虑创建布尔变量来作为中介

  • 事件会按依序执行,只有当前一个事件中的所有代码执行后,才会执行下一个事件中的代码

综合示例-音乐键盘

结合新掌握的事件,我们就可以给程序添加新的交互。下面只要用几分钟的时间,就能简单地模拟一个音乐键盘。

视频地址:(http://v.qq.com/page/q/o/t/q0322w02zot.html)

代码示例(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

除了键盘事件外,鼠标事件也是个好东西,得把它灵活运用起来。下面的示例可以打造一个音乐画板,其中用到两个和鼠标相关的事件。

视频地址:(http://v.qq.com/x/page/d03224cwykv.html)

代码示例(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 的位置时,便会在极短的时间内播放多次音乐,产生卡顿的感觉。这些问题我们都可以通过以下例子来解决

由于微信有视频数量限制,可到以下地址观看:

http://v.qq.com/x/page/w03226o4y4l.html)

代码示例(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)

未经允许不得转载:前端撸码笔记 » 写给设计师的 Processing 编程指南(10)-媒体加载与事件

上一篇:

下一篇: