2018/02/02
Pluck
デモを見る (github.io)
Karplus-Strongアルゴリズムを用いたシンセサイザです。
Karplus-Strongアルゴリズムはギターなどの撥弦楽器のような音が出せます。デモページのCutoffの値を1にすると素朴なKarplus-Strongの音になります。
フィルタのかけ方はmrahtzさんの実装を参考にしました。
2017/11/26
PADcymbal
デモを見る (github.io)
PADcymbalはPADsynthを利用したシンバルのような音を合成するシンセサイザです。
仕組みとしてはランダムに作った周波数と音量の組をPADsynthに入力しているだけです。その他、製作の過程で調べたことをライドシンバル合成の試みにまとめています。
2017/11/23
PADsynthでシンバルの合成
PADsynthによるシンバル (Freesound)
始めは周波数成分を手作業でPADsynthに入力するつもりでしたが、無理そうだったのでKernel Density Estimationという手法を応用してある程度ランダムに合成することにしました。
作ったプログラムにFreesoundにあったシンバルのPackをいくつか投げてみたところ思ったよりもいい結果が出たのですが、シンバル以外のPackも試したところ何を入れても似たような音が出てくることが分かりました。
何を入れても同じならランダムに作ったデータでもいいんじゃないかと試したところ冒頭のような音が合成できました。ただし音量エンベロープはAudacityで後付けしています。
以下のPython3のプログラムで音量エンベロープをかけていない音が合成できます。
padsynth.py (GitHub)
合成についての細かい話を以下にまとめています。
ライドシンバル合成の試み (GitHub)
2017/09/10
2017/09/08
Freeverb
デモを見る (github.io)
Freeverbのインパルス応答を書き出すレンダラを作りました。
Freeverbはリバーブの実装の一つで金属的な音が出ます(1, 2)。デモページのAllpassの値を大きくすると金属的な部分を強調して遊ぶことができます。パラメータによってはレンダリングに時間がかかるので注意してください。
2017/08/20
Ardourクイックスタート
ArdourクイックスタートはGitHub Pagesで読むこともできます。
2017/07/27
SuperColliderでZynAddSubFXのPADsynth
SuperColliderでZynAddSubFXのPADsynth algorithmを実装します。細かい部分についてはPADsynth algorithmのページにあるC/C++でのリファレンス実装が参考になりました。
オリジナルもそうですが、レンダリングに時間がかかります。
(
// 正規分布のharmonic profile。
~profile = { |fi, bwi|
var x = fi / bwi;
exp(x.neg * x) / bwi
};
// デフォルト値はリファレンス実装のc_basicと同じ。
~padTable = {
| server(s), size(2**18), f0(261.0), bw(40.0), number_harmonics(64)
, ampFunc({ |i| (if ((i % 2) == 0) {2.0} {1.0}) / i }) |
var sampleRate = server.sampleRate;
var freq_amp = Signal.newClear(size / 2);
var amps = Array.fill(number_harmonics + 1, ampFunc);
var complex, smp;
for (1, number_harmonics, { |nh|
var bw_Hz = (pow(2, bw / 1200) - 1.0) * f0 * nh;
var bwi = bw_Hz / (2.0 * sampleRate);
var fi = f0 * nh / sampleRate;
freq_amp.do{ |f_amp, index|
var hprofile = ~profile.value((index / size) - fi, bwi);
freq_amp[index] = f_amp + (hprofile * amps[nh]);
}
});
// 直流を除去。
freq_amp[0] = 0;
freq_amp.plot;
// ナイキスト周波数以上の成分を付け加える。
freq_amp = freq_amp ++ Signal.fill(freq_amp.size, 0);
// 位相のランダマイズ。
complex = Polar(
freq_amp,
freq_amp.class.fill(freq_amp.size, {2pi.rand})
);
smp = complex.real.ifft(complex.imag, Signal.fftCosTable(freq_amp.size));
smp.real.normalize
};
);
(
~sig = ~padTable.value(s, 2**18); // 乗数が 10 以下のときにピッチがおかしくなる。
~sig.plot;
~waveTable = Buffer.loadCollection(s, ~sig);
{ Pan2.ar(PlayBuf.ar(1, ~waveTable, 1, 1, 0, 1)) }.play;
);
2017/07/26
SuperCollider - ウェーブテーブルの作成
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");
);
2017/07/25
初めてのFaust
Faust環境を整えたので、簡単なシンセサイザを作りながらFaustを使うときのワークフローを組み立てていきます。
プロジェクトの作成
プロジェクトのディレクトリを作ります。
mkdir saw cd saw
エディタにはAtomを使います。language-faustをインストールして便利な補完を使います。
atom saw.dsp
saw.dspを編集して音を出します。以下はFaust Libraries Documentationにあった例です。
import("stdfaust.lib");
process = os.osc(440);
編集した.dspをFaustLiveに渡して動作確認します。
FaustLive saw.dsp
GUI
GUIについてはFaust Quick Referenceの3.5.6 User Interface Elementsに載っています。
saw.dspに音量を調節するスライダを追加します。
import("stdfaust.lib");
osc = os.osc(440);
process = osc * hslider("amp", 0.1, 0.0, 1.0, 0.01);
hslider(str, cur, min, max, step) となっています。 cur は初期値です。
オシレータ
saw.dspという名前にしたのでのこぎり波を出すように修正します。oscillators.libの sawtooth を使います。
import("stdfaust.lib");
amp = hslider("amp", 0.1, 0.0, 1.0, 0.01);
pan = hslider("pan", 0.5, 0.0, 1.0, 0.01);
freq = hslider("freq", 0.5, 0.0, 1.0, 0.01) : ba.lin2LogGain * 980 + 20;
osc = os.sawtooth(freq);
process = osc * amp : sp.panner(pan);
lin2LogGain でスライダの値を対数スケールに変換しています。 : は式の左から右に信号を送る演算子です。
panner で信号をステレオにしています。
エンベロープ
envelopes.libの ar を使います。
import("stdfaust.lib");
amp = hslider("amp", 0.1, 0.0, 1.0, 0.01);
pan = hslider("pan", 0.5, 0.0, 1.0, 0.01);
freq = hslider("freq", 0.5, 0.0, 1.0, 0.01) : ba.lin2LogGain * 980 + 20;
attack = hslider("attack", 0.001, 0.0, 1.0, 0.001);
release = hslider("release", 0.2, 0.0, 1.0, 0.001);
gate = button("gate");
env = en.ar(attack, release, gate);
osc = os.sawtooth(freq);
process = env * osc * amp : sp.panner(pan);
フィルタ
filters.libの resonlp を使います。フィルタ用のエンベロープも用意します。
import("stdfaust.lib");
amp = hslider("amp", 0.1, 0.0, 1.0, 0.01);
pan = hslider("pan", 0.5, 0.0, 1.0, 0.01);
freq = hslider("freq", 0.5, 0.0, 1.0, 0.01) : ba.lin2LogGain * 980 + 20;
ampAttack = hslider("amp attack", 0.001, 0.0, 1.0, 0.001);
ampRelease = hslider("amp release", 0.2, 0.0, 1.0, 0.001);
gate = button("gate");
cutoff = hslider("cutoff", 0.5, 0.0, 1.0, 0.01) : ba.lin2LogGain * 980 + 20;
resonance = hslider("resonance", 0.5, 0.01, 1.0, 0.01) : ba.lin2LogGain * 100;
filterAttack = hslider("filter attack", 0.001, 0.0, 1.0, 0.001);
filterRelease = hslider("filter release", 0.2, 0.0, 1.0, 0.001);
filterEnvAmount = hslider("filter envelope amount", 0.5, 0.0, 1.0, 0.01)
: ba.lin2LogGain * 980 + 20;
filterEnv = en.ar(filterAttack, filterRelease, gate);
filter = fi.resonlp(
cutoff + filterEnvAmount * filterEnv,
resonance,
1);
ampEnv = en.ar(ampAttack, ampRelease, gate);
osc = os.sawtooth(freq);
process = ampEnv * osc * amp : filter : sp.panner(pan);
仕上げ
オシレータを派手にします。2つに増やしてデチューンしたものを倍音の間隔でさらに重ねます。音の高さを pink_noise と noise で揺らしています。
... osc(f) = os.sawtooth(f) + os.sawtooth(f * (1.0 + 0.1 * no.pink_noise)); chord(numHarmo) = sum(i, numHarmo, osc((i + no.noise) * freq / numHarmo)) / numHarmo; process = ampEnv * chord(7) * amp : filter : sp.panner(pan);
osc(f) = os.sinosc(f); のような書き方は関数の宣言です。
sum(ident, numiter, expression) は expression を numiter 回ループして足し合わせます。現在のループ回数は ident に格納され expression から参照できます。 sum 以外のループは Faust Quick Reference -> 3.4.1 Diagram Expressions -> Iterations (version 0.9.100では23ページ目) に載っています。
最後にデフォルトのパラメータを調整して完成です。
import("stdfaust.lib");
amp = hslider("amp", 0.5, 0.0, 1.0, 0.01);
pan = hslider("pan", 0.5, 0.0, 1.0, 0.01);
freq = hslider("freq", 0.4, 0.0, 1.0, 0.01) : ba.lin2LogGain * 980 + 20;
ampAttack = hslider("amp attack", 0.7, 0.0, 1.0, 0.001);
ampRelease = hslider("amp release", 0.8, 0.0, 1.0, 0.001);
gate = button("gate");
cutoff = hslider("cutoff", 0.5, 0.0, 1.0, 0.01) : ba.lin2LogGain * 980 + 20;
resonance = hslider("resonance", 0.2, 0.01, 1.0, 0.01) : ba.lin2LogGain * 100;
filterAttack = hslider("filter attack", 0.25, 0.0, 1.0, 0.001);
filterRelease = hslider("filter release", 0.9, 0.0, 1.0, 0.001);
filterEnvAmount = hslider("filter envelope amount", 0.7, 0.0, 1.0, 0.01)
: ba.lin2LogGain * 980 + 20;
filterEnv = en.ar(filterAttack, filterRelease, gate);
filter = fi.resonlp(
cutoff + filterEnvAmount * filterEnv,
resonance,
1);
ampEnv = en.ar(ampAttack, ampRelease, gate);
osc(f) = os.sawtooth(f)
+ os.sawtooth(f * (1.0 + 0.1 * no.pink_noise));
chord(numHarmo) = sum(i, numHarmo, osc((i + no.noise) * freq / numHarmo))
/ numHarmo;
process = ampEnv * chord(7) * amp : filter : sp.panner(pan);
2017/07/18
SuperCollider - PtparとPsetp
Ptpar は時間のオフセットを指定してパターンを重ねるときに使えるパターンです。 Psetp や Paddp を使うことで重ねるパターンの一部を変更することができます。
例として音程を平行移動します。
( // 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_%y%m%d_%H%M%S.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;



