← back to Wallpaper Paint Antics

scripts/audio.py

63 lines

"""Original playful score and synchronized foley, synthesized locally (no samples)."""
import math, wave, struct, pathlib, random
RATE=48000
ROOT=pathlib.Path(__file__).resolve().parent.parent
def make(ep):
    a=[0.0]*(RATE*20)
    def tone(at,dur,freq,vol=.15,kind='pluck',end=None):
        start=int(at*RATE); length=int(dur*RATE)
        for i in range(length):
            j=start+i
            if j>=len(a): break
            t=i/RATE; u=i/max(1,length)
            fphase=2*math.pi*(freq*t+((end-freq)*t*t/(2*dur) if end else 0))
            if kind=='pluck': env=math.exp(-t*10)*min(1,t/.008); z=math.sin(fphase)+.25*math.sin(fphase*2)+.1*math.sin(fphase*3)
            elif kind=='bell': env=math.exp(-t*5)*min(1,t/.003);z=math.sin(fphase)+.27*math.sin(fphase*2.75)
            else: env=math.sin(math.pi*u)**1.3;z=math.sin(fphase)
            a[j]+=z*vol*env
    def noise(at,dur,vol,seed=0):
        rng=random.Random(seed);start=int(at*RATE);last=0
        for i in range(int(dur*RATE)):
            if start+i>=len(a):break
            last=.65*last+.35*rng.uniform(-1,1);u=i/(dur*RATE)
            a[start+i]+=last*vol*(1-u)**2
    midi=lambda n:440*2**((n-69)/12)
    beat=60/126
    chords=[[48,52,55,59],[53,57,60,64],[50,53,57,60],[55,59,62,65]]
    melody=[72,76,79,76,74,72,67,71,72,74,76,79,81,79,76,74]
    for k in range(42):
        t=k*beat
        if t>19.25:break
        chord=chords[(k//4+ep)%4]
        tone(t,.32,midi(chord[0]-(12 if k%2 else 0)),.13)
        if k%2:
            for n in chord[1:]:tone(t,.27,midi(n+12),.035)
        tone(t+.08,.22,midi(melody[(k+ep*3)%len(melody)]),.065)
        noise(t+.24,.035,.075,k)
        if k%2==0:tone(t,.09,110,.075,'sine',45)
    if ep==0:
        tone(3.1,.8,670,.17,'sine',130)
        for t in [5.6,7.9,9.15,10.5]:tone(t,.27,100,.23,'sine',380);noise(t,.09,.15,int(t*9))
        tone(11.3,.9,380,.13,'sine',760);tone(14.7,.4,770,.12,'sine',220)
        for n,t in zip([72,76,79,84],[15.5,15.65,15.8,16]):tone(t,.7,midi(n),.14,'bell')
    elif ep==1:
        for t in [1,1.7,2.4,3.1]:noise(t,.27,.28,int(t*10))
        tone(6.7,.45,330,.16,'sine',920);noise(7.4,.55,.9,33);tone(8.0,.35,145,.17,'sine',60)
        tone(10.4,.8,390,.1,'sine',305)
        for t,n in [(13.4,76),(14.1,79),(15.3,84)]:tone(t,.5,midi(n),.13,'bell')
    else:
        for t in [.1,1.1,2.1]:tone(t,.17,740,.13,'bell')
        tone(3,.35,1200,.15,'bell')
        for k in range(20):noise(3.2+k*.32,.1,.15,k)
        tone(10.2,.3,500,.1,'sine',220);tone(11.4,.4,250,.08,'sine',170)
        noise(17.68,.12,.7,44);tone(17.7,.5,900,.11,'bell')
    for n in [60,64,67,72]:tone(18.8,.95,midi(n),.065,'bell')
    peak=max(abs(x) for x in a);gain=.8/max(.8,peak)
    buf=bytearray()
    for i,z in enumerate(a):
        fade=min(1,i/(RATE*.025),(len(a)-i)/(RATE*.24));v=int(max(-1,min(1,z*gain*fade))*32767);buf.extend(struct.pack('<hh',v,v))
    out=ROOT/'assets'/f'short-{ep+1}.wav'
    with wave.open(str(out),'wb') as w:w.setnchannels(2);w.setsampwidth(2);w.setframerate(RATE);w.writeframes(buf)
    print(out.name,'20.000 seconds; peak',round(peak*gain,3))
for ep in range(3):make(ep)