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
2 changes: 2 additions & 0 deletions doc/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
- [Phonemes](./lang/phonemes.md)
- [Phonotactics](./lang/phonotactics.md)
- [Frequencies](./lang/frequencies.md)
- [Stress](./lang/stress.md)
- [Vowel Reduction](./lang/reduction.md)
- [Grammar](./lang/grammar.md)
- [Morphology](./lang/morphology.md)
- [Voices](./voices/README.md)
Expand Down
67 changes: 2 additions & 65 deletions doc/lang/grammar.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,71 +86,8 @@ that makes sentences feel like language rather than random sound.

## Stress Assignment

Generated words can receive IPA stress marks (ˈ for primary, ˌ for secondary) according to a configurable strategy.
Stress is assigned after syllable generation, so it works with both fresh generation and lexicon-based sampling.

### Top-Level Configuration

Add a `"stress"` field to your sketch to set the default strategy for all word classes:

```json
{
"stress": {
"default": "trochaic",
"secondary": true
}
}
```

`"default"` is the stress strategy applied to every word class that does not override it.
`"secondary"` enables secondary stress on alternating syllables in words with 3 or more syllables.
It defaults to `false` when omitted.

### Strategies

| Strategy | Primary stress position | Typical languages |
|:--------------|:---------------------------------|:------------------|
| `trochaic` | First syllable | English, German |
| `iambic` | Second syllable (first if mono) | French-ish |
| `penultimate` | Second-to-last syllable | Latin, Spanish |
| `final` | Last syllable | French, Turkish |
| `none` | No stress assigned | Function words |

Monosyllabic words receive primary stress unless the strategy is `none`.

### Per-Class Overrides

Each word class can override the default strategy with its own `"stress"` field.
This is how you keep function words unstressed while content words carry stress:

```json
{
"stress": { "default": "trochaic", "secondary": true },
"word_classes": {
"det": { "patterns": ["CV"], "stress": "none" },
"noun": { "patterns": ["$OV$K", "$OV$K $OV$K"] },
"verb": { "patterns": ["$OV$K"], "stress": "penultimate" }
}
}
```

Here, determiners get no stress, nouns inherit the top-level `trochaic` strategy, and verbs use `penultimate`.

### Secondary Stress

When `"secondary": true` and a word has 3 or more syllables, secondary stress (ˌ) is placed on alternating
even-indexed syllables that do not already carry primary stress.
For example, a four-syllable trochaic word: **ˈ**ta.ˌka.ta.ka.

### Without Stress Configuration

When no `"stress"` field is present at either level, no stress is assigned -- words are generated without stress marks.

Note that omitting `"stress"` is distinct from setting `"stress": "none"`.
Both produce unstressed words, but `"none"` is meaningful as a per-class override: it keeps specific word classes
unstressed when a top-level default would otherwise give them stress.
This is common for function words; for example, in English, articles like "a" `/ə/` and "the" `/ðə/` and prepositions
like "to" `/tu/` and "of" `/ʌv/` carry no stress.
The top-level `"stress"` field and per-class `"stress"` overrides (like `"stress": "none"` in the example above)
are documented on the [Stress](stress.md) page.

## Sentence Templates

Expand Down
2 changes: 1 addition & 1 deletion doc/lang/morphology.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ Add a `morphology` section to your config JSON:

### Application Order

Morphology runs **after** word generation but **before** stress assignment.
Morphology runs **after** word generation but **before** [stress assignment](stress.md).
This means stress is computed over the full derived form including affixes, matching how natural languages work (e.g., English "PHOto" vs "phoTOgraphy").

### Decay
Expand Down
106 changes: 106 additions & 0 deletions doc/lang/reduction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Vowel Reduction

Vowel reduction collapses the vowels of unstressed syllables to a weaker, more central vowel (e.g.:
schwa `ə`).
This is characteristic of stress-timed languages: English pronounces `banana` as /bəˈnænə/ rather
than /baˈnana/, Russian merges unstressed /o/ and /a/ (*молоко* /məlɐˈko/), European Portuguese
reduces unstressed vowels so far they often nearly disappear, and Central Catalan collapses
unstressed /a/, /e/, and /ɛ/ to [ə].
Syllable-timed languages like Spanish, standard Italian, and Japanese instead keep full vowel
quality everywhere; leave reduction off for languages with that rhythm.

Natural languages vary in what unstressed vowels reduce *to* (Russian [ɐ], European Portuguese [ɨ],
Catalan [u] for back vowels) and in which vowels reduce at all.
The `target` and `targets` fields configure both aspects.

## Configuration

Add a `reduction` section to your config JSON:

```json
{
"reduction": { "probability": 0.7, "target": "ə" }
}
```

### Fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `probability` | number | Yes | Probability that an unstressed syllable's vowel reduces, between 0.0 and 1.0. |
| `target` | string | One of `target`/`targets` | Single vowel that every reduced vowel collapses to. |
| `targets` | object | One of `target`/`targets` | Map from source vowels to the vowel they reduce to. Only mapped vowels reduce. |

Exactly one of `target` and `targets` must be present.
If the `reduction` section is absent, no reduction occurs.

### Targets

`target` is the simple form: every vowel in a reduced syllable collapses to that one vowel, as in
the English-style example above.

`targets` gives per-vowel control.
Each key is a string of one or more IPA vowels; the value is the single vowel they reduce to.
Only the listed vowels reduce — everything else keeps its quality even in unstressed syllables.
A vowel may appear in at most one key.

A Central Catalan–style system, where /a e ɛ/ merge to [ə] and /o ɔ/ raise to [u] while the high
vowels /i u/ never reduce:

```json
{
"reduction": {
"probability": 1.0,
"targets": { "aeɛ": "ə", "oɔ": "u" }
}
}
```

A Russian-style *akanye*, merging unstressed /o/ and /a/ into [ɐ]:

```json
{
"reduction": {
"probability": 0.9,
"targets": { "oa": "ɐ" }
}
}
```

### Interaction with Stress

Reduction targets whatever syllables are left unstressed by [stress assignment](./stress.md), so
which syllables are eligible is determined by the `stress` configuration:

- Syllables carrying primary stress never reduce.
This includes monosyllabic words, which always receive primary stress (unless the strategy is
`none`).
- A class with `"stress": "none"` has every syllable eligible, so the word can reduce entirely.
- With `"secondary": true`, alternating syllables carry secondary stress and are also protected,
halving the eligible syllables in longer words.

Because of this dependency, a sketch with a `reduction` section but no stress configuration at all
(no top-level `stress` and no per-class `"stress"` fields) is rejected as a validation error.
Without stress, every vowel is a candidate for reduction, which leads to an undirected mush of
whatever your target is.
If you still want this, you can explicitly write `"stress": "none"` at the top-level or per-class.

## Behavior

- Reduction runs after [stress assignment](./stress.md), so it depends on the final stress marking
of each word.
- Only fully unstressed syllables are eligible.
Syllables with primary or secondary stress never reduce.
Secondary stress protects vowel quality, as it does in English (compare the final syllables of
`ˈal.pha.ˌbet` and `ˈtrum.pet`).
- Word classes with `"stress": "none"` have every syllable unstressed, so function-word classes
(determiners, pronouns, prepositions) reduce throughout — giving the "the → ðə, of → əv" effect.
- The whole vocalic nucleus reduces: diacritics on the vowel are dropped, and a diphthong collapses
to a single vowel.
With `targets`, a diphthong is keyed by its first component (so with `"aeɛ": "ə"`, /ai/ collapses
to [ə] but /ui/ is untouched).
Consonants in the onset and coda are untouched.
- Reduction applies to a word's lexicon entry when the vocabulary is pre-generated, so a reduced
word form is stable across a session.
- A reduction target may appear in output even if it is not in the configured vowel inventory;
targets are outcomes of reduction, not inventory phonemes.
73 changes: 73 additions & 0 deletions doc/lang/stress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Stress

Generated words can receive IPA stress marks (ˈ for primary, ˌ for secondary) according to a configurable strategy.
Stress is assigned after syllable generation, so it works with both fresh generation and lexicon-based sampling.
When [morphology](morphology.md) is configured, affixation runs first, so stress is computed over the full derived form.
Syllables left unstressed are eligible for [vowel reduction](reduction.md).

## Top-Level Configuration

Add a `"stress"` field to your sketch to set the default strategy for all word classes:

```json
{
"stress": {
"default": "trochaic",
"secondary": true
}
}
```

`"default"` is the stress strategy applied to every word class that does not override it.
`"secondary"` enables secondary stress on alternating syllables in words with 3 or more syllables.
It defaults to `false` when omitted.

## Strategies

| Strategy | Primary stress position | Typical languages |
|:--------------|:---------------------------------|:------------------|
| `trochaic` | First syllable | English, German |
| `iambic` | Second syllable (first if mono) | French-ish |
| `penultimate` | Second-to-last syllable | Latin, Spanish |
| `final` | Last syllable | French, Turkish |
| `none` | No stress assigned | Function words |

Monosyllabic words receive primary stress unless the strategy is `none`.

## Per-Class Overrides

Each [word class](grammar.md#word-classes) can override the default strategy with its own `"stress"` field.
This is how you keep function words unstressed while content words carry stress:

```json
{
"stress": { "default": "trochaic", "secondary": true },
"word_classes": {
"det": { "patterns": ["CV"], "stress": "none" },
"noun": { "patterns": ["$OV$K", "$OV$K $OV$K"] },
"verb": { "patterns": ["$OV$K"], "stress": "penultimate" }
}
}
```

Here, determiners get no stress, nouns inherit the top-level `trochaic` strategy, and verbs use `penultimate`.

## Secondary Stress

When `"secondary": true` and a word has 3 or more syllables, secondary stress (ˌ) is placed on alternating
syllables aligned with the primary stress, so the word parses into uniform two-syllable feet and
secondary stress never lands adjacent to the primary.

- `trochaic` builds strong-weak feet from the start of the word: ˈta.ka.ˌta.ka
- `iambic` builds weak-strong feet: ta.ˈta.ta.ˌta
- `penultimate` and `final` alternate outward from the primary: ta.ˌta.ta.ˈta.ta and ta.ˌta.ta.ˈta

## Without Stress Configuration

When no `"stress"` field is present at either level, no stress is assigned -- words are generated without stress marks.

Note that omitting `"stress"` is distinct from setting `"stress": "none"`.
Both produce unstressed words, but `"none"` is meaningful as a per-class override: it keeps specific word classes
unstressed when a top-level default would otherwise give them stress.
This is common for function words; for example, in English, articles like "a" `/ə/` and "the" `/ðə/` and prepositions
like "to" `/tu/` and "of" `/ʌv/` carry no stress.
1 change: 1 addition & 0 deletions examples/english.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"distribution": { "curve": "cosine" }
},
"stress": { "default": "trochaic", "secondary": true },
"reduction": { "probability": 0.7, "target": "ə" },
"sets": {
"O": ["n", "t", "s", "ɹ", "d", "l", "k", "m", "p", "f", "b", "h", "ʃ", "g", "j", "θ", "st", "sp", "sk", "tɹ", "pɹ", "pl", "bl", "kl"],
"K": ["n", "t", "s", "ɹ", "d", "l", "ð", "k", "z", "m", "p", "v", "f", "ŋ", "ʃ", "θ", "nd", "nt", "ns", "ks", "lz", "lk"]
Expand Down
Loading
Loading