subsequence.cadences

Cadences — the curated formula table behind the producer cadence names.

A cadence is a two-chord tail formula plus a melodic close degree. The producer names are primary — "strong", "soft", "open", "fakeout" — with the theory names (authentic, plagal, half, deceptive) accepted as aliases, per the standing rule: theory machinery under the hood, producer words on the surface.

The table is pure data; the consumers wire it in:

  • Progression.cadence(name) — tail substitution on a progression value.
  • Progression.generate(cadence=) / freeze(cadence=) — the formula becomes pins on the final bars of the constrained walk.
  • Motif.generate(cadence=) — the close degree becomes end_on.
  • Composition.request_cadence() / section_cadence() — the live clock steers its walk to arrive at the formula.
  • sentence() / period() — the close degree aims the final unit.

Formula elements follow the progression-element grammar: ints are diatonic degrees (quality inferred from key+scale at resolution time — 4 is IV in major and iv in minor), roman strings carry their quality with them ("V" is the major dominant even in minor — the cadential convention).

  1"""Cadences — the curated formula table behind the producer cadence names.
  2
  3A cadence is a two-chord tail formula plus a melodic close degree.  The
  4producer names are primary — ``"strong"``, ``"soft"``, ``"open"``,
  5``"fakeout"`` — with the theory names (authentic, plagal, half, deceptive)
  6accepted as aliases, per the standing rule: theory machinery under the hood,
  7producer words on the surface.
  8
  9The table is pure data; the consumers wire it in:
 10
 11- ``Progression.cadence(name)`` — tail substitution on a progression value.
 12- ``Progression.generate(cadence=)`` / ``freeze(cadence=)`` — the formula
 13  becomes pins on the final bars of the constrained walk.
 14- ``Motif.generate(cadence=)`` — the close degree becomes ``end_on``.
 15- ``Composition.request_cadence()`` / ``section_cadence()`` — the live
 16  clock steers its walk to arrive at the formula.
 17- ``sentence()`` / ``period()`` — the close degree aims the final unit.
 18
 19Formula elements follow the progression-element grammar: ints are diatonic
 20degrees (quality inferred from key+scale at resolution time — ``4`` is IV
 21in major and iv in minor), roman strings carry their quality with them
 22(``"V"`` is the major dominant even in minor — the cadential convention).
 23"""
 24
 25import dataclasses
 26import typing
 27
 28
 29@dataclasses.dataclass(frozen=True)
 30class Cadence:
 31
 32	"""One cadence formula — a named tail plus its melodic close.
 33
 34	Attributes:
 35		name: The producer name (the primary key in the table).
 36		theory_name: The traditional name, for the curious.
 37		formula: The chord tail, in progression-element grammar, ending on
 38			the arrival chord.
 39		close_degree: The scale degree a melody lands on at this cadence
 40			(1 for full closes; 5 for the open half — and 1 for the
 41			fakeout too: the melody resolves as promised while the
 42			harmony swerves, which is the trick of it).
 43	"""
 44
 45	name: str
 46	theory_name: str
 47	formula: typing.Tuple[typing.Any, ...]
 48	close_degree: int
 49
 50
 51# The curated table — producer names primary.  Two-chord tails throughout:
 52# a cadence is an arrival WITH its approach, and two chords is the smallest
 53# honest spelling of that.
 54CADENCES: typing.Dict[str, Cadence] = {
 55	"strong": Cadence(
 56		name = "strong",
 57		theory_name = "authentic",
 58		formula = ("V", 1),
 59		close_degree = 1,
 60	),
 61	"soft": Cadence(
 62		name = "soft",
 63		theory_name = "plagal",
 64		formula = (4, 1),
 65		close_degree = 1,
 66	),
 67	"open": Cadence(
 68		name = "open",
 69		theory_name = "half",
 70		formula = (4, "V"),
 71		close_degree = 5,
 72	),
 73	"fakeout": Cadence(
 74		name = "fakeout",
 75		theory_name = "deceptive",
 76		formula = ("V", 6),
 77		close_degree = 1,
 78	),
 79}
 80
 81# Theory names as aliases — accuracy costs nothing here, the words name the
 82# same formulas.
 83_ALIASES: typing.Dict[str, str] = {
 84	"authentic": "strong",
 85	"perfect": "strong",
 86	"plagal": "soft",
 87	"half": "open",
 88	"deceptive": "fakeout",
 89	"interrupted": "fakeout",
 90}
 91
 92
 93def cadence_formula (name: str) -> Cadence:
 94
 95	"""Look up a cadence by producer name or theory alias, loudly.
 96
 97	Raises:
 98		ValueError: If the name is unknown — the error lists every valid
 99			name and alias.
100	"""
101
102	if not isinstance(name, str):
103		raise TypeError(f"a cadence is named by string, got {name!r}")
104
105	key = name.strip().lower()
106	key = _ALIASES.get(key, key)
107
108	if key not in CADENCES:
109		names = ", ".join(sorted(CADENCES))
110		aliases = ", ".join(sorted(_ALIASES))
111		raise ValueError(f"Unknown cadence {name!r}. Cadences: {names} (aliases: {aliases}).")
112
113	return CADENCES[key]
@dataclasses.dataclass(frozen=True)
class Cadence:
30@dataclasses.dataclass(frozen=True)
31class Cadence:
32
33	"""One cadence formula — a named tail plus its melodic close.
34
35	Attributes:
36		name: The producer name (the primary key in the table).
37		theory_name: The traditional name, for the curious.
38		formula: The chord tail, in progression-element grammar, ending on
39			the arrival chord.
40		close_degree: The scale degree a melody lands on at this cadence
41			(1 for full closes; 5 for the open half — and 1 for the
42			fakeout too: the melody resolves as promised while the
43			harmony swerves, which is the trick of it).
44	"""
45
46	name: str
47	theory_name: str
48	formula: typing.Tuple[typing.Any, ...]
49	close_degree: int

One cadence formula — a named tail plus its melodic close.

Attributes:
  • name: The producer name (the primary key in the table).
  • theory_name: The traditional name, for the curious.
  • formula: The chord tail, in progression-element grammar, ending on the arrival chord.
  • close_degree: The scale degree a melody lands on at this cadence (1 for full closes; 5 for the open half — and 1 for the fakeout too: the melody resolves as promised while the harmony swerves, which is the trick of it).
Cadence( name: str, theory_name: str, formula: Tuple[Any, ...], close_degree: int)
name: str
theory_name: str
formula: Tuple[Any, ...]
close_degree: int
CADENCES: Dict[str, Cadence] = {'strong': Cadence(name='strong', theory_name='authentic', formula=('V', 1), close_degree=1), 'soft': Cadence(name='soft', theory_name='plagal', formula=(4, 1), close_degree=1), 'open': Cadence(name='open', theory_name='half', formula=(4, 'V'), close_degree=5), 'fakeout': Cadence(name='fakeout', theory_name='deceptive', formula=('V', 6), close_degree=1)}
def cadence_formula(name: str) -> Cadence:
 94def cadence_formula (name: str) -> Cadence:
 95
 96	"""Look up a cadence by producer name or theory alias, loudly.
 97
 98	Raises:
 99		ValueError: If the name is unknown — the error lists every valid
100			name and alias.
101	"""
102
103	if not isinstance(name, str):
104		raise TypeError(f"a cadence is named by string, got {name!r}")
105
106	key = name.strip().lower()
107	key = _ALIASES.get(key, key)
108
109	if key not in CADENCES:
110		names = ", ".join(sorted(CADENCES))
111		aliases = ", ".join(sorted(_ALIASES))
112		raise ValueError(f"Unknown cadence {name!r}. Cadences: {names} (aliases: {aliases}).")
113
114	return CADENCES[key]

Look up a cadence by producer name or theory alias, loudly.

Raises:
  • ValueError: If the name is unknown — the error lists every valid name and alias.