from pathlib import Path
import json,math,wave
import numpy as np
from scipy.signal import butter,sosfilt
P=Path(__file__).parent;S=json.loads((P/'cache/scene.json').read_text());sr=48000;dur=S['duration'];n=int(dur*sr);rng=np.random.default_rng(424)
a=np.zeros((n,2),dtype=np.float32)
def add(start,x,gain=.1,pan=0):
 i=int(start*sr)
 if i>=n:return
 x=np.asarray(x,dtype=np.float32)[:n-i]*gain;a[i:i+len(x),0]+=x*math.sqrt((1-pan)/2);a[i:i+len(x),1]+=x*math.sqrt((1+pan)/2)
def tone(freq,d,amp=1):
 t=np.arange(int(d*sr))/sr;return (np.sin(2*np.pi*freq*t)+.18*np.sin(2*np.pi*freq*2*t))*amp
# Restrained original electronic score, entirely synthesized.
for start in np.arange(0,dur,10):
 d=min(12,dur-start);t=np.arange(int(d*sr))/sr;env=np.minimum(1,t/2)*np.minimum(1,(d-t)/2)
 chord=[146.832,174.614,220.0] if int(start/10)%4 in [0,3] else [116.54,174.614,233.08]
 for j,f in enumerate(chord):add(start,tone(f,d)*env*(.75+.25*np.sin(t*.4+j)),.013,(j-1)*.65)
# Intro and reveal accents.
for start in [1.7,4.2,8.7,12.8]:
 d=2.4;t=np.arange(int(d*sr))/sr;env=np.exp(-t*2);x=np.sin(2*np.pi*(44*t+23*(1-np.exp(-t*8))))*env;add(start,x,.13)
 noise=sosfilt(butter(2,[800,4500],btype='bandpass',fs=sr,output='sos'),rng.normal(0,1,len(t)));add(start,noise*np.exp(-t*3),.018,.25)
beat=.625
for i,start in enumerate(np.arange(13,dur-7,beat)):
 t=np.arange(int(.4*sr))/sr;env=np.exp(-t*13)
 if i%2==0:add(start,np.sin(2*np.pi*(44*t+6*(1-np.exp(-t*24))))*env,.115)
 root=[73.416,58.27,87.307,65.406][(i//16)%4];add(start,tone(root,.4)*np.exp(-t*8),.053)
 h=np.arange(int(.085*sr))/sr;noise=rng.normal(0,1,len(h));noise=np.concatenate([[0],np.diff(noise)])*.25;add(start+.3125,noise*np.exp(-h*60),.013,(-1 if i%2 else 1)*.45)
 if i%4==2:
  t=np.arange(int(.15*sr))/sr;noise=sosfilt(butter(2,[1000,5000],btype='bandpass',fs=sr,output='sos'),rng.normal(0,1,len(t)));add(start,noise*np.exp(-t*28),.027)
# Soft scanner punctuation only at actual editorial boundaries.
for s in S['segments'][1:]:
 d=.32;t=np.arange(int(d*sr))/sr;x=np.sin(2*np.pi*(350*t+500*t*t))*np.sin(np.pi*t/d)**2;add(s['start']-.08,x,.009,.3)
t=np.arange(n)/sr;env=np.minimum(1,t/2)*np.minimum(1,(dur-t)/2);a*=env[:,None];peak=np.max(np.abs(a));a*=min(1,.65/peak)
with wave.open(str(P/'cache/score.wav'),'wb') as w:w.setnchannels(2);w.setsampwidth(2);w.setframerate(sr);w.writeframes((np.clip(a,-1,1)*32767).astype('<i2').tobytes())
print('score',dur,'peak',peak)
