#!/usr/bin/env python3 import wave, math from pathlib import Path import numpy as np SR=48000; DURATION=76.0; BPM=100; BEAT=60/BPM rng=np.random.default_rng(118) t=np.arange(int(SR*DURATION))/SR left=np.zeros_like(t); right=np.zeros_like(t) def add(buf,start,sound,gain=1.0): i=int(start*SR); j=min(len(buf),i+len(sound)); if i<0 or i>=len(buf): return buf[i:j]+=sound[:j-i]*gain def kick(): d=.24; x=np.arange(int(SR*d))/SR; phase=2*np.pi*(72*x + (88-72)*(1-np.exp(-x*28))/28) return np.sin(phase)*np.exp(-x*18) def snare(): d=.20; x=np.arange(int(SR*d))/SR; n=rng.normal(0,1,len(x)); tone=np.sin(2*np.pi*190*x) return (.75*n+.25*tone)*np.exp(-x*20) def hat(openhat=False): d=.17 if openhat else .055; x=np.arange(int(SR*d))/SR; n=rng.normal(0,1,len(x)); n=np.concatenate([[0],np.diff(n)]); return n*np.exp(-x*(20 if openhat else 60))*.35 def pluck(freq,d=.34): x=np.arange(int(SR*d))/SR; sig=(np.sin(2*np.pi*freq*x)+.35*np.sin(2*np.pi*2*freq*x)+.12*np.sin(2*np.pi*3*freq*x)); return sig*np.exp(-x*8)*.25 def bass(freq,d=.48): x=np.arange(int(SR*d))/SR; env=(1-np.exp(-x*30))*np.exp(-x*3.8); sig=np.sin(2*np.pi*freq*x)+.24*np.sin(2*np.pi*2*freq*x) return sig*env*.32 def pad(freq,d): x=np.arange(int(SR*d))/SR; env=np.minimum(1,x/.45)*np.minimum(1,(d-x)/.6); env=np.maximum(env,0) return (np.sin(2*np.pi*freq*x)+.22*np.sin(2*np.pi*(freq*1.003)*x))*env*.055 K=kick(); S=snare(); H=hat(); OH=hat(True) roots=[65.41,51.91,77.78,58.27] # C2 Ab1 Eb2 Bb1 chords=[[261.63,311.13,392.00],[207.65,261.63,311.13],[155.56,196.00,233.08],[233.08,293.66,349.23]] # Four-bar phrases; energy opens gradually, drops under safety section, returns. for beat_i in range(int(DURATION/BEAT)+1): sec=beat_i*BEAT; bar=beat_i//4; pos=beat_i%4; section_gain=.72 if 31<=sec<43 else 1.0 add(left,sec,K,.58*section_gain); add(right,sec,K,.58*section_gain) if pos in (1,3): add(left,sec,S,.18*section_gain); add(right,sec,S,.22*section_gain) for sub in range(2): hs=sec+sub*BEAT/2; pan=.86 if (beat_i*2+sub)%2 else 1.0 add(left,hs,H,.10*pan*section_gain);add(right,hs,H,.10*(2-pan)*section_gain) if pos==3 and bar%2: add(left,sec+BEAT/2,OH,.07*section_gain);add(right,sec+BEAT/2,OH,.11*section_gain) root=roots[(bar//2)%4] if pos in (0,2) or (pos==3 and bar%2): b=bass(root*(2 if pos==2 else 1));add(left,sec,b,.75*section_gain);add(right,sec,b,.70*section_gain) # pads every 2 bars for bar in range(int(DURATION/(BEAT*4))+1): if bar%2: continue start=bar*4*BEAT; chord=chords[(bar//2)%4] for idx,f in enumerate(chord): p=pad(f,8*BEAT);add(left,start,p,.78 if idx!=1 else .58);add(right,start,p,.58 if idx!=1 else .78) # syncopated bright plucks outside serious segment melody=[392,466.16,523.25,622.25,523.25,466.16,392,349.23] for i in range(int(DURATION/(BEAT/2))): sec=i*BEAT/2 if sec<7 or 31<=sec<43 or i%2==0: continue p=pluck(melody[(i//2)%len(melody)]) add(left,sec,p,.50 if i%4==1 else .30);add(right,sec,p,.30 if i%4==1 else .50) # subtle stereo width and fade fade=np.ones_like(t);fade[:SR*2]=np.linspace(0,1,SR*2);fade[-SR*3:]=np.linspace(1,0,SR*3) left*=fade;right*=fade peak=max(np.max(np.abs(left)),np.max(np.abs(right)));left=np.tanh(left*1.25)/(max(1,peak)*1.15);right=np.tanh(right*1.25)/(max(1,peak)*1.15) stereo=np.stack([left,right],axis=1);pcm=(np.clip(stereo,-1,1)*32767).astype('