Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/check.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,7 @@ jobs:
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
run: nix develop . --command cargo run --locked --no-default-features --manifest-path tools/site/Cargo.toml -- check

- name: Check photo feature
run: nix develop . --command cargo check --locked --tests --manifest-path tools/site/Cargo.toml --no-default-features --features photos
4 changes: 2 additions & 2 deletions .github/workflows/generate.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:
run: rustup toolchain install --profile minimal stable

- name: Generate files
run: cargo run --locked --manifest-path tools/site/Cargo.toml -- generate
run: cargo run --locked --no-default-features --manifest-path tools/site/Cargo.toml -- generate

- name: Commit generated files
run: cargo run --locked --manifest-path tools/site/Cargo.toml -- commit generate
run: cargo run --locked --no-default-features --manifest-path tools/site/Cargo.toml -- commit generate
2 changes: 1 addition & 1 deletion .github/workflows/github-pages.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ jobs:
run: nix develop . --command rustup toolchain install --profile minimal stable --target wasm32-unknown-unknown

- name: Build site
run: nix develop . --command cargo run --locked --manifest-path tools/site/Cargo.toml -- build --prod
run: nix develop . --command cargo run --locked --no-default-features --manifest-path tools/site/Cargo.toml -- build --prod

- name: Upload artifact
uses: actions/upload-pages-artifact@v5.0.0
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/update_and_lint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:
run: nix develop . --command rustup toolchain install --profile minimal stable --component rustfmt --component clippy --target wasm32-unknown-unknown

- name: Update dependencies and lint
run: nix develop . --command cargo run --locked --manifest-path tools/site/Cargo.toml -- update
run: nix develop . --command cargo run --locked --no-default-features --manifest-path tools/site/Cargo.toml -- update

- name: Commit maintenance changes
run: nix develop . --command cargo run --locked --manifest-path tools/site/Cargo.toml -- commit update_and_lint
run: nix develop . --command cargo run --locked --no-default-features --manifest-path tools/site/Cargo.toml -- commit update_and_lint
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ tsconfig.tsbuildinfo
quartz/cli/tui/dist/
private/
private/photography/originals/
private/tools/nightshade/
.replit
replit.nix

Expand Down
82 changes: 34 additions & 48 deletions content/blog/recursive-just-intonation.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,38 @@
---
title: Why you should buy 12 Pianos | Recursive Just Intonation
date: 2022-11-29
updated: 2026-05-05
updated: 2026-05-08
tags:
- music
- programming
---

## What is this about?

Recursive Just Intonation is a novel tuning system I came up with during my highschool physics classes, while trying to find a solution
to the dissonance of 12TET. Before i explain what it is I'll give a little background on tuning systems in general.
Before getting to recursive just intonation, there are two tuning ideas worth
separating:

- 12-tone equal temperament, or 12-TET, divides the octave into twelve equal
steps. It is why a piano can play in every key without retuning, but its
thirds and fifths are approximations of simpler acoustic ratios.
- Just intonation tunes notes as small whole-number frequency ratios, like
`2/1`, `3/2`, and `5/4`. Those intervals can sound more settled because the
waves line up more often, but the "right" version of a note depends on the
harmonic context.

The rest of the post builds from that tradeoff: one global keyboard versus
cleaner chord-local relationships.

## Equal Temperament vs 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
very convenient, at the cost of shaving almost every interval a little. The
intervals are close enough to simple ratios that they work, but most of them
are not exact.
Equal temperament gives us one frequency table. 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 very
convenient, at the cost of shaving almost every interval a little. 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:
Just intonation goes the other way. It treats notes as relationships to a root,
then builds those relationships from simple frequency ratios:

- octave: `2/1`
- perfect fifth: `3/2`
Expand Down Expand Up @@ -63,21 +75,7 @@ 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.

The experiment here is:

> 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.

For a C-based just-intonation scale the 12 pitch
classes are:
For a C-based just-intonation scale, the 12 pitch classes are:

| pitch | ratio from C |
| ----- | -----------: |
Expand Down Expand Up @@ -151,8 +149,18 @@ closer to `500:630:749`, or `1:1.260:1.498`.

## 12 Just Pianos | Recursive Just Intonation

Recursive just intonation changes only one rule: after choosing a root, restart
the ratio table at that root.
Here is what I call recursive just intonation:

> Keep the roots on a C-based just-intonation keyboard, but give every chord
> root its own just-intonated 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.

For an E major chord:

Expand Down Expand Up @@ -387,28 +395,6 @@ Recursive just intonation is mostly a listening tool for me. It makes the
root-relationship audible again, instead of flattening every chord tone onto one
global keyboard.

### Implementation Notes

The shared Rust renderer lives in `crates/recursive_ji_core`. 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 and
feeds the browser notation through the WASM bundle.

Generate the files with:

```sh
cargo run --manifest-path tools/site/Cargo.toml -- recursive-ji-music
```

The same command also writes a CSV frequency report to
`content/misc/plaintext/recursive-ji-frequencies.csv`.

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`

## My Other Music Work

- [Play around with different tuning systems and your computer keyboard](/tools/tuningplayground.md)
Expand Down
12 changes: 12 additions & 0 deletions content/photography.css
Original file line number Diff line number Diff line change
Expand Up @@ -191,10 +191,22 @@
}

.photo-lightbox figcaption {
display: grid;
gap: 0.18rem;
color: var(--photo-muted);
line-height: 1.35;
text-align: center;
}

.photo-lightbox figcaption strong {
color: var(--dark);
}

.photo-lightbox figcaption span {
max-width: 52rem;
justify-self: center;
}

.photo-lightbox button {
border: 1px solid var(--photo-border);
border-radius: 6px;
Expand Down
Loading
Loading