问题
Just run into a bit of wall with my current project. So for my comp musics course we have to create a 24 key (2 octave) keyboard by first rendering a keyboard using a canvas and then using web audio to loading and play 24 different sounds. I have gotten my clips successfully loaded into an array(or so I hope!) but am a bit confused as to how I would go about handling click events and playing each sound. Searching around the internet has only yielded results about handling click events through finding the coordinates of the click and performing a certain event through that. That may not work for my project as I first render the white keys (14 of them) and then rendering the black keys on top (10 of those). That would make it difficult to detect clicks via coordinates as the white keys are no longer rectangle. My code for rendering the keys looks as such
function createKeys() {
var r = document.getElementById('piano');
var key = r.getContext('2d');
// creates white piano keys
key.beginPath();
for (i = 0; i < 14; i++)
{
wKey = [key.rect(i*70, 0, 70, 120)];
key.fillStyle = "#FFFFFF";
key.fill();
key.lineWidth = 2;
key.strokeStyle = 'black';
key.stroke();
}
key.closePath();
// begin black keys
key.beginPath();
var bKey1 = key.rect(53, 0, 35, 80);
key.fillStyle = "#000000";
key.fill();
key.lineWidth= 2;
key.strokeStyle = '#888888';
key.stroke();
var bKey2 = key.rect(123, 0, 35, 80);
key.fillStyle = "#000000";
key.fill();
key.lineWidth= 2;
key.strokeStyle = '#888888';
key.stroke();
var bKey3 = key.rect(263, 0, 35, 80);
key.fillStyle = "#000000";
key.fill();
key.lineWidth= 2;
key.strokeStyle = '#888888';
key.stroke();
var bKey4 = key.rect(333, 0, 35, 80);
key.fillStyle = "#000000";
key.fill();
key.lineWidth= 2;
key.strokeStyle = '#888888';
key.stroke();
var bKey5 = key.rect(403, 0, 35, 80);
key.fillStyle = "#000000";
key.fill();
key.lineWidth= 2;
key.strokeStyle = '#888888';
key.stroke();
var bKey6 = key.rect(543, 0, 35, 80);
key.fillStyle = "#000000";
key.fill();
key.lineWidth= 2;
key.strokeStyle = '#888888';
key.stroke();
var bKey7 = key.rect(613, 0, 35, 80);
key.fillStyle = "#000000";
key.fill();
key.lineWidth= 2;
key.strokeStyle = '#888888';
key.stroke();
var bKey8 = key.rect(753, 0, 35, 80);
key.fillStyle = "#000000";
key.fill();
key.lineWidth= 2;
key.strokeStyle = '#888888';
key.stroke();
var bKey9 = key.rect(823, 0, 35, 80);
key.fillStyle = "#000000";
key.fill();
key.lineWidth= 2;
key.strokeStyle = '#888888';
key.stroke();
var bKey10 = key.rect(893, 0, 35, 80);
key.fillStyle = "#000000";
key.fill();
key.lineWidth= 2;
key.strokeStyle = '#888888';
key.stroke();
key.closePath();
}
Now as you can see I created a reference variable for the white keys, an array called 'wKey' with indices 0 through 13(to represent each key), and individual variables for black keys called bKey1-10, because figuring out a formula to such a pattern was frying my brain. I was just wondering if I could create a function to check if those references were clicked instead of using coordinate tracking to perform actions (such as changing the color and playing the soundfile) on each key. Ideally I would like to do something along the lines of
If wKey[i] = clicked
then
play soundfile[i]
else if bKey1 = clicked
play soundfile[14]
else if bKey2 = clicked
play soundfile[15]
... and so on
Not sure of the feasibility of this as I've never had to mess around with a canvas, let alone performing functions on a canvas. Would like to hear what some fresh minds have to say about this.
EDIT: Since I'm creating a canvas I figured posting the HTML might be beneficial
<body>
<h1><u>The Cory Matthews "UNDAPANTS" Piano</u> by Chris C.</h1>
<div id = "controls_toolbar">
</div>
<canvas id="piano" width = "980" height = "120" style ="border:1px solid #000000;" class="center" onclick ="keyClicked()"> </canvas>
<script>
const PATH = '/mp3/'
SOUNDS = ['DOWNUnderPantsC', 'DOWNUnderPantsD', 'DOWNUnderPantsE', 'DOWNUnderPantsF', 'DOWNUnderPantsG',
'DOWNUnderPantsA', 'DOWNUnderPantsB', 'UPUnderPantsC', 'UPUnderPantsD', 'UPUnderPantsE', 'UPUnderPantsF',
'UPUnderPantsG', 'UPUnderPantsA', 'UPUnderPantsB', 'DOWNUnderPantsC#', 'DOWNUnderPantsD#', 'DOWNUnderPantsF#',
'DOWNUnderPantsG#', 'DOWNUnderPantsA#', 'UPUnderPantsC#', 'UPUnderPantsD#', 'UPUnderPantsF#', 'UPUnderPantsG#', 'UPUnderPantsA#']
createKeys();
init();
fetchSounds();
</script>
<p>Volume: <input type="range" min="0" max="100" value="100" oninput="VolumeSample.changeVolume(this);" /></p>
<img class="center" src="images/cm.png" alt="UNDAPANTS" style="margin-top: 20px">
</body>
回答1:
Basically you have to implement a manual button solution for canvas. This is not so complicated as it sounds as (a map is btw. also a way but I'm not covering it here).
This solution is dynamic which means you can expand the number of octaves, size of canvas etc. all everything will adjust automatically.
First let the notes array defined the number of keys in each octave as well as which keys should be black; we'll use the # appendix to determine that:
var notes = ['C','C#','D','D#','E','F','F#','G','G#','A','A#','B'];
var keys = notes.length * octaves;
Please refer to the source below on how the render is performed in detail.
Looping through the number of keys using a separate index for white keys (as the black keys are relative to those) we get a full piano keyboard. We store the calculated x positions of the keys in two different arrays, one for black keys and one for whites - this makes it easier to do hit tests later (alternative is to store key shapes - max 4 - and use those as hit test paths).
When the piano is rendered we can check for mouse down events (if you want to be able to drag when mouse is down to play different keys you need to set a down flag instead when mouse is down and then use the rest of the code inside a mouse move events with some optimizations - not shown here).
// check for mouse down
canvas.addEventListener('mousedown', function(e) {
// adjust mouse position
var rect = canvas.getBoundingClientRect(),
x = e.clientX - rect.left,
y = e.clientY - rect.top,
i, a;
// fill color for key down
ctx.fillStyle = '#fa2';
//in blacks?
for(i = 0; a = arrayBlacks[i++];) {
ctx.beginPath(); // start new path for test
ctx.rect(a[0], 0, blackKeyWidth - 2, h * .67); // add a rect to path
if (ctx.isPointInPath(x, y)) { // test if point is in path
ctx.fill(); // yes, fill it
outputKey(a[1], a[2]); // show/play note
return;
}
}
//in whites? (same as above, but for arrayWhites)
for(i = 0; a = arrayWhites[i++];) {
...cut... see full source
}, false);
When mouse is released we just re-render everything for simplicity. For larger keyboards (additional octaves) you might want to consider a per-path object approach where each key is stored as shape.
// if mouse up, re-render all
canvas.addEventListener('mouseup', function(e) {
renderPiano(false);
}, false);
All details can be found in the attached live snippet. Hope this helps!
// some initial values/setup
var canvas = document.getElementById('piano'), // get canvas
ctx = canvas.getContext('2d'), // get context
h = canvas.height, // cache dimension
w = canvas.width,
octaves = 2, // octaves to render
notes = ['C','C#','D','D#','E','F','F#','G','G#','A','A#','B'],
keys = notes.length * octaves, // pre-calc num of keys
noteIndex = 0, // note index
keyIndex = 0, // white key index
note, // note
whiteKeyWidth = (w / (7 * octaves))|0, // width of whites
blackKeyWidth = whiteKeyWidth * 0.75, // width of blacks
i, // iterator
x, // x pos of key
arrayBlacks = [], // store black positions
arrayWhites = [] // store white positions
;
// calc piano key positions
for (i = 0; i < keys; i++) {
noteIndex = i % notes.length; // force within notes range
note = notes[noteIndex]; // get note name
x = keyIndex * whiteKeyWidth; // start pos. of key
if (note.length === 1) { // white key (no sharp)
arrayWhites.push([x, note, i]); // store position
keyIndex++; // next white key index
}
else {
x -= blackKeyWidth * .5; // adjust for black key
arrayBlacks.push([x, note, i]);
}
}
renderPiano(false);
// common render function based on whites/blacks arrays
// special switch: onlyBlacks to override white key rendered while down
function renderPiano(onlyBlacks) {
var i, a;
//render white keys
if (!onlyBlacks) {
ctx.clearRect(0, 0, w, h); // clear canvas for full render
ctx.fillStyle = '#ffe';
for(i = 0; a = arrayWhites[i++];) {
ctx.fillRect(a[0], 0, whiteKeyWidth - 2, h - 1);
ctx.strokeRect(a[0], 0, whiteKeyWidth - 2, h - 1);
}
}
//render black keys
ctx.fillStyle = '#000';
for(i = 0; a = arrayBlacks[i++];) {
ctx.fillRect(a[0], 0, blackKeyWidth - 2, h * .67);
}
}
// check for mouse down
canvas.addEventListener('mousedown', function(e) {
// adjust mouse position
var rect = canvas.getBoundingClientRect(),
x = e.clientX - rect.left,
y = e.clientY - rect.top,
i, a;
ctx.fillStyle = '#fa2';
//in blacks?
for(i = 0; a = arrayBlacks[i++];) {
ctx.beginPath();
ctx.rect(a[0], 0, blackKeyWidth - 2, h * .67);
if (ctx.isPointInPath(x, y)) {
ctx.fill();
outputKey(a[1], a[2]);
return;
}
}
//in whites?
for(i = 0; a = arrayWhites[i++];) {
ctx.beginPath();
ctx.rect(a[0], 0, whiteKeyWidth - 2, h - 1);
if (ctx.isPointInPath(x, y)) {
ctx.fill();
renderPiano(true); // render black keys on top!
outputKey(a[1], a[2]);
return;
}
}
}, false);
// if mouse up, re-render all
canvas.addEventListener('mouseup', function(e) {
renderPiano(false);
}, false);
// format output here, ie. play correct note etc.
function outputKey(key, index) {
out.innerHTML = key + ((index / 12)|0);
// play note
}
<canvas id="piano" width=540 height=160></canvas>
<br><output id="out"></output>
来源:https://stackoverflow.com/questions/26330719/click-functions-through-references-variables-on-a-canvas-possible