Skip to content

Sound Effects [AARD-1887] #1135

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 20 commits into from
Jun 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions NOTICE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
This product bundles the following third-party asset:

* fission/src/assets/sound-files/ButtonPress.mp3
Licensed under CC0 1.0
https://creativecommons.org/publicdomain/zero/1.0/

* fission/src/asset/sound-files/CheckboxPress.wav
Licensed under CC0 1.0
https://creativecommons.org/publicdomain/zero/1.0/

* fission/src/asset/sound-files/DullClick.wav
Licensed under CC0 1.0
https://creativecommons.org/publicdomain/zero/1.0/
Binary file added fission/src/assets/sound-files/ButtonPress.mp3
Binary file not shown.
Binary file not shown.
Binary file added fission/src/assets/sound-files/DullClick.wav
Binary file not shown.
4 changes: 4 additions & 0 deletions fission/src/systems/preferences/PreferenceTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ export type GlobalPreference =
| "RenderScoreboard"
| "SubsystemGravity"
| "SimAutoReconnect"
| "MuteAllSound"
| "SFXVolume"

export const RobotPreferencesKey: string = "Robots"
export const FieldPreferencesKey: string = "Fields"
Expand All @@ -36,6 +38,8 @@ export const DefaultGlobalPreferences: { [key: string]: unknown } = {
RenderScoreboard: true,
SubsystemGravity: false,
SimAutoReconnect: false,
MuteAllSound: false,
SFXVolume: 25,
}

export type GraphicsPreferences = {
Expand Down
35 changes: 35 additions & 0 deletions fission/src/systems/sound/SoundPlayer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import PreferencesSystem from "../preferences/PreferencesSystem"
import { clamp } from "@/util/Utility"

export class SoundPlayer {
private static audioElements: HTMLAudioElement[] = []

constructor() {}

public static async play(filePath: string): Promise<void> {
const audio = new Audio(filePath)

SoundPlayer.audioElements.push(audio)
audio.addEventListener("ended", () => {
const index = SoundPlayer.audioElements.indexOf(audio)
if (index !== -1) {
SoundPlayer.audioElements.splice(index, 1)
}
})

audio.volume = PreferencesSystem.getGlobalPreference<boolean>("MuteAllSound")
? 0
: clamp(PreferencesSystem.getGlobalPreference<number>("SFXVolume") / 100, 0, 1)

return audio.play().catch(error => {
console.error("Error playing the audio file:", error)
})
}

public static changeVolume(): void {
const volume = PreferencesSystem.getGlobalPreference<boolean>("MuteAllSound")
? 0
: clamp(PreferencesSystem.getGlobalPreference<number>("SFXVolume") / 100, 0, 1)
SoundPlayer.audioElements.forEach(audio => (audio.volume = volume))
}
}
3 changes: 3 additions & 0 deletions fission/src/ui/components/Button.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import React, { ReactNode } from "react"
import { Button as BaseButton } from "@mui/base/Button"
import { SoundPlayer } from "@/systems/sound/SoundPlayer"
import buttonPressSound from "@/assets/sound-files/ButtonPress.mp3"

export enum ButtonSize {
Small,
Expand Down Expand Up @@ -53,6 +55,7 @@ const Button: React.FC<ButtonProps> = ({
return (
<BaseButton
onClick={onClick}
onMouseDown={() => SoundPlayer.play(buttonPressSound)}
className={`
${colorOverrideClass || "bg-gradient-to-r from-interactive-element-left via-interactive-element-right to-interactive-element-left bg-[length:200%_100%] active:bg-right"}
${sizeClassNames}
Expand Down
3 changes: 3 additions & 0 deletions fission/src/ui/components/Checkbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import Label, { LabelSize } from "./Label"
import { Switch } from "@mui/base/Switch"
import { Box } from "@mui/material"
import { LabelWithTooltip } from "./StyledComponents"
import { SoundPlayer } from "@/systems/sound/SoundPlayer"
import checkboxPressSound from "@/assets/sound-files/CheckboxPress.wav"

type CheckboxProps = {
label: string
Expand Down Expand Up @@ -55,6 +57,7 @@ const Checkbox: React.FC<CheckboxProps> = ({
)}
<Switch
onChange={(e: React.ChangeEvent<HTMLInputElement>) => onClick && onClick(e.target.checked)}
onMouseDown={() => SoundPlayer.play(checkboxPressSound)}
slotProps={{
root: {
className: `group relative inline-block w-[24px] h-[24px] m-2.5 cursor-pointer transform transition-transform hover:scale-[1.03] active:scale-[1.06]`,
Expand Down
4 changes: 4 additions & 0 deletions fission/src/ui/components/Dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import React, { useEffect, useRef, useState } from "react"
import { alpha, styled } from "@mui/system"
import { Menu, MenuItem, Button, Tooltip } from "@mui/material"
import { colorNameToVar } from "../ThemeContext"
import { SoundPlayer } from "@/systems/sound/SoundPlayer"
import dropdownMenuSound from "@/assets/sound-files/DullClick.wav"

/** The clickable button for a dropdown that shows the selected item and opens the menu. Custom styling over the MUI material button.*/
const CustomButton = styled(Button)({
Expand Down Expand Up @@ -104,6 +106,7 @@ const Dropdown: React.FC<DropdownProps> = ({ options, onSelect, defaultValue, la
/** Handles closing the dropdown menu. */
const handleClose = () => {
setAnchorEl(null)
SoundPlayer.play(dropdownMenuSound)
}

/** Handles the selection of a dropdown option. */
Expand Down Expand Up @@ -131,6 +134,7 @@ const Dropdown: React.FC<DropdownProps> = ({ options, onSelect, defaultValue, la
<div>
<CustomButton
onClick={handleClick}
onMouseDown={() => SoundPlayer.play(dropdownMenuSound)}
ref={buttonRef}
className={`transform transition-transform hover:scale-[1.012] active:scale-[1.024]`}
>
Expand Down
3 changes: 3 additions & 0 deletions fission/src/ui/components/MainHUD.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { ButtonIcon, SynthesisIcons } from "./StyledComponents"
import { Button } from "@mui/base"
import { Box } from "@mui/material"
import { setAddToast } from "./GlobalUIControls"
import { SoundPlayer } from "@/systems/sound/SoundPlayer"
import buttonPressSound from "@/assets/sound-files/ButtonPress.mp3"
import MatchMode from "@/systems/MatchMode"
import { Global_AddToast } from "@/components/GlobalUIControls.ts"

Expand All @@ -26,6 +28,7 @@ const MainHUDButton: React.FC<ButtonProps> = ({ value, icon, onClick, larger })
return (
<Button
onClick={onClick}
onMouseDown={() => SoundPlayer.play(buttonPressSound)}
className={`relative flex flex-row
cursor-pointer
bg-background w-full m-auto px-2 py-1 text-main-text border-none rounded-md ${larger ? "justify-center" : ""}
Expand Down
5 changes: 5 additions & 0 deletions fission/src/ui/components/Modal.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import React, { ReactNode } from "react"
import { ClickAwayListener } from "@mui/base/ClickAwayListener"
import { useModalControlContext } from "@/ui/ModalContext"
import { SoundPlayer } from "@/systems/sound/SoundPlayer"
import buttonPressSound from "@/assets/sound-files/ButtonPress.mp3"

export type ModalPropsImpl = {
modalId: string
Expand Down Expand Up @@ -102,6 +104,7 @@ const Modal: React.FC<ModalProps> = ({
closeModal()
if (!cancelBlocked && onCancel) onCancel()
}}
onMouseDown={() => SoundPlayer.play(buttonPressSound)}
className={`${
cancelBlocked ? "bg-interactive-background" : "bg-cancel-button"
} rounded-md cursor-pointer px-4 py-1 font-bold duration-100 hover:brightness-90
Expand All @@ -116,6 +119,7 @@ const Modal: React.FC<ModalProps> = ({
onClick={() => {
if (!middleBlocked && onMiddle) onMiddle()
}}
onMouseDown={() => SoundPlayer.play(buttonPressSound)}
className={`${
middleBlocked ? "bg-interactive-background" : "bg-accept-button"
} rounded-md cursor-pointer px-4 py-1 font-bold duration-100 hover:brightness-90
Expand All @@ -131,6 +135,7 @@ const Modal: React.FC<ModalProps> = ({
closeModal()
if (!acceptBlocked && onAccept) onAccept()
}}
onMouseDown={() => SoundPlayer.play(buttonPressSound)}
className={`${
acceptBlocked ? "bg-interactive-background" : "bg-accept-button"
} rounded-md cursor-pointer px-4 py-1 font-bold duration-100 hover:brightness-90
Expand Down
5 changes: 5 additions & 0 deletions fission/src/ui/components/Panel.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import React, { ReactNode } from "react"
import { usePanelControlContext } from "@/ui/PanelContext"
import { SoundPlayer } from "@/systems/sound/SoundPlayer"
import buttonPressSound from "@/assets/sound-files/ButtonPress.mp3"

export type OpenLocation =
| "top-left"
Expand Down Expand Up @@ -172,6 +174,7 @@ const Panel: React.FC<PanelProps> = ({
closePanel(panelId)
if (!cancelBlocked && onCancel) onCancel()
}}
onMouseDown={() => SoundPlayer.play(buttonPressSound)}
className={`${
cancelBlocked ? "bg-interactive-background" : "bg-cancel-button"
} rounded-md cursor-pointer px-4 py-1 font-bold duration-100 hover:brightness-90
Expand All @@ -186,6 +189,7 @@ const Panel: React.FC<PanelProps> = ({
onClick={() => {
if (!middleBlocked && onMiddle) onMiddle()
}}
onMouseDown={() => SoundPlayer.play(buttonPressSound)}
className={`${
middleBlocked ? "bg-interactive-background" : "bg-accept-button"
} rounded-md cursor-pointer px-4 py-1 font-bold duration-100 hover:brightness-90
Expand All @@ -201,6 +205,7 @@ const Panel: React.FC<PanelProps> = ({
closePanel(panelId)
if (!acceptBlocked && onAccept) onAccept()
}}
onMouseDown={() => SoundPlayer.play(buttonPressSound)}
className={`${
acceptBlocked ? "bg-interactive-background" : "bg-accept-button"
} rounded-md cursor-pointer px-4 py-1 font-bold duration-100 hover:brightness-90
Expand Down
3 changes: 3 additions & 0 deletions fission/src/ui/components/TransformGizmoControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import World from "@/systems/World"
import Button, { ButtonSize } from "./Button"
import InputSystem from "@/systems/input/InputSystem"
import * as THREE from "three"
import { SoundPlayer } from "@/systems/sound/SoundPlayer"
import buttonPressSound from "@/assets/sound-files/ButtonPress.mp3"

/**
* Creates GizmoSceneObject and gives you a toggle button group to control the modes of the gizmo.
Expand Down Expand Up @@ -109,6 +111,7 @@ function TransformGizmoControl({
setMode(v)
gizmo?.SetMode(v)
}}
onMouseDown={() => SoundPlayer.play(buttonPressSound)}
sx={{
...(sx ?? {}),
alignSelf: "center",
Expand Down
28 changes: 28 additions & 0 deletions fission/src/ui/modals/configuring/SettingsModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import PreferencesSystem from "@/systems/preferences/PreferencesSystem"
import { SceneOverlayEvent, SceneOverlayEventKey } from "@/ui/components/SceneOverlayEvents"
import { Box } from "@mui/material"
import { Spacer, SynthesisIcons } from "@/ui/components/StyledComponents"
import Slider from "@/ui/components/Slider"
import { SoundPlayer } from "@/systems/sound/SoundPlayer"

const SettingsModal: React.FC<ModalPropsImpl> = ({ modalId }) => {
const { closeModal } = useModalControlContext()
Expand Down Expand Up @@ -44,13 +46,22 @@ const SettingsModal: React.FC<ModalPropsImpl> = ({ modalId }) => {
const [subsystemGravity, setSubsystemGravity] = useState<boolean>(
PreferencesSystem.getGlobalPreference<boolean>("SubsystemGravity")
)
const [muteAllSound, setMuteAllSound] = useState<boolean>(
PreferencesSystem.getGlobalPreference<boolean>("MuteAllSound")
)

const [sfxVolume, setSFXVolume] = useState<number>(PreferencesSystem.getGlobalPreference<number>("SFXVolume"))

const saveSettings = () => {
PreferencesSystem.setGlobalPreference<boolean>("ReportAnalytics", reportAnalytics)
PreferencesSystem.setGlobalPreference<boolean>("RenderScoringZones", renderScoringZones)
PreferencesSystem.setGlobalPreference<boolean>("RenderSceneTags", renderSceneTags)
PreferencesSystem.setGlobalPreference<boolean>("RenderScoreboard", renderScoreboard)
PreferencesSystem.setGlobalPreference<boolean>("SubsystemGravity", subsystemGravity)
PreferencesSystem.setGlobalPreference<boolean>("MuteAllSound", muteAllSound)
PreferencesSystem.setGlobalPreference<number>("SFXVolume", sfxVolume)

SoundPlayer.changeVolume() // Apply the new sound volume

// Disabled until these settings are implemented
/* PreferencesSystem.setGlobalPreference<number>("ZoomSensitivity", zoomSensitivity)
Expand Down Expand Up @@ -165,6 +176,23 @@ const SettingsModal: React.FC<ModalPropsImpl> = ({ modalId }) => {
setRenderScoreboard(checked)
}}
/>
<Checkbox
label="Mute All Sound"
defaultState={PreferencesSystem.getGlobalPreference<boolean>("MuteAllSound")}
onClick={checked => {
setMuteAllSound(checked)
}}
/>
<Slider
min={0}
max={100}
value={sfxVolume}
label={"SFX Volume"}
format={{ maximumFractionDigits: 2 }}
onChange={(_, value: number | number[]) => setSFXVolume(value as number)}
tooltipText="Volume of sound effects (%)."
/>
{Spacer(8)}
</Box>
</div>
</Modal>
Expand Down
8 changes: 4 additions & 4 deletions fission/src/ui/modals/mirabuf/ImportLocalMirabufModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { ToggleButton, ToggleButtonGroup } from "@/ui/components/ToggleButtonGro
import { usePanelControlContext } from "@/ui/PanelContext"
import { PAUSE_REF_ASSEMBLY_SPAWNING } from "@/systems/physics/PhysicsSystem"
import { Global_OpenPanel } from "@/ui/components/GlobalUIControls"
import { SoundPlayer } from "@/systems/sound/SoundPlayer"
import buttonPressSound from "@/assets/sound-files/ButtonPress.mp3"

const ImportLocalMirabufModal: React.FC<ModalPropsImpl> = ({ modalId }) => {
// update tooltip based on type of drivetrain, receive message from Synthesis
Expand Down Expand Up @@ -73,10 +75,8 @@ const ImportLocalMirabufModal: React.FC<ModalPropsImpl> = ({ modalId }) => {
<ToggleButtonGroup
value={miraType}
exclusive
onChange={(_, v) => {
if (v == null) return
setSelectedType(v)
}}
onChange={(_, v) => v != null && setSelectedType(v)}
onMouseDown={() => SoundPlayer.play(buttonPressSound)}
sx={{
alignSelf: "center",
}}
Expand Down
3 changes: 3 additions & 0 deletions fission/src/ui/panels/configuring/CameraSelectionPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import Panel, { PanelPropsImpl } from "@/ui/components/Panel"
import { ToggleButton, ToggleButtonGroup } from "@/ui/components/ToggleButtonGroup"
import { useCallback, useEffect, useState } from "react"
import { AiOutlineCamera } from "react-icons/ai"
import { SoundPlayer } from "@/systems/sound/SoundPlayer"
import buttonPressSound from "@/assets/sound-files/ButtonPress.mp3"

interface OrbitSettingsProps {
controls: CustomOrbitControls
Expand Down Expand Up @@ -58,6 +60,7 @@ const CameraSelectionPanel: React.FC<PanelPropsImpl> = ({ panelId }) => {

setCameraControls(v)
}}
onMouseDown={() => SoundPlayer.play(buttonPressSound)}
>
<ToggleButton value={"Orbit"}>Orbit</ToggleButton>
</ToggleButtonGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import { ConfigMode, popConfigurePanelSettings } from "./ConfigurePanelControls"
import BrainSelectionInterface from "./interfaces/BrainSelectionInterface"
import SimulationInterface from "./interfaces/SimulationInterface"
import { mirabufPanelState } from "@/panels/mirabuf/MirabufState.tsx"
import { SoundPlayer } from "@/systems/sound/SoundPlayer"
import buttonPressSound from "@/assets/sound-files/ButtonPress.mp3"

/** Option for selecting a robot of field */
class AssemblySelectionOption extends SelectMenuOption {
Expand Down Expand Up @@ -358,6 +360,7 @@ const ConfigurePanel: React.FC<PanelPropsImpl> = ({ panelId }) => {
new ConfigurationSavedEvent()
setConfigMode(undefined)
}}
onMouseDown={() => SoundPlayer.play(buttonPressSound)}
sx={{
alignSelf: "center",
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import SynthesisBrain from "@/systems/simulation/synthesis_brain/SynthesisBrain"
import WPILibBrain from "@/systems/simulation/wpilib_brain/WPILibBrain"
import { ToggleButton, ToggleButtonGroup } from "@/ui/components/ToggleButtonGroup"
import { useState } from "react"
import { SoundPlayer } from "@/systems/sound/SoundPlayer"
import buttonPressSound from "@/assets/sound-files/ButtonPress.mp3"

type BrainSelectionInterfaceProps = {
selectedAssembly: MirabufSceneObject
Expand Down Expand Up @@ -33,6 +35,7 @@ export default function BrainSelectionInterface({ selectedAssembly }: BrainSelec
}
setRobotBrainType(brainType)
}}
onMouseDown={() => SoundPlayer.play(buttonPressSound)}
sx={{
alignSelf: "center",
}}
Expand Down
9 changes: 8 additions & 1 deletion fission/src/ui/panels/mirabuf/ImportMirabufPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ import Button from "@/ui/components/Button"
import { Global_AddToast, Global_OpenPanel } from "@/ui/components/GlobalUIControls"
import { PAUSE_REF_ASSEMBLY_SPAWNING } from "@/systems/physics/PhysicsSystem"
import { mirabufPanelState } from "@/panels/mirabuf/MirabufState.tsx"
import { SoundPlayer } from "@/systems/sound/SoundPlayer"
import buttonPressSound from "@/assets/sound-files/ButtonPress.mp3"

interface ItemCardProps {
id: string
Expand Down Expand Up @@ -393,7 +395,12 @@ const ImportMirabufPanel: React.FC<PanelPropsImpl> = ({ panelId }) => {
<ToggleButtonGroup
value={viewType}
exclusive
onChange={(_, v) => v != null && setViewType(v)}
onChange={(_, v) => {
if (v != null) {
setViewType(v)
}
}}
onMouseDown={() => SoundPlayer.play(buttonPressSound)}
sx={{
alignSelf: "center",
}}
Expand Down
Loading
Loading