yoppa.org


Blog

SuperCollider自主練 – 基礎編その5

1.11 配列、反復、論理構造 (Arrays, Iteration, and Logical Expression)

音楽の演奏は、しばしば集合として表現される。12音技法、倍音構造、モード(ピッチの集合)、旋律、ピッチクラスなど。配列はこうした集合を管理することができる。SCのステレオ出力もこれと同様に配列のひとつ。

配列から要素をとりだすには、.atメッセージを使用する。at()の引数に入るインデックス番号は0から開始しているのに注意。

a = ["C", "C#", "D",  "Eb", "E", "F", "F#", "G", "Ab", "A", "Bb", "B "];
a.at(8);
"Item at index 5 is: ".post; a.at(5).postln; // why didn't it print E?
"Item at index 0 is: ".post; a.at(0).postln; // because we start with 0
do(50, { [0, 2, 4, 5, 7, 9, 11].at(7.rand).postln})
do(50, { ["C", "D", "E", "F", "G", "A", "B"].at(7.rand).postln})

ここで新たにdoというメッセージが登場している。これは反復(iteration)の一種。反復はこの他にも loop, while, for, forby などがある。doの第1引数は反復する数、第2引数は反復する関数。

doをTaskの中に入れることで、一定間隔の時間を置いて反復するようになる。

Task({
    50.do({
        ["C", "D", "E", "F", "G", "A", "B"].at(7.rand).postln;
        1.wait;
    });
}).play

Arrayのデータを別のタイプにマッピングすることも可能。例えば、音程名をMIDIナンバーと対応させる。こうすることでよりコンピュータが理解しやすい形式に変換できる。

Task({
a = ["C", "C#", "D",  "Eb", "E", "F", "F#", "G", "Ab", "A", "Bb", "B"];
"count, midi, pitch, octave".postln;
    do(50, {arg count;
        p = rrand(36, 72);
        [count, p, a.wrapAt(p), (p/12).round(1) - 1].postln;
    1.wait;
    })
}).play 

Taskを実際に音に応用した例。まず始めに楽器PMCrotaleを定義。

(
SynthDef("PMCrotale", {
arg midi = 60, tone = 3, art = 1, amp = 0.8, pan = 0; 
var env, out, mod, freq;

freq = midi.midicps;
env = Env.perc(0, art);
mod = 5 + (1/IRand(2, 6));

out = PMOsc.ar(freq, mod*freq, 
    pmindex: EnvGen.kr(env, timeScale: art, levelScale: tone), 
    mul: EnvGen.kr(env, timeScale: art, levelScale: 0.3));

out = Pan2.ar(out, pan);

out = out * EnvGen.kr(env, timeScale: 1.3*art, 
    levelScale: Rand(0.1, 0.5), doneAction:2);  
Out.ar(0, out); //Out.ar(bus, out);

}).add;
)

これをTaskを利用してくりかえし演奏する。

(
a = ["C", "C#", "D",  "Eb", "E", "F", "F#", "G", "Ab", "A", "Bb", "B"];
"event, midi, pitch, octave".postln;
r = Task({
    inf.do({ arg count;
        var midi, oct, density;
        density = 1.0; // 100% of the time. Uncomment below for 70%, etc.
        // density = 0.7; 
        // density = 0.3;
        midi = [0, 2, 4, 7, 9].choose;
        // midi = [0, 2, 4, 5, 7, 9, 11].choose;
        // midi = [0, 2, 3, 5, 6, 8, 9, 11] .choose; 
        // midi = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] .choose;
        oct = [48, 60, 72].choose;
        if(density.coin, 
            { // true action
                "".postln;
                [midi + oct, a.wrapAt(midi), 
                (oct/12).round(1)].post;
                Synth("PMCrotale", 
                    ["midi", midi + oct, "tone", rrand(1, 7), 
                    "art", rrand(0.3, 2.0), "amp", rrand(0.3, 0.6), "pan", 1.0.rand2]);
            }, {["rest"].post}); // false action
        0.2.wait; 
    }); 
}).start
)

r.stop; // 停止

この例では、if文による条件判定を使用している。SCでのifはやや特殊な書き方となる。

if(条件式, {trueのときの処理}, {falseのときの処理})

if文による条件式やその使用法などのサンプルあれこれ。

//シンプルな例
if(10 == 10, {"10 is indeed equal to 10"}, {"false"})

//AND条件と、型の判定
if((1 < 20).and(1.isInteger), {"1 is less than 20"}, {"false"})

//1から10までの数を数えて、奇数かどうかを判定
10.do({arg count; [count, if(count.odd, {"odd"}, {"even"})].postln})

次の例は、より音楽的で面白い。MIDI番号をカウントアップしていって、そのノートがCの三和音に含まれるかを判別している。12の剰余計算をすることでオクターブを単位でループしている。

(
84.do({arg count; if([0, 4, 7].includes(count%12), 
{count.post; " is part of a C triad.".postln}, 
{count.post; " is not part of a C traid".postln})})
)

音の出現する確率などを操作する際に重宝できそうな、coin演算子について。

//乱数を使用して50%の確率を発生させる例
50.do({if(1.0.rand.round(0.01).post > 0.5,  {" > 0.5".postln}, {" < 0.5".postln})})

//半分の確率で音を発生させる、という構造の基本
50.do({if(1.0.rand > 0.5,  {"play a note".postln}, {"rest".postln})})

//これをcoinを使用するとこうなる
50.do({if(0.5.coin, {"play a note".postln}, {"rest".postln})})

1.12 どうやって配列を実行(Do)するのか?

doの構文と配列(Array)をどうやって結びつけるのか。カウンタを使用して配列の要素を順番にとりだすサンプル。

[0, 2, 4, 5, 7, 9, 11].do({arg each, count; ["count", count, "each", each].postln})

この構文はよりシンプルに書き換え可能。

[0, 2, 4, 5, 7, 9, 11].do({arg whatever, blech; [blech, whatever].postln})

これを応用し、MIDI番号を音階に変換して出力するサンプル。

(
var pc;
pc = ["C", "C#", "D",  "Eb", "E", "F", "F#", "G", "Ab", "A", "Bb", "B"];
[0, 2, 4, 5, 7, 9, 11].do({arg each; pc.wrapAt(each).postln;})
)

これを実行すると、C, D, E, F, G, A, B と順番に音階が出力される。

doをネストして使用することもできる。doのネストを活用して、12音のマトリクスを生成するサンプル。

(
var row, inversion, pitchClass;

//0から11迄の数値をシャッフルして配列に
row = Array.series(11, 1).scramble.insert(0, 0); 

// 上記の表記の代わりに、例えばウェーベルン作品27のような音列を指定することも可能
// row = [0, 11, 8, 2, 1, 7, 9, 10, 4, 3, 5, 6]; 

row.postln;
inversion = 12 - row;

//ピッチクラス配列に音階の名前を指定
pitchClass = ["C  ", "C# ", "D  ", "Eb ", "E  ", "F  ", "F# ", "G  ", "Ab ", "A  ", "Bb ", "B  "];

//展開        
inversion.do({arg eachInv;
    var trans;
    trans = (row + eachInv); 
    // 音階名を表示
    trans.do({arg scaleDegree; pitchClass.wrapAt(scaleDegree).post});
    //改行を出力
    "".postln;
    });
//改行を出力
"".postln
)

このコードを実行した結果の一例:

[ 0, 3, 5, 7, 4, 6, 10, 8, 9, 1, 2, 11 ]
C  Eb F  G  E  F# Bb Ab A  C# D  B  
A  C  D  E  C# Eb G  F  F# Bb B  Ab 
G  Bb C  D  B  C# F  Eb E  Ab A  F# 
F  Ab Bb C  A  B  Eb C# D  F# G  E  
Ab B  C# Eb C  D  F# E  F  A  Bb G  
F# A  B  C# Bb C  E  D  Eb G  Ab F  
D  F  G  A  F# Ab C  Bb B  Eb E  C# 
E  G  A  B  Ab Bb D  C  C# F  F# Eb 
Eb F# Ab Bb G  A  C# B  C  E  F  D  
B  D  E  F# Eb F  A  G  Ab C  C# Bb 
Bb C# Eb F  D  E  Ab F# G  B  C  A  
C# E  F# Ab F  G  B  A  Bb D  Eb C      

配列を利用してシグナルをミックスすることも可能。Mixを使用する。arメッセージを指定して、1つ目の引数にミックスするシグナルを配列を入れる。例えば、LFNoise1で音量をコントロールした加算合成の例。

(
{
var fund = 220;
Mix.ar(
    [
    SinOsc.ar(220, mul: max(0, LFNoise1.kr(12))),
    SinOsc.ar(440, mul: max(0, LFNoise1.kr(12)))*1/2,
    SinOsc.ar(660, mul: max(0, LFNoise1.kr(12)))*1/3,
    SinOsc.ar(880, mul: max(0, LFNoise1.kr(12)))*1/4,
    SinOsc.ar(1110, mul: max(0, LFNoise1.kr(12)))*1/5,
    SinOsc.ar(1320, mul: max(0, LFNoise1.kr(12)))*1/6
    ]
)*0.3
}.play
)

Mixとあわせて、Arrayのメッセージfillを利用するという方法もある。これは、Arrayの中にUGen(音を生成する関数)の集合を生成できる。第1引数に生成するアイテムの数を指定し、第2引数に音を生成する関数を入れる。例えば、基音の周波数に110Hz間隔でハーモニクスを生成しミックスするという例。

{Mix.ar(
    Array.fill(12, 
        {arg count; 
        var harm;
        harm = count + 1 * 110; //ハーモニクスを生成
            SinOsc.ar(harm, 
                mul: max([0, 0], SinOsc.kr(count+1/4))
                )*1/(count+1)
        })
)*0.7}.play
)

このコードの SinOsc.kr(count+¼)) の部分を、SinOsc.kr(¼, 2pi.rand) とか LFNoise1.kr(1) とか LFNoise0.kr(rrand(1, 5)) などと変えてみると倍音の音量の変化が様々な表情を見せて面白い。

さらに、Array.fill は、Klangを用いると面白い効果を発揮する。Klangは共鳴する倍音構造を指定することで仮想の物理モデルを再現可能。ここでは、ベルのような音を鳴らしている。

(
{
    var scale, specs, freqs, amps, rings, 
    numRes = 5, bells = 20, pan; 
    scale = [60, 62, 64, 67, 69].midicps;
    Mix.fill(bells, {
        //周波数
        freqs = Array.fill(numRes, {rrand(1, 15)*(scale.choose)});
        //音量
        amps = Array.fill(numRes, {rrand(0.3, 0.9)});
        //減衰
        rings = Array.fill(numRes, {rrand(1.0, 4.0)});
        //周波数、音量、減衰からKlangのspecs引数を生成
        specs = [freqs, amps, rings].round(0.01);
        //定位
        pan = (LFNoise1.kr(rrand(3, 6))*2).softclip;
        //Klangを生成
        Pan2.ar( 
            Klank.ar(`specs, Dust.ar(1/6, 0.03)), 
            pan)
    })
}.play;
)