ここでは,より音・映像メディアのより応用的な扱いかたを,実例をとおして,解説していきます.下のリンクから 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 は口の開き(高さ)を表わしています.
仕込み中です.