Files
OpenClaw-bot-review/lib/pixel-office/notificationSound.ts
T
xmanrui eebe431560 feat: implement Pixel Office Phase 2 - layout editor, persistence, sound, sub-agent visualization
- Add layout editor with floor/wall paint, erase, furniture place/move/rotate/delete tools
- Add editor state management (undo/redo, selection, drag, ghost preview)
- Add EditorToolbar and EditActionBar UI components
- Add layout persistence API (GET/POST to ~/.openclaw/pixel-office/layout.json)
- Add notification sound system (Web Audio API ascending chime on agent completion)
- Add sub-agent visualization via session JSONL parsing
- Fix agent activity API to read from agents.list config and correct session paths
- Add agent name labels above character sprites in canvas renderer
- Add i18n translations for editor features (zh/en)
2026-02-25 01:39:44 +08:00

67 lines
1.6 KiB
TypeScript

import {
NOTIFICATION_NOTE_1_HZ,
NOTIFICATION_NOTE_2_HZ,
NOTIFICATION_NOTE_1_START_SEC,
NOTIFICATION_NOTE_2_START_SEC,
NOTIFICATION_NOTE_DURATION_SEC,
NOTIFICATION_VOLUME,
} from './constants'
let soundEnabled = true
let audioCtx: AudioContext | null = null
export function setSoundEnabled(enabled: boolean): void {
soundEnabled = enabled
}
export function isSoundEnabled(): boolean {
return soundEnabled
}
function playNote(ctx: AudioContext, freq: number, startOffset: number): void {
const t = ctx.currentTime + startOffset
const osc = ctx.createOscillator()
const gain = ctx.createGain()
osc.type = 'sine'
osc.frequency.setValueAtTime(freq, t)
gain.gain.setValueAtTime(NOTIFICATION_VOLUME, t)
gain.gain.exponentialRampToValueAtTime(0.001, t + NOTIFICATION_NOTE_DURATION_SEC)
osc.connect(gain)
gain.connect(ctx.destination)
osc.start(t)
osc.stop(t + NOTIFICATION_NOTE_DURATION_SEC)
}
export async function playDoneSound(): Promise<void> {
if (!soundEnabled) return
try {
if (!audioCtx) {
audioCtx = new AudioContext()
}
if (audioCtx.state === 'suspended') {
await audioCtx.resume()
}
playNote(audioCtx, NOTIFICATION_NOTE_1_HZ, NOTIFICATION_NOTE_1_START_SEC)
playNote(audioCtx, NOTIFICATION_NOTE_2_HZ, NOTIFICATION_NOTE_2_START_SEC)
} catch {
// Audio may not be available
}
}
export function unlockAudio(): void {
try {
if (!audioCtx) {
audioCtx = new AudioContext()
}
if (audioCtx.state === 'suspended') {
audioCtx.resume()
}
} catch {
// ignore
}
}