O problema que você está enfrentando tem a ver com o conceito de "closures" (fechamentos) em JavaScript. Vou explicar o que está acontecendo no código e o motivo pelo qual o uso de uma const dentro do loop resolve o problema.
No código original, você está usando um loop while para percorrer todas as teclas e adicionar um evento de clique a cada uma delas. No entanto, o evento de clique está chamando a função playSound passando o valor do index como argumento. O index é uma variável que está sendo incrementada a cada iteração do loop e, quando a função playSound é executada, ela usará o valor atual de index.
O problema é que, quando ocorre um clique em uma das teclas, a função playSound é chamada, mas o valor do index já foi incrementado para o número total de teclas (ou seja, teclas.length). Portanto, a função playSound sempre recebe o mesmo valor para idSound, que é o valor de teclas[teclas.length].classList[1], o que provavelmente não existe, gerando o erro que você viu no console.
Para resolver esse problema, a alternativa com o uso de uma const dentro do loop é uma forma de criar um escopo separado para cada iteração do loop. Ao fazer isso, o valor do index é capturado como uma cópia dentro do escopo da constante, e cada função de clique criada dentro do loop terá acesso a esse valor específico.
Aqui está o exemplo corrigido usando uma const dentro do loop:
const teclas = document.querySelectorAll('.tecla');
function playSound(idSound) {
document.querySelector('#som_' + idSound).play();
}
for (let index = 0; index < teclas.length; index++) {
teclas[index].onclick = function () {
playSound(teclas[index].classList[1]);
};
}
Ao usar let index = 0, o index é criado como uma variável de bloco no escopo do loop for, e cada iteração terá um valor diferente para index. Isso evita o problema de closure e faz com que o código funcione corretamente.