const RECORDING_URL=x=>`assets/audio/${x}.mp3`,BEATS_URL="assets/audio/beats.json",FADE=.015,LOOKAHEAD=.15;class HeartAudio{constructor(){this.ctx=null,this.master=null,this.noiseBuf=null,this.enabled=!1,this.lastTriggerKey=null,this.murmurNode=null,this.murmurKind=null,this.stateId="normal",this.site="mitral",this.beatReq=null,this.beatTable=null,this.buffers={},this.live=new Set,this.done=new Set,this.beatIdx=-1,this.lastPos=null,this.last=null,this.log=[]}setState(t){this.stateId=t,this.stopMurmur(),this.stopSlices(),this.done.clear(),this.beatIdx=-1,this.enabled&&this._load(t)}setSite(t){this.site=t}_src(t){const e=typeof STATES<"u"&&STATES[t];return e&&e.audio||t}_ag(){const t=typeof STATES<"u"&&STATES[this.stateId];return t&&t.audioGain||1}_load(t){this.ensure();const e=this._src(t);this.beatReq||(this.beatReq=fetch(BEATS_URL,{cache:"no-cache"}).then(s=>s.ok?s.json():{}).catch(()=>({})).then(s=>{this.beatTable=s})),!this.buffers[e]&&(this.buffers[e]="loading",fetch(RECORDING_URL(e),{cache:"no-cache"}).then(s=>{if(!s.ok)throw new Error(String(s.status));return s.arrayBuffer()}).then(s=>this.ctx.decodeAudioData(s)).then(s=>this.beatReq.then(()=>{const a=this.beatTable[e];a&&s.duration+.5<a.duration?(console.warn(`[\u5FC3\u97F3] ${e}.mp3 \u53EA\u6709 ${s.duration.toFixed(1)} s\uFF0C\u8282\u62CD\u8868\u6309 ${a.duration} s \u6807\u7684 \u2014\u2014 \u5F55\u97F3\u548C beats.json \u4E0D\u662F\u4E00\u5957\uFF0C\u6539\u7528\u5408\u6210\u97F3`),this.buffers[e]="failed"):this.buffers[e]=s})).catch(()=>{this.buffers[e]="failed"}))}_mode(){const t=this._src(this.stateId),e=this.buffers[t];if(e==="failed")return"synth";if(!this.beatTable||e==="loading"||!e)return"wait";const s=this.beatTable[t];return s&&s.s2&&s.s2.length?"rec":"synth"}ensure(){if(this.ctx)return;const t=window.AudioContext||window.webkitAudioContext;this.ctx=new t,this.master=this.ctx.createGain(),this.master.gain.value=.55,this.master.connect(this.ctx.destination);const e=this.ctx.sampleRate*2,s=this.ctx.createBuffer(1,e,this.ctx.sampleRate),a=s.getChannelData(0);for(let i=0;i<e;i++)a[i]=Math.random()*2-1;this.noiseBuf=s}resume(){this.ensure(),this.ctx.state==="suspended"&&this.ctx.resume()}setEnabled(t){this.enabled=t,t?(this.resume(),this._load(this.stateId)):(this.stopMurmur(),this.stopSlices(),this.done.clear())}update(t,e,s,a,i=.15){if(!this.enabled||!this.ctx)return;if(!s){this.stopSlices(),this.stopMurmur(),this.done.clear(),this.lastTriggerKey=null,this.lastPos=null;return}const n=a+t;if(this.lastPos!=null&&n<this.lastPos-1e-6&&(this.stopSlices(),this.done.clear()),this.lastPos=n,this._mode()!=="rec")return;const r=this.ctx.currentTime;for(const[h,f]of[["S1",PHASE_BOUNDS[1]],["S2",PHASE_BOUNDS[4]]]){let u=f-t;u>.5?u-=1:u<=-.5&&(u+=1);const g=u*e;if(g>i||g<-.05)continue;const o=Math.round(a+t+u-f)+":"+h;this.done.has(o)||(this.done.add(o),this.done.size>32&&this.done.delete(this.done.values().next().value),this._slice(h,r+g))}}_slice(t,e){const s=this._src(this.stateId),a=this.buffers[s],i=this.beatTable[s],n=i.s2.length,r=i.preRoll;(t==="S1"||this.beatIdx<0)&&(this.beatIdx=(this.beatIdx+1)%n);const h=this.beatIdx,f=(t==="S1"?i.s1[h]:i.s2[h])-r,u=(t==="S1"?i.s2[h]:i.s1[h+1])-r,g=this.ctx.currentTime,o=Math.max(g,e-r),T=Math.min(o-(e-r),r),R=Math.max(.03,u-f-T),S=o+R,l=this.ctx.createBufferSource();l.buffer=a;const p=typeof auscSliceGain=="function"?auscSliceGain(this.stateId,this.site,t):{gain:1,lp:0},b=p.gain*this._ag(),c=this.ctx.createGain();if(c.gain.setValueAtTime(0,o),c.gain.linearRampToValueAtTime(b,o+.005),c.gain.setValueAtTime(b,Math.max(o+.005,S-.015)),c.gain.linearRampToValueAtTime(0,S),p.lp>0){const m=this.ctx.createBiquadFilter();m.type="lowpass",m.frequency.value=p.lp,m.Q.value=-3.01,l.connect(m),m.connect(c)}else l.connect(c);c.connect(this.master),l.start(o,f+T,R+.005);const d=this.last;if(d&&o<d.end-.015){try{d.g.gain.cancelScheduledValues(o),d.g.gain.setTargetAtTime(0,o,.015/3),d.src.stop(o+2*.015)}catch{}d.end=o+2*.015}const A={src:l,g:c,when:o,end:S};this.live.add(A),l.onended=()=>this.live.delete(A),this.last=A,this.log.push({kind:t,id:this.stateId,beat:h,when:o,peakAt:o+r-T,site:this.site,gain:b,lp:p.lp}),this.log.length>400&&this.log.shift()}stopSlices(){if(!this.ctx)return;const t=this.ctx.currentTime;for(const e of this.live)try{if(e.when>t){e.src.stop();continue}e.g.gain.cancelScheduledValues(t),e.g.gain.setValueAtTime(e.g.gain.value,t),e.g.gain.linearRampToValueAtTime(0,t+.015),e.src.stop(t+.015+.005)}catch{}this.live.clear(),this.last=null}liveCount(){return this.live.size}tone(t,e,s=.9){if(!this.enabled)return;this.ensure();const a=this.ctx.currentTime,i=this.ctx.createOscillator();i.type="sine",i.frequency.value=t;const n=this.ctx.createGain();n.gain.setValueAtTime(0,a),n.gain.linearRampToValueAtTime(s,a+.01),n.gain.linearRampToValueAtTime(s*.5,a+.03),n.gain.linearRampToValueAtTime(s*.4,a+e*.6),n.gain.linearRampToValueAtTime(0,a+e);const r=this.ctx.createBiquadFilter();r.type="lowpass",r.frequency.value=t*5,i.connect(r),r.connect(n),n.connect(this.master),i.start(a),i.stop(a+e+.05)}S1(t=1){this.tone(50,.13,.95*t)}S2(t=1){this.tone(70,.1,.85*t)}S3(t=1){this.tone(40,.08,.55*t)}S4(t=1){this.tone(35,.08,.55*t)}OS(t=1){this.tone(160,.035,.5*t)}startMurmur(t,e=1){if(!this.enabled||(this.ensure(),this.murmurNode&&this.murmurKind===t.kind))return;this.stopMurmur();const s=this.ctx.currentTime,a=this.ctx.createBufferSource();a.buffer=this.noiseBuf,a.loop=!0;const i=this.ctx.createBiquadFilter();i.type="bandpass",i.Q.value=t.q,i.frequency.value=t.freq;const n=this.ctx.createGain();n.gain.setValueAtTime(1e-4,s),t.shape==="rumble"?n.gain.linearRampToValueAtTime(.2*e,s+.06):t.shape==="decrescendo"?(n.gain.linearRampToValueAtTime(.18*e,s+.02),n.gain.exponentialRampToValueAtTime(.005*e,s+.45)):t.shape==="holo"?n.gain.linearRampToValueAtTime(.16*e,s+.03):(n.gain.linearRampToValueAtTime(.06*e,s+.02),n.gain.linearRampToValueAtTime(.22*e,s+.13),n.gain.linearRampToValueAtTime(.05*e,s+.28)),a.connect(i),i.connect(n),n.connect(this.master),a.start(),this.murmurNode={src:a,g:n},this.murmurKind=t.kind}stopMurmur(){if(this.murmurNode){try{const t=this.ctx.currentTime;this.murmurNode.g.gain.cancelScheduledValues(t),this.murmurNode.g.gain.setValueAtTime(this.murmurNode.g.gain.value,t),this.murmurNode.g.gain.linearRampToValueAtTime(0,t+.05),this.murmurNode.src.stop(t+.06)}catch{}this.murmurNode=null,this.murmurKind=null}}triggerPhaseEvents(t,e,s){if(!this.enabled||this._mode()!=="synth"||s===this.lastTriggerKey)return;this.lastTriggerKey=s;const a=typeof auscSynthGain=="function"?auscSynthGain(e.id,this.site):{},i=h=>((e.soundGain&&e.soundGain[h])??1)*(a[h]??1),n=h=>e.sounds.includes(h);t===1&&this.S1(i("S1")),t===4&&this.S2(i("S2")),t===5&&(n("OS")&&this.OS(i("OS")),n("S3")&&this.S3(i("S3"))),t===0&&n("S4")&&this.S4(i("S4"));const r=e.murmur;r&&r.phases.includes(t)?this.startMurmur(r,a.murmur??1):this.stopMurmur()}dispose(){if(this.stopSlices(),this.stopMurmur(),this.ctx)try{this.ctx.close()}catch{}this.ctx=null}}window.HeartAudio=HeartAudio;
