ここでは,より音・映像メディアのより応用的な扱いかたを,実例をとおして,解説していきます.下のリンクから sozai2.zip をダウンロードし,必要に応じて,プロジェクトごとの bin/data フォルダにコピーしてください.
- このページで利用するおもな素材:sozai2.zip
音ファイル(wav や mp3 など)を再生するのは簡単です.ofSoundPlayer のインスタンスを生成し,loadSound() メソッドで音ファイルを読み込めば,play() メソッドで再生できます.sozai2 の中にある sound.wav を bin/data フォルダに入れておいてください.
class testApp : public ofxiPhoneApp {
private:
ofSoundPlayer player;
public:
(... 変更なし ...)
};
void testApp::setup() {
ofRegisterTouchEvents(this);
ofxAccelerometer.setup();
ofxiPhoneAlerts.addListener(this);
ofBackground(0, 0, 0);
// load sound
player.loadSound("sound.wav");
// start playing
player.play();
}
void testApp::update() {}
void testApp::draw() {}
発展課題: 画面をタッチしているあいだ(あるいはマウスボタンを押し下げているあいだ)ポーズ状態となるように改造してみます.以下の部分を書き足してください.
void testApp::touchDown(ofTouchEventArgs &touch) {
player.setPaused(true);
}
void testApp::touchUp(ofTouchEventArgs &touch) {
player.setPaused(false);
}
音ファイルを扱う ofSoundPlayer クラスの詳細については,openFrameworks のリファレンス を参照してください.
音ファイルを再生するのではなく,音の自在に生成・再生するには,ofSoundStream を利用します.たとえば,つぎのプログラムは,880Hz の正弦波を生成・再生するものです.
class testApp : public ofxiPhoneApp {
private:
float soundFreq;
float phase;
public:
void audioRequested(float *buf, int bufSize, int nChan);
(... 変更なし ...)
};
void testApp::setup() {
ofRegisterTouchEvents(this);
ofxAccelerometer.setup();
ofxiPhoneAlerts.addListener(this);
ofBackground(0, 0, 0);
// [osc~ 880]
soundFreq = 880;
phase = 0;
ofSoundStreamSetup(2, 0, this);
}
void testApp::audioRequested(float *buf, int bufSize, int nChan) {
float phasePerSample = TWO_PI * soundFreq / 44100;
for (int i = 0; i < bufSize; i++) {
phase += phasePerSample;
while (phase > TWO_PI) phase -= TWO_PI;
float value = sin(phase);
buf[i * nChan ] = value;
buf[i * nChan + 1] = value;
}
}
void testApp::update() {}
void testApp::draw() {}
ofSoundStreamSetup(2, 0, this) では,出統2チャネル,入力チャネルなし,コールバックは自分(this=testApp)として,音出力機能をオンにしています.これ以降,システム側の準備が出来るたびに,testApp::audioRequested(float *buf, int bufSize, int nChan) というメソッドが呼び出されますので,その中で,bufSize サンプル分(たとえば 512 サンプル分)の波形データを,ofSoundStream に入力します.ofSoundStream は,波形データの再生を開始し,つぎの波形データの入力が可能になった時点で,再び testApp::audioRequested() を呼び出します.
testApp::audioRequested(float *buf, int bufSize, int nChan) の中では,現在の位相 phase から始めて,bufSize サンプル分だけの波形データを,buf に格納するようにします.buf に入れる波形データは,左・右の順に,bufSize サンプル(左・右の組の数)だけ連続した実数値 (float) です.呼び出しごとに phase が進むようにしています.
発展課題1: 画面をタッチして,音の高さ変えられるように改造してみます.以下の部分を書き足してください.
void testApp::touchDown(ofTouchEventArgs &touch) {
soundFreq = 500 + touch.y;
}
void testApp::touchUp(ofTouchEventArgs &touch) {
soundFreq = 500 + touch.y;
}
発展課題2: 音の周波数を表示するように改造してみます.以下の部分を書き足してください.
void testApp::draw() {
ofSetColor(255, 127, 127);
ofDrawBitmapString(ofToString(soundFreq), 100, 100);
}
正弦波の出力装置をクラス Osc にまとめます.そのインスタンスを複数生成することで,音の重ね合わせが実現できます.クラスの作り方は「C++ 編:踊る人形のクラス」を参考にしてください.
class Osc {
private:
float soundFreq, soundAmp;
float phase, phasePerSample;
public:
void init(float freq, float amp);
void change(float freq, float amp);
void make(float *buf, int bufSize, bool overlay);
};
#include "osc.h"
void Osc::init(float freq, float amp) {
phase = 0;
change(freq, amp);
}
void Osc::change(float freq, float amp) {
soundFreq = freq;
soundAmp = amp;
phasePerSample = TWO_PI * soundFreq / 44100;
}
void Osc::make(float *buf, int bufSize, bool overlay) {
for (int i = 0; i < bufSize; i++) {
phase += phasePerSample;
while (phase > TWO_PI) phase -= TWO_PI;
float value = soundAmp * sin(phase);
if (overlay) {
buf[i * 2 ] += value;
buf[i * 2 + 1] += value;
}
else {
buf[i * 2 ] = value;
buf[i * 2 + 1] = value;
}
}
}
Osc クラスは,周波数と振幅(0〜1)を与えて初期化する init() メソッド,周波数と振幅を変更する change メソッド,波形データを生成する make() メソッドを持ちます.make() メソッドは,overlay が false のときは新しい波形データを上書きし,true のときは現在の値に新しい波形データを加算(重ね合わせ)します.この Osc クラスを利用して,ド (C) とソ (G) を重ねた正弦波を生成・出力するには,testApp.h・testApp.mm をつぎのように用意します.
#include "osc.h" class testApp : public ofxiPhoneApp { private: Osc osc1, osc2; public: (... 変更なし ...) };
void testApp::setup() {
ofRegisterTouchEvents(this);
ofxAccelerometer.setup();
ofxiPhoneAlerts.addListener(this);
ofBackground(0, 0, 0);
// [osc~ "C"] and [osc~ "G"]
osc1.init(261.626, 0.5);
osc2.init(391.995, 0.5);
// sound stream
ofSoundStreamSetup(2, 0, this);
}
void testApp::audioRequested(float *buf, int bufSize, int nChan) {
osc1.make(buf, bufSize, false);
osc2.make(buf, bufSize, true);
}
void testApp::update() {}
void testApp::draw() {}
発展課題: 画面をタッチした位置に応じて,2つの正弦波の高さが変わるように改造してみます.以下の部分を追加してください.
void testApp::touchDown(ofTouchEventArgs &touch) {
osc1.change(touch.x + 200, 0.5);
osc2.change(touch.y + 200, 0.5);
}
void testApp::touchMoved(ofTouchEventArgs &touch) {
osc1.change(touch.x + 200, 0.5);
osc2.change(touch.y + 200, 0.5);
}
マイクから音を入力し,その波形を配列変数に格納することができます.ここでは,その波形を画面に描画するプログラムをつくってみましょう.
#define BUFSIZE 512 class testApp : public ofxiPhoneApp { private: float buffer[BUFSIZE]; int bufferSize; public: void audioReceived(float *buf, int bufSize, int nChan); (... 変更なし ...) };
void testApp::setup() {
ofRegisterTouchEvents(this);
ofxAccelerometer.setup();
ofxiPhoneAlerts.addListener(this);
ofBackground(0, 0, 0);
// [adc~]
bufSize = 0;
ofSoundStreamSetup(0, 1, this, 44100, BUFSIZE, 4);
}
void testApp::audioReceived(float *buf, int bufSize, int nChan) {
bufferSize = bufSize;
for (int i = 0; i < bufferSize; i++)
buffer[i] = buf[i];
}
void testApp::update() {}
void testApp::draw() {
for (int i = 0; i < bufferSize; i++) {
int dy = buffer[i] * 100;
ofSetColor(255, 127, 127);
ofLine(i, 240, i, 240 - dy);
}
}
ofSoundStreamSetup(0, 1, this, 44100, 512, 4) では,出力チャネルなし,入力1チャネル(iPhone のマイクはモノラル...たぶん),コールバックは自分(this=testApp)として,音入力機能をオンにしています.これ以降,最大 512 サンプルの波形データ(約 11.6ms 相当)が用意されるたびに,testApp::audioReceived(float *buf, int bufSize, int nChan) というメソッドが呼び出されますので,その中で,bufSize サンプル(最大 512 サンプル)分の波形データを処理します.波形データは,−1〜1 の実数 (float) からなる配列です.
testApp::audioReceived(float *buf, int bufSize, int nChan) の中では,bufSize サンプル分(最大 512 サンプル)分の波形データを,実数配列 buffer にコピーしています.その波形データは,testApp::draw() によって画面に描画されるようにしています.
発展課題: testApp::draw() をつぎのように改造して,クチパクするようにしてみてください.顔のデザインはご自由に.
void testApp::draw(){
// the eyes
ofSetColor(255, 255, 255);
ofEllipse( 80, 150, 100, 50);
ofEllipse(240, 150, 100, 50);
ofSetColor(0, 0, 0);
ofCircle( 80, 150, 30);
ofCircle( 240, 150, 30);
// loudness
float sum = 0;
for (int i = 0; i < bufferSize; i++)
sum += buffer[i] * buffer[i];
float rms = sqrt(sum / bufferSize);
float mag = ofClamp(rms * 10, 0, 1);
float height = mag * 120 + 80;
// moving mouth
ofSetColor(255, 127, 127);
ofEllipse(160, 300, 300, height);
ofSetColor(0, 0, 0);
ofEllipse(160, 300, 240, height - 60);
}
rms は 波形データの平均レベル(512 サンプルについての二乗平均平方根),mag はそれを 10倍に拡大して 0〜1 区間に制限したもの,そして height は口の開き(高さ)を表わしています.
仕込み中です.