Fix audio controls

This commit is contained in:
2025-12-10 23:00:18 +01:00
parent 06c597f15c
commit d68538d285
2 changed files with 93 additions and 33 deletions

View File

@@ -46,6 +46,8 @@ const audioDelayInput = document.getElementById('asset-audio-delay');
const audioSpeedInput = document.getElementById('asset-audio-speed'); const audioSpeedInput = document.getElementById('asset-audio-speed');
const audioSpeedLabel = document.getElementById('asset-audio-speed-label'); const audioSpeedLabel = document.getElementById('asset-audio-speed-label');
const audioPitchInput = document.getElementById('asset-audio-pitch'); const audioPitchInput = document.getElementById('asset-audio-pitch');
const audioDelayLabel = document.getElementById('asset-audio-delay-label');
const audioPitchLabel = document.getElementById('asset-audio-pitch-label');
const controlsPlaceholder = document.getElementById('asset-controls-placeholder'); const controlsPlaceholder = document.getElementById('asset-controls-placeholder');
const fileNameLabel = document.getElementById('asset-file-name'); const fileNameLabel = document.getElementById('asset-file-name');
const assetInspector = document.getElementById('asset-inspector'); const assetInspector = document.getElementById('asset-inspector');
@@ -56,6 +58,8 @@ const selectedAssetIdLabel = document.getElementById('selected-asset-id');
const selectedAssetBadges = document.getElementById('selected-asset-badges'); const selectedAssetBadges = document.getElementById('selected-asset-badges');
const selectedVisibilityBtn = document.getElementById('selected-asset-visibility'); const selectedVisibilityBtn = document.getElementById('selected-asset-visibility');
const selectedDeleteBtn = document.getElementById('selected-asset-delete'); const selectedDeleteBtn = document.getElementById('selected-asset-delete');
const assetActionRow = document.getElementById('asset-actions');
const assetActionButtons = Array.from(assetActionRow?.querySelectorAll('button') ?? []);
const canvasResolutionLabel = document.getElementById('canvas-resolution'); const canvasResolutionLabel = document.getElementById('canvas-resolution');
const canvasScaleLabel = document.getElementById('canvas-scale'); const canvasScaleLabel = document.getElementById('canvas-scale');
const aspectLockState = new Map(); const aspectLockState = new Map();
@@ -174,6 +178,27 @@ function setAudioSpeedLabel(percentValue) {
audioSpeedLabel.textContent = `${formatted}x`; audioSpeedLabel.textContent = `${formatted}x`;
} }
function formatDelayLabel(ms) {
const numeric = Math.max(0, parseInt(ms, 10) || 0);
if (numeric >= 1000) {
const seconds = numeric / 1000;
const decimals = Number.isInteger(seconds) ? 0 : 1;
return `${seconds.toFixed(decimals)}s`;
}
return `${numeric}ms`;
}
function setAudioDelayLabel(value) {
if (!audioDelayLabel) return;
audioDelayLabel.textContent = formatDelayLabel(value);
}
function setAudioPitchLabel(percentValue) {
if (!audioPitchLabel) return;
const numeric = Math.round(Math.max(0, percentValue));
audioPitchLabel.textContent = `${numeric}%`;
}
function clamp(value, min, max) { function clamp(value, min, max) {
return Math.min(max, Math.max(min, value)); return Math.min(max, Math.max(min, value));
} }
@@ -226,9 +251,18 @@ if (heightInput) heightInput.addEventListener('change', () => commitSizeChange()
if (speedInput) speedInput.addEventListener('input', updatePlaybackFromInputs); if (speedInput) speedInput.addEventListener('input', updatePlaybackFromInputs);
if (volumeInput) volumeInput.addEventListener('input', updateVolumeFromInput); if (volumeInput) volumeInput.addEventListener('input', updateVolumeFromInput);
if (audioLoopInput) audioLoopInput.addEventListener('change', updateAudioSettingsFromInputs); if (audioLoopInput) audioLoopInput.addEventListener('change', updateAudioSettingsFromInputs);
if (audioDelayInput) audioDelayInput.addEventListener('change', updateAudioSettingsFromInputs); if (audioDelayInput) audioDelayInput.addEventListener('input', () => {
if (audioSpeedInput) audioSpeedInput.addEventListener('change', updateAudioSettingsFromInputs); setAudioDelayLabel(audioDelayInput.value);
if (audioPitchInput) audioPitchInput.addEventListener('change', updateAudioSettingsFromInputs); updateAudioSettingsFromInputs();
});
if (audioSpeedInput) audioSpeedInput.addEventListener('input', () => {
setAudioSpeedLabel(audioSpeedInput.value);
updateAudioSettingsFromInputs();
});
if (audioPitchInput) audioPitchInput.addEventListener('input', () => {
setAudioPitchLabel(audioPitchInput.value);
updateAudioSettingsFromInputs();
});
if (selectedVisibilityBtn) { if (selectedVisibilityBtn) {
selectedVisibilityBtn.addEventListener('click', () => { selectedVisibilityBtn.addEventListener('click', () => {
const asset = getSelectedAsset(); const asset = getSelectedAsset();
@@ -1411,8 +1445,8 @@ function updateSelectedAssetControls(asset = getSelectedAsset()) {
aspectLockInput.checked = isAspectLocked(asset.id); aspectLockInput.checked = isAspectLocked(asset.id);
aspectLockInput.onchange = () => setAspectLock(asset.id, aspectLockInput.checked); aspectLockInput.onchange = () => setAspectLock(asset.id, aspectLockInput.checked);
} }
const hideLayout = isAudioAsset(asset);
if (layoutSection) { if (layoutSection) {
const hideLayout = isAudioAsset(asset);
layoutSection.classList.toggle('hidden', hideLayout); layoutSection.classList.toggle('hidden', hideLayout);
const layoutControls = layoutSection.querySelectorAll('input, button'); const layoutControls = layoutSection.querySelectorAll('input, button');
layoutControls.forEach((control) => { layoutControls.forEach((control) => {
@@ -1420,6 +1454,14 @@ function updateSelectedAssetControls(asset = getSelectedAsset()) {
control.classList.toggle('disabled', hideLayout); control.classList.toggle('disabled', hideLayout);
}); });
} }
if (assetActionButtons.length) {
assetActionButtons.forEach((button) => {
const allowForAudio = button.dataset.audioEnabled === 'true';
const disableButton = hideLayout && !allowForAudio;
button.disabled = disableButton;
button.classList.toggle('disabled', disableButton);
});
}
if (speedInput) { if (speedInput) {
const percent = Math.round((asset.speed ?? 1) * 100); const percent = Math.round((asset.speed ?? 1) * 100);
speedInput.value = Math.min(1000, Math.max(0, percent)); speedInput.value = Math.min(1000, Math.max(0, percent));
@@ -1455,10 +1497,15 @@ function updateSelectedAssetControls(asset = getSelectedAsset()) {
}); });
if (showAudio) { if (showAudio) {
audioLoopInput.checked = !!asset.audioLoop; audioLoopInput.checked = !!asset.audioLoop;
audioDelayInput.value = Math.max(0, asset.audioDelayMillis ?? 0); const delayMs = clamp(Math.max(0, asset.audioDelayMillis ?? 0), 0, 30000);
audioSpeedInput.value = Math.round(Math.max(0.25, asset.audioSpeed ?? 1) * 100); audioDelayInput.value = delayMs;
setAudioSpeedLabel(audioSpeedInput.value); setAudioDelayLabel(delayMs);
audioPitchInput.value = Math.round(Math.max(0.5, asset.audioPitch ?? 1) * 100); const audioSpeedPercent = clamp(Math.round(Math.max(0.25, asset.audioSpeed ?? 1) * 100), 25, 400);
audioSpeedInput.value = audioSpeedPercent;
setAudioSpeedLabel(audioSpeedPercent);
const pitchPercent = clamp(Math.round(Math.max(0.5, asset.audioPitch ?? 1) * 100), 50, 200);
audioPitchInput.value = pitchPercent;
setAudioPitchLabel(pitchPercent);
} }
} }
} }
@@ -1587,11 +1634,18 @@ function updateAudioSettingsFromInputs() {
const asset = getSelectedAsset(); const asset = getSelectedAsset();
if (!asset || !isAudioAsset(asset)) return; if (!asset || !isAudioAsset(asset)) return;
asset.audioLoop = !!audioLoopInput?.checked; asset.audioLoop = !!audioLoopInput?.checked;
asset.audioDelayMillis = Math.max(0, parseInt(audioDelayInput?.value || '0', 10)); const delayMs = clamp(Math.max(0, parseInt(audioDelayInput?.value || '0', 10)), 0, 30000);
const nextAudioSpeedPercent = Math.max(25, parseInt(audioSpeedInput?.value || '100', 10)); asset.audioDelayMillis = delayMs;
setAudioDelayLabel(delayMs);
if (audioDelayInput) audioDelayInput.value = delayMs;
const nextAudioSpeedPercent = clamp(Math.max(25, parseInt(audioSpeedInput?.value || '100', 10)), 25, 400);
setAudioSpeedLabel(nextAudioSpeedPercent); setAudioSpeedLabel(nextAudioSpeedPercent);
if (audioSpeedInput) audioSpeedInput.value = nextAudioSpeedPercent;
asset.audioSpeed = Math.max(0.25, (nextAudioSpeedPercent / 100)); asset.audioSpeed = Math.max(0.25, (nextAudioSpeedPercent / 100));
asset.audioPitch = Math.max(0.5, (parseInt(audioPitchInput?.value || '100', 10) / 100)); const nextAudioPitchPercent = clamp(Math.max(50, parseInt(audioPitchInput?.value || '100', 10)), 50, 200);
setAudioPitchLabel(nextAudioPitchPercent);
if (audioPitchInput) audioPitchInput.value = nextAudioPitchPercent;
asset.audioPitch = Math.max(0.5, (nextAudioPitchPercent / 100));
const controller = ensureAudioController(asset); const controller = ensureAudioController(asset);
applyAudioSettings(controller, asset); applyAudioSettings(controller, asset);
persistTransform(asset); persistTransform(asset);

View File

@@ -92,21 +92,6 @@
</div> </div>
</div> </div>
</div> </div>
<div class="control-actions compact unified-actions">
<button type="button" onclick="sendToBack()" class="secondary" title="Send to back"><i class="fa-solid fa-angles-down"></i></button>
<button type="button" onclick="bringBackward()" class="secondary" title="Move backward"><i class="fa-solid fa-arrow-down"></i></button>
<button type="button" onclick="bringForward()" class="secondary" title="Move forward"><i class="fa-solid fa-arrow-up"></i></button>
<button type="button" onclick="bringToFront()" class="secondary" title="Bring to front"><i class="fa-solid fa-angles-up"></i></button>
<button type="button" onclick="recenterSelectedAsset()" class="secondary" title="Center on canvas"><i class="fa-solid fa-bullseye"></i></button>
<button type="button" onclick="nudgeRotation(-5)" class="secondary" title="Rotate left"><i class="fa-solid fa-rotate-left"></i></button>
<button type="button" onclick="nudgeRotation(5)" class="secondary" title="Rotate right"><i class="fa-solid fa-rotate-right"></i></button>
<button id="selected-asset-visibility" class="secondary" type="button" title="Hide asset" disabled>
<i class="fa-solid fa-eye-slash"></i>
</button>
<button id="selected-asset-delete" class="secondary danger" type="button" title="Delete asset" disabled>
<i class="fa-solid fa-trash"></i>
</button>
</div>
</div> </div>
<div class="panel-section" id="playback-section"> <div class="panel-section" id="playback-section">
@@ -151,10 +136,14 @@
</span> </span>
</label> </label>
</div> </div>
<div class="property-row"> </div>
<span class="property-label">Delay (ms)</span> <div class="stacked-field">
<input id="asset-audio-delay" class="number-input property-control" type="number" min="0" step="100"/> <div class="label-row">
<span>Delay</span>
<span class="value-hint" id="asset-audio-delay-label">0ms</span>
</div> </div>
<input id="asset-audio-delay" class="range-input property-control" type="range" min="0" max="30000" step="100" value="0" />
<div class="range-meta"><span>0ms</span><span>30s</span></div>
</div> </div>
<div class="stacked-field"> <div class="stacked-field">
<div class="label-row"> <div class="label-row">
@@ -164,13 +153,30 @@
<input id="asset-audio-speed" class="range-input" type="range" min="25" max="400" step="5" value="100" /> <input id="asset-audio-speed" class="range-input" type="range" min="25" max="400" step="5" value="100" />
<div class="range-meta"><span>0.25x</span><span>4x</span></div> <div class="range-meta"><span>0.25x</span><span>4x</span></div>
</div> </div>
<div class="property-list"> <div class="stacked-field">
<div class="property-row"> <div class="label-row">
<span class="property-label">Pitch (%)</span> <span>Pitch</span>
<input id="asset-audio-pitch" class="range-input property-control" type="range" min="50" max="200" step="5" value="100" /> <span class="value-hint" id="asset-audio-pitch-label">100%</span>
</div> </div>
<input id="asset-audio-pitch" class="range-input property-control" type="range" min="50" max="200" step="5" value="100" />
<div class="range-meta"><span>50%</span><span>200%</span></div>
</div> </div>
</div> </div>
<div class="control-actions compact unified-actions" id="asset-actions">
<button type="button" onclick="sendToBack()" class="secondary" title="Send to back"><i class="fa-solid fa-angles-down"></i></button>
<button type="button" onclick="bringBackward()" class="secondary" title="Move backward"><i class="fa-solid fa-arrow-down"></i></button>
<button type="button" onclick="bringForward()" class="secondary" title="Move forward"><i class="fa-solid fa-arrow-up"></i></button>
<button type="button" onclick="bringToFront()" class="secondary" title="Bring to front"><i class="fa-solid fa-angles-up"></i></button>
<button type="button" onclick="recenterSelectedAsset()" class="secondary" title="Center on canvas"><i class="fa-solid fa-bullseye"></i></button>
<button type="button" onclick="nudgeRotation(-5)" class="secondary" title="Rotate left"><i class="fa-solid fa-rotate-left"></i></button>
<button type="button" onclick="nudgeRotation(5)" class="secondary" title="Rotate right"><i class="fa-solid fa-rotate-right"></i></button>
<button id="selected-asset-visibility" class="secondary" type="button" title="Hide asset" disabled data-audio-enabled="true">
<i class="fa-solid fa-eye-slash"></i>
</button>
<button id="selected-asset-delete" class="secondary danger" type="button" title="Delete asset" disabled data-audio-enabled="true">
<i class="fa-solid fa-trash"></i>
</button>
</div>
</div> </div>
</div> </div>
</div> </div>