Files
CircleOfFifths/circleOfFifths.js
2019-04-13 12:55:37 +08:00

380 lines
8.4 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

let outRatio = 0.8;
let outC1;
let outC2;
let outCH;
let outCT;
let outCSP; // 分隔
let outText = ["沒有升降", "1 個 #", "2 個 #", "3 個 #", "4 個 #", "7 個 b / 5 個 #", "6 個 b / 6 個 #", "5 個 b / 7 個 #", "4 個 b", "3 個 b", "2 個 b", "1 個 b"]
let outTextSize = 15;
let majorRatio = 0.74;
let majorC1;
let majorC2;
let majorCT;
let majorText = ["C", "G", "D", "A", "E", "Cb/B", "Gb/F#", "Db/C#", "Ab", "Eb", "Bb", "F"]
let majorTextSize = 22;
let minorRatio = 0.65;
let minorC1;
let minorC2;
let minorCT;
let minorText = ["Am", "Em", "Bm", "F#m", "C#m", "Abm/G#m", "Ebm/D#m", "Bbm/A#m", "Fm", "Cm", "Gm", "Dm"]
let minorTextSize = 17;
let detailText1 = [
"C 大調 ─ A 小調",
"G 大調 ─ E 小調",
"D 大調 ─ B 小調",
"A 大調 ─ F# 小調",
"E 大調 ─ C# 小調",
"Cb 大調 ─ Ab 小調 / B 大調 ─ G# 小調",
"Gb 大調 ─ Eb 小調 / F# 大調 ─ D# 小調",
"Db 大調 ─ Bb 小調 / C# 大調 ─ A# 小調",
"Ab 大調 ─ F 小調",
"Eb 大調 ─ C 小調",
"Bb 大調 ─ G 小調",
"F 大調 ─ D 小調"
]
let detailTextSize = 22;
let coreRatio = 0.57;
let coreType = "Detail";
let dbText = "Circle of Fifths by NiceChord (Wiwi Kuan)"
let mouseDir;
let acc = 0;
let vel = 0;
let angle = -3.5; // 1 = 1/12 TAU, global angle
let locked = true;
let imgStaff;
let imgSharp;
let imgFlat;
let modeLabel = ["Major/Minor", "Ionian", "Dorian", "Phrygian", "Lydian", "Mixolydian", "Aeolian", "Locrian"];
let paintingArray = new Array();
function preload() {
imgStaff = loadImage("./staff.png");
imgFlat = loadImage("./flat.png");
imgSharp = loadImage("./sharp.png");
}
function setup() {
createCanvas(800, 800);
ellipseMode(CENTER);
colorMode(HSB, 100);
//colors
outC1 = color(60, 30, 70);
outC2 = color(60, 30, 80);
outCH = color(60, 30, 100);
outCT = color(60, 0, 95);
outCSP = color(60, 20, 95);
majorC1 = color(80, 30, 80);
majorC2 = color(80, 30, 70);
majorCT = color(80, 30, 20);
minorC1 = color(80, 30, 75);
minorC2 = color(80, 30, 65);
minorCT = color(80, 10, 90);
outTextSize = width * 0.01875;
majorTextSize = width * 0.0275;
minorTextSize = width * 0.02125;
detailTextSize = width * 0.0275;
lockButton = createButton('[已鎖定]');
lockButton.position(19, 19);
lockButton.mousePressed(toggleLocked);
}
function toggleLocked() {
if (locked) {
lockButton.html("鎖定");
locked = false;
} else {
lockButton.html("[已鎖定]");
locked = true;
}
}
function highlighted() {
let x = mouseDir - angle;
while (x < 0) {
x += 12;
}
x = ((x * 10000) % 120000) / 10000;
return floor(x);
}
function drawOuter(ang) {
stroke(outCSP);
strokeWeight(width / 600);
for (i = 0; i < 12; i++) {
fill((i % 2 == 0) ? outC1 : outC2);
if (i == highlighted()) {
fill(outCH);
}
let j = i + ang;
arc(0, 0, width * outRatio, height * outRatio, TAU * (j / 12), TAU * ((j + 1) / 12) - 0.000001, PIE);
}
stroke(20);
noFill();
ellipse(0, 0, width * outRatio);
}
function drawOuterText(ang) {
for (i = 0; i < 12; i++) {
let j = i + ang + 3.5; // 把第一個字畫到右方
push();
rotate(TAU * (j / 12));
textAlign(CENTER);
textSize(outTextSize);
noStroke();
fill(outCT);
text(outText[i], 0, -(height * outRatio / 2 * 0.945));
pop();
}
}
function drawMajor(ang) {
stroke(90);
for (i = 0; i < 12; i++) {
fill((i % 2 == 0) ? majorC1 : majorC2);
let j = i + ang;
arc(0, 0, width * majorRatio, height * majorRatio, TAU * (j / 12), TAU * ((j + 1) / 12) - 0.000001);
}
}
function drawMajorText(ang) {
for (i = 0; i < 12; i++) {
let j = i + ang + 3.5;
push();
rotate(TAU * (j / 12));
textAlign(CENTER);
textSize(majorTextSize);
noStroke();
fill(majorCT);
text(majorText[i], 0, -(height * majorRatio / 2 * 0.91));
pop();
}
}
function drawMinor(ang) {
noStroke();
for (i = 0; i < 12; i++) {
fill((i % 2 == 0) ? minorC1 : minorC2);
let j = i + ang;
arc(0, 0, width * minorRatio, height * minorRatio, TAU * (j / 12), TAU * ((j + 1) / 12) - 0.000001);
}
}
function drawMinorText(ang) {
for (i = 0; i < 12; i++) {
let j = i + ang + 3.5;
push();
rotate(TAU * (j / 12));
textAlign(CENTER);
textSize(minorTextSize);
noStroke();
fill(minorCT);
text(minorText[i], 0, -(height * minorRatio / 2 * 0.91));
pop();
}
}
function drawCore(ang) {
switch (coreType) {
case "Empty":
drawCoreEmpty(ang);
break;
case "Black":
drawCoreBlack(ang);
break;
case "Detail":
drawCoreDetail(ang);
break;
default:
drawCoreEmpty(ang);
}
}
function drawCoreDetail(ang) {
fill(100);
stroke(40);
ellipse(0, 0, width * coreRatio);
fill(20);
noStroke();
textAlign(CENTER);
textSize(detailTextSize);
text(detailText1[highlighted()], 0, height * 0.08);
imageMode(CENTER);
image(imgStaff, 0, height * -0.03, width / 2, height * 0.15);
let sig = highlighted();
sig = (sig > 6) ? sig - 12 : sig;
//
// dbText = sig;
switch (sig) {
case 5:
stroke(0);
strokeWeight(width / 400);
line(width * -0.008, height * -0.066, width * -0.008, height * 0.009);
strokeWeight(width / 800);
drawKeySig(5, 0.02, 1);
drawKeySig(-7, -0.16, 1);
drawSigNumber(5, -7);
break;
case 6:
stroke(0);
strokeWeight(width / 400);
line(width * -0.008, height * -0.066, width * -0.008, height * 0.009);
strokeWeight(width / 800);
drawKeySig(6, 0.02, 1);
drawKeySig(-6, -0.16, 1);
drawSigNumber(6, -6);
break;
case -5:
stroke(0);
strokeWeight(width / 400);
line(width * -0.008, height * -0.066, width * -0.008, height * 0.009);
strokeWeight(width / 800);
drawKeySig(7, 0.02, 1);
drawKeySig(-5, -0.16, 1);
drawSigNumber(7, -5);
break;
default:
drawKeySig(sig, -0.16, 1);
drawSigNumber((sig > 0) ? sig : 0, (sig < 0) ? sig : 0);
}
}
function drawSigNumber(s, f) {
// draw center one
noStroke();
fill(80);
let acci = [
"Fb", "Cb", "Gb", "Db", "Ab", "Eb", "Bb", "",
"F#", "C#", "G#", "D#", "A#", "E#", "B#"
]
let sp = 0.024; // space
let w = 0.02; // width
let h = 0.004; // height
rectMode(CENTER);
textAlign(CENTER);
for (i = -7; i < 8; i++) {
switch (true) {
case (i < 0): // flat
fill(20, (i < f) ? 20 : 100, (i < f) ? 90 : 70);
break;
case (i > 0): // sharp
fill(100, (i > s) ? 20 : 90, (i < s) ? 90 : 80);
break;
default:
fill(80);
textSize(width / 60);
text("♮", 0, height * 0.14);
fill(60);
}
rect((0 + sp * i) * width, height * 0.12, height * w, height * h);
textSize(width / 74);
text(acci[i + 7], (0 + sp * i) * width, height * 0.138);
}
textSize(width / 60);
fill(90);
text(`${-f} 個降記號 / ${s} 個升記號)`, 0, height * 0.16);
}
function drawKeySig(sig, x, y) {
let sx = x;
let sp = 0.02;
if (sig > 0) {
imageMode(CENTER);
let sy = [-0.066, -0.039, -0.077, -0.0475, -0.017, -0.0575, -0.02875];
for (i = 0; i < sig; i++) {
image(imgSharp, width * (sx + (i * sp)), height * sy[i], width / 16, height / 16);
}
}
if (sig < 0) {
imageMode(CENTER);
let sy = [-0.0289, -0.059, -0.021, -0.049, -0.012, -0.04, -0.002];
for (i = 0; i < abs(sig); i++) {
image(imgFlat, width * (sx + (i * sp)), height * sy[i], width / 16, height / 16);
}
}
}
function drawCoreEmpty(ang) {
fill(100);
stroke(40);
ellipse(0, 0, width * coreRatio);
}
function drawCoreBlack(ang) {
fill(0);
stroke(40);
ellipse(0, 0, width * coreRatio);
}
function debugText() {
fill(0);
noStroke();
textAlign(CENTER);
text(dbText, 0, height * 0.45);
}
function mouseAngle(ang) {
let v = createVector(mouseX - width / 2, mouseY - height / 2);
let h = v.heading(); // -PI ~ PI
let i = map(h, -PI, PI, 6, 18);
mouseDir = (i > 12) ? i - 12 : i;
}
function rotateGlobal() {
acc = (mouseX - width / 2) / width;
if (abs(acc) > 0.4) {
vel += acc / 70;
vel = constrain(vel, -0.1, 0.1);
}
if (locked) {
vel = 0; // friction
} else {
vel *= 0.9
}
angle += vel;
}
function draw() {
background(95);
translate(width / 2, height / 2);
mouseAngle(angle);
rotateGlobal();
highlighted();
drawOuter(angle);
drawOuterText(angle);
drawMajor(angle);
drawMajorText(angle);
drawMinor(angle);
drawMinorText(angle);
drawCore(angle);
debugText();
}