多摩美 - “iTamabi” – iPhoneアプリ開発プロジェクト 2010
openFrameworks for iPhone:サウンドの録音・再生
今日の内容
- サウンドファイルの再生「Buddha Machine」的なアプリを作ってみる
- サウンドの録音と再生を使用した音アプリに挑戦
- 録音した音のデータをインタラクティブに操作する
サウンドファイルの再生
- あらかじめ録音したサウンドファイルを再生するには、ofxOpenALSoundPlayerを使用する – 詳細は過去の授業資料「openFrameworks + iPhone マルチタッチイベントの取得、音アプリを作る」を参照
- 自分で作成した音ファイルと、画面に表示する画像さえあれば「Buddah machine」のようなアプリは作れてしまう
サンプル1:サウンドファイルの再生
- オリジナルな"Buddha Machine"的アプリを制作できる雛形
- サウンドファイル(caf形式)と表示する画像を入れ替えれば、そのままアプリにできるテンプレートを以下に掲載
- 音ファイル(caf形式)は、「プロジェクトのフォルダ」→「bin」→「data」→「mySound.caf」に入れる
- caf形式のフォーマット – リニアPCM、16bit リトルエンディアン符号付き整数、22050Hz
- 表示する画像ファイル(PNG形式)は、「プロジェクトのフォルダ」→「bin」→「data」→「myScreen.png」に入れる
- PNGファイルノフォーマット – PNG 320 x 480 pixel
testApp.h
[code language=”cpp”]
#pragma once
#include "ofMain.h"
#include "ofxiPhone.h"
#include "ofxiPhoneExtras.h"
#include "ofxALSoundPlayer.h"
class testApp : public ofxiPhoneApp {
public:
    void setup();
    void update();
    void draw();
    void exit();
    void touchDown(ofTouchEventArgs &touch);
    void touchMoved(ofTouchEventArgs &touch);
    void touchUp(ofTouchEventArgs &touch);
    void touchDoubleTap(ofTouchEventArgs &touch);
    void lostFocus();
    void gotFocus();
    void gotMemoryWarning();
    void deviceOrientationChanged(int newOrientation);
    //表示する画面イメージ
    ofImage image;
    //OpenALを利用して音を再生するプレーヤー
    ofxALSoundPlayer synth;
};
[/code]
testApp.mm
[code language=”cpp”]
#include "testApp.h"
//————————————————————–
void testApp::setup(){
	ofRegisterTouchEvents(this);
	ofxAccelerometer.setup();
	ofxiPhoneAlerts.addListener(this);
    //画面イメージを読みこみ
    image.loadImage("myScreen.png");
    //ループ再生するcafファイルをロードして、Synthにわりあて
    synth.loadLoopingSound("mySound.caf");
    //読み込んだcafファイルを再生
    synth.play();
}
//————————————————————–
void testApp::update(){
}
//————————————————————–
void testApp::draw(){
    //画面を表示
    image.draw(0, 0);
}
//————————————————————–
void testApp::exit(){
}
//————————————————————–
void testApp::touchDown(ofTouchEventArgs &touch){
}
//————————————————————–
void testApp::touchMoved(ofTouchEventArgs &touch){
}
//————————————————————–
void testApp::touchUp(ofTouchEventArgs &touch){
}
//————————————————————–
void testApp::touchDoubleTap(ofTouchEventArgs &touch){
}
//————————————————————–
void testApp::lostFocus(){
}
//————————————————————–
void testApp::gotFocus(){
}
//————————————————————–
void testApp::gotMemoryWarning(){
}
//————————————————————–
void testApp::deviceOrientationChanged(int newOrientation){
}
[/code]
サウンドを録音・再生する (サンプリング&プレイバック)
- あらかじめ録音したサウンドファイルを再生するのではなく、iPhoneのマイク入力から音を録音したり、録音した音を再生するには、SoundStreamを使用する必要がある
- SoundStreamは、音のサンプルのデータ自体を数値として配列に記憶し、それを再生することができる機能 → サンプリング&プレイバック
サンプリグ (標本化)とは
- 時間的に連続した信号を一定の間隔をおいて測定することにより、離散的な(連続でない)データとして収集すること
- 時間的に連続した信号 = アナログ信号
- 離散的な信号 = デジタル信号
サンプリング周波数と量子化ビット数
- サンプリング周波数 – 1秒間にいくつのサンプルを使用してサンプリングするか、単位はHz
- 量子化ビットレイト – AD変換の際に信号を何段階の数値で表現するか、単位はbit
iPhoneシミュレータでサンプリング&プレイバックのプレビューを行う際には「Audio MIDI 設定で」下記の設定にする
- サンプリング周波数、44.1KHz
- 量子化ビットレイト – 24bit
Sound Streamの機能
SoundStreamを使用するにはまず ofSoundStreamSetup() で初期設定を行う、通常は、setup() の中で指定
- ofSoundStreamSetup(int nOutputs, int nInputs, ofSimpleApp * OFSA, int sampleRate, int bufferSize, int nBuffers)
- サウンドの録音再生の初期設定を行う
- int nOutput – 再生する音のチャンネル数
- int nInput – 録音する音のチャンネル数
- ofSimpleApp * OFSA – 呼び出し元のofSimpleApp (普通は、this でOK)
- int sampleRate – サンプリングレイト
- int bufferSize – 音をバッファ(格納)するサイズ
 
Sound Streamに関連するイベント
testApp では、ofSoundStreamSetup() の設定すると音の入出力に関するイベントが発生する
- audioReceived(float * input, int bufferSize, int nChannels)
- 音が入力(録音)される際に発生するイベント
- float * input – 入力された音のサンプルの配列
- int bufferSize – バッファサイズ、一度に格納されるサンプルの長さ
- int nChannels – チャンネル数
 
- audioRequested(float * output, int bufferSize, int nChannels)
- 音が出力(再生)される際に発生するイベント
- float * output – 音として出力するサウンドのサンプルの配列
- int bufferSize – バッファサイズ、一度に格納されるサンプルの長さ
- int nChannels – チャンネル数
 
サンプル2:サンプリング&プレイバック
- 音を録音して再生するという「サンプリング&プレイバック」の機能をシンプルに実現してみる
- 画面をタッチ(touchDown)すると録音、タッチしている指を離すと(touchUp)録音した音を再生するように設計
- 録音した音の波形を画面に表示する
testApp.h
[code language=”cpp”]
#pragma once
#include "ofMain.h"
#include "ofxiPhone.h"
#include "ofxiPhoneExtras.h"
//録音・再生する音の長さ
#define LENGTH 44100 * 3
class testApp : public ofxiPhoneApp{
public:
    void setup();
    void update();
    void draw();
    void touchDown(ofTouchEventArgs &touch);
    void touchMoved(ofTouchEventArgs &touch);
    void touchUp(ofTouchEventArgs &touch);
    void touchDoubleTap(ofTouchEventArgs &touch);
    void audioReceived( float * input, int bufferSize, int nChannels );
    void audioRequested( float * output, int bufferSize, int nChannels );
    float buffer[LENGTH]; //オーディオバッファ
    int sampleRate; //サンプリングレイト
    int recPos; //レコード位置
    int playPos; //再生位置
    int mode; //現在のモード、0:off, 1:recording, 2:play
};
[/code]
testApp.mm
[code language=”cpp”]
#include "testApp.h"
//————————————————————–
void testApp::setup(){
    //iPhone基本設定
	ofRegisterTouchEvents(this);
    ofBackground(0, 0, 0);
    ofSetFrameRate(60);
    //横位置で使用
    ofxiPhoneSetOrientation(OFXIPHONE_ORIENTATION_LANDSCAPE_RIGHT);
    //サンプリングレイトの設定
	sampleRate = 44100;
    //サウンド録音再生の初期化
	ofSoundStreamSetup(1, 1, this, sampleRate, LENGTH, 4);
    //再生モードに
    mode = 2;
    //録音、再生の位置を先頭に
    recPos = 0;
    playPos = 0;
}
//————————————————————–
void testApp::update(){
}
//————————————————————–
void testApp::draw(){
    ofSetColor(255, 255, 255);
    if (mode == 1) {
        //録音モードの場合、recordingの表示をして、背景を青に
        ofDrawBitmapString("recording", 10, 20);
        ofBackground(255, 0, 0);
    } else if (mode == 2) {
        //再生モードの場合、playingの表示をして、背景を赤に
        ofDrawBitmapString("playing", 10, 20);
        ofBackground(0, 0, 255);
    }
    //画面の幅と録音サンプル数の比率を計算
    int ratio = LENGTH / ofGetWidth();    
    //画面の横幅にあわせて、波形を描画
    for (int i = 0; i < LENGTH; i+=ratio){
		ofLine(i/ratio,ofGetHeight()/2,i/ratio,ofGetHeight()/2+buffer[i]*100.0f);
	}
}
//————————————————————–
//オーディオ入力の処理
void testApp::audioReceived(float * input, int bufferSize, int nChannels){
    //もし録音モードだったら
    if (mode == 1) {
        //バッファされたサンプルの数だけ処理
        for (int i = 0; i < bufferSize*nChannels; i++){
            //録音位置が設定した最大の長さに逹っしていなければ
            if(recPos<LENGTH){
                //バッファにサウンド入力を設定
                buffer[recPos] = input[i];
                //録音位置を進める
                recPos++;
            } else {
                //もし最大位置を越えていたら、最初に戻る(ループ録音)
                recPos = 0;
            }
        }
    }
}
//————————————————————–
//オーディオ再生の処理
void testApp::audioRequested(float * output, int bufferSize, int nChannels){
    //もし再生モードだったら
    if (mode == 2) {
        //バッファされたサンプル数だけ処理
        for (int i = 0; i < bufferSize*nChannels; i++) {
            //再生位置が設定した最大の長さに逹っしていなければ
            if(playPos<LENGTH){
                //バッファに格納したサウンドを再生
                output[i] = buffer[playPos];
                //再生位置を進める
                playPos++;
            } else {
                //もし最大位置を越えていたら、最初に戻る(ループ再生)
                playPos = 0;
            }
        }
    }
}
//————————————————————–
void testApp::touchDown(ofTouchEventArgs &touch){
    //画面をタッチすると、録音モードへ
    mode = 1;
    recPos = 0;
}
//————————————————————–
void testApp::touchMoved(ofTouchEventArgs &touch){
}
//————————————————————–
void testApp::touchUp(ofTouchEventArgs &touch){
    //画面をから指を離すと、再生モードへ
    mode = 2;
    playPos = 0;
}
//————————————————————–
void testApp::touchDoubleTap(ofTouchEventArgs &touch){
}
[/code]
サンプル3:録音した音をインタラクティブに操作する
- 録音した音のピッチや音量を変化できるようにする
- 簡易版"Sampletoy" – 参考:Sampletoy
- 録音したサンプルを再生する際に工夫を加えてみる
- そのままの状態で録音開始
- 画面をタッチすると再生
- 画面をタッチしながら横に移動すると、音のピッチが変化
- 画面をタッチしながら縦に移動すると、音量が変化
testApp.h
[code language=”cpp”]
#pragma once
#include "ofMain.h"
#include "ofxiPhone.h"
#include "ofxiPhoneExtras.h"
//録音・再生する音の長さ
#define LENGTH 44100 * 3
class testApp : public ofxiPhoneApp{
public:
    void setup();
    void update();
    void draw();
    void touchDown(ofTouchEventArgs &touch);
    void touchMoved(ofTouchEventArgs &touch);
    void touchUp(ofTouchEventArgs &touch);
    void touchDoubleTap(ofTouchEventArgs &touch);
    void audioReceived( float * input, int bufferSize, int nChannels );
    void audioRequested( float * output, int bufferSize, int nChannels );
    float buffer[LENGTH]; //オーディオバッファ
    int sampleRate; //サンプリングレイト
    int recPos; //レコード位置
    float playPos; //再生位置
    int mode; //現在のモード、0:off, 1:recording, 2:play
    float audioLevel; //音量
    float playSpeed; //再生スピード(ピッチを変更できるようFloat型で)
};
[/code]
testApp.mm
[code language=”cpp”]
#include "testApp.h"
//————————————————————–
void testApp::setup(){
    //iPhone基本設定
	ofRegisterTouchEvents(this);
    ofBackground(0, 0, 0);
    ofSetFrameRate(60);
    //横位置で使用
    ofxiPhoneSetOrientation(OFXIPHONE_ORIENTATION_LANDSCAPE_RIGHT);
    //サンプリングレイトの設定
	sampleRate = 44100;
    //サウンド録音再生の初期化
	ofSoundStreamSetup(1, 1, this, sampleRate, LENGTH, 4);
    //録音モードに
    mode = 1;
    //録音、再生の位置を先頭に
    recPos = 0;
    playPos = 0;
playSpeed = 1.0;
}
//————————————————————–
void testApp::update(){
}
//————————————————————–
void testApp::draw(){
    ofSetColor(255, 255, 255);
    if (mode == 1) {
        //録音モードの場合、recordingの表示をして、背景を青に
        ofDrawBitmapString("recording", 10, 20);
        ofBackground(255, 0, 0);
    } else if (mode == 2) {
        //再生モードの場合、playingの表示をして、背景を赤に
        ofDrawBitmapString("playing", 10, 20);
        ofDrawBitmapString("pos = " + ofToString(playSpeed, 3), 10, 40);
        ofBackground(0, 0, 255);
    }
    //画面の幅と録音サンプル数の比率を計算
    int ratio = LENGTH / ofGetWidth();    
    //画面の横幅にあわせて、波形を描画
    for (int i = 0; i < LENGTH; i+=ratio){
		ofLine(i/ratio,ofGetHeight()/2,i/ratio,ofGetHeight()/2+buffer[i]*100.0f);
	}
}
//————————————————————–
//オーディオ入力の処理
void testApp::audioReceived(float * input, int bufferSize, int nChannels){
    //もし録音モードだったら
    if (mode == 1) {
        //バッファされたサンプルの数だけ処理
        for (int i = 0; i < bufferSize*nChannels; i++){
            //録音位置が設定した最大の長さに逹っしていなければ
            if(recPos<LENGTH){
                //バッファにサウンド入力を設定
                buffer[recPos] = input[i];
                //録音位置を進める
                recPos++;
            }else {
                //もし最大位置を越えていたら、最初に戻る(ループ再生)
                recPos = 0;
            }
        }
    }
}
//————————————————————–
//オーディオ再生の処理
void testApp::audioRequested(float * output, int bufferSize, int nChannels){
    //もし再生モードだったら
    if (mode == 2) {
        //バッファされたサンプル数だけ処理
        for (int i = 0; i < bufferSize*nChannels; i++) {
            //再生位置が設定した最大の長さに逹っしていなければ
            if(playPos<LENGTH){
                //バッファに格納したサウンドを再生
                output[i] = buffer[int(playPos)] * audioLevel;
                //音程にあわせて、再生位置を移動する
                playPos += playSpeed;
            } else {
                //もし最大位置を越えていたら、最初に戻る(ループ再生)
                playPos = 0;
            }
        }
    }
}
//————————————————————–
void testApp::touchDown(ofTouchEventArgs &touch){
    //画面をから指を離すと、再生モードへ
    mode = 2;
    playPos = 0;
    //音量を設定
    audioLevel = (ofGetHeight() – touch.y) / ofGetHeight() * 3.0;
    //音程を設定
    playSpeed = touch.x / ofGetWidth() * 2.0;
}
//————————————————————–
void testApp::touchMoved(ofTouchEventArgs &touch){
    //音量を設定
    audioLevel = (ofGetHeight() – touch.y) / ofGetHeight() * 3.0;
    //音程を設定
    playSpeed = touch.x / ofGetWidth() * 2.0;
}
//————————————————————–
void testApp::touchUp(ofTouchEventArgs &touch){
    //画面をタッチすると、録音モードへ
    mode = 1;
    recPos = 0;
}
//————————————————————–
void testApp::touchDoubleTap(ofTouchEventArgs &touch){
}
[/code]
サンプルファイルのダウンロード
今回の授業で紹介した全てのサンプルは、下記のリンクからダウンロード可能です







