見出し画像

p5.jsでシンセサイザーを作る 第17話 p5.soundで音階をつける方法

Javascriptとp5.jsを使って、オリジナルなシンセサイザーを作るプログラミングの記事です。とりあえず何を作るのかを手っ取り早くお伝えしたいので、第0話で公開している完成品もチェックしてみてください。

p5.soundで音階を奏でる

p5.soundを使って最低限、音が出ることは確認できました。今回はドレミファソラシドーと言う音階をつけるようにしていきます。

  • キーを配置する

  • 押されたキーに応じて音階を設定する

  • 発音する

このような流れになります。

この辺りから、注意事項なのですが、この記事は解説とまではいかない日記です。
第0話で完成品と謳っているコードと根本的にアプローチが異なる記述がある部分もあります。
あくまで、部分的な実装の一例として記事を書いていこいうと思います。
参考になる部分があるかもしれませんが、正しいアプローチとかコーディングの質についてはご容赦ください。。。。。

キーを配置する

第13話で制作したキーを配置していきます。

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; // 枠線の太さ

function setup() {
  createCanvas(500, 400);
}

function draw() {
  background(250); //背景の色
  drawHousing();
  drawInterface(); 
}

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 touchStarted() {
  for (let i = 0; i < 8; i++) {
    if (
      mouseX > keyX + keySize * i &&
      mouseX < keyX + keySize * i + keySize &&
      mouseY > keyY&&
      mouseY < keyY + keySize
    ) {
      keyStat[i] = true;
    }
  }
  for (let i = 0; i < 6; i++) {
    if (
      mouseX > keyX + keySize / 2 + keySize * i &&
      mouseX < keyX + keySize / 2 + keySize * i + keySize &&
      mouseY > keyY - keySize &&
      mouseY < keyY
    ) {
      if(i != 2){
        keyStatS[i] = true;
      }
    }
  }
}

function touchEnded() {
  for (let i = 0; i < 8; i++) {
    keyStat[i] = false;
  }
  for (let i = 0; i < 6; i++) {
    keyStatS[i] = false;
  }
}

マウスクリックでそれぞれのキーが反応するところまでは問題ありません。
ここからは、p5.soundを使ったオシレータが押されたキーに対応して音階を奏でるようにしていきます。

①宣言の部

力技でいきます。私のシンセはせいぜい1オクターブしかありませんので、押されたキーに対応する音階を格納する配列を最初につくってしまおうと思います。

let osc;
let baseFreq = 0;
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];

音階は周波数(Hz)で指定します。

ソフトシンセを作る過程で色々勉強していたのですが、結局音階というのは周波数で定義されているようです。そしてp5.soundのオシレーターもこの周波数であるHzの単位で指定するようになっています。
ということで、1オクターブを表現する白鍵と黒鍵がそれぞれ担当する周波数Hzを全て配列の中に入れてしまうことにしました。

freqKeyは白鍵の周波数、freqKeySは黒鍵の周波数です。
取りあえずこれでいきます。実装と動作が最優先!

①初期化の部

function setup() {
  createCanvas(500, 400);
  osc = new p5.Oscillator('sawtooth'); // ハコの中にノコギリ波を入れる
}

オシレーターの部品を設定します。今回は聞き取りやすいようにoscのハコの中にはノコギリ波を入れておきました。サイン波よりもはっきりと音階がわかります。

③実行の部

function draw() {
  background(250); //背景の色
  drawHousing();
  drawInterface();  
}

ここも、後述するnoteOn()を呼び出しするだけなので、特に変更はありません。

④動作の部

この部分では、まず、キーが押された時と離された時の処理を先に説明します。

キーのクリックを検出する。クリックの終了を検出する部分

function touchStarted() {
  for (let i = 0; i < 8; i++) {
    if (
      mouseX > keyX + keySize * i &&
      mouseX < keyX + keySize * i + keySize &&
      mouseY > keyY&&
      mouseY < keyY + keySize
    ) {
      baseFreq = freqKey[i];
      keyStat[i] = true;
      noteOn();
    }
  }
  for (let i = 0; i < 6; i++) {
    if (
      mouseX > keyX + keySize / 2 + keySize * i &&
      mouseX < keyX + keySize / 2 + keySize * i + keySize &&
      mouseY > keyY - keySize &&
      mouseY < keyY
    ) {
      if(i != 2){
        baseFreq = freqKeyS[i];
        keyStatS[i] = true;
        noteOn();
      }
    }
  }
}

function touchEnded() {
  for (let i = 0; i < 8; i++) {
    keyStat[i] = false;
    noteOff();
  }
  for (let i = 0; i < 6; i++) {
    keyStatS[i] = false;
    noteOff();
  }
}

touchStarted()では、白鍵と黒鍵に分けて、それぞれ当たり判定を行い、マウスによってクリックされたキーを判別します。ここでは、for文の繰り返しによって押されたキーの変数配列であるkeyStat[]とkeyStatS[]のうち、いずれかがtrueになるようにしています。
もし、いずれかの配列のうち、trueが検出された場合は、baseFreqにfreqKey[]かfreqKeyS[]で設定した周波数を代入します。
次にnoteOn()を呼び出し、音を鳴らすと言う流れになっています。

touchEnded()では、白鍵と黒鍵に分けて、それぞれ離されたキーを検出しています。それぞれ、クリックが終了したキーをfor文によって検出してkeyStat[]とkeyStatS[]の該当要素をfalseに変えます。
次にnoteOff()によって発音を止めます。

発音開始と発音停止の処理

function noteOn(){
  for (let i = 0; i < 8; i++) {
    if (keyStat[i] == true){
      osc.freq(baseFreq);
      osc.start(); //キーが押されたらオシレーターを発音する
    }
  }
  for (let i = 0; i < 6; i++) {
    if (keyStatS[i] == true){
      osc.freq(baseFreq);
      osc.start(); //キーが押されたらオシレーターを発音する
    }
  }
}

function noteOff(){
  for (let i = 0; i < 8; i++) {
    if (keyStat[i] == false){
      osc.stop(); //キーが離されたらオシレータを止める
    }
  }
}

noteOn()は白鍵と黒鍵のうちtrueになっているキーのステータスを検出しています。
trueになっているステータスが見つかった場合は、oscのハコの中にある周波数を設定するパラメータである”freq”にbaseFreqの値を入れます。
次に、周波数が設定されたオシレータoscをstartによって発音開始しています。

実行してみましょう!

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 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];


function setup() {
  createCanvas(500, 400);
  osc = new p5.Oscillator('sawtooth'); // ハコの中にノコギリ波を入れる
}

function draw() {
  background(250); //背景の色
  drawHousing();
  drawInterface();  
}

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){ //キーが押されたら
      osc.freq(baseFreq); //oscのハコに周波数を入れる
      osc.start(); //オシレーターを発音する
    }
  }
  for (let i = 0; i < 6; i++) {
    if (keyStatS[i] == true){ //キーが押されたら
      osc.freq(baseFreq); //oscのハコに周波数を入れる
      osc.start(); //キーが押されたらオシレーターを発音する
    }
  }
}

function noteOff(){
  for (let i = 0; i < 8; i++) {
    if (keyStat[i] == false){
      osc.stop(); //キーが離されたらオシレータを止める
    }
  }
  for (let i = 0; i < 6; i++) {
    if (keyStat[i] == false){
      osc.stop(); //キーが離されたらオシレータを止める
    }
  }
}

function touchStarted() {
  for (let i = 0; i < 8; i++) {
    if (
      mouseX > keyX + keySize * i &&
      mouseX < keyX + keySize * i + keySize &&
      mouseY > keyY&&
      mouseY < keyY + keySize
    ) {
      baseFreq = freqKey[i];
      keyStat[i] = true;
      noteOn();
    }
  }
  for (let i = 0; i < 6; i++) {
    if (
      mouseX > keyX + keySize / 2 + keySize * i &&
      mouseX < keyX + keySize / 2 + keySize * i + keySize &&
      mouseY > keyY - keySize &&
      mouseY < keyY
    ) {
      if(i != 2){
        baseFreq = freqKeyS[i];
        keyStatS[i] = true;
        noteOn();
      }
    }
  }
}

function touchEnded() {
  for (let i = 0; i < 8; i++) {
    keyStat[i] = false;
    noteOff();
  }
  for (let i = 0; i < 6; i++) {
    keyStatS[i] = false;
    noteOff();
  }
}

実行結果(ちょっとうるさいので音量注意)

全文をコピペして試してみてください。

楽器らしく音階が出てくるようになりました。ただし、やはりマウスクリックでの演奏というのは無理があります。PCのキーボードを使って発音するくらいの機能は付けておきたいですね。

いいなと思ったら応援しよう!

この記事が参加している募集