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_YYMMDD_HHMMSS.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/06/18

Fedora26でfaust2jaqt

Faust Quick ReferenceのChapter 6でfaust2jaqtが出てきますが、以下のようなエラーが出てうまく動きません。

$ cat noise.dsp
process = library("music.lib").noise * hslider("level", 0, 0, 1, 0.01);

$ faust2jaqt noise.dsp
In file included from /usr/include/c++/7/ext/string_conversions.h:41:0,
                 from /usr/include/c++/7/bits/basic_string.h:6159,
                 from /usr/include/c++/7/string:52,
                 from /usr/include/c++/7/bits/locale_classes.h:40,
                 from /usr/include/c++/7/bits/ios_base.h:41,
                 from /usr/include/c++/7/ios:42,
                 from /usr/include/c++/7/ostream:38,
                 from /usr/include/c++/7/iostream:39,
                 from noise.cpp:46:
/usr/include/c++/7/cstdlib:75:15: fatal error: stdlib.h: No such file or directory
 #include_next <stdlib.h>
               ^~~~~~~~~~
compilation terminated.
make: *** [Makefile:656: noise.o] Error 1

これはQt5.7.1のqmakeがgccのオプションに-isystem /usr/includeを付け加えるのが原因のようです。調べたところvoidlinuxのissue#5254に暫定的な解決策がありました。

Qt5のgcc-base.confを開きます。

sudo nano /usr/lib64/qt5/mkspecs/common/gcc-base.conf

QMAKE_CFLAGS_ISYSTEMの値を-Iに変更します。

#QMAKE_CFLAGS_ISYSTEM        = -isystem
QMAKE_CFLAGS_ISYSTEM        = -I

これでfaust2jaqtが通るようになりました。

faust2jaqt noise.dsp
./noise

2017/06/12

Fedora26でLMMSをコンパイル

LMMSはどことなくFL Studioに似たDAWです。以前はFLPのインポートもできたようですが、去年の今頃に機能が削除されています

この文章を書いている時点では、Fedora26の公式リポジトリからインストールできるLMMSはバージョン1.1.3です。GUIがかっこよくなったバージョン1.2.0にアップグレードするためにLMMSをコンパイルします。

コンパイル

LMMS Wikiにコンパイル方法と必要ライブラリが書いてあります。

LMMSをgit cloneしてインストールします。cmakeの出力にあるInstallation Summaryの項でnot foundになっているライブラリのパッケージ名はrpmfindRPM Searchで検索することができます。

git clone -b stable-1.2 https://github.com/LMMS/lmms.git
cd lmms
mkdir build; cd build
cmake .. -DCMAKE_INSTALL_PREFIX=/usr -DWANT_QT5=ON
make
sudo make install

LibGig、Carla、libsoundio

LibGig、Carla、libsoundioはFedora向けのパッケージが見つからなかったのでコンパイルします。

LibGig

LibGigをgit cloneします。

git clone https://github.com/Neonking/LibGig.git
cd LibGig

autoconfでconfigureを作成してmakeしてインストールします。

libtoolize
aclocal
autoheader
automake --force-missing --add-missing
autoconf
./configure --prefix=/usr
make
sudo make install

Carla

CarlaのGUIにQt5を使う場合はPyQt5が必要です。Gtk3を使うこともできます。

# Qt5の場合。
pip3 install --user PyQt5

# Gtk3の場合。
sudo dnf install gtk3 gtk3-devel

Carlaをgit cloneしてインストールします。

git clone https://github.com/falkTX/Carla.git
cd Carla
make
sudo make install PREFIX=/usr

pkgconfigのファイルを/usr/lib64に移動します。

sudo mv /usr/lib/pkgconfig/carla-*.pc /usr/lib64/pkgconfig/

libsoundio

libsoundioをgit cloneしてインストールします

git clone https://github.com/andrewrk/libsoundio.git
mkdir build; cd build
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr
make
sudo make install

2017/06/10

Fedora26にFL Studioをインストール

Fedora26にFL Studioをインストールします。

wineをインストール

wineはWindowsアプリをLinuxやBSDなどの上でも動くようにしてくれるソフトウェアです。

dnfからwineをインストールします。

sudo dnf install wine

wineについてくるnotepadを起動して動作確認します。

wine notepad.exe

コンフィグはwinecfgで行うことができます。

winecfg

FL Studio 12.4をインストール

インストーラをダウンロードして起動します。

wine flstudio_12.4.2.exe

インストール先のパスはWindows形式のままで大丈夫でした。デフォルトでは~/.wine以下の適当なディレクトリに変換されるようです。私の環境ではC:が~/.wine/drive_cに変換されていました。

FL Studioを購入している場合はregexitを起動してレジストリを登録する必要があります。以下のコマンドでregeditを起動して、メニューから Registry -> Import Registry File… を選んでFLRegkey.Regをインポートします。

wine regedit

このままFL Studioを起動してもメニューなどの文字が表示されません。フォントが無いことが原因のようなのでwinetricksでcorefontsをインストールします。corefontsのインストールにはcabextractが要求されるので無ければdnfからインストールします。

# cabextractをインストール。
sudo dnf install cabextract

# corefontsをインストール。念のためwgetのリンク先を確認すること。
wget https://raw.githubusercontent.com/Winetricks/winetricks/master/src/winetricks
chmod +x winetricks
./winetricks corefonts

FL Studioを起動します。

wine ~/.wine/drive_c/Program\ Files\ \(x86\)/Image-Line/FL\ Studio\ 12/FL.exe

Auto Scrollingが勝手にONになる場合はScroll Lockを有効にします。xorgを使っていてScroll Lockキーが効かない場合は~/.Xmodmapに以下の行を追加して再ログインすると有効になります。

add mod3 = Scroll_Lock

2017/05/27

Fedora26でFaustをコンパイル

Faust 2.0.a72とFaustLive 2.45をインストールします。

Faustのインストール

必要なライブラリをインストールします。私の環境では以前SuperColliderをインストールした時にいろいろいれたのでllvm-develとopenssl-develを入れたらコンパイルできました。

sudo dnf install llvm-devel openssl-devel

git cloneします。ここではfaust2ブランチをインストールします。faust.gitのurlはFaustのGitHubリポジトリから取得して置き換えてください。

git clone -b faust2 faust.git
cd faust

masterブランチの場合はmake installまで飛ばしてください。faust2ブランチをmakeするとエラーがでるので以下のファイルを修正します。コードは修正後のものです。

  • dsp_factory.hh
  • dsp_aux.hh
  • dsp_aux.cpp
  • remote_dsp_aux.cpp

dsp_factory.hhにdsp.hをインクルードして、40行目あたりのforward declarationを消します。

#include "../architecture/faust/dsp/dsp.h"

...

// struct dsp_memory_manager;
// class dsp_factory;
// class dsp;

dsp_aux.hhからfaust/dsp/dsp.hのインクルードを消してdsp_factory.hhをインクルードします。

#include "export.hh"
#include "exception.hh"
#include "dsp_factory.hh"

dsp_aux.cppからdsp_factory.hhのインクルードを消します。

// #include "dsp_factory.hh"

remote_dsp_aux.cppの634行目、jack_net_master_openの3つめの引数に適当なconst char*を与えます。jack 1.9.10で確認しました。

if ((fNetJack = jack_net_master_open(DEFAULT_MULTICAST_IP, atoi(port), "some_name", &request, &result))) {

makeします。

make all httpd

デフォルトでは/usr/local以下にインストールされます。そのままではパスが通らなかったのですが/usr以下にインストールするとうまくいきました。

sudo make install PREFIX=/usr

libHTTPDFaustがインストールされたのでremoteをmakeしてインストールします。

make remote
sudo make install PREFIX=/usr

もしインストール先を間違えた場合は以下でアンインストールできます。

sudo make uninstall

FaustLiveのインストール

必要なライブラリをインストールします。

sudo dnf install libcurl libcurl-devel liblo liblo-devel qrencode-libs qrencode-devel

ソースコードをダウンロードして解凍します。今回はSourceForgeのFaustのページからFaustLive-sources-2.45.tgzをダウンロードしました。

tar -xf FaustLive-sources-2.45.tgz
cd FaustLive-2.45

makeでこけるのでqmakeのパスを通します。まずは場所を調べます。

find /usr -name 'qmake'

私の環境では/usr/lib64/qt5/bin/qmakeにインストールされていました。Build/Linux/Makefileを開いてqmの値を修正します。

### Defining some variables
qm := /usr/lib64/qt5/bin/qmake
##qm := $(shell which qmake)
##qm := $(if $(qm4),$(qm4),qmake)

コンパイルが通るように以下のソースコードを修正します。

  • JA_audioManager.cpp
  • AudioManager.h
  • JA_audioFader.cpp
  • FLWindow.cpp

JA_audioManager.cppの関数名を修正します。

fCurrentAudio->defaultConnections();

...

return fCurrentAudio->getBufferSize();

...

return fCurrentAudio->getSampleRate();

AudioManager.hを開いてAudioManagerクラスにaudioではなくjackaudioを継承させます。

class AudioManager : public QObject, public jackaudio {

JA_audioFader.cppで使われている2個所のsave_connections()をsaveConnections()に変更します。

MainStructure/FLWindow.cppの関数名を修正します。

void FLWindow::update_AudioParams()
{
    fSettings->setValue("SampleRate", fAudioManager->getSampleRate());
    fSettings->setValue("BufferSize", fAudioManager->getBufferSize());
}

makeしてインストールします。

make NETJACK=1 REMOTE=1
sudo make install

エディタ

faust/syntax-highlightingにシンタックスハイライトに対応しているエディタと設定方法の一覧があります。

AtomのプラグインはFaustと同じGRAMEのアカウントで配布されています。Atom内蔵のプラグインインストーラから検索してインストールできます。

Vimには紹介されているものよりも高機能なvim-faustがあります。

音を出す

FaustLive Tutorialが短くまとまっています。qjackctlが必要となります。

sudo dnf install qjackctl

qjackctlはConnectボタンでルーティング画面が開き、Startボタンでクライアントが表示されるようになります。

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の音律とスケール、パターン、テンポについてです。