Skip to content

Commit f3f2de4

Browse files
committed
Add multi-sound workflow, tooltips, and compact preview layout to Audio SFX Playground V2 - PR_26144_005-audio-sfx-multi-sound-workflow-and-tooltips
1 parent 1a48142 commit f3f2de4

8 files changed

Lines changed: 437 additions & 121 deletions

File tree

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# Audio / SFX Playground V2 Multi-Sound Workflow Validation
2+
3+
PR: `PR_26144_005-audio-sfx-multi-sound-workflow-and-tooltips`
4+
5+
## Scope
6+
7+
- Updated only `tools/audio-sfx-playground-v2` plus required reports.
8+
- Replaced the preset selector with a Name field and Add button.
9+
- Added hover/focus tooltips to the audio parameter labels.
10+
- Added a created-sounds tile list.
11+
- Selecting a sound tile loads that saved SFX back into the editor.
12+
- Moved Play to the bottom of the SFX Shape section.
13+
- Moved Wave Preview to the top of the right column and reduced preview sizing.
14+
- Preserved external CSS/JS only; no inline handlers, script blocks, or style blocks.
15+
- Did not modify `start_of_day` folders.
16+
17+
## Static Validation
18+
19+
PASS:
20+
21+
- HTML/CSS restriction check:
22+
- no `<style>` blocks
23+
- no inline event handlers
24+
- no script tags without `src`
25+
- linked stylesheets resolve
26+
- CSS braces are balanced
27+
- preset UI references are removed from HTML
28+
- Audio / SFX Playground V2 JavaScript syntax check:
29+
- `node --check` over `tools/audio-sfx-playground-v2/js/**/*.js`
30+
- Changed runtime module import check:
31+
- `AudioSfxPlaygroundV2App.js`
32+
- `SfxControlPanel.js`
33+
- `SfxTileListControl.js`
34+
- `ToolStateSerializer.js`
35+
- Whitespace validation:
36+
- `git diff --check -- tools/audio-sfx-playground-v2`
37+
- JSON validation:
38+
- No JSON files changed in this PR.
39+
40+
## Playwright Impact
41+
42+
Playwright impacted: Yes.
43+
44+
Expected validation:
45+
46+
- Workspace V2 shows the Audio / SFX Playground V2 tile.
47+
- The tile launches Audio / SFX Playground V2 with no console errors.
48+
- Hovering audio parameter labels shows tooltips.
49+
- The Add button creates a new SFX tile from the current editor values.
50+
- Selecting a tile restores that saved editor state.
51+
- Preview renders at the top of the right column with the reduced layout.
52+
- Play appears at the bottom of the SFX Shape section and remains the explicit audio action.
53+
54+
Local command results:
55+
56+
- `npm run test:workspace-v2`
57+
- FAIL: PowerShell blocked `npm.ps1` because script execution is disabled on this system.
58+
- `npm.cmd run test:workspace-v2`
59+
- FAIL: package script started, but `playwright` is not installed or not available on PATH.
60+
61+
Because Playwright is unavailable in this local environment, browser launch validation and V8 coverage could not be completed here.
62+
63+
## Coverage
64+
65+
WARN: Runtime JavaScript changed, but Playwright V8 coverage could not be generated because the local Playwright command is unavailable.
66+
67+
- `(WARN) tools/audio-sfx-playground-v2/js/AudioSfxPlaygroundV2App.js - Playwright unavailable`
68+
- `(WARN) tools/audio-sfx-playground-v2/js/bootstrap.js - Playwright unavailable`
69+
- `(WARN) tools/audio-sfx-playground-v2/js/controls/SfxControlPanel.js - Playwright unavailable`
70+
- `(WARN) tools/audio-sfx-playground-v2/js/controls/SfxTileListControl.js - Playwright unavailable`
71+
- `(WARN) tools/audio-sfx-playground-v2/js/services/ToolStateSerializer.js - Playwright unavailable`
72+
73+
## Full Samples Smoke Test
74+
75+
Skipped. This PR only impacts Audio / SFX Playground V2 UI workflow.

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

Lines changed: 32 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ <h2 class="tools-platform-frame__eyebrow">First-Class Tools Surface V2</h2>
3838
</details>
3939

4040
<nav class="tool-starter__menu tool-starter__tool__menu audio-sfx__tool__menu" aria-label="Tool actions" data-launch-mode-nav="tool">
41-
<button id="toolPlayButton" type="button">Play</button>
4241
<button id="toolCopyJsonButton" type="button">Copy JSON</button>
4342
<button id="toolExportToolStateButton" type="button">Export toolState</button>
4443
</nav>
@@ -58,18 +57,15 @@ <h2 class="tools-platform-frame__eyebrow">First-Class Tools Surface V2</h2>
5857
<span class="accordion-v2__icon" aria-hidden="true">+</span>
5958
</button>
6059
<div id="sfxShapeContent" class="accordion-v2__content">
61-
<label class="tool-starter__field" for="presetSelect">
62-
<span>Preset</span>
63-
<select id="presetSelect">
64-
<option value="coin">Coin</option>
65-
<option value="laser">Laser</option>
66-
<option value="jump">Jump</option>
67-
<option value="hit">Hit</option>
68-
<option value="powerup">Power Up</option>
69-
</select>
70-
</label>
60+
<div class="tool-starter__field audio-sfx__name-field">
61+
<label for="nameInput">
62+
<span class="audio-sfx__label-tip" tabindex="0" data-tooltip="Names this sound in the tile list and exported toolState.">Name</span>
63+
</label>
64+
<input id="nameInput" type="text" maxlength="48" autocomplete="off">
65+
<button id="addSfxButton" type="button">Add</button>
66+
</div>
7167
<label class="tool-starter__field" for="waveformSelect">
72-
<span>Wave</span>
68+
<span class="audio-sfx__label-tip" tabindex="0" data-tooltip="Chooses the oscillator shape that defines the sound character.">Wave</span>
7369
<select id="waveformSelect">
7470
<option value="sine">Sine</option>
7571
<option value="square">Square</option>
@@ -78,59 +74,72 @@ <h2 class="tools-platform-frame__eyebrow">First-Class Tools Surface V2</h2>
7874
</select>
7975
</label>
8076
<label class="tool-starter__field" for="frequencyInput" data-density-control="frequency">
81-
<span>Frequency</span>
77+
<span class="audio-sfx__label-tip" tabindex="0" data-tooltip="Sets the starting pitch before any sweep is applied.">Frequency</span>
8278
<input id="frequencyInput" type="range" min="80" max="1800" step="1">
8379
<output id="frequencyValue" for="frequencyInput"></output>
8480
</label>
8581
<label class="tool-starter__field" for="durationInput" data-density-control="duration">
86-
<span>Duration</span>
82+
<span class="audio-sfx__label-tip" tabindex="0" data-tooltip="Controls the total playback length of the effect.">Duration</span>
8783
<input id="durationInput" type="range" min="60" max="1400" step="10">
8884
<output id="durationValue" for="durationInput"></output>
8985
</label>
9086
<label class="tool-starter__field" for="attackInput" data-density-control="attack">
91-
<span>Attack</span>
87+
<span class="audio-sfx__label-tip" tabindex="0" data-tooltip="Controls how quickly the sound reaches full volume.">Attack</span>
9288
<input id="attackInput" type="range" min="0" max="250" step="5">
9389
<output id="attackValue" for="attackInput"></output>
9490
</label>
9591
<label class="tool-starter__field" for="releaseInput" data-density-control="release">
96-
<span>Release</span>
92+
<span class="audio-sfx__label-tip" tabindex="0" data-tooltip="Controls how long the sound fades out at the end.">Release</span>
9793
<input id="releaseInput" type="range" min="20" max="700" step="10">
9894
<output id="releaseValue" for="releaseInput"></output>
9995
</label>
10096
<label class="tool-starter__field" for="volumeInput" data-density-control="volume">
101-
<span>Volume</span>
97+
<span class="audio-sfx__label-tip" tabindex="0" data-tooltip="Sets the playback loudness for audition and export.">Volume</span>
10298
<input id="volumeInput" type="range" min="0" max="1" step="0.01">
10399
<output id="volumeValue" for="volumeInput"></output>
104100
</label>
105101
<label class="tool-starter__field" for="pitchSweepInput" data-density-control="sweep">
106-
<span>Sweep</span>
102+
<span class="audio-sfx__label-tip" tabindex="0" data-tooltip="Bends pitch over time in cents, positive upward and negative downward.">Sweep</span>
107103
<input id="pitchSweepInput" type="range" min="-1200" max="1200" step="25">
108104
<output id="pitchSweepValue" for="pitchSweepInput"></output>
109105
</label>
110106
<label class="tool-starter__toggle" for="noiseInput">
111107
<input id="noiseInput" type="checkbox">
112-
<span>Blend noise transient</span>
108+
<span class="audio-sfx__label-tip" tabindex="0" data-tooltip="Adds a short noise burst for punchy hits and impacts.">Blend noise transient</span>
113109
</label>
114110
<p id="sfxValidationMessage" class="tool-starter__hint">Ready to audition.</p>
111+
<div class="audio-sfx__shape-actions">
112+
<button id="toolPlayButton" type="button">Play</button>
113+
</div>
115114
</div>
116115
</section>
117116
</aside>
118117

119-
<section class="tool-starter__panel tool-starter__panel--center" aria-label="SFX preview">
118+
<section class="tool-starter__panel tool-starter__panel--center" aria-label="SFX sound list">
120119
<section class="accordion-v2 tool-starter__accordion tool-starter__accordion--fill is-open" data-accordion-v2-open="true">
120+
<button class="accordion-v2__header" type="button" aria-expanded="true" aria-controls="soundListContent">
121+
<span>Created Sounds</span>
122+
<span class="accordion-v2__icon" aria-hidden="true">+</span>
123+
</button>
124+
<div id="soundListContent" class="accordion-v2__content">
125+
<div id="sfxTileList" class="audio-sfx__tile-list" role="listbox" aria-label="Created SFX entries"></div>
126+
</div>
127+
</section>
128+
</section>
129+
130+
<aside class="tool-starter__panel tool-starter__panel--right" aria-label="SFX output">
131+
<section class="accordion-v2 tool-starter__accordion audio-sfx__preview-accordion is-open" data-accordion-v2-open="true">
121132
<button class="accordion-v2__header" type="button" aria-expanded="true" aria-controls="previewPanelContent">
122133
<span>Wave Preview</span>
123134
<span class="accordion-v2__icon" aria-hidden="true">+</span>
124135
</button>
125136
<div id="previewPanelContent" class="accordion-v2__content">
126-
<div id="previewOutput" class="tool-starter__preview" role="img" aria-label="SFX envelope and waveform preview">
137+
<div id="previewOutput" class="tool-starter__preview audio-sfx__preview" role="img" aria-label="SFX envelope and waveform preview">
127138
<p>No preview rendered yet.</p>
128139
</div>
129140
</div>
130141
</section>
131-
</section>
132142

133-
<aside class="tool-starter__panel tool-starter__panel--right" aria-label="SFX output">
134143
<section class="accordion-v2 tool-starter__accordion is-open" data-accordion-v2-open="true">
135144
<button class="accordion-v2__header" type="button" aria-expanded="true" aria-controls="inspectorContent">
136145
<span>Output Summary</span>

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

Lines changed: 78 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,32 @@
1+
function cloneSound(sound) {
2+
return {
3+
attackMs: sound.attackMs,
4+
durationMs: sound.durationMs,
5+
frequencyHz: sound.frequencyHz,
6+
name: sound.name,
7+
noise: sound.noise,
8+
pitchSweepCents: sound.pitchSweepCents,
9+
releaseMs: sound.releaseMs,
10+
volume: sound.volume,
11+
waveform: sound.waveform
12+
};
13+
}
14+
115
export class AudioSfxPlaygroundV2App {
2-
constructor({ accordions, actionNav, audioEngine, controls, inspector, preview, serializer, shell, statusLog, windowRef = window }) {
16+
constructor({ accordions, actionNav, audioEngine, controls, inspector, preview, serializer, shell, statusLog, tileList, windowRef = window }) {
317
this.accordions = accordions;
418
this.actionNav = actionNav;
19+
this.activeSoundId = "";
520
this.audioEngine = audioEngine;
621
this.controls = controls;
722
this.inspector = inspector;
23+
this.nextSoundNumber = 1;
824
this.preview = preview;
925
this.serializer = serializer;
1026
this.shell = shell;
27+
this.soundEntries = [];
1128
this.statusLog = statusLog;
29+
this.tileList = tileList;
1230
this.window = windowRef;
1331
}
1432

@@ -28,9 +46,14 @@ export class AudioSfxPlaygroundV2App {
2846
onWorkspaceImportManifest: () => this.statusLog.write("Import manifest action ready for workspace wiring.")
2947
});
3048
this.controls.mount({
31-
onChange: () => this.refreshPreview()
49+
onAdd: () => this.addCurrentSound(),
50+
onChange: () => this.handleEditorChange()
51+
});
52+
this.tileList.mount({
53+
onSelect: (soundId) => this.selectSound(soundId)
3254
});
3355
this.statusLog.mount();
56+
this.renderSoundList();
3457
this.refreshPreview();
3558
this.statusLog.write("Audio / SFX Playground V2 ready.");
3659
}
@@ -41,7 +64,11 @@ export class AudioSfxPlaygroundV2App {
4164
this.controls.showMessage(validation.message, true);
4265
return { toolState: null, validation };
4366
}
44-
const toolState = this.serializer.createToolState(validation.value);
67+
const toolState = this.serializer.createToolState({
68+
activeSoundId: this.activeSoundId,
69+
sound: validation.value,
70+
soundEntries: this.soundEntries
71+
});
4572
this.controls.showMessage("Ready to audition.", false);
4673
return { toolState, validation };
4774
}
@@ -59,6 +86,54 @@ export class AudioSfxPlaygroundV2App {
5986
this.actionNav.setToolActionsEnabled(true);
6087
}
6188

89+
handleEditorChange() {
90+
if (this.activeSoundId) {
91+
this.activeSoundId = "";
92+
this.renderSoundList();
93+
}
94+
this.refreshPreview();
95+
}
96+
97+
addCurrentSound() {
98+
const validation = this.controls.validate();
99+
if (!validation.valid) {
100+
this.statusLog.error(validation.message);
101+
this.refreshPreview();
102+
return;
103+
}
104+
105+
const entry = {
106+
id: `sfx-${this.nextSoundNumber}`,
107+
sound: cloneSound(validation.value)
108+
};
109+
this.nextSoundNumber += 1;
110+
this.soundEntries.push(entry);
111+
this.activeSoundId = entry.id;
112+
this.renderSoundList();
113+
this.refreshPreview();
114+
this.statusLog.write(`Added ${entry.sound.name}.`);
115+
}
116+
117+
selectSound(soundId) {
118+
const entry = this.soundEntries.find((candidate) => candidate.id === soundId);
119+
if (!entry) {
120+
this.statusLog.error(`Unable to select missing SFX entry: ${soundId}.`);
121+
return;
122+
}
123+
this.activeSoundId = entry.id;
124+
this.controls.loadSound(entry.sound);
125+
this.renderSoundList();
126+
this.refreshPreview();
127+
this.statusLog.write(`Loaded ${entry.sound.name}.`);
128+
}
129+
130+
renderSoundList() {
131+
this.tileList.render({
132+
activeSoundId: this.activeSoundId,
133+
soundEntries: this.soundEntries
134+
});
135+
}
136+
62137
async play() {
63138
const { toolState, validation } = this.currentToolState();
64139
if (!validation.valid) {

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { AccordionSection } from "./controls/AccordionSection.js";
44
import { InspectorControl } from "./controls/InspectorControl.js";
55
import { SfxControlPanel } from "./controls/SfxControlPanel.js";
66
import { SfxPreviewControl } from "./controls/SfxPreviewControl.js";
7+
import { SfxTileListControl } from "./controls/SfxTileListControl.js";
78
import { StatusLogControl } from "./controls/StatusLogControl.js";
89
import { ToolShellControl } from "./controls/ToolShellControl.js";
910
import { AudioSfxEngine } from "./services/AudioSfxEngine.js";
@@ -38,16 +39,17 @@ window.addEventListener("DOMContentLoaded", () => {
3839
}),
3940
audioEngine: new AudioSfxEngine({ windowRef: window }),
4041
controls: new SfxControlPanel({
42+
addButton: requireElement("#addSfxButton"),
4143
attackInput: requireElement("#attackInput"),
4244
attackValue: requireElement("#attackValue"),
4345
durationInput: requireElement("#durationInput"),
4446
durationValue: requireElement("#durationValue"),
4547
frequencyInput: requireElement("#frequencyInput"),
4648
frequencyValue: requireElement("#frequencyValue"),
49+
nameInput: requireElement("#nameInput"),
4750
noiseInput: requireElement("#noiseInput"),
4851
pitchSweepInput: requireElement("#pitchSweepInput"),
4952
pitchSweepValue: requireElement("#pitchSweepValue"),
50-
presetSelect: requireElement("#presetSelect"),
5153
releaseInput: requireElement("#releaseInput"),
5254
releaseValue: requireElement("#releaseValue"),
5355
validationMessage: requireElement("#sfxValidationMessage"),
@@ -60,6 +62,9 @@ window.addEventListener("DOMContentLoaded", () => {
6062
serializer: new ToolStateSerializer(),
6163
shell: new ToolShellControl(),
6264
statusLog,
65+
tileList: new SfxTileListControl({
66+
list: requireElement("#sfxTileList")
67+
}),
6368
windowRef: window
6469
});
6570

0 commit comments

Comments
 (0)