utils/music_theory/DOCS-article-walkthrough.md

105 lines
4.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# The article, section by section, as code
Each row: the article's claim, where it lives here, and the demo that lets
you hear it. Run `python -m music_theory demo all --out /tmp/mt` once and
follow along with `aplay`.
## 1. Sound is a number that changes over time
A note is a frequency in Hz. Pitch perception is multiplicative: "one
semitone up" means "multiply by 2^(1/12)", never "add N Hz".
`pitch.midi_to_hz` is that exponent counted from A4 = 440 Hz.
Demo `tone`; `analysis.dominant_frequency` confirms the FFT peak is 440.
## 2. Envelopes and timbre
Volume over time (attack, decay, sustain, release) is what makes a sound a
note. A 10 ms attack reads as struck, 500 ms as bowed; the release is what
stops a click at the end. `synth.Envelope` + `synth.adsr`. Timbre is the
recipe of overtones at 2×, 3×, 4× the fundamental; sine, triangle, square
and saw are four such recipes (`synth.oscillator`). Demos `envelope`,
`waveforms`; the printed spectral centroid rises from sine to saw.
## 3. Octaves and consonance
Doubling the frequency gives the "same" note (`octaves`). Two notes sound
consonant when their ratio is simple: for p/q the combined wave repeats
after q cycles of the lower note (`tuning.repeat_cycles`), so 3/2 locks in
after 2 cycles, 16/15 after 15, and √2 (the tritone) never. Demo
`consonance`.
## 4. The Pythagorean comma
Stack twelve pure fifths and you overshoot seven octaves by
(3/2)¹² / 2⁷ = 1.013643, about 23.5 cents. The proof is one line: 3ⁿ = 2ᵐ
has no integer solution because both are prime. "The system we want does
not exist and never has." `tuning.pythagorean_comma`; demo `comma` plays
A4 against A4 × comma and you hear it beat six times a second.
## 5. Equal temperament, and why twelve
Split the octave into twelve equal multiplicative steps. Every interval
except the octave is slightly wrong, but wrong by the same amount in every
key. The fifth becomes 2^(7/12) = 1.498307, 0.113 % flat.
`tuning.best_divisions` brute-forces every division up to 25: twelve is the
smallest whose fifth is within 0.2 % of 3/2 (`first_acceptable_division`).
Demos `temperament`, `divisions`.
## 6. Scales
A scale is a subset of the twelve notes with UNEVEN gaps, so the ear can
tell where it is. Encode it as the step array: major `[2,2,1,2,2,2,1]`,
minor `[2,1,2,2,1,2,2]`. `scales.build(60, major)` is the article's
`reduce` → `[60,62,64,65,67,69,71,72]`. All seven modes are the same array
rotated (`scales.mode`); aeolian is rotation 5 and equals natural minor.
Demos `scale`, `modes`.
## 7. Chords
Stack every other scale degree (`chords.stacked_thirds`). Major triad
`[0,4,7]`, minor `[0,3,7]`: one array element is the entire happy/sad
difference. Sevenths add one more third. Demo `chords`.
## 8. Keys and Roman numerals
Build a triad on each degree and you get the diatonic chords of the key:
C major → C Dm Em F G Am B° (`chords.diatonic_chords`). Roman numerals are
relative addressing, so `progressions.transpose` is adding one constant to
every MIDI number. Demos `diatonic`, `transpose`.
## 9. Tension and resolution
V7 contains the leading tone (B in C) and a tritone (B–F). Both resolve by
a single semitone into the tonic: B→C, F→E, contrary motion, the tritone
collapsing into a major third. `chords.resolution` returns exactly those
two moves. Common loops (I V vi IV, ii V I, vi IV I V, I vi IV V) are in
`progressions.NAMED`. Demos `resolution`, `progressions`.
## 10. Notation as a serialization format
The staff is diatonic, not chromatic; sharps and flats are the escape
hatch; durations are powers of two and a dot multiplies by 1.5; tempo
turns beats into seconds. Here that is `sequencer.NoteEvent` (pitch, start
beat, length in beats) and `sequencer.beat_seconds`. No demo; the
sequencer is what every later demo renders through.
A hand-written score (`score.py`, `examples/`) spells notes out in text:
3/4, 6/8 or any x/2, x/4, x/8 meter, named voices, noise drums, A/B sections.
## 11. The complete composition
Pick a key and a progression, play the root as bass, the chord tones as an
arpeggio, and a melody drawn from the scale. Constraining every voice to
the scale is what makes any melody fit. `compose.Recipe` +
`compose.compose`, seeded so the same recipe always renders the same bytes.
Demo `piece`.
## Beyond the article: degrade
If the piece is data, "the ending is the opening, destroyed" is a transform
on that data. `degrade` darkens the mode past 0.5, drags the tempo, thins
and detunes the melody, then bit-crushes, hisses and low-passes the render.
Order matters: filtering LAST is what makes the result measurably darker
(spectral centroid 514 Hz against 2207 Hz clean); filtering first left it
brighter than the original. Demo `bracket`.