Pangram verdict · v3.3
We believe that this entire text is AI.
AI likelihood · overall
AIArticle text · 1,739 words · 1 segments analyzed
I can't play an instrument. I have tried more than once, and each time I got as far as being able to make roughly the right noises without ever understanding why they were the right noises. The problem was never the practice. It was that every explanation of music theory seemed to miss out the fundamental reasons for how and why things are the way they are. Here is a staff. Here are the notes on it. This is a major scale, memorise the pattern. Why those notes? Why that pattern? Because that is the convention. Which is a strange way to teach a system that essentially comes out of physics and arithmetic. There are twelve notes for a reason. The major scale has the shape it has for a reason. Chords that sound good sound good for a reason, and you can compute those reasons. So I wanted to start from scratch and learn music from first principles, and I began that journey by writing code. This article is the result. It starts with a single number changing over time, and if you follow along, you will derive the twelve notes, build scales and chords out of arrays, and write a chord progression that sounds like actual music. No instrument needed, and nothing you have to take on faith. Written notation does turn up, but not until the very end, once there is something for it to be notation of. A sound is a number that changes over time Sound is just air pressure wobbling. A speaker makes sound by pushing its cone in and out, and everything your computer does with audio comes down to producing a list of numbers describing where that cone should be, forty-four thousand times a second. An audio file is that list written down. A synthesiser makes the list up as it goes, and the browser will do that part for you if you say what shape you want. The simplest shape is a sine wave, so here is one repeating 440 times a second: A sine wave at 440Hzconst osc = ctx.createOscillator();osc.frequency.value = 440;osc.connect(out);osc.start();osc.stop(ctx.currentTime + 1); ctx and out are mine rather than the browser's. Everything else is the Web Audio API exactly as it ships. To run that snippet anywhere else, start with: const ctx = new AudioContext();const out = ctx.destination; The number 440 is the only thing there that carries any musical meaning, and even that is arbitrary. It is the frequency somebody agreed to call "A", and it is the tuning fork the rest of the system is pinned to. Change it to 300 and run it again. You get a different pitch and nothing breaks, because at this level there are no notes yet, just a number. Frequency is pitch: higher number, higher note. That is the entire mapping, and it is the last thing about music that will be this simple. Why that note clicked You may have heard a little click at the end of that. That is not a bug in the browser, it is physics being unforgiving. The oscillator was mid-wave when it stopped, so the speaker cone was somewhere out at the edge of its travel and then instantly snapped back. An instant jump in pressure is what a click is. The fix is a second number that changes over time, this one controlling volume rather than pitch. Musicians call the shape of it an envelope: The same note with an envelopeconst osc = ctx.createOscillator();osc.frequency.value = 440;const env = ctx.createGain();const t = ctx.currentTime;env.gain.setValueAtTime(0, t);env.gain.linearRampToValueAtTime(0.3, t + 0.01);env.gain.exponentialRampToValueAtTime(0.001, t + 1);osc.connect(env).connect(out);osc.start(t);osc.stop(t + 1); An envelope is the volume curve of a single note, from silence back to silence. The rise at the front is the attack and the fall afterwards is the decay. Ten milliseconds to fade in, then a slow decay to nearly nothing. That is the difference between a test tone and something you would be willing to listen to twice. attack 10msdecay 1.00s Drag the attack out towards half a second and the note stops arriving and starts swelling. It is no longer something struck, it is something bowed, and the pitch has not moved by a single hertz. The envelope is doing more work here than the frequency is. This is a simplified envelope, though. The full version is ADSR: attack, decay, sustain and release, where sustain is the level a note holds at while a key is down, and release is how it fades once you let go. The function below is a helper that each subsequent example uses: function note(freq, start = 0, length = 0.5, type = "sine") { const t = ctx.currentTime + start; const osc = ctx.createOscillator(); const env = ctx.createGain(); osc.type = type; osc.frequency.value = freq; env.gain.setValueAtTime(0, t); env.gain.linearRampToValueAtTime(0.3, t + 0.01); env.gain.exponentialRampToValueAtTime(0.001, t + length); osc.connect(env).connect(out); osc.start(t); osc.stop(t + length);} Timbre is the frequencies you did not ask for A sine wave is a single frequency and nothing else, which is why it sounds like a hearing test and unlike any instrument. Pluck a guitar string tuned to 440Hz and you do get a wave repeating 440 times a second, but the string is also vibrating in halves, and in thirds, and in quarters, all at the same time. Those are extra frequencies at 880, 1320, 1760 and on up, all riding on top of the one you asked for. That stack is called the harmonic series. The note you asked for is the fundamental, and the series is that frequency multiplied by 1, 2, 3, 4, 5 and on up: 1x220Hz2x440Hz3x660Hz4x880Hz5x1100Hz6x1320Hz7x1540Hz8x1760HzThe harmonic series of 220Hz. Click a bar to hear that harmonic on its own. Click the bars. On their own they are fairly boring. What matters is that they arrive as a package, and the recipe of how loud each one is relative to the others is what makes a violin sound like a violin and not a trumpet. Musicians call that timbre, and it is the same note either way. The browser ships four of those recipes ready-made: Four waveforms, same pitch["sine", "triangle", "square", "sawtooth"].forEach((type, i) => { note(220, i * 0.7, 0.6, type);}); Same 220Hz, four very different characters. A square wave contains only the odd harmonics, which is why it sounds hollow and slightly electronic. A sawtooth contains all of them and sounds harsh and buzzy. The scope above shows the shape of each one as it plays, and the shape is the harmonic recipe. Remember the harmonic series, because it is about to explain the entire rest of this article. Every note you play drags a stack of quiet extra notes along with it, and which notes those are is not up to us. It is arithmetic, fixed by the physics of vibrating strings and columns of air, and it comes out the same on every instrument built around them. Doubling the frequency gives you the same note Here are five notes. Every one is double the frequency of the one before it. One note, five times[110, 220, 440, 880, 1760].forEach((freq, i) => note(freq, i * 0.45, 0.4),); They are different pitches, and yet they sound like the same note. Not just similar, the same. Cultures with no contact with each other have landed on this independently: double the frequency and you get something so alike it deserves the same name. In Western notation these frequencies, in the above example, are all called A, and the distance between them is the octave. The naming is not arbitrary, and the harmonic series explains why. Every harmonic of 440 is already sitting in the harmonic series of 220, because 220's series is 220, 440, 660, 880, 1100 and 440's is 440, 880, 1320, 1760. The higher note adds no frequency the lower note was not already producing. It is not a new colour, it is the same colour, brighter. 220Hz and 440Hz, repeating every cycle of the lower note Two things fall straight out of that. Pitch is multiplicative, not additive. Going up an octave means times two, not plus anything. The gap from 110 to 220 is 110Hz and the gap from 880 to 1760 is 880Hz, and they sound like exactly the same distance. Frequency space is logarithmic, and every interval in music is a ratio. We only have to solve one octave. Because doubling returns you to the same note, the entire problem of "which pitches should exist" reduces to "how should we divide up the space between a frequency and twice that frequency". Solve it once and the answer tiles the whole audible range for free. So: how do you divide an octave? Simple ratios sound good, and here is why The naive answer is to divide it evenly and go home. Nobody does that, because it turns out we do not experience all pairs of frequencies the same way. Some combinations sound settled and some sound like a mistake, and you can hear the difference immediately. Six ratios against the same noteconst ratios = [ ["2/1 octave", 2], ["3/2 fifth", 3 / 2], ["4/3 fourth", 4 / 3], ["5/4 major third", 5 / 4], ["16/15 semitone", 16 / 15], ["√2 the awkward one", Math.SQRT2],];ratios.forEach(([label, ratio], i) => { note(220, i * 1.4, 1.2); note(220 * ratio, i * 1.4, 1.2); console.log(label, "->", (220 * ratio).toFixed(2) + "Hz");}); The first four sound like chords. The 16/15 sounds like two notes arguing. The last one sounds like a car alarm. And the pattern is not subtle once you see it: the simpler the fraction, the better it sounds. 2/1 the octave, then 3/2 the fifth, then 4/3 the fourth, then 5/4 the major third, and by the time you get to 16/15 it has fallen apart entirely. That is a suspiciously arithmetic result for something as subjective as "sounds nice", and there are two physical reasons for it. The first is the harmonic series again. Play 220 and 330 together, which is a 3:2 ratio. The first note produces 220, 440, 660, 880, 1100, 1320. The second produces 330, 660, 990, 1320, so they share 660 and 1320 exactly. Two notes a fifth apart are not really two separate sounds, they are two heavily overlapping stacks that reinforce each other. Now try 220 and 311, which is close to √2. Nothing lines up, at any harmonic. You get two full stacks of frequencies that have nothing to do with each other. The second reason is