Skip to content

Commit dda34d3

Browse files
author
DavidQ
committed
Add pitch gender helper labels and voice age tuning notes - PR_26130_012-text2speach-v2-pitch-age-labels
1 parent ecd78b5 commit dda34d3

8 files changed

Lines changed: 164 additions & 13 deletions

File tree

docs/dev/reports/PR_26130_011-text2speach-v2-control-dependency-order.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ No `start_of_day` files were changed.
3030
15. Speak / Pause / Resume / Stop
3131
- Moved Text into the ordered Speech Options flow.
3232
- Moved Speak/Pause/Resume/Stop to the end of the ordered control flow.
33+
- Aligned the `text2speach-V2` second header line with the other V2 tool header pattern: concise context in the eyebrow, operational detail in the meta line.
3334
- Changed Gender to a helper filter with `Any`, `Male Preferred`, and `Female Preferred`.
3435
- Included unknown and explicit neutral voices in both preferred Gender buckets so language coverage is not lost.
3536
- Removed claims of true gender transformation from behavior/logging; Gender is logged as a helper filter only.
@@ -56,6 +57,7 @@ Playwright impacted: Yes.
5657
Coverage added/updated for:
5758

5859
- exact control ordering from Gender through Speak/Pause/Resume/Stop
60+
- header eyebrow/meta copy matching the V2 tool header pattern
5961
- Gender-to-Language filtering through preferred helper buckets
6062
- unknown and explicit neutral voices remaining available under preferred Gender helper buckets
6163
- Language-to-Voice filtering within the selected Gender helper bucket
@@ -67,9 +69,9 @@ Coverage added/updated for:
6769
- Voice Age shaping without filtering or clearing the selected Language/Voice
6870
- Auto Speak and runtime queue action behavior already covered by the speech action path
6971

70-
Expected pass behavior: controls appear in the requested order, Gender helper filters keep unknown/neutral voices available in preferred buckets, Language narrows Voice choices within the selected Gender helper, invalid selected voices are adjusted and logged, presets visibly update sliders, manual slider edits are preserved against later derived shaping, and runtime speech actions still target the current queue/options.
72+
Expected pass behavior: header copy follows the other V2 tools' eyebrow/meta pattern, controls appear in the requested order, Gender helper filters keep unknown/neutral voices available in preferred buckets, Language narrows Voice choices within the selected Gender helper, invalid selected voices are adjusted and logged, presets visibly update sliders, manual slider edits are preserved against later derived shaping, and runtime speech actions still target the current queue/options.
7173

72-
Expected fail behavior: tests fail if order regresses, Gender loses unknown/neutral voices, Language/Voice filtering breaks, invalid voice reset is silent, preset slider updates stop applying, manual slider edits are overwritten by later Voice Age shaping, Auto Speak no longer queues valid playback, or Speak/Pause/Resume/Stop stop operating on the runtime queue.
74+
Expected fail behavior: tests fail if the header copy regresses, order regresses, Gender loses unknown/neutral voices, Language/Voice filtering breaks, invalid voice reset is silent, preset slider updates stop applying, manual slider edits are overwritten by later Voice Age shaping, Auto Speak no longer queues valid playback, or Speak/Pause/Resume/Stop stop operating on the runtime queue.
7375

7476
## Validation
7577

@@ -111,7 +113,7 @@ Skipped. The full samples smoke test is intentionally out of scope because this
111113
Repo-structured delta ZIP:
112114

113115
```text
114-
tmp/PR_26130_011-text2speach-v2-control-dependency-order_delta.zip
116+
tmp/PR_26130_011-text2speach-v2-control-dependency-order-header_delta.zip
115117
```
116118

117119
## Manual Validation Steps
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# PR_26130_012-text2speach-v2-pitch-age-labels
2+
3+
## Purpose
4+
5+
Add the requested `text2speach-V2` Pitch helper scale and lock Voice Age simulation behavior to visible Rate/Pitch slider updates without claiming true gender conversion.
6+
7+
## Scope
8+
9+
Changed only the `text2speach-V2` UI, existing speech defaults/schema hooks, targeted Workspace Manager V2 Playwright coverage, and required reports.
10+
11+
No `start_of_day` files were changed.
12+
13+
## Implementation Summary
14+
15+
- Added a centered helper scale directly under the Pitch slider:
16+
17+
```text
18+
Male < Neutral > Female
19+
```
20+
21+
- Kept the helper visual-only. It labels the pitch direction and does not claim true gender conversion.
22+
- Renamed Voice Age option/value `elder` to `elderly` across defaults, schema, labels, and Playwright expectations.
23+
- Kept Voice Age simulation behavior in the existing preset-derived slider path:
24+
- `child`: higher pitch, slightly faster rate
25+
- `teen`: slightly higher pitch, normal/slightly faster rate
26+
- `adult`: neutral pitch and rate
27+
- `elderly`: lower pitch, slower rate
28+
- Preserved manual Rate and Pitch slider authority: once the user edits those sliders, later Voice Age changes do not overwrite those manual values.
29+
30+
## Playwright Impact
31+
32+
Playwright impacted: Yes.
33+
34+
Coverage added/updated for:
35+
36+
- Pitch helper scale text `Male < Neutral > Female`
37+
- helper scale placement immediately below the Pitch slider
38+
- helper scale centered alignment
39+
- Voice Age enum/options using `elderly`
40+
- Child, Teen, Adult, Elderly, and Any Voice Age slider updates
41+
- existing manual Rate/Pitch override behavior after Voice Age changes
42+
43+
Expected pass behavior: the Pitch helper appears centered under the Pitch slider, Voice Age selections visibly update Rate/Pitch when sliders are not manually overridden, and manual Rate/Pitch edits remain authoritative.
44+
45+
Expected fail behavior: tests fail if the helper text is missing/misplaced/not centered, if `elderly` is not present in the schema/UI options, if Voice Age no longer updates sliders, or if manual Rate/Pitch edits are overwritten.
46+
47+
## Validation
48+
49+
Passed:
50+
51+
```text
52+
npm run test:workspace-v2
53+
```
54+
55+
Result:
56+
57+
```text
58+
28 passed
59+
```
60+
61+
Additional checks passed:
62+
63+
```text
64+
node --check src/engine/audio/TextToSpeechDefaults.js
65+
node --check tools/text2speach-V2/js/controls/SpeechOptionsControl.js
66+
node --check tests/playwright/tools/WorkspaceManagerV2.spec.mjs
67+
node -e "JSON.parse(require('fs').readFileSync('tools/schemas/tools/text2speach-V2.schema.json','utf8')); console.log('schema json ok')"
68+
rg -n -P "<script(?![^>]*\bsrc=)|<style|\son[a-zA-Z]+=" tools/text2speach-V2/index.html
69+
git diff --check HEAD -- .
70+
```
71+
72+
The inline HTML restriction scan returned no matches. `git diff --check` reported only the existing Windows line-ending warnings and no whitespace errors.
73+
74+
The workspace-v2 Playwright run also regenerated advisory V8 coverage reports:
75+
76+
- `docs/dev/reports/playwright_v8_coverage_report.txt`
77+
- `docs/dev/reports/coverage_changed_js_guardrail.txt`
78+
79+
Changed runtime JavaScript coverage from the guardrail:
80+
81+
- `(98%) tools/text2speach-V2/js/controls/SpeechOptionsControl.js - executed lines 478/478; executed functions 46/47`
82+
- `(100%) src/engine/audio/TextToSpeechDefaults.js - executed lines 162/162; executed functions 1/1`
83+
84+
## Full Samples Smoke Test
85+
86+
Skipped. The full samples smoke test is intentionally out of scope because this PR is limited to `text2speach-V2` Pitch helper labeling, Voice Age simulation labels/defaults, schema enum alignment, and targeted Workspace Manager V2 Playwright coverage.
87+
88+
## ZIP Artifact
89+
90+
Repo-structured delta ZIP:
91+
92+
```text
93+
tmp/PR_26130_012-text2speach-v2-pitch-age-labels_delta.zip
94+
```
95+
96+
## Manual Validation Steps
97+
98+
1. Open `tools/text2speach-V2/index.html`.
99+
2. Confirm the Pitch slider shows the centered helper scale `Male < Neutral > Female` directly below the slider.
100+
3. Select Voice Age `Child`, `Teen`, `Adult`, and `Elderly`; Rate and Pitch should update visibly when those sliders have not been manually edited.
101+
4. Manually change Rate and Pitch, then select another Voice Age; the manual Rate and Pitch values should remain.
102+
5. Confirm the UI does not describe the Pitch helper as true gender conversion.
103+
104+
Expected outcome: Pitch direction is visually explained, Voice Age simulation affects default Rate/Pitch only while allowed, and manual slider edits win.
105+
106+
## Changed Files
107+
108+
- `src/engine/audio/TextToSpeechDefaults.js`
109+
- `tests/playwright/tools/WorkspaceManagerV2.spec.mjs`
110+
- `tools/schemas/tools/text2speach-V2.schema.json`
111+
- `tools/text2speach-V2/index.html`
112+
- `tools/text2speach-V2/js/controls/SpeechOptionsControl.js`
113+
- `tools/text2speach-V2/styles/text2speach-V2.css`
114+
- `docs/dev/reports/PR_26130_012-text2speach-v2-pitch-age-labels.md`
115+
- `docs/dev/reports/playwright_v8_coverage_report.txt`
116+
- `docs/dev/reports/coverage_changed_js_guardrail.txt`

src/engine/audio/TextToSpeechDefaults.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,15 @@ const TEXT_TO_SPEECH_AGE_FILTER_OPTIONS = Object.freeze([
2020
Object.freeze({ label: "Any", value: "any" }),
2121
Object.freeze({ label: "Adult", value: "adult" }),
2222
Object.freeze({ label: "Child", value: "child" }),
23-
Object.freeze({ label: "Elder", value: "elder" }),
23+
Object.freeze({ label: "Elderly", value: "elderly" }),
2424
Object.freeze({ label: "Teen", value: "teen" })
2525
]);
2626

2727
const TEXT_TO_SPEECH_VOICE_AGE_PRESET_DEFAULTS = Object.freeze({
2828
adult: Object.freeze({ pitchOffset: 0, rateMultiplier: 1 }),
2929
any: Object.freeze({ pitchOffset: 0, rateMultiplier: 1 }),
3030
child: Object.freeze({ pitchOffset: 0.4, rateMultiplier: 1.15 }),
31-
elder: Object.freeze({ pitchOffset: -0.2, rateMultiplier: 0.85 }),
31+
elderly: Object.freeze({ pitchOffset: -0.2, rateMultiplier: 0.85 }),
3232
teen: Object.freeze({ pitchOffset: 0.2, rateMultiplier: 1.08 })
3333
});
3434

tests/playwright/tools/WorkspaceManagerV2.spec.mjs

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ const MOCK_TEXT2SPEACH_EN_US_DETAILS = "4 voices match Any / en-US: Google US En
9191

9292
const MOCK_TEXT2SPEACH_GENDER_FILTER_VALUES = ["any", "male-preferred", "female-preferred"];
9393

94-
const MOCK_TEXT2SPEACH_AGE_FILTER_VALUES = ["any", "adult", "child", "elder", "teen"];
94+
const MOCK_TEXT2SPEACH_AGE_FILTER_VALUES = ["any", "adult", "child", "elderly", "teen"];
9595

9696
const MOCK_TEXT2SPEACH_FEMALE_LANGUAGE_VALUES = [
9797
"de-DE",
@@ -952,8 +952,8 @@ test.describe("Workspace Manager V2 bootstrap", () => {
952952
try {
953953
await expect(page.locator("body[data-tool-id='text2speach-V2']")).toBeVisible();
954954
await expect(page.locator("h1")).toHaveText("text2speach-V2");
955-
await expect(page.locator(".text2speach-V2__header .tools-platform-frame__eyebrow")).toHaveText("Speech synthesis controls");
956-
await expect(page.locator(".text2speach-V2__header .tools-platform-frame__meta")).toHaveText("Configure browser voices, language filters, character presets, and speech controls.");
955+
await expect(page.locator(".text2speach-V2__header .tools-platform-frame__eyebrow")).toHaveText("Browser speech synthesis");
956+
await expect(page.locator(".text2speach-V2__header .tools-platform-frame__meta")).toHaveText("Configure voices, helper filters, presets, queue behavior, and runtime playback.");
957957
await expect(page.locator(".text2speach-V2__header .tools-platform-frame__description")).toHaveCount(0);
958958
await expect(page.locator('[data-launch-mode-nav="tool"]')).toBeVisible();
959959
await expect(page.locator('[data-launch-mode-nav="workspace"]')).toBeHidden();
@@ -1012,6 +1012,15 @@ test.describe("Workspace Manager V2 bootstrap", () => {
10121012
await expect(page.locator("#text2speach-V2PitchSlider")).toHaveAttribute("max", "2");
10131013
await expect(page.locator("#text2speach-V2PitchSlider")).toHaveAttribute("step", "0.1");
10141014
await expect(page.locator("#text2speach-V2PitchSlider")).toHaveValue("1");
1015+
const pitchHelperScale = page.locator("#text2speach-V2PitchHelperScale");
1016+
await expect(page.locator("#text2speach-V2PitchSlider + #text2speach-V2PitchHelperScale")).toBeVisible();
1017+
await expect(pitchHelperScale).toHaveText("Male < Neutral > Female");
1018+
expect(await pitchHelperScale.evaluate((node) => getComputedStyle(node).textAlign)).toBe("center");
1019+
const pitchSliderBox = await page.locator("#text2speach-V2PitchSlider").boundingBox();
1020+
const pitchHelperBox = await pitchHelperScale.boundingBox();
1021+
expect(pitchSliderBox).not.toBeNull();
1022+
expect(pitchHelperBox).not.toBeNull();
1023+
expect(pitchHelperBox.y).toBeGreaterThan(pitchSliderBox.y);
10151024
await expect(page.locator("#text2speach-V2DelayBetweenRepeatsMsSlider")).toHaveAttribute("min", "0");
10161025
await expect(page.locator("#text2speach-V2DelayBetweenRepeatsMsSlider")).toHaveAttribute("max", "5000");
10171026
await expect(page.locator("#text2speach-V2DelayBetweenRepeatsMsSlider")).toHaveAttribute("step", "100");
@@ -1071,6 +1080,22 @@ test.describe("Workspace Manager V2 bootstrap", () => {
10711080
await expect(page.locator("#text2speach-V2VoiceSelect")).toHaveValue("mock-google-us-english");
10721081
await expect(page.locator("#text2speach-V2SpeakButton")).toBeEnabled();
10731082
await expect(page.locator("#text2speach-V2StatusLog")).toHaveValue(/OK Voice Age shaping applied: Any; rate=1\.1; pitch=1\.2\./);
1083+
await page.locator("#text2speach-V2AgeFilterSelect").selectOption("teen");
1084+
await expect(page.locator("#text2speach-V2RateSlider")).toHaveValue("1.2");
1085+
await expect(page.locator("#text2speach-V2PitchSlider")).toHaveValue("1.4");
1086+
await expect(page.locator("#text2speach-V2StatusLog")).toHaveValue(/OK Voice Age shaping applied: Teen; rate=1\.2; pitch=1\.4\./);
1087+
await page.locator("#text2speach-V2AgeFilterSelect").selectOption("adult");
1088+
await expect(page.locator("#text2speach-V2RateSlider")).toHaveValue("1.1");
1089+
await expect(page.locator("#text2speach-V2PitchSlider")).toHaveValue("1.2");
1090+
await expect(page.locator("#text2speach-V2StatusLog")).toHaveValue(/OK Voice Age shaping applied: Adult; rate=1\.1; pitch=1\.2\./);
1091+
await page.locator("#text2speach-V2AgeFilterSelect").selectOption("elderly");
1092+
await expect(page.locator("#text2speach-V2RateSlider")).toHaveValue("0.9");
1093+
await expect(page.locator("#text2speach-V2PitchSlider")).toHaveValue("1");
1094+
await expect(page.locator("#text2speach-V2StatusLog")).toHaveValue(/OK Voice Age shaping applied: Elderly; rate=0\.9; pitch=1\./);
1095+
await page.locator("#text2speach-V2AgeFilterSelect").selectOption("any");
1096+
await expect(page.locator("#text2speach-V2RateSlider")).toHaveValue("1.1");
1097+
await expect(page.locator("#text2speach-V2PitchSlider")).toHaveValue("1.2");
1098+
await expect(page.locator("#text2speach-V2StatusLog")).toHaveValue(/OK Voice Age shaping applied: Any; rate=1\.1; pitch=1\.2\./);
10741099
await page.locator("#text2speach-V2VoiceSelect").selectOption("mock-microsoft-zira");
10751100
await page.locator("#text2speach-V2GenderFilterSelect").selectOption("male-preferred");
10761101
expect(await page.locator("#text2speach-V2LanguageSelect option").evaluateAll((options) => options.map((option) => option.value))).toEqual(MOCK_TEXT2SPEACH_MALE_LANGUAGE_VALUES);

tools/schemas/tools/text2speach-V2.schema.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@
7474
},
7575
"voiceAge": {
7676
"type": "string",
77-
"enum": ["any", "adult", "child", "elder", "teen"]
77+
"enum": ["any", "adult", "child", "elderly", "teen"]
7878
},
7979
"volume": {
8080
"type": "number",

tools/text2speach-V2/index.html

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@
2121
<div class="tools-platform-frame__accordion-summary">
2222
<div class="tools-platform-frame__summary-copy">
2323
<h1 class="tools-platform-frame__title" data-tool-id="text2speach-V2">text2speach-V2</h1>
24-
<h2 class="tools-platform-frame__eyebrow">Speech synthesis controls</h2>
24+
<h2 class="tools-platform-frame__eyebrow">Browser speech synthesis</h2>
2525
</div>
2626
<div class="tools-platform-frame__summary-meta">
27-
<div class="tools-platform-frame__meta">Configure browser voices, language filters, character presets, and speech controls.</div>
27+
<div class="tools-platform-frame__meta">Configure voices, helper filters, presets, queue behavior, and runtime playback.</div>
2828
</div>
2929
</div>
3030
</div>
@@ -110,6 +110,7 @@ <h2 class="tools-platform-frame__eyebrow">Speech synthesis controls</h2>
110110
<label class="text2speach-V2__field" for="text2speach-V2PitchSlider">
111111
<span>Pitch <output id="text2speach-V2PitchOutput" for="text2speach-V2PitchSlider"></output></span>
112112
<input id="text2speach-V2PitchSlider" type="range">
113+
<span id="text2speach-V2PitchHelperScale" class="text2speach-V2__pitch-helper">Male &lt; Neutral &gt; Female</span>
113114
</label>
114115
<nav class="text2speach-V2__menu" aria-label="Tool actions" data-launch-mode-nav="tool">
115116
<button id="text2speach-V2SpeakButton" type="button" disabled>Speak</button>

tools/text2speach-V2/js/controls/SpeechOptionsControl.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ function ageFilterLabel(value) {
5050
if (value === "child") {
5151
return "Child";
5252
}
53-
if (value === "elder") {
54-
return "Elder";
53+
if (value === "elderly") {
54+
return "Elderly";
5555
}
5656
if (value === "teen") {
5757
return "Teen";

tools/text2speach-V2/styles/text2speach-V2.css

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,13 @@
8989
text-align: right;
9090
}
9191

92+
.text2speach-V2__pitch-helper {
93+
color: var(--text-muted, #cbd5e1);
94+
font-size: 0.78rem;
95+
font-weight: 700;
96+
text-align: center;
97+
}
98+
9299
.text2speach-V2__details {
93100
margin-top: -4px;
94101
margin-bottom: 12px;

0 commit comments

Comments
 (0)