Skip to content
Merged
16 changes: 6 additions & 10 deletions workspaces/simon-game/src/app/components/App.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div>
<Box color="red" />
<Box
color={
// cobalt blue
"#0050B5"
}
/>
<Box color="green" />
<Box color="yellow" />
<div style={{ display: "flex", gap: 10 }}>
{config.boxes.map((box) => (
<Box color={box.color} onClick={() => playNote(box.frequency)} />
))}
</div>
);
}
22 changes: 16 additions & 6 deletions workspaces/simon-game/src/app/components/Box.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<div
<button
style={{
backgroundColor: props.color ?? "lightblue",
height: props.height ?? 100,
width: props.width ?? 100,
backgroundColor: color,
height,
width,
borderRadius: 20, // rounded corners
margin: props.margin, // spaces between boxes
margin, // spaces between boxes
cursor: "pointer",
border: 0,
}}
onClick={onClick}
/>
);
}
22 changes: 22 additions & 0 deletions workspaces/simon-game/src/app/constants.ts
Original file line number Diff line number Diff line change
@@ -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,
},
],
};
6 changes: 6 additions & 0 deletions workspaces/simon-game/src/app/util/playNote.ts
Original file line number Diff line number Diff line change
@@ -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);
}
55 changes: 55 additions & 0 deletions workspaces/simon-game/src/app/util/playSound.ts
Original file line number Diff line number Diff line change
@@ -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);
}
Loading