2017/11/26
PADcymbal
デモを見る (github.io)
PADcymbalはPADsynthを利用したシンバルのような音を合成するシンセサイザです。
仕組みとしてはランダムに作った周波数と音量の組をPADsynthに入力しているだけです。その他、製作の過程で調べたことをライドシンバル合成の試みにまとめています。
2017/11/23
PADsynthでシンバルの合成
PADsynthによるシンバル (Freesound)
始めは周波数成分を手作業でPADsynthに入力するつもりでしたが、無理そうだったのでKernel Density Estimationという手法を応用してある程度ランダムに合成することにしました。
作ったプログラムにFreesoundにあったシンバルのPackをいくつか投げてみたところ思ったよりもいい結果が出たのですが、シンバル以外のPackも試したところ何を入れても似たような音が出てくることが分かりました。
何を入れても同じならランダムに作ったデータでもいいんじゃないかと試したところ冒頭のような音が合成できました。ただし音量エンベロープはAudacityで後付けしています。
以下のPython3のプログラムで音量エンベロープをかけていない音が合成できます。
padsynth.py (GitHub)
合成についての細かい話を以下にまとめています。
ライドシンバル合成の試み (GitHub)
2017/09/10
2017/09/08
Freeverb
デモを見る (github.io)
Freeverbのインパルス応答を書き出すレンダラを作りました。
Freeverbはリバーブの実装の一つで金属的な音が出ます(1, 2)。デモページのAllpassの値を大きくすると金属的な部分を強調して遊ぶことができます。パラメータによってはレンダリングに時間がかかるので注意してください。
2017/08/20
Ardourクイックスタート
ArdourクイックスタートはGitHub Pagesで読むこともできます。
2017/07/27
SuperColliderでZynAddSubFXのPADsynth
SuperColliderでZynAddSubFXのPADsynth algorithmを実装します。細かい部分についてはPADsynth algorithmのページにあるC/C++でのリファレンス実装が参考になりました。
オリジナルもそうですが、レンダリングに時間がかかります。
( // 正規分布のharmonic profile。 ~profile = { |fi, bwi| var x = fi / bwi; exp(x.neg * x) / bwi }; // デフォルト値はリファレンス実装のc_basicと同じ。 ~padTable = { | server(s), size(2**18), f0(261.0), bw(40.0), number_harmonics(64) , ampFunc({ |i| (if ((i % 2) == 0) {2.0} {1.0}) / i }) | var sampleRate = server.sampleRate; var freq_amp = Signal.newClear(size / 2); var amps = Array.fill(number_harmonics + 1, ampFunc); var complex, smp; for (1, number_harmonics, { |nh| var bw_Hz = (pow(2, bw / 1200) - 1.0) * f0 * nh; var bwi = bw_Hz / (2.0 * sampleRate); var fi = f0 * nh / sampleRate; freq_amp.do{ |f_amp, index| var hprofile = ~profile.value((index / size) - fi, bwi); freq_amp[index] = f_amp + (hprofile * amps[nh]); } }); // 直流を除去。 freq_amp[0] = 0; freq_amp.plot; // ナイキスト周波数以上の成分を付け加える。 freq_amp = freq_amp ++ Signal.fill(freq_amp.size, 0); // 位相のランダマイズ。 complex = Polar( freq_amp, freq_amp.class.fill(freq_amp.size, {2pi.rand}) ); smp = complex.real.ifft(complex.imag, Signal.fftCosTable(freq_amp.size)); smp.real.normalize }; ); ( ~sig = ~padTable.value(s, 2**18); // 乗数が 10 以下のときにピッチがおかしくなる。 ~sig.plot; ~waveTable = Buffer.loadCollection(s, ~sig); { Pan2.ar(PlayBuf.ar(1, ~waveTable, 1, 1, 0, 1)) }.play; );
2017/07/26
SuperCollider - ウェーブテーブルの作成
Signal
を使って Osc
などのUGenで使うウェーブテーブルを作ります。
sineFill
sineFill
で加算合成ができます。
以下の例ではランダムに生成したArrayを降順にソートしています。これによって低い倍音ほど音量が大きくなり、音程がわかりやすくなります。
( var size = 1024; var harmonics = 128; var sig = Signal.sineFill( size, {exprand(0.00001, 1.0)}.dup(harmonics).sort.reverse, {100pi.rand}.dup(harmonics).sort); sig.plot; sig.play(loop: true, numChannels: 2) ); Window.closeAll; // ウィンドウをまとめて閉じる。
chebyFill
chebyFill
でチェビシェフ多項式を使った合成ができます。
得られるウェーブテーブルは Shaper
での利用に向いているようです。 Shaper
で使うときは引数に zeroOffset: true
を渡すことが推奨されています。
( var size = 1024; var ampSize = 32; var sig = Signal.chebyFill( size, {exprand(0.00001, 1.0)}.dup(ampSize).sort.reverse, zeroOffset: true); sig.plot; { var buffer = Buffer.loadCollection(s, sig); var mouseX = 1 - MouseX.kr(1, 0.00001, 1); var osc = SinOsc.ar(60, mul: mouseX); Pan2.ar(Shaper.ar(buffer, osc, 0.2)) }.play; );
以下は chebyFill
を点対称な波形に変形する例です。
( var size = 1024; var ampSize = 32; var sig = Signal.chebyFill( size / 2, {exprand(0.00001, 1.0)}.dup(ampSize).sort.reverse); var zero = sig[0]; var sigPositive = Signal.newFrom(sig) - zero; var sigNegative = sig.invert.reverse + zero; sig = (sigNegative ++ sigPositive).normalize; sig.plot; { var buffer = Buffer.loadCollection(s, sig); var mouseX = 1 - MouseX.kr(1, 0.00001, 1); var osc = SinOsc.ar(60, mul: mouseX); Pan2.ar(Shaper.ar(buffer, osc, 0.2)) }.play; );
fft, ifft
FFTを使って周波数領域での編集を行います。 Signal.fft
と Signal.ifft
は引数に Signal.fftCosTable
が必要となる点に注意してください。
のこぎり波を作ります。
( // FFT saw. ~addSaw = { |array, step| var sign = 1000; forBy(1, array.size / 2 - 1, 1, { |index| array[index] = array[index] + (sign / index); sign = sign.neg; }); array }; ~dckill = { |complex| complex.real[0] = 0; complex.imag[0] = 0; complex }; ~saw = { |size(1024), noiseGain(0.001)| var signal, cosTable; cosTable = Signal.fftCosTable(size); signal = Signal.fill(size, {noiseGain.rand}); // ノイズ。 signal = ~addSaw.value(signal); signal[0] = 0; // 直流を除去。 signal = Signal.newClear(size).ifft(signal, cosTable); signal = signal.real.rotate(size / 2); // 位相を進める。 signal.normalize }; ~wave = ~saw.value(noiseGain: 1.0); ~wave.plot; ~wave.play(true, numChannels: 2); );
以下の例では、時間領域で作ったのこぎり波の位相を周波数領域でランダマイズして、矩形波を重ねています。
( ~addSquare = { |array| var sign = 1000/0.75; forBy(1, array.size / 2 - 1, 2, { |index| array[index] = array[index] + (sign / index); sign = sign.neg; }); array }; ~randPhase = { |complex| var polar = complex.asPolar; polar.theta = polar.theta.collect{360.0.rand}; polar.asComplex }; ~dckill = { |complex| complex.real[0] = 0; complex.imag[0] = 0; complex }; ~sawsquare = { |size(1024), noiseGain(0.001)| var signal, cosTable; signal = Signal.fill(size, { |index| 2 * index / size -1 + noiseGain.rand}); // のこぎり波とノイズ。 cosTable = Signal.fftCosTable(size); signal = signal.fft(Signal.newClear(size), cosTable); signal = ~randPhase.value(signal); signal.real = ~addSquare.value(signal.real); signal = ~dckill.value(signal); signal = signal.real.ifft(signal.imag, cosTable); signal.real.normalize }; ~wave = ~sawsquare.value; ~wave.plot; ~wave.play(true, numChannels: 2); );
ファイルに保存
一度 Buffer
にしてから保存します。
( ~sineFill = { |size(1024), harmonics(128)| Signal.sineFill( size, {exprand(0.00001, 1.0)}.dup(harmonics).sort.reverse, {100pi.rand}.dup(harmonics).sort); }: ~dir = Platform.recordingsDir +/+ "wavetable"; ~dir.mkdir; // String.mkdir Buffer .loadCollection(s, ~sineFill.value) .write(~dir +/+ "wavetable.wav", "WAV", "float"); );
2017/07/25
初めてのFaust
Faust環境を整えたので、簡単なシンセサイザを作りながらFaustを使うときのワークフローを組み立てていきます。
プロジェクトの作成
プロジェクトのディレクトリを作ります。
mkdir saw cd saw
エディタにはAtomを使います。language-faustをインストールして便利な補完を使います。
atom saw.dsp
saw.dspを編集して音を出します。以下はFaust Libraries Documentationにあった例です。
import("stdfaust.lib"); process = os.osc(440);
編集した.dspをFaustLiveに渡して動作確認します。
FaustLive saw.dsp
GUI
GUIについてはFaust Quick Referenceの3.5.6 User Interface Elementsに載っています。
saw.dspに音量を調節するスライダを追加します。
import("stdfaust.lib"); osc = os.osc(440); process = osc * hslider("amp", 0.1, 0.0, 1.0, 0.01);
hslider(str, cur, min, max, step)
となっています。 cur
は初期値です。
オシレータ
saw.dspという名前にしたのでのこぎり波を出すように修正します。oscillators.libの sawtooth
を使います。
import("stdfaust.lib"); amp = hslider("amp", 0.1, 0.0, 1.0, 0.01); pan = hslider("pan", 0.5, 0.0, 1.0, 0.01); freq = hslider("freq", 0.5, 0.0, 1.0, 0.01) : ba.lin2LogGain * 980 + 20; osc = os.sawtooth(freq); process = osc * amp : sp.panner(pan);
lin2LogGain
でスライダの値を対数スケールに変換しています。 :
は式の左から右に信号を送る演算子です。
panner
で信号をステレオにしています。
エンベロープ
envelopes.libの ar
を使います。
import("stdfaust.lib"); amp = hslider("amp", 0.1, 0.0, 1.0, 0.01); pan = hslider("pan", 0.5, 0.0, 1.0, 0.01); freq = hslider("freq", 0.5, 0.0, 1.0, 0.01) : ba.lin2LogGain * 980 + 20; attack = hslider("attack", 0.001, 0.0, 1.0, 0.001); release = hslider("release", 0.2, 0.0, 1.0, 0.001); gate = button("gate"); env = en.ar(attack, release, gate); osc = os.sawtooth(freq); process = env * osc * amp : sp.panner(pan);
フィルタ
filters.libの resonlp
を使います。フィルタ用のエンベロープも用意します。
import("stdfaust.lib"); amp = hslider("amp", 0.1, 0.0, 1.0, 0.01); pan = hslider("pan", 0.5, 0.0, 1.0, 0.01); freq = hslider("freq", 0.5, 0.0, 1.0, 0.01) : ba.lin2LogGain * 980 + 20; ampAttack = hslider("amp attack", 0.001, 0.0, 1.0, 0.001); ampRelease = hslider("amp release", 0.2, 0.0, 1.0, 0.001); gate = button("gate"); cutoff = hslider("cutoff", 0.5, 0.0, 1.0, 0.01) : ba.lin2LogGain * 980 + 20; resonance = hslider("resonance", 0.5, 0.01, 1.0, 0.01) : ba.lin2LogGain * 100; filterAttack = hslider("filter attack", 0.001, 0.0, 1.0, 0.001); filterRelease = hslider("filter release", 0.2, 0.0, 1.0, 0.001); filterEnvAmount = hslider("filter envelope amount", 0.5, 0.0, 1.0, 0.01) : ba.lin2LogGain * 980 + 20; filterEnv = en.ar(filterAttack, filterRelease, gate); filter = fi.resonlp( cutoff + filterEnvAmount * filterEnv, resonance, 1); ampEnv = en.ar(ampAttack, ampRelease, gate); osc = os.sawtooth(freq); process = ampEnv * osc * amp : filter : sp.panner(pan);
仕上げ
オシレータを派手にします。2つに増やしてデチューンしたものを倍音の間隔でさらに重ねます。音の高さを pink_noise
と noise
で揺らしています。
... osc(f) = os.sawtooth(f) + os.sawtooth(f * (1.0 + 0.1 * no.pink_noise)); chord(numHarmo) = sum(i, numHarmo, osc((i + no.noise) * freq / numHarmo)) / numHarmo; process = ampEnv * chord(7) * amp : filter : sp.panner(pan);
osc(f) = os.sinosc(f);
のような書き方は関数の宣言です。
sum(ident, numiter, expression)
は expression
を numiter
回ループして足し合わせます。現在のループ回数は ident
に格納され expression
から参照できます。 sum
以外のループは Faust Quick Reference -> 3.4.1 Diagram Expressions -> Iterations (version 0.9.100では23ページ目) に載っています。
最後にデフォルトのパラメータを調整して完成です。
import("stdfaust.lib"); amp = hslider("amp", 0.5, 0.0, 1.0, 0.01); pan = hslider("pan", 0.5, 0.0, 1.0, 0.01); freq = hslider("freq", 0.4, 0.0, 1.0, 0.01) : ba.lin2LogGain * 980 + 20; ampAttack = hslider("amp attack", 0.7, 0.0, 1.0, 0.001); ampRelease = hslider("amp release", 0.8, 0.0, 1.0, 0.001); gate = button("gate"); cutoff = hslider("cutoff", 0.5, 0.0, 1.0, 0.01) : ba.lin2LogGain * 980 + 20; resonance = hslider("resonance", 0.2, 0.01, 1.0, 0.01) : ba.lin2LogGain * 100; filterAttack = hslider("filter attack", 0.25, 0.0, 1.0, 0.001); filterRelease = hslider("filter release", 0.9, 0.0, 1.0, 0.001); filterEnvAmount = hslider("filter envelope amount", 0.7, 0.0, 1.0, 0.01) : ba.lin2LogGain * 980 + 20; filterEnv = en.ar(filterAttack, filterRelease, gate); filter = fi.resonlp( cutoff + filterEnvAmount * filterEnv, resonance, 1); ampEnv = en.ar(ampAttack, ampRelease, gate); osc(f) = os.sawtooth(f) + os.sawtooth(f * (1.0 + 0.1 * no.pink_noise)); chord(numHarmo) = sum(i, numHarmo, osc((i + no.noise) * freq / numHarmo)) / numHarmo; process = ampEnv * chord(7) * amp : filter : sp.panner(pan);
2017/07/18
SuperCollider - PtparとPsetp
Ptpar
は時間のオフセットを指定してパターンを重ねるときに使えるパターンです。 Psetp
や Paddp
を使うことで重ねるパターンの一部を変更することができます。
例として音程を平行移動します。
( // Paddpを利用。 b = Pbind(\note, Pseq(Array.interpolation(8, 0, 7)), \dur, 0.1); Ptpar([ 0.0, Paddp(\note, 0, b), 2.0, Paddp(\note, 5, b), 4.0, Paddp(\note, 9, b) ]).play; ) ( // Psetpを利用。 b = Pbind(\note, Pseq(Array.interpolation(8, 0, 7)), \dur, 0.1); Ptpar([ 0.0, Psetp(\ctranspose, 0, b), 2.0, Psetp(\ctranspose, 5, b), 4.0, Psetp(\ctranspose, 9, b) ]).play; ) ( // Pbindで\degreeを使用。 b = Pbind( \scale, Scale.chromatic, \degree, Pseq(Array.interpolation(8, 0, 7)), \dur, 0.1); Ptpar([ 0.0, Psetp(\ctranspose, 0, b), 2.0, Psetp(\ctranspose, 5, b), 4.0, Psetp(\ctranspose, 9, b) ]).play; )
Pmulp
で値をかけあわせることができます。
( b = Pbind( \scale, Scale.chromatic, \degree, Pseq(Array.interpolation(8, 0, 7)), \dur, 0.1); Ptpar([ 0.0, Pmulp(\dur, 2, b), 2.0, Pmulp(\dur, 3, b), 4.0, Pmulp(\dur, 5, b) ]).play; )
組み合わせます。
( b = Pbind( \scale, Scale.chromatic, \degree, Pseq(Array.interpolation(8, 0, 7)), \dur, 0.1); Ptpar([ 0.0, Paddp(\degree, 0, Pmulp(\dur, 2, b)), 2.0, Paddp(\degree, 5, Pmulp(\dur, 3, b)), 4.0, Paddp(\degree, 9, Pmulp(\dur, 5, b)) ]).play; )
2017/07/14
SuperColliderでファイルへの録音と再生
SuperColliderが読み込めるファイルフォーマットを確認します。libsndfileが使われているので、少なくともlibsndfileの対応表にあるファイルは読めるはずです。
まずはRecorderでテスト用のファイルを作ります。
{ SinOsc.ar( SinOsc.ar( XLine.kr(1, 100, 0.5)).exprange(*XLine.kr([20, 800], [7000, 200], 1) ) ) * 0.1 }.play; s.record(duration: 1); // sはデフォルトのServer。 thisProcess.platform.recordingsDir.postln; // 保存先のディレクトリを出力。
デフォルトでは Recorder
で録音した音の保存先は thisProcess.platform.recordingsDir
、ファイル名は"SC_%y%m%d_%H%M%S.aiff"となります。ファイル名は秒単位で更新されるので、1秒以下の録音を繰り返し行う場合は明示的にファイル名を指定する方がよさそうです。今回録音したファイル名は SC_170704_063246.aiff
となりました。
ffmpegでwavに変換します。利用できるコーデック名は ffmpeg -codecs
で確認できます。wavの場合はpcm_*となっているコーデックが使えます。エンディアンを指定する必要があるのでlscpuで確認しています。
Fedora26ではdnfにrpmfusionのリポジトリを追加することでffmpegをインストールできるようになります。
$ sudo dnf install ffmpeg $ cd path/to/recordingsDir $ lscpu | grep Endian Byte Order: Little Endian $ ffmpeg -i SC_170704_063246.aiff -c:a pcm_s16le test.wav
wavからmp3、ogg、opus、flacに変換します。aiffも名前をtest.aiffに変更します。
$ lame --preset "insane" test.wav test.mp3 $ oggenc -q 10 test.wav $ opusenc --bitrate 256 test.wav test.opus $ flac -8 test.wav $ cp SC_170704_063246.aiff test.aiff
SoundFileでファイルを読み込んで再生できるか試します。
( ~checkFile = { | path(nil) | f = SoundFile.new; f.openRead(path); f.play; f.inspect; f.close; }; ); ~dir = thisProcess.platform.recordingsDir; ~checkFile.value(~dir +/+ "test.aiff"); // OK ~checkFile.value(~dir +/+ "test.wav"); // OK ~checkFile.value(~dir +/+ "test.mp3"); // could not be opened ~checkFile.value(~dir +/+ "test.ogg"); // OK ~checkFile.value(~dir +/+ "test.opus"); // could not be opened ~checkFile.value(~dir +/+ "test.flac"); // OK Window.closeAll; // inspectウィンドウをまとめて閉じる。
今のところmp3とopusは再生できないようです。
mp3を再生
lameをインストールします。
sudo dnf install lame
QuarksのMP3をインストールします。上側にあるメニューのLanguage > QuarksからGUIでインストールすることもできます。
Quarks.install(MP3);
インストール後にCtrl+Shift+Lでクラスライブラリを再コンパイルするとMP3が使えるようになります。
MP3がlameのパスを正しく指定しているか確認します。
File.exists(MP3.lamepath);
mp3が再生できることを確認します。
b = MP3.readToBuffer(s, thisProcess.platform.recordingsDir +/+ "test.mp3"); b.play;
2017/05/25
SuperColliderのクラス
クラスを記述したファイルは拡張子を.scとして systemExtensionDir
あるいは userExtensionDir
に保存します。以下でパスを確認できます。
Platform.systemExtensionDir.postln; Platform.userExtensionDir.postln;
ここでは userExtensionDir
に ClassTest
というディレクトリを作ってその中に.scファイルを置くことにします。まずは以下の内容で classTest.sc
というファイルを作ります。
Superclass { var <>x; var <>y = 10; init { x = 5; } } MyClass : Superclass { init { super.init; "hi".postln; } }
var
の後に続く <
と >
はゲッタとセッタを意味します。 var <>x;
で x
のゲッタとセッタを定義しています。
クラス名 : スーパークラス名 {}
で継承を行います。スーパークラスのメソッドは super
で参照します。
ファイルを保存したらCtrl+Shift+Iでクラスライブラリを再コンパイルします。続いて新しいファイルを開いて以下の内容を入力します。
{ var me = MyClass.new; me.init; me.x.postln; me.y.postln; me.x = 100; me.x.postln; }.value;
実行して次の出力が得られれば成功です。
hi 5 10 100 -> 100
SuperColliderでディレイとフェイザ
ディレイ
ヘルプではUGen > Delaysとして分類されています。エフェクトとしてのディレイには CombC
などを使います。 DelayC
などはただの遅延素子なのでフィードバックはかかりません。
exception in real time: alloc failed
が出るときは s.options.memSize
で確保するメモリを増やしてサーバを再起動します。デフォルトでは8192KBが確保されます。
s.options.memSize = 1000000; // 1GBのメモリを確保。 SynthDef(\delay, { | out(0), in(0), delayTime(0.2), decaytime(16), wet(0.5) | var input = In.ar(in, 2); var dry = input * (1 - wet); var delay = CombC.ar(input, 4, delayTime, decaytime, wet, dry); OffsetOut.ar(out, delay); } ).add; TempoClock.tempo = 140 / 60; ~seq = Pbind( \degree, Pseq([0, 2.2, 4.35, 6.67, 8.81], inf), \dur, 0.5, \amp, 0.3, \out, 10 ); ~delay = Pfx(~seq, \delay, \delayTime, 0.63, \in, 10).play;
以下はステレオディレイの例です。 Pfxb
を使う場合は SynthDef
の出力チャンネル数を s.options.numOutputBusChannels
以下にしないとエラーがでるようなので注意してください。
SynthDef(\delayStereo, { | out(0), in(0), delayTimeL(0.2), delayTimeR(0.15), decaytime(16) , wet(0.5) | var input = In.ar(in, 2); var dry = input * (1 - wet); var delayL = CombC.ar(input[0], 4, delayTimeL, decaytime, wet, dry[0]); var delayR = CombC.ar(input[1], 4, delayTimeR, decaytime, wet, dry[1]); OffsetOut.ar(out, [delayL, delayR]); } ).add; ~seq1 = Pbind( \out, 10, \degree, Pseq([[0, 3], [0, 5], [2, 5], [2, 7]], inf), \amp, 0.2 ); ~seq2 = Pbind( \out, 10, \degree, Pseq([ Pseq([Rest], 4), Pseq([14, 11, 8, Rest]) ], inf), \dur, 4, \amp, 0.2 ); ~seq3 = Pbind( \out, 10, \degree, Pseq([ Pseq([Rest], 8), Pseq([-10, -9, -11, -10, -9, -11, -10, -14]) ], inf), \dur, 8, \amp, 0.4 ); ~par = Ppar([~seq1, ~seq2, ~seq3]); TempoClock.tempo = 100 / 60; ~delay = Pfxb(~par, \delayStereo, \in, 10, \delayTimeL, TempoClock.beatDur * 0.78, \delayTimeR, TempoClock.beatDur * 0.666, ).play;
フィードバック
フェイザ
ノッチができるフィルタを直列につないでカットオフ周波数をLFOで揺らすとフェイザになります。
以下の例では DelayC
でコムフィルタを作っています。 DelayC
のかわりに AllpassC
などを使っても似たような効果になります。
s.options.memSize = 1000000; SynthDef(\phaser, { | out(0), in(0), rate(0.2), minDelay(0.0001), maxDelay(0.001) , dry(1), feedback(0.5) | var input = In.ar(in, 2); var inputFeedback = DelayC.ar(LocalIn.ar(2), 0.01, 0.0001, feedback); var phaser = List[]; var lfo = LFPar.ar(rate).exprange(minDelay, maxDelay); var temp = input.do({ |channel, index| var filter = channel + inputFeedback[index]; 4.do({ filter = DelayC.ar(filter, 1, lfo, 2/7, 0-filter * 5/7) // filter = AllpassC.ar(filter, 1, lfo, 0.001, 2/7, 0-filter * 5/7) }); phaser.add(filter) }); var output = phaser + (input * dry * 5/7); LocalOut.ar(output); OffsetOut.ar(out, output) } ).add; ~seq = Pbind( \out, 10, \scale, Scale.chromatic, \degree, Pseq([ [0, 5, 9, 14], [2.01, 9.01, 16, -2.5], [3, 6.98, 8.1, 10], [4.97, 9, 10, 12], [8, 9.99, 11.5, 19], [3, 7.02, 8.01, 10], [5, 8.96, 10, 12.01], [-2, 1.41, 3, 5] ], inf), \dur, 1, ); TempoClock.tempo = 130 / 60; ~phaser = Pfx(~seq, \phaser, \in, 10, \feedback, 0.8).play;
2017/05/16
SuperColliderでエフェクト
SynthDef
で定義したエフェクトを In
と Out
でルーティングします。
Bus
SuperColliderではBusを指定することでルーティングを行います。
BusにはAudioBusとControlBusの2つがあります。AudioBusは.ar、ControlBusは.krの管理に使います。ここでは単にBusと言う場合、AudioBusを指すことにします。
Bus0からの数チャンネルはハードウェアへの出力となります。例えばステレオ環境ならBus0が左、Bus1が右チャンネルとなります。 ServerOptions
から出力チャンネル数を調べることができます。
// s = Server.local; s.options.numAudioBusChannels; // ハードウェアの出力チャンネル数
以下でAudioBusとControlBusの最大チャンネル数を調べます。デフォルトの最大チャンネル数はAudioBusが1024、ControlBusが16384となります。
s.options.numAudioBusChannels; s.options.numControlBusChannels;
In, Out
In
で入力Bus、 Out
で出力Busを指定します。以下の例ではBus10に出力した Saw
を In
で受け取り、 BMoog
フィルタを適用してBus0に出力します。
~bus = 10; { var numChannels = s.options.numOutputBusChannels; var input = In.ar(~bus, numChannels); var filter = BMoog.ar(input, 500, 0.991); Out.ar(0, filter) }.play; { var osc = Saw.ar(100, 0.2); Out.ar(~bus, Pan2.ar(osc)) }.play;
Pfxb
パターンにエフェクトをかけるときは Pfxb
が使えます。
thisThread.randSeed = 200; SynthDef(\hihat, { | out(0), amp(1), pan(0), dur(0.1), curve(-64) | var envAmp = Env.perc(0, dur, 1, curve); var envGenAmp = EnvGen.ar(envAmp, doneAction: 2); var noise = FBSineC.ar(40000, 4, 1.02, 1.05); var dckill = BHiPass4.ar(noise, 240, 4, amp * envGenAmp); OffsetOut.ar(out, dckill) } ).add; SynthDef(\delay, { | out(0), in(0), freq(0.1) | var input = In.ar(in, 1); var filter = input; var delay = 0.1; 8.do({ filter = AllpassL.ar(filter, delay, delay.rand, delay.rand, 0.5, filter) }); OffsetOut.ar(out, Pan2.ar(filter)) } ).add; TempoClock.default.tempo = 118 / 60; ~bind = Pbind( \instrument, \hihat, \out, 10, // bus 10 に出力。 \dur, 0.25, \curve, Pseq([-39, -70, -2, -33], inf), \amp, 0.1 ); Pfxb( ~bind, \delay, \in, 10, // bus 10 から入力。 \out, 0, \freq, 0.1 ).play;