Processingでエフェクターをつくる
今回は、Processingでエフェクターを作ろうと思います。 エフェクターは入力した音に音響効果を加えて出力するもので、特にエレキギターやエレキベースといった楽器で音を加工するために使われています。 エフェクターの種類は多々ありますが、そのうちのいくつかをProcessingのMinimとControlP5で作ってみたいと思います。
入力した音をそのまま出力する
まずは、第1回で紹介した入力した音をそのままスピーカーから出力する方法を確認しましょう。
/* * sample01 */ import ddf.minim.spi.*; import ddf.minim.signals.*; import ddf.minim.*; import ddf.minim.analysis.*; import ddf.minim.ugens.*; import ddf.minim.effects.*; Minim minim; AudioOutput out; LiveInput in; void setup(){ size(512, 350); minim = new Minim(this); out = minim.getLineOut(); AudioStream inputStream = minim.getInputStream( out.getFormat().getChannels(), out.bufferSize(), out.sampleRate(), out.getFormat().getSampleSizeInBits()); in = new LiveInput(inputStream); in.patch(out); } void draw(){ background(128); stroke(255); for(int i = 0; i < out.bufferSize() - 1; i++){ line(i, 50 + out.left.get(i) * 50, i + 1, 50 + out.left.get(i + 1) * 50); line(i, 150 + out.right.get(i) * 50, i + 1, 150 + out.right.get(i + 1) * 50); } }
Minimではマイクからの入力を利用するためにLiveInputを使います。 このプログラムの入力と出力にPCのマイクとスピーカーで利用するとハウリングが生じる場合があります。 そのときは、イヤフォンやヘッドフォンを使ってください。
ディストーション
まずは、ディストーションを作ってみましょう。 ディストーションは、音を歪ませるエフェクターです。 入力した音を増幅してから、過大部分をクリッピングすることにより、音を歪ませています。 オーバードライブやファズも基本的には同じ処理を行うエフェクターです。
MinimとControlP5で作ったディストーションのプログラムは次のようになります。 「DISTORTION」ノブで値を大きくするほど、歪みが大きくなります。 音を入力すると、上端と下端が平らになっている(クリッピングされている)のが確認できると思います。
/* * sample02 */ import ddf.minim.spi.*; import ddf.minim.signals.*; import ddf.minim.*; import ddf.minim.analysis.*; import ddf.minim.ugens.*; import ddf.minim.effects.*; import controlP5.*; Minim minim; AudioOutput out; LiveInput in; WaveShaper shaper; ControlP5 cp5; void setup(){ size(512, 350); minim = new Minim(this); out = minim.getLineOut(); AudioStream inputStream = minim.getInputStream( out.getFormat().getChannels(), out.bufferSize(), out.sampleRate(), out.getFormat().getSampleSizeInBits()); in = new LiveInput(inputStream); float[] sawVal= {-1.0, 1.0}; Wavetable saw = new Wavetable(sawVal); shaper = new WaveShaper(0.7, 1.0, saw); in.patch(shaper); shaper.patch(out); cp5 = new ControlP5(this); float knobRadius = 40; cp5.addKnob("setDistortion") .setLabel("distortion") .setRange(1.0, 100.0) .setValue(1.0) .setPosition(width / 2 - knobRadius, 250 - knobRadius) .setRadius(knobRadius); } void setDistortion(float value){ shaper.mapAmplitude.setLastValue(value); } void draw(){ background(128); stroke(255); for(int i = 0; i < out.bufferSize() - 1; i++){ line(i, 50 + out.left.get(i) * 50, i + 1, 50 + out.left.get(i + 1) * 50); line(i, 150 + out.right.get(i) * 50, i + 1, 150 + out.right.get(i + 1) * 50); } }
今回はディストーション処理を行うために、WaveShaperクラスを利用しています。 WaveShaperクラスはWaveShaperという音響合成のための加工をするクラスです。 Wave Shaperは、あらかじめ入力に対する出力の対応関係と入力の増幅度を決めておき、それもとに入力音を増幅してから出力に変換する処理を行います。
偏ったDTM用語辞典 - ウェーブシェイパー:Wave Shaperとは - DTM / MIDI 用語の意味・解説 | g200kg Music & Software
WaveShaperクラスのコンストラクタの引数は、それぞれ次の値を指定しています。
ディストーションプログラムでは、第3引数として与えた対応関係は「入力音 = 出力音」となるようにして、増幅して限界値を超えた値は限界値となるというWaveShaperクラスの特性を利用してクリッピングを行っています。
WaveShaperクラスはもともとクリッピングではなく音響合成のためのものなので、工夫次第でこれをもとに面白い音を作り出すことができます。
ディレイ
ディレイは、入力した音を遅れて出力するエフェクターです。
単発ディレイ
次のプログラムを入力した音を一度だけ遅れて鳴らすディレイプログラムです。 「DELAY TIME」で遅れ時間を指定します。 「DELAY AMP」で遅れた音の音量を指定します。
/* * sample03 */ import ddf.minim.spi.*; import ddf.minim.signals.*; import ddf.minim.*; import ddf.minim.analysis.*; import ddf.minim.ugens.*; import ddf.minim.effects.*; import controlP5.*; Minim minim; AudioOutput out; LiveInput in; Delay delay; ControlP5 cp5; void setup(){ size(512, 350); minim = new Minim(this); out = minim.getLineOut(); AudioStream inputStream = minim.getInputStream( out.getFormat().getChannels(), out.bufferSize(), out.sampleRate(), out.getFormat().getSampleSizeInBits()); in = new LiveInput(inputStream); delay = new Delay(1.0, 1.0); in.patch(delay); delay.patch(out); cp5 = new ControlP5(this); float knobRadius = 40; cp5.addKnob("setDelayTime") .setLabel("dekay time") .setRange(0.00, 2.0) .setValue(1.0) .setPosition(width / 3.0 * 1.0 - knobRadius, 250 - knobRadius) .setRadius(knobRadius); cp5.addKnob("setDelayAmp") .setLabel("delay amp") .setRange(0.00, 5.0) .setValue(1.0) .setPosition(width / 3.0 * 2.0 - knobRadius, 250 - knobRadius) .setRadius(knobRadius); } void setDelayTime(float value){ delay.setDelTime(value); } void setDelayAmp(float value){ delay.setDelAmp(value); } void draw(){ background(128); stroke(255); for(int i = 0; i < out.bufferSize() - 1; i++){ line(i, 50 + out.left.get(i) * 50, i + 1, 50 + out.left.get(i + 1) * 50); line(i, 150 + out.right.get(i) * 50, i + 1, 150 + out.right.get(i + 1) * 50); } }
ディレイには、MinimのDelayクラスを指定します。 Delayクラスのコンストラクタの第1引数が遅れ時間、第2引数が遅れた音の音量を表しています。
フィードバック・ディレイ
フィードバックディレイは、出力した音を再びディレイに入力する(フィードバック)することにより、音を段々と減衰させていくディレイです。
/* * sample04 */ import ddf.minim.spi.*; import ddf.minim.signals.*; import ddf.minim.*; import ddf.minim.analysis.*; import ddf.minim.ugens.*; import ddf.minim.effects.*; import controlP5.*; Minim minim; AudioOutput out; LiveInput in; Delay delay; ControlP5 cp5; void setup(){ size(512, 350); minim = new Minim(this); out = minim.getLineOut(); AudioStream inputStream = minim.getInputStream( out.getFormat().getChannels(), out.bufferSize(), out.sampleRate(), out.getFormat().getSampleSizeInBits()); in = new LiveInput(inputStream); delay = new Delay(1.0, 1.0, true); in.patch(delay); delay.patch(out); cp5 = new ControlP5(this); float knobRadius = 40; cp5.addKnob("setDelayTime") .setLabel("dekay time") .setRange(0.0, 2.0) .setValue(1.0) .setPosition(width / 3.0 * 1.0 - knobRadius, 250 - knobRadius) .setRadius(knobRadius); cp5.addKnob("setDelayAmp") .setLabel("delay amp") .setRange(0.0, 1.0) .setValue(0.5) .setPosition(width / 3.0 * 2.0 - knobRadius, 250 - knobRadius) .setRadius(knobRadius); } void setDelayTime(float value){ delay.setDelTime(value); } void setDelayAmp(float value){ delay.setDelAmp(value); } void draw(){ background(128); stroke(255); for(int i = 0; i < out.bufferSize() - 1; i++){ line(i, 50 + out.left.get(i) * 50, i + 1, 50 + out.left.get(i + 1) * 50); line(i, 150 + out.right.get(i) * 50, i + 1, 150 + out.right.get(i + 1) * 50); } }
Delayクラスでフィードバックを行うには、コンストラクタの第3引数にtrueを設定します。 フィードバックをかけた状態で、遅れた音の音量を1より大きくすると、ポジティブフィードバックが発生し、音量が無限大に発散するので注意してください。
フィルター
フィルターは、入力音のうち特定の周波数帯域のみを出力音にするエフェクターです。 フィルターには、次のものがあります。
- ローパス・フィルター(Low-Pass Filter, LP)
- ハイパス・フィルター(High-Pass Filter, HP)
- バンドパス・フィルター(Band-Pass Filter, BP)
ローパス・フィルターは入力音のうち特定の周波数以下の帯域のみを通します。 ハイパス・フィルターは入力音のうち特定の周波数以上の帯域のみを通します。 バンドパス・フィルターは入力音のうち特定の周波数の帯域のみを通します。 この境界値となる周波数をカットオフ周波数と呼びます。
フィルターでは、レゾナンスを設定できることがあります。 レゾナンスを設定することにより、カットオフ周波数付近の音を強調することができます。
Minimでのフィルターは次のようになります。 「FILTER TYPE」でフィルターの種類を選択できます。0がLP、1がHP、2がBPに対応しています。 「CUTOFF FREQUENCY」でカットオフ周波数を指定します。 「RESONANCE」でレゾナンスの値を指定します。 レゾナンスを1に近づけすぎると共鳴が始まり、フィルター自体が音を発してしまいます。
/* * sample05 */ import ddf.minim.spi.*; import ddf.minim.signals.*; import ddf.minim.*; import ddf.minim.analysis.*; import ddf.minim.ugens.*; import ddf.minim.effects.*; import controlP5.*; Minim minim; AudioOutput out; LiveInput in; MoogFilter filter; ControlP5 cp5; void setup(){ size(512, 350); minim = new Minim(this); out = minim.getLineOut(); AudioStream inputStream = minim.getInputStream( out.getFormat().getChannels(), out.bufferSize(), out.sampleRate(), out.getFormat().getSampleSizeInBits()); in = new LiveInput(inputStream); filter = new MoogFilter(1000, 0.5, MoogFilter.Type.LP); in.patch(filter); filter.patch(out); cp5 = new ControlP5(this); float knobRadius = 40; cp5.addKnob("setFilterType") .setLabel("filter type") .setRange(0, 2) .setValue(0) .setPosition(width / 4.0 * 1.0 - knobRadius, 250 - knobRadius) .setRadius(knobRadius) .setNumberOfTickMarks(2) .setTickMarkLength(5) .snapToTickMarks(true); cp5.addKnob("setCutoffFreq") .setLabel("cutoff frequency") .setRange(0, 2000) .setValue(1000) .setPosition(width / 4.0 * 2.0 - knobRadius, 250 - knobRadius) .setRadius(knobRadius); cp5.addKnob("setResonance") .setLabel("resonance") .setRange(0.0, 1.0) .setValue(0.5) .setPosition(width / 4.0 * 3.0 - knobRadius, 250 - knobRadius) .setRadius(knobRadius); } void setFilterType(int value){ switch(value){ case 0: filter.type = MoogFilter.Type.LP; break; case 1: filter.type = MoogFilter.Type.HP; break; case 2: filter.type = MoogFilter.Type.BP; break; } } void setCutoffFreq(float value){ filter.frequency.setLastValue(value); } void setResonance(float value){ filter.resonance.setLastValue(value); } void draw(){ background(128); stroke(255); for(int i = 0; i < out.bufferSize() - 1; i++){ line(i, 50 + out.left.get(i) * 50, i + 1, 50 + out.left.get(i + 1) * 50); line(i, 150 + out.right.get(i) * 50, i + 1, 150 + out.right.get(i + 1) * 50); } }
MinimではMoogFilterクラスを利用することでフィルタリング処理を簡単に行うことができます。
トレモロ
トレモロは音量を周期的に上下させ揺れているような効果を与えるエフェクターです。
次のプログラムは、サイン波で入力音を揺らすプログラムです。 「RATE」で音を揺らす早さを指定することができます。 「DEPTH」で変化する音量の幅を指定することができます。
/* * sample06 */ import ddf.minim.spi.*; import ddf.minim.signals.*; import ddf.minim.*; import ddf.minim.analysis.*; import ddf.minim.ugens.*; import ddf.minim.effects.*; import controlP5.*; Minim minim; AudioOutput out; LiveInput in; Oscil tremolo; Multiplier multiplier; ControlP5 cp5; void setup(){ size(512, 350); minim = new Minim(this); out = minim.getLineOut(); AudioStream inputStream = minim.getInputStream( out.getFormat().getChannels(), out.bufferSize(), out.sampleRate(), out.getFormat().getSampleSizeInBits()); in = new LiveInput(inputStream); tremolo = new Oscil(2.0, 0.5, Waves.SINE); tremolo.offset.setLastValue(1.0); multiplier = new Multiplier(1.0); tremolo.patch(multiplier.amplitude); in.patch(multiplier); multiplier.patch(out); cp5 = new ControlP5(this); float knobRadius = 40; cp5.addKnob("setRate") .setLabel("rate") .setRange(0, 10.0) .setValue(2.0) .setPosition(width / 3.0 * 1.0 - knobRadius, 250 - knobRadius) .setRadius(knobRadius); cp5.addKnob("setDepth") .setLabel("depth") .setRange(0, 1.0) .setValue(0.5) .setPosition(width / 3.0 * 2.0 - knobRadius, 250 - knobRadius) .setRadius(knobRadius); } void setRate(float value){ tremolo.setFrequency(value); } void setDepth(float value){ tremolo.setAmplitude(value); } void draw(){ background(128); stroke(255); for(int i = 0; i < out.bufferSize() - 1; i++){ line(i, 50 + out.left.get(i) * 50, i + 1, 50 + out.left.get(i + 1) * 50); line(i, 150 + out.right.get(i) * 50, i + 1, 150 + out.right.get(i + 1) * 50); } }
このプログラムでは、OscilクラスとMultiplierクラスを利用してトレモロを実現しています。 Multiplierは入力音の音量を制御するためのクラスです。 Oscilクラスの変数であるtremoloは、音量を揺らすための波を表しています。 これをMultiplierインスタンスの音量にパッチしてから、Multiplierインスタンスに入力をパッチしています。
この記事はProcessingで楽器をつくろうの第5回目です。