diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml new file mode 100644 index 00000000..f80e24bc --- /dev/null +++ b/.github/workflows/check.yaml @@ -0,0 +1,35 @@ +name: Check + +on: + pull_request: + push: + branches: [master] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: check-${{ github.ref }} + cancel-in-progress: true + +jobs: + check: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v6.0.2 + with: + submodules: recursive + + - name: Install Nix + uses: cachix/install-nix-action@v31 + with: + nix_path: nixpkgs=channel:nixos-unstable + + - name: Install Rust toolchain + run: nix develop . --command rustup toolchain install --profile minimal stable + + - name: Check + run: nix develop . --command cargo run --locked --manifest-path tools/site/Cargo.toml -- check diff --git a/.gitignore b/.gitignore index 67d82784..a68b3570 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ tsconfig.tsbuildinfo .quartz/ quartz/cli/tui/dist/ private/ +private/photography/originals/ .replit replit.nix diff --git a/README.md b/README.md index 507e2ec0..5b94433f 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ build system: ```sh cargo run --locked --manifest-path tools/site/Cargo.toml -- build +cargo run --locked --manifest-path tools/site/Cargo.toml -- check cargo run --locked --manifest-path tools/site/Cargo.toml -- generate cargo run --locked --manifest-path tools/site/Cargo.toml -- help ``` diff --git a/content/_index.md b/content/_index.md index 9c98e8da..43920431 100644 --- a/content/_index.md +++ b/content/_index.md @@ -18,6 +18,7 @@ Content - [notes](/notes/) - [projects](/projects.md) - [art](/art.md) +- [photography](/photography.md) - [misc](/misc/) - [tools](/tools/) - [about](/about.md) diff --git a/content/blog/recursive-just-intonation.md b/content/blog/recursive-just-intonation.md index a7de6bc0..11428566 100644 --- a/content/blog/recursive-just-intonation.md +++ b/content/blog/recursive-just-intonation.md @@ -1,205 +1,283 @@ --- -title: Recursive Just-Intonation +title: Recursive Just Intonation date: 2022-11-29 -updated: 2024-12-09 +updated: 2026-05-04 tags: - music - programming --- +## Recursive Just Intonation + +Equal temperament gives us one keyboard. Every C# is the same C#, every G is +the same G, and every semitone is the same distance from the last one. That is +extremely useful. It is also a compromise: the intervals are close enough to +simple ratios that they work, but most of them are not exact. + +Just intonation goes the other way. It starts from simple frequency ratios: + +- octave: `2/1` +- perfect fifth: `3/2` +- perfect fourth: `4/3` +- major third: `5/4` +- major chord: `4:5:6`, or `1/1`, `5/4`, `3/2` + +Those ratios sound still and locked-in because their waveforms repeat against +each other quickly. A just major chord is not only "close" to consonant; it is +consonant by construction. + +The catch is that just intonation normally needs a root. A `5/4` major third +above C is E. A `5/4` major third above E is G#/Ab. Those two facts cannot both +fit into one fixed 12-note keyboard unless we allow the same pitch name to mean +different frequencies in different harmonic contexts. + +That is the idea of this experiment: + +> Keep the roots on a C-based just-intonation keyboard, but give every chord +> root its own just-intonation keyboard. + +I think of it as 12 pianos: one just piano rooted on C, one on C#/Db, one on D, +and so on. The root of each piano is taken from the original C just-intonation +scale. Once a chord chooses a root, all of its notes come from the piano rooted +on that note. + +This is "recursive" in the simple algorithmic sense: use a just-ratio table to +choose the chord root, then use the same ratio table again inside that root. + +## 12-TET + +In 12-tone equal temperament, the ratio between adjacent semitones is: + +```text +2^(1/12) = 1.059463... +``` + +The frequency of a note `n` semitones above some reference note is: + +```text +frequency(n) = reference * 2^(n/12) +``` + +The nice property is composability: + +```text +2^(1/12) * 2^(1/12) = 2^(2/12) +``` + +Going up two semitones one step at a time lands at the same frequency as +jumping up two semitones directly. This is why transposition is easy in equal +temperament. There is only one global grid. + +## One C Just Scale + +For the C-based just-intonation scale used in my tuning playground, the 12 pitch +classes are: + +| pitch | ratio from C | +| --- | ---: | +| C | `1/1` | +| C#/Db | `17/16` | +| D | `9/8` | +| D#/Eb | `19/16` | +| E | `5/4` | +| F | `4/3` | +| F#/Gb | `45/32` | +| G | `3/2` | +| G#/Ab | `51/32` | +| A | `27/16` | +| A#/Bb | `57/32` | +| B | `15/8` | + +This already makes a C major chord exact: + +```text +C = 1/1 +E = 5/4 +G = 3/2 +``` + +But an E major chord on the same fixed C just keyboard has a problem: + +```text +E = 5/4 +G#/Ab = 51/32 +B = 15/8 +``` + +Relative to E, the G#/Ab is: + +```text +(51/32) / (5/4) = 51/40 = 1.275 +``` + +A just major third should be `5/4 = 1.25`. So the E major chord has a fifth that +works and a third that is too high by about `34.3` cents. That is not a tiny +rounding error. It is enough to make the chord feel tense. + +## 12 Just Pianos + +Recursive just intonation changes only one rule: after choosing a root, restart +the ratio table at that root. + +For an E major chord: + +```text +E = C * 5/4 +G#/Ab = E * 5/4 = C * 25/16 +B = E * 3/2 = C * 15/8 +``` + +Now the E major chord is internally just: + +```text +E : G# : B = 1 : 5/4 : 3/2 = 4 : 5 : 6 +``` + +The cost is that `G#/Ab` is no longer globally stable. Fixed-C just intonation +puts G#/Ab at `51/32` from C. Recursive just intonation puts the G#/Ab inside E +major at `25/16` from C. + +```text +fixed C just G#/Ab = 51/32 = 1.59375 +recursive E-major G# = 25/16 = 1.5625 +``` + +Those are different notes hiding under the same name. + +The general formula is: + +```text +recursive_frequency(root, degree) = + C_frequency * J[root] * J[degree] +``` + +where `J[x]` is the just-ratio table above, with octave correction whenever the +index crosses C again. + +So the system is not a 12-note tuning system anymore. It is a chord-contextual +tuning system. Pitch classes split according to harmonic function. + +## What It Sounds Like + +I wrote a small Rust renderer for this post. It creates the same chord +progression three ways: + +```text +C -> E -> G#/Ab -> C -> F -> A -> D -> G7 -> C -> E -> F -> C +``` + +The progression deliberately visits chords where fixed-C just intonation has +audible trouble. In the recursive version, each chord gets to retune itself +around its own root. + +Generate the files with: + +```sh +cargo run --manifest-path tools/site/Cargo.toml -- recursive-ji-music ``` -░▒█░░▒█░▀█▀░▒█▀▀█░▒█░░▒█░▀█▀░▒█▀▀█░▒█░░▒█░▀█▀░▒█▀▀█░▒█░░▒█░▀█▀░▒█▀▀█░▒█░░▒█░▀█▀░▒█▀▀█░▒█░░▒█░▀█▀░▒█▀▀█░▒█░░▒█░▀█▀░▒█▀▀█░▒█░░▒█░▀█▀░▒█▀▀█░▒█░░▒█░▀█▀░▒█▀▀█░▒█░░▒█░▀█▀░▒█▀▀█░▒█░░▒█░▀█▀░▒█▀▀█░▒ -░▒█▒█▒█░▒█░░▒█▄▄█░▒█▒█▒█░▒█░░▒█▄▄█░▒█▒█▒█░▒█░░▒█▄▄█░▒█▒█▒█░▒█░░▒█▄▄█░▒█▒█▒█░▒█░░▒█▄▄█░▒█▒█▒█░▒█░░▒█▄▄█░▒█▒█▒█░▒█░░▒█▄▄█░▒█▒█▒█░▒█░░▒█▄▄█░▒█▒█▒█░▒█░░▒█▄▄█░▒█▒█▒█░▒█░░▒█▄▄█░▒█▒█▒█░▒█░░▒█▄▄█░▒ -░▒▀▄▀▄▀░▄█▄░▒█░░░░▒▀▄▀▄▀░▄█▄░▒█░░░░▒▀▄▀▄▀░▄█▄░▒█░░░░▒▀▄▀▄▀░▄█▄░▒█░░░░▒▀▄▀▄▀░▄█▄░▒█░░░░▒▀▄▀▄▀░▄█▄░▒█░░░░▒▀▄▀▄▀░▄█▄░▒█░░░░▒▀▄▀▄▀░▄█▄░▒█░░░░▒▀▄▀▄▀░▄█▄░▒█░░░░▒▀▄▀▄▀░▄█▄░▒█░░░░▒▀▄▀▄▀░▄█▄░▒█░░░░▒ + +The generator writes WAV examples and a CSV frequency report into +`content/blog/recursive-just-intonation/`. + +
+ +
12-TET: stable pitch classes, compromised intervals.
+
+ +
+ +
Fixed C just intonation: C is beautiful, but remote chords start leaning hard.
+
+ +
+ +
Recursive just intonation: each chord is tuned from its own C-derived root.
+
+ +There is also a more clinical example that alternates a fixed-C pitch with its +recursive chord-local version, then plays both at once so the beating is easier +to hear: + +
+ +
Pitch-name splits: same nominal note, different chord context.
+
+ +Some of the generated split points: + +| chord context | note | fixed C JI | recursive JI | difference | +| --- | --- | ---: | ---: | ---: | +| E major | G#/Ab | `208.483 Hz` | `204.395 Hz` | `-34.283 cents` | +| F major | A | `220.747 Hz` | `218.021 Hz` | `-21.506 cents` | +| A major | C#/Db | `277.977 Hz` | `275.933 Hz` | `-12.777 cents` | +| G7 | F | `348.834 Hz` | `349.515 Hz` | `+3.378 cents` | + +## Why This Is Nice + +The upside is direct: every major chord can be made into a clean `4:5:6` +relationship, even if the chord root is not C. E major does not inherit C's +G#/Ab; it gets its own G#/Ab. F major does not inherit C's A; it gets its own A. + +This matches how harmony often behaves perceptually. When a chord arrives, the +ear can accept the chord root as a local gravitational center. Recursive just +intonation uses that local center instead of forcing every chord to negotiate +with one global keyboard. + +It is also a useful programming model. A chord can be rendered as: + +```text +root_frequency = base_frequency * global_just_ratio[root] +note_frequency = root_frequency * local_just_ratio[chord_degree] ``` -## Recursive Just-Intonation: An unusable Tuning System or a Frustrating Journey through tuning - -[Play around with different tuning systems and your computer keyboard](/playground) - -## 12Tone Equal Temperament: the current standard - - - -In 12TET the ratio P between two tones is defined as -Pn=Pa(212)(n−a)P_n = P_a\big(\sqrt[12]{2}\big)^{(n-a)} -or -Pn=Pa2(n−a)⁄12P_n = P_a2^{(n-a)/12} -where n is the index of the second tone and a is the index of the first tone a starting at one. -Which means that to go one semitone up you have to multiply your current frequency by -2(2−1)⁄122^{(2-1)/12} -which is approximately equal to 1.059463... - -This serves the purpose of making sure all steps have the same size, relative to their base frequency (every step is 100 cents). -e.g multiplying a frequency by -2(2−1)⁄122^{(2-1)/12} -7 times in a row is the same as going 7 steps at once, which is a nice property that's true only for equal temperament systems. - -proof: - - - -28−112=(22−112)72712=2(2−112)⋅7=214−712=2712\begin{align*} -2^{ \frac{{8-1}}{{12}} } &= \left( 2^{ \frac{{2-1}}{{12}} } \right)^7 \\ -2^{ \frac{{7}}{{12}} } &= 2^{ \left( \frac{{2-1}}{{12}} \right) \cdot 7 } \\ -&= 2^{ \frac{{14-7}}{{12}} } \\ -&= 2^{ \frac{{7}}{{12}} } \\ -\end{align*} - -here's a table of the ratios (rounded to 6 decimal places) - -
-+-----+----------+----+----------+
-| N   | Ratio    | N  | Ratio    |
-+-----+----------+----+----------+
-| -12 | 0.5      | 1  | 1        |
-| -11 | 0.529732 | 2  | 1.059463 |
-| -10 | 0.561231 | 3  | 1.122462 |
-| -9  | 0.594604 | 4  | 1.189207 |
-| -8  | 0.629961 | 5  | 1.259921 |
-| -7  | 0.629961 | 6  | 1.33484  |
-| -6  | 0.66742  | 7  | 1.414214 |
-| -5  | 0.707107 | 8  | 1.498307 |
-| -4  | 0.749154 | 9  | 1.587401 |
-| -3  | 0.793701 | 10 | 1.681793 |
-| -2  | 0.840896 | 11 | 1.781797 |
-| -1  | 0.890899 | 12 | 1.887749 |
-| 0   | 0.943874 | 13 | 2        |
-+-----+----------+----+----------+
-
- -## Just Intonation: - -In Just Intonation we take the ratios directly from the overtone series. -so as an exercise let's derrive them ourselves: -as a base frequency we'll use 1 -to construct the overtone series we just start multiplying it with the Natural Number series: -let's have a look at the 64 first overtones - -we can calculate the ratios by diving the overtone's frequency(or it's ratio to the base tone) by the next smaller power of 2 - -
-┬─[hill@nixos:~]─[20時10分43秒]─[I]
-╰─> λ math 7 / 4
-1.75
-┬─[hill@nixos:~]─[20時10分54秒]─[I]
-╰─> λ math 9 / 8
-1.125
-┬─[hill@nixos:~]─[20時11分02秒]─[I]
-╰─> λ math 11 / 8
-1.375
-┬─[hill@nixos:~]─[20時11分12秒]─[I]
-╰─> λ math 13 / 8
-1.625
-┬─[hill@nixos:~]─[20時11分26秒]─[I]
-╰─> λ math 15 / 8
-1.875
-┬─[hill@nixos:~]─[20時11分55秒]─[I]
-╰─> λ math 17 / 16
-1.0625
-
- -here are some tables - -
-+----------+--------+---------+-------+             +----------+----+---------+-------+
-| Overtone | N      | Ratio   | Ratio |             | Overtone | N  | Ratio   | Ratio |
-+----------+--------+---------+-------+             +----------+----+---------+-------+
-| 1        | 1      | 1       | 1/1   |             | 1        | 1  | 1       | 1/1   |
-| 3        | 8      | 1.5     | 3/2   |             | 3        | 8  | 1.5     | 3/2   |
-| 5        | 5      | 1.25    | 5/4   |             | 5        | 5  | 1.25    | 5/4   |
-| 7        | unused | 1.75    | 7/4   |             | 9        | 3  | 1.125   | 9/8   |
-| 9        | 3      | 1.125   | 9/8   |             | 15       | 12 | 1.875   | 15/8  |
-| 11       | unused | 1.375   | 11/8  |             | 17       | 2  | 1.0625  | 17/16 |
-| 13       | unused | 1.625   | 13/8  |             | 19       | 4  | 1.1875  | 19/16 |
-| 15       | 12     | 1.875   | 15/8  |             | 27       | 10 | 1.6875  | 27/16 |
-| 17       | 2      | 1.0625  | 17/16 |             | 45       | 7  | 1.40625 | 45/32 |
-| 19       | 4      | 1.1875  | 19/16 |             | 51       | 9  | 1.59375 | 51/32 |
-| 21       | unused | 1.3125  | 21/16 |             | 57       | 11 | 1.78125 | 57/32 |
-| 23       | unused | 1.4375  | 23/16 |             +----------+----+---------+-------+
-| 25       | unused | 1.5625  | 25/16 |
-| 27       | 10     | 1.6875  | 27/16 |
-| 29       | unused | 1.8125  | 29/16 |
-| 31       | unused | 1.9375  | 31/32 |
-| 33       | unused | 1.03125 | 33/32 |             +----+----------+------------+-------+
-| 35       | unused | 1.09375 | 35/32 |             | N  | Overtone | Ratio      | Ratio |
-| 37       | unused | 1.15625 | 37/32 |             +----+----------+------------+-------+
-| 39       | unused | 1.21875 | 39/32 |             | 1  | 1        | 1          | 1/1   |
-| 41       | unused | 1.28125 | 41/32 |             | 2  | 17       | 1.0625     | 17/16 |
-| 43       | unused | 1.34375 | 43/32 |             | 3  | 9        | 1.125      | 9/8   |
-| 45       | 7      | 1.40625 | 45/32 |             | 4  | 19       | 1.1875     | 19/16 |
-| 47       | unused | 1.46875 | 47/32 |             | 5  | 5        | 1.25       | 5/4   |
-| 49       | unused | 1.53125 | 49/32 |             | 6  | N/A      | 1.33333... | 4/3   |
-| 51       | 9      | 1.59375 | 51/32 |             | 7  | 45       | 1.40625    | 45/32 |
-| 53       | unused | 1.65625 | 53/32 |             | 8  | 3        | 1.5        | 3/2   |
-| 55       | unused | 1.71875 | 55/32 |             | 9  | 51       | 1.59375    | 51/32 |
-| 57       | 11     | 1.78125 | 57/32 |             | 10 | 27       | 1.6875     | 27/16 |
-| 59       | unused | 1.84375 | 59/32 |             | 11 | 57       | 1.78125    | 57/32 |
-| 61       | unused | 1.90625 | 61/32 |             | 12 | 15       | 1.875      | 15/8  |
-| 63       | unused | 1.96875 | 63/32 |             | 13 | 2        | 2          | 2/2   |
-+----------+--------+---------+-------+             +----+----------+------------+-------+
-
- -skipping over any duplicate ratios, we can find all 12 tones of the western tuning system, apart from the perfect fourth, in the first 64 overtones. -the reason we can't find the perfect fourth is that it's ratio of 4/3 has a rational denominator so it can never be part of the overtone series directly. -i.e. {43×2n|n∈ℕ}⊆ℚ∖ℕ\biggl\{\frac{4}{3}\times 2^{n} \bigg| n \in \mathbb{N}\biggr\}\subseteq \mathbb{Q}\setminus\mathbb{N} but it is present nonetheless as the ratio between individual overtones, for example between the 3rd and the 4th overtone (4/3). - -The nice thing about Just intonation is that we have exact ratios, - - - -## What makes one interval nice and another unpleasant - -Nice mathematical ratios are pleasant to our ears. -x+2\*x where x is some frequency is gonna sound nice, because it has a short period, - - - -while for example x+13/12x has a much longer period - - - -## Why Just Intonation is good - -Waves that are nice to look at are nice to the Ear. -Just Intonation is nice because intervals have nice mathematical ratios. For Example, a major chord is 4:5:6 (1:1.25:1.5). -While in 12TET a major cord is 500:630:749 (1:1.260:1.498) -the following graph shows the difference between the just intonated major chord and the 12TET major chord. - - - - - -## Why Just Intonation is bad - -1.06252≠1.1251.0625²\ne1.125 - -but - -(2(2−1)/12)2=2(3−1/12)(2^{(2-1)/12})² = 2^{(3-1/12)} - -Now while just intonated intervals are nicer all of these intervals are in relation to X, our Root -While a major third (4:5) and a perfect fifth (2:3) on their own sound good, -if we keep going up the steps one by one (1.0625), we don't end up at the same place that we would end up if we skipped a step (1.125) -(i.e. just intonation does't have the property mentioned earlier) - -## My other music related work: - -[play around with different tuning systems and your computer keyboard](/tools/tuningplayground.md) - -### Visualize and listen to Polyrhythms in a Shader: +The same pure function works for any root. + +## Why This Is Bad + +The downside is also direct: the same note name can jump when the chord changes. + +In 12-TET, G#/Ab is one frequency per octave. In fixed C just intonation, G#/Ab +is also one frequency per octave, just a different one. In recursive just +intonation, G#/Ab depends on why you are playing it. + +That creates several problems: + +- A melody can wobble if a held pitch is reinterpreted by the next chord. +- Enharmonic spelling starts to matter, but a 12-key interface usually hides it. +- Modulation becomes a negotiation between smooth voice-leading and pure local + chords. +- Instruments with fixed frets, keys, or holes cannot do this without pitch + bending or multiple samples per pitch class. + +So this is not a replacement for equal temperament. Equal temperament is still +the heroic compromise that lets every key share one physical instrument. + +Recursive just intonation is more like a harmonic microscope. It lets us hear +what equal temperament flattens: a chord tone is not only a key on a keyboard, +it is also a relationship to a root. + +## Implementation Notes + +The Rust generator lives in `tools/site/src/recursive_ji.rs`. It does not use +MIDI, because standard MIDI note numbers assume fixed pitch classes unless you +add extra tuning messages. Instead it writes 16-bit mono WAV files directly. + +The renderer supports three tunings: + +- `12-TET`: `C4 * 2^(n/12)` +- `Fixed C just intonation`: `C4 * J[pitch_class] * octave` +- `Recursive just intonation`: `C4 * J[root] * J[chord_degree] * octave` + +The current implementation intentionally uses the same 12-entry table as the +blog post and tuning playground. That means the sound of a "minor third" depends +on the chosen chromatic just table. A future version could swap in a 5-limit or +7-limit table per chord quality, but this version keeps the experiment clean: +one C just scale, reused recursively as 12 local pianos. + +## My Other Music Work + +[Play around with different tuning systems and your computer keyboard](/tools/tuningplayground.md) + +### Visualize and Listen to Polyrhythms in a Shader diff --git a/content/blog/recursive-just-intonation/fixed-c-ji-progression.wav b/content/blog/recursive-just-intonation/fixed-c-ji-progression.wav new file mode 100644 index 00000000..35b8050a Binary files /dev/null and b/content/blog/recursive-just-intonation/fixed-c-ji-progression.wav differ diff --git a/content/blog/recursive-just-intonation/recursive-ji-frequencies.csv b/content/blog/recursive-just-intonation/recursive-ji-frequencies.csv new file mode 100644 index 00000000..84ecf65c --- /dev/null +++ b/content/blog/recursive-just-intonation/recursive-ji-frequencies.csv @@ -0,0 +1,112 @@ +tuning,chord,note,frequency_hz,cents_vs_12_tet,cents_vs_fixed_c_ji +12-TET,C,C,130.813,0.000,0.000 +Fixed C just intonation,C,C,130.813,0.000,0.000 +Recursive just intonation,C,C,130.813,0.000,0.000 +12-TET,C,E,164.814,0.000,13.686 +Fixed C just intonation,C,E,163.516,-13.686,0.000 +Recursive just intonation,C,E,163.516,-13.686,0.000 +12-TET,C,G,195.998,0.000,-1.955 +Fixed C just intonation,C,G,196.219,1.955,0.000 +Recursive just intonation,C,G,196.219,1.955,0.000 +12-TET,E,E,164.814,0.000,13.686 +Fixed C just intonation,E,E,163.516,-13.686,0.000 +Recursive just intonation,E,E,163.516,-13.686,0.000 +12-TET,E,G#/Ab,207.652,0.000,-6.910 +Fixed C just intonation,E,G#/Ab,208.483,6.910,0.000 +Recursive just intonation,E,G#/Ab,204.395,-27.373,-34.283 +12-TET,E,B,246.942,0.000,11.731 +Fixed C just intonation,E,B,245.274,-11.731,0.000 +Recursive just intonation,E,B,245.274,-11.731,0.000 +12-TET,G#/Ab,G#/Ab,207.652,0.000,-6.910 +Fixed C just intonation,G#/Ab,G#/Ab,208.483,6.910,0.000 +Recursive just intonation,G#/Ab,G#/Ab,208.483,6.910,0.000 +12-TET,G#/Ab,C,261.626,0.000,0.000 +Fixed C just intonation,G#/Ab,C,261.626,0.000,0.000 +Recursive just intonation,G#/Ab,C,260.604,-6.776,-6.776 +12-TET,G#/Ab,D#/Eb,311.127,0.000,2.487 +Fixed C just intonation,G#/Ab,D#/Eb,310.680,-2.487,0.000 +Recursive just intonation,G#/Ab,D#/Eb,312.724,8.865,11.352 +12-TET,C,C,130.813,0.000,0.000 +Fixed C just intonation,C,C,130.813,0.000,0.000 +Recursive just intonation,C,C,130.813,0.000,0.000 +12-TET,C,E,164.814,0.000,13.686 +Fixed C just intonation,C,E,163.516,-13.686,0.000 +Recursive just intonation,C,E,163.516,-13.686,0.000 +12-TET,C,G,195.998,0.000,-1.955 +Fixed C just intonation,C,G,196.219,1.955,0.000 +Recursive just intonation,C,G,196.219,1.955,0.000 +12-TET,F,F,174.614,0.000,1.955 +Fixed C just intonation,F,F,174.417,-1.955,0.000 +Recursive just intonation,F,F,174.417,-1.955,0.000 +12-TET,F,A,220.000,0.000,-5.865 +Fixed C just intonation,F,A,220.747,5.865,0.000 +Recursive just intonation,F,A,218.021,-15.641,-21.506 +12-TET,F,C,261.626,0.000,0.000 +Fixed C just intonation,F,C,261.626,0.000,0.000 +Recursive just intonation,F,C,261.626,0.000,0.000 +12-TET,A,A,220.000,0.000,-5.865 +Fixed C just intonation,A,A,220.747,5.865,0.000 +Recursive just intonation,A,A,220.747,5.865,0.000 +12-TET,A,C#/Db,277.183,0.000,-4.955 +Fixed C just intonation,A,C#/Db,277.977,4.955,0.000 +Recursive just intonation,A,C#/Db,275.933,-7.821,-12.777 +12-TET,A,E,329.628,0.000,13.686 +Fixed C just intonation,A,E,327.032,-13.686,0.000 +Recursive just intonation,A,E,331.120,7.820,21.506 +12-TET,D,D,146.832,0.000,-3.910 +Fixed C just intonation,D,D,147.164,3.910,0.000 +Recursive just intonation,D,D,147.164,3.910,0.000 +12-TET,D,F#/Gb,184.997,0.000,9.776 +Fixed C just intonation,D,F#/Gb,183.955,-9.776,0.000 +Recursive just intonation,D,F#/Gb,183.955,-9.776,0.000 +12-TET,D,A,220.000,0.000,-5.865 +Fixed C just intonation,D,A,220.747,5.865,0.000 +Recursive just intonation,D,A,220.747,5.865,0.000 +12-TET,G7,G,195.998,0.000,-1.955 +Fixed C just intonation,G7,G,196.219,1.955,0.000 +Recursive just intonation,G7,G,196.219,1.955,0.000 +12-TET,G7,B,246.942,0.000,11.731 +Fixed C just intonation,G7,B,245.274,-11.731,0.000 +Recursive just intonation,G7,B,245.274,-11.731,0.000 +12-TET,G7,D,293.665,0.000,-3.910 +Fixed C just intonation,G7,D,294.329,3.910,0.000 +Recursive just intonation,G7,D,294.329,3.910,0.000 +12-TET,G7,F,349.228,0.000,1.955 +Fixed C just intonation,G7,F,348.834,-1.955,0.000 +Recursive just intonation,G7,F,349.515,1.423,3.378 +12-TET,C,C,130.813,0.000,0.000 +Fixed C just intonation,C,C,130.813,0.000,0.000 +Recursive just intonation,C,C,130.813,0.000,0.000 +12-TET,C,E,164.814,0.000,13.686 +Fixed C just intonation,C,E,163.516,-13.686,0.000 +Recursive just intonation,C,E,163.516,-13.686,0.000 +12-TET,C,G,195.998,0.000,-1.955 +Fixed C just intonation,C,G,196.219,1.955,0.000 +Recursive just intonation,C,G,196.219,1.955,0.000 +12-TET,E,E,164.814,0.000,13.686 +Fixed C just intonation,E,E,163.516,-13.686,0.000 +Recursive just intonation,E,E,163.516,-13.686,0.000 +12-TET,E,G#/Ab,207.652,0.000,-6.910 +Fixed C just intonation,E,G#/Ab,208.483,6.910,0.000 +Recursive just intonation,E,G#/Ab,204.395,-27.373,-34.283 +12-TET,E,B,246.942,0.000,11.731 +Fixed C just intonation,E,B,245.274,-11.731,0.000 +Recursive just intonation,E,B,245.274,-11.731,0.000 +12-TET,F,F,174.614,0.000,1.955 +Fixed C just intonation,F,F,174.417,-1.955,0.000 +Recursive just intonation,F,F,174.417,-1.955,0.000 +12-TET,F,A,220.000,0.000,-5.865 +Fixed C just intonation,F,A,220.747,5.865,0.000 +Recursive just intonation,F,A,218.021,-15.641,-21.506 +12-TET,F,C,261.626,0.000,0.000 +Fixed C just intonation,F,C,261.626,0.000,0.000 +Recursive just intonation,F,C,261.626,0.000,0.000 +12-TET,C,C,130.813,0.000,0.000 +Fixed C just intonation,C,C,130.813,0.000,0.000 +Recursive just intonation,C,C,130.813,0.000,0.000 +12-TET,C,E,164.814,0.000,13.686 +Fixed C just intonation,C,E,163.516,-13.686,0.000 +Recursive just intonation,C,E,163.516,-13.686,0.000 +12-TET,C,G,195.998,0.000,-1.955 +Fixed C just intonation,C,G,196.219,1.955,0.000 +Recursive just intonation,C,G,196.219,1.955,0.000 diff --git a/content/blog/recursive-just-intonation/recursive-ji-note-splits.wav b/content/blog/recursive-just-intonation/recursive-ji-note-splits.wav new file mode 100644 index 00000000..b370a8f0 Binary files /dev/null and b/content/blog/recursive-just-intonation/recursive-ji-note-splits.wav differ diff --git a/content/blog/recursive-just-intonation/recursive-ji-progression.wav b/content/blog/recursive-just-intonation/recursive-ji-progression.wav new file mode 100644 index 00000000..b4c0b248 Binary files /dev/null and b/content/blog/recursive-just-intonation/recursive-ji-progression.wav differ diff --git a/content/blog/recursive-just-intonation/twelve-tet-progression.wav b/content/blog/recursive-just-intonation/twelve-tet-progression.wav new file mode 100644 index 00000000..99aa7e6f Binary files /dev/null and b/content/blog/recursive-just-intonation/twelve-tet-progression.wav differ diff --git a/content/notes/math_stuff.css b/content/notes/math_stuff.css index 1c108b0d..4b47e7a2 100644 --- a/content/notes/math_stuff.css +++ b/content/notes/math_stuff.css @@ -120,11 +120,7 @@ } .bayes-solver-result[data-bayes-role="numerator"] { - background: linear-gradient( - 135deg, - #ffbc3f52 0 50%, - #fe7fb352 50% 100% - ); + background: linear-gradient(135deg, #ffbc3f52 0 50%, #fe7fb352 50% 100%); border-color: #ffc668; box-shadow: inset 0 0 0 1px #fe7fb380; } @@ -182,34 +178,47 @@ font-weight: 700; } +.bayes-phrase { + font-weight: 700; +} + .bayes-solver-field[data-bayes-role="posterior"] .bayes-solver-symbol, .bayes-solver-result[data-bayes-role="posterior"] .bayes-solver-symbol, -.bayes-term[data-bayes-role="posterior"] { +.bayes-term[data-bayes-role="posterior"], +.bayes-phrase[data-bayes-role="posterior"] { color: var(--bayes-posterior); } .bayes-solver-field[data-bayes-role="likelihood"] .bayes-solver-symbol, .bayes-solver-result[data-bayes-role="likelihood"] .bayes-solver-symbol, -.bayes-term[data-bayes-role="likelihood"] { +.bayes-term[data-bayes-role="likelihood"], +.bayes-phrase[data-bayes-role="likelihood"] { color: var(--bayes-likelihood); } .bayes-solver-field[data-bayes-role="prior"] .bayes-solver-symbol, .bayes-solver-result[data-bayes-role="prior"] .bayes-solver-symbol, -.bayes-term[data-bayes-role="prior"] { +.bayes-term[data-bayes-role="prior"], +.bayes-phrase[data-bayes-role="prior"] { color: var(--bayes-prior); } .bayes-solver-field[data-bayes-role="evidence"] .bayes-solver-symbol, .bayes-solver-result[data-bayes-role="evidence"] .bayes-solver-symbol, -.bayes-term[data-bayes-role="evidence"] { +.bayes-term[data-bayes-role="evidence"], +.bayes-phrase[data-bayes-role="evidence"] { color: var(--bayes-evidence); } -.bayes-term[data-bayes-role="neutral"] { +.bayes-term[data-bayes-role="neutral"], +.bayes-phrase[data-bayes-role="neutral"] { color: #4d8dc3; } +.bayes-phrase[data-bayes-role="numerator"] { + color: #ffc668; +} + .bayes-event-field .bayes-solver-symbol { color: #4d8dc3; } @@ -278,11 +287,7 @@ } .bayes-solver-result[data-bayes-role="numerator"] strong { - background: linear-gradient( - 135deg, - #ffbc3f33 0 50%, - #fe7fb333 50% 100% - ); + background: linear-gradient(135deg, #ffbc3f33 0 50%, #fe7fb333 50% 100%); border-color: #ffc668; } @@ -325,67 +330,22 @@ width: fit-content; } -.bayes-latex { - background: color-mix(in srgb, var(--bayes-posterior) 18%, var(--bayes-card)); - border: 1px solid var(--bayes-posterior); - border-radius: 4px; - box-shadow: inset 0 0 0 1px #0466e780; - display: grid; - gap: 0.55rem; - padding: 0.7rem; -} - -.bayes-latex-heading { +.bayes-solution { align-items: center; display: flex; - flex-wrap: wrap; - gap: 0.55rem; + justify-content: center; + padding: 0.35rem 0; } -.bayes-latex-heading span:first-child { - color: var(--bayes-posterior); - font-family: var(--codeFont); - font-weight: 700; -} - -.bayes-latex-heading button { - background: color-mix(in srgb, var(--bayes-posterior) 30%, var(--light)); - border: 1px solid var(--bayes-posterior); - border-radius: 4px; - color: var(--dark); - cursor: pointer; - font: inherit; - min-height: 2rem; - padding: 0.25rem 0.6rem; -} - -.bayes-latex-rendered { +.bayes-solution-equation { + max-width: 100%; + min-width: 0; overflow-x: auto; - padding: 0.4rem 0; + padding: 0.25rem 0; } -.bayes-latex-rendered .katex-display { - margin: 0; -} - -.bayes-latex-output { - background: color-mix(in srgb, var(--bayes-posterior) 18%, var(--light)); - border: 1px solid var(--bayes-posterior); - border-radius: 4px; - box-sizing: border-box; - color: var(--dark); - font-family: var(--codeFont); - font-size: 0.82rem; - line-height: 1.45; - min-height: 10rem; - padding: 0.6rem; - resize: vertical; - width: 100%; -} - -.bayes-latex-status { - color: #4d8dc3; - min-height: 1.25rem; +.bayes-rendered-math { + margin: 0 auto; } .bayes-solver-error { diff --git a/content/photography.css b/content/photography.css new file mode 100644 index 00000000..7c65250f --- /dev/null +++ b/content/photography.css @@ -0,0 +1,228 @@ +.photo-page { + --photo-border: color-mix(in srgb, var(--gray) 32%, transparent); + --photo-surface: color-mix(in srgb, var(--light) 92%, var(--lightgray)); + --photo-surface-strong: color-mix(in srgb, var(--light) 84%, var(--lightgray)); + --photo-muted: var(--gray); + + display: grid; + gap: 1.25rem; +} + +.photo-page h2 { + margin-block: 0 0.65rem; + font-size: 1.15rem; +} + +.photo-gear-sections { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr)); + gap: 0.85rem; +} + +.photo-gear-group { + display: grid; + align-content: start; + gap: 0.55rem; +} + +.photo-gear-group-wide { + grid-column: 1 / -1; +} + +.photo-gear-group h3, +.photo-gear-group h4 { + margin: 0; + line-height: 1.25; +} + +.photo-gear-group h3 { + font-size: 0.98rem; +} + +.photo-gear-group h4 { + color: var(--photo-muted); + font-size: 0.83rem; + font-weight: 650; + text-transform: uppercase; +} + +.photo-lens-columns { + display: grid; + grid-template-columns: minmax(0, 2fr) minmax(min(100%, 16rem), 1fr); + gap: 0.85rem; +} + +.photo-lens-columns > section { + display: grid; + align-content: start; + gap: 0.5rem; +} + +.photo-gear-list { + display: grid; + gap: 0.55rem; +} + +.photo-gear-item { + display: grid; + gap: 0.12rem; + border: 1px solid var(--photo-border); + border-radius: 8px; + padding: 0.68rem 0.75rem; + background: var(--photo-surface); +} + +.photo-gear-item strong { + font-size: 0.93rem; + line-height: 1.25; +} + +.photo-gear-item p { + margin: 0; + color: var(--darkgray); + font-size: 0.84rem; + line-height: 1.35; +} + +.photo-workflow li { + color: var(--darkgray); +} + +.photo-workflow { + border-block: 1px solid var(--photo-border); + padding-block: 1rem; +} + +.photo-workflow ol { + margin-block: 0; + padding-left: 1.25rem; +} + +.photo-workflow li + li { + margin-top: 0.35rem; +} + +.photo-gallery-header { + display: flex; + gap: 1rem; + align-items: baseline; + justify-content: space-between; +} + +#photo-gallery-count { + margin: 0; + color: var(--photo-muted); + font-size: 0.9rem; +} + +.photo-gallery { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr)); + gap: 0.85rem; +} + +.photo-card { + display: grid; + overflow: hidden; + border: 1px solid var(--photo-border); + border-radius: 8px; + background: var(--photo-surface); + color: inherit; + text-decoration: none; +} + +.photo-card img { + width: 100%; + aspect-ratio: 4 / 3; + object-fit: cover; + background: var(--photo-surface-strong); +} + +.photo-card span { + display: grid; + gap: 0.15rem; + padding: 0.65rem 0.75rem 0.75rem; +} + +.photo-card strong { + font-size: 0.95rem; +} + +.photo-card small { + color: var(--photo-muted); +} + +.photo-lightbox { + width: min(92vw, 68rem); + border: 1px solid var(--photo-border); + border-radius: 8px; + padding: 0; + background: var(--photo-surface); + color: var(--dark); +} + +.photo-lightbox::backdrop { + background: rgba(0, 0, 0, 0.72); +} + +.photo-lightbox[open] { + display: grid; + grid-template-columns: auto minmax(0, 1fr) auto; + gap: 0.5rem; + align-items: center; +} + +.photo-lightbox figure { + display: grid; + gap: 0.5rem; + margin: 0; + padding: 0.75rem 0; +} + +.photo-lightbox img { + max-width: 100%; + max-height: 78vh; + object-fit: contain; +} + +.photo-lightbox figcaption { + color: var(--photo-muted); + text-align: center; +} + +.photo-lightbox button { + border: 1px solid var(--photo-border); + border-radius: 6px; + margin: 0.75rem; + padding: 0.35rem 0.6rem; + background: var(--photo-surface-strong); + color: var(--dark); + cursor: pointer; + font: inherit; +} + +.photo-lightbox-close { + position: absolute; + top: 0; + right: 0; +} + +@media (max-width: 760px) { + .photo-lens-columns { + grid-template-columns: 1fr; + } + + .photo-gallery-header { + display: block; + } + + .photo-lightbox[open] { + grid-template-columns: 1fr 1fr; + } + + .photo-lightbox figure { + grid-column: 1 / -1; + grid-row: 1; + padding: 2.75rem 0.75rem 0.5rem; + } +} diff --git a/content/photography.md b/content/photography.md new file mode 100644 index 00000000..00a8a56a --- /dev/null +++ b/content/photography.md @@ -0,0 +1,142 @@ +--- +title: photography +date: 2026-05-04 +updated: 2026-05-04 +tags: + - photography +--- + + + + +
+
+

equipment

+
+
+

35mm cameras

+
+
+Minolta X-500 +

manual-focus 35mm SLR, also sold as the X-570.

+
+
+Nikon F100 +

autofocus F-mount 35mm SLR.

+
+
+Nikon FA +

F-mount SLR with Nikon's early Matrix Metering system.

+
+
+
+
+

lenses

+
+
+

auto focus compatible

+
+
+AF Zoom-Nikkor 28-70mm f/3.5-4.5D +

compact AF-D standard zoom.

+
+
+AF-S DX VR Zoom-Nikkor 18-200mm f/3.5-5.6G IF-ED +

DX all-in-one zoom.

+
+
+AF-S NIKKOR 35mm f/1.8G ED +

full-frame F-mount prime.

+
+
+AF-S NIKKOR 50mm f/1.4G +

full-frame F-mount prime.

+
+
+AF-S NIKKOR 50mm f/1.8G +

full-frame F-mount prime.

+
+
+AF-S VR Micro-Nikkor 105mm f/2.8G IF-ED +

macro lens with VR.

+
+
+Sigma Zoom-alpha III 35-135mm f/3.5-4.5 +

multi-coated zoom, marked 55mm.

+
+
+
+
+

not auto focus compatible

+
+
+NIKKOR-S Auto 50mm f/1.4 +

Nippon Kogaku Japan pre-AI standard prime.

+
+
+
+
+
+
+

film

+
+
+Fujifilm and Kodak 200/400 ISO stocks +

my default film speeds are ISO 200 and 400, in both black-and-white and color.

+
+
+
+
+

flashes and other equipment

+
+
+Nikon Speedlight SB-15 +

compact TTL-capable Nikon flash.

+
+
+ROWI Skylight 1A 55mm +

skylight filter mounted on the Sigma zoom.

+
+
+
+
+

scanning equipment

+
+
+Panasonic LUMIX S5 (DC-S5) +

full-frame mirrorless camera used for camera scanning.

+
+
+VALOI easy35 +

35mm camera-scanning system.

+
+
+
+
+
+
+

publish workflow

+
    +
  1. keep unprocessed originals out of the site tree in private/photography/originals.
  2. +
  3. edit the selected scans in Lightroom.
  4. +
  5. run the Rust build-tool command to strip metadata and create protected exports.
  6. +
  7. review the generated gallery in the local Quartz server before publishing.
  8. +
+
+ + + + +
+ +
+
+ +
+
diff --git a/content/photography/gallery.json b/content/photography/gallery.json new file mode 100644 index 00000000..1ca3fcd0 --- /dev/null +++ b/content/photography/gallery.json @@ -0,0 +1,9 @@ +[ + { + "src": "/photography/gallery/weathered-sign.jpg", + "title": "weathered sign", + "meta": "protected export from weathered-sign.jpg", + "width": 1080, + "height": 1440 + } +] diff --git a/content/photography/gallery/weathered-sign.jpg b/content/photography/gallery/weathered-sign.jpg new file mode 100644 index 00000000..876deecf Binary files /dev/null and b/content/photography/gallery/weathered-sign.jpg differ diff --git a/content/tools/polyrhythm.css b/content/tools/polyrhythm.css index 5751864f..b850594a 100644 --- a/content/tools/polyrhythm.css +++ b/content/tools/polyrhythm.css @@ -1,35 +1,93 @@ -[saved-theme="dark"] canvas { - filter: invert(1); +.polyrhythm-tool { + display: grid; + gap: 1rem; + margin-top: 1.5rem; + max-width: 58rem; } -/* Container for all controls */ -#controls { - display: flex; - flex-direction: column; - gap: 1em; - width: 320px; - /* adjust width as needed */ +.polyrhythm-tool h2 { + margin: 0; +} + +.polyrhythm-controls { + align-items: end; + display: grid; + gap: 0.75rem; + grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr)); } -/* Each individual control group */ .control { - display: flex; - align-items: center; - justify-content: space-between; + display: grid; + gap: 0.35rem; + min-width: 0; } -/* Ensure labels do not wrap */ -.control label { - white-space: nowrap; - margin-right: 1em; +.control span { + color: var(--darkgray); + font-size: 0.9rem; } -/* Inputs take the remaining space */ .control input { - flex: 1; + background: var(--light); + border: 1px solid var(--lightgray); + border-radius: 4px; + box-sizing: border-box; + color: var(--dark); + min-width: 0; + padding: 0.45rem 0.55rem; + width: 100%; +} + +.buttons, +.polyrhythm-presets { + display: flex; + flex-wrap: wrap; + gap: 0.45rem; +} + +.buttons button, +.polyrhythm-presets button { + background: color-mix(in srgb, var(--secondary, #4f8cff) 18%, var(--light)); + border: 1px solid color-mix(in srgb, var(--secondary, #4f8cff) 46%, var(--lightgray)); + border-radius: 4px; + color: var(--dark); + cursor: pointer; + min-height: 2.2rem; + padding: 0.35rem 0.65rem; +} + +.buttons button:disabled { + cursor: not-allowed; + opacity: 0.55; +} + +.polyrhythm-status { + color: var(--darkgray); + grid-column: 1 / -1; + min-height: 1.35rem; + margin: 0; +} + +.polyrhythm-canvases { + display: grid; + gap: 0.75rem; } -/* Center the button */ -#start-button { - align-self: center; +.polyrhythm-canvases canvas { + background: + linear-gradient( + color-mix(in srgb, var(--light) 92%, transparent), + color-mix(in srgb, var(--light) 92%, transparent) + ), + repeating-linear-gradient( + 90deg, + transparent 0 24px, + color-mix(in srgb, var(--lightgray) 28%, transparent) 24px 25px + ); + border: 1px solid var(--lightgray); + border-radius: 4px; + box-sizing: border-box; + height: auto; + max-width: 100%; + width: 100%; } diff --git a/content/tools/polyrhythm.md b/content/tools/polyrhythm.md index 056edffd..86430dc1 100644 --- a/content/tools/polyrhythm.md +++ b/content/tools/polyrhythm.md @@ -1,7 +1,7 @@ --- title: polyrhythm date: 2025-02-19 -updated: 2025-02-26 +updated: 2026-05-04 tags: - wasm - tools @@ -9,33 +9,53 @@ tags: - music --- - -
-
- - -
-
- - -
-
- - + + +
+

polyrhythm

+ +
+ + + + + + + + +
+ + + + +
+ +
+ + +
+ +

+
-
- - + +
+ + +
-
- - -
-
- - - - - - - +
+ + diff --git a/content/tools/tuningplayground.md b/content/tools/tuningplayground.md index 957dba82..1d24322f 100644 --- a/content/tools/tuningplayground.md +++ b/content/tools/tuningplayground.md @@ -64,7 +64,7 @@ tags:

Volume:

-

Transpose: +

Transpose: