diff --git a/workspaces/simon-game/src/app/components/App.tsx b/workspaces/simon-game/src/app/components/App.tsx index 895d9015..4a5c06dc 100644 --- a/workspaces/simon-game/src/app/components/App.tsx +++ b/workspaces/simon-game/src/app/components/App.tsx @@ -1,19 +1,15 @@ import React from "react"; import { Box } from "./Box.tsx"; +import { playNote } from "../util/playNote.ts"; +import { config } from "../constants.ts"; export function App() { return ( -
- - - - +
+ {config.boxes.map((box) => ( + playNote(box.frequency)} /> + ))}
); } diff --git a/workspaces/simon-game/src/app/components/Box.tsx b/workspaces/simon-game/src/app/components/Box.tsx index 41032b28..64e989ab 100644 --- a/workspaces/simon-game/src/app/components/Box.tsx +++ b/workspaces/simon-game/src/app/components/Box.tsx @@ -5,18 +5,28 @@ type Props = { height?: number; width?: number; margin?: number; + onClick: () => void; }; -export function Box(props: Props) { +export function Box({ + color = "lightblue", + height = 100, + width = 100, + margin, + onClick, +}: Props) { return ( -
); } diff --git a/workspaces/simon-game/src/app/constants.ts b/workspaces/simon-game/src/app/constants.ts new file mode 100644 index 00000000..59317da3 --- /dev/null +++ b/workspaces/simon-game/src/app/constants.ts @@ -0,0 +1,22 @@ +export const config = { + soundDurationMs: 300, + volumePct: 0.1, + boxes: [ + { + color: "red", + frequency: 261.63, + }, + { + color: "#0050B5", // cobalt blue + frequency: 329.63, + }, + { + color: "green", + frequency: 392.0, + }, + { + color: "yellow", + frequency: 523.25, + }, + ], +}; diff --git a/workspaces/simon-game/src/app/util/playNote.ts b/workspaces/simon-game/src/app/util/playNote.ts new file mode 100644 index 00000000..f33e716a --- /dev/null +++ b/workspaces/simon-game/src/app/util/playNote.ts @@ -0,0 +1,6 @@ +import { config } from "../constants.ts"; +import { playSound } from "./playSound.ts"; + +export function playNote(freq: number): void { + playSound(freq, config.soundDurationMs, config.volumePct); +} diff --git a/workspaces/simon-game/src/app/util/playSound.ts b/workspaces/simon-game/src/app/util/playSound.ts new file mode 100644 index 00000000..5c37e4be --- /dev/null +++ b/workspaces/simon-game/src/app/util/playSound.ts @@ -0,0 +1,55 @@ +import { MS_IN_SEC } from "@code-chronicles/util/timeConstants"; + +let audioContext: AudioContext | null = null; + +const FADE_DURATION = 0.01; + +/** + * Plays a sound using the Web Audio API. + * + * @param context - The audio context to use for generating the sound. + * @param frequency - The frequency of the sound in Hertz + * @param durationMs - The duration of the sound in milliseconds. + * @param volumePct - The volume of the sound, typically between 0 and 1. + */ +export function playSound( + frequency: number, + durationMs: number, + volumePct: number, +): void { + if (frequency <= 0) { + throw new RangeError("Frequency must be a positive number."); + } + + if (durationMs <= 0) { + throw new RangeError("Duration must be a positive number."); + } + + if (volumePct < 0 || volumePct > 1) { + throw new RangeError("Volume must be between 0 and 1."); + } + + audioContext ??= new AudioContext(); + + const oscillatorNode = audioContext.createOscillator(); + oscillatorNode.frequency.setValueAtTime(frequency, audioContext.currentTime); + + const gainNode = audioContext.createGain(); + gainNode.gain.setValueAtTime(volumePct, audioContext.currentTime); + + // Creates the smooth fades-in / fade-out sound effect (Avoids the popping sounds) + gainNode.gain.linearRampToValueAtTime( + volumePct, + audioContext.currentTime + FADE_DURATION, + ); + gainNode.gain.linearRampToValueAtTime( + 0, + audioContext.currentTime + durationMs / MS_IN_SEC - FADE_DURATION, + ); + + oscillatorNode.connect(gainNode); + gainNode.connect(audioContext.destination); + + oscillatorNode.start(); + oscillatorNode.stop(audioContext.currentTime + durationMs / MS_IN_SEC); +}