Skip to content

Commit f938fe1

Browse files
committed
Reorder Audio SFX sound styles and add non selectable divider - PR_26145_005-audio-sfx-style-order-and-divider
1 parent 3a365a4 commit f938fe1

4 files changed

Lines changed: 236 additions & 0 deletions

File tree

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# Audio / SFX Playground V2 Style Order Validation
2+
3+
PR: `PR_26145_005-audio-sfx-style-order-and-divider`
4+
5+
## Scope
6+
7+
- Updated `tools/audio-sfx-playground-v2` Sound Style ordering.
8+
- Added a disabled visual divider entry after `Pure Tone`.
9+
- Added style profile application without persisting the selected style name into exported toolState JSON.
10+
- Left schema files, `start_of_day`, and unrelated tools untouched.
11+
12+
## Targeted Static Validation
13+
14+
- `node --check` over `tools/audio-sfx-playground-v2/js/**/*.js`: PASS
15+
- HTML/CSS static guard for changed tool files:
16+
- no inline event handlers: PASS
17+
- no `<style>` blocks: PASS
18+
- no inline `<script>` blocks: PASS
19+
- no empty changed HTML/CSS/JS files: PASS
20+
- `git diff --check -- tools/audio-sfx-playground-v2`: PASS
21+
- Git reported LF-to-CRLF working-copy warnings only.
22+
23+
## Targeted Style Validation
24+
25+
Validated with an ESM Node harness against the changed tool files:
26+
27+
- Sound Style select exists: PASS
28+
- Order is:
29+
- `Custom`
30+
- `Pure Tone`
31+
- disabled divider
32+
- `Atari-style`
33+
- `Classic Arcade`
34+
- `Early Analog`
35+
- `Namco-style`
36+
- `Nintendo-style`
37+
- `TTL Arcade`
38+
- `Vector Arcade`
39+
- Divider value is disabled and visual-only: PASS
40+
- Selecting `Classic Arcade` updates editor defaults/ranges behavior through the profile handler: PASS
41+
- Programmatic divider selection is rejected and resets to `Custom`: PASS
42+
- Validated sound payload does not include `style`, `styleProfile`, or divider data: PASS
43+
44+
## Playwright Impacted Validation
45+
46+
Ran a focused Playwright check against `tools/audio-sfx-playground-v2/index.html` through a temporary local HTTP server using local browser binaries from `.ms-playwright`.
47+
48+
- Tool launches successfully: PASS
49+
- No console errors on launch or style selection: PASS
50+
- Sound Style order matches the requested order: PASS
51+
- Divider is visible but disabled/not selectable by user interaction: PASS
52+
- Style selection updates defaults:
53+
- `Classic Arcade` set name to `Classic Zap`: PASS
54+
- `Classic Arcade` set waveform to `square`: PASS
55+
- Copy JSON after style selection omits style metadata and persists only sound payload data: PASS
56+
- Programmatic divider selection does not persist and resets to `Custom`: PASS
57+
58+
## Workspace V2
59+
60+
Command:
61+
62+
```powershell
63+
$env:PLAYWRIGHT_BROWSERS_PATH='.ms-playwright'; npm.cmd run test:workspace-v2
64+
```
65+
66+
Result: TIMEOUT after 900 seconds.
67+
68+
Observed progress before timeout:
69+
70+
- 72 tests discovered.
71+
- Tests 1-70 began running.
72+
- 67 observed as passed.
73+
- 3 observed as failed before timeout:
74+
- Input Mapping V2 mouse drag visual capture state test.
75+
- Object Vector Studio V2 layout shell/schema-only palette gate test.
76+
- Object Vector Studio V2 compact geometry layout/selected palette state test.
77+
- The timeout occurred before the final two tests reported.
78+
79+
The observed failures are outside the Audio / SFX Playground V2 scope.
80+
81+
## Notes
82+
83+
- Full samples smoke test skipped per request.
84+
- V8 coverage was not used for this focused validation.

tools/audio-sfx-playground-v2/index.html

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,21 @@ <h2 class="tools-platform-frame__eyebrow">First-Class Tools Surface V2</h2>
6767
<button id="addSfxButton" type="button" aria-label="Add SFX">+</button>
6868
<button id="deleteSfxButton" type="button" aria-label="Delete selected SFX">&#128465;</button>
6969
</div>
70+
<label class="tool-starter__field" for="styleProfileSelect">
71+
<span class="audio-sfx__label-tip" tabindex="0" data-tooltip="Applies a profile of default SFX ranges without saving the style name into toolState.">Sound Style</span>
72+
<select id="styleProfileSelect">
73+
<option value="custom">Custom</option>
74+
<option value="pure-tone">Pure Tone</option>
75+
<option value="divider" disabled>────────────</option>
76+
<option value="atari-style">Atari-style</option>
77+
<option value="classic-arcade">Classic Arcade</option>
78+
<option value="early-analog">Early Analog</option>
79+
<option value="namco-style">Namco-style</option>
80+
<option value="nintendo-style">Nintendo-style</option>
81+
<option value="ttl-arcade">TTL Arcade</option>
82+
<option value="vector-arcade">Vector Arcade</option>
83+
</select>
84+
</label>
7085
<label class="tool-starter__field" for="waveformSelect">
7186
<span class="audio-sfx__label-tip" tabindex="0" data-tooltip="Chooses the oscillator shape that defines the sound character.">Wave</span>
7287
<select id="waveformSelect">

tools/audio-sfx-playground-v2/js/bootstrap.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ window.addEventListener("DOMContentLoaded", () => {
6161
pitchSweepValue: requireElement("#pitchSweepValue"),
6262
releaseInput: requireElement("#releaseInput"),
6363
releaseValue: requireElement("#releaseValue"),
64+
styleProfileSelect: requireElement("#styleProfileSelect"),
6465
validationMessage: requireElement("#sfxValidationMessage"),
6566
volumeInput: requireElement("#volumeInput"),
6667
volumeValue: requireElement("#volumeValue"),

tools/audio-sfx-playground-v2/js/controls/SfxControlPanel.js

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,112 @@ const DEFAULT_SOUND = Object.freeze({
1414
});
1515

1616
const ALLOWED_WAVEFORMS = Object.freeze(new Set(["sine", "square", "triangle", "sawtooth"]));
17+
const STYLE_PROFILES = Object.freeze({
18+
"pure-tone": {
19+
durationMs: 240,
20+
frequencyHz: 660,
21+
name: "Pure Tone",
22+
noise: false,
23+
noiseAmount: 0,
24+
noiseDecayMs: 80,
25+
noiseFilterHz: 5200,
26+
pitchSweepCents: 0,
27+
releaseMs: 120,
28+
volume: 0.38,
29+
waveform: "sine"
30+
},
31+
"atari-style": {
32+
durationMs: 180,
33+
frequencyHz: 520,
34+
name: "Atari Blip",
35+
noise: true,
36+
noiseAmount: 0.48,
37+
noiseDecayMs: 75,
38+
noiseFilterHz: 4200,
39+
pitchSweepCents: -520,
40+
releaseMs: 60,
41+
volume: 0.5,
42+
waveform: "square"
43+
},
44+
"classic-arcade": {
45+
durationMs: 220,
46+
frequencyHz: 880,
47+
name: "Classic Zap",
48+
noise: true,
49+
noiseAmount: 0.65,
50+
noiseDecayMs: 95,
51+
noiseFilterHz: 5600,
52+
pitchSweepCents: 700,
53+
releaseMs: 85,
54+
volume: 0.46,
55+
waveform: "square"
56+
},
57+
"early-analog": {
58+
durationMs: 360,
59+
frequencyHz: 340,
60+
name: "Analog Bloom",
61+
noise: false,
62+
noiseAmount: 0.22,
63+
noiseDecayMs: 140,
64+
noiseFilterHz: 3600,
65+
pitchSweepCents: 240,
66+
releaseMs: 180,
67+
volume: 0.42,
68+
waveform: "sawtooth"
69+
},
70+
"namco-style": {
71+
durationMs: 170,
72+
frequencyHz: 1040,
73+
name: "Namco Ping",
74+
noise: false,
75+
noiseAmount: 0.15,
76+
noiseDecayMs: 70,
77+
noiseFilterHz: 6800,
78+
pitchSweepCents: 420,
79+
releaseMs: 70,
80+
volume: 0.44,
81+
waveform: "triangle"
82+
},
83+
"nintendo-style": {
84+
durationMs: 260,
85+
frequencyHz: 760,
86+
name: "Nintendo Pop",
87+
noise: true,
88+
noiseAmount: 0.36,
89+
noiseDecayMs: 90,
90+
noiseFilterHz: 7600,
91+
pitchSweepCents: 520,
92+
releaseMs: 95,
93+
volume: 0.4,
94+
waveform: "square"
95+
},
96+
"ttl-arcade": {
97+
durationMs: 130,
98+
frequencyHz: 1180,
99+
name: "TTL Tick",
100+
noise: true,
101+
noiseAmount: 0.72,
102+
noiseDecayMs: 50,
103+
noiseFilterHz: 8300,
104+
pitchSweepCents: -260,
105+
releaseMs: 45,
106+
volume: 0.48,
107+
waveform: "square"
108+
},
109+
"vector-arcade": {
110+
durationMs: 300,
111+
frequencyHz: 420,
112+
name: "Vector Sweep",
113+
noise: false,
114+
noiseAmount: 0.12,
115+
noiseDecayMs: 120,
116+
noiseFilterHz: 4600,
117+
pitchSweepCents: 900,
118+
releaseMs: 130,
119+
volume: 0.43,
120+
waveform: "sawtooth"
121+
}
122+
});
17123

18124
function toNumber(input) {
19125
return Number.parseFloat(input.value);
@@ -51,6 +157,7 @@ export class SfxControlPanel {
51157
pitchSweepValue,
52158
releaseInput,
53159
releaseValue,
160+
styleProfileSelect,
54161
validationMessage,
55162
volumeInput,
56163
volumeValue,
@@ -76,6 +183,7 @@ export class SfxControlPanel {
76183
this.pitchSweepValue = pitchSweepValue;
77184
this.releaseInput = releaseInput;
78185
this.releaseValue = releaseValue;
186+
this.styleProfileSelect = styleProfileSelect;
79187
this.validationMessage = validationMessage;
80188
this.volumeInput = volumeInput;
81189
this.volumeValue = volumeValue;
@@ -86,6 +194,11 @@ export class SfxControlPanel {
86194
this.loadSound(DEFAULT_SOUND);
87195
this.addButton.addEventListener("click", onAdd);
88196
this.deleteButton.addEventListener("click", onDelete);
197+
this.styleProfileSelect.addEventListener("change", () => {
198+
if (this.applyStyleProfile()) {
199+
onChange();
200+
}
201+
});
89202
this.setDeleteEnabled(false);
90203
[
91204
this.attackInput,
@@ -113,6 +226,7 @@ export class SfxControlPanel {
113226
}
114227

115228
loadSound(sound) {
229+
this.styleProfileSelect.value = "custom";
116230
this.attackInput.value = String(sound.attackMs);
117231
this.durationInput.value = String(sound.durationMs);
118232
this.frequencyInput.value = String(sound.frequencyHz);
@@ -128,6 +242,28 @@ export class SfxControlPanel {
128242
this.syncOutputs();
129243
}
130244

245+
applyStyleProfile() {
246+
const profile = STYLE_PROFILES[this.styleProfileSelect.value];
247+
if (!profile) {
248+
this.styleProfileSelect.value = "custom";
249+
return false;
250+
}
251+
this.attackInput.value = String(DEFAULT_SOUND.attackMs);
252+
this.durationInput.value = String(profile.durationMs);
253+
this.frequencyInput.value = String(profile.frequencyHz);
254+
this.nameInput.value = profile.name;
255+
this.noiseInput.checked = profile.noise;
256+
this.noiseAmountInput.value = String(profile.noiseAmount);
257+
this.noiseDecayInput.value = String(profile.noiseDecayMs);
258+
this.noiseFilterInput.value = String(profile.noiseFilterHz);
259+
this.pitchSweepInput.value = String(profile.pitchSweepCents);
260+
this.releaseInput.value = String(profile.releaseMs);
261+
this.volumeInput.value = String(profile.volume);
262+
this.waveformSelect.value = profile.waveform;
263+
this.syncOutputs();
264+
return true;
265+
}
266+
131267
syncOutputs() {
132268
this.attackValue.textContent = `${Math.round(toNumber(this.attackInput))} ms`;
133269
this.durationValue.textContent = `${Math.round(toNumber(this.durationInput))} ms`;

0 commit comments

Comments
 (0)