📜 ⬆️ ⬇️

Piano in 24 lines on Javascript: if you play, then music

As long as handset makers measure who is thinner, programmers continue to measure who is shorter.

I also decided to take part in this special spontaneous Olympiad of coding skills, and remembered the phrase of one of my musician friends: “If you play, then on the piano”. And I decided: so be it. Instead of playing, I'll write a piano. And wrote .

I’ll make a reservation right away: I’m never a musician, my musical education is limited to a dozen thieves' songs on an upset guitar, so with the terminology I can and I will surely lie godlessly, but I’ll be damn glad if you correct me.
')
So, let's begin.

The keyboard of a classical piano consists of 88 keys covering the range from A0 (A la sub-counter-octave, sound frequency 27.5 Hz) to C8 (Up to the fifth octave, frequency 4186 Hz). Each octave on the keyboard consists of twelve notes:
Before, C-sharp , D, D-sharp , Mie, Fa, F-sharp , Sol, G-sharp , A, A-sharp / B-flat , C. The bold keys are the top row, they are usually black on the keyboard.

Actually, this is what one octave looks like:

image

Just by looking at the frequency table of the notes , the pattern becomes obvious: each successive octave is exactly twice as high as the previous one. Thus, we can say that:

Nx = N1 × 2 x-1 , where:

N1 instead of N0 appears in the formula only because part of the sub-counter-octave notes (N0) has a sound frequency below the threshold of hearing by the human ear (<20 Hz).

In order for the notes to turn out clean, we need sufficiently accurate values ​​of the frequencies of the notes of the contraktawa, from which we begin to count. Actually, here they are:

C: 32.703,
C #: 34.648,
D: 36.708,
D #: 38.891,
E: 41.203,
F: 43.654,
F #: 46.249,
G: 48.999,
G #: 51.913,
A: 55,
A #: 58.27,
B: 61.735

Based on this, we write a function that takes as its argument a string with the name of the key in the form of "A4" or "C5#" , and returns the frequency of its sound:

 function play(key) { var controctave = { 'C': 32.703, '#': 34.648, 'D': 36.708, 'D#': 38.891, 'E': 41.203, 'F': 43.654, 'F#': 46.249, 'G': 48.999, 'G#': 51.913, 'A': 55, 'A#': 58.27, 'B': 61.735, }, note = key[0].toUpperCase(), octave = parseInt(key[1]), sharp = key[2] == '#' ? true : false; if (sharp) { return controctave[note + '#'] * Math.pow(2, octave-1); } else { return controctave[note] * Math.pow(2, octave-1); } } 

Oh yes, we do not write beautifully, but briefly. Slightly shorten:

 function play(key) { var controctave = { 'C': 32.703, '#': 34.648, 'D': 36.708, 'D#': 38.891, 'E': 41.203, 'F': 43.654, 'F#': 46.249, 'G': 48.999, 'G#': 51.913, 'A': 55, 'A#': 58.27, 'B': 61.735}; freq = key[2] == '#' ? controctave[key[0].toUpperCase() + '#'] * Math.pow(2, (key[1]|0) - 1) : controctave[key[0].toUpperCase()] * Math.pow(2, (key[1]|0) - 1); return freq; } 


Already used four lines of code.

Let's draw the keyboard

The 88 keyboard keys start with the note A (A0).
Accordingly, the cycle will be the following: in the cycle we draw 12 keys, and every second, fourth, seventh, ninth and eleventh we make black. Each key is assigned an id corresponding to the note that it should play when pressed.

In general, as follows:

 var width = 1000; var deck = document.createElement('div'), octave = ['A', 'A#', 'B', 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#'], id = "", keynumber = 0, whitekeys = 0, keys = []; deck.style.width = width; parent: for (var i = 0; i < 8; i++) { for (var j = 0; j < 12; j++) { keynumber = (i * 12) + j; if (keynumber >= 88) break parent; keys[keynumber] = document.createElement('div'); keys[keynumber].style.border = "1px solid black"; keys[keynumber].style.position = "absolute"; id = (octave[j][1] == '#') ? octave[j] + i + 's' : octave[j] + i; keys[keynumber].id = id; switch(j%12) { case 1: case 3: case 6: case 8: case 10: keys[keynumber].style.backgroundColor = 'black'; keys[keynumber].style.left = ((width / 50 * whitekeys) - (width / 200)) + 'px'; keys[keynumber].style.width = width/100 + "px"; keys[keynumber].style.height = "200px"; keys[keynumber].style.zIndex = 10; break; default: keys[keynumber].style.backgroundColor = 'white'; keys[keynumber].style.left = (width / 50 * whitekeys) + 'px'; keys[keynumber].style.width = width/50 + "px"; keys[keynumber].style.height = "300px"; whitekeys++; } deck.appendChild(keys[keynumber]); } } document.body.appendChild(deck); 

And once again turn the normal code into unreadable govnishch apply a small optimization.

 var width = 1000, octave = ['A', 'A#', 'B', 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#'], id = "", div, whitekeys=0, keys = []; parent: for (var i = 0; i < 8; i++) { for (var j = 0; j < 12; j++) { if ((i * 12) + j >= 88) break parent; div = document.createElement('div'); div.id = (octave[j][1] == '#') ? octave[j][0] + ((((i * 12) + j + 9) / 12)|0) + 's' : octave[j] + ((((i * 12) + j + 9) / 12)|0); if (j % 12 == 1 || j % 12 == 4 || j % 12 == 6 || j % 12 == 9 || j % 12 == 11) { div.setAttribute('style', 'border:1px solid black; position:absolute; background-color:black; left:' + ((width / 50 * whitekeys) - (width / 200)) + 'px; width:' + width/100 + 'px; height: 200px; z-index:1;');} else { div.setAttribute('style', 'border:1px solid black; position:absolute; background-color:white; left:' + (width / 50 * whitekeys) + 'px; width:' + width/50 + 'px; height:300px;'); whitekeys++; } document.body.appendChild(div);}} 


We spent another 13 lines.

Teach the piano to make sounds

For this we need the Web Audio API , which at this moment is supported only by Webkit-based browsers and Firefox.

Add the creation of the audio context to the line of the declaration of global variables
 context = window.AudioContext ? new AudioContext() : new webkitAudioContext(); 

add a keystroke handler:
 document.body.addEventListener('click', play); 

and the play function itself is changed as follows:
 function play(e) { var controctave = { 'C': 32.703, 'Cs': 34.648, 'D': 36.708, 'Ds': 38.891, 'E': 41.203, 'F': 43.654, 'Fs': 46.249, 'G': 48.999, 'Gs': 51.913, 'A': 55, 'As': 58.27, 'B': 61.735}, osc = context.createOscillator(); osc.frequency.value = e.target.id[2] == 's' ? controctave[e.target.id[0] + 's'] * Math.pow(2, (e.target.id[1]|0) - 1) : controctave[e.target.id[0]] * Math.pow(2, (e.target.id[1]|0) - 1); osc.type = "square"; osc.connect(context.destination); osc.start(0); setTimeout(function() { osc.stop(0); osc.disconnect(context.destination); }, 1000 / 2);} 


Here we created an oscillator: osc = context.createOscillator(); , set the necessary frequency for it: osc.frequency.value = e.target.id[2] == 's' ? controctave[e.target.id[0] + 's'] * Math.pow(2, (e.target.id[1]|0) - 1) : controctave[e.target.id[0]] * Math.pow(2, (e.target.id[1]|0) - 1); osc.frequency.value = e.target.id[2] == 's' ? controctave[e.target.id[0] + 's'] * Math.pow(2, (e.target.id[1]|0) - 1) : controctave[e.target.id[0]] * Math.pow(2, (e.target.id[1]|0) - 1); (well, we don’t follow the cleanliness and neatness of the code, do we?), set the waveform: osc.type = "square"; (the default was sinusoidal) connected it to an audio output device: osc.connect(context.destination); , and gave the command to start playback: osc.start(0); . After that, we need to silence the key after a while (500ms), otherwise it will be squeaking like that. For this we use osc.stop(0) , wrapped in an interval. Required element is osc.disconnect(context.destination); - disconnect the oscillator from the output device.

We summarize: we have such a simple code:

 var width = 1000, octave = ['A', 'A#', 'B', 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#'], id = "", div, whitekeys=0, keys = [],context = window.AudioContext ? new AudioContext() : new webkitAudioContext(); parent: for (var i = 0; i < 8; i++) { for (var j = 0; j < 12; j++) { if ((i * 12) + j >= 88) break parent; div = document.createElement('div'); div.id = (octave[j][1] == '#') ? octave[j][0] + ((((i * 12) + j + 9) / 12)|0) + 's' : octave[j] + ((((i * 12) + j + 9) / 12)|0); if (j % 12 == 1 || j % 12 == 4 || j % 12 == 6 || j % 12 == 9 || j % 12 == 11) { div.setAttribute('style', 'border:1px solid black; position:absolute; background-color:black; left:' + ((width / 50 * whitekeys) - (width / 200)) + 'px; width:' + width/100 + 'px; height: 200px; z-index:1;');} else { div.setAttribute('style', 'border:1px solid black; position:absolute; background-color:white; left:' + (width / 50 * whitekeys) + 'px; width:' + width/50 + 'px; height:300px;'); whitekeys++; } document.body.appendChild(div);}} document.body.addEventListener('click', play); function play(e) { var controctave = { 'C': 32.703, 'Cs': 34.648, 'D': 36.708, 'Ds': 38.891, 'E': 41.203, 'F': 43.654, 'Fs': 46.249, 'G': 48.999, 'Gs': 51.913, 'A': 55, 'As': 58.27, 'B': 61.735}, osc = context.createOscillator(); osc.frequency.value = e.target.id[2] == 's' ? controctave[e.target.id[0] + 's'] * Math.pow(2, (e.target.id[1]|0) - 1) : controctave[e.target.id[0]] * Math.pow(2, (e.target.id[1]|0) - 1); osc.type = "square"; osc.connect(context.destination); osc.start(0); setTimeout(function() { osc.stop(0); osc.disconnect(context.destination); }, 1000 / 2);} 


In conclusion, I want to say that now I need to call the Stradivarius of the XXI century Web Audio API - a very cool and interesting thing. You can read about it, of course, on MDN , I can advise a nice tutorial on HTML5Rocks and one more fun experiment .

And the piano came out terribly primitive, but I'm still satisfied with the experiment. I hope you were interested too.

To play

View code

PS Macbook speakers, for example, refuse to make audible sounds down to a small octave (ie, up to 130 Hz), which is not surprising. In general, do not be surprised if the left side of the keyboard does not seem to sound at all.

Source: https://habr.com/ru/post/202646/


All Articles