mirror of
https://github.com/kennethreitz/pytheory.git
synced 2026-06-05 23:00:20 +00:00
Add Blues/Pentatonic and Javanese Gamelan systems
Blues: major/minor pentatonic, blues scale, major blues, dominant, minor (Dorian). The foundational scales of blues, rock, and jazz. Gamelan: slendro (5-tone equidistant), pelog (7-tone with 3 pathet subsets: nem, barang, lima). 12-TET approximations of Javanese gamelan tuning with traditional tone names (ji, ro, lu, pat, mo, nem, pi/barang). Total systems: 6 (western, indian, arabic, japanese, blues, gamelan) 277 tests passing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -116,6 +116,84 @@ and heptatonic scales from Japanese music.
|
||||
|
||||
**Heptatonic scales:** ritsu, ryo
|
||||
|
||||
Blues and Pentatonic
|
||||
-------------------
|
||||
|
||||
The blues system provides the scales foundational to blues, rock, jazz,
|
||||
and folk music worldwide. Pentatonic scales (5 notes) are the oldest
|
||||
known musical scales, found independently in cultures across every
|
||||
continent.
|
||||
|
||||
The **blues scale** adds the "blue note" (flat 5th / sharp 4th) to the
|
||||
minor pentatonic — this chromatic passing tone is the defining sound
|
||||
of the blues.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from pytheory import TonedScale
|
||||
from pytheory.systems import SYSTEMS
|
||||
|
||||
c = TonedScale(tonic="C4", system=SYSTEMS["blues"])
|
||||
|
||||
c["major pentatonic"].note_names # the "happy" pentatonic
|
||||
# ['C', 'D', 'E', 'G', 'A', 'C']
|
||||
|
||||
c["minor pentatonic"].note_names # the "sad" pentatonic
|
||||
# ['C', 'D#', 'F', 'G', 'A#', 'C']
|
||||
|
||||
c["blues"].note_names # minor pentatonic + blue note
|
||||
# ['C', 'D#', 'F', 'F#', 'G', 'A#', 'C']
|
||||
|
||||
c["major blues"].note_names # major pentatonic + blue note
|
||||
# ['C', 'D', 'D#', 'E', 'G', 'A', 'C']
|
||||
|
||||
**Pentatonic:** major pentatonic, minor pentatonic
|
||||
|
||||
**Hexatonic:** blues, major blues
|
||||
|
||||
**Heptatonic:** dominant (Mixolydian — the dominant 7th sound),
|
||||
minor (Dorian — the jazz minor sound)
|
||||
|
||||
|
||||
Javanese Gamelan
|
||||
----------------
|
||||
|
||||
The gamelan system approximates the scales of the Javanese and Balinese
|
||||
gamelan orchestra in 12-tone equal temperament. True gamelan tuning is
|
||||
unique to each ensemble and does not conform to Western intonation —
|
||||
these are the closest 12-TET approximations.
|
||||
|
||||
**Slendro** is a roughly equal 5-tone division of the octave, producing
|
||||
an ethereal, floating quality. **Pelog** is a 7-tone scale with unequal
|
||||
intervals, typically performed using 5-note subsets called *pathet*.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from pytheory import TonedScale
|
||||
from pytheory.systems import SYSTEMS
|
||||
|
||||
ji = TonedScale(tonic="ji4", system=SYSTEMS["gamelan"])
|
||||
|
||||
ji["slendro"].note_names # the 5-tone equidistant scale
|
||||
# ['ji', 'ro', 'pat', 'mo', 'pi', 'ji']
|
||||
|
||||
ji["pelog"].note_names # full 7-tone pelog
|
||||
# ['ji', 'ro-', 'lu', 'pat', 'mo', 'nem-', 'barang', 'ji']
|
||||
|
||||
ji["pelog nem"].note_names # pathet nem subset
|
||||
# ['ji', 'ro-', 'lu', 'pat', 'mo', 'ji']
|
||||
|
||||
**Pentatonic:** slendro, pelog nem, pelog barang, pelog lima
|
||||
|
||||
**Heptatonic:** pelog (full 7-tone)
|
||||
|
||||
.. note::
|
||||
|
||||
Gamelan tone names follow Javanese numbering: ji (1), ro (2),
|
||||
lu (3), pat (4), mo (5), nem (6), pi/barang (7). Suffixes
|
||||
indicate microtonal variants approximated to the nearest semitone.
|
||||
|
||||
|
||||
Cross-System Comparison
|
||||
-----------------------
|
||||
|
||||
|
||||
+115
-1
@@ -68,6 +68,37 @@ TONES = {
|
||||
("G",),
|
||||
("G#", "Ab"),
|
||||
],
|
||||
# Blues/Pentatonic — Western names with blues and pentatonic scales.
|
||||
"blues": [
|
||||
("A",),
|
||||
("A#", "Bb"),
|
||||
("B",),
|
||||
("C",),
|
||||
("C#", "Db"),
|
||||
("D",),
|
||||
("D#", "Eb"),
|
||||
("E",),
|
||||
("F",),
|
||||
("F#", "Gb"),
|
||||
("G",),
|
||||
("G#", "Ab"),
|
||||
],
|
||||
# Javanese gamelan — pelog approximation in 12-TET.
|
||||
# True gamelan uses non-Western intonation; these are closest 12-TET fits.
|
||||
"gamelan": [
|
||||
("nem",), # A — 6
|
||||
("pi",), # Bb — 7 (barang in some)
|
||||
("barang",), # B — 7
|
||||
("ji",), # C — 1
|
||||
("ro-",), # Db — 2b
|
||||
("ro",), # D — 2
|
||||
("lu-",), # Eb — 3b
|
||||
("lu",), # E — 3
|
||||
("pat",), # F — 4
|
||||
("pat+",), # F# — 4#
|
||||
("mo",), # G — 5
|
||||
("nem-",), # Ab — 6b
|
||||
],
|
||||
}
|
||||
|
||||
DEGREES = {
|
||||
@@ -107,7 +138,25 @@ DEGREES = {
|
||||
("san", ()), # 3rd
|
||||
("shi", ()), # 4th
|
||||
("go", ()), # 5th
|
||||
("roku", ()), # 6th (pentatonic scales skip some)
|
||||
("roku", ()), # 6th
|
||||
],
|
||||
"blues": [
|
||||
("tonic", ()),
|
||||
("supertonic", ()),
|
||||
("mediant", ()),
|
||||
("subdominant", ()),
|
||||
("dominant", ()),
|
||||
("submediant", ()),
|
||||
("subtonic", ()),
|
||||
],
|
||||
"gamelan": [
|
||||
("ji", ()), # 1
|
||||
("ro", ()), # 2
|
||||
("lu", ()), # 3
|
||||
("pat", ()), # 4
|
||||
("mo", ()), # 5
|
||||
("nem", ()), # 6
|
||||
("pi", ()), # 7
|
||||
],
|
||||
}
|
||||
|
||||
@@ -254,6 +303,71 @@ JAPANESE_SCALES = {
|
||||
}
|
||||
}
|
||||
|
||||
# Blues and pentatonic scales — foundational to American music.
|
||||
BLUES_SCALES = {
|
||||
12: {
|
||||
"chromatic": (12, {}),
|
||||
"pentatonic": [
|
||||
5,
|
||||
{
|
||||
# Major pentatonic — C D E G A
|
||||
"major pentatonic": {"intervals": (2, 2, 3, 2, 3)},
|
||||
# Minor pentatonic — C Eb F G Bb
|
||||
"minor pentatonic": {"intervals": (3, 2, 2, 3, 2)},
|
||||
},
|
||||
],
|
||||
"hexatonic": [
|
||||
6,
|
||||
{
|
||||
# Blues scale — C Eb F F# G Bb
|
||||
"blues": {"intervals": (3, 2, 1, 1, 3, 2)},
|
||||
# Major blues — C D D# E G A
|
||||
"major blues": {"intervals": (2, 1, 1, 3, 2, 3)},
|
||||
},
|
||||
],
|
||||
"heptatonic": [
|
||||
7,
|
||||
{
|
||||
# Mixolydian (dominant blues sound) — C D E F G A Bb
|
||||
"dominant": {"intervals": (2, 2, 1, 2, 2, 1, 2)},
|
||||
# Dorian (minor blues/jazz) — C D Eb F G A Bb
|
||||
"minor": {"intervals": (2, 1, 2, 2, 2, 1, 2)},
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
# Javanese gamelan scales — 12-TET approximations.
|
||||
# True gamelan tuning varies between ensembles and does not conform
|
||||
# to equal temperament. These approximations capture the melodic
|
||||
# character of the scales.
|
||||
GAMELAN_SCALES = {
|
||||
12: {
|
||||
"chromatic": (12, {}),
|
||||
"pentatonic": [
|
||||
5,
|
||||
{
|
||||
# Slendro — roughly equal 5-tone division of the octave
|
||||
# Approximated as: C D F G Bb
|
||||
"slendro": {"intervals": (2, 3, 2, 3, 2)},
|
||||
# Pelog pathet nem — C Db E F G (approx)
|
||||
"pelog nem": {"intervals": (1, 3, 1, 2, 5)},
|
||||
# Pelog pathet barang — C Db E F# B (approx)
|
||||
"pelog barang": {"intervals": (1, 3, 3, 4, 1)},
|
||||
# Pelog pathet lima — C Db E F Ab (approx)
|
||||
"pelog lima": {"intervals": (1, 3, 1, 3, 4)},
|
||||
},
|
||||
],
|
||||
"heptatonic": [
|
||||
7,
|
||||
{
|
||||
# Full pelog — all 7 tones: C Db E F G Ab B (approx)
|
||||
"pelog": {"intervals": (1, 3, 1, 2, 1, 3, 1)},
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
SYSTEMS = NotImplemented
|
||||
|
||||
# Modes are rotations of the major scale pattern.
|
||||
|
||||
+4
-1
@@ -1,6 +1,7 @@
|
||||
from ._statics import (
|
||||
TEMPERAMENTS, TONES, DEGREES, SCALES,
|
||||
INDIAN_SCALES, ARABIC_SCALES, JAPANESE_SCALES, SYSTEMS,
|
||||
INDIAN_SCALES, ARABIC_SCALES, JAPANESE_SCALES,
|
||||
BLUES_SCALES, GAMELAN_SCALES, SYSTEMS,
|
||||
)
|
||||
|
||||
|
||||
@@ -134,4 +135,6 @@ SYSTEMS = {
|
||||
"indian": System(tone_names=TONES["indian"], degrees=DEGREES["indian"], scales=INDIAN_SCALES[12]),
|
||||
"arabic": System(tone_names=TONES["arabic"], degrees=DEGREES["arabic"], scales=ARABIC_SCALES[12]),
|
||||
"japanese": System(tone_names=TONES["japanese"], degrees=DEGREES["japanese"], scales=JAPANESE_SCALES[12]),
|
||||
"blues": System(tone_names=TONES["blues"], degrees=DEGREES["blues"], scales=BLUES_SCALES[12]),
|
||||
"gamelan": System(tone_names=TONES["gamelan"], degrees=DEGREES["gamelan"], scales=GAMELAN_SCALES[12]),
|
||||
}
|
||||
|
||||
@@ -2157,3 +2157,86 @@ def test_japanese_heptatonic_intervals_sum_to_12():
|
||||
for name, scale in japanese.scales["heptatonic"].items():
|
||||
total = sum(scale["intervals"])
|
||||
assert total == 12, f"{name} intervals sum to {total}, not 12"
|
||||
|
||||
|
||||
# ── Blues system ────────────────────────────────────────────────────────────
|
||||
|
||||
def test_blues_system_exists():
|
||||
assert "blues" in SYSTEMS
|
||||
assert SYSTEMS["blues"].semitones == 12
|
||||
|
||||
|
||||
def test_blues_major_pentatonic():
|
||||
c = TonedScale(tonic="C4", system=SYSTEMS["blues"])
|
||||
s = c["major pentatonic"]
|
||||
assert s.note_names == ["C", "D", "E", "G", "A", "C"]
|
||||
|
||||
|
||||
def test_blues_minor_pentatonic():
|
||||
c = TonedScale(tonic="C4", system=SYSTEMS["blues"])
|
||||
s = c["minor pentatonic"]
|
||||
assert s.note_names == ["C", "D#", "F", "G", "A#", "C"]
|
||||
|
||||
|
||||
def test_blues_scale():
|
||||
c = TonedScale(tonic="C4", system=SYSTEMS["blues"])
|
||||
s = c["blues"]
|
||||
names = s.note_names
|
||||
assert names == ["C", "D#", "F", "F#", "G", "A#", "C"]
|
||||
assert len(names) == 7 # 6 notes + octave
|
||||
|
||||
|
||||
def test_blues_all_scales_available():
|
||||
c = TonedScale(tonic="C4", system=SYSTEMS["blues"])
|
||||
for scale in ["major pentatonic", "minor pentatonic", "blues",
|
||||
"major blues", "dominant", "minor"]:
|
||||
assert scale in c.scales, f"Missing scale: {scale}"
|
||||
|
||||
|
||||
def test_blues_all_intervals_sum_to_12():
|
||||
blues = SYSTEMS["blues"]
|
||||
for scale_type in blues.scales:
|
||||
for name, scale in blues.scales[scale_type].items():
|
||||
total = sum(scale["intervals"])
|
||||
assert total == 12, f"{name} intervals sum to {total}, not 12"
|
||||
|
||||
|
||||
# ── Gamelan system ──────────────────────────────────────────────────────────
|
||||
|
||||
def test_gamelan_system_exists():
|
||||
assert "gamelan" in SYSTEMS
|
||||
assert SYSTEMS["gamelan"].semitones == 12
|
||||
|
||||
|
||||
def test_gamelan_tones():
|
||||
gamelan = SYSTEMS["gamelan"]
|
||||
names = [t.name for t in gamelan.tones]
|
||||
assert "ji" in names
|
||||
assert "ro" in names
|
||||
assert "mo" in names
|
||||
|
||||
|
||||
def test_gamelan_slendro():
|
||||
ji = TonedScale(tonic="ji4", system=SYSTEMS["gamelan"])
|
||||
s = ji["slendro"]
|
||||
assert s.note_names == ["ji", "ro", "pat", "mo", "pi", "ji"]
|
||||
|
||||
|
||||
def test_gamelan_pelog():
|
||||
ji = TonedScale(tonic="ji4", system=SYSTEMS["gamelan"])
|
||||
s = ji["pelog"]
|
||||
assert len(s) == 8 # 7 notes + octave
|
||||
|
||||
|
||||
def test_gamelan_all_scales_available():
|
||||
ji = TonedScale(tonic="ji4", system=SYSTEMS["gamelan"])
|
||||
for scale in ["slendro", "pelog nem", "pelog barang", "pelog lima", "pelog"]:
|
||||
assert scale in ji.scales, f"Missing scale: {scale}"
|
||||
|
||||
|
||||
def test_gamelan_all_intervals_sum_to_12():
|
||||
gamelan = SYSTEMS["gamelan"]
|
||||
for scale_type in gamelan.scales:
|
||||
for name, scale in gamelan.scales[scale_type].items():
|
||||
total = sum(scale["intervals"])
|
||||
assert total == 12, f"{name} intervals sum to {total}, not 12"
|
||||
|
||||
Reference in New Issue
Block a user