SuperCollider+OSC+OpenFrameworksその2

SuperColliderでFFTからFreqScopeと同様の値を作成する試み。

上が自作、下がSCのFreqScope(周波数はリニアスケール)

↑の波形の確認用に使ったTidalcyclesのパターン:

d2 $ spread ($) [fast 1, rev, fast 2, slow 2]

$ n (run 8*16) # s "supersaw"

SuperColliderのコード:

RMSメーター同様に数フレーム分のバッファからスペクトルのピークを算出。

FFTの結果をいくつかの帯域に切り分けてパワースペクトルを求めている。

2019.1.21修正版

// 先にSuperDirtを起動しておく。後からだとうまくいかない。

SuperDirt.postBadValues = false // ChocolateyのSupercolliderの挙動対策

SuperDirt.start

SuperDirt.stop

s.boot;

b = Buffer.alloc(s,2048);

// 実行すると Warningが出るが支障ない。コメント入れてコード量が増えたからかも。

// SuperColliderのオーディオOUTを分析

(

// スペアナのバンド数

~size=16*8*8*2;

// 0Hz~22.1kHzまで帯域を切り分ける

~cutfreqs = Array.fill(~size,{|i| (s.sampleRate/~size*i).asInt});

c = Bus.control(s,~size);

x = {

Out.kr(c.index, // 分析結果をcで受け取る

RunningSum.kr( // RMSを求める

FFTSubbandPower.kr( // 帯域毎のpowerスペクトルを求める

FFT(b,In.ar), // serverの音声出力をFFTする

~cutfreqs, square: 1, scalemode: 1)/(~cutfreqs[1]), // 帯域当たりのサンプル数で割る。

40)/40 // RMS用のFrame数。平均をとるためFrame数で割る。

);

}.play(s,addAction: \addToTail); // serverが音声出力した後に分析をかける

)

x.free;

// グラフと数値の表示

(

p = Plotter("Freq.Scope",Rect(500,500,800,250));

t = Task({

loop{

c.getn(~size/2,{|val|

{

(val.log10*10).postln;

p.setValue(val.log10*10,findSpecs: true, minval:-96, maxval:0,defaultRange:1);

}.defer

});

0.016.wait;

};

}).start;

)

t.stop;

// テスト用音源

(

z = { SinOsc.ar(440*4,0,0.125)+SinOsc.ar(440*10,0,0.125)+SinOsc.ar(440*45,0,0.125)

}.play(s);

)

z.free;

// OSCでスペアナの結果を送信する

(

a = NetAddr.new("127.0.0.1", 8000);

v = Int8Array.fill(~size/2,0); // OSCメッセージ blob型用

o = Task({

loop{

c.getn(~size/2,{|val|

{

(val.log10*10).do({|e,i|

if(e < -96 || e.isNaN) {v[i] = -96} {v[i] = e.asInt};

});

v.postln;

a.sendMsg("/test",v);

}.defer

});

0.016.wait;

}

}).start;

)

o.stop;

OpenFrameworks側のコード

ofApp.h

const static int receivedBuffer_size = 1024;

char receivedBuffer[receivedBuffer_size];

ofApp.cpp

PIとsin cosの定義:

#include <cmath>

void ofApp::draw() の中で、スペクトルを表示

int x = 10;

ofSetColor(255,255,255);

for (char c : receivedBuffer) {

ofDrawLine(x,400,x,400-(96+c)*4.0);

x += 2;

}

ofSetColor(255, 0, 127);

int i = 0;

for (char c : receivedBuffer) {

int r2 = 150;

int r1 = r2+2 * (96 + c);

float theta = 2.0*PI* i / receivedBuffer_size;

ofDrawLine(800 + r2 * cos(theta), 400 + r2 * sin(theta), 800+ r1*cos(theta), 400 + r1*sin(theta));

i++;

}

実行結果

SupercolliderのFFTの調整

このyotube動画と同じ感じを目指してみた。https://twitter.com/_kobashi/status/1087609112127553537

まだいろいろと差がある。

周波数のメモリをlogにしてさらにsqrtする。

FFT用のバッファを8倍確保して、FFTのwindowsizeは元の半分にしてみた。

処理が重たくなるので、RMSで平均する個数は抑えた。

スペクトルの値はdB表示から値をsqrtしたものに変更した。

※OSCで送信する値は、適切な精度を保ったままInt8に収まるように調整する必要がある。

s.boot;

s.stop;

b = Buffer.alloc(s,2048*8);

// 実行すると Warningが出るが支障ない。コメント入れてコード量が増えたからかも。

// SuperColliderのオーディオOUTを分析

(

// スペアナのバンド数

//~size=16*8*8*2;

~size=16*8*2;

// 0Hz~22.1kHzまで帯域を切り分ける

//~cutfreqs = Array.fill(~size,{|i| (s.sampleRate/~size*i/2).asInt}); // linearスケール

~cutfreqs = Array.fill(~size,{|i| exp(log(s.sampleRate/2)*sqrt(i/~size)).asInt}); //logスケールをsqrtしてグラフを0Hz側に寄せた

c = Bus.control(s,~size);

x = {

Out.kr(c.index, // 分析結果をcで受け取る

RunningSum.kr( // RMSを求める

FFTSubbandPower.kr( // 帯域毎のpowerスペクトルを求める

FFT(b,In.ar(2),wintype: 1,winsize: 2048/2), // serverの音声出力をFFTする

~cutfreqs, square: 1, scalemode: 1),

2)/2 // RMS用のFrame数。平均をとるためFrame数で割る。

);

}.play(s,addAction: \addToTail); // serverが音声出力した後に分析をかける

)

x.free;

// グラフと数値の表示

(

p = Plotter("Freq.Scope",Rect(500,500,800,250));

t = Task({

loop{

c.getn(~size,{|val|

{

(val.log10*10).postln;

//p.setValue(val.log10*10,findSpecs: true, minval:-96, maxval:0,defaultRange:1);

p.setValue(sqrt(val)*1,findSpecs: true, minval:0, maxval:0.025,defaultRange:1);

}.defer

});

0.016.wait;

};

}).start;

)

t.stop;

s.boot;

s.stop;

b = Buffer.alloc(s,2048);

(

// クロマグラフの音数

~size=12;

c = Bus.control(s,~size);

x = {

Out.kr(c.index, // 分析結果をcで受け取る

RunningSum.kr( // RMSを求める

Chromagram.kr(

FFT(b,In.ar(2)), // serverの音声出力をFFTする

fftsize: 2048, n: ~size, tuningbase: 32.703195662575,

octaves: 8, integrationflag: 0, coeff: 0.9, octaveratio: 2,

perframenormalize: 1),

40)/40 // RMS用のFrame数。平均をとるためFrame数で割る。

);

}.play(s,addAction: \addToTail); // serverが音声出力した後に分析をかける

)

x.free;

// グラフと数値の表示

(

p = Plotter("Freq.Scope",Rect(500,500,800,250));

t = Task({

loop{

c.getn(~size,{|val|

{

(val.log10*10).postln;

p.setValue(val*100,findSpecs: true, minval:0, maxval:10,defaultRange:1);

}.defer

});

0.016.wait;

};

}).start;

)

t.stop;

// テスト用音源

(

z = { SinOsc.ar(440*4,0,0.125)+SinOsc.ar(440*10,0,0.125)+SinOsc.ar(440*45,0,0.125)

}.play(s);

)

z.free;

追記:サウンドの可視化の際は可聴域に応じて帯域制限してからFFTを実行すると効果的の模様。

b = Buffer.alloc(s,2048);

// 実行すると Warningが出るが支障ない。コメント入れてコード量が増えたからかも。

// SuperColliderのオーディオOUTを分析

(

// スペアナのバンド数

// 0Hz~22.1kHzまで帯域を切り分ける

~size=100;

~cutfreqs = Array.fill(~size,{|i| (440*exp(log(2)/12*(i-40))).asInt});

c = Bus.control(s,~size+1);

x = {

Out.kr(c.index, // 分析結果をcで受け取る

RunningSum.kr( // RMSを求める

FFTSubbandPower.kr( // 帯域毎のpowerスペクトルを求める

FFT(b,HPF.ar(In.ar(2),440) // 可視化する帯域を制限する

,wintype: 1,hop:0.5,winsize: 2048), // serverの音声出力をFFTする

~cutfreqs, square: 1, scalemode: 2),

60)/60 // RMS用のFrame数。平均をとるためFrame数で割る。

);

}.play(s,addAction: \addToTail); // serverが音声出力した後に分析をかける

)

x.free;

// グラフと数値の表示

(

p = Plotter("Freq.Scope",Rect(500,500,800,250));

t = Task({

loop{

c.getn(~size+1,{|vals|

{

(vals.log2 + 7 * 0.08).max(0).min(1).postln;

p.setValue((vals*30+1).log10,findSpecs: true, minval:0, maxval:3,defaultRange:1);

}.defer

});

0.016.wait;

};

}).start;

)

t.stop;

// OSCでスペアナの結果を送信する

(

a = NetAddr.new("127.0.0.1", 8000);

v = Int8Array.fill(~size+1,0); // OSCメッセージ blob型用

o = Task({

loop{

c.getn(~size+1,{|val|

{

((vals*30+1).log10).do({|e,i|

if(e > 3) {v[i] = 127} {v[i] = (e*127/3).asInt};

});

v.postln;

a.sendMsg("/test",v);

}.defer

});

0.016.wait;

}

}).start;

)

o.stop;

追記2:

oF側のコードにOSCで受信したSCのFFTの値を合計して60*5(60fpsで受信できていれば約5秒間分)個記録して表示するコードを加えた。

ofApp.h

int waveBuffer[60*5];

ofApp.cpp

int sum = 0;

for (char c : receivedBuffer) {

sum += c;

}

memcpy(&waveBuffer[0], &waveBuffer[1], (60 * 5 - 1) * sizeof(int));

waveBuffer[60 * 5 - 1] = sum/20;

ofSetColor(0, 192, 64);

x = -100;

for (int e : waveBuffer) {

ofDrawLine(x, 600 + e * 0.5, x, 600 - e * 0.5);

x += 3;

}