portaudio

portaudio + OpenGL (ortho-graphic)

Xcode
cocoaPatip01

VC++.net
pa000.zip

 キー"1"でストリームスタート、
キー"2"でストリームストップ
です。

アウトプットのみ( L R 2ch )


pa使用手順→
pacall.h にて音データの型・パラメータ・チャンネル等
を定義しておく
pacall.c にてアウトプットストリームへ送る手順を定義する

ここからは自前クラス(サンプルではPaSnd.h,m)

PaSnd.h,mにて波形を定義したり、操作関数を書いておく

メインループを持っている描画クラス内で(GLView.h,m)オブジェクト化して使う。キー操作などと結びつける。

ファイルの構成(Xcodeのファイルエクスプローラ Groups&Files から見て)
フォルダ"Other Sources"  
     フォルダ"pa" portaudio関連のファイルが色々入っていますが、全く関知する必要ありません。
pacall.h

ここにアウトプットストリームに送るデータを設計しておく。
#define OUTCHANNELS 2

typedef struct
{
float* wave[OUTCHANNELS];
float wavcount[OUTCHANNELS];
unsigned long int wavlength[OUTCHANNELS];
}paData;

もっともシンプルな例の一つ。
音のデータ(波形=数列)
データのどの部分にキューしているかを示すカウント/数値。
データの長さ。
という構成になる。

次のような構成も考えられる。
typedef struct
{
float* wave[OUTCHANNELS];
float wavcount[OUTCHANNELS];
unsigned long int wavlength[OUTCHANNELS];
float pitch[OUTCHANNELS];
float level[OUTCHANNELS]
}paData;

音のデータ(波形=数列)
データのどの部分にキューしているかを示すカウント/数値。
データの長さ。
ピッチ。
レベル。

pacall.c

pacall.hで設計したデータの構成をどういう順番でストリームに送るかを記述する。
int
paCallback(void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, PaTimestamp tms, void *userData)
{
paData *data=(paData*)userData;
unsigned int frameIndex,chn;
float *out = (float*)outputBuffer;

for(frameIndex=0;frameIndex<framesPerBuffer;frameIndex++)
{
for(chn=0;chn<OUTCHANNELS;chn++)
{
*out++ = data->wave[chn][(int)data->wavcount[chn]];

if(data->wavcount[chn]<data->wavlength[chn]-1)
{
data->wavcount[chn]++;
}else
{
data->wavcount[chn]=0;
}
}//for chn
}//for buffer
return 0;
}

このコールバック関数は、ストリームが流れ出すと勝手にフレーム毎に呼び出されます。つまりこの関数は自分でコールするものではありません。
この関数の中に2つのループが入れ子になっています。
for(frameIndex=0;frameIndex<framesPerBuffer;frameIndex++)

for(chn=0;chn<OUTCHANNELS;chn++)
フレーム毎の繰り返し、チャンネル毎の繰り返し。

そして
if(data->wavcount[chn]<data->wavlength[chn]-1)
{
data->wavcount[chn]++;
}else
{
data->wavcount[chn]=0;
}
で音データ/数列の中のカウントを長さに応じてリセットしています。

各引数、変数は以下のような意味を持っています。
void *inputBuffer
入力ストリームへのポインタです。このサンプルでは利用していません。

void *outputBuffer/float *out
出力 ストリームへのポインタです。ここにデータを送り込んでいきます。チャンネル数毎にオフセットするところに注意して下さい。

unsigned long framesPerBuffer
バッファ内のフレームの数です。portaudioでストリームが開かれるとき(関数Pa_OpenStream)に設定されます。このサンプルではクラス"PaSnd"の中で一連の初期化/設定を行っていますが、数値が少ないほどレイテンシーが良くなると考えてください。

PaTimestamp tms
タイムスタンプです。このサンプルでは利用していません。

void *userData /paData *data
pacall.hで設計したデータとつながるところです。ここが自分で改造していくところです。

main.m ーー
フォルダ"Classes"  
       NSGLFont.h OpenGLフォント関連クラス
NSGLFont.m
PaSnd.h portaudioを使うためにまとめたクラス。このサンプルでは以下のものを実装してあります。
-(void)ini;
初期化/ストリームを開きサンプルレート、バッファ内のフレームの数などを設定します。

-(void)start;
ストリームスタート。クラスGLViewのなかで、"1"のキーと関連づけてあります。

-(void)stop;
ストリームストップ。クラスGLViewのなかで、"2"のキーと関連づけてあります。

-(void)releace;
後処理。

-(paData)getFlow;
クラスGLViewで描画するために、flowdataを返す関数(アクセッサメソッド)
ここで返しているflowdataがpacall.hで設計した音のデータ/数列であることに留意して下さい。
PaSnd.m
GLView.h 描画、キー操作関連。
GLView.m

portaudio + OpenGL (ortho-graphic)

Xcode
cocoaPatip03

キー"1"でストリームスタート、
キー"2"でストリームストップ、
キー"スペース"で新しい音源生成、
キー"d"で一番古い音源を排除。

アウトプットのみ( L R 2ch )

cocoaPatip01を改造したものです。pacall.hで定義している構造体paDataの内容が
typedef struct
{
NSMutableArray *array;
}paData;
という風にNSMutableArrayのみになっていて、この可変配列に格納されるのは
SndSrcというクラスから作られるオブジェクトになります。
SndSrcのメンバは

float* wave; 波形データ
bool loop; ループするか否か
unsigned long length; 波形データの長さ
unsigned long count;  波形データの現在位置

になっていて setメソッドで様々な長さの波形が作れます。
pacall.cでは、ポインタで渡されたpaDataの中のNSMutableArray arrayをその長さ分読み出し、ミックスし、
一つの波形データの配列を同等にLRのストリームに書き込んでいます。ここにパンニングの機能を組み込むのもアリでしょう。
理屈ではメモリの許す限り音源を生成することが出来るハズですが、このサンプルではすべての波形をリアルタイムに描画する様になっているので、描画部分の負担によってパフォーマンスが落ちてくると思います。


portaudio + OpenGL (ortho-graphic)
cocoaPatip02

portaudio tip

入力ストリームを考える
cocoaPatip01
とほとんど同じです。どこが違うのか探してみてください。
キー"1"でストリームスタート、
キー"2"でストリームストップ
です。


portaudio + OpenGL (ortho-graphic)
cocoaPatip04

各チャンネルの入力レベルを計る
キー"1"でストリームスタート、
キー"2"でストリームストップ

pacall.hのpaDataという構造体に入力に関するデータ型を追加してあります。
pacall.cのpaCallback内で
****=*in++;
で構造体に入力ポートからの波形データを収納しています。

クラスPaSndでのメソッド
-(float)getInputLevel:(int)chn
でチャンネル指定し何となくレベルを返していますが、レベルの計算これでいいのだろうか??
久保田先生に要質問。
GlViewの- (void)drawメソッドの中でコールしています。


portaudio + OpenGL (ortho-graphic)
cocoaPatip05

入力ストリームを出力ストリームへ切り替える
キー"1"でストリームスタート、
キー"2"でストリームストップ、
キー"3"でチャンネル 0の録音スタート/再押下でストップ&プレイバック、
キー"4"でチャンネル 1の録音スタート/再押下でストップ&プレイバック

pacall.hのpaDataという構造体に
int rec[OUTCHANNELS];
というメンバ を追加してあります。
pacall.cのpaCallback内で
int rec[OUTCHANNELS];の値によってバッファをストリームに出力するか否かを切り替えています。
要するに
*out++ =*in++;
とすればそのままリダイレクトされるということです。

クラスPaSndでのメソッド
-(void)rec:(int)chn;
でチャンネル指定しint rec[OUTCHANNELS];の値を変化させています。
GlViewの- (void)keyDown:(NSEvent*)eventメソッドの中でコールしています。


portaudio + OpenGL (ortho-graphic)
Xcode
cocoaPatip06

VC++ .Net
vcPatip06

リアルタイムエフェクト:遊んでみました。色々バリエーションがあると思います。
キー"1"でストリームスタート、
キー"2"でストリームストップ、
キー"3"でチャンネル 0の録音スタート/再押下でストップ&プレイバック、
キー"4"でチャンネル 1の録音スタート/再押下でストップ&プレイバック、
キー"5"でチャンネル 0のプレイバック中の重ね回数を増やす、
キー"6"でチャンネル 0のプレイバック中の重ね回数を減らす、
キー"7"でチャンネル 1のプレイバック中の重ね回数を増やす、
キー"8"でチャンネル 1のプレイバック中の重ね回数を減らす。

マイクとスピーカーの位置が近いとフィードバック+ハウリングするので注意してください。マイクとスピーカーを別途接続した方が無難です。

pacall.hのpaDataという構造体に
int delayval[OUTCHANNELS];
というメンバ を追加してあります。
pacall.cのpaCallback内で
int delayval[OUTCHANNELS];の値によってバッファをストリームに出力する際の重ね合わせ回数を変化させています。
例えば2回重ね合わせるときは 3000カウント先のバッファを現在のカウントのバッファに加算してストリームに送っています。
例えば3回重ね合わせるときは 3000カウント先のバッファと6000カウント先のバッファ
を現在のカウントのバッファに加算してストリームに送っています。

クラスPaSndでのメソッド
-(void)delay:(int)chn:(int)up
でチャンネル指定しint delayval[OUTCHANNELS];の値を変化させています。
GlViewの- (void)keyDown:(NSEvent*)eventメソッドの中でコールしています。