11.SuperColliderでオーディオファイル(wav/aiff)を鳴らす
サイン波を鳴らすのにSinOscを使ったように、オーディオファイルを鳴らすためのクラスがあります。
Bufferと、PlayBufです。
wavファイルの情報をBufferを使ってSC3に読み込み、その読み込んだデータをPlayBufで鳴らす、という使い方です。
また、これまで変数(空箱)を作るときにvarを用いていましたが、
~(チルダ)で変数を作る方法についても書きます。
Buffer
今回使うwavファイルはこちらです。
BPM120の1小節分のリズムループ用音源にしました。ジャスト2秒の長さです。(48kHz/24bit/stereo)
よかったらダウンロードして使ってください。
ではまずwavファイルをSC3内のバッファ(一時データ置き場)に読み込みます。
Buffer.read(s, “オーディオファイルのパス”);
このように書くことで読み込むことができます。
sはサーバを表すsです。
※今まで
s.plotTree;
s.scope;
などを紹介しましたが、このsと同じ意味です。
"オーディオファイルのパス"については、僕は今回、macのデスクトップ上のsamplesというフォルダ内にあるrythm01.wavというファイルを使いたいので、下記のように書きます。※パスの部分はそれぞれご自身のファイル置き場に書き換えてください。
Buffer.read(s, "/Users/kuri/Desktop/samples/rythm01.wav");
ただ、このパスは手入力しなくてもwavファイルをマウスで掴んでSuperColliderのエディタ上にドラッグ&ドロップすることで自動的に入力されます。
※エディタ上のどこにドロップしてもOKです。
さて次に、この読み込んだデータを変数(空箱)に入れます。
ここではまずアルファベット1文字の変数を使った例です。
a = Buffer.read(s, "/Users/kuri/Desktop/samples/rythm01.wav");
このコードを実行すると・・・これで変数aの中にrythm01.wavが入りました。
a.play;
playメソッドで、aに入ったデータを再生してくれます。(aの中にwavが入ってることを確認できます!)
PlayBuf
では次にPlayBufを使ってaを再生します。
{PlayBuf.ar(2, a, 1, 1, 0, 0, 2)}.play;
これでとりあえずは鳴るはずです。順に各パラメータを見ていきますが、まだループ再生していません。とりあえずループの方法だけ書いておくと。。
{PlayBuf.ar(2, a, 1, 1, 0, 1, 0)}.play;
このようにすることでループ再生します。
音を止めるには、コマンド&ピリオドです。
※元のサンプルとピッチが違う、という方も居るかもしれません。このあと対処法を書きます。
PlayBufをcommand+dしてヘルプ画面を見ながらPlayBuf.arのパラメータを順に確認します。
numChannels
最初のパラメータは、PlayBufが再生するチャンネル数です。変数aに入れたデータがモノラル波形なら1、ステレオ波形なら2、としておきます。
{PlayBuf.ar(2,
rythm01.wavは、それほど左右の広がりは無いですがステレオ波形です。 読み込んだファイルのチャンネル数がわからない場合は、
a.numChannels;
を実行することで、aに入っているデータのチャンネル数をPost windowに出力してくれます。
bufnum
次のパラメータはbufnumで、bufnumの意味は「サーバが起動してから何個目のバッファデータか」という数字です。0からカウントします。
a.bufnum;
を実行すると、Post windowに出力してくれます。
たいていの方は出力される数字が0か、又はBuffer.read(s, ****)のコードを何度か実行した方は、ここが1以上の数字になっていると思います。また、サーバを再起動するとカウントが0に戻ります。
なので、ふたつ目のパラメータには
{PlayBuf.ar(2, 0,
のように、bufnumの数字を入れてもよいのですが、たくさんのサンプルを扱う場合いちいちbufnumを確かめてその数字を入力するのは大変、ってことで
{PlayBuf.ar(2, a.bufnum,
と書くことで機能します。これでbufnumがいくつであってもその都度Post windowで確かめる必要はありません。
さらに、.bufnumは省略しても機能するようになっているので
{PlayBuf.ar(2, a,
このように変数名を記述するだけでもOKです。
rate
3つ目のパラメータは再生レートです。
1が通常再生、2が倍速(ピッチも上がる)、0.5が半分の速さ(ピッチも下がる)、で再生します。もちろんそれ以外の数字でもOKです。
{PlayBuf.ar(2, a, 1,
さらに、マイナスの数字だと逆方向に再生します。
例えば-0.5にすると
{PlayBuf.ar(2, a, -0.5,
半分の速さ且つ半分のピッチで逆再生します。
trigger
4つ目はtrigger(トリガー)でです。
triggerは、エンベロープ(EnvGen.kr)におけるgateの役割に似ていて、「正の値」を受けるとサンプルを再生しますので、SynthDef内でargにtrigger登録しておけばPbindからサンプルの再生/停止ができます。
※あとでやってみます。
デフォルトでは1としておきます。
{PlayBuf.ar(2, a, 1, 1,
startPos
5つ目はスタートポジションです。
トリガーを受けたときに(つまりサンプルを鳴らすときに)”サンプルのどこから鳴らすか”を設定できます。
先ほどのコードでは0にしていたので、サンプルの頭から再生されました。
途中から再生させるには、再生したいサンプル数で指定します。
aに読み込んだサンルプの総サンプル数を知るには、下記のコードを実行します(Post windowに結果が表示されます)。
a.numFrames;
rythm01.wavは全尺96000サンプルです。
なのでstartPosを48000に設定すると、rythm01.wavの後半が再生されます。
{PlayBuf.ar(2, a, 1, 1, 48000, 0, 2)}.play;
いちいち計算するのは面倒なので、下記のような書き方でもOKです。
{PlayBuf.ar(2, a, 1, 1, a.numFrames/2, 0, 2)}.play;
※この書き方ができるのはfunction 内の場合に限ります。(PbindでstartPosを操作する場合は数字で行います。)
loop
次のパラメータはループのON/OFFです。
1ならループ再生する
0ならループ再生しない
{PlayBuf.ar(2, a, 1, 1, 0, 1,
doneAction
最後のパラメータはdoneActionです。
EnvGen.krで扱ったdoneActionと同じく、サンプルを再生したあとにSC3が処理を解放するかどうか、です。
2で「解放する」
0で「何もしない」
です。ループ再生するときは、この値は無視されます。(ループ再生なので、ずっと処理を解放しなくていいので。また、他の方法、例えばエンベロープやPbindのノートオフなどで音を止めればそちらから解放されるので。)
{PlayBuf.ar(2, a, 1, 1, 0, 1, 0)}
ピッチの問題を解消
これで全部のパラメータを見てきました。
これらのパラメータを設定してSynthDef内でPlayBufを使えば、Pbindでシーケンスできることになります。
それをやってみる前に、ピッチの問題を解消しておきす。
元のrythm01.wavをMACやWindowsOS上で再生して聴いたときと、SC3上で再生して聴いたときとで、ピッチが違うという方。それはrythm01.wavのサンプリングレートとSC3が動作しているサンプリングレートの不一致が原因です。
双方のサンプリングレートを確認してみましょう。
SC3のサンプリングレートは、オーディオドライバの設定を引き継いでいることがほとんどだと思います。
サーバを起動又はリブートしたときに、Post windowに表示される情報の中にCS3のサンプリングレートが含まれています。
下から数行目にこのような記述があると思います。
SC_AudioDriver: sample rate = 44100.000000
この場合、サンプリングレートは44100です。
そして、rythm01.wavのサンプリングレートは下記のコードで確認できます。
a.sampleRate;
※サンプリングレートに詳しくない方のためにざっくり説明すると、「このwavファイルを録音したとき(又はDAWから書き出したとき)に、その音をファイル化(サンプリング)するのに、どのくらいの細かさでデータ化したか」という数値です。また、再生するときも「どらくらいの細かさでデータを再生するか」という数値です。
たいていのwavファイルのサンプリングレートは48000か44100だと思います。
これは1秒あたり48000回、又は44100回のサンプリング(データ化)を行なっているということです。
このrythm01.wavは48000なので、1秒間に48000回のサンプリングを行なっています。
※また、このrythm01.wavの長さはちょうど2秒です。なので48000掛ける2で96000個のサンプルで出来ています。(なのでnumFramesの数値が96000でした。)
なので再生するときも1秒につき48000回のデータ処理を行う必要があるのですが、そうなっていません。僕の環境ではSC3は44100Hzで動いています。サンプリングレートが不一致であることがわかりました。
この場合、PlayBuf.arで再生するとOS上で再生したときよりも若干ピッチが低く再生されます。
これを解消するためにBufRateScaleクラスを使います。
使い方は・・・
BufRateScale.kr(バッファ)
これを、PlauBuf.arのrateのパラメータに掛け算します。
下記の具合です。
{PlayBuf.ar(2, a, BufRateScale.kr(a)*1, 1, 0, 1, 2)}.play;
BufRateScale.krをあてがったうえでサンプルのピッチを変えたい場合は、上記コードの*1の部分の数値を変えます。
例えば2倍速にしたい場合は「掛ける2」
{PlayBuf.ar(2, a, BufRateScale.kr(a)*2, 1, 0, 1, 2)}.play;
例えば逆再生したい場合は「掛けるマイナス1」
{PlayBuf.ar(2, a, BufRateScale.kr(a)*(-1), 1, 0, 1, 2)}.play;
BufRateScaleクラスについては、SC3が自動的に解決してくれるものなので、理屈云々は抜きにしてこの書き方をこのまま覚えてしまうだけでよいと思います。
SynthDefでPlayBufを使う
さて、これでピッチの問題が解消されたので、SynthDefでPlayBufを使ってみます。
(
SynthDef(\buftest, {
var sig;
sig = PlayBuf.ar(2, a, BufRateScale.kr(a)*1, 1, 0, 0, 2);
Out.ar(0, sig);
}).add;
)
基本的には、↑こうですが、
rateとtrigger、それとstartPosを外から(あとでPbindから)操作できるようargに登録します。それぞれの初期値も決めておきます。
(
SynthDef(\buftest, {
arg rate=1, trig=1, stPos=0;
var sig;
sig = PlayBuf.ar(2, a, BufRateScale.kr(a)*rate, trig, stPos, 0, 2);
Out.ar(0, sig);
}).add;
)
そしてPbindでシーケンスさせます。
(
Pbind(
\instrument, \buftest,
\midinote, 60,
\dur, 4
).play(TempoClock(120/60));
)
BPMを元のサンプルネタと同じ120にして、durを4拍分にしています。(4拍に一度ノートONする。)PlayBufでループせずPbindでループさせている形です。
テンポを121とか121.5とか微妙にずらしてやるとノリが生まれて面白いです。
次は、SynthDefでPlayBufをループ再生させて、EnvGenでゲートを受けるように(ノートOFFで音が止まるように)しています。
(
SynthDef(\buftest, {
arg rate=1, trig=1, stPos=0, gate=1;
var sig, env;
env = EnvGen.kr(Env.asr(0.001, 1.0, 0.001), gate, doneAction:2);
sig = PlayBuf.ar(2, a, BufRateScale.kr(a)*rate, trig, stPos, 1, 0);
Out.ar(0, sig);
}).add;
)
そしてPbindでサンプルをブツ切りにする感じでちょっと遊んでみました。
(
Pbind(
\instrument, \buftest,
\midinote, 0,
\dur, Pseq([2, 1, 1, 1, 1, 2], inf),
\rate, Pseq([1, 2, 0.5, 1, -1, 1], inf),
\stPos, Pseq([0, 48000, 72000, 0, 12000, 36000], inf),
).play(TempoClock(120/60));
)
これらふたつのPbindどちらにも言えるのですが、midinoteの数値については、実のところ何と設定しようが関係なく、同じ音程のサンプルが鳴ります。
midiratioについて
では、ピッチを変えるにはどうすればよいか、とうことですが。。
ピッチの変化がわかりやすいサンプルでやってみます。
b = Buffer.read(s, "/Users/kuri/Desktop/samples/sample_synth.wav");
(
SynthDef(\pitchtest, {
arg rate=1, trig=1, stPos=0;
var sig;
sig = PlayBuf.ar(2, b, BufRateScale.kr(b)*rate, trig, stPos, 0, 2);
Out.ar(0, sig);
}).add;
)
(
Pbind(
\instrument, \pitchtest,
\rate, 1,
\dur, 2
).play(TempoClock(120/60));
)
rateを変化させることでピッチが変わることを利用します。
なので上記のPbindにはrateというパラメータを加えました。
(
Pbind(
\instrument, \pitchtest,
\rate, Pseq([1, 2], inf),
\dur, 2
).play(TempoClock(120/60));
)
これでPbindからピッチを操作することができます。
ただ、、これだとrate1が元のピッチでrate2がそのオクターブ上ということになり、ドレミファなどの音階を鳴らすには不便です。そこで、midiratioというメソッドを使います。
(
Pbind(
\instrument, \pitchtest,
\rate, 0.midiratio,
\dur, 2
).play(TempoClock(120/60));
)
midiratioをこのように使うと
0.midiratioで元のピッチ
1.midiratioで半音上(元のピッチが"ド"なら、"ド#")
2.midiratioで全音上(元のピッチが"ド"なら、"レ")
・
・
・
12.midiratioで全音上(元のピッチのオクターブ上)
ということになり、扱いやすいです。
(
Pbind(
\instrument, \pitchtest,
\rate, Pseq([0, 2, 4, 5].midiratio, inf),
\dur, 2
).play(TempoClock(120/60));
)
このように音階でのシーケンスが可能です。
[角カッコ]の性質
少し話が逸れます。。
上記のようにPseq内に[0, 2, 4, 5]や
ステレオの回で周波数を[220, 222, 660]としたり、
SC3で[角カッコ]内に数値を並べることが多々あります。
[カッコ内]に並べた数値には面白い特徴があるので紹介します。
例えば
[0, 2, 4, 5]+10;
これ↑を実行すると、[ 10, 12, 14, 15 ]がPost windowに表示されます。
[0, 2, 4, 5]*10;
これ↑だと[ 0, 20, 40, 50 ]です。
という感じで、[角カッコ]内の数値にひとつずつ足し算(又は掛け算)してくれます。
これがとても便利に使えて、例えば
[0, 2, 4, 5]+12
こうすることで全部をオクターブ上にできますし、
[0, 2, 4, 5]-12
こうすることで全部をオクターブ下にできます。
ステレオの回の周波数の[220, 222, 660]も
[220, 222, 660]*2
こうすることで全部をオクターブ上にできますし、
[220, 222, 660]/2
こうすると全部をオクターブ下にできます。
ですので先ほどのPbindで
[0, 2, 4, 5].midiratio
という記述があったのは、
[0.midiratio, 2.midiratio, 4.midiratio, 5.midiratio]
と同じ意味です。
[0, 2, 4, 5].midiratio
これをオクターブ上にして全部にmidiratioするにはちょっと工夫が必要ですが、、、
(
Pbind(
\instrument, \pitchtest,
\rate, Pseq(([0, 2, 4, 5]+12).midiratio, inf),
\dur, 2
).play(TempoClock(120/60));
)
(カッコ)でくくってあげればOKです。
変数名について
さて、最後に変数名について書いておきたく最初のリズムサンプルを使った例に戻ります。
今回はBuffer.readをaという変数に入れましたが、やはり変数には名前をつけておきたいですよね。
そうすると変数名でその中身が判別でき、たくさんの変数を使うようなコードになると、利便性が増します。
※今後、BUSを作成するときなども同様の感じで変数に入れておくような使い方をします。そのときにアルファベット1文字の変数名だと、aとbがサンプルで、cがバスで、、みたいになってしまい整理もしづらいです。
※ちょっと試し再生したい、くらいのときであればアルファベット1文字のほうがよいですかね。
実は名前付き変数(空箱)を作るのはとても簡単です。
~sample_a = Buffer.read(s, "/Users/kuri/Desktop/samples/rythm01.wav");
~チルダに続けてアルファベット小文字で始まる名前をつけるだけです。
下記のようにオーディオファイルと同じ名前、などでもわかりやすいですよね。
これまで変数名aで書いていたものを~rythm01に置き換えてみます。
~rythm01 = Buffer.read(s, "/Users/kuri/Desktop/samples/rythm01.wav");
{PlayBuf.ar(2, ~rythm01, BufRateScale.kr(~rythm01)*1, 1, 0, 1, 2)}.play;
このほか、本日紹介したコードの変数”a”の部分の全てを”~rythm01”に入れ替えることで全て実行可能です。
例えば
~rythm01.play;
~rythm01.sampleRate;
などなど。全部~(チルダ)付きの変数名に置き換えてください。
rythm01でなくとも、ご自身で好きな名前をつけてかまいません。
ところでちょっとオマケ情報です。
~rythm01.sampleRate;
や
~rythm01.bufnum;
のようにしてサンプルの情報を出力しましたが、これら1個ずつを実行せずとも、下記のようにすると4つの情報を一度にゲットできます。
~rythm01.query;
ところで、最終的にSynthDefにするんだったら「これまでvarで変数名をつけていたみたいにできないの?」と思う方もいらっしゃると思います。
・・・できます。
(
SynthDef(\buftest, {
arg rate=1, trig=1, stPos=0;
var sig, rythm01;
rythm01 = Buffer.read(s, "/Users/kuri/Desktop/samples/rythm01.wav");
sig = PlayBuf.ar(2, rythm01, BufRateScale.kr(rythm01)*rate, trig, stPos, 0, 2);
Out.ar(0, sig);
}).add;
)
ファイルパスの部分はご自身の環境に合わせて変更してください。
そうすればこれで実行可能です。
addをplayに置き換えることで再生もできます。
確かにこれで実行可能ではあるのですが、バッファの情報を.queryなどで確認したいときに不便です。
なのでBuffer.readはそれだけで変数に入れておくと使い勝手がいいです。
また、var を使って作った変数は、そのvarが使われているfunction内でしか使うことができません。これを変数のスコープ(有効範囲・参照範囲)といいます。
これに対して、~(チルダ)付きの変数のスコープは広く、一度その変数の中に何かを入れるとどこからでも、例えば別のSynthDefからも参照することができます。
つまり一度オーディオファイルを読み込んだら、いろんなSynthDefから使えることになります。
アルファベット小文字1文字の変数も同様に、スコープの範囲は広いです。
こういった理由から、Buffe.readを入れる変数はfunction内のvarで作るのではなく、~(チルダ)付きで名前をつけるのが便利です。
今日はこんなところで終わります。
次回はエフェクトやBUSについて書きます。
今日のまとめ
・Bufferクラスでwav情報を読み込んで、PlayBufクラスで鳴らす。
・オーディオファイルをSC3のエディタ上にドラッグ&ドロップすると、そのオーディオファイルのパスが表示される。
・numChannelsメソッドでオーディオファイルのチャンネル数を表示
・bufnumメソッドで読み込んだバッファナンバーを表示
・numFramesメソッドで読み込んだオーディオファイルのサンプル数を表示
・sampleRateメソッドで読み込んだオーディオファイルのサンプリングレートを表示
・queryメソッドで読み込んだオーディオファイルのnumChannels, bufnum, numFrames, sampleRateの情報を一度に取得できる。
・BufRateScaleはオーディオドライバとSC3のサンプリングレートの不一致によるピッチの違いをなおしてくれる。
・PlayBufに対してmidiratioで半音単位のピッチ指定ができる。
・~に続けてアルファベット小文字ではじまるワードを書くと、変数になる。
・~付きの変数のスコープ(有効範囲・参照範囲)は広く、どこからでも参照できる。※アルファベット小文字1文字だけの変数のスコープも同様。
・それに対してvarで宣言するスコープはその関数内のみ。
<目次>にも今回のリンクを作っておきます。https://note.com/sc3/n/nb08177c4c01