Grok bot君はテクノ作れる

275文字1分で読めます

Grok bot君に「君ミニマルテクノ作れる?」って適当になげてみたら普通にまともなのをすぐ出してきた。しかもSuno AIとかで生成したわけでなくPythonのコード生成で作ってる。

minimal_125bpm_loop
import numpy as np
from scipy.signal import butter, sosfilt, sosfiltfilt
from scipy.io import wavfile
SR=44100; BPM=125; BARS=16
beat=60/BPM; s16=beat/4
N=int(round(BARS*4*beat*SR))
rng=np.random.default_rng(7)
L=np.zeros(N); R=np.zeros(N)
def place(buf,sig,start):
    idx=(np.arange(len(sig))+start)%N   # wrap tails to loop start -> seamless
    np.add.at(buf,idx,sig)
def pos(step): return int(round(step*s16*SR))
def env_exp(n,tau): return np.exp(-np.arange(n)/(tau*SR))
def fade(sig,ms=2):
    k=int(ms*SR/1000); sig[:k]*=np.linspace(0,1,k); sig[-k:]*=np.linspace(1,0,k); return sig
# global 16-bar filter LFO (cosine -> continuous over loop)
def lfo(start): ph=start/N; return 0.5-0.5*np.cos(2*np.pi*ph)
TOT=BARS*16
# KICK
n=int(0.45*SR); t=np.arange(n)/SR
f=45+110*np.exp(-t/0.035)
kick=np.sin(2*np.pi*np.cumsum(f)/SR)*env_exp(n,0.18)
kick=np.tanh(1.6*kick)
click=rng.standard_normal(int(0.004*SR))*env_exp(int(0.004*SR),0.0008)
click=sosfilt(butter(2,2000,'hp',fs=SR,output='sos'),click)
kick[:len(click)]+=0.35*click
kick=fade(kick)*0.95
kick_steps=[s for s in range(TOT) if s%4==0]
for s in kick_steps: place(L,kick,pos(s)); place(R,kick,pos(s))
# sidechain envelope (circular)
sc=np.ones(N)
dn=int(0.22*SR); d=1-0.6*np.exp(-np.arange(dn)/(0.07*SR))
for s in kick_steps:
    idx=(np.arange(dn)+pos(s))%N; sc[idx]=np.minimum(sc[idx],d)
# HATS
hp_sos=butter(4,7000,'hp',fs=SR,output='sos')
def hat(dur,tau):
    m=int(dur*SR); x=sosfilt(hp_sos,rng.standard_normal(m))*env_exp(m,tau); return fade(x,1)
for s in range(TOT):
    if s%4==2:
        v=0.16*(0.8+0.25*rng.random()); h=hat(0.06,0.012)*v
        place(L,h*0.9,pos(s)); place(R,h*1.1,pos(s))
    elif s%2==1 and rng.random()<0.35:   # ghost 16ths
        h=hat(0.03,0.006)*0.05*(0.7+0.5*rng.random())
        pan=rng.random(); place(L,h*(1-pan)*1.4,pos(s)); place(R,h*pan*1.4,pos(s))
# CLAP on 2 & 4 (subtle) + open hat accents in 2nd half
bp=butter(2,[900,2600],'bp',fs=SR,output='sos')
m=int(0.25*SR); noise=sosfilt(bp,rng.standard_normal(m))
envc=np.zeros(m)
for o in [0,0.011,0.022]:
    k=int(o*SR); envc[k:]+=env_exp(m-k,0.008 if o<0.02 else 0.07)
clap=fade(noise*envc)*0.09
for s in range(TOT):
    if s%16 in (4,12):
        place(L,clap*1.05,pos(s)+int(0.002*SR)); place(R,clap*0.95,pos(s))
oh=hat(0.25,0.07)*0.06
for s in range(TOT):
    if s>=128 and s%8==6: place(L,oh,pos(s)); place(R,oh,pos(s))
# BASS (A minor), filtered saw+sine, sidechained
A1=55.0
semis={'A':0,'C':3,'D':5,'E':7,'G':10}
pattern=[(2,'A',0),(3,'A',0),(6,'A',12),(7,'A',0),(10,'C',0),(11,'A',0),(14,'E',-12),(15,'A',0)]
pattern_b=[(2,'A',0),(3,'A',0),(6,'G',0),(7,'A',0),(10,'C',0),(11,'D',0),(14,'E',0),(15,'A',12)]
bass=np.zeros(N)
for bar in range(BARS):
    pat=pattern_b if bar%4==3 else pattern
    for st,nn,oc in pat:
        start=pos(bar*16+st); m=int(s16*SR*0.85)
        fr=A1*2**((semis[nn]+oc)/12); tt=np.arange(m)/SR
        ph=(fr*tt)%1; saw=2*ph-1
        x=0.6*saw+0.7*np.sin(2*np.pi*fr*tt)
        cut=180+900*lfo(start)+1200*np.exp(-tt/0.03)*(0.5+lfo(start))
        # time-varying one-pole lowpass x2
        a=np.exp(-2*np.pi*cut/SR); y=np.zeros(m); z1=z2=0.0
        for i in range(m):
            z1=(1-a[i])*x[i]+a[i]*z1; z2=(1-a[i])*z1+a[i]*z2; y[i]=z2
        y=fade(y*env_exp(m,0.09),3)*0.42
        place(bass,y,start)
bass*=sc
L+=bass; R+=bass
# STAB/BLIP with ping-pong delay (circular)
stabL=np.zeros(N); stabR=np.zeros(N)
chord=[220.0*2**(k/12) for k in (0,3,7,10)]  # Am7
stab_steps=[(b*16+s) for b in range(BARS) for s in ([3] if b%2==0 else [11,14]) ]
for s in stab_steps:
    start=pos(s); m=int(0.12*SR); tt=np.arange(m)/SR
    x=sum(np.sign(np.sin(2*np.pi*f0*tt))*0.5+np.sin(2*np.pi*f0*2*tt)*0.5 for f0 in chord)/4
    cut=600+3500*lfo(start)
    x=sosfiltfilt(butter(2,[cut*0.5,cut],'bp',fs=SR,output='sos'),x)
    x=fade(x*env_exp(m,0.035),2)*0.25
    place(stabL,x,start); place(stabR,x,start)
dl=int(round(3*s16*SR)); fb=0.42
dryL,dryR=stabL.copy(),stabR.copy(); wl=np.zeros(N); wr=np.zeros(N)
srcL,srcR=dryL,dryR
for k in range(1,8):
    srcL,srcR=np.roll(srcR,dl)*fb,np.roll(srcL,dl)*fb   # ping-pong
    lp=butter(1,3000,'lp',fs=SR,output='sos')
    wl+=srcL; wr+=srcR
L+=dryL+0.8*wl; R+=dryR+0.8*wr
# subtle stereo bed: very quiet filtered noise swell for evolution (loop-periodic)
mix=np.stack([L,R],1)
# master: gentle soft clip then normalize to -1 dBFS
mix=np.tanh(mix*1.1)/1.1
peak=np.abs(mix).max(); mix*=10**(-1/20)/peak
assert np.isfinite(mix).all()
out=np.clip(np.round(mix*32767),-32768,32767).astype(np.int16)
wavfile.write('/workspace/techno/minimal_125bpm_loop.wav',SR,out)
print('samples',N,'dur',N/SR)

やってるのはSonic Piみたいなライブコーディングとおんなじような感じで、音声波形を計算して鳴らしているだけではあるが、思ったよりかなりちゃんと作ってきて驚く。

もうちょっと作り込みできるか試してみる予定。