yoppa.org


多摩美 - “iTamabi” – iPhoneアプリ開発プロジェクト 2010

openFrameworks for iPhone:サウンドの録音・再生

今日の内容

  • サウンドファイルの再生「Buddha 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

#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;
};

testApp.mm

#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){
    
}

サウンドを録音・再生する (サンプリング&プレイバック)

  • あらかじめ録音したサウンドファイルを再生するのではなく、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

#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
};

testApp.mm

#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){
    
}

サンプル3:録音した音をインタラクティブに操作する

  • 録音した音のピッチや音量を変化できるようにする
  • 簡易版"Sampletoy" – 参考:Sampletoy

  • 録音したサンプルを再生する際に工夫を加えてみる
  • そのままの状態で録音開始
  • 画面をタッチすると再生
  • 画面をタッチしながら横に移動すると、音のピッチが変化
  • 画面をタッチしながら縦に移動すると、音量が変化

testApp.h

#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型で)
};

testApp.mm

#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){
    
}

サンプルファイルのダウンロード

今回の授業で紹介した全てのサンプルは、下記のリンクからダウンロード可能です