maxiFilter

いわゆるフィルター。
一般的なものが最低限揃っている。

ソースを見る

class maxiFilter { 	
	double gain;
	double input;
	double output;
	double inputs[10];
	double outputs[10];
	double cutoff1;
	double x;//speed
	double y;//pos
	double z;//pole
	double c;//filter coefficient
public:
	maxiFilter():x(0.0), y(0.0), z(0.0), c(0.0){};
	double cutoff;
	double resonance;
	double lores(double input,double cutoff1, double resonance);
	double hires(double input,double cutoff1, double resonance);
	double bandpass(double input,double cutoff1, double resonance);
	double lopass(double input,double cutoff);
	double hipass(double input,double cutoff);
	
};
//I particularly like these. cutoff between 0 and 1
double maxiFilter::lopass(double input, double cutoff) {
	output=outputs[0] + cutoff*(input-outputs[0]);
	outputs[0]=output;
	return(output);
}
//as above
double maxiFilter::hipass(double input, double cutoff) {
	output=input-(outputs[0] + cutoff*(input-outputs[0]));
	outputs[0]=output;
	return(output);
}
//awesome. cuttof is freq in hz. res is between 1 and whatever. Watch out!
double maxiFilter::lores(double input,double cutoff1, double resonance) {
	cutoff=cutoff1*0.5;
	if (cutoff<10) cutoff=10;
	if (cutoff>(maxiSettings::sampleRate*0.5)) cutoff=(maxiSettings::sampleRate*0.5);
	if (resonance<1.) resonance = 1.;
	z=cos(TWOPI*cutoff/maxiSettings::sampleRate);
	c=2-2*z;
	double r=(sqrt(2.0)*sqrt(-pow((z-1.0),3.0))+resonance*(z-1))/(resonance*(z-1));
	x=x+(input-y)*c;
	y=y+x;
	x=x*r;
	output=y;
	return(output);
}

//working hires filter
double maxiFilter::hires(double input,double cutoff1, double resonance) {
	cutoff=cutoff1*0.5;
	if (cutoff<10) cutoff=10;
	if (cutoff>(maxiSettings::sampleRate*0.5)) cutoff=(maxiSettings::sampleRate*0.5);
	if (resonance<1.) resonance = 1.;
	z=cos(TWOPI*cutoff/maxiSettings::sampleRate);
	c=2-2*z;
	double r=(sqrt(2.0)*sqrt(-pow((z-1.0),3.0))+resonance*(z-1))/(resonance*(z-1));
	x=x+(input-y)*c;
	y=y+x;
	x=x*r;
	output=input-y;
	return(output);
}

//This works a bit. Needs attention.
double maxiFilter::bandpass(double input,double cutoff1, double resonance) {
	cutoff=cutoff1;
	if (cutoff>(maxiSettings::sampleRate*0.5)) cutoff=(maxiSettings::sampleRate*0.5);
	if (resonance>=1.) resonance=0.999999;
	z=cos(TWOPI*cutoff/maxiSettings::sampleRate);
	inputs[0] = (1-resonance)*(sqrt(resonance*(resonance-4.0*pow(z,2.0)+2.0)+1));
	inputs[1] = 2*z*resonance;
	inputs[2] = pow((resonance*-1),2);
	
	output=inputs[0]*input+inputs[1]*outputs[1]+inputs[2]*outputs[2];
	outputs[2]=outputs[1];
	outputs[1]=output;
	return(output);
}

フィルタの実装

他にフィルタを実装しているライブラリを見て
参考にしようと探したところ
Robert Bristow-Johnsonという方が「RBJ Audio EQ Cookbook」というテキストにまとめているようだ。
説明は下記がわかりやすい。

上記を参考にソースを追ってみたい。

関数を読み解く

と、いろいろ調べていたところで
どうもMaximilianのローパス・ハイパスフィルタはもっとお手軽に実装しているようだ。

//I particularly like these. cutoff between 0 and 1
double maxiFilter::lopass(double input, double cutoff) {
	output=outputs[0] + cutoff*(input-outputs[0]);
	outputs[0]=output;
	return(output);
}
//as above
double maxiFilter::hipass(double input, double cutoff) {
	output=input-(outputs[0] + cutoff*(input-outputs[0]));
	outputs[0]=output;
	return(output);
}

output[0] + cutoff*(input-output[0])
となっている。
これは、加算平均というデジタルフィルタで、主にノイズの除去に使われている
計算に似ている。
しかし加算平均であれば
output[0]*(1-cutoff) + cuoff*(input-output[0])
とすべきだが、それをもっとお手軽にした形なのだろう。
ま、そういうものということで、この二つはいいかと思う。

次にlores,hiresをみよう。

//awesome. cuttof is freq in hz. res is between 1 and whatever. Watch out!
double maxiFilter::lores(double input,double cutoff1, double resonance) {
	cutoff=cutoff1*0.5;
	if (cutoff<10) cutoff=10;
	if (cutoff>(maxiSettings::sampleRate*0.5)) cutoff=(maxiSettings::sampleRate*0.5);
	if (resonance<1.) resonance = 1.;
	z=cos(TWOPI*cutoff/maxiSettings::sampleRate);
	c=2-2*z;
	double r=(sqrt(2.0)*sqrt(-pow((z-1.0),3.0))+resonance*(z-1))/(resonance*(z-1));
	x=x+(input-y)*c;
	y=y+x;
	x=x*r;
	output=y;
	return(output);
}

//working hires filter
double maxiFilter::hires(double input,double cutoff1, double resonance) {
	cutoff=cutoff1*0.5;
	if (cutoff<10) cutoff=10;
	if (cutoff>(maxiSettings::sampleRate*0.5)) cutoff=(maxiSettings::sampleRate*0.5);
	if (resonance<1.) resonance = 1.;
	z=cos(TWOPI*cutoff/maxiSettings::sampleRate);
	c=2-2*z;
	double r=(sqrt(2.0)*sqrt(-pow((z-1.0),3.0))+resonance*(z-1))/(resonance*(z-1));
	x=x+(input-y)*c;
	y=y+x;
	x=x*r;
	output=input-y;
	return(output);
}

maxiEnvelope

maximilianには二つのエンベロープが存在する
そのうちの一つmaxiEnvelopeは最大1000個までのアンカーポイントを打てて、
それぞれの間をリニアにつなぐシンプルなエンベロープである。
もう一つのmaxEnvはadsrを使ったもののようである。

ソースを見る

class maxiEnvelope {
	
	double period;
	double output;
	double startval;
	double currentval;
	double nextval;
	int isPlaying;
	
public:	
	double line(int numberofsegments,double segments[100]);
	void trigger(int index,double amp);
	int valindex;
	double amplitude;
	
};
//I like this.
double maxiEnvelope::line(int numberofsegments,double segments[1000]) {
	if (isPlaying==1) {//only make a sound once you've been triggered
		
		period=2./(segments[valindex+1]*0.004);
		nextval=segments[valindex+2];
		currentval=segments[valindex];
		if (currentval-amplitude > 0.0000001 && valindex < numberofsegments) {
			amplitude += ((currentval-startval)/(maxiSettings::sampleRate/period));
		} else if (currentval-amplitude < -0.0000001 && valindex < numberofsegments) {
			amplitude -= (((currentval-startval)*(-1))/(maxiSettings::sampleRate/period));
		} else if (valindex >numberofsegments-1) {
			valindex=numberofsegments-2;
		} else {
			valindex=valindex+2;
			startval=currentval;
		}
		output=amplitude;
		
	}
	else {
		output=0;
		
	}
	return(output);
}

//and this
void maxiEnvelope::trigger(int index, double amp) {
	isPlaying=1;//ok the envelope is being used now.
	valindex=index;
	amplitude=amp;
	
}

関数を読み解く

関数は二つあるが、triggerはセットされたエンベロープをスタート・リセットさせるだけなので
lineを読み解く。

lineのsegmentsには、その配列の個数と値とステップ数を交互に入れたものを渡す。
{値、ステップ数、値、ステップ数、、、}
という具合だ。
値もステップ数も、前の値に関係なく絶対値だ。
ステップ数は秒でもミリ秒でもなく、
サンプリングレート等を考えて求める必要がある。

いまいち、step数が直感的でなく分かりづらいので、コードを追ってみたい。
5行目の部分

period=2./(segments[valindex+1]*0.004);

ここは、前の値からの増加期間を計算しているようだ。
9行目、11行目と関わってくる

amplitude += ((currentval-startval)/(maxiSettings::sampleRate/period));

サンプリングレートをperiodで割っている。
9,11行目の分子の部分が44100となると1秒、22050となると0.5秒で
目的の値になるのがわかるので、ここの値を求めてみたい。
実際にstep数を代入して計算してみる

maxSetting::sampleRateは44100とする

segments[valindex+1] = 1000の場合
period = 2./(1000*0.004);
period = 0.5
44100/0.5
=88200となる
つまり88200/44100=2で、2秒ということになる。

segments[valindex+1] = 500の場合
period = 2./(500*0.004);
period = 1.0
44100/1.0
=44100となる
つまり44100/44100=1で、1秒ということになる。


segments[valindex+1] = 250の場合
0.5秒
segments[valindex+1] = 2000の場合
4秒

となり
まとめると
2000の時4秒
1000の時2秒
500の時1秒
250の時0.5秒

で500で1秒と考えれば良さそう。

ただ気持ち悪い場合は0.004の部分を0.002にすれば
ミリ秒で考えられそうですね。

使ってみる

適当なキーを押すとトリガーになって、円が描画されます。

#pragma once
#include "ofMain.h"
#include "ofxMaxim.h"

class testApp : public ofBaseApp {

	public:
		~testApp();/* destructor is very useful */
		void setup();
		void update();
		void draw();

		void keyPressed  (int key);
		void keyReleased(int key);
		void mouseMoved(int x, int y );
		void mouseDragged(int x, int y, int button);
		void mousePressed(int x, int y, int button);
		void mouseReleased(int x, int y, int button);
		void windowResized(int w, int h);
		void dragEvent(ofDragInfo dragInfo);
		void gotMessage(ofMessage msg);
	
		void audioRequested 	(float * input, int bufferSize, int nChannels); /* output method */
		void audioReceived 	(float * input, int bufferSize, int nChannels); /* input method */
	
		int		initialBufferSize; /* buffer size */
		int		sampleRate;
	
		/* stick you maximilian stuff below */
        double sample;
        ofxMaxiEnvelope envelope;
};
#include "testApp.h"

double env[8]={20,0, 0,500, 250,1000, 40,250};

//-------------------------------------------------------------
testApp::~testApp() {
}

//--------------------------------------------------------------
void testApp::setup(){
	ofEnableAlphaBlending();
	ofSetupScreen();
	ofBackground(0, 0, 0);
	ofSetVerticalSync(true);
	
	sampleRate 			= 44100; /* Sampling Rate */
	initialBufferSize	= 512;	/* Buffer Size. you have to fill this buffer with sound*/
	
	ofSoundStreamSetup(2,0,this, sampleRate, initialBufferSize, 4);/* Call this last ! */
}
//--------------------------------------------------------------
void testApp::update(){
}
//--------------------------------------------------------------
void testApp::draw(){
	ofSetColor(255, 255, 255,255);
	ofCircle(ofGetWidth()/2, ofGetHeight()/2, sample);
	
}
//--------------------------------------------------------------
void testApp::audioRequested 	(float * output, int bufferSize, int nChannels){
	for (int i = 0; i < bufferSize; i++){
        sample = envelope.line(8, env);
	}
}
//--------------------------------------------------------------
void testApp::audioReceived 	(float * input, int bufferSize, int nChannels){	
	for (int i = 0; i < bufferSize; i++){}
}
//--------------------------------------------------------------
void testApp::keyPressed(int key){
    envelope.trigger(0, env[0]);
}
//--------------------------------------------------------------
void testApp::keyReleased(int key){
}
//--------------------------------------------------------------
void testApp::mouseMoved(int x, int y ){
}
//--------------------------------------------------------------
void testApp::mouseDragged(int x, int y, int button){
}
//--------------------------------------------------------------
void testApp::mousePressed(int x, int y, int button){
}
//--------------------------------------------------------------
void testApp::mouseReleased(int x, int y, int button){
}
//--------------------------------------------------------------
void testApp::windowResized(int w, int h){
}
//--------------------------------------------------------------
void testApp::gotMessage(ofMessage msg){
}
//--------------------------------------------------------------
void testApp::dragEvent(ofDragInfo dragInfo){ 
}

maxiDelayline

ソースを見る

まずはコードをmaxiDelaylineクラスの部分だけ抜き出して見てみる

class maxiDelayline {
	double frequency;
	int phase;
	double startphase;
	double endphase;
	double output;
	double memory[88200];
	
public:
	maxiDelayline();
	double dl(double input, int size, double feedback);
	double dl(double input, int size, double feedback, int position);
	
	
};
//Delay with feedback
maxiDelayline::maxiDelayline() {
	memset( memory, 0, 88200*sizeof (double) );
    phase=0;
}


double maxiDelayline::dl(double input, int size, double feedback)  {
	if ( phase >=size ) {
		phase = 0;
	}
	output=memory[phase];
	memory[phase]=(memory[phase]*feedback)+(input*feedback)*0.5;
	phase+=1;
	return(output);
	
}

double maxiDelayline::dl(double input, int size, double feedback, int position)  {
	if ( phase >=size ) phase = 0;
	if ( position >=size ) position = 0;
	output=memory[position];
	memory[phase]=(memory[phase]*feedback)+(input*feedback)*chandiv;
	phase+=1;
	return(output);
	
}

たったこれだけ。
バッファで用意されているのが、一秒間44100Hzとすると2秒分用意されている。

関数を読み解く

dl関数が二つ用意されていいるが、基本的に中身は一緒なので、より簡単な方で見ていこう。

double maxiDelayline::dl(double input, int size, double feedback)  {
	if ( phase >=size ) {
		phase = 0;
	}
	output=memory[phase];
	memory[phase]=(memory[phase]*feedback)+(input*feedback)*0.5;
	phase+=1;
	return(output);
}

上記は簡易的なリングバッファを使ったディレイと考えられる。
88200個のバッファに順に書き込まれ、終わりが来たらまた頭に戻る。
ただそれだけだ。
そして、書き込むと同時にアウトプットの値も返ってくる。

引数で注意したいのはsizeで、この値は88200を超えないことが暗黙の了解のようだ。
dlを呼ぶたびにphaseの値はインクリメントされinputの値とバッファがfeedbackの割合で加算される。その時inputの値は0.5倍される。
決めうちの値が多いが、初心者にとってはこのぐらいの方が使いやすい。
配列を渡すのではなく、ループでそれぞれの値を渡す形になっている
ここら辺気持ち悪ければ、関数をオーバーロードして配列を渡すようにしてもいいかもしれない。

使ってみる

下記は、ループ音源を使ったサンプル。
マウスのx座標でバッファのサイズを0~88200まで変化するようにした。
0だと、ディレイのない音で10,000を超えたぐらいからおかしくなる感じだ。
大体、4000~8000ぐらいが良さそうな感じ。

#pragma once

#include "ofMain.h"
#include "ofxMaxim.h"


class testApp : public ofBaseApp{

	public:
		~testApp();/* destructor is very useful */
		void setup();
		void update();
		void draw();

		void keyPressed  (int key);
		void keyReleased(int key);
		void mouseMoved(int x, int y );
		void mouseDragged(int x, int y, int button);
		void mousePressed(int x, int y, int button);
		void mouseReleased(int x, int y, int button);
		void windowResized(int w, int h);
		void dragEvent(ofDragInfo dragInfo);
		void gotMessage(ofMessage msg);
	
		void audioRequested 	(float * input, int bufferSize, int nChannels); /* output method */
		void audioReceived 	(float * input, int bufferSize, int nChannels); /* input method */
	
		int		initialBufferSize; /* buffer size */
		int		sampleRate;
	
	
		/* stick you maximilian stuff below */
	
		double sample,size,outputs[2];
		ofxMaxiMix mymix;
		ofxMaxiSample beat;
	
        ofxMaxiDelayline delay;
};
/* This is an example of how to integrate maximilain into openFrameworks, 
 including using audio received for input and audio requested for output.
 
 
 You can copy and paste this and use it as a starting example.
 
 */


#include "testApp.h"


//-------------------------------------------------------------
testApp::~testApp() {
	
	/*delete beat.myData;*/ /*you should probably delete myData for any sample object
						 that you've created in testApp.h*/
	
}


//--------------------------------------------------------------
void testApp::setup(){
    
	ofEnableAlphaBlending();
	ofSetupScreen();
	ofBackground(0, 0, 0);
	ofSetVerticalSync(true);
	
	sampleRate 			= 44100; /* Sampling Rate */
	initialBufferSize	= 512;	/* Buffer Size. you have to fill this buffer with sound*/
	
	beat.load(ofToDataPath("beat2.wav"));
	beat.getLength();
	
	ofSoundStreamSetup(2,0,this, sampleRate, initialBufferSize, 4);/* Call this last ! */
}

//--------------------------------------------------------------
void testApp::update(){
	
}

//--------------------------------------------------------------
void testApp::draw(){
	
	ofSetColor(255, 255, 255,255);
	ofCircle(ofGetWidth()/2, ofGetHeight()/2, sample*150);
	
}

//--------------------------------------------------------------
void testApp::audioRequested 	(float * output, int bufferSize, int nChannels){
	
	for (int i = 0; i < bufferSize; i++){
		
		sample=delay.dl(beat.play(0.25, 0, beat.length),size,0.5);
		mymix.stereo(sample, outputs, 0.5);
		
		output[i*nChannels    ] = outputs[0]; /* You may end up with lots of outputs. add them here */
		output[i*nChannels + 1] = outputs[1];
	}
	
}

//--------------------------------------------------------------
void testApp::audioReceived 	(float * input, int bufferSize, int nChannels){	
	
	
	/* You can just grab this input and stick it in a double, then use it above to create output*/
	
	for (int i = 0; i < bufferSize; i++){
		
		/* you can also grab the data out of the arrays*/
		
		
	}
	
}

//--------------------------------------------------------------
void testApp::keyPressed(int key){
	
}

//--------------------------------------------------------------
void testApp::keyReleased(int key){
	
}

//--------------------------------------------------------------
void testApp::mouseMoved(int x, int y ){
    size = ((double)ofGetMouseX()/(double)ofGetWidth()) * 88200.f;
    if(size < 0)
        size=0;
    if(size > 88200)
        size = 88200;
    printf("size:%f\n",size);
}

//--------------------------------------------------------------
void testApp::mouseDragged(int x, int y, int button){
	
}

//--------------------------------------------------------------
void testApp::mousePressed(int x, int y, int button){
	
}

//--------------------------------------------------------------
void testApp::mouseReleased(int x, int y, int button){
	
}

//--------------------------------------------------------------
void testApp::windowResized(int w, int h){
	
}



//--------------------------------------------------------------
void testApp::gotMessage(ofMessage msg){

}

//--------------------------------------------------------------
void testApp::dragEvent(ofDragInfo dragInfo){ 

}

Maximilian

OPenframeworksのmaximilianというAddonの
ソースコドを追ってサウンドプログラミングを学んでみようと思います。

github
サイト