| @@ -421,45 +421,12 @@ function FullscreenViewer({ | |||||
| ); | ); | ||||
| } | } | ||||
| function playFlipSound(ctx: AudioContext | null) { | |||||
| if (!ctx) return; | |||||
| const duration = 0.28; | |||||
| const sr = ctx.sampleRate; | |||||
| const buffer = ctx.createBuffer(1, Math.floor(sr * duration), sr); | |||||
| const data = buffer.getChannelData(0); | |||||
| for (let i = 0; i < data.length; i++) { | |||||
| const t = i / sr; | |||||
| const envelope = Math.exp(-t * 14) * (1 - Math.exp(-t * 80)); | |||||
| data[i] = (Math.random() * 2 - 1) * envelope * 0.45; | |||||
| } | |||||
| const src = ctx.createBufferSource(); | |||||
| src.buffer = buffer; | |||||
| const bp = ctx.createBiquadFilter(); | |||||
| bp.type = 'bandpass'; | |||||
| bp.frequency.value = 2200; | |||||
| bp.Q.value = 0.9; | |||||
| const gain = ctx.createGain(); | |||||
| gain.gain.value = 0.6; | |||||
| src.connect(bp); bp.connect(gain); gain.connect(ctx.destination); | |||||
| src.start(); | |||||
| } | |||||
| function FlipBook({ pages, onClose }: { pages: string[]; onClose: () => void }) { | function FlipBook({ pages, onClose }: { pages: string[]; onClose: () => void }) { | ||||
| const containerRef = useRef<HTMLDivElement>(null); | const containerRef = useRef<HTMLDivElement>(null); | ||||
| const flipRef = useRef<import('@/lib/page-flip').PageFlip | null>(null); | const flipRef = useRef<import('@/lib/page-flip').PageFlip | null>(null); | ||||
| const audioRef = useRef<AudioContext | null>(null); | |||||
| const [currentPage, setCurrentPage] = useState(0); | const [currentPage, setCurrentPage] = useState(0); | ||||
| const [pageCount, setPageCount] = useState(pages.length); | const [pageCount, setPageCount] = useState(pages.length); | ||||
| const getCtx = () => { | |||||
| if (!audioRef.current) { | |||||
| const Ctx = (window as unknown as { AudioContext?: typeof AudioContext; webkitAudioContext?: typeof AudioContext }).AudioContext | |||||
| ?? (window as unknown as { AudioContext?: typeof AudioContext; webkitAudioContext?: typeof AudioContext }).webkitAudioContext; | |||||
| if (Ctx) audioRef.current = new Ctx(); | |||||
| } | |||||
| return audioRef.current; | |||||
| }; | |||||
| useEffect(() => { | useEffect(() => { | ||||
| if (!containerRef.current || pages.length === 0) return; | if (!containerRef.current || pages.length === 0) return; | ||||
| let cancelled = false; | let cancelled = false; | ||||
| @@ -510,7 +477,6 @@ function FlipBook({ pages, onClose }: { pages: string[]; onClose: () => void }) | |||||
| flip.on('flip', (e) => { | flip.on('flip', (e) => { | ||||
| const newPage = typeof e.data === 'number' ? e.data : 0; | const newPage = typeof e.data === 'number' ? e.data : 0; | ||||
| setCurrentPage(newPage); | setCurrentPage(newPage); | ||||
| playFlipSound(getCtx()); | |||||
| }); | }); | ||||
| flip.on('init', () => { | flip.on('init', () => { | ||||
| setPageCount(flip.getPageCount()); | setPageCount(flip.getPageCount()); | ||||