Loading packages/client/src/components/Settings/AudioSettings.tsx +152 −2 Original line number Diff line number Diff line Loading @@ -14,20 +14,23 @@ import African3 from "../../sounds/African3.mp3"; import African4 from "../../sounds/African4.mp3"; import Coffee1 from "../../sounds/Coffee1.mp3"; import Coffee2 from "../../sounds/Coffee2.mp3"; import Retro6 from "../../sounds/Retro6.mp3"; import WoodBlock1 from "../../sounds/Wood Block1.mp3"; import WoodBlock2 from "../../sounds/Wood Block2.mp3"; import WoodBlock3 from "../../sounds/Wood Block3.mp3"; import { Switch } from "../core/Switch"; const _ = (v: boolean) => (v ? "enable" : "disable"); const DEFAULT_PIXEL_BUCKET_THRESHOLD = 6; export const AudioSettings = () => { const { state, dispatch } = useAudio(); const { config } = useAppContext<true>(); const { config } = useAppContext(); const coffee2Ref = useRef<(() => void) | null>(null); const delayedAvailableSoundRef = useRef<number | undefined>(undefined); const earlyAvailableSoundPlayedRef = useRef(false); const earlyBucketSoundPlayedRef = useRef(false); const [Coffee1Sound] = useSound(Coffee1, { volume: 0.5 }); Loading @@ -43,6 +46,8 @@ export const AudioSettings = () => { const [African4Sound] = useSound(African4, { volume: 0.5 }); const [Retro6Sound] = useSound(Retro6, { volume: 0.3 }); const [WoodBlock1Sound] = useSound(WoodBlock1, { volume: 0.1 }); const [WoodBlock2Sound] = useSound(WoodBlock2, { volume: 0.1 }); Loading @@ -51,6 +56,14 @@ export const AudioSettings = () => { const [pixelsAvailable, setPixelsAvailable] = useState(0); const [pixelAvailableOffset, setPixelAvailableOffset] = useState(0); const [pixelBucketThreshold, setPixelBucketThreshold] = useState(6); const [pixelBucketOffset, setPixelBucketOffset] = useState(0); const [pixelGainStartedAt, setPixelGainStartedAt] = useState< number | undefined >(() => network.getState("pixelLastPlaced")?.[0]); const [bucketReachedAt, setBucketReachedAt] = useState<number | undefined>( undefined, ); const WoodSounds = [WoodBlock1Sound, WoodBlock2Sound, WoodBlock3Sound]; Loading @@ -67,6 +80,18 @@ export const AudioSettings = () => { }; }, [pixelAvailableOffset, state.pixelAvailable]); useEffect(() => { const handlePixelGainStarted = (time: number) => { setPixelGainStartedAt(time); }; network.on("pixelLastPlaced", handlePixelGainStarted); return () => { network.off("pixelLastPlaced", handlePixelGainStarted); }; }, []); const handleAvailable = useCallback( (available: number, isUndo: boolean) => { if (available > pixelsAvailable && state.pixelAvailable && !isUndo) { Loading @@ -85,13 +110,33 @@ export const AudioSettings = () => { earlyAvailableSoundPlayedRef.current = false; } if ( state.pixelBucketFull && !isUndo && available >= pixelBucketThreshold && pixelsAvailable < pixelBucketThreshold ) { if (pixelBucketOffset < 0) { if (!earlyBucketSoundPlayedRef.current) Retro6Sound(); earlyBucketSoundPlayedRef.current = false; } else { setBucketReachedAt(Date.now()); } } else if (isUndo || available < pixelsAvailable) { earlyBucketSoundPlayedRef.current = false; } setPixelsAvailable(available); }, [ Retro6Sound, Coffee2Sound, pixelAvailableOffset, pixelBucketOffset, pixelBucketThreshold, pixelsAvailable, state.pixelAvailable, state.pixelBucketFull, ], ); Loading @@ -109,6 +154,7 @@ export const AudioSettings = () => { if ( !state.pixelAvailable || pixelAvailableOffset >= 0 || !config || pixelsAvailable >= config.canvas.pixel.maxStack ) { return; Loading @@ -129,7 +175,6 @@ export const AudioSettings = () => { earlyAvailableSoundPlayedRef.current = true; Coffee2Sound(); }, delay); return () => { window.clearTimeout(timeout); }; Loading @@ -141,6 +186,63 @@ export const AudioSettings = () => { state.pixelAvailable, ]); useEffect(() => { earlyBucketSoundPlayedRef.current = false; if (!state.pixelBucketFull || !config) return; let soundAt: number; let clearReachedAt = false; if (pixelBucketOffset >= 0) { if (bucketReachedAt === undefined) return; soundAt = bucketReachedAt + pixelBucketOffset * 1000; clearReachedAt = true; } else { if ( pixelGainStartedAt === undefined || pixelsAvailable >= pixelBucketThreshold ) { return; } let cooldownSeconds = 0; for ( let pixel = pixelsAvailable + 1; pixel <= pixelBucketThreshold; pixel += 1 ) { cooldownSeconds += CanvasLib.getPixelCooldown(pixel, config); } soundAt = pixelGainStartedAt + cooldownSeconds * 1000 + pixelBucketOffset * 1000; if (soundAt <= Date.now()) return; } const timeout = window.setTimeout(() => { Retro6Sound(); if (pixelBucketOffset < 0) earlyBucketSoundPlayedRef.current = true; if (clearReachedAt) setBucketReachedAt(undefined); }, Math.max(0, soundAt - Date.now())); return () => { window.clearTimeout(timeout); }; }, [ Retro6Sound, bucketReachedAt, config, pixelBucketOffset, pixelBucketThreshold, pixelGainStartedAt, pixelsAvailable, state.pixelBucketFull, ]); const handlePlace = useCallback(() => { if (!state.place) return; Loading Loading @@ -254,6 +356,54 @@ export const AudioSettings = () => { }} /> )} <Switch silent isSelected={state.pixelBucketFull} onValueChange={(v) => { dispatch([_(v), "pixelBucketFull"]); setBucketReachedAt(undefined); if (v) { Retro6Sound(); } }} > Pixel Bucket Full </Switch> {state.pixelBucketFull && ( <div className="flex flex-col gap-4 px-2 pb-2"> <Slider label="Bucket Alert Level" step={1} minValue={1} maxValue={ config?.canvas.pixel.maxStack ?? DEFAULT_PIXEL_BUCKET_THRESHOLD } value={pixelBucketThreshold} onChange={(value) => { setBucketReachedAt(undefined); setPixelBucketThreshold(value as number); }} getValue={(value) => { const pixels = value as number; return `${pixels} pixel${pixels === 1 ? "" : "s"}`; }} /> <Slider label="Chime Offset" step={1} minValue={-5} maxValue={15} value={-pixelBucketOffset} onChange={(value) => setPixelBucketOffset(-(value as number)) } getValue={(value) => { const offset = -(value as number); return `${offset > 0 ? "+" : ""}${offset}s`; }} /> </div> )} <Switch silent isSelected={state.place} Loading packages/client/src/contexts/AudioContext.tsx +2 −0 Original line number Diff line number Diff line Loading @@ -14,6 +14,7 @@ interface IAudioState { disconnected: boolean; reconnected: boolean; pixelAvailable: boolean; pixelBucketFull: boolean; pixelUndo: boolean; uiClick: boolean; } Loading @@ -24,6 +25,7 @@ const Defaults: IAudioState = { disconnected: false, reconnected: false, pixelAvailable: false, pixelBucketFull: false, pixelUndo: false, uiClick: false, }; Loading packages/client/src/sounds/Retro6.mp3 0 → 100644 +18.5 KiB File added.No diff preview for this file type. View file Loading
packages/client/src/components/Settings/AudioSettings.tsx +152 −2 Original line number Diff line number Diff line Loading @@ -14,20 +14,23 @@ import African3 from "../../sounds/African3.mp3"; import African4 from "../../sounds/African4.mp3"; import Coffee1 from "../../sounds/Coffee1.mp3"; import Coffee2 from "../../sounds/Coffee2.mp3"; import Retro6 from "../../sounds/Retro6.mp3"; import WoodBlock1 from "../../sounds/Wood Block1.mp3"; import WoodBlock2 from "../../sounds/Wood Block2.mp3"; import WoodBlock3 from "../../sounds/Wood Block3.mp3"; import { Switch } from "../core/Switch"; const _ = (v: boolean) => (v ? "enable" : "disable"); const DEFAULT_PIXEL_BUCKET_THRESHOLD = 6; export const AudioSettings = () => { const { state, dispatch } = useAudio(); const { config } = useAppContext<true>(); const { config } = useAppContext(); const coffee2Ref = useRef<(() => void) | null>(null); const delayedAvailableSoundRef = useRef<number | undefined>(undefined); const earlyAvailableSoundPlayedRef = useRef(false); const earlyBucketSoundPlayedRef = useRef(false); const [Coffee1Sound] = useSound(Coffee1, { volume: 0.5 }); Loading @@ -43,6 +46,8 @@ export const AudioSettings = () => { const [African4Sound] = useSound(African4, { volume: 0.5 }); const [Retro6Sound] = useSound(Retro6, { volume: 0.3 }); const [WoodBlock1Sound] = useSound(WoodBlock1, { volume: 0.1 }); const [WoodBlock2Sound] = useSound(WoodBlock2, { volume: 0.1 }); Loading @@ -51,6 +56,14 @@ export const AudioSettings = () => { const [pixelsAvailable, setPixelsAvailable] = useState(0); const [pixelAvailableOffset, setPixelAvailableOffset] = useState(0); const [pixelBucketThreshold, setPixelBucketThreshold] = useState(6); const [pixelBucketOffset, setPixelBucketOffset] = useState(0); const [pixelGainStartedAt, setPixelGainStartedAt] = useState< number | undefined >(() => network.getState("pixelLastPlaced")?.[0]); const [bucketReachedAt, setBucketReachedAt] = useState<number | undefined>( undefined, ); const WoodSounds = [WoodBlock1Sound, WoodBlock2Sound, WoodBlock3Sound]; Loading @@ -67,6 +80,18 @@ export const AudioSettings = () => { }; }, [pixelAvailableOffset, state.pixelAvailable]); useEffect(() => { const handlePixelGainStarted = (time: number) => { setPixelGainStartedAt(time); }; network.on("pixelLastPlaced", handlePixelGainStarted); return () => { network.off("pixelLastPlaced", handlePixelGainStarted); }; }, []); const handleAvailable = useCallback( (available: number, isUndo: boolean) => { if (available > pixelsAvailable && state.pixelAvailable && !isUndo) { Loading @@ -85,13 +110,33 @@ export const AudioSettings = () => { earlyAvailableSoundPlayedRef.current = false; } if ( state.pixelBucketFull && !isUndo && available >= pixelBucketThreshold && pixelsAvailable < pixelBucketThreshold ) { if (pixelBucketOffset < 0) { if (!earlyBucketSoundPlayedRef.current) Retro6Sound(); earlyBucketSoundPlayedRef.current = false; } else { setBucketReachedAt(Date.now()); } } else if (isUndo || available < pixelsAvailable) { earlyBucketSoundPlayedRef.current = false; } setPixelsAvailable(available); }, [ Retro6Sound, Coffee2Sound, pixelAvailableOffset, pixelBucketOffset, pixelBucketThreshold, pixelsAvailable, state.pixelAvailable, state.pixelBucketFull, ], ); Loading @@ -109,6 +154,7 @@ export const AudioSettings = () => { if ( !state.pixelAvailable || pixelAvailableOffset >= 0 || !config || pixelsAvailable >= config.canvas.pixel.maxStack ) { return; Loading @@ -129,7 +175,6 @@ export const AudioSettings = () => { earlyAvailableSoundPlayedRef.current = true; Coffee2Sound(); }, delay); return () => { window.clearTimeout(timeout); }; Loading @@ -141,6 +186,63 @@ export const AudioSettings = () => { state.pixelAvailable, ]); useEffect(() => { earlyBucketSoundPlayedRef.current = false; if (!state.pixelBucketFull || !config) return; let soundAt: number; let clearReachedAt = false; if (pixelBucketOffset >= 0) { if (bucketReachedAt === undefined) return; soundAt = bucketReachedAt + pixelBucketOffset * 1000; clearReachedAt = true; } else { if ( pixelGainStartedAt === undefined || pixelsAvailable >= pixelBucketThreshold ) { return; } let cooldownSeconds = 0; for ( let pixel = pixelsAvailable + 1; pixel <= pixelBucketThreshold; pixel += 1 ) { cooldownSeconds += CanvasLib.getPixelCooldown(pixel, config); } soundAt = pixelGainStartedAt + cooldownSeconds * 1000 + pixelBucketOffset * 1000; if (soundAt <= Date.now()) return; } const timeout = window.setTimeout(() => { Retro6Sound(); if (pixelBucketOffset < 0) earlyBucketSoundPlayedRef.current = true; if (clearReachedAt) setBucketReachedAt(undefined); }, Math.max(0, soundAt - Date.now())); return () => { window.clearTimeout(timeout); }; }, [ Retro6Sound, bucketReachedAt, config, pixelBucketOffset, pixelBucketThreshold, pixelGainStartedAt, pixelsAvailable, state.pixelBucketFull, ]); const handlePlace = useCallback(() => { if (!state.place) return; Loading Loading @@ -254,6 +356,54 @@ export const AudioSettings = () => { }} /> )} <Switch silent isSelected={state.pixelBucketFull} onValueChange={(v) => { dispatch([_(v), "pixelBucketFull"]); setBucketReachedAt(undefined); if (v) { Retro6Sound(); } }} > Pixel Bucket Full </Switch> {state.pixelBucketFull && ( <div className="flex flex-col gap-4 px-2 pb-2"> <Slider label="Bucket Alert Level" step={1} minValue={1} maxValue={ config?.canvas.pixel.maxStack ?? DEFAULT_PIXEL_BUCKET_THRESHOLD } value={pixelBucketThreshold} onChange={(value) => { setBucketReachedAt(undefined); setPixelBucketThreshold(value as number); }} getValue={(value) => { const pixels = value as number; return `${pixels} pixel${pixels === 1 ? "" : "s"}`; }} /> <Slider label="Chime Offset" step={1} minValue={-5} maxValue={15} value={-pixelBucketOffset} onChange={(value) => setPixelBucketOffset(-(value as number)) } getValue={(value) => { const offset = -(value as number); return `${offset > 0 ? "+" : ""}${offset}s`; }} /> </div> )} <Switch silent isSelected={state.place} Loading
packages/client/src/contexts/AudioContext.tsx +2 −0 Original line number Diff line number Diff line Loading @@ -14,6 +14,7 @@ interface IAudioState { disconnected: boolean; reconnected: boolean; pixelAvailable: boolean; pixelBucketFull: boolean; pixelUndo: boolean; uiClick: boolean; } Loading @@ -24,6 +25,7 @@ const Defaults: IAudioState = { disconnected: false, reconnected: false, pixelAvailable: false, pixelBucketFull: false, pixelUndo: false, uiClick: false, }; Loading
packages/client/src/sounds/Retro6.mp3 0 → 100644 +18.5 KiB File added.No diff preview for this file type. View file