p5.jsでシンセサイザーを作る 第12話 配列を使ってノブを配置する
Javascriptとp5.jsを使って、オリジナルなシンセサイザーを作るプログラミングの記事です。とりあえず何を作るのかを手っ取り早くお伝えしたいので、第0話で公開している完成品もチェックしてみてください。
ノブを配置します
シンセサイザー部分のノブを並べるセクションについて書いていきます。
メインになる四つのノブを配置する部分です。
とりあえずノブを一つだけ描画する
第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;
}
①宣言の部 配列の定義
4つのノブをそれぞれ独立して動作させたいと思います。
ここでは、ノブの角度、当たり判定を行うステータスを制御する変数を配列にしています。
knobStatとangleの二つを配列にしました。
let knobX = 100;
let knobY = 100;
let knobInterval = 60;
let knobSize = 50;
let strokeValue = 2;
let knobStat = [false, false, false, false];//////////ノブの当たり判定に使う配列
let mousePosY;
let angle = [-1.0, -0.8, -0.6, -0.4];//////////ノブの角度に使う配列
let speed = 0;
let angleMax = 0.8;
let angleMin = -3.95;
const radius = knobSize/2-6;
②初期化の部
ここはキャンバスサイズの指定だけなので、変更はありません。
function setup() {
createCanvas(500, 400);
}
③実行の部
function draw() {
fill(100, 100, 100);
drawHousing();
drawInterface();
knobDraw();
}
実行の部では、背景を一度リセットするためのfill()を置いています。
そのあとは、
ノブのベースになる部分(動かない部分)を描画するdrawHousing()
ノブが動作する時にオレンジ色に光る処理をするdrawInterface()
ノブの回転を反映して描画するknodraw()
と言う三つの関数を呼び出すようにしています。
それぞれの関数は④動作の部で定義します。
④動作の部
ここでは関数の定義が三つ、そしてマウスの動作を検出する関数が三つあります。
動作を定義する関数三つ
function drawHousing(){
for(let i = 0; i < 4; i++){
circle(knobX - 3 + knobInterval * i, knobY + 3 ,knobSize); //影の描画
}
stroke(150, 100, 100);
strokeWeight(strokeValue); // 枠線の太さ
fill(200, 200, 200);
for(let i = 0; i < 4; i++){
circle(knobX + knobInterval * i, knobY ,knobSize); // ノブのベース
}
fill(250, 250, 250); //
for(let i = 0; i < 4; i++){
circle(knobX + knobInterval * i, knobY, knobSize / 2); // ノブのトップ
}
}
function drawInterface(){
for(let i = 0; i < 4; i++){
if (knobStat == true){ // マウスクリックでオレンジ色でノブトップを塗り替える
mousePosY = mouseY;
fill(255, 125, 0);
circle(knobX, knobY, knobSize / 2);
}
}
}
function knobDraw() {
for (let i = 0; i < 4; i++) {
if (knobStat[i] == true) {
fill(255, 125, 0);
mousePosY = mouseY;
circle(knobX + knobInterval * i, knobY, knobSize / 2);
}
const sinValue = sin(angle[i]);
const cosValue = cos(angle[i]);
const knobVX = knobX + cosValue * radius + i * knobInterval;
const knobVY = knobY + sinValue * radius;
fill(250, 0, 0);
circle(knobVX, knobVY, 10);
}
}
三つの関数の役割は以下のようになっています。
drawHouding()
drawInterface()
knobDraw()
drawHousing()は、ノブの動作の中でも動きのない部分を描画します。具体的には、ノブの影、ノブの本体(グレーの部分)、ノブのトップ(真ん中の白い丸)です。
drawInterface()は、ノブがクリックされた時にノブのトップがオレンジ色に光る動作を担当しています。
knobDraw()は、ノブがドラッグされた時に、ノブのトップにある目盛りが回転する部分の動作を描画します。
ここまでのポイントは、それぞれの動作がfor文を使って4回繰り返されていることです。例えばノブのベースを描画する部分を抜粋するとこのようになっています。
for(let i = 0; i < 4; i++){
circle(knobX + knobInterval * i, knobY, knobSize / 2); // ノブのトップ
}
このコードの場合
knobXにknobIntervalのi倍だけ右にずらして、円を4回描画していることになります。
+ knobInterval * I
この記述がポイントになっており、他にも繰り返し描画をする時に応用ができると思います、
マウスの動作を検出する関数三つ
function touchStarted() {
for (let i = 0; i < 4; i++) {
if (
mouseX > knobX + knobInterval * i - knobSize / 2 &&
mouseX < knobX + knobInterval * i + knobSize / 2 &&
mouseY > knobY - knobSize / 2 &&
mouseY < knobY + knobSize / 2
) {
knobStat[i] = true;
mousePosY = mouseY;
}
}
}
function touchMoved() {
for (let i = 0; i < 4; i++) {
if (knobStat[i] === true) {
if (angle[i] <= angleMax && angle[i] >= angleMin) {
speed = (mousePosY - mouseY) / 30;
angle[i] += speed;
}
if (angle[i] >= angleMax) {
angle[i] = angleMax;
}
if (angle[i] <= angleMin) {
angle[i] = angleMin;
}
}
}
}
function touchEnded() {
for (let i = 0; i < 4; i++) {
knobStat[i] = false;
}
}
この関数の役割は以下のようになっています。
touchStarted()
touchMoved()
touchEnded()
touchStarted()はマウスがクリックされた時の座標を検知しています。このコードでは当たり判定を行っており、指定された範囲にマウスがあった場合、該当するknobStat[]配列の値がtureになるようにしています。
touchMoved()はクリックされたマウスポインタがドラッグされた時、またはドラッグしている間、常に実行されるかんすです。このコードでは、ドラッグが検出されている間、trueになっているノブの角度を加算したり、減算したりするようにしています。
touchEnded()はクリックされたマウスが離された時に実行されています。このコードではマウスが離された時に、該当するknobStat[]配列の値をfalseに戻すようにしています。
配列とfor文で実現する複数パーツの動き
この記事で最も重要なのは、配列を設定した変数が、for文によって制御されると言うロジックだと思います。例えば、touchStarted()の中身を見た場合
function touchStarted() {
for (let i = 0; i < 4; i++) {
if (
mouseX > knobX + knobInterval * i - knobSize / 2 &&
mouseX < knobX + knobInterval * i + knobSize / 2 &&
mouseY > knobY - knobSize / 2 &&
mouseY < knobY + knobSize / 2
) {
knobStat[i] = true;
mousePosY = mouseY;
}
}
}
当たり判定を見ているif文の中では、X軸に対してknobIntervalのi倍を指定することで、当たり判定が始まる位置を横にずらすことができます。
そして、配列で構成されているknobStatは、knobStat[i]のように記述されています。これによって、4回の繰り返しのうち、当たり判定が成立したknobStatのi番目に当たるステータスがtureになるのです。
実行してみます
配列によって角度とステータスを4つ独立させて動作するコードができました。
実行した結果はこのようになります。
この記事のコード全文
let knobX = 100;
let knobY = 100;
let knobInterval = 60;
let knobSize = 50;
let strokeValue = 2;
let knobStat = [false, false, false, false];//////////ノブの当たり判定に使う配列
let mousePosY;
let angle = [-1.0, -0.8, -0.6, -0.4];//////////ノブの角度に使う配列
let speed = 0;
let angleMax = 0.8;
let angleMin = -3.95;
const radius = knobSize/2-6;
function setup() {
createCanvas(500, 400);
}
function draw() {
fill(100, 100, 100);
drawHousing();
drawInterface();
knobDraw();
}
function drawHousing(){
for(let i = 0; i < 4; i++){
circle(knobX - 3 + knobInterval * i, knobY + 3 ,knobSize); //影の描画
}
stroke(150, 100, 100);
strokeWeight(strokeValue); // 枠線の太さ
fill(200, 200, 200);
for(let i = 0; i < 4; i++){
circle(knobX + knobInterval * i, knobY ,knobSize); // ノブのベース
}
fill(250, 250, 250); //
for(let i = 0; i < 4; i++){
circle(knobX + knobInterval * i, knobY, knobSize / 2); // ノブのトップ
}
}
function drawInterface(){
for(let i = 0; i < 4; i++){
if (knobStat == true){ // マウスクリックでオレンジ色でノブトップを塗り替える
mousePosY = mouseY;
fill(255, 125, 0);
circle(knobX, knobY, knobSize / 2);
}
}
}
function knobDraw() {
for (let i = 0; i < 4; i++) {
if (knobStat[i] == true) {
fill(255, 125, 0);
mousePosY = mouseY;
circle(knobX + knobInterval * i, knobY, knobSize / 2);
}
const sinValue = sin(angle[i]);
const cosValue = cos(angle[i]);
const knobVX = knobX + cosValue * radius + i * knobInterval;
const knobVY = knobY + sinValue * radius;
fill(250, 0, 0);
circle(knobVX, knobVY, 10);
}
}
function touchStarted() {
for (let i = 0; i < 4; i++) {
if (
mouseX > knobX + knobInterval * i - knobSize / 2 &&
mouseX < knobX + knobInterval * i + knobSize / 2 &&
mouseY > knobY - knobSize / 2 &&
mouseY < knobY + knobSize / 2
) {
knobStat[i] = true;
mousePosY = mouseY;
}
}
}
function touchMoved() {
for (let i = 0; i < 4; i++) {
if (knobStat[i] === true) {
if (angle[i] <= angleMax && angle[i] >= angleMin) {
speed = (mousePosY - mouseY) / 30;
angle[i] += speed;
}
if (angle[i] >= angleMax) {
angle[i] = angleMax;
}
if (angle[i] <= angleMin) {
angle[i] = angleMin;
}
}
}
}
function touchEnded() {
for (let i = 0; i < 4; i++) {
knobStat[i] = false;
}
}
実際にp5.jsのエディターにコピペして確認して見て下さい!
続きはまた後日!