06.エンベロープ(SuperCollider)
SuperColliderではおおよそどんなパラメータにもエンベロープを使えます。数値で入力できるパラメータなら。
ピッチや音量はもちろん、フィルターのカットオフやレゾナンス、パルス派のパルス幅、LFOの周期、LFOの深さ、LFOにかけるLFOの深さ、FMの深さやピッチ、LRパンニング、エフェクトバスへの送り、サンプルの再生レート、加算合成のミックスバランス、思いつくもの何にでもエンベロープをつけられます。
(同じことがLFOにも言えます。どんなパラメータにも、シンセで言うならどのノブ(スライダー)にもLFO可です。しかもLFOの周期は低周波から可聴領域までシームレス!詳しくはLFOの回にて。)
エンベロープの形も思いつくものは何でも試せると言っていいくらいです。いくつか実用的な雛形が用意されている他、自分で自由に組むこともできます。
エンベロープで時間変化を作るには、ふたつのクラス(部品)を使います。EnvGenと、Envです。
使い方のイメージを書くと、こんな感じです。
では実際にノコギリ波に音量のエンベロープをかけてみます。
ざっくりとやってみたあとに、詳細な説明を書くことにします。
実践
percメソッドを使ってエンベロープを作る。
Env.perc()
デフォルトパラメータでOKなので(カッコ)内の記述はナシ。
plotメソッドで形を確かめる。
Env.perc().plot;
testメソッドで聴いて確かめることもできる。
(左スピーカーからのみ再生)
Env.perc().test;
アタックタイムやリリースタイムを調整したい場合はパラメータを記入。
Env.perc(0.1, 1.2).test;
EnvGenクラスにあてがう。
EnvGen.kr(Env.perc(0.1, 1.2))
これもEnv.perc()以降はデフォルトパラメータでOK。
(なので何も書かない。)
さて、ノコギリ波を再生する下記コードの0.3のところが音量だったので、
{Saw.ar(220, 0.3)!2}.play;
//音を止めるにはコマンド&ピリオド
そこにさっき作ったEnvGenを入れるだけ。
{Saw.ar(220, EnvGen.kr(Env.perc(0.1, 1.2)))!2}.play;
ちょっと見づらいので変数を使って書き直し。
(
{
var env;
env = EnvGen.kr(Env.perc());
Saw.ar(220, env)!2}.play;
)
完成です。
実行すると短い音量変化のノコギリ波が鳴りました。
ただ、少し音量大きく感じたかもしれません。
その理由も含め、いろいろ説明を端折ったので、このあと詳細を書きます。
時間変化をつくるメソッド
まずEnvクラスにメソッドを送ることで時間変化の形を作ることができます。
メソッドはたくさん用意されていて、下記はその一部です。
adsr
dadsr
asr
perc
new
ADSRはシンセサイザーのパラメータでおなじみですが、今日はまず一番単純な形のpercでやってみました。
percはpercussion(又はpercussive)の略だと思います。
何か打楽器を叩いたような、アタックとリリースだけのエンベロープです。
SuperColliderのエディタにここ↓まで書くと
Env.perc(
小さなポップアップが出てきますよね。
それが入力できるパラメータです。
※ポップアップが消えてしまったら、(カッコ)内にカンマを打つとまた出てきます。escキーで消せます。
もちろんヘルププラウザでも調べられます。
percの文字の上にカーソルを持ってきてcommand+dです。
ヘルプブラウザにEnvと出てくるのでEnvをクリックします。
4つのパラメータはこんな感じです。
Env.perc(アタックタイム, リリースタイム, レベル, カーブ)
色んな数字で試してみましょう。
4つ目の”カーブ”は、この3つのエンベロープを比較してみると意味がわかると思います。
Env.perc(0.2, 1.5, 1, 0).plot;
Env.perc(0.2, 1.5, 1, -3).plot;
Env.perc(0.2, 1.5, 1, 3).plot;
ほとんどのエンベロープ用メソッドがカーブパラメータを持っています。
音量について
ところで”レベル”をデフォルトのままで使ってしまったので、さっきは少し音が大きく感じたんですね。(いつもは0.3でやっているので。)
エンベロープの最高到達点を0.3にするには、このようにすればOKです。
Env.perc(0.5, 1.5, 0.3, 0).plot;
鳴らしてみます。
(
{
var env;
env = EnvGen.kr(Env.perc(0.5, 1.5, 0.3, 0));
Saw.ar(220, env)!2}.play;
)
ただ、これって、次のコードと音量的には同じなんです。
(
{
var env;
env = EnvGen.kr(Env.perc(0.5, 1.5, 1, 0)) * 0.3;
Saw.ar(220, env)!2}.play;
)
Env.perc内では最大レベルの1を使い、エンベロープ全体に0.3を掛け算しています。
つまり、次のコードとも音量的には同じです。
変数envに入れる前に0.3を掛けるか(さっきのコード)、
入れたあとに0.3を掛けるか(次のコード)、の違いです。
(
{
var env;
env = EnvGen.kr(Env.perc(0.5, 1.5, 1, 0));
Saw.ar(220, env * 0.3)!2}.play;
)
さらにさらに、次のコードとも音量的には同じです。
(
{
var env;
env = EnvGen.kr(Env.perc(0.5, 1.5, 1, 0));
Saw.ar(220, env)!2 * 0.3}.play;
)
「エンベロープ付きサイン派に0.3を掛けた」という形です。
それと、下記のように「音量0.3のサイン派にエンベロープの音量変化を掛けた」形でも音量的には同じこと。
(
{
var env;
env = EnvGen.kr(Env.perc(0.5, 1.5, 1, 0));
Saw.ar(220, 0.3)!2 * env}.play;
)
つまり、最終的には下記でもOK。
(0.3もenvの音量変化も、最終的にアウトプットに掛け算されればよい。)
(
{
var env;
env = EnvGen.kr(Env.perc(0.5, 1.5, 1, 0));
Saw.ar(220)!2 * 0.3 *env}.play;
)
実はこの形が一番扱いやすいかなぁと僕は思います。
なぜなら、上記のコードではEnv.percにも音量パラメータがありますし、Saw.arにもmulという音量パラメータがあるわけです。なので個々のパラメータではデフォルトの1にしておいて、最終的に音量値を掛け算してあげるほうが、全体音量を1箇所で設定できます。
[フィルター]の回のときにも、2箇所で音量設定しているコードがありました。
こちらです。
{ RLPF.ar(Saw.ar(220, 0.3)!2, 1000, 0.1, 0.3 )}.play;
変数を使って見やすくします。
(
{
var sig;
sig = Saw.ar(220, 0.3)!2;
RLPF.ar(sig, 1000, 0.1, 0.3 )}.play;
)
0.3の箇所が音量ですが、2箇所ありますよね。
0.3を2回掛け算してたから、実は音量がめっちゃ小さくなってたんですよ。(この時それについては触れませんでした。。)
これを、どちらもデフォルトの1で使って(デフォルトだから何も書かないことになります)、最後の最後にRLPF全体に0.3を掛け算することで全体音量をコントロールできます。
※先ほどのコードよりも音量が少し大きくなります。↓
(
{
var sig;
sig = Saw.ar(220)!2;
RLPF.ar(sig, 1000, 0.1) * 0.3}.play;
)
余談ですが、変数を使うと意味もわかりやすいので、僕はいつも次のような感じにします。
(変数の名前は、ampでもvolでも、好きな名前にしてOKです。)
(
{
var sig, amp;
amp = 0.3;
sig = Saw.ar(220)!2;
RLPF.ar(sig, 1000, 0.1) * amp}.play;
)
また、この形にしておけば、例えば変数sigにふたつの波形をミックスしたものを入れようとしたときなど、Saw.ar個々の音量はミックスバランスの調整にも使えます。
例えば、下記の0.5の部分はミックスバランスのための音量調整してます。ampが全体音量です。
(
{
var sig, amp;
amp = 0.3;
sig = Saw.ar(220) + Saw.ar(331, 0.5);
RLPF.ar(sig!2, 1000, 0.1) * amp}.play;
)
フィルターにエンベロープ
せっかくフィルターのコードが出てきたので、カットオフ周波数にエンベロープをかけてみます。
レベルのパラメータを1のままにしておいて、1000を掛け算してやれば、0Hz〜1000Hz〜0Hzという山(エンベロープ)が出来上がります。
(
{
var sig, amp, env;
amp = 0.3;
env = EnvGen.kr(Env.perc(0.5, 2));
sig = Saw.ar(220)!2;
RLPF.ar(sig, 1000*env, 0.1) * amp}.play;
)
または次のコードのようにしても同じ効果です。
Env.percのレベルのパラメータを1000にしておいてあげます。
(
{
var sig, amp, env;
amp = 0.3;
env = EnvGen.kr(Env.perc(0.5, 2, 1000));
sig = Saw.ar(220)!2;
RLPF.ar(sig, env, 0.1) * amp}.play;
)
このコードを実行するとき、ちょっと注意したいことがあります。
エンベロープが終わったときに音も終わったように聞こえますが、実は0Hzでローパスフィルターされた音が実は出続けているんですよね。
下記を実行することでわかります。
s.meter;
これでSuperColliderのレベルメーターウィンドウが出てきます。
(s(サーバ)に対して「メーター出して!」と指示してます。command+mでもOK。)
上記のコードを実行したあと、音が消えてしまってもメーターには緑色の信号が出っぱなしですよね。フィルターが閉じきったあとも(実際には音は聞こえなくても)音圧みたいなものは存在するんですね。
なのでそれを消すために、コマンド&ピリオドで音を止めてあげましょう。止めないまま何度も上記コードを実行し続けていると音圧だけが上がりっぱなしになるようです。
音が出っぱなしかどうか、もうひとつ目で見て確認できる方法があります。
s.plotTree;
これを実行すると「Node Tree」というウィンドウが表示されます。
真ん中のグレーのボックス(Group 1 - default group)に四角いボックスが表示されていたら、SuperColliderは何か信号を処理中(音を出力中)の印です。
どのコードでもいいので何か音を出してみるとわかります。
コマンド&ピリオドをすると、四角いボックスは消えるはずです。
このウィンドウやレベルメーターを出しっ放しにしておいてSuperColliderが音声処理をしているかどうか確認することは多々あります。
話を戻します。さきほどは0Hz〜1000Hz〜0Hzという時間変化を作りましたが、フィルターを0Hzにまで閉じ切らないようにしたい場合もあります。例えば0Hz〜1000Hz〜400Hzのように。
その場合、こうするのはどうでしょうか。
(
{
var sig, amp, env;
amp = 0.3;
env = EnvGen.kr(Env.perc(0.5, 2, 600));
sig = Saw.ar(220)!2;
RLPF.ar(sig, 400 + env, 0.1) * amp}.play;
)
こうすると「最高到達点600のエンベロープ」が出来上がります。
もともと"カットオフ周波数400"にそのエンベロープを足し算しているので、カットオフ周波数は400から始まって0.5秒で1000に到達、その後2秒かけて400に戻ってきます。
ピッチにエンベロープをかけるときも、こんなふうに「元のピッチに対してどれだけ増減させるか」を考えるとうまくいきそうです。
SuperColliderでは方法に正解不正解はありません。
自分なりの方法で結果が伴えばそれでOKですので、他のやり方もあると思います。
さて、ADSRのエンベロープについては次回書くことにします。
今日取り上げたEnv.percは、ゼロから始まって最高到達点に届いたあともう一度ゼロに戻ってエンベロープを終了する、単純なものでした。なので今日まず紹介しました。
ADSRはこれに「キーオン/キーオフ」情報が入ってきます。「キーオンの間はサスティンし続ける、キーオフしたらリリースする」というものです。エンベロープ付きの音源を作ってそれをシーケンスする(音符オン/オフする)ことをやりながら、EnvGenクラスも含めてそのあたりまた詳しく書いていきたいです。
arとkr
ところで、今日出てきたEnvGenではkrメソッドを使いました。
[03.フィルター]の回で出てきたMouseX, MouseYもkrメソッドを使っていました。
SawやSinOscはいつもarですが、krとarにはどんな違いがあるのか・・・。
一言で言えば「きめ細かさ」の違いです。
arはオーディオレイトですので、1秒につき44100個のサンプルを刻んで音声信号を作り上げます。(使っているオーディオインターフェイスの設定によって44100ではなく48000の場合もあります。SuperColliderのデフォルトは44100です。)
それに対してkrは、”arを64分の1に間引いたサンプル数”で音声信号を処理します。
少しだけ”荒い”ということになります。
ただそのぶん、コンピュータの処理負荷はkrのほうが軽くなります。
音を鳴らす目的ならやはりきめ細かいほうがよいですが、エンベロープのように音を制御・変調する目的ならそんなにきめ細かくなくてよい、というわけです。
変調といえば、[03.フィルター]でLineクラスを取り上げました。
あのときはarしか扱ったことがなかったのでLine.arを使いましたが、特にarである必要はなく実はLine.krでじゅうぶんでした。
{ RLPF.ar(Saw.ar(220, 0.3)!2, Line.kr(20, 2000, 3.0), 0.1, 0.3 )}.play;
{ RLPF.ar(Saw.ar(220, 0.3)!2, Line.ar(20, 2000, 3.0), 0.1, 0.3 )}.play;
ふたつの出音を聴き比べても、音質の差は感じないと思います。
それならばコンピュータの負荷は軽い方がいいのでこういう場合はkrを推奨します。
(まだコードが1行なので負荷といってもほとんどないと思いますけども。。)
ただ処理が軽いからと言っても、音を出す大元の信号ではkrは使えません。例えば下記のようにkrでは音は出ません。
{Saw.kr(220, 0.3)!2}.play;
パラメータの順番
もうひとつ今日のうちに書いておきたいことがあります。
これまで「パラメータを書く順番を守るべし」という感じでやってきましたが、そうではない書き方もあります。
例えばEnv.percは下記のようなパラメータを持っていますが
Env.perc(attackTime:0.01, releaseTime:1.0, level:1.0, curve:-4.0)
これらパラメータ名と一緒にパラメータ値を書けば順番通り書かなくてもOKなのです。下記のような感じです。
Env.perc(curve:-4.0, level:1.0, attackTime:0.01, releaseTime:1.0).plot;
パラメータ名:(コロン)数値、という書き方です。
なのでcurveだけパラメータを書き換えたい場合は下記の書き方でも問題ありません。(書かないパラメータはデフォルト値として扱われるので。)
Env.perc(curve:-2.0).plot;
最初だけ順番通りに書いて(そこまでは数値だけでよくて)、順番を変えたい箇所だけパラメータ名とともに書く方法もOKです。下記のような感じです。今後この書き方がでてきます。
Env.perc(0.01, 1.5, curve:-2.0, level:1.0).plot;
今日はここで終わりにします。
次回からエンベロープやゲートの話を織り交ぜながら、音源(SynthDef)を作ってをれをシーケンスさせる(Pattern)ということを数回に渡ってやりたいと考えています。
今日のまとめ
・エンベロープはEnvGen.kr()に入れて、音源に掛け算することで使える。
・使っているクラス全体に掛け算することで全体音量をコントロールすることができる。
・s.meter;(又はcommand+m)でレベルメーターウィンドウ表示
・s.plotTree;で音声信号を処理中かどうかを確認できる
・krはarの64分の1のきめ細かさ
・メソッド内でパラメータを書く順番は、パラメータ名付きで書くことで自由になる
<目次>にも今回のリンクを作っておきました。https://note.com/sc3/n/nb08177c4c011