Add files via upload
Add chord recognition by tonal.js
This commit is contained in:
@@ -5,6 +5,8 @@
|
|||||||
<title>好和弦的鋼琴鍵盤顯示器</title>
|
<title>好和弦的鋼琴鍵盤顯示器</title>
|
||||||
<link rel="stylesheet" href="style.css">
|
<link rel="stylesheet" href="style.css">
|
||||||
<script src="p5.min.js"></script>
|
<script src="p5.min.js"></script>
|
||||||
|
<script src="tonal.min.js"></script>
|
||||||
|
|
||||||
<script src="webmidi.js"></script>
|
<script src="webmidi.js"></script>
|
||||||
<script src="globals.js"></script>
|
<script src="globals.js"></script>
|
||||||
<script src="piano-visualizer.js"></script>
|
<script src="piano-visualizer.js"></script>
|
||||||
@@ -36,7 +38,7 @@
|
|||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
|
||||||
覺得好用嗎?到 <a href="https://nicechord.com">NiceChord.com</a> 逛逛支持我!
|
覺得好用嗎?到 <a href="https://nicechord.com">NiceChord.com</a> 逛逛支持我!原始碼在 <a href="https://github.com/wiwikuan/pianometer">GitHub</a>。
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
@@ -1,24 +1,23 @@
|
|||||||
|
|
||||||
function setup() {
|
function setup() {
|
||||||
createCanvas(1098, 118).parent('piano-visualizer');
|
createCanvas(1098, 118).parent('piano-visualizer');
|
||||||
colorMode(HSB, 360, 100, 100, 100);
|
colorMode(HSB, 360, 100, 100, 100);
|
||||||
keyOnColor = color(326, 100, 100, 100); // <---- 編輯這裡換「按下時」的顏色![HSB Color Mode]
|
keyOnColor = color(326, 100, 100, 100); // <---- 編輯這裡換「按下時」的顏色![HSB Color Mode]
|
||||||
pedaledColor = color(326, 100, 70, 100); // <---- 編輯這裡換「踏板踩住」的顏色![HSB Color Mode]
|
pedaledColor = color(326, 100, 70, 100); // <---- 編輯這裡換「踏板踩住」的顏色![HSB Color Mode]
|
||||||
smooth(2);
|
smooth(2);
|
||||||
frameRate(60);
|
frameRate(60);
|
||||||
initKeys();
|
initKeys();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function draw() {
|
function draw() {
|
||||||
background(0, 0, 20, 100);
|
background(0, 0, 20, 100);
|
||||||
pushHistories();
|
pushHistories();
|
||||||
drawWhiteKeys();
|
drawWhiteKeys();
|
||||||
drawBlackKeys();
|
drawBlackKeys();
|
||||||
// drawPedalLines();
|
// drawPedalLines();
|
||||||
// drawNotes();
|
// drawNotes();
|
||||||
|
|
||||||
drawTexts();
|
drawTexts();
|
||||||
}
|
}
|
||||||
|
|
||||||
function calculateSessionTime() {
|
function calculateSessionTime() {
|
||||||
@@ -37,66 +36,66 @@ function calculateSessionTime() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function initKeys() {
|
function initKeys() {
|
||||||
for (i = 0; i<128; i++) {
|
for (i = 0; i < 128; i++) {
|
||||||
isKeyOn[i] = 0;
|
isKeyOn[i] = 0;
|
||||||
isPedaled[i] = 0;
|
isPedaled[i] = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawWhiteKeys() {
|
function drawWhiteKeys() {
|
||||||
let wIndex = 0; // white key index
|
let wIndex = 0; // white key index
|
||||||
stroke(0, 0, 0);
|
stroke(0, 0, 0);
|
||||||
strokeWeight(1);
|
strokeWeight(1);
|
||||||
for (let i = 21; i < 109; i++) {
|
for (let i = 21; i < 109; i++) {
|
||||||
if (isBlack[i % 12] == 0) {
|
if (isBlack[i % 12] == 0) {
|
||||||
// it's a white key
|
// it's a white key
|
||||||
if (isKeyOn[i] == 1 && !rainbowMode) {
|
if (isKeyOn[i] == 1 && !rainbowMode) {
|
||||||
fill(keyOnColor); // keypressed
|
fill(keyOnColor); // keypressed
|
||||||
} else if (isKeyOn[i] == 1 && rainbowMode) {
|
} else if (isKeyOn[i] == 1 && rainbowMode) {
|
||||||
fill(map(i, 21, 108, 0, 1080)%360, 100, 100, 100); // rainbowMode
|
fill(map(i, 21, 108, 0, 1080) % 360, 100, 100, 100); // rainbowMode
|
||||||
} else if (isPedaled[i] == 1 && !rainbowMode) {
|
} else if (isPedaled[i] == 1 && !rainbowMode) {
|
||||||
fill(pedaledColor); // pedaled
|
fill(pedaledColor); // pedaled
|
||||||
} else if (isPedaled[i] == 1 && rainbowMode) {
|
} else if (isPedaled[i] == 1 && rainbowMode) {
|
||||||
fill(map(i, 21, 108, 0, 1080)%360, 100, 70, 100); // pedaled rainbowMode
|
fill(map(i, 21, 108, 0, 1080) % 360, 100, 70, 100); // pedaled rainbowMode
|
||||||
} else {
|
} else {
|
||||||
fill(0, 0, 100); // white key
|
fill(0, 0, 100); // white key
|
||||||
}
|
|
||||||
let thisX = border + wIndex*(whiteKeyWidth+whiteKeySpace);
|
|
||||||
rect(thisX, keyAreaY, whiteKeyWidth, keyAreaHeight, radius);
|
|
||||||
// println(wIndex);
|
|
||||||
wIndex++;
|
|
||||||
}
|
}
|
||||||
|
let thisX = border + wIndex * (whiteKeyWidth + whiteKeySpace);
|
||||||
|
rect(thisX, keyAreaY, whiteKeyWidth, keyAreaHeight, radius);
|
||||||
|
// println(wIndex);
|
||||||
|
wIndex++;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawBlackKeys() {
|
function drawBlackKeys() {
|
||||||
let wIndex = 0; // white key index
|
let wIndex = 0; // white key index
|
||||||
stroke(0, 0, 0);
|
stroke(0, 0, 0);
|
||||||
strokeWeight(1.5);
|
strokeWeight(1.5);
|
||||||
for (let i = 21; i < 109; i++) {
|
for (let i = 21; i < 109; i++) {
|
||||||
if (isBlack[i % 12] == 0) {
|
if (isBlack[i % 12] == 0) {
|
||||||
// it's a white key
|
// it's a white key
|
||||||
wIndex++;
|
wIndex++;
|
||||||
}
|
|
||||||
|
|
||||||
if (isBlack[i % 12] > 0) {
|
|
||||||
// it's a black key
|
|
||||||
if (isKeyOn[i] == 1 && !rainbowMode) {
|
|
||||||
fill(keyOnColor); // keypressed
|
|
||||||
} else if (isKeyOn[i] == 1 && rainbowMode) {
|
|
||||||
fill(map(i, 21, 108, 0, 1080)%360, 100, 100, 100); // rainbowMode
|
|
||||||
} else if (isPedaled[i] == 1 && !rainbowMode) {
|
|
||||||
fill(pedaledColor); // pedaled
|
|
||||||
} else if (isPedaled[i] == 1 && rainbowMode) {
|
|
||||||
fill(map(i, 21, 108, 0, 1080)%360, 100, 70, 100); // pedaled rainbowMode
|
|
||||||
} else {
|
|
||||||
fill(0, 0, 0); // white key
|
|
||||||
}
|
|
||||||
|
|
||||||
let thisX = border + (wIndex-1)*(whiteKeyWidth+whiteKeySpace) + isBlack[i % 12];
|
|
||||||
rect(thisX, keyAreaY-1, blackKeyWidth, blackKeyHeight, bRadius);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isBlack[i % 12] > 0) {
|
||||||
|
// it's a black key
|
||||||
|
if (isKeyOn[i] == 1 && !rainbowMode) {
|
||||||
|
fill(keyOnColor); // keypressed
|
||||||
|
} else if (isKeyOn[i] == 1 && rainbowMode) {
|
||||||
|
fill(map(i, 21, 108, 0, 1080) % 360, 100, 100, 100); // rainbowMode
|
||||||
|
} else if (isPedaled[i] == 1 && !rainbowMode) {
|
||||||
|
fill(pedaledColor); // pedaled
|
||||||
|
} else if (isPedaled[i] == 1 && rainbowMode) {
|
||||||
|
fill(map(i, 21, 108, 0, 1080) % 360, 100, 70, 100); // pedaled rainbowMode
|
||||||
|
} else {
|
||||||
|
fill(0, 0, 0); // white key
|
||||||
|
}
|
||||||
|
|
||||||
|
let thisX = border + (wIndex - 1) * (whiteKeyWidth + whiteKeySpace) + isBlack[i % 12];
|
||||||
|
rect(thisX, keyAreaY - 1, blackKeyWidth, blackKeyHeight, bRadius);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawTexts() {
|
function drawTexts() {
|
||||||
@@ -104,7 +103,7 @@ function drawTexts() {
|
|||||||
fill(0, 0, 100, 90)
|
fill(0, 0, 100, 90)
|
||||||
textFont('Monospace');
|
textFont('Monospace');
|
||||||
textStyle(BOLD);
|
textStyle(BOLD);
|
||||||
textSize(14);
|
textSize(14);
|
||||||
textAlign(LEFT, TOP);
|
textAlign(LEFT, TOP);
|
||||||
|
|
||||||
// TIME
|
// TIME
|
||||||
@@ -120,23 +119,27 @@ function drawTexts() {
|
|||||||
text(notesText, 85, 79);
|
text(notesText, 85, 79);
|
||||||
|
|
||||||
// CALORIES
|
// CALORIES
|
||||||
let caloriesText = "CALORIES" + "\n" + (totalIntensityScore/250).toFixed(3); // 250 Intensity = 1 kcal.
|
let caloriesText = "CALORIES" + "\n" + (totalIntensityScore / 250).toFixed(3); // 250 Intensity = 1 kcal.
|
||||||
text(caloriesText, 350, 79);
|
text(caloriesText, 350, 79);
|
||||||
|
|
||||||
// SHORT-TERM DENSITY
|
// SHORT-TERM DENSITY
|
||||||
let shortTermDensity = shortTermTotal.reduce((accumulator, currentValue) => accumulator + currentValue, 0); // Sum the array.
|
let shortTermDensity = shortTermTotal.reduce((accumulator, currentValue) => accumulator + currentValue, 0); // Sum the array.
|
||||||
if (shortTermDensity > notesSMax) { notesSMax = shortTermDensity };
|
if (shortTermDensity > notesSMax) {
|
||||||
|
notesSMax = shortTermDensity
|
||||||
|
};
|
||||||
let shortTermDensityText = "NPS(MAX)" + "\n" + shortTermDensity + " (" + notesSMax + ")";
|
let shortTermDensityText = "NPS(MAX)" + "\n" + shortTermDensity + " (" + notesSMax + ")";
|
||||||
text(shortTermDensityText, 190, 79);
|
text(shortTermDensityText, 190, 79);
|
||||||
|
|
||||||
// LEGATO SCORE
|
// LEGATO SCORE
|
||||||
let legatoScore = legatoHistory.reduce((accumulator, currentValue) => accumulator + currentValue, 0)
|
let legatoScore = legatoHistory.reduce((accumulator, currentValue) => accumulator + currentValue, 0)
|
||||||
legatoScore /= 60;
|
legatoScore /= 60;
|
||||||
let legatoText = "LEGATO" + "\n" + legatoScore.toFixed(2);
|
let legatoText = "LEGATO" + "\n" + legatoScore.toFixed(2);
|
||||||
text(legatoText, 276, 79);
|
text(legatoText, 276, 79);
|
||||||
|
|
||||||
// NOW PLAYING
|
// NOW PLAYING
|
||||||
let nowPlayingText = "KEYS" + "\n" + truncateString(getPressedKeys(), 47);
|
let chordSymbol = Tonal.Chord.detect(getPressedKeys(false), { assumePerfectFifth: true })
|
||||||
|
let chordSymbolWithoutM = chordSymbol.map((str) => str.replace(/M($|(?=\/))/g, "")); // get rid of the M's
|
||||||
|
let nowPlayingText = truncateString(getPressedKeys(true), 47) + "\n" + truncateString(chordSymbolWithoutM.join(' '), 47);
|
||||||
text(nowPlayingText, 440, 79);
|
text(nowPlayingText, 440, 79);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,7 +149,7 @@ function pushHistories() {
|
|||||||
notesThisFrame = 0;
|
notesThisFrame = 0;
|
||||||
legatoHistory.push(isKeyOn.reduce((accumulator, currentValue) => accumulator + currentValue, 0));
|
legatoHistory.push(isKeyOn.reduce((accumulator, currentValue) => accumulator + currentValue, 0));
|
||||||
legatoHistory.shift();
|
legatoHistory.shift();
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,21 +179,19 @@ function convertNumberToBars(number) {
|
|||||||
return combinedString;
|
return combinedString;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPressedKeys() {
|
function getPressedKeys(returnString = true) {
|
||||||
let pressedOrPedaled = [];
|
let pressedOrPedaled = [];
|
||||||
|
|
||||||
for (let i = 0; i < isKeyOn.length; i++) {
|
for (let i = 0; i < isKeyOn.length; i++) {
|
||||||
pressedOrPedaled[i] = isKeyOn[i] === 1 || isPedaled[i] === 1 ? 1 : 0;
|
pressedOrPedaled[i] = isKeyOn[i] === 1 || isPedaled[i] === 1 ? 1 : 0;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let noteNames = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']; // default if sharp
|
let noteNames = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']; // default if sharp
|
||||||
if ([0, 1, 3, 5, 8, 10].includes(pressedOrPedaled.indexOf(1) % 12)) {
|
if ([0, 1, 3, 5, 8, 10].includes(pressedOrPedaled.indexOf(1) % 12)) {
|
||||||
// flat
|
// flat
|
||||||
noteNames = ['C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'B'];
|
// noteNames = ['C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'B'];
|
||||||
}
|
}
|
||||||
|
|
||||||
const pressedKeys = [];
|
const pressedKeys = [];
|
||||||
|
|
||||||
@@ -201,8 +202,12 @@ function getPressedKeys() {
|
|||||||
pressedKeys.push(`${noteName}${octave}`);
|
pressedKeys.push(`${noteName}${octave}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (returnString == true){
|
||||||
|
return pressedKeys.join(' ');
|
||||||
|
} else {
|
||||||
|
return pressedKeys;
|
||||||
|
}
|
||||||
|
|
||||||
return pressedKeys.join(' ');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function truncateString(str, maxLength = 40) {
|
function truncateString(str, maxLength = 40) {
|
||||||
@@ -222,15 +227,15 @@ function mouseClicked() {
|
|||||||
if (mouseX <= 84) {
|
if (mouseX <= 84) {
|
||||||
sessionStartTime = new Date();
|
sessionStartTime = new Date();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mouseX > 84 && mouseX < 170) {
|
if (mouseX > 84 && mouseX < 170) {
|
||||||
totalNotesPlayed = 0;
|
totalNotesPlayed = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mouseX > 187 && mouseX < 257) {
|
if (mouseX > 187 && mouseX < 257) {
|
||||||
notesSMax = 0;
|
notesSMax = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mouseX > 347 && mouseX < 420) {
|
if (mouseX > 347 && mouseX < 420) {
|
||||||
totalIntensityScore = 0; // RESET CALORIES
|
totalIntensityScore = 0; // RESET CALORIES
|
||||||
}
|
}
|
||||||
|
2
tonal.min.js
vendored
Normal file
2
tonal.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user