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;

2017/05/10

SuperColliderでSync

私が調べた範囲では汎用的にオシレータシンクを行う方法が見当たりませんでした。ここでは場当たり的な実装をいくつか紹介します。

正確にHard syncできるのはphaseを入力できるgeneratorを使う方法です。次点でPlaybufですが位相リセット時にクリックノイズが乗ります。EnvGenは波系が歪んでいます。LoopBufはBufferのループ回数にばらつきが出ます。

Deterministic generatorでsync

phase を入力できるgeneratorは周波数を0にして LFSaw などで位相を回すことでsyncできます。

次の例の slave は上のどれかを使う必要があります。

{
  var freq = MouseY.kr(30, 3000, 1);
  var ratio = MouseX.kr(1, 100, 1);
  var master = LFPulse.ar(freq);
  var syncPhase = LFSaw.ar(freq, 0, pi * ratio);
  var slave = SinOsc.ar(0, syncPhase % 2pi, 1);
  var osc = master.range(0, 1) * slave;
  OffsetOut.ar(0, Pan2.ar(osc, 0, 0.5))
}.play;

EnvGenでsync

EnvGen もゲートを使ってsyncできます。ゲートについてはヘルプに解説があります。IDEでは EnvGen にカーソルを合わせてCtrl+Dでヘルプを開けます。

{
  var freq = MouseY.kr(30, 3000, 1);
  var ratio = MouseX.kr(1, 100, 1);
  var gate = LFPulse.ar(freq, 0, 0.5, 2, -1);

  var levels = [0] ++ [1, 0.9, 0, -0.9, -1] ++ [0];
  var waveSize = levels.size - 3;
  var times = [0] ++ { 1 / (ratio * freq) / waveSize }.dup(waveSize) ++ [0];
  var env = Env(levels, times, \lin, levels.size - 2, 0);
  var envGen = EnvGen.ar(env, gate);

  OffsetOut.ar(0, Pan2.ar(envGen, 0, 0.5))
}.play;

levelstimes の前後に [0] を加えている理由を説明します。まずは Env のループで使われる引数の releaseNodeloopNode の挙動を確認します。それぞれ4番目と5番目の引数です。以下の例を順に再生してみてください。

Env([1, 0, -0.1, 0], [1, 1, 1], \lin, 2, 0).test(5).plot; // (1)
Env([1, 0, -0.1, 0], [1, 1, 1], \lin, 2, 1).test(5).plot; // (2)
Env([1, 0, -0.1, 0], [1, 1, 1], \lin, 3, 0).test(5).plot; // (3)

(1)と(2)を再生すると、ループ開始点の値は levels[loopNode + 1] 、ループ終了点の値は levels[releaseNode] であることがわかります。

(3)を再生するとループしません。 releaseNode の値に levels.size - 1 を指定するとリリース後のエンベロープが無くなってしまうのでループしないようです。

つまり [0] を前に加えるのはループ開始点を正しく設定するため、後に加えるのは Env をループさせるため、ということです。

この方法の元ネタは DemandEnvGen のヘルプにあった例です。あわせて参照してみてください。

Bufferでsync

Buffer を用意して PlayBuf をトリガすることでsyncできます。

~buffer = Buffer.alloc(s, 512).sine1(1.0, true, false, true);
{
  var freq = MouseY.kr(30, 3000, 1);
  var rate = ~buffer.numFrames / SampleRate.ir * freq * MouseX.kr(1, 32, 1);
  var trigger = Impulse.ar(freq) - 0.5;
  var loopBuf = PlayBuf.ar(
    ~buffer.numChannels,
    ~buffer.bufnum,
    rate,
    trigger,
    0,
    1
  );
  var master = LFPulse.ar(freq).range(0, 1);
  OffsetOut.ar(0, Pan2.ar(master * loopBuf, 0, 0.5))
}.play;

// ~buffer.free;

LoopBuf でもsyncできます。 LoopBuf の利点はループ開始点を指定できる点です。

~buffer = Buffer.alloc(s, 512).sine1(1.0, true, false, true);
{
  var freq = MouseY.kr(30, 3000, 1);
  var rate = ~buffer.numFrames / SampleRate.ir * freq * MouseX.kr(1, 32, 1);
  var gate = LFPulse.ar(freq);
  var loopBuf = LoopBuf.ar(
    ~buffer.numChannels,
    ~buffer.bufnum,
    rate,
    gate,
    0,
    0,
    ~buffer.numFrames - 1,
    4
  );
  OffsetOut.ar(0, Pan2.ar(loopBuf, 0, 0.5))
}.play;

// ~buffer.free;

2017/05/09

SuperCollider - オシレータの位相

トーンクラスターを作るときなどは音が演奏されるたびに位相をリセットしたくない場合があります。以下の例ではパターンから現在の時刻をシンセに与えることで位相の進みを計算しています。

位相は普通 [0, 2pi) の範囲の値を指定しますが LFSaw では [0, 2) の範囲で位相を指定します。

SystemClockInterpreter 側で動作しているようで SynthDef の中で呼び出した場合は add でサーバに送られる時点で計算されて定数になるようです。パターンで指定する場合も {} で囲って関数にしないと play が呼び出された時点で定数になってしまいます。

SynthDef(\phase,
  { | out, freq, dur, pan, amp, seconds |
    var env = Env([1, 0], [dur], 6);
    var envGen = EnvGen.ar(env, doneAction: 2);
    var osc = LFSaw.ar(freq, (2 * freq * seconds) % 2);
    OffsetOut.ar(out, Pan2.ar(osc, pan, amp * envGen))
  }
).add;

SynthDef(\reset,
  { | out, freq, dur, pan, amp, seconds |
    var env = Env([1, 0], [dur], 6);
    var envGen = EnvGen.ar(env, doneAction: 2);
    var osc = LFSaw.ar(freq, 0);
    OffsetOut.ar(out, Pan2.ar(osc, pan, amp * envGen))
  }
).add;

TempoClock.default.tempo = 70 / 60;

~scale = Scale.chromatic(Tuning.chalmers);
~root = 0;

~seqPhase = Pbind(
  \instrument, Pseq([Pseq([\phase], 8), Pseq([\reset], 8)], inf),
  \scale, ~scale,
  \root, ~root,
  \degree, Pseq([[-7, -3.05, -2.98, 0, 1.98, 2, 4, 4.01, 4.02, 5, 5.04, 7]], inf),
  \dur, 0.25,
  \amp, 0.05,
  \seconds, {SystemClock.seconds}
).play;

2017/05/06

SuperColliderのスケールとパターン

SuperColliderの音律とスケール、パターン、テンポについてです。

2017/05/04

SuperColliderのエンベロープなど

SuperColliderのエンベロープ、式の評価順、マウスとキーボードの入力、arとkrについてです。

2017/04/30

SuperColliderで音を出す

音を出して計器類でファンシーに表示しつつSynthDefします。

2017/04/29

SuperColliderの関数と変数

関数

sclangでは {} で囲んだ部分が関数になります。

{} 内の最後の式の評価値が返ってきます。関数の途中で値を返すことはできないようです。

関数を呼び出すには value を使います。

f = { |a, b| a + b };
f.value(4, 5).postln;   // 9
postln(value(f, 4, 5)); // 9 上と同じ。

g = { |a(10)| a * a * a }; // 引数のデフォルトを設定。
g.value.postln;            // 1000

h = { |a, b ... arr| // ... arr で残りの引数をArrayにしてarrに代入。
  var sum = 0;
  arr.do{ arg item; sum = sum + item }; // doは他言語のforeachに相当。
  sum * a * b
};
h.value(2, 3, 4, 5, 6).postln; // 90

() ブロックと組み合わせると以下のような書き方ができます。

f = { |a, b| a + b };
f.value(
  c = 10.rand;
  c.postln;
  c, c
);

変数

以降の例は一行ずつではなく、範囲選択して実行してください。エラーの種類が変わる場合があります。

var で変数を宣言します。 var による変数の宣言は関数の始めでしか行えません。また一番外側のスコープは関数とみなされています。

var alpha = 100;
var beta = 200;

alpha.postln; // 100
beta.postln;  // 200

// var gamma; // コメントを外すとエラー。

変数のスコープについて確認してみます。

var alpha = 100;
var beta = { |value|
  var gamma = 200;
  value + gamma.value
};

beta.value(alpha).postln; // 300

// gamma.postln; // コメントを外すとエラー。

グローバル変数

これまでの例では var による宣言なしに変数を使っている箇所がありました。それらは全てInterpreterによって用意されたグローバル変数です。

InterpreterはSuperCollider IDEの起動と共にインスタンスが作られ [a-z] をグローバル変数として保持しています。その中でも s は特殊でServerが代入されます。 s は再代入などによって変更しないことが推奨されています。

a = 100;
b = 200;

a.postln; // 100
b.postln; // 200

c = 300;  // グローバル変数なのでOK。

環境変数とEnvironment

sclangでは環境変数(Environment Variable)という変数を使うことができます。 ~ から始まる名前は currentEnvironment に所属する環境変数となります。

currentEnvironment.postln; // Environment[  ]

~alpha = 100;
~alpha.postln; // 100

~beta = 200;
~beta.postln; // 200

currentEnvironment.postln; // Environment[ (beta -> 200), (alpha -> 100) ]

SuperCollider IDEで新しいセッションを開始した時は currentEnvironment には topEnvironment が代入されています。 topEnvironment はどこからでも参照できます。

var env = Environment.make;

(currentEnvironment === topEnvironment).postln; // true
(currentEnvironment === env).postln;            // false

~value = 100;
~value.postln; // 100

env.push;

(currentEnvironment === topEnvironment).postln; // false
(currentEnvironment === env).postln;            // true

~value.postln; // nil
~value = "hoge";
~value.postln; // hoge

topEnvironment.at(\value).postln; // 100

env.pop;

(currentEnvironment === topEnvironment).postln; // true
(currentEnvironment === env).postln;            // false

~value.postln; // 100

Environmentはスタックに格納されているようです。 currentEnviromnet は常にスタックの一番上を指しています。新しいEnvironmentを作った時は .push でスタックに挿入できます。 .pop はどのEnvironmentから呼び出されるかにかかわらず常にスタックの一番上を取り出すようです。

var env1 = Environment.make;
var env2 = Environment.make;

var checkEnv = {
  [
    (currentEnvironment === topEnvironment),
    (currentEnvironment === env1),
    (currentEnvironment === env2)
  ]
};

checkEnv.value.postln; // [ true, false, false ]
env1.push;

checkEnv.value.postln; // [ false, true, false ]
env2.push;

checkEnv.value.postln; // [ false, false, true ]
env1.pop;

checkEnv.value.postln; // [ false, true, false ]
env2.pop;

checkEnv.value.postln; // [ true, false, false ]

2017/04/27

SuperColliderのプラグインのインストール

公式によるプラグインの例の説明に基づいています。

プラグインは .../SuperCollider/Extensions に配置することでインストールされます。

プラグインをビルドする場合はSuperColliderのソースコードが必要になります。例としてf0pluginsをインストールします。

  1. SuperColliderのソースコードをダウンロードして解凍。
  2. f0pluginsのソースコードをダウンロードして解凍。
  3. /path/to/f0plugins で以下のコマンドを実行。
mkdir build; cd build
cmake -DSC_PATH=.../SuperCollider_Source -DINSTALL_DESTINATION=/usr/local/share/SuperCollider/Extensions ..
sudo make install

-DSC_PATH はソースコードへのパス -DINSTALL_DESTINATIONExtensions へのパスを指定してください。

以上でインストールができているはずです。SuperCollider IDEで確認してみます。

  1. SuperCollider IDEを起動。
  2. Ctrl+Bでサーバを起動。
  3. 下のコードをペースト。
  4. ペーストしたコードを選択してCtrl+Return。
// Simple synth definition using the Atari2600 UGen:
(
SynthDef(\atari2600, {|out= 0, gate= 1, tone0= 5,
tone1= 8, freq0= 10, freq1= 20, amp= 1, pan= 0|
  var e, z;
  e= EnvGen.kr(Env.asr(0.01, amp, 0.05), gate, doneAction:2);
  z= Atari2600.ar(tone0, tone1, freq0, freq1, 15, 15);
  Out.ar(out, Pan2.ar(z * e, pan));
}).add
)

// And a pattern to play it:
(
Pbind(
  \instrument, \atari2600,
  \dur, Pseq([0.25, 0.25, 0.25, 0.45], inf),
  \amp, 0.8,
  \tone0, Pseq([Pseq([2, 5], 32), Pseq([3, 5], 32)], inf),
  \tone1, 14,
  \freq0, Pseq([Pbrown(28, 31, 1, 32), Pbrown(23, 26, 3, 32)], inf),
  \freq1, Pseq([Pn(10, 16), Pn(11, 16)], inf)
).play
)

音が出れば成功です。

上の例は公式にあったものです。私の環境では実行できなかったので方法を調べました。

SuperColliderの()

SuperColliderの()

SuperColliderの()はブロックのようにも使えるメソッドの呼び出しです。Messageとも関連がありますが、何と呼んでいいのかよくわかりません。lispのprognが感覚としては近いです。

まずはブロックとして使ってみます。SuperCollider IDEでは貼り付けた行にカーソルを合わせてCtrl+Returnで実行できます。

(a = 10; b = a * a; c = b * b; postln(c)) // 10000

メソッド名を指定すれば関数を呼び出せます。()は最後の式の評価値を返します。

postln(a = 10; b = a * a; c = b * b; c + c) // 20000

入れ子にしてみます。以下を範囲選択してCtrl+Returnで実行できます。

(postln(
  a = 10;
  c = (
    b = a * a;
    b * b
  );
  postln(b); // 100
  c + c
)); // 20000
postln(a) // 10

宣言した変数はグローバルになっていることがわかりました。

メソッド名は後ろにも書けます。

postln(100); // 100
100.postln;  // 100

postln(neg(1)); // -1
1.neg.postln    // -1

引数が2つ以上の場合は後ろに置けません。ただし先頭の引数は前に置くことができます。

squared(difsqr(4, 8));   // 2304
difsqr(4, 8).squared;    // 2304
4.difsqr(8).squared      // 2304
// (4, 8).difsqr.squared // エラー

2017/04/24

Snake






遊んでみる

Snakeは古典的なゲームです。

ヘビの体をより長く伸ばすことがゲームの目的です。矢印キーの左右でヘビを操り、エサを食べさせることで体が伸びます。ヘビは自分の体か画面の端にぶつかると死にます。

Fedora26でSuperCollider3.8をコンパイル

SuperColliderは音の合成やアルゴリズムによる作曲に使われるソフトウェアです。
Fedora25以降はCCRMAのリポジトリが無くなったのでソースコードからコンパイルしてインストールすることにしました。

基本はソースコードに同梱されているインストール方法に従えばいいのですが、ところどころ引っかかる部分があったのでまとめました。

2017/04/03

Emacs Org mode 9.0.5でのリンクのハイライト

Org mode 9.0.5のデフォルトの設定ではDouble bracket links([[]]で囲まれたリンク)の後に続く文字が正しくハイライトされません。以下に例を挙げます。

1. [[http://example.com]]は例に使えるドメイン名です。
2. これは[[http://example.com][リンク]]です。

この問題はPlain text linksが原因のようで、"http:"に続く文字列は次の空白が見つかるまでリンクになってしまいます。M-x customizeからOrg Highlight LinksのPlane text linksを無効にすることで問題を回避できます。

以下はその他調べたことなどです。

2017/03/18

Singen0.2



デモを見る

ワンショットを量産するためのシンセサイザーです。より細かな調整が可能となったエンベロープとオーバーサンプリングが追加されました。

Randomを押すとパラメータがランダムに変わって音を探すことができます。気に入った音はSaveで保存できます。

2017/02/12

Blenderでアニメーション


Blenderでアニメーションを作りました。