Add files via upload

Add chord recognition by tonal.js
This commit is contained in:
Wiwi Kuan
2023-03-30 19:11:36 +08:00
committed by GitHub
parent 7d3cde5a53
commit 3a35d1cb56
3 changed files with 94 additions and 85 deletions

View File

@@ -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>

View File

@@ -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

File diff suppressed because one or more lines are too long