aa develop

開発と成長

Processingでシンセサイザーをつくる

Processingで楽器をつくろう」の最後の記事です。

最後は、倍音を含んだ音を加工して音をつくる減算式シンセサイザーをつくろうと思います。

シンセサイザーの構成

次の図は、今回作成するシンセサイザーの構成を表しています。

f:id:aa_debdeb:20161123132642j:plain

太い矢印が音声信号、細い矢印が制御信号の流れを表しています。 各部分の役割は次のとおりです。

  • キーボード:発振周波数を入力する
  • VCO:基本波形をつくる
  • VCF:波形を加工する
  • VCA:音量を制御する
  • EG:音量に時間的な変化を与える
  • LFO:音量や音程に周期的な変化を与える
  • スピーカー:音を出力する

各部分について、順番に作っていきたいと思います。

キーボードとVCOをつくる

まずは、図の赤枠で示したキーボードとVCOをつくって、それをもとにスピーカーから音を出したいと思います。

f:id:aa_debdeb:20161123132713j:plain

「VCO WAVE」で波形を矩形波とノコギリ波から選択することができます。 矩形波とノコギリ波は倍音を多く含んでおり、VCFで加工すると音色が変化しやすいので、減算方式のシンセサイザーではよく用いられます。

OCTAVE」で鳴らす音程を変更することができます。 キーボードの対応した部分を押すと、対応した音を鳴らすことができます。

f:id:aa_debdeb:20161123132858j:plain

/*
* sample01
*/

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;
ControlP5 cp5;

// variance for VCO
Oscil oscil;
int octave;
// variance for keyboard
boolean isKeyLocked;
char lockedKey;

void setup(){
  size(800, 500);
  textAlign(CENTER);
  fill(255);
  
  minim = new Minim(this);
  out = minim.getLineOut();
  oscil = new Oscil(440, 1.0, Waves.SQUARE);

  isKeyLocked = false;
  
  cp5 = new ControlP5(this);
  float knobRadius = 25;
  PVector toggleSize = new PVector(40, 20);
  // GUI for VCO
  text("VCO", width / 4.0 * 1.0, height / 4.0 * 0.5);
  cp5.addToggle("setVcoWave")
     .setLabel("VCO WAVE")
     .setValue(true)
     .setPosition(width / 4.0 * 1.0 - toggleSize.x / 2.0, height / 4.0 * 1.0 - toggleSize.y / 2.0)
     .setSize(int(toggleSize.x), int(toggleSize.y))
     .setMode(ControlP5.SWITCH);
  cp5.addKnob("octave")
     .setLabel("OCTAVE")
     .setRange(2, 6)
     .setValue(4)
     .setPosition(width / 4.0 * 1.0 - knobRadius, height / 4.0 * 2.0 - knobRadius)
     .setRadius(knobRadius)
     .setNumberOfTickMarks(4)
     .setTickMarkLength(5)
     .snapToTickMarks(true); 

}

// functions for VCO
void setVcoWave(boolean value){
  if(value){
    oscil.setWaveform(Waves.SQUARE);
  } else {
    oscil.setWaveform(Waves.SAW);  
  }
}

void keyPressed(){
  if(!isKeyLocked){
    String tone = "";
    switch(key){
    case 'z':
      tone += "C"; break;
    case 's':
      tone += "C#"; break;
    case 'x':
      tone += "D"; break;
    case 'd':
      tone += "D#"; break;
    case 'c':
      tone += "E"; break;
    case 'v':
      tone += "F"; break;
    case 'g':
      tone += "F#"; break;
    case 'b':
      tone += "G"; break;
    case 'h':
      tone += "G#"; break;
    case 'n':
      tone += "A"; break;
    case 'j':
      tone += "A#"; break;
    case 'm':
      tone += "B"; break;
    }
    if(tone != ""){
      oscil.setFrequency(Frequency.ofPitch(tone + octave).asHz());
      oscil.patch(out);
      isKeyLocked = true;
      lockedKey = key;
    }
  }
}

void keyReleased(){
  if(isKeyLocked && key == lockedKey){
    oscil.unpatch(out);
    isKeyLocked = false;
  }
}

void draw(){
//  background( 0 );
//  stroke( 255 );
//  for( int i = 0; i < out.bufferSize() - 1; i++ )
//  {
//    float x1  =  map( i, 0, out.bufferSize(), 0, width );
//    float x2  =  map( i+1, 0, out.bufferSize(), 0, width );
//    line( x1, 50 + out.left.get(i)*50, x2, 50 + out.left.get(i+1)*50);
//    line( x1, 150 + out.right.get(i)*50, x2, 150 + out.right.get(i+1)*50);
//  }  
}

プログラムの基本は、第4回の「Processingでキーボードを鍵盤にする」で作成したものです。 そのときはキーボードの代わりにInstrumentインターフェースを利用しましたが、今回は利用しませんでした。 その代わりに、keyPressed()とkeyReleased()を使いました。 これにより、キーを押している間は音を鳴らし続け、キーを離すと音が止まるようにしました。

VCFをつくる

次に赤枠で示したVCFをつくります。すでに作成した箇所は黒の太枠で示しています。

f:id:aa_debdeb:20161123132729j:plain

VCFでは、VCOで作成した波形をフィルターにより加工します。 これにより、出力する音の音色を変えることができます。 VCFで最もよく用いられるフィルターは、ローパス・フィルターです。

次のプログラムは、前に作成したVCOプログラムにローパス・フィルターのVCFを追加したものです。 「CUTOFF FREQUENCY」ノブでフィルターのカットオフ周波数を変更することができます。 「RESONANCE」ノブでフィルターのレゾナンスを変更することができます。

f:id:aa_debdeb:20161123132913j:plain

/*
* 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;
ControlP5 cp5;

// variance for VCO
Oscil oscil;
int octave;
// variance for VCF
MoogFilter filter;
// variance for keyboard
boolean isKeyLocked;
char lockedKey;

void setup(){
  size(800, 500);
  textAlign(CENTER);
  fill(255);
  
  minim = new Minim(this);
  out = minim.getLineOut();
  oscil = new Oscil(440, 1.0, Waves.SQUARE);
  filter = new MoogFilter(10000, 0.0, MoogFilter.Type.LP);
  oscil.patch(filter);

  isKeyLocked = false;
  
  cp5 = new ControlP5(this);
  float knobRadius = 25;
  PVector toggleSize = new PVector(40, 20);
  // GUI for VCO
  text("VCO", width / 4.0 * 1.0, height / 4.0 * 0.5);
  cp5.addToggle("setVcoWave")
     .setLabel("VCO WAVE")
     .setValue(true)
     .setPosition(width / 4.0 * 1.0 - toggleSize.x / 2.0, height / 4.0 * 1.0 - toggleSize.y / 2.0)
     .setSize(int(toggleSize.x), int(toggleSize.y))
     .setMode(ControlP5.SWITCH);
  cp5.addKnob("octave")
     .setLabel("OCTAVE")
     .setRange(2, 6)
     .setValue(4)
     .setPosition(width / 4.0 * 1.0 - knobRadius, height / 4.0 * 2.0 - knobRadius)
     .setRadius(knobRadius)
     .setNumberOfTickMarks(4)
     .setTickMarkLength(5)
     .snapToTickMarks(true); 
  // GUI for VCF
  text("VCF", width / 4.0 * 2.0, height / 4.0 * 0.5);
  cp5.addKnob("setCutoffFrequency")
     .setLabel("CUTOFF FREQUENCY")
     .setRange(0, 10000)
     .setValue(10000)
     .setPosition(width / 4.0 * 2.0 - knobRadius, height / 4.0 * 1.0 - knobRadius)
     .setRadius(knobRadius);
  cp5.addKnob("setResonance")
     .setLabel("RESONANCE")
     .setRange(0, 1)
     .setValue(0)
     .setPosition(width / 4.0 * 2.0 - knobRadius, height / 4.0 * 2.0 - knobRadius)
     .setRadius(knobRadius);
}

// functions for VCO
void setVcoWave(boolean value){
  if(value){
    oscil.setWaveform(Waves.SQUARE);
  } else {
    oscil.setWaveform(Waves.SAW);  
  }
}
// functions for VCF
void setCutoffFrequency(float value){
  filter.frequency.setLastValue(value);
}
void setResonance(float value){
  filter.resonance.setLastValue(value);
}

void keyPressed(){
  if(!isKeyLocked){
    String tone = "";
    switch(key){
    case 'z':
      tone += "C"; break;
    case 's':
      tone += "C#"; break;
    case 'x':
      tone += "D"; break;
    case 'd':
      tone += "D#"; break;
    case 'c':
      tone += "E"; break;
    case 'v':
      tone += "F"; break;
    case 'g':
      tone += "F#"; break;
    case 'b':
      tone += "G"; break;
    case 'h':
      tone += "G#"; break;
    case 'n':
      tone += "A"; break;
    case 'j':
      tone += "A#"; break;
    case 'm':
      tone += "B"; break;
    }
    if(tone != ""){
      oscil.setFrequency(Frequency.ofPitch(tone + octave).asHz());
      filter.patch(out);
      isKeyLocked = true;
      lockedKey = key;
    }
  }
}

void keyReleased(){
  if(isKeyLocked && key == lockedKey){
    filter.unpatch(out);
    isKeyLocked = false;
  }
}

void draw(){
//  background( 0 );
//  stroke( 255 );
//  for( int i = 0; i < out.bufferSize() - 1; i++ )
//  {
//    float x1  =  map( i, 0, out.bufferSize(), 0, width );
//    float x2  =  map( i+1, 0, out.bufferSize(), 0, width );
//    line( x1, 50 + out.left.get(i)*50, x2, 50 + out.left.get(i+1)*50);
//    line( x1, 150 + out.right.get(i)*50, x2, 150 + out.right.get(i+1)*50);
//  }  
}

ローパス・フィルターには、第5回「Processingでエフェクターをつくる」でも利用したMoogFilterクラスを利用しました。 MoogFilterの使い方はそちらを参考にしてください。

VCAとEGをつくる

次にVCAとEGを作りたいと思います。

f:id:aa_debdeb:20161123132749j:plain

VCAで音量を、EGで音量の変化を設定します。 この部分により、シンセサイザーに楽器のような音量の変化を加えることができます。 シンセサイザーの一般的なEGは、ADSRを設定します。 今回作成しているシンセサイザーもADSRを使いたいと思います。

各ノブで次の値を制御できます。

  • AMPLITUDE:音量
  • ATTACK:attackの長さ
  • SUSTAIN:sustain時の音量
  • DECAY / RELEAE: decayとreleaseの長さ

f:id:aa_debdeb:20161123132940j:plain

/*
* 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;
ControlP5 cp5;

// variance for VCO
Oscil oscil;
int octave;
// variance for VCF
MoogFilter filter;
// variance for VCA
ADSR adsr;
Multiplier multiplier;
Knob amplitudeKnob, attackKnob, sustainKnob, decayReleaseKnob;
// variance for keyboard
boolean isKeyLocked;
char lockedKey;

void setup(){
  size(800, 500);
  textAlign(CENTER);
  fill(255);
  
  minim = new Minim(this);
  out = minim.getLineOut();
  oscil = new Oscil(440, 1.0, Waves.SQUARE);
  filter = new MoogFilter(10000, 0.0, MoogFilter.Type.LP);
  multiplier = new Multiplier(1.0);
  adsr = new ADSR(1.0, 1.0, 1.0, 0.5, 1.0);
  oscil.patch(filter).patch(multiplier).patch(adsr);

  isKeyLocked = false;
  
  cp5 = new ControlP5(this);
  float knobRadius = 25;
  PVector toggleSize = new PVector(40, 20);
  // GUI for VCO
  text("VCO", width / 4.0 * 1.0, height / 4.0 * 0.5);
  cp5.addToggle("setVcoWave")
     .setLabel("VCO WAVE")
     .setValue(true)
     .setPosition(width / 4.0 * 1.0 - toggleSize.x / 2.0, height / 4.0 * 1.0 - toggleSize.y / 2.0)
     .setSize(int(toggleSize.x), int(toggleSize.y))
     .setMode(ControlP5.SWITCH);
  cp5.addKnob("octave")
     .setLabel("OCTAVE")
     .setRange(2, 6)
     .setValue(4)
     .setPosition(width / 4.0 * 1.0 - knobRadius, height / 4.0 * 2.0 - knobRadius)
     .setRadius(knobRadius)
     .setNumberOfTickMarks(4)
     .setTickMarkLength(5)
     .snapToTickMarks(true); 
  // GUI for VCF
  text("VCF", width / 4.0 * 2.0, height / 4.0 * 0.5);
  cp5.addKnob("setCutoffFrequency")
     .setLabel("CUTOFF FREQUENCY")
     .setRange(0, 10000)
     .setValue(10000)
     .setPosition(width / 4.0 * 2.0 - knobRadius, height / 4.0 * 1.0 - knobRadius)
     .setRadius(knobRadius);
  cp5.addKnob("setResonance")
     .setLabel("RESONANCE")
     .setRange(0, 1)
     .setValue(0)
     .setPosition(width / 4.0 * 2.0 - knobRadius, height / 4.0 * 2.0 - knobRadius)
     .setRadius(knobRadius);
  // GUI for VCA + EG
  text("VCA + EG", width / 4.0 * 3.0, height / 7.0 * 0.5);
  amplitudeKnob = cp5.addKnob("setAmplitude")
     .setLabel("AMPLITUDE")
     .setRange(0, 1)
     .setValue(0.5)
     .setPosition(width / 4.0 * 3.0 - knobRadius, height / 7.0 * 1.0 - knobRadius)
     .setRadius(knobRadius);
  attackKnob = cp5.addKnob("setAttack")
                  .setLabel("ATTACK")
                  .setRange(0.01, 5.0)
                  .setValue(1.0)
                  .setPosition(width / 4.0 * 3.0 - knobRadius, height / 7.0 * 2.0 - knobRadius)
                  .setRadius(knobRadius);
  sustainKnob = cp5.addKnob("setSustain")
                   .setLabel("SUSTAIN")
                   .setRange(0, 1.0)
                   .setValue(0.5)
                   .setPosition(width / 4.0 * 3.0 - knobRadius, height / 7.0 * 3.0 - knobRadius)
                   .setRadius(knobRadius); 
  decayReleaseKnob = cp5.addKnob("setDecayRelease")
                        .setLabel("DECAY / RELEASE")
                        .setRange(0.01, 5.0)
                        .setValue(1.0)
                        .setPosition(width / 4.0 * 3.0 - knobRadius, height / 7.0 * 4.0 - knobRadius)
                        .setRadius(knobRadius);  
}

// functions for VCO
void setVcoWave(boolean value){
  if(value){
    oscil.setWaveform(Waves.SQUARE);
  } else {
    oscil.setWaveform(Waves.SAW);  
  }
}
// functions for VCF
void setCutoffFrequency(float value){
  filter.frequency.setLastValue(value);
}
void setResonance(float value){
  filter.resonance.setLastValue(value);
}
// function for VCA + EG
void setAmplitude(float value){
  multiplier.amplitude.setLastValue(value);
}
void setParameters(){
  adsr.setParameters(1.0, attackKnob.getValue(), decayReleaseKnob.getValue(), sustainKnob.getValue(), decayReleaseKnob.getValue(), 0.0, 0.0);
}
void setAttack(){
  setParameters();
}
void setSustain(){
  setParameters();
}
void setDecayRelease(){
  setParameters();
}

void keyPressed(){
  if(!isKeyLocked){
    String tone = "";
    switch(key){
    case 'z':
      tone += "C"; break;
    case 's':
      tone += "C#"; break;
    case 'x':
      tone += "D"; break;
    case 'd':
      tone += "D#"; break;
    case 'c':
      tone += "E"; break;
    case 'v':
      tone += "F"; break;
    case 'g':
      tone += "F#"; break;
    case 'b':
      tone += "G"; break;
    case 'h':
      tone += "G#"; break;
    case 'n':
      tone += "A"; break;
    case 'j':
      tone += "A#"; break;
    case 'm':
      tone += "B"; break;
    }
    if(tone != ""){
      oscil.setFrequency(Frequency.ofPitch(tone + octave).asHz());
      adsr.unpatch(out);
      adsr.noteOn();
      adsr.patch(out);
      isKeyLocked = true;
      lockedKey = key;
    }
  }
}

void keyReleased(){
  if(isKeyLocked && key == lockedKey){
    adsr.unpatchAfterRelease(out);
    adsr.noteOff();
    isKeyLocked = false;
  }
}

void draw(){
//  background( 0 );
//  stroke( 255 );
//  for( int i = 0; i < out.bufferSize() - 1; i++ )
//  {
//    float x1  =  map( i, 0, out.bufferSize(), 0, width );
//    float x2  =  map( i+1, 0, out.bufferSize(), 0, width );
//    line( x1, 50 + out.left.get(i)*50, x2, 50 + out.left.get(i+1)*50);
//    line( x1, 150 + out.right.get(i)*50, x2, 150 + out.right.get(i+1)*50);
//  }  
}

VCAにはMultiplierクラスを、EGにはADSRクラスを利用しました。 それぞれ、第5回「Processingでエフェクターをつくる」、第3回「Processingでキーボードを鍵盤にする」で解説しています。 使い方はそちらを参考にしてください。

LFOをつくる

最後に、残ったLFOを作りたいと思います。

f:id:aa_debdeb:20161123132802j:plain

LFOは「Low Frequency Oscilator」の略で、低い周波数で音程や音量を変調するために用いられます。 LFOでVCOの音程を変調するとビブラートに、VCFのカットオフ周波数を変調するとワウに、VCAを音量を変調するとトレモロになります。 今回作成するシンセサイザーでは、一つのLFOでVCO、VCF、VCAをそれぞれ変調させます。 LFOの波形は「LFO WAVE」で正弦波と矩形波から選択でき、「RATE」ノブで発振周波数を変更することができます。 VCO、VCF、VCAに対する変調の強さは、それぞれ「VCA INTENSITY」、「VCF INTENSITY」、「VCA INTENSITY」で制御することができます。

f:id:aa_debdeb:20161123132956j:plain

/*
* 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;
ControlP5 cp5;

// variance for VCO
Oscil oscil;
int octave;
// variance for VCF
MoogFilter filter;
Knob cutoffFrequencyKnob;
// variance for VCA
ADSR adsr;
Multiplier multiplier;
Knob amplitudeKnob, attackKnob, sustainKnob, decayReleaseKnob;
// variance for LFO
Oscil vcoLfo, vcfLfo, vcaLfo;
float vcoLfoIntensity;
Knob vcfLfoIntensityKnob, vcaLfoIntensityKnob;
// variance for keyboard
boolean isKeyLocked;
char lockedKey;

void setup(){
  size(800, 500);
  textAlign(CENTER);
  fill(255);
  
  minim = new Minim(this);
  out = minim.getLineOut();
  oscil = new Oscil(440, 1.0, Waves.SQUARE);
  filter = new MoogFilter(10000, 0.0, MoogFilter.Type.LP);
  multiplier = new Multiplier(1.0);
  adsr = new ADSR(1.0, 1.0, 1.0, 0.5, 1.0);
  oscil.patch(filter).patch(multiplier).patch(adsr);
  vcoLfo = new Oscil(10, 0.0, Waves.SINE);
  vcoLfo.patch(oscil.frequency);
  vcfLfo = new Oscil(10, 0.0, Waves.SINE);
  vcfLfo.amplitude.setLastValue(0.0);
  vcfLfo.offset.setLastValue(10000);
  vcfLfo.patch(filter.frequency);
  vcaLfo = new Oscil(10, 0.0, Waves.SINE);
  vcaLfo.amplitude.setLastValue(0.0);
  vcaLfo.offset.setLastValue(1.0);
  vcaLfo.patch(multiplier.amplitude);
  
  isKeyLocked = false;
  
  cp5 = new ControlP5(this);
  float knobRadius = 25;
  PVector toggleSize = new PVector(40, 20);
  // GUI for VCO
  text("VCO", width / 4.0 * 1.0, height / 4.0 * 0.5);
  cp5.addToggle("setVcoWave")
     .setLabel("VCO WAVE")
     .setValue(true)
     .setPosition(width / 4.0 * 1.0 - toggleSize.x / 2.0, height / 4.0 * 1.0 - toggleSize.y / 2.0)
     .setSize(int(toggleSize.x), int(toggleSize.y))
     .setMode(ControlP5.SWITCH);
  cp5.addKnob("octave")
     .setLabel("OCTAVE")
     .setRange(2, 6)
     .setValue(4)
     .setPosition(width / 4.0 * 1.0 - knobRadius, height / 4.0 * 2.0 - knobRadius)
     .setRadius(knobRadius)
     .setNumberOfTickMarks(4)
     .setTickMarkLength(5)
     .snapToTickMarks(true); 
  // GUI for VCF
  text("VCF", width / 4.0 * 2.0, height / 4.0 * 0.5);
  cutoffFrequencyKnob = cp5.addKnob("setCutoffFrequency")
                           .setLabel("CUTOFF FREQUENCY")
                            .setRange(0, 10000)
                           .setValue(10000)
                           .setPosition(width / 4.0 * 2.0 - knobRadius, height / 4.0 * 1.0 - knobRadius)
                           .setRadius(knobRadius);
  cp5.addKnob("setResonance")
     .setLabel("RESONANCE")
     .setRange(0, 1)
     .setValue(0)
     .setPosition(width / 4.0 * 2.0 - knobRadius, height / 4.0 * 2.0 - knobRadius)
     .setRadius(knobRadius);
  // GUI for VCA + EG
  text("VCA + EG", width / 4.0 * 3.0, height / 7.0 * 0.5);
  amplitudeKnob = cp5.addKnob("setAmplitude")
                     .setLabel("AMPLITUDE")
                     .setRange(0, 1)
                     .setValue(0.5)
                     .setPosition(width / 4.0 * 3.0 - knobRadius, height / 7.0 * 1.0 - knobRadius)
                     .setRadius(knobRadius);
  attackKnob = cp5.addKnob("setAttack")
                  .setLabel("ATTACK")
                  .setRange(0.01, 5.0)
                  .setValue(1.0)
                  .setPosition(width / 4.0 * 3.0 - knobRadius, height / 7.0 * 2.0 - knobRadius)
                  .setRadius(knobRadius);
  sustainKnob = cp5.addKnob("setSustain")
                   .setLabel("SUSTAIN")
                   .setRange(0, 1.0)
                   .setValue(0.5)
                   .setPosition(width / 4.0 * 3.0 - knobRadius, height / 7.0 * 3.0 - knobRadius)
                   .setRadius(knobRadius); 
  decayReleaseKnob = cp5.addKnob("setDecayRelease")
                        .setLabel("DECAY / RELEASE")
                        .setRange(0.01, 5.0)
                        .setValue(1.0)
                        .setPosition(width / 4.0 * 3.0 - knobRadius, height / 7.0 * 4.0 - knobRadius)
                        .setRadius(knobRadius);  
  // GUI for LFO
  text("LFO", width / 2.0, height / 7.0 * 5.0);
  cp5.addToggle("setLfoWave")
     .setLabel("LFO WAVE")
     .setValue(true)
     .setPosition(width / 6.0 * 1.0 - toggleSize.x / 2.0, height / 7.0 * 6.0 - toggleSize.y / 2.0)
     .setSize(int(toggleSize.x), int(toggleSize.y))
     .setMode(ControlP5.SWITCH);
  cp5.addKnob("setLfoRate")
     .setLabel("RATE")
     .setRange(0, 100)
     .setValue(10)
     .setPosition(width / 6.0 * 2.0 - knobRadius, height / 7.0 * 6.0 - knobRadius)
     .setRadius(knobRadius);  
   cp5.addKnob("vcoLfoIntensity")
     .setLabel("VCO INTENSITY")
     .setRange(0.0, 1.0)
     .setValue(0.0)
     .setPosition(width / 6.0 * 3.0 - knobRadius, height / 7.0 * 6.0 - knobRadius)
     .setRadius(knobRadius);  
  vcfLfoIntensityKnob = cp5.addKnob("setVcfIntensity")
                           .setLabel("VCF INTENSiTY")
                           .setRange(0.0, 1.0)
                           .setValue(0.0)
                           .setPosition(width / 6.0 * 4.0 - knobRadius, height / 7.0 * 6.0 - knobRadius)
                           .setRadius(knobRadius);  
   vcaLfoIntensityKnob = cp5.addKnob("setVcaIntensity")
                            .setLabel("VCA INTENSITY")
                            .setRange(0.0, 1.0)
                            .setValue(0.0)
                            .setPosition(width / 6.0 * 5.0 - knobRadius, height / 7.0 * 6.0 - knobRadius)
                            .setRadius(knobRadius); 
}

// functions for VCO
void setVcoWave(boolean value){
  if(value){
    oscil.setWaveform(Waves.SQUARE);
  } else {
    oscil.setWaveform(Waves.SAW);  
  }
}
// functions for VCF
void setCutoffFrequency(float value){
  vcfLfo.amplitude.setLastValue(value / 2.0 * vcfLfoIntensityKnob.getValue());
  vcfLfo.offset.setLastValue(value);
}
void setResonance(float value){
  filter.resonance.setLastValue(value);
}
// function for VCA + EG
void setAmplitude(float value){
  vcaLfo.amplitude.setLastValue(value / 2.0 * vcaLfoIntensityKnob.getValue());
  vcaLfo.offset.setLastValue(value);
}
void setParameters(){
  adsr.setParameters(1.0, attackKnob.getValue(), decayReleaseKnob.getValue(), sustainKnob.getValue(), decayReleaseKnob.getValue(), 0.0, 0.0);
}
void setAttack(){
  setParameters();
}
void setSustain(){
  setParameters();
}
void setDecayRelease(){
  setParameters();
}
// functions for LFO
void setLfoWave(boolean value){
   if(value){
     vcoLfo.setWaveform(Waves.SINE);
     vcfLfo.setWaveform(Waves.SINE);
     vcaLfo.setWaveform(Waves.SINE);
   } else {
     vcoLfo.setWaveform(Waves.SQUARE); 
     vcfLfo.setWaveform(Waves.SQUARE); 
     vcaLfo.setWaveform(Waves.SQUARE); 
   }
}
void setLfoRate(float value){
  vcoLfo.setFrequency(value);
  vcfLfo.setFrequency(value);
  vcaLfo.setFrequency(value);
}
void setVcfIntensity(float value){
  vcfLfo.amplitude.setLastValue(cutoffFrequencyKnob.getValue() / 2.0 * value);
}
void setVcaIntensity(float value){
  vcaLfo.amplitude.setLastValue(amplitudeKnob.getValue() / 2.0 * value);
}

void keyPressed(){
  if(!isKeyLocked){
    String tone = "";
    switch(key){
    case 'z':
      tone += "C"; break;
    case 's':
      tone += "C#"; break;
    case 'x':
      tone += "D"; break;
    case 'd':
      tone += "D#"; break;
    case 'c':
      tone += "E"; break;
    case 'v':
      tone += "F"; break;
    case 'g':
      tone += "F#"; break;
    case 'b':
      tone += "G"; break;
    case 'h':
      tone += "G#"; break;
    case 'n':
      tone += "A"; break;
    case 'j':
      tone += "A#"; break;
    case 'm':
      tone += "B"; break;
    }
    if(tone != ""){
      vcoLfo.amplitude.setLastValue(Frequency.ofPitch(tone + octave).asHz() / 2.0 * vcoLfoIntensity);
      vcoLfo.offset.setLastValue(Frequency.ofPitch(tone + octave).asHz());
      adsr.unpatch(out);
      adsr.noteOn();
      adsr.patch(out);
      isKeyLocked = true;
      lockedKey = key;
    }
  }
}

void keyReleased(){
  if(isKeyLocked && key == lockedKey){
    adsr.unpatchAfterRelease(out);
    adsr.noteOff();
    isKeyLocked = false;
  }
}

void draw(){
//  background( 0 );
//  stroke( 255 );
//  for( int i = 0; i < out.bufferSize() - 1; i++ )
//  {
//    float x1  =  map( i, 0, out.bufferSize(), 0, width );
//    float x2  =  map( i+1, 0, out.bufferSize(), 0, width );
//    line( x1, 50 + out.left.get(i)*50, x2, 50 + out.left.get(i+1)*50);
//    line( x1, 150 + out.right.get(i)*50, x2, 150 + out.right.get(i+1)*50);
//  }  
}

変調の方法は、第5回「Processingでエフェクターをつくる」でトレモロを作ったときと同じです。 今回は、VCOでは音程を変調するためにoscil変数のfrequencyプロパティに、VCFではカットオフ周波数を変調するためにfilter変数のfrequencyプロパティに、VCOで音量を変調するためにmultiplier変数のamplitudeプロパティに、それぞれLFOをパッチしています。

この記事はProcessingで楽器をつくろうの第7回目です。