p5.jsでシンセサイザーを作る 第20話 フィルター
Javascriptとp5.jsを使って、オリジナルなシンセサイザーを作るプログラミングの記事です。とりあえず何を作るのかを手っ取り早くお伝えしたいので、第0話で公開している完成品もチェックしてみてください。
今回使うコード
前回の続きです。このコードではキーボードを使って演奏ができる。それから、ADSRによるエンベロープができる様になっています。
ここにローパスフィルターを追加していきます。
let keySize = 50; // キーのサイズ
let keyTop = keySize / 1.4; // キートップのサイズ
let topSize = 7; // キートップの位置を中央に寄せる幅
let keyX = 50; // キーの位置X
let keyY = 150; // キーの位置Y
let keyInterval = 50;
let keyStat = [false, false, false, false, false, false, false, false]; // 白鍵の判定
let keyStatS = [false, false, false, false, false, false]; // 黒鍵の判定
let strokeValue = 2; // 枠線の太さ
let osc;
let env;
let decay = 0.50;
let baseFreq = 0;
let freqKey = [130.813,146.832,164.814,174.614,195.998,220.000,246.942,261.626];
let freqKeyS = [138.591,155.563,0,184.997,207.652,233.082];
let keyboard = ["s","d","f","g","h","j","k","l"]; //白鍵のキー
let keyboardS = ["e","r","t","y","u","i"]; //黒鍵のキー
let noteCount = 0;
let noteStartValue = 30;
let isPlaying = false;
function setup() {
createCanvas(500, 400);
osc = new p5.Oscillator('sawtooth'); // ハコの中にノコギリ波を入れる
env = new p5.Envelope(); //ここはエンベロープを入れるハコ
}
function draw() {
background(250); //背景の色
drawHousing();
drawInterface();
if (isPlaying == true){
noteCount--
if(noteCount < 0){
noteOff();
}
}
}
function drawHousing(){
stroke(150, 100, 100);
strokeWeight(strokeValue);
////////ここから白鍵////////////////
fill(230, 230, 230); // キーのベース
for(let i = 0; i < 8; i++){
rect(keyX + keyInterval * i, keyY, keySize, keySize);
}
fill(100, 100, 100); // キーの影部分の色
for(let i = 0; i < 8; i++){
triangle(
keyX + keyInterval * i, keyY, //左上
keyX + keyInterval * i, keyY + keySize, //左下
keyX + keyInterval * i+ keySize, keyY + keySize //右下
);
}
fill(250, 250, 250) // キーのトップ部分
for(let i = 0; i < 8; i++){
rect(keyX + keyInterval * i + topSize, keyY + topSize, keyTop, keyTop);
}
////////ここから黒鍵////////////////
fill(230, 230, 230); // キーのベース
for(let i = 0; i < 6; i++){
if(i != 2){
rect(keyX + keyInterval * i + keySize / 2, keyY - keySize, keySize, keySize);
}
}
fill(100, 100, 100); // キーの影部分の色
for(let i = 0; i < 6; i++){
if(i != 2){
triangle(
keyX + keyInterval * i + keySize / 2, keyY - keySize, //左上
keyX + keyInterval * i + keySize / 2, keyY, //左下
keyX + keyInterval * i + keySize / 2 + keySize, keyY //右下
);
}
}
fill(250, 250, 250) // キーのトップ部分
for(let i = 0; i < 6; i++){
if(i != 2){
rect(keyX + keyInterval * i + topSize + keySize / 2, keyY- keySize + topSize, keyTop, keyTop);
}
}
}
function drawInterface(){
for(let i = 0; i < 8; i++){
if (keyStat[i] == true){ // オレンジ色で白鍵のキートップを塗り替える
fill(255, 125, 0);
rect(keyX + keyInterval * i + topSize, keyY + topSize, keyTop, keyTop);
}
}
for(let i = 0; i < 6; i++){
if (keyStatS[i] == true){ // オレンジ色で白鍵のキートップを塗り替える
fill(255, 125, 0);
rect(keyX + keyInterval * i + topSize + keySize / 2, keyY- keySize + topSize, keyTop, keyTop);
}
}
}
function noteOn(){
for (let i = 0; i < 8; i++) {
if (keyStat[i] == true){ //キーが押されたら
env.setADSR(0.01,decay,0.0,0.0); // ADSRの値を入れておく
osc.freq(baseFreq); //oscのハコに周波数を入れる
noteCount = noteStartValue;
isPlaying = true;
osc.start(); //オシレーターを発音する
env.play(osc); //発音したオシレータを制御する
}
}
for (let i = 0; i < 6; i++) {
if (keyStatS[i] == true){ //キーが押されたら
env.setADSR(0.01,decay,0.0,0.0); // ADSRの値を入れておく
osc.freq(baseFreq); //oscのハコに周波数を入れる
noteCount = noteStartValue;
isPlaying = true;
osc.start(); //キーが押されたらオシレーターを発音する
env.play(osc); //発音したオシレータを制御する
}
}
}
function noteOff(){
osc.stop();
}
function keyPressed(){
for (let i = 0; i < 8; i++){ //白鍵の処理
if (key == keyboard[i]){
baseFreq = freqKey[i];
keyStat[i] = true;
noteOn();
}
}
for (let i = 0; i < 6; i++){ //黒鍵の処理
if (key == keyboardS[i]){
baseFreq = freqKeyS[i];
keyStatS[i] = true;
noteOn();
}
}
}
function keyReleased() {
for (let i = 0; i < 8; i++) {
if(key == keyboard[i]){
keyStat[i] = false;
}
}
for (let i = 0; i < 6; i++) {
if(key == keyboardS[i]){
keyStatS[i] = false;
}
}
}
ローパスフィルターの実装
ここにローパスフィルターを実装していきます。
まずはリファレンスをチェック
かなり細かいことが書かれているので、この説明を元に実装を始めます。
全部説明するとだいぶ長くなるので、実装優先で記述していきます。
①宣言の部
変数の宣言には、フィルターの部品を入れるfil
それに、カットオフとレゾナンスを指定する変数のcutOffValueとresValueを宣言しました。
let fil;
let cutOffValue = 1550.8;
let resValue = 21.9;
②初期化の部
ここではオシレーターとエンベロープの後にフィルターの部品を入れ込みます。
function setup() {
createCanvas(500, 400);
osc = new p5.Oscillator('sawtooth'); // ハコの中にノコギリ波を入れる
env = new p5.Envelope(); //ここはエンベロープを入れるハコ
fil = new p5.LowPass(); //ここはローパスフィルターを入れるハコ
}
ここで、filの中にローパスフィルターの部品を入れ込んでいきます。
③実行の部
function draw() {
background(250); //背景の色
drawHousing();
drawInterface();
if (isPlaying == true){
noteCount--
if(noteCount < 0){
noteOff();
}
}
}
特に変更はありません。
④動作の部
noteOn()の中に、フィルターのカットオフとレゾナンスを設定する値を入れ込みます。
コードはこうなります。
function noteOn(){
for (let i = 0; i < 8; i++) {
if (keyStat[i] == true){ //キーが押されたら
env.setADSR(0.01,decay,0.0,0.0); // ADSRの値を入れておく
fil.set(cutOffValue,resValue);//カットオフとレゾナンスを設定
osc.connect(fil);//オシレータにフィルターを接続
osc.freq(baseFreq); //oscのハコに周波数を入れる
noteCount = noteStartValue;
isPlaying = true;
osc.start(); //オシレーターを発音する
env.play(osc); //発音したオシレータを制御する
}
}
for (let i = 0; i < 6; i++) {
if (keyStatS[i] == true){ //キーが押されたら
env.setADSR(0.01,decay,0.0,0.0); // ADSRの値を入れておく
fil.set(cutOffValue,resValue);//カットオフとレゾナンスを設定
osc.connect(fil);//オシレータにフィルターを接続
osc.freq(baseFreq); //oscのハコに周波数を入れる
noteCount = noteStartValue;
isPlaying = true;
osc.start(); //キーが押されたらオシレーターを発音する
env.play(osc); //発音したオシレータを制御する
}
}
}
前回の変更点ですが、noteOn()の中に以下の2行を付け加えているだけです。
fil.set(cutOffValue,resValue);
osc.connect(fil);
フィルターの部品であるfilに対してset()関数を用いてcutOffValue,resValueを代入しました。
オシレータの部品であるoscに対してconnect()関数を用いて、オシレータにフィルターを接続しました。
実行結果
コードと実行画面を切り取っています。
cutOffValueとresValueの値を変えることによってフィルターによる音の変化が反映されています。
この記事のコード全文
let keySize = 50; // キーのサイズ
let keyTop = keySize / 1.4; // キートップのサイズ
let topSize = 7; // キートップの位置を中央に寄せる幅
let keyX = 50; // キーの位置X
let keyY = 150; // キーの位置Y
let keyInterval = 50;
let keyStat = [false, false, false, false, false, false, false, false]; // 白鍵の判定
let keyStatS = [false, false, false, false, false, false]; // 黒鍵の判定
let strokeValue = 2; // 枠線の太さ
let osc;
let env;
let decay = 0.50;
let fil;
let cutOffValue = 1550.8;
let resValue = 21.9;
let baseFreq = 0;
let freqKey = [130.813,146.832,164.814,174.614,195.998,220.000,246.942,261.626];
let freqKeyS = [138.591,155.563,0,184.997,207.652,233.082];
let keyboard = ["s","d","f","g","h","j","k","l"]; //白鍵のキー
let keyboardS = ["e","r","t","y","u","i"]; //黒鍵のキー
let noteCount = 0;
let noteStartValue = 30;
let isPlaying = false;
function setup() {
createCanvas(500, 400);
osc = new p5.Oscillator('sawtooth'); // ハコの中にノコギリ波を入れる
env = new p5.Envelope(); //ここはエンベロープを入れるハコ
fil = new p5.LowPass(); //ここはローパスフィルターを入れるハコ
}
function draw() {
background(250); //背景の色
drawHousing();
drawInterface();
if (isPlaying == true){
noteCount--
if(noteCount < 0){
noteOff();
}
}
}
function drawHousing(){
stroke(150, 100, 100);
strokeWeight(strokeValue);
////////ここから白鍵////////////////
fill(230, 230, 230); // キーのベース
for(let i = 0; i < 8; i++){
rect(keyX + keyInterval * i, keyY, keySize, keySize);
}
fill(100, 100, 100); // キーの影部分の色
for(let i = 0; i < 8; i++){
triangle(
keyX + keyInterval * i, keyY, //左上
keyX + keyInterval * i, keyY + keySize, //左下
keyX + keyInterval * i+ keySize, keyY + keySize //右下
);
}
fill(250, 250, 250) // キーのトップ部分
for(let i = 0; i < 8; i++){
rect(keyX + keyInterval * i + topSize, keyY + topSize, keyTop, keyTop);
}
////////ここから黒鍵////////////////
fill(230, 230, 230); // キーのベース
for(let i = 0; i < 6; i++){
if(i != 2){
rect(keyX + keyInterval * i + keySize / 2, keyY - keySize, keySize, keySize);
}
}
fill(100, 100, 100); // キーの影部分の色
for(let i = 0; i < 6; i++){
if(i != 2){
triangle(
keyX + keyInterval * i + keySize / 2, keyY - keySize, //左上
keyX + keyInterval * i + keySize / 2, keyY, //左下
keyX + keyInterval * i + keySize / 2 + keySize, keyY //右下
);
}
}
fill(250, 250, 250) // キーのトップ部分
for(let i = 0; i < 6; i++){
if(i != 2){
rect(keyX + keyInterval * i + topSize + keySize / 2, keyY- keySize + topSize, keyTop, keyTop);
}
}
}
function drawInterface(){
for(let i = 0; i < 8; i++){
if (keyStat[i] == true){ // オレンジ色で白鍵のキートップを塗り替える
fill(255, 125, 0);
rect(keyX + keyInterval * i + topSize, keyY + topSize, keyTop, keyTop);
}
}
for(let i = 0; i < 6; i++){
if (keyStatS[i] == true){ // オレンジ色で白鍵のキートップを塗り替える
fill(255, 125, 0);
rect(keyX + keyInterval * i + topSize + keySize / 2, keyY- keySize + topSize, keyTop, keyTop);
}
}
}
function noteOn(){
for (let i = 0; i < 8; i++) {
if (keyStat[i] == true){ //キーが押されたら
env.setADSR(0.01,decay,0.0,0.0); // ADSRの値を入れておく
fil.set(cutOffValue,resValue);//カットオフとレゾナンスを設定
osc.connect(fil);//オシレータにフィルターを接続
osc.freq(baseFreq); //oscのハコに周波数を入れる
noteCount = noteStartValue;
isPlaying = true;
osc.start(); //オシレーターを発音する
env.play(osc); //発音したオシレータを制御する
}
}
for (let i = 0; i < 6; i++) {
if (keyStatS[i] == true){ //キーが押されたら
env.setADSR(0.01,decay,0.0,0.0); // ADSRの値を入れておく
fil.set(cutOffValue,resValue);//カットオフとレゾナンスを設定
osc.connect(fil);//オシレータにフィルターを接続
osc.freq(baseFreq); //oscのハコに周波数を入れる
noteCount = noteStartValue;
isPlaying = true;
osc.start(); //キーが押されたらオシレーターを発音する
env.play(osc); //発音したオシレータを制御する
}
}
}
function noteOff(){
osc.stop();
}
function keyPressed(){
for (let i = 0; i < 8; i++){ //白鍵の処理
if (key == keyboard[i]){
baseFreq = freqKey[i];
keyStat[i] = true;
noteOn();
}
}
for (let i = 0; i < 6; i++){ //黒鍵の処理
if (key == keyboardS[i]){
baseFreq = freqKeyS[i];
keyStatS[i] = true;
noteOn();
}
}
}
function keyReleased() {
for (let i = 0; i < 8; i++) {
if(key == keyboard[i]){
keyStat[i] = false;
}
}
for (let i = 0; i < 6; i++) {
if(key == keyboardS[i]){
keyStatS[i] = false;
}
}
}
実際にコピペして試してみてください!
カットオフやレゾナンスの変数を書き換えた時の音の変化もチェックしてみてください。
ここまでで、オシレータ、エンベロープ、フィルターという3つのパーツが揃いました。
しかし、すべての値はコードを書き換えることでパラメータを変化させています。
本来の楽器としてはツマミ(ノブ)とかボタンなどのインターフェースによってリアルタイムに変化させたいものです。
その辺り、また記事を作っていこうかと思います。