Signal
を使って Osc
などのUGenで使うウェーブテーブルを作ります。
sineFill
sineFill
で加算合成ができます。
以下の例ではランダムに生成したArrayを降順にソートしています。これによって低い倍音ほど音量が大きくなり、音程がわかりやすくなります。
( var size = 1024; var harmonics = 128; var sig = Signal.sineFill( size, {exprand(0.00001, 1.0)}.dup(harmonics).sort.reverse, {100pi.rand}.dup(harmonics).sort); sig.plot; sig.play(loop: true, numChannels: 2) ); Window.closeAll; // ウィンドウをまとめて閉じる。
chebyFill
chebyFill
でチェビシェフ多項式を使った合成ができます。
得られるウェーブテーブルは Shaper
での利用に向いているようです。 Shaper
で使うときは引数に zeroOffset: true
を渡すことが推奨されています。
( var size = 1024; var ampSize = 32; var sig = Signal.chebyFill( size, {exprand(0.00001, 1.0)}.dup(ampSize).sort.reverse, zeroOffset: true); sig.plot; { var buffer = Buffer.loadCollection(s, sig); var mouseX = 1 - MouseX.kr(1, 0.00001, 1); var osc = SinOsc.ar(60, mul: mouseX); Pan2.ar(Shaper.ar(buffer, osc, 0.2)) }.play; );
以下は chebyFill
を点対称な波形に変形する例です。
( var size = 1024; var ampSize = 32; var sig = Signal.chebyFill( size / 2, {exprand(0.00001, 1.0)}.dup(ampSize).sort.reverse); var zero = sig[0]; var sigPositive = Signal.newFrom(sig) - zero; var sigNegative = sig.invert.reverse + zero; sig = (sigNegative ++ sigPositive).normalize; sig.plot; { var buffer = Buffer.loadCollection(s, sig); var mouseX = 1 - MouseX.kr(1, 0.00001, 1); var osc = SinOsc.ar(60, mul: mouseX); Pan2.ar(Shaper.ar(buffer, osc, 0.2)) }.play; );
fft, ifft
FFTを使って周波数領域での編集を行います。 Signal.fft
と Signal.ifft
は引数に Signal.fftCosTable
が必要となる点に注意してください。
のこぎり波を作ります。
( // FFT saw. ~addSaw = { |array, step| var sign = 1000; forBy(1, array.size / 2 - 1, 1, { |index| array[index] = array[index] + (sign / index); sign = sign.neg; }); array }; ~dckill = { |complex| complex.real[0] = 0; complex.imag[0] = 0; complex }; ~saw = { |size(1024), noiseGain(0.001)| var signal, cosTable; cosTable = Signal.fftCosTable(size); signal = Signal.fill(size, {noiseGain.rand}); // ノイズ。 signal = ~addSaw.value(signal); signal[0] = 0; // 直流を除去。 signal = Signal.newClear(size).ifft(signal, cosTable); signal = signal.real.rotate(size / 2); // 位相を進める。 signal.normalize }; ~wave = ~saw.value(noiseGain: 1.0); ~wave.plot; ~wave.play(true, numChannels: 2); );
以下の例では、時間領域で作ったのこぎり波の位相を周波数領域でランダマイズして、矩形波を重ねています。
( ~addSquare = { |array| var sign = 1000/0.75; forBy(1, array.size / 2 - 1, 2, { |index| array[index] = array[index] + (sign / index); sign = sign.neg; }); array }; ~randPhase = { |complex| var polar = complex.asPolar; polar.theta = polar.theta.collect{360.0.rand}; polar.asComplex }; ~dckill = { |complex| complex.real[0] = 0; complex.imag[0] = 0; complex }; ~sawsquare = { |size(1024), noiseGain(0.001)| var signal, cosTable; signal = Signal.fill(size, { |index| 2 * index / size -1 + noiseGain.rand}); // のこぎり波とノイズ。 cosTable = Signal.fftCosTable(size); signal = signal.fft(Signal.newClear(size), cosTable); signal = ~randPhase.value(signal); signal.real = ~addSquare.value(signal.real); signal = ~dckill.value(signal); signal = signal.real.ifft(signal.imag, cosTable); signal.real.normalize }; ~wave = ~sawsquare.value; ~wave.plot; ~wave.play(true, numChannels: 2); );
ファイルに保存
一度 Buffer
にしてから保存します。
( ~sineFill = { |size(1024), harmonics(128)| Signal.sineFill( size, {exprand(0.00001, 1.0)}.dup(harmonics).sort.reverse, {100pi.rand}.dup(harmonics).sort); }: ~dir = Platform.recordingsDir +/+ "wavetable"; ~dir.mkdir; // String.mkdir Buffer .loadCollection(s, ~sineFill.value) .write(~dir +/+ "wavetable.wav", "WAV", "float"); );