important feature: use Web Audio API to get microwave sounds just right.

This commit is contained in:
Joeri Exelmans 2025-10-25 19:27:39 +02:00
parent a5afcd5908
commit f3a9656891
3 changed files with 47 additions and 21 deletions

View file

@ -399,7 +399,7 @@ export function App() {
flex: '0 0 content', flex: '0 0 content',
overflowY: "auto", overflowY: "auto",
overflowX: "visible", overflowX: "visible",
maxWidth: '50vw', maxWidth: 'min(400px,50vw)',
}}> }}>
<div className="stackVertical" style={{height:'100%'}}> <div className="stackVertical" style={{height:'100%'}}>
<div <div

View file

@ -12,3 +12,7 @@ rect.microwaveButtonHelper:active {
fill: red; fill: red;
fill-opacity: 0.5; fill-opacity: 0.5;
} }
rect.microwaveDoorHelper {
fill-opacity: 0;
}

View file

@ -10,7 +10,7 @@ import sndBell from "./bell.wav";
import sndRunning from "./running.wav"; import sndRunning from "./running.wav";
import { Plant } from "../Plant"; import { Plant } from "../Plant";
import { RaisedEvent } from "@/statecharts/runtime_types"; import { RaisedEvent } from "@/statecharts/runtime_types";
import { useEffect, useState } from "react"; import { useEffect, useRef, useState } from "react";
import "./Microwave.css"; import "./Microwave.css";
@ -82,23 +82,54 @@ const DOOR_Y0 = 68;
const DOOR_WIDTH = 353; const DOOR_WIDTH = 353;
const DOOR_HEIGHT = 217; const DOOR_HEIGHT = 217;
const ctx = new AudioContext();
function fetchAudioBuffer(url: string): Promise<AudioBuffer> {
return fetch(url).then(res => {
return res.arrayBuffer();
}).then(buf => {
return ctx.decodeAudioData(buf);
});
}
function playAudioBufer(buf: AudioBuffer, loop: boolean) {
const src = ctx.createBufferSource();
src.buffer = buf;
src.connect(ctx.destination);
if (loop) src.loop = true;
src.start();
return () => src.stop();
}
export function Magnetron({state: {timeDisplay, bell, magnetron}, callbacks}: MicrowaveProps) { export function Magnetron({state: {timeDisplay, bell, magnetron}, callbacks}: MicrowaveProps) {
const [door, setDoor] = useState<DoorState>("closed"); const [door, setDoor] = useState<DoorState>("closed");
const [playBell, setPlayBell] = useState(false);
// a bit hacky: when the bell-state changes to true, we play the bell sound for 610 ms... const bufRunningPromise = useRef(fetchAudioBuffer(sndRunning));
const bufBellPromise = useRef(fetchAudioBuffer(sndBell));
const refSndBell = useRef<HTMLAudioElement>(null);
const refSndRunning = useRef<HTMLAudioElement>(null);
// a bit hacky: when the bell-state changes to true, we play the bell sound...
useEffect(() => { useEffect(() => {
let timeout: NodeJS.Timeout;
if (bell) { if (bell) {
setPlayBell(true); bufBellPromise.current.then(buf => {
timeout = setTimeout(() => { playAudioBufer(buf, false);
setPlayBell(false); })
}, 610);
} }
return () => { if (timeout) clearTimeout(timeout); };
}, [bell]); }, [bell]);
useEffect(() => {
if (magnetron === "on") {
const stop = bufRunningPromise.current.then(buf => {
return playAudioBufer(buf, true);
});
return () => stop.then(stop => stop());
}
return () => {};
}, [magnetron])
preload(imgSmallClosedOff, {as: "image"}); preload(imgSmallClosedOff, {as: "image"});
preload(imgSmallClosedOn, {as: "image"}); preload(imgSmallClosedOn, {as: "image"});
preload(imgSmallOpenedOff, {as: "image"}); preload(imgSmallOpenedOff, {as: "image"});
@ -122,7 +153,7 @@ export function Magnetron({state: {timeDisplay, bell, magnetron}, callbacks}: Mi
src: url(${fontDigital}); src: url(${fontDigital});
} }
`}</style> `}</style>
<svg width={520} height={348}> <svg style={{maxWidth: 520}} width='100%' height='auto' viewBox="0 0 520 348">
<image xlinkHref={imgs[door][magnetron]} width={520} height={348}/> <image xlinkHref={imgs[door][magnetron]} width={520} height={348}/>
<rect className="microwaveButtonHelper" x={START_X0} y={START_Y0} width={BUTTON_WIDTH} height={BUTTON_HEIGHT} <rect className="microwaveButtonHelper" x={START_X0} y={START_Y0} width={BUTTON_WIDTH} height={BUTTON_HEIGHT}
@ -135,20 +166,11 @@ export function Magnetron({state: {timeDisplay, bell, magnetron}, callbacks}: Mi
onMouseDown={() => callbacks.incTimePressed()} onMouseDown={() => callbacks.incTimePressed()}
onMouseUp={() => callbacks.incTimeReleased()} onMouseUp={() => callbacks.incTimeReleased()}
/> />
<rect className="microwaveButtonHelper" x={DOOR_X0} y={DOOR_Y0} width={DOOR_WIDTH} height={DOOR_HEIGHT} <rect className="microwaveDoorHelper" x={DOOR_X0} y={DOOR_Y0} width={DOOR_WIDTH} height={DOOR_HEIGHT} onMouseDown={() => door === "open" ? closeDoor() : openDoor()}
onMouseDown={() => door === "open" ? closeDoor() : openDoor()}
/> />
<text x={472} y={106} textAnchor="end" fontFamily="digital-font" fontSize={24} fill="lightgreen">{timeDisplay}</text> <text x={472} y={106} textAnchor="end" fontFamily="digital-font" fontSize={24} fill="lightgreen">{timeDisplay}</text>
</svg> </svg>
{magnetron === "on" && <audio hidden autoPlay loop>
<source src={sndRunning} type="audio/wav"/>
</audio>}
{playBell && <audio hidden autoPlay>
<source src={sndBell} type="audio/wav"/>
</audio>}
</>; </>;
} }