見出し画像

p5.jsでシンセサイザーを作る 第11話 関数を作ってコードを整理する

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

前回までに、ノブ、キー、ボタンなどのパーツ描画に関わるアイデア、それから、p5.jsの基本的なアプローチ方法について記載してきました。ここからは作ったパーツを実際に配置することで、より本格的なプログラミングに進んでいきます。

すみません

この記事からノブを複数並べる実装について一気に書こうと思ったのですが、全体の構造をより深く考察するほうが、応用的になると思いまして、ノブの配置は、またもや次回以降にさせて下さい。。

ノブを複数並べる前の準備

パーツの配置については、まず、ノブの配置を優先としたいと思います。シンセサイザではとても重要なインターフェースであり、プログラミングの分野においても、回転や当たり判定など、有用な出来事が多いと思ったからです。まずは単一のノブを描画するところから、複数のノブを実装する流れを丁寧に記載していきたいと思います。

p5.jsのプログラム実行の流れ

前回の記事で記載したように、p5.jsのプログラムを4つのパートに分けて解説を整理していきます。各パートの構成を再掲します。

①から④を上手につかいたいです

ノブを単体で描画するコード

let knobX = 100;
let knobY = 100;
let knobSize = 50;
let strokeValue = 2;

let knobStat = false;
let mousePosY;
let angle = -1.0;
let speed = 0;
let angleMax = 0.8;
let angleMin = -3.95;

const radius = knobSize/2-6;

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

function draw() {
  
  background(250); //背景の色
  
  noStroke(); // 枠線を消す
  fill(100, 100, 100); //
  circle(knobX - 3, knobY + 3 ,knobSize); //影の描画
  
  stroke(150, 100, 100); //
  strokeWeight(strokeValue); // 枠線の太さ
  
  fill(200, 200, 200); //
  circle(knobX, knobY ,knobSize); // ノブのベース
  
  fill(250, 250, 250); //
  circle(knobX, knobY, knobSize / 2); // ノブのトップ
  
  if (knobStat == true){ // マウスクリックでオレンジ色でノブトップを塗り替える
    mousePosY = mouseY; /// <---ここを追加します
    fill(255, 125, 0);
    circle(knobX, knobY, knobSize / 2);
  }
  
  knobDraw() 
}


function knobDraw(){
  const sinValue = sin(angle);
  const cosValue = cos(angle);

  const knobVX = knobX + cosValue * radius;
  const knobVY = knobY + sinValue * radius;
  
  fill(255, 0, 0);
  circle(knobVX, knobVY, 10);
}

function touchStarted() {
  if (
    mouseX > knobX - knobSize/2 &&
    mouseX < knobX + knobSize/2 &&
    mouseY > knobY - knobSize/2 &&
    mouseY < knobY + knobSize/2
  ) {
    knobStat = true;
    mousePosY = mouseY;
  }
}

function touchMoved() {
  if(knobStat == true){
    if (angle <= angleMax && angle >= angleMin){
      speed = (mousePosY - mouseY)/30;
      angle += speed;
    }
    if (angle >= angleMax) {
      angle = angleMax;
    }
    if (angle <= angleMin) {
      angle = angleMin;
    }
  } 
}

function touchEnded() {
  knobStat = false;
}

①宣言の部

ノブの動作に必要な変数を定義していきます。必要な変数は以下のタイプに分類して見ます。

  • 座標を指定する変数(数値)

  • ノブの角度を指定する変数(数値)

  • 当たり判定を反映する変数(true or false)

let knobX = 100;
let knobY = 100;
let knobSize = 50;
let strokeValue = 2;
let mousePosY;

let angle = -1.0;
let speed = 0;
let angleMax = 0.8;
let angleMin = -3.95;
const radius = knobSize/2-6;

let knobStat = false;

②初期化の部

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

この部分はこれだけなので変更はありません。

③実行の部

この部分はなるべく④で定義される関数をひたすら呼び出すように記述したいと思います。

function draw() {
  
  background(250); //背景の色
  
  noStroke(); // 枠線を消す
  fill(100, 100, 100); //
  circle(knobX - 3, knobY + 3 ,knobSize); //影の描画
  
  stroke(150, 100, 100); //
  strokeWeight(strokeValue); // 枠線の太さ
  
  fill(200, 200, 200); //
  circle(knobX, knobY ,knobSize); // ノブのベース
  
  fill(250, 250, 250); //
  circle(knobX, knobY, knobSize / 2); // ノブのトップ
  
  if (knobStat == true){ // マウスクリックでオレンジ色でノブトップを塗り替える
    mousePosY = mouseY; /// <---ここを追加します
    fill(255, 125, 0);
    circle(knobX, knobY, knobSize / 2);
  }
  
  knobDraw();
}

③実行の部にある処理を関数にする

③の中にある処理は、なるべく関数化して、draw()の外に出してやりたいと思います。これは、このあと複雑な処理を加筆修正するのを容易にするためです。
定型処理はなるべく関数にしようというアプローチです。③の中身は至極単純なコードにしておいて④の中で細かい処理を記述したい。というのが目的です。

関数化できそうな部分を洗い出す。

赤線で囲った処理のうち、上二つは定型的な処理にできそうです。
最後のknobDraw()は、以前にあげた記事の中ですでに関数として作成済みなので変更しません。

関数を作って定型的な処置は③の外に追い出す

では上記の画像で”そもそも動かない部分”と書いてある部分を関数にします。
④動作の部にこのように記述しました。

function drawHousing(){
  noStroke(); // 枠線を消す
  fill(100, 100, 100); //
  circle(knobX - 3, knobY + 3 ,knobSize); //影の描画
  
  stroke(150, 100, 100); //
  strokeWeight(strokeValue); // 枠線の太さ
  
  fill(200, 200, 200); //
  circle(knobX, knobY ,knobSize); // ノブのベース
  
  fill(250, 250, 250); //
  circle(knobX, knobY, knobSize / 2); // ノブのトップ
}

ここでは
function drawHousing(){}
と書かれた{}の中に、定型化したいコードを入れただけです。これでOKです。

次に、クリックされたら反応する部分を同じように関数にしてdraw();の外に追い出して見たいと思います。
④の部分にはこのように記述します。

function drawKnobTop(){
  if (knobStat == true){ // マウスクリックでオレンジ色でノブトップを塗り替える
    mousePosY = mouseY; /// <---ここを追加します
    fill(255, 125, 0);
    circle(knobX, knobY, knobSize / 2);
  }
}

関数を呼び出す

これで

  • drawHousing() シンセの筐体を描画する

  • drawKnobTop() ノブのトップ部分を点灯させる

  • knobDraw() ノブの目盛りと回転を反映させる

という3つの関数に処理をまとめて見ました。
ここまでくると③実行の部はとても読みやすいコードになります。

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

③が綺麗になりました。

関数というと難しく思えますが、④で関数を定義すると③の部では呼び出すだけで動作してくれますね。私の場合は、これを、draw()の中で”ショートカットキーを押している感覚”に似ているなと思いました。

この関数化して③の外に処理を追いやるというのはとても大事だと思います。これによって、③は定義された関数をひたすら呼び出すだけ。
という記述に特化できます。
一方④処理の部はそれぞれの関数を個別に作り込む事ができるようになります。

動作の部

function knobDraw(){
  const sinValue = sin(angle);
  const cosValue = cos(angle);

  const knobVX = knobX + cosValue * radius;
  const knobVY = knobY + sinValue * radius;
  
  fill(255, 0, 0);
  circle(knobVX, knobVY, 10);
}

function drawHousing(){
  noStroke(); // 枠線を消す
  fill(100, 100, 100); //
  circle(knobX - 3, knobY + 3 ,knobSize); //影の描画
  
  stroke(150, 100, 100); //
  strokeWeight(strokeValue); // 枠線の太さ
  
  fill(200, 200, 200); //
  circle(knobX, knobY ,knobSize); // ノブのベース
  
  fill(250, 250, 250); //
  circle(knobX, knobY, knobSize / 2); // ノブのトップ
}

function drawKnobTop(){
  if (knobStat == true){ // マウスクリックでオレンジ色でノブトップを塗り替える
    mousePosY = mouseY; /// <---ここを追加します
    fill(255, 125, 0);
    circle(knobX, knobY, knobSize / 2);
  }
}

function touchStarted() {
  if (
    mouseX > knobX - knobSize/2 &&
    mouseX < knobX + knobSize/2 &&
    mouseY > knobY - knobSize/2 &&
    mouseY < knobY + knobSize/2
  ) {
    knobStat = true;
    mousePosY = mouseY;
  }
}

function touchMoved() {
  if(knobStat == true){
    if (angle <= angleMax && angle >= angleMin){
      speed = (mousePosY - mouseY)/30;
      angle += speed;
    }
    if (angle >= angleMax) {
      angle = angleMax;
    }
    if (angle <= angleMin) {
      angle = angleMin;
    }
  } 
}

function touchEnded() {
  knobStat = false;
}

結局4動作の部はどういうふうになったのかと言うと

drawHousing()
drawKnobTop()
knobDraw()
touchStarted()
touchMoved()
touchEnded()
これらが記述されることになりました。

最初の3つは自分で処理を定義した関数です。自作関数と言うふうに呼ぶようにします。
これらは、あるタイミングでプログラムの中で呼び出す必要があります。

最後の3つはp5.jsであらかじめ定義されている関数で、自動的に呼び出される存在と言ったらいいのかなと思います。勝手関数とでも読んでおこうかと思います。

今日はここまで

とにかく定型的な処理は関数として④動作の部に追いやる。必要なタイミングで③実行の部で④を呼び出す。と言うふうにプログラミングすることにしました。

なぜかというと、なるべく④動作の部をひたすら作り込むと言う制作活動に専念できると思ったからです。
ちなみに、第0話で公開しているソースの中で③実行の部はどのようになっているかというと

function draw() {
  drawInterface();
  keyDraw();
  knobDraw();
  octDraw();
  octLedDraw();
  seqOnDraw();
  stepDraw();
  stepLedDraw();
  wavDraw();
  wavLedDraw();
  skipDraw();
  beatDraw();
  beatKnobDraw();
  beatOnDraw();
  beatLedDraw();
  muteDraw();
  muteLedDraw();
  selectDraw();
  selectLedDraw();
  playDraw();
  playLedDraw();
  tempoDraw();
  swingDraw();
  mixMuteDraw();
  mixMuteLedDraw();
  delayKnobDraw();
  slideLineDraw();
  slideKnobDraw();

  if (countTime <= 0){
    resetAll();
  }
  print(countTime);*/
}

こんなに関数を作って呼び出していました。ものすごい数の自作関数が呼び出されているようです。。。。
これはその道に精通した方々にとって適切ではないと言うのは重々承知なのですが、、、
原点に帰ります。とにかく実装の精神で解説を続けたいと思います。

この記事で使ったコードの全文

let knobX = 100;
let knobY = 100;
let knobSize = 50;
let strokeValue = 2;
let mousePosY;

let angle = -1.0;
let speed = 0;
let angleMax = 0.8;
let angleMin = -3.95;
const radius = knobSize/2-6;

let knobStat = false;

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

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

function knobDraw(){
  const sinValue = sin(angle);
  const cosValue = cos(angle);

  const knobVX = knobX + cosValue * radius;
  const knobVY = knobY + sinValue * radius;
  
  fill(255, 0, 0);
  circle(knobVX, knobVY, 10);
}

function drawHousing(){
  noStroke(); // 枠線を消す
  fill(100, 100, 100); //
  circle(knobX - 3, knobY + 3 ,knobSize); //影の描画
  
  stroke(150, 100, 100); //
  strokeWeight(strokeValue); // 枠線の太さ
  
  fill(200, 200, 200); //
  circle(knobX, knobY ,knobSize); // ノブのベース
  
  fill(250, 250, 250); //
  circle(knobX, knobY, knobSize / 2); // ノブのトップ
}

function drawKnobTop(){
  if (knobStat == true){ // マウスクリックでオレンジ色でノブトップを塗り替える
    mousePosY = mouseY; /// <---ここを追加します
    fill(255, 125, 0);
    circle(knobX, knobY, knobSize / 2);
  }
}

function touchStarted() {
  if (
    mouseX > knobX - knobSize/2 &&
    mouseX < knobX + knobSize/2 &&
    mouseY > knobY - knobSize/2 &&
    mouseY < knobY + knobSize/2
  ) {
    knobStat = true;
    mousePosY = mouseY;
  }
}

function touchMoved() {
  if(knobStat == true){
    if (angle <= angleMax && angle >= angleMin){
      speed = (mousePosY - mouseY)/30;
      angle += speed;
    }
    if (angle >= angleMax) {
      angle = angleMax;
    }
    if (angle <= angleMin) {
      angle = angleMin;
    }
  } 
}

function touchEnded() {
  knobStat = false;
}

実際にp5.jsのエディターにコピペして確認して見て下さい!

次回からは頑張ります。。。!

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

この記事が気に入ったらサポートをしてみませんか?