2017/11/26

PADcymbal


デモを見る (github.io)

PADcymbalはPADsynthを利用したシンバルのような音を合成するシンセサイザです。

仕組みとしてはランダムに作った周波数と音量の組をPADsynthに入力しているだけです。その他、製作の過程で調べたことをライドシンバル合成の試みにまとめています。

2017/11/23

PADsynthでシンバルの合成

ZynAddSubFXのDrums -> Natural Drum KitのハイハットがSUBsynthで合成されているのを見てシンバルを加算合成できる気がしたので、いろいろ試したらPADsynthで以下のリンクのような音が簡単に作れることがわかりました。

PADsynthによるシンバル (Freesound)

始めは周波数成分を手作業でPADsynthに入力するつもりでしたが、無理そうだったのでKernel Density Estimationという手法を応用してある程度ランダムに合成することにしました。

作ったプログラムにFreesoundにあったシンバルのPackをいくつか投げてみたところ思ったよりもいい結果が出たのですが、シンバル以外のPackも試したところ何を入れても似たような音が出てくることが分かりました。

何を入れても同じならランダムに作ったデータでもいいんじゃないかと試したところ冒頭のような音が合成できました。ただし音量エンベロープはAudacityで後付けしています。

以下のPython3のプログラムで音量エンベロープをかけていない音が合成できます。

padsynth.py (GitHub)

合成についての細かい話を以下にまとめています。

ライドシンバル合成の試み (GitHub)

2017/09/10

Pulseverb


デモを見る (github.io)

PulseverbはBLITを利用したリバーブのインパルス応答をレンダリングします。

手軽なわりにそれなりの音がします。

2017/09/08

Freeverb


デモを見る (github.io)

Freeverbのインパルス応答を書き出すレンダラを作りました。

Freeverbはリバーブの実装の一つで金属的な音が出ます(1, 2)。デモページのAllpassの値を大きくすると金属的な部分を強調して遊ぶことができます。パラメータによってはレンダリングに時間がかかるので注意してください。

2017/08/20

Ardourクイックスタート

Ardourクイックスタート

2017/07/27

SuperColliderでZynAddSubFXのPADsynth

SuperColliderでZynAddSubFXPADsynth 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.fftSignal.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.libsawtooth を使います。

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.libar を使います。

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.libresonlp を使います。フィルタ用のエンベロープも用意します。

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_noisenoise で揺らしています。

...

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)expressionnumiter 回ループして足し合わせます。現在のループ回数は 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 は時間のオフセットを指定してパターンを重ねるときに使えるパターンです。 PsetpPaddp を使うことで重ねるパターンの一部を変更することができます。

例として音程を平行移動します。

( // 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;

ここでは userExtensionDirClassTest というディレクトリを作ってその中に.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;

フィードバック

フィードバックを作るときは LocalInLocalOut が使えます。

{
  var input = LocalIn.ar(2, 1) * 10;
  var osc = SinOsc.ar(input, input * 1000, MouseX.kr(0.001, 0.5, 1));
  LocalOut.ar(osc);
  OffsetOut.ar(0, [osc, osc])
}.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 で定義したエフェクトを InOut でルーティングします。

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に出力した SawIn で受け取り、 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;