查看原文
其他

写给设计师的 OF 编程指南10-媒体加载与事件

2016-09-12 Wenzy InsLab

Openframeworks 中可以载入许多外部数据。
其中有三类最为常用,分别是图片,音频,视频。

这节我们将结合事件,对音视频的加载进行详细展开。在最后,你可以亲自打造自己的音乐键盘,音乐画板。

读取图片

在开始前,先来回顾一下图片的加载方法。

与图片相关的函数

基本函数事件说明
.load(string a)读取图片,a 为图片路径
.draw(float x,float y)x,y 为图片的横纵坐标
.draw(float x,float y,float w,float h)x,y 为图片的横纵坐标.w,h 为图片的宽高
.setAnchorPercent(float a,float b)a,b 会决定图片绘制中心的的偏移比例,a 值表示中心坐标在 x 轴方向上与图片宽度的比值,b 值表示在 y 轴方向上与图片高度的比值。当不调用此函数,图片的绘制中心默认处于图片左上角。与调用此函数, 并将 a ,b 值设为 0 的效果一样。 若 a,b 值都为 0.5,图片的绘制中心会居中。a,b 值都为 1,图片的绘制中心则会处于右下角
    • 前面带.号的函数,都属于 ofImage 对象的成员函数,只有先声明图片对象,才能正常使用。

    • 程序运行前,别忘记在 bin 中的 data 文件夹内放入图片素材

    音乐的载入,播放与暂定

    下面开始正式介绍音乐的调用方法。与载入图片非常类似,开始需要先声明一个音频对象。具体语法可参看以下例子

    代码示例(10-1):

    —- ofApp.h 内

    #include "ofMain.h" class ofApp : public ofBaseApp{    public:        void setup();        void update();        void draw();        ...        ofSoundPlayer sound; };

    —- ofApp.cpp 内

    void ofApp::setup(){    //载入音频    sound.load("1.mp3"); } void ofApp::update(){ } void ofApp::draw(){ } void ofApp::keyPressed(int key){    //播放音频    if(key == 'p'){        sound.play();    }    //暂停音频    if(key == 's'){        sound.stop();    } }

    代码说明:

    Openframeworks 本身就自带声音库,无需像 Processing 一样做许多前期的准备。现在只要复制上面代码,点 RUN 即能运行。按 P 键音乐播放,S 键音乐暂停。

    • ofApp.h 中的 “ ofSoundPlayer sound; ”声明了一个音频对象。ofSoundPlayer 的作用类似于 ofImage。

    • setup 函数中的 “ sound.load(“1.mp3”); ”,作用是指定音频对象的读取路径,将音乐素材载入其中。

    • keyPressed() 事件中的 “ sound.play() ” 与 “ sound.stop() ” 分别起播放和暂停的作用。中间的 “.” 表示 play 和 stop 是属于音频对象的成员函数。成员函数可理解为这个对象中包含的函数,属于这个对象,是事先定义好的。当以后需要播放多个音频对象,只要在对应的变量名后加上.play()。

    • 音频素材需放在工程文件目录下的 data 文件夹内

    Openframeworks 中支持常用的音频格式,如 mp3,wav,ogg 等。在写路径时记得写上对应的后缀名。

    音乐速度控制

    下面的例子会开始变得有意思起来,Openframeworks 中提供了一些函数可以控制音乐的播放速度。播放速度改变的同时,音调也会同时发生变化。当用鼠标来控制,会产生非常迷幻的效果。

    视频地址(

    https://v.qq.com/txp/iframe/player.html?vid=y1308mzqsaq&width=500&height=375&auto=0

    代码示例(10-2):

    —- ofApp.h 内

    #include "ofMain.h" class ofApp : public ofBaseApp{    public:        void setup();        void update();        void draw();        ...        ofSoundPlayer sound; };

    —- ofApp.cpp 内

    void ofApp::setup(){    //载入音频    sound.load("1.mp3"); } void ofApp::update(){    //左右移动控制播放速度    float speed = mouseX/(float)ofGetWidth() * 3;    sound.setSpeed(speed);    //上下移动控制播放音量    float vol = mouseY/(float)ofGetHeight() * 4;    sound.setVolume(vol); } void ofApp::draw(){ } void ofApp::keyPressed(int key){    //播放音频    if(key == 'p'){        sound.play();    }    //暂停音频    if(key == 's'){        sound.stop();    } }

    代码说明:

    • .setSpeed() 函数用于控制音频的播放速度。括号中的数值决定播放速度的大小。数值为 1 时,播放速度正常。数值大于 1 加速,小于 1 减速。

    • .setVolume() 函数用于控制音频的音量大小。括号中的数值决定音量值。数值为 1 时,音量值正常。数值大于 1 时音量增大,小于 1 减小。

    • 这里创建了两个局部变量 speed 和 vol 作为参数的传入。因而鼠标的横坐标会改变音乐的音调,纵坐标会改变音量大小。

    视频暂停与播放

    Openframeworks 中加载视频与加载音频类似,无需加载外部库就能直接调用。

    代码示例(10-3):

    —- ofApp.h 内

    #include "ofMain.h" class ofApp : public ofBaseApp{    public:        void setup();        void update();        void draw();        ...       ofVideoPlayer video; };

    —- ofApp.cpp 内

    void ofApp::setup(){     ofSetWindowShape(640,360);    video.load("1.mov"); } void ofApp::update(){    video.update(); } void ofApp::draw(){    video.draw(0,0,640,360);   // 位置x坐标,位置y坐标,视频宽度,视频长度 } void ofApp::keyPressed(int key){    // 按键 P  与 按键 D 功能等价    if(key == 'p'){        video.play();  // 播放    }    if(key == 's'){        video.setPaused(true);  // 暂停    }    if(key == 'd'){        video.setPaused(false);  // 继续(播放)    } }

    视频截图:

    代码说明:

    • 第二行 “ ofVideoPlayer video; ” 用于声明视频对象。其中“ ofVideoPlayer ” 作用类似于 PImage。

    • setup 中的“ video.load(“1.mov”); ”,作用是指定视频对象的读取路径。括号中填写视频素材的地址

    • .update() 函数用于更新视频,是必须要写的函数。

    • .draw() 函数用于显示视频。第一第二个参数是视频绘制的横纵坐标。第三第四个参数决定视频显示的长和宽。

    • .play() 函数表示播放。.setPaused() 可以决定视频是播放还是暂定。括号中可以写 true 或 false。写成 true 时表示暂停。false 则表示继续播放。当参数为 false 时,与.play() 函数的效果等价。

    视频速度控制

    代码示例(10-4):

    —- ofApp.h 内

    #include "ofMain.h" class ofApp : public ofBaseApp{    public:        void setup();        void update();        void draw();        ...        ofVideoPlayer video; };

    —- ofApp.cpp 内

    void ofApp::setup(){     ofSetWindowShape(640,360);    video.load("1.mov");    video.play() } void ofApp::update(){    video.update();    float speed = ofGetMouseX()/(float)ofGetWidth() * 4;    video.setSpeed(speed); } void ofApp::draw(){    video.draw(0,0,640,360); }

    代码说明:

    • .setSpeed() 函数可以控制视频的播放速度。当参数数值为 1 时,播放速度正常。数值大于 1 加速,小于 1 减速

    • 因为创建了局部变量 newSpeed 并传入了 setSpeed() 函数中,鼠标的横坐标便会直接影响视频的播放速度

    Openframeworks 常用事件一览

    之前只介绍了 keyPressed() 事件,当它在键盘按下后就会触发。下面再解释 Openframeworks 中的其他最常用事件。

    基本函数事件说明
    void ofApp::setup()最先执行,只运行一次
    void ofApp::update()反复执行,数据更新一般写在update内
    void ofApp::draw()反复执行,绘图函数一般写在draw内
    键盘相关函数事件说明
    void ofApp::keyPressed(int key)键盘按下时执行,key返回键值
    void ofApp::keyReleased(int key)键盘释放时执行,key返回键值
    鼠标相关函数事件说明
    void ofApp::mousePressed(int x, int y, int button)鼠标按下时执行,x,y返回坐标,button返回键值
    void ofApp::mouseReleased(int x, int y, int button)鼠标释放时执行,x,y返回坐标,button返回键值
    void ofApp::mouseDragged(int x, int y, int button)鼠标拖动时执行,x,y返回坐标,button返回键值
    void ofApp::mouseMoved(int x, int y )鼠标移动时执行,x,y返回坐标,button返回键值
    void ofApp::mouseEntered(int x, int y);鼠标进入窗口时执行,x,y返回鼠标坐标
    void ofApp::mouseExited(int x, int y);鼠标退出窗口时执行,x,y返回鼠标坐标
    其他函数事件说明
    void ofApp::windowResized(int w, int h)窗口尺寸改变时运行,w,h返回窗口的宽高
    void ofApp::dragEvent(ofDragInfo dragInfo)拖动文件到窗口

    它们的使用方法和 keyPressed 类似,在 ofApp.cpp 中早已定义了这些事件,使用的时候直接在里面添代码即可,非常方便。上面的事件都非常容易理解,只要略微实验一下就能迅速掌握用法。

    事件流

    我们可以通过一个实例,了解事件的执行顺序。

    代码示例(10-5):

    —- ofApp.h 内

    #include "ofMain.h" class ofApp : public ofBaseApp{    public:        void setup();        void update();        void draw();        ... };

    —- ofApp.cpp 内

    void ofApp::setup(){    ofSetFrameRate(1);    cout << 1 << endl; } void ofApp::update(){    cout << 2 << endl; } void ofApp::draw(){    cout << 3 << endl; } void ofApp::keyPressed(int key){    cout << 4 << endl; } void ofApp::keyReleased(int key){    cout << 5 << endl; } void ofApp::mouseMoved(int x, int y){    cout << 6 << endl; } void ofApp::mouseDragged(int x, int y, int button){    cout << 7 << endl; } void ofApp::mousePressed(int x, int y, int button){    cout << 8 << endl; } void ofApp::mouseReleased(int x, int y, int button){    cout << 9 << endl; }

    代码说明:

    • setup 函数中通过 ofSetFrameRate() 函数把程序的运行速率设成了 2 帧每秒。降低帧率有助于我们观察控制台的输出。不至于触发事件后就迅速被新数据刷到后面了。

    • 尝试移动鼠标,点击鼠标,释放鼠标。观察输出的结果。通过 cout 来了解事件的执行顺序

    • 值得注意的是,绘图函数一般不能写在除了 draw 函数的其他事件中。否则无法显示。若希望通过 keyPressed 之类的事件来控制图形元件的显示或隐藏,可以考虑创建布尔变量来作为中介

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

    综合示例-音乐键盘

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

    视频地址:(

    https://v.qq.com/txp/iframe/player.html?vid=a130872dluv&width=500&height=375&auto=0

    代码示例(10-6):

    —- ofApp.h 内

    #include "ofMain.h" class ofApp : public ofBaseApp{    public:        void setup();        void update();        void draw();        ...        ofSoundPlayer sound1,sound2,sound3,sound4,sound5;        bool key1,key2,key3,key4,key5; };

    —- ofApp.cpp 内

    void ofApp::setup(){    ofSetWindowShape(700, 400);    //载入音频    sound1.load("do.wav");    sound2.load("re.wav");    sound3.load("mi.wav");    sound4.load("fa.wav");    sound5.load("so.wav"); } void ofApp::update(){ } void ofApp::draw(){    ofBackground(255,214,79);    ofSetRectMode(OF_RECTMODE_CENTER);    float w = ofGetWidth() * 0.1;    float h = ofGetHeight() * 0.8;    if(key1){        ofSetColor(255);    }else{        ofSetColor(238,145,117);    }    ofDrawRectangle(ofGetWidth()/6 , ofGetHeight()/2, w, h);    if(key2){        ofSetColor(255);    }else{        ofSetColor(246,96,100);    }    ofDrawRectangle(ofGetWidth()/6 * 2, ofGetHeight()/2, w, h);    if(key3){        ofSetColor(255);    }else{        ofSetColor(214,86,113);    }    ofDrawRectangle(ofGetWidth()/6 * 3, ofGetHeight()/2, w, h);    if(key4){        ofSetColor(255);    }else{        ofSetColor(124,60,131);    }    ofDrawRectangle(ofGetWidth()/6 * 4, ofGetHeight()/2, w, h);    if(key5){        ofSetColor(255);    }else{        ofSetColor(107,27,157);    }    ofDrawRectangle(ofGetWidth()/6 * 5, ofGetHeight()/2, w, h); } void ofApp::keyPressed(int key){    //播放音频    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 ofApp::keyReleased(int key){    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):

    —- ofApp.h 内

    #include "ofMain.h" class ofApp : public ofBaseApp{    public:        void setup();        void update();        void draw();        ...       ofSoundPlayer sound1,sound2,sound3,sound4,sound5;     bool isDragging; };

    —- ofApp.cpp 内

    void ofApp::setup(){    ofSetWindowShape(600, 400);    ofBackground(255,214,79);    ofSetBackgroundAuto(false);    //载入音频    sound1.load("do.wav");    sound2.load("re.wav");    sound3.load("mi.wav");    sound4.load("fa.wav");    sound5.load("so.wav"); } void ofApp::update(){ } void ofApp::draw(){      if(isDragging){        ofSetColor(107,27,157,100);        ofDrawCircle(mouseX,mouseY,8);    } } void ofApp::mouseDragged(int x, int y, int button){    isDragging = true;    if(x > 100 && x < 105){        sound1.play();    }    if(x > 200 && x < 205){        sound2.play();    }    if(x > 300 && x < 305){        sound3.play();    }    if(x > 400 && x < 405){        sound4.play();    }    if(x > 500 && x < 505){        sound5.play();    } }

    代码说明:

    • 我们希望当按住鼠标拖动时,才在屏幕上进行绘图。所以需要创建一个布尔变量 isDragging,来获取当前的状态。

    • 当拖动鼠标时,isDragging 就变成 true 值。Draw 函数中的绘图函数才会执行。所以会在屏幕上留下痕迹。当松开鼠标时,isDragging 会变成 false 值,所以 draw 函数中的绘图函数便会停止执行

    • 在鼠标拖动事件中设计了几个触发条件。比如当鼠标的横坐标为 100 到 105 像素之间,音乐会自动播放。这就让屏幕产生了几根隐形的琴弦。只要经过特定的几个区域,便会触发相应的音乐。

    综合示例-音乐画板2(改良版)

    上面的例子效果已经很不错了。但仔细观察就会发现有不少问题。比如当鼠标移动速度特别快,屏幕上就会残留一个个圆点,并不是连贯的直线。同时还会导致某些音乐漏播。而当鼠标移动速度特别慢,经过横坐标为 100 到 105 的位置时,便会在极短的时间内播放多次音乐,产生卡顿的感觉。这些问题我们都可以通过以下例子来解决

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

    代码示例(10-8):

    —- ofApp.h 内

    #include "ofMain.h" class ofApp : public ofBaseApp{    public:        void setup();        void update();        void draw();        ...       ofSoundPlayer sound1,sound2,sound3,sound4,sound5;    bool play1,play2,play3,play4,play5;    bool isDragging;    float lastMouseX,lastMouseY; };

    —- ofApp.cpp 内

    void ofApp::setup(){    ofSetWindowShape(600, 400);    ofBackground(255,214,79);    ofSetBackgroundAuto(false);    sound1.load("do.wav");    sound2.load("re.wav");    sound3.load("mi.wav");    sound4.load("fa.wav");    sound5.load("so.wav"); } void ofApp::update(){ } void ofApp::draw(){    if(isDragging){        ofSetColor(107,27,157,100);        ofSetLineWidth(10);       ofDrawLine(mouseX,mouseY,lastMouseX,lastMouseY);    }    lastMouseX = mouseX;    lastMouseY = mouseY; } void ofApp::mouseDragged(int x, int y, int button){    isDragging = true;    if((mouseX - 100) * (lastMouseX - 100) < 0){        sound1.play();    }    if((mouseX - 200) * (lastMouseX - 200) < 0){        sound2.play();    }    if((mouseX - 300) * (lastMouseX - 300) < 0){        sound3.play();    }    if((mouseX - 400) * (lastMouseX - 400) < 0){        sound4.play();    }    if((mouseX - 500) * (lastMouseX - 500) < 0){        sound5.play();    } } void ofApp::mouseReleased(int x, int y, int button){    isDragging = false; }

    代码说明:

    • 这里 Openframeworks 不像 Processing 一样自带 pmouseX 和 pmouseY。所以需要创建两个局部变量来模拟两者的效果。这里用了 lastMouseX 和 lastMouseY 来表示。当它们写在 draw 函数的末尾。就相当于获取上一帧鼠标的横纵坐标

    • draw 函数中使用了 ofDrawLine() 函数来替换原来的 ofDrawCircle() 函数。将上一帧的坐标和当前帧的坐标用直接连接起来。也就可以绘制出连续的曲线或直线。

    • mouseDragged 事件中设计了一个新的触发条件。它通过判断上一帧的坐标和当前帧的坐标是否在同一边,来判断是否有越过某个坐标。以这个条件为例“ if ((mouseX - 100) * (lastMouseX - 100) < 0) ”。其中” mouseX - 100 “得出的正负值,可以知道 mouseX 是距离横坐标100 的左侧还是右侧。结果为负数代表左侧,正数代表右侧。” lastMouseX - 100 “ 同理。因此,当前后的两个点不是同侧,一正一负相乘得出的便会是负数。也就满足了执行条件。

    • 以上是一种简化的表达,巧妙地运用了某个数学运算规律 - 负负得正。你也可以分两种情况去分别讨论。但判断条件写起来就繁复多了。” if ((mouseX < 100 && lastMouseX >= 100) || (mouseX > 100 && lastMouseY <= 100)) “ 此判定条件等价于源代码的判定条件。



    源文件下载链接

    https://github.com/Wenzy--/OpenFrameWorksTest/tree/master/%E7%AC%AC%E5%8D%81%E7%AF%87%EF%BC%8DOF%E5%AE%9E%E4%BE%8B

    Openframeworks 系列文章

    [1][2][3][4]

    [5][6][7][8][9]

    资源索引

    CreativeCoding学习资源索引

    Twitter资源索引


    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存