import { type InstrumentType, type PlayTuneRes, type Tune, instruments, tones } from '../api.js' export function playFrequency(frequency: number, duration: number, instrument: InstrumentType, ctx: AudioContext, dest: AudioNode) { const osc = ctx.createOscillator() const rampGain = ctx.createGain() osc.connect(rampGain) rampGain.connect(dest) osc.frequency.value = frequency osc.type = instrument ?? 'sine' osc.start() const endTime = ctx.currentTime + duration*2/1000 osc.stop(endTime) rampGain.gain.setValueAtTime(0, ctx.currentTime) rampGain.gain.linearRampToValueAtTime(.2, ctx.currentTime + duration/5/1000) rampGain.gain.exponentialRampToValueAtTime(0.00001, ctx.currentTime + duration/1000) rampGain.gain.linearRampToValueAtTime(0, ctx.currentTime + duration*2/1000) // does this ramp from the last ramp osc.onended = () => { osc.disconnect() rampGain.disconnect() } } const sleep = async (duration: number) => new Promise(resolve => setTimeout(resolve, duration)) export async function playTuneHelper(tune: Tune, number: number, playingRef: { playing: boolean }, ctx: AudioContext, dest: AudioNode) { for (let i = 0; i < tune.length*number; i++) { const index = i%tune.length if (!playingRef.playing) break const noteSet = tune[index]! const sleepTime = noteSet[0] for (let j = 1; j < noteSet.length; j += 3) { const instrument = noteSet[j] as InstrumentType const note = noteSet[j+1]! const duration = noteSet[j+2] as number const frequency = typeof note === 'string' ? tones[note.toUpperCase()] : 2**((note-69)/12)*440 if (instruments.includes(instrument) && frequency !== undefined) playFrequency(frequency, duration, instrument, ctx, dest) } await sleep(sleepTime) } } let audioCtx: AudioContext | null = null export function playTune(tune: Tune, number = 1): PlayTuneRes { const playingRef = { playing: true } if (audioCtx === null) audioCtx = new AudioContext() playTuneHelper(tune, number, playingRef, audioCtx, audioCtx.destination) return { end() { playingRef.playing = false }, isPlaying() { return playingRef.playing } } }