Add widget emit speed control for emitted audio

This commit is contained in:
Jage9
2026-02-21 23:07:37 -05:00
parent a747046dfe
commit dd06d882e7
8 changed files with 54 additions and 2 deletions

View File

@@ -24,6 +24,15 @@ type EmitSpatialConfig = {
const ITEM_EMIT_BASE_GAIN = 0.3;
function resolveEmitPlaybackRate(raw: unknown): number {
const speed = Number(raw);
const clamped = Number.isFinite(speed) ? Math.max(0, Math.min(100, speed)) : 50;
if (clamped <= 50) {
return 0.5 + (clamped / 50) * 0.5;
}
return 1 + ((clamped - 50) / 50) * 1;
}
export class ItemEmitRuntime {
private readonly outputs = new Map<string, EmitOutput>();
private layerEnabled = true;
@@ -101,6 +110,7 @@ export class ItemEmitRuntime {
const effect = normalizeRadioEffect(item.params.emitEffect);
const effectValue = normalizeRadioEffectValue(item.params.emitEffectValue);
const effectRuntime = connectEffectChain(audioCtx, effectInput, gain, effect, effectValue);
element.playbackRate = resolveEmitPlaybackRate(item.params.emitSpeed);
if (this.audio.supportsStereoPanner()) {
panner = audioCtx.createStereoPanner();
gain.connect(panner).connect(audioCtx.destination);
@@ -138,6 +148,10 @@ export class ItemEmitRuntime {
output.effect = effect;
output.effectValue = effectValue;
}
const nextPlaybackRate = resolveEmitPlaybackRate(item.params.emitSpeed);
if (Math.abs(output.element.playbackRate - nextPlaybackRate) > 0.001) {
output.element.playbackRate = nextPlaybackRate;
}
const spatialConfig = this.getSpatialConfig(item);
const mix = resolveSpatialMix({
dx: item.x - playerPosition.x,

View File

@@ -52,7 +52,7 @@ const DEFAULT_ITEM_TYPE_EDITABLE_PROPERTIES: Record<ItemType, string[]> = {
dice: ['title', 'sides', 'number'],
wheel: ['title', 'spaces'],
clock: ['title', 'timeZone', 'use24Hour'],
widget: ['title', 'enabled', 'directional', 'facing', 'emitRange', 'emitVolume', 'emitEffect', 'emitEffectValue', 'useSound', 'emitSound'],
widget: ['title', 'enabled', 'directional', 'facing', 'emitRange', 'emitVolume', 'emitSpeed', 'emitEffect', 'emitEffectValue', 'useSound', 'emitSound'],
};
const DEFAULT_ITEM_TYPE_GLOBAL_PROPERTIES: Record<ItemType, Record<string, string | number | boolean>> = {
@@ -196,6 +196,7 @@ export function itemPropertyLabel(key: string): string {
if (key === 'emitRange') return 'emit range';
if (key === 'mediaVolume') return 'media volume';
if (key === 'emitVolume') return 'emit volume';
if (key === 'emitSpeed') return 'emit speed';
if (key === 'mediaChannel') return 'media channel';
if (key === 'mediaEffect') return 'media effect';
if (key === 'mediaEffectValue') return 'media effect value';

View File

@@ -753,6 +753,7 @@ function inferItemPropertyValueType(item: WorldItem, key: string): string | unde
key === 'version' ||
key === 'mediaVolume' ||
key === 'emitVolume' ||
key === 'emitSpeed' ||
key === 'mediaEffectValue' ||
key === 'emitEffectValue' ||
key === 'facing' ||
@@ -2158,6 +2159,14 @@ function handleItemPropertyEditModeInput(code: string, key: string, ctrlKey: boo
return;
}
signaling.send({ type: 'item_update', itemId, params: { emitVolume: parsed.value } });
} else if (propertyKey === 'emitSpeed') {
const parsed = validateNumericItemPropertyInput(item, propertyKey, value, true);
if (!parsed.ok) {
updateStatus(parsed.message);
audio.sfxUiCancel();
return;
}
signaling.send({ type: 'item_update', itemId, params: { emitSpeed: parsed.value } });
} else if (propertyKey === 'mediaEffect' || propertyKey === 'emitEffect') {
const normalized = value.trim().toLowerCase() as EffectId;
if (!EFFECT_IDS.has(normalized)) {