S9) The Five-Layer Pitch System
From Actor to Hz
“Sa” is not a frequency. It’s a name. The path between this name and the 261.63 Hz coming out of the speaker goes through five layers — each independent, each replaceable.
Where does this article fit in?
We saw in S3 that each actor has its own resolver. Here we open up the pitch system to understand its five layers — and why they are separated.
Why five layers?
Most computer music systems treat pitches as a single block: note name → MIDI number → frequency. This works for Western piano in equal temperament (12-TET: twelve identical semitones). But it quickly reaches its limits as soon as you step outside this framework:
- A sitar in 22 shruti (the micro-intervals of classical Indian music) does not use the same intervals as a piano
- An Arabic maqam (Arabic melodic mode) has quarter tones that standard MIDI cannot represent
- An Indian raga can have different scales for ascending and descending
- Two musicians playing the same notes can be tuned differently
BPscript organizes these distinctions into five layers:
actor → context (who plays what, where)
alphabet → names + alterations + registers (how it's written)
temperament → interval grid (where notes are placed)
tuning → concrete scale (which degrees are used in the scale)
resolver → token → frequency (what produces the sound)
The first layer — the actor — is the context that links all the others. The next three (alphabet, temperament, tuning) are cultural and mathematical data. The last one produces the whole.
Layer 0 — Actor: the attachment unit
A BPscript scene can contain several instruments that share the same alphabet. Two sitar players play the same notes (sargam — Indian solfège: sa re ga ma pa dha ni) but can lead to different outputs, use different tunings, or different octave conventions. The alphabet alone is not enough: the context is missing.
The actor is the unit that links all resolution layers together:
@actor sitar1 alphabet:sargam scale:sargam_22shruti sounds:sitar_timbre transport:webaudio
@actor sitar2 alphabet:sargam scale:sargam_12TET transport:midi(ch:3)
@actor tabla alphabet:tabla_bols sounds:tabla_perc transport:midi(ch:10)
@actor lights alphabet:dmx_fixtures transport:dmx
Available properties for the @actor directive:
| Property | Required | Role |
|---|---|---|
alphabet |
yes | vocabulary (symbol names) |
scale |
if actor has pitches | degree selection → pitch via temperament |
sounds |
if percussive (no pitch) or specific timbre | terminal definitions (timbre, perc, sample) |
transport |
yes | rendering destination |
eval |
no | evaluation key for backticks (default: no REPL — null) |
If scale is omitted → no frequency resolution (percussions, DMX). If sounds is omitted → default transport timbre.
The . (dot notation) prefixes a terminal with its actor, like a namespace:
sitar1.Sa // Sa resolved via sitar1 (sargam + 22shruti + webaudio)
sitar2.Sa // same note, another actor (sargam + 12TET + midi ch3)
tabla.tin // tin resolved via tabla (bols + midi ch10)
If only one actor contains a symbol, the prefix is optional (implicit resolution). If multiple actors share the symbol without a prefix → error. Each actor has its own resolver: the resolver is not a global singleton, it’s an instance per actor. This is what allows sitar1.Sa and sitar2.Sa — same name, same octave — to produce different frequencies because their scale (and thus their temperament) differs.
Layer 1 — Alphabet: names, alterations and registers
The alphabet defines an ordered sequence of names, the available alterations, and the register convention (octaves). Names and alterations are purely nominal — no frequency, no MIDI, no ratio. The register convention, however, only states how notes are written — each alphabet chooses its own (western → western registers, sargam → saptak registers).
{
"western": {
"notes": ["C", "D", "E", "F", "G", "A", "B"],
"alterations": ["bb", "b", "", "#", "##"]
},
"sargam": {
"notes": ["sa", "re", "ga", "ma", "pa", "dha", "ni"],
"alterations": ["", "komal", "tivra"]
}
}
The position in the list is the index (degree): C = degree 0, D = degree 1, etc.
Each tradition has its own alteration conventions:
| Tradition | Alterations |
|---|---|
| Western | bb, b, #, ## |
| Sargam (India) | komal (flat), tivra (sharp) |
| Arabic | bb, b, half_b, half_#, #, ## |
| Turkish | bakiye, kucuk_mucenneb, buyuk_mucenneb |
Registers (octaves)
How to name registers (octaves)? The convention is part of the alphabet — each tradition has its own:
{
"western": {
"position": "suffix",
"separator": "",
"registers": ["0","1","2","3","4","5","6","7","8","9"],
"default": 4
},
"saptak": {
"position": "suffix",
"separator": "_",
"registers": ["v", "", "^"],
"default": 1
}
}
position:"prefix"or"suffix"— the register comes before or after the noteseparator: character between note and register (""= no space,"_"= underscore)registers: ordered list low→high,""= no marker (default register)default: index of the central register (the reference one)
Resolution examples:
- Western:
C4→ noteC, register index 4 - Saptak:
Sa_^→ noteSa, register index 2 (taar saptak) - Saptak:
Sa→ noteSa, register index 1 (madhya, by default —""in the list)
Layer 2 — Temperament: the mathematical grid
Temperament defines the interval grid — a division of the frequency space. It is independent of any alphabet or scale.
Type “table” — fixed ratios
{
"12TET": {
"period_ratio": 2,
"divisions": 12,
"ratios": ["0c", "100c", "200c", "300c", "400c", "500c",
"600c", "700c", "800c", "900c", "1000c", "1100c"]
},
"22shruti": {
"period_ratio": 2,
"divisions": 22,
"ratios": [1, "256/243", "16/15", "10/9", "9/8", "32/27", "6/5", "5/4",
"81/64", "4/3", "27/20", "45/32", "729/512", "3/2", "128/81",
"8/5", "5/3", "27/16", "16/9", "9/5", "15/8", "243/128"]
}
}
period_ratio: the repeating interval (2 = octave, 3 = tritave for Bohlen-Pierce)divisions: number of steps in the periodratios: the ratio of each step — 3 accepted formats:
| Format | Usage | Example | Precision |
|---|---|---|---|
| Fraction | Just intonation, shruti, Pythagorean | "9/8" |
Exact (rational) |
| Decimal | Gamelan, measured systems | 1.05946 |
Approximation |
| Cents | Equal temperaments (12-TET, 24-TET) | "100c" |
Converted to irrational |
The resolver normalizes everything to float upon loading. Only one calculation path afterwards.
Type “parametric” — Dynamic Tonality
Inspired by Dynamic Tonality (Milne, Sethares, Plamondon). Instead of fixed ratios, temperament is defined by a period and a generator whose size can vary continuously:
{
"meantone": {
"type": "parametric",
"period": 1200,
"generator": 697,
"generator_range": [685, 720],
"mapping": [[1,0], [1,1], [0,4]],
"primes": [2, 3, 5],
"commas": ["81/80"]
}
}
By varying the generator from 685 to 720 cents, a continuum of temperaments is traversed:
| Generator | Temperament | Character |
|---|---|---|
| 694.7¢ | 2/7-comma meantone | Very soft |
| 697¢ | 1/4-comma meantone | Classic |
| 700¢ | 12-TET | Equal |
| 702¢ | Pythagorean | Bright |
All share the same mapping (major third = 4g-2P, perfect fifth = g). Only the color changes.
The generator is a CV — it can vary over time via polymetry (S5):
// Morphs from Pythagorean to meantone in real time
S -> { Sa Re Ga Pa , sitar.tuning.generator(ramp(702, 697)) }
All frequencies change simultaneously — the scale structure remains, only the temperament moves.
Layer 3 — Tuning: the concrete scale
Tuning bridges the gap between names (alphabet) and positions (temperament):
{
"Cmaj_just": {
"temperament": "just_5limit",
"degrees": [0, 2, 4, 5, 7, 9, 11],
"alterations": { "#": "25/24", "b": "24/25" },
"baseHz": 440,
"baseNote": "A",
"baseRegister": 4
}
}
degrees: which steps of the temperament are used, in the order of the alphabetalterations: alterations, specified in three ways (see below)baseHz/baseNote/baseRegister: the reference point
Three ways to specify an alteration
An alteration (#, komal, half_b…) is an offset applied to a degree. Depending on the system, it is specified in three ways — all converted to a frequency ratio upon loading:
| Way | Example | When |
|---|---|---|
| ratio | "#": "25/24" |
just intonation: the ½-tone is not a degree, an exact ratio is needed |
| cents | "half_b": "-50c" |
measured microtonal: the Arabic quarter tone = −50 cents |
| degrees | "#": "+1" |
equal grid: the ½-tone is already a degree, shift by one step (whole_tone = 2 degrees) |
This is exactly the diatonic / chromatic difference:
// Just diatonic — 7 degrees; # is NOT a degree → ratio
"Cmaj_just": { "degrees": [0,2,4,5,7,9,11], "alterations": { "#": "25/24" } }
// Equal chromatic — 12 degrees; # IS already a degree → step offset
"chromatic_12tet": { "degrees": [0,1,2,3,4,5,6,7,8,9,10,11], "alterations": { "#": "+1" } }
In diatonic, C# is calculated (C × 25/24) because the scale does not contain this ½-tone. In chromatic, C# is simply the next degree. The ratio remains indispensable as soon as you leave the equal grid: 25/24 is neither a whole step nor a proper fraction of an octave in an unequal system.
Compound scales — tetrachords and jins
In Arabic, Turkish, and Greek traditions, scales are built by stacking fragments (tetrachords = 4 notes spanning a fourth):
{
"maqam_rast": {
"compose": ["jins_rast", "jins_rast"],
"junction": "3/2",
"description": "Maqam Rast = Rast + Rast on the fifth"
}
}
The resolver stacks the fragments upon loading → produces an array of degrees like a normal tuning.
Directional scales — aroha and avaroha
In Indian music, some ragas have different scales for ascending and descending:
{
"bhairav": {
"temperament": "22shruti",
"ascending": [0, 2, 7, 9, 13, 15, 20],
"descending": [0, 4, 7, 9, 13, 17, 20],
"baseHz": 240,
"baseNote": "sa",
"description": "Raga Bhairav — different aroha and avaroha"
}
}
When ascending and descending are present (instead of degrees), the resolver chooses according to the melodic direction — determined by the context (previous note vs. current).
Layer 4 — Resolver: token → frequency
The resolver is the runtime component that assembles the previous layers. There is one resolver per actor (S3) — not a global singleton. It is the actor (layer 0) that provides it with its alphabet (including registers), tuning, and temperament.
The resolution path:
"Sa_^" (raw token)
→ analysis: note="sa", register="^" (index 2)
→ alphabet lookup: sa = degree 0
→ tuning: degree 0 → step 0 of temperament
→ temperament: step 0 → ratio 1/1
→ octave: register 2 - default 1 = +1 octave → ratio × 2
→ baseHz: 240 × 1 × 2 = 480 Hz
Same token, two actors, two results:
Sa_^viasitar1(22 shruti, base 240) → 480 HzSa_^viasitar2(12-TET, base 261.63) → 523.25 Hz
Transposition: why it’s complicated
A fundamental musicological point that justifies this architecture: transposition does not work the same way in all systems.
In equal temperament (12-TET), transposing = shifting by a number of semitones. Simple. But:
- In just intonation, the steps are not all equal: shifting the melody on the grid deforms the intervals. Transposing C major to D by adding a whole tone to each note changes the major third from
5/4(just, 386 c) to81/64(Pythagorean, 408 c) — the syntonic comma appears. (It is the same inequality of steps that meansC#andDbdo not have the same frequency.) - In 22 shruti: same problem, only worse. “Moving up one step” changes the interval depending on the position.
- Solution: rather than counting steps, we change the tonic (the reference note) while preserving the intervals.
BPscript handles this by separating tonic (tuning, baseNote/baseHz) and grid (temperament, ratios). Changing the tonic = changing the tuning, not the temperament. Details are in the transposition appendix of PITCH.md, developed in S15.
Key takeaways
- Five layers: actor → alphabet → temperament → tuning → resolver
- Actor = attachment context (alphabet + scale + sounds + transport) — one resolver per actor
- Alphabet = notation — names + alterations + register convention (octaves), zero frequency; each alphabet chooses its registers
- Temperament = mathematical grid — ratios in fractions, decimal, or cents
- Tuning = concrete scale — degrees + alterations + reference Hz
- Compound scales (tetrachords/jins) and directional scales (aroha/avaroha)
- Dynamic Tonality: the temperament generator can vary as a CV — real-time temperament morphing
Further reading
- Milne, A., Sethares, W. & Plamondon, J. (2007): “Isomorphic controllers and dynamic tuning” — the foundations of Dynamic Tonality
- Asselin, P.-Y. (1985): Musique et tempérament — French-language reference on historical tuning systems
- Sethares, W. (2005): Tuning, Timbre, Spectrum, Scale — the timbre-tuning coupling (spectral matching)
Glossary
- Actor: Attachment unit that links alphabet + scale + sounds + transport — carries a dedicated resolver
- Alphabet: Ordered sequence of note names + available alterations — purely nominal
- Octaves: Register naming convention (position, separator, low→high list)
- Temperament: Mathematical interval grid — division of frequency space into steps
- Tuning: Concrete scale = which steps of the temperament to use + reference frequency
- Resolver: Instance (1 per actor) that translates a token into a frequency by traversing the five layers
- Dynamic Tonality: Parametric temperament whose generator can vary continuously
- Tetrachord/Jins: 4-note fragment spanning a fourth — basic building block of Arabic and Turkish scales
- Aroha/Avaroha: Ascending/descending scale of an Indian raga — can be different
- Cents: Logarithmic unit of interval — 1200 cents = 1 octave, 100 cents = 1 semitone in 12-TET
- MOS (Moment of Symmetry): Scale generated by a single interval (generator) reduced within a period
Links in the series
- S3 — Types, actors and attachments — each actor has its own resolver
- S5 — Structuring Time — the generator as CV in polymetry
- S10 — Under the Hood — how the five layers are loaded and resolved
- S15 — Transposition beyond equal temperament
Prerequisite: S3
Reading time: 16 min
Tags: #BPscript #pitch #temperament #tuning #alphabet #intonation
Next article: S10 — Under the Hood: compiler, engine, and dispatcher