JavaScript 65.5%
Python 17.8%
CSS 13%
HTML 3.7%
1#!/usr/bin/env python32"""Moteur musical procédural KA — compose un lit musical UNIQUE par reel.346 styles (house, lofi, synthwave, epic, funk, trap) × plusieurs progressions5d'accords × tonalité, ligne de basse, batterie et MÉLODIE générées à partir6d'une graine : deux reels n'ont jamais la même musique.78API : compose(dur, seed, out_wav, style=None) -> style utilisé9CLI : music_engine.py <dur> <graine> <out.wav> [style]10Sortie : WAV stéréo 44,1 kHz 16 bits (fondu d'entrée/sortie inclus).11"""12import sys, math, random, wave13import numpy as np1415SR = 441001617# ---------- filtres (magnitude ordre 1, via FFT — rapide et sans scipy) ----------18def _fftfilt(x, H_of_f):19 n = len(x)20 X = np.fft.rfft(x)21 f = np.fft.rfftfreq(n, 1.0/SR)22 return np.fft.irfft(X * H_of_f(f), n)2324def lowpass(x, fc):25 fc = max(30.0, fc)26 return _fftfilt(x, lambda f: 1.0/np.sqrt(1.0+(f/fc)**2))2728def highpass(x, fc):29 fc = max(30.0, fc)30 return _fftfilt(x, lambda f: (f/fc)/np.sqrt(1.0+(f/fc)**2))3132# ---------- oscillateurs / percussions ----------33def _t(n): return np.arange(n)/SR34def sine(f, n, ph=0.0): return np.sin(2*np.pi*f*_t(n)+ph)35def saw(f, n): return 2.0*((f*_t(n)) % 1.0)-1.036def tri(f, n): return 2.0*np.abs(2.0*((f*_t(n)) % 1.0)-1.0)-1.03738def expdec(n, tau): return np.exp(-_t(n)/max(1e-4, tau))3940def kick(dur=0.5, f0=150.0, f1=45.0, punch=1.0, rng=None):41 n = int(dur*SR); t = _t(n)42 f = f1+(f0-f1)*np.exp(-t*28.0)43 x = np.sin(2*np.pi*np.cumsum(f)/SR)*np.exp(-t*7.5)*punch44 nz = (rng or np.random).standard_normal(n)*np.exp(-t*400.0)45 return x + highpass(nz, 3500)*0.354647def sub808(f, dur, rng=None):48 n = int(dur*SR); t = _t(n)49 fr = f*(1.0+0.35*np.exp(-t*22.0)) # petit plongeon de hauteur50 x = np.sin(2*np.pi*np.cumsum(fr)/SR)51 x = np.tanh(x*2.2)*np.exp(-t*1.6)52 x[:int(0.004*SR)] *= np.linspace(0, 1, int(0.004*SR))53 return x5455def snare(dur=0.22, rng=None):56 n = int(dur*SR)57 nz = (rng or np.random).standard_normal(n)58 body = sine(190, n)*expdec(n, 0.035)*0.559 return highpass(nz, 1600)*expdec(n, 0.055) + body6061def clap(rng=None):62 n = int(0.30*SR)63 out = np.zeros(n)64 r = rng or np.random65 for i, off in enumerate((0.0, 0.012, 0.026)):66 o = int(off*SR); m = n-o67 out[o:] += highpass(r.standard_normal(m), 1200)*expdec(m, 0.05)*(0.8-0.15*i)68 return out6970def hat(dur=0.05, opened=False, rng=None):71 d = 0.30 if opened else dur72 n = int(max(d, 0.03)*SR)73 nz = (rng or np.random).standard_normal(n)74 return highpass(nz, 8200)*expdec(n, 0.11 if opened else 0.018)7576def bass_note(f, dur, kind="saw"):77 n = int(dur*SR)78 if kind == "sub":79 x = sine(f, n)+0.35*sine(2*f, n)80 else:81 x = lowpass(saw(f, n), f*4.0)*0.9 + sine(f, n)*0.582 e = np.minimum(1.0, _t(n)/0.006)*np.exp(-_t(n)/max(0.05, dur*0.7))83 return x*e8485def pluck(f, dur, bright=5.0):86 n = int(dur*SR)87 x = 0.6*saw(f, n)+0.4*tri(f, n)88 x = lowpass(x, f*bright)89 return x*expdec(n, max(0.04, dur*0.35))*np.minimum(1.0, _t(n)/0.003)9091def pad_chord(freqs, dur, detune=0.004, cutoff=1900.0):92 n = int(dur*SR)93 L = np.zeros(n); R = np.zeros(n)94 for f in freqs:95 L += saw(f*(1+detune), n)+0.5*sine(f, n)96 R += saw(f*(1-detune), n)+0.5*sine(f, n, ph=0.7)97 a = np.minimum(1.0, _t(n)/0.35)98 rel = np.minimum(1.0, (dur-_t(n))/0.25)99 e = a*np.clip(rel, 0, 1)100 return lowpass(L, cutoff)*e, lowpass(R, cutoff)*e101102# ---------- théorie : gammes, accords ----------103NOTE_HZ = {"F2": 87.31, "G2": 98.00, "A2": 110.00, "B2": 123.47, "C3": 130.81, "D3": 146.83, "E3": 164.81}104SCALES = {"minor": [0, 2, 3, 5, 7, 8, 10], "major": [0, 2, 4, 5, 7, 9, 11], "dorian": [0, 2, 3, 5, 7, 9, 10]}105106def deg_semi(scale, d):107 return scale[d % 7] + 12*(d//7)108109def chord_freqs(root_hz, scale, degree, spread=(0, 2, 4)):110 return [root_hz*2**(deg_semi(scale, degree+s)/12.0) for s in spread]111112# ---------- définition des styles ----------113# patterns = 16 pas (doubles-croches) par mesure 4/4 ; valeurs = vélocité114S = {115 "house": dict(116 bpm=(120, 126), scale="minor", roots=["A2", "G2", "F2", "C3"],117 progs=[[0, 5, 3, 4], [0, 5, 2, 6], [5, 3, 0, 4], [0, 2, 5, 6]],118 kick=[1, 0, 0, 0]*4, snare=None,119 clap=[0]*4+[1]+[0]*7+[1]+[0]*3,120 hats=[0, 0, .8, 0]*4, openhat=[0, 0, 1, 0]*4,121 bass="offbeat8", pad="sustain", mel_density=0.55, mel_oct=2,122 g=dict(kick=.95, clap=.5, hat=.35, bass=.62, pad=.30, mel=.42), swing=0.0),123 "lofi": dict(124 bpm=(78, 88), scale="dorian", roots=["F2", "G2", "A2"],125 progs=[[0, 3, 5, 4], [0, 2, 3, 4], [5, 4, 0, 3]],126 kick=[1]+[0]*6+[.7]+[0]*2+[.9]+[0]*5, snare=[0]*4+[.8]+[0]*7+[.8]+[0]*3, clap=None,127 hats=[.5, 0, .35, 0]*4, openhat=None,128 bass="half", pad="sustain", mel_density=0.35, mel_oct=1,129 g=dict(kick=.8, snare=.4, hat=.3, bass=.6, pad=.36, mel=.5), swing=0.12, vinyl=True),130 "synthwave": dict(131 bpm=(104, 112), scale="minor", roots=["A2", "C3", "G2"],132 progs=[[0, 5, 3, 6], [0, 6, 5, 4], [0, 3, 6, 4]],133 kick=[1, 0, 0, 0]*4, snare=[0]*4+[1]+[0]*7+[1]+[0]*3, clap=None,134 hats=[.35]*16, openhat=None,135 bass="arp16", pad="big", mel_density=0.4, mel_oct=2,136 g=dict(kick=.9, snare=.5, hat=.22, bass=.5, pad=.4, mel=.4), swing=0.0),137 "epic": dict(138 bpm=(96, 104), scale="minor", roots=["C3", "A2", "G2"],139 progs=[[0, 6, 3, 5], [0, 3, 6, 5], [0, 5, 6, 3]],140 kick=[1]+[0]*7+[0, 0, .9, 0]+[0]*4, snare=None, clap=[0]*12+[.6]+[0]*3,141 hats=None, openhat=None,142 bass="pulse8", pad="big", mel_density=0.45, mel_oct=2,143 g=dict(kick=.95, clap=.35, bass=.55, pad=.5, mel=.45), swing=0.0, swell=True),144 "funk": dict(145 bpm=(106, 114), scale="dorian", roots=["G2", "A2", "F2"],146 progs=[[0, 0, 3, 4], [0, 3, 0, 4], [0, 4, 3, 4]],147 kick=[1, 0, 0, 0, 0, 0, .8, 0, 0, .7, 0, 0, 1, 0, 0, 0],148 snare=[0]*4+[1]+[0]*7+[1]+[0, 0, .5], clap=None,149 hats=[.5, .2, .4, .2]*4, openhat=None,150 bass="synco", pad="stab", mel_density=0.5, mel_oct=1,151 g=dict(kick=.9, snare=.5, hat=.3, bass=.7, pad=.25, mel=.45), swing=0.08),152 "trap": dict(153 bpm=(136, 146), scale="minor", roots=["F2", "G2", "A2"],154 progs=[[0, 0, 5, 5], [0, 0, 6, 6], [0, 0, 3, 3]],155 kick=[1]+[0]*6+[.8]+[0]*8, snare=[0]*8+[1]+[0]*7, clap=None,156 hats=[.5, 0, .4, .4]*2+[.5, .3, .3, .3, .5, 0, .8, .4], openhat=None,157 bass="e808", pad="sustain", mel_density=0.3, mel_oct=2,158 g=dict(kick=.85, snare=.55, hat=.3, bass=.75, pad=.22, mel=.4), swing=0.0),159}160161# ---------- séquenceur ----------162def _place(dst, sig, i0, gain):163 if gain <= 0: return164 i1 = min(len(dst), i0+len(sig))165 if i1 <= i0: return166 dst[i0:i1] += sig[:i1-i0]*gain167168def _bass_events(kind, bar, rng):169 """-> liste (pas, longueur_en_pas, offset_degré, octave+)"""170 if kind == "offbeat8": return [(s, 2, 0, 0) for s in (2, 6, 10, 14)]171 if kind == "half": return [(0, 8, 0, 0), (8, 8, 0, 0)]172 if kind == "arp16": return [(s, 1, 0, (s % 4 == 2)) for s in range(16)]173 if kind == "pulse8": return [(s, 2, 0, 0) for s in range(0, 16, 2)]174 if kind == "e808": return [(0, 10, 0, 0), (10, 6, 0, 0)] if bar % 2 == 0 else [(0, 12, 0, 0), (13, 3, -2, 0)]175 if kind == "synco":176 pat = [(0, 2, 0, 0), (3, 1, 0, 1), (6, 2, 0, 0), (10, 1, 4, 0), (11, 1, 0, 1), (14, 2, 0, 0)]177 return pat if bar % 2 == 0 else pat[:-1]+[(14, 1, 0, 1), (15, 1, 0, 0)]178 return [(0, 4, 0, 0)]179180def _melody_bar(rng, density, prev_deg, chord_deg):181 """Rythme + hauteurs (degrés de gamme) pour une mesure. -> [(pas,long,deg)]"""182 events = []183 step = 0184 cur = prev_deg185 while step < 16:186 ln = rng.choice((2, 2, 2, 4, 4, 1, 3))187 if rng.random() < density:188 if rng.random() < 0.5: # note d'accord189 cur = chord_deg + rng.choice((0, 2, 4, 7))190 else: # marche par degrés191 cur = max(chord_deg-1, min(chord_deg+9, cur+rng.choice((-2, -1, -1, 1, 1, 2))))192 events.append((step, ln, cur))193 step += ln194 return events, cur195196def compose(dur, seed, out_wav, style=None):197 rng = random.Random(str(seed))198 nrng = np.random.RandomState(abs(hash(str(seed))) % (2**31))199 name = style if style in S else rng.choice(sorted(S.keys()))200 st = S[name]201 bpm = rng.uniform(*st["bpm"])202 spb = 60.0/bpm203 stepd = spb/4.0204 scale = SCALES[st["scale"]]205 root = NOTE_HZ[rng.choice(st["roots"])]206 prog = rng.choice(st["progs"])207 swing = st.get("swing", 0.0)208209 total = int((dur+0.2)*SR)210 L = np.zeros(total); R = np.zeros(total)211 g = st["g"]212213 # percussions pré-rendues214 kick_s = kick(rng=nrng)215 snare_s = snare(rng=nrng) if st.get("snare") else None216 clap_s = clap(rng=nrng) if st.get("clap") else None217 hat_c = hat(rng=nrng); hat_o = hat(opened=True, rng=nrng)218219 nbars = int(math.ceil(dur/(4*spb)))+1220 prev_deg = 7221 for bar in range(nbars):222 bar_t = bar*4*spb223 cdeg = prog[bar % len(prog)]224 cfreqs = chord_freqs(root*2, scale, cdeg)225226 # pad / nappes227 if g.get("pad", 0) > 0:228 pl, pr = pad_chord(cfreqs, 4*spb*1.05,229 detune=0.006 if st["pad"] == "big" else 0.003,230 cutoff=2400 if st["pad"] == "big" else 1700)231 if st["pad"] == "stab": # stabs funk : accord plaqué sur 2 offbeats232 for s in (2, 10):233 n = int(spb*0.9*SR)234 stab = sum(pluck(f, spb*0.9, bright=4) for f in cfreqs)235 _place(L, stab, int((bar_t+s*stepd)*SR), g["pad"]*0.8)236 _place(R, stab, int((bar_t+s*stepd)*SR), g["pad"]*0.8)237 else:238 _place(L, pl, int(bar_t*SR), g["pad"])239 _place(R, pr, int(bar_t*SR), g["pad"])240241 # batterie242 for s in range(16):243 tt = bar_t+s*stepd+(stepd*swing if s % 2 == 1 else 0.0)244 i0 = int(tt*SR)245 v = st["kick"][s] if st.get("kick") else 0246 if v: _place(L, kick_s, i0, g["kick"]*v); _place(R, kick_s, i0, g["kick"]*v)247 if snare_s is not None and st["snare"][s]:248 _place(L, snare_s, i0, g["snare"]*st["snare"][s]); _place(R, snare_s, i0, g["snare"]*st["snare"][s])249 if clap_s is not None and st.get("clap") and st["clap"][s]:250 _place(L, clap_s, i0, g["clap"]*st["clap"][s]); _place(R, clap_s, i0, g["clap"]*st["clap"][s])251 if st.get("hats") and st["hats"][s]:252 hs = hat_o if (st.get("openhat") and st["openhat"][s]) else hat_c253 vv = g["hat"]*st["hats"][s]*(0.8+0.4*rng.random())254 _place(L, hs, i0, vv*0.9); _place(R, hs, i0, vv*1.1)255256 # basse257 bkind = st["bass"]258 for (s, ln, doff, octu) in _bass_events(bkind, bar, rng):259 f = root*2**(deg_semi(scale, cdeg+doff)/12.0)*(2 if octu else 1)260 d = ln*stepd261 sig = sub808(f/2 if bkind == "e808" else f, d*1.1) if bkind == "e808" else \262 bass_note(f, d*0.95, kind="sub" if bkind in ("half", "pulse8") else "saw")263 i0 = int((bar_t+s*stepd)*SR)264 _place(L, sig, i0, g["bass"]); _place(R, sig, i0, g["bass"])265266 # mélodie (graine → jamais deux fois pareille)267 if g.get("mel", 0) > 0:268 events, prev_deg = _melody_bar(rng, st["mel_density"], prev_deg, cdeg)269 for (s, ln, deg) in events:270 f = root*2**(deg_semi(scale, deg)/12.0)*(2**st["mel_oct"])271 d = ln*stepd272 sig = pluck(f, min(d*1.4, 1.2), bright=6.0)273 tt = bar_t+s*stepd+(stepd*swing if s % 2 == 1 else 0.0)274 i0 = int(tt*SR)275 pan = 0.15*math.sin(bar+s) # léger mouvement stéréo276 _place(L, sig, i0, g["mel"]*(1-pan)); _place(R, sig, i0, g["mel"]*(1+pan))277278 # montée bruitée vers chaque 4e mesure (styles amples)279 if st.get("swell") and bar % 4 == 3:280 n = int(4*spb*SR)281 sw = highpass(nrng.standard_normal(n), 1200)*np.linspace(0, 1, n)**2.5282 _place(L, sw, int(bar_t*SR), 0.10); _place(R, sw, int(bar_t*SR), 0.10)283284 # texture vinyle (lofi)285 if st.get("vinyl"):286 hiss = lowpass(nrng.standard_normal(total), 4000)*0.006287 crackle = np.zeros(total)288 for _ in range(int(dur*7)):289 i = rng.randrange(total-200)290 crackle[i:i+120] += nrng.standard_normal(120)*expdec(120, 0.001)*0.05291 L += hiss+crackle; R += hiss+crackle292293 # écho discret sur le mix (profondeur)294 dly = int(0.375*spb/0.5*SR*0.5) or 1295 eL = np.zeros(total); eR = np.zeros(total)296 eL[dly:] = R[:-dly]*0.18; eR[dly:] = L[:-dly]*0.18 # ping-pong léger297 L += eL; R += eR298299 # master : drive doux, normalisation, fondus300 mix = np.stack([L, R])301 mix = np.tanh(mix*1.25)302 peak = np.max(np.abs(mix)) or 1.0303 mix = mix/peak*0.88304 nfi = int(0.5*SR); nfo = int(1.4*SR)305 mix[:, :nfi] *= np.linspace(0, 1, nfi)306 if total > nfo: mix[:, -nfo:] *= np.linspace(1, 0, nfo)307 mix = mix[:, :int(dur*SR)]308309 data = (np.clip(mix.T, -1, 1)*32767).astype(np.int16)310 with wave.open(out_wav, "wb") as w:311 w.setnchannels(2); w.setsampwidth(2); w.setframerate(SR)312 w.writeframes(data.tobytes())313 return name314315if __name__ == "__main__":316 dur = float(sys.argv[1]); seed = sys.argv[2]; out = sys.argv[3]317 style = sys.argv[4] if len(sys.argv) > 4 else None318 print(compose(dur, seed, out, style))319