aa develop

開発と成長

Processingでサンプラーをつくる

前回はキーボードを鍵盤にしました。今回はMinimとControlP5を使って簡単なサンプラーを作りたいと思います。 サンプラーとは、いろいろな音を録音して、それを音源として利用する楽器です。 音を録音することをサンプリングと呼びます。

ファイルを再生する

サンプラーを作るにあたって、まずはMinimのSamplerクラスの使い方を確認しましょう。 次のプログラムでは、キック、スネア、ハイハットの3つのサウンドファイルをSamplerクラスを使って再生することができます。

f:id:aa_debdeb:20161112153432j: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;
Sampler kick, snare, hihat;

ControlP5 cp5;

void setup(){
  size(500, 300);
  minim = new Minim(this);
  out = minim.getLineOut();
  kick = new Sampler("kick.wav", 4, minim);
  kick.patch(out);
  snare = new Sampler("snare.wav", 4, minim);
  snare.patch(out);
  hihat = new Sampler("hihat.wav", 4, minim);
  hihat.patch(out);
  
  PVector bangSize = new PVector(40, 40);
  float knobRadius = 30;
  cp5 = new ControlP5(this);
  cp5.addBang("playKick")
     .setLabel("kick")
     .setPosition(width / 4.0 * 1.0 - bangSize.x / 2.0, height / 3.0 * 1.0 - bangSize.y / 2.0)
     .setSize(int(bangSize.x), int(bangSize.y));
  cp5.addBang("playSnare")
     .setLabel("snare")
     .setPosition(width / 4.0 * 2.0 - bangSize.x / 2.0, height / 3.0 * 1.0 - bangSize.y / 2.0)
     .setSize(int(bangSize.x), int(bangSize.y));
  cp5.addBang("playHihat")
     .setLabel("hihat")
     .setPosition(width / 4.0 * 3.0 - bangSize.x / 2.0, height / 3.0 * 1.0 - bangSize.y / 2.0)
     .setSize(int(bangSize.x), int(bangSize.y));
}

void draw(){

}

void playKick(){
  kick.trigger();
}

void playSnare(){
  snare.trigger();
}

void playHihat(){
  hihat.trigger();
}

このプログラムを実行して、ボタンを押すと対応する音源を鳴らすことができます。

Minimでサウンドファイルを再生する方法として、第1回目ではAudioPlayerクラスを紹介しました。 SamplerはAudioPlayerと比べて再生時間が短いファイルに適しており、再生後に巻き戻す必要もありません。

Samplerクラスのコンストラクタには第1引数にファイル名、第2引数にボイス数、第3引数にMinim型変数を渡します。 ボイス数とは、同時にサウンドファイルを再生できる数を表しています。 今回は「4」を指定しているので、最大4つ同じ音を重ねることができます。 音を再生するにはtriggerメソッドを利用します。

今回は、BangというGUIパーツを初めて利用しました。Bangは値を持たないGUIパーツで、押すとイベントとして関数を呼び出すことができます。

再生速度を制御する

サンプラーでは、サンプリングした音を加工することもできます。 次のプログラムは、ここまでのプログラムにサウンドファイルの再生速度の変更機能を追加したものです。 再生速度を遅くすると音が低くなり、再生速度を早くすると音が高くなることが確認できると思います。

f:id:aa_debdeb:20161112153445j: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;
Sampler kick, snare, hihat;

ControlP5 cp5;

void setup(){
  size(500, 300);
  minim = new Minim(this);
  out = minim.getLineOut();
  kick = new Sampler("kick.wav", 4, minim);
  kick.patch(out);
  snare = new Sampler("snare.wav", 4, minim);
  snare.patch(out);
  hihat = new Sampler("hihat.wav", 4, minim);
  hihat.patch(out);
  
  PVector bangSize = new PVector(40, 40);
  float knobRadius = 30;
  cp5 = new ControlP5(this);
  cp5.addBang("playKick")
     .setLabel("kick")
     .setPosition(width / 4.0 * 1.0 - bangSize.x / 2.0, height / 3.0 * 1.0 - bangSize.y / 2.0)
     .setSize(int(bangSize.x), int(bangSize.y));
  cp5.addBang("playSnare")
     .setLabel("snare")
     .setPosition(width / 4.0 * 2.0 - bangSize.x / 2.0, height / 3.0 * 1.0 - bangSize.y / 2.0)
     .setSize(int(bangSize.x), int(bangSize.y));
  cp5.addBang("playHihat")
     .setLabel("hihat")
     .setPosition(width / 4.0 * 3.0 - bangSize.x / 2.0, height / 3.0 * 1.0 - bangSize.y / 2.0)
     .setSize(int(bangSize.x), int(bangSize.y));
  cp5.addKnob("changeKickRate")
     .setLabel("kick rate")
     .setRange(-2.0, 2.0)
     .setValue(0.0)
     .setPosition(width / 4.0 * 1.0 - knobRadius, height / 3.0 * 2.0 - knobRadius)
     .setRadius(knobRadius);
  cp5.addKnob("changeSnareRate")
     .setLabel("snare rate")
     .setRange(-2.0, 2.0)
     .setValue(0.0)
     .setPosition(width / 4.0 * 2.0 - knobRadius, height / 3.0 * 2.0 - knobRadius)
     .setRadius(knobRadius);
  cp5.addKnob("changeHihatRate")
     .setLabel("hihat rate")
     .setRange(-2.0, 2.0)
     .setValue(0.0)
     .setPosition(width / 4.0 * 3.0 - knobRadius, height / 3.0 * 2.0 - knobRadius)
     .setRadius(knobRadius);
}

void draw(){

}

void playKick(){
  kick.trigger();
}

void playSnare(){
  snare.trigger();
}

void playHihat(){
  hihat.trigger();
}

void changeKickRate(float value){
  kick.rate.setLastValue(pow(10.0, value));
}

void changeSnareRate(float value){
  snare.rate.setLastValue(pow(10.0, value));
}

void changeHihatRate(float value){
  hihat.rate.setLastValue(pow(10.0, value));
}

再生速度を指定するために、各音源に対してノブを追加しました。 再生速度はSamplerインスタンスのrateプロパティにより設定されています。 このrateプロパティのsetLastValueメソッドに引数として再生速度を渡すことで、再生速度を変更することができます。 ノブの値は線形的に変化しますが、pow関数を使って再生速度が指数的に変化するようにしました。 ノブの値が-2から2までなので、0.01倍から100倍まで再生速度を変更することができます。 ノブの値が0のときが1倍速です。

マイク入力をサンプリングする

ここまでは、すでに用意してあるサウンドファイルを利用してきました。 次は音を実際にサンプリングしてみましょう。

次のプログラムではマイク入力の音を録音して、それをSamplerクラスで再生します。 Recordボタンを一度押すと録音を開始し、もう一度Recordボタンを押すと録音を終了します。 録音した音源はPlayボタンを押すと再生することができます。

f:id:aa_debdeb:20161112153520p: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;
AudioInput in;
AudioOutput out;
AudioRecorder recorder;
Sampler sampler;

ControlP5 cp5;

void setup(){
  size(500, 200);
  minim = new Minim(this);
  in = minim.getLineIn();
  out = minim.getLineOut();
  
  cp5 = new ControlP5(this);
  PVector bangSize = new PVector(40, 40);
  float knobRadius = 40;
  cp5.addBang("record")
     .setPosition(width / 4.0 * 1.0 - bangSize.x / 2.0, height / 2.0 - bangSize.y / 2.0)
     .setSize(int(bangSize.x), int(bangSize.y));
  cp5.addBang("play")
     .setPosition(width / 4.0 * 2.0 - bangSize.x / 2.0, height / 2.0 - bangSize.y / 2.0)
     .setSize(int(bangSize.x), int(bangSize.y));
}

void record(){
  if(recorder != null && recorder.isRecording()){
    recorder.endRecord();
    recorder.save();
    sampler = new Sampler("sample.wav", 4, minim);
    sampler.patch(out);
  } else {
    recorder = minim.createRecorder(in, "sample.wav");
    recorder.beginRecord();
  }
}

void play(){
  if(sampler != null){
    sampler.trigger();
  }
}

void draw(){
  if(recorder != null && recorder.isRecording()){
    background(255, 128, 128);
  } else {
    background(200);
  }
}

マイク入力音を録音するのに、AudioRecorderクラスを利用しています。 最初にRecordボタンを押すと、createRecorderメソッドでAudioRecorderインスタンスを作成し、beginRecordメソッドで録音を開始します。 もう一度、Reordボタンを押すとisRecording()で録音中だと判定され、endRecord()で録音を終了します。 その後、saveメソッドで録音した音源が「sample.wav」ファイルに保存されます。 この保存したファイルをもとにSamplerインスタンスを作成し、Playボタンが押されたら音が鳴るようにしています。

サンプリングした音の再生速度を制御する

最後にこれまでのプログラムを組み合わせて、録音した音の再生速度を変更できるようにしてみました。

f:id:aa_debdeb:20161112153530p:plain

/*
* 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;
AudioInput in;
AudioOutput out;
AudioRecorder recorder;
Sampler sampler;

ControlP5 cp5;

void setup(){
  size(500, 200);
  minim = new Minim(this);
  in = minim.getLineIn();
  out = minim.getLineOut();
  
  cp5 = new ControlP5(this);
  PVector bangSize = new PVector(40, 40);
  float knobRadius = 40;
  cp5.addBang("record")
     .setPosition(width / 4.0 * 1.0 - bangSize.x / 2.0, height / 2.0 - bangSize.y / 2.0)
     .setSize(int(bangSize.x), int(bangSize.y));
  cp5.addBang("play")
     .setPosition(width / 4.0 * 2.0 - bangSize.x / 2.0, height / 2.0 - bangSize.y / 2.0)
     .setSize(int(bangSize.x), int(bangSize.y));
  cp5.addKnob("changeRate")
     .setLabel("rate")
     .setRange(-1.0, 1.0)
     .setValue(0.0)
     .setPosition(width / 4.0 * 3.0 - knobRadius, height / 2.0 - knobRadius)
     .setRadius(knobRadius);
}

void record(){
  if(recorder != null && recorder.isRecording()){
    recorder.endRecord();
    recorder.save();
    sampler = new Sampler("sample.wav", 4, minim);
    sampler.patch(out);
  } else {
    recorder = minim.createRecorder(in, "sample.wav");
    recorder.beginRecord();
  }
}

void play(){
  if(sampler != null){
    sampler.trigger();
  }
}

void changeRate(float value){
  if(sampler != null){
    sampler.rate.setLastValue(pow(10.0, value));
  }
}

void draw(){
  if(recorder != null && recorder.isRecording()){
    background(255, 128, 128);
  } else {
    background(200);
  }
}

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