小嶋秀樹 | 研究室
日本語 | English
iPhone アプリ開発道場(応用編:その2)

ここでは,より音・映像メディアのより応用的な扱いかたを,実例をとおして,解説していきます.下のリンクから sozai2.zip をダウンロードし,必要に応じて,プロジェクトごとの bin/data フォルダにコピーしてください.

音メディアの扱いかた
【音ファイルの再生】

音ファイル(wav や mp3 など)を再生するのは簡単です.ofSoundPlayer のインスタンスを生成し,loadSound() メソッドで音ファイルを読み込めば,play() メソッドで再生できます.sozai2 の中にある sound.wav を bin/data フォルダに入れておいてください.

testApp.h
class testApp : public ofxiPhoneApp {
    private:
        ofSoundPlayer player;
    public:
        (... 変更なし ...)
};
testApp.mm
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 のリファレンス を参照してください.

【音波形の出力:その1】

音ファイルを再生するのではなく,音の自在に生成・再生するには,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);
}
【音波形の出力:その2】

正弦波の出力装置をクラス Osc にまとめます.そのインスタンスを複数生成することで,音の重ね合わせが実現できます.クラスの作り方は「C++ 編:踊る人形のクラス」を参考にしてください.

osc.h
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);
};
osc.cpp
#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() をつぎのように改造して,クチパクするようにしてみてください.顔のデザインはご自由に.

moving mouth
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 は口の開き(高さ)を表わしています.

OpenCV を使った画像処理

仕込み中です.