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
[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]

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

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