Studio: indicateur de chargement (spinner+shimmer) ; musique v3 (basse+arpege) ; vignette+pulse
3 changed files +95 −65
modified
public/app.js
+40 −33
@@ -1435,10 +1435,23 @@ | |||||
| 1435 | })(); | 1435 | })(); | ||
| 1436 | 1436 | ||||
| 1437 | 1437 | ||||
| 1438 | −// ===== STUDIO_SOCIAL v2 (générateurs + galerie, download + copy) ===== | 1438 | + | ||
| 1439 | + | ||||
| 1440 | + | ||||
| 1441 | +// ===== STUDIO_SOCIAL v3 (générateurs + galerie + loading) ===== | ||||
| 1439 | (function(){ | 1442 | (function(){ | ||
| 1440 | const $ = (id) => document.getElementById(id); | 1443 | const $ = (id) => document.getElementById(id); | ||
| 1441 | const SITE_LABEL = {"lou-ka":"Lou·Ka","immo-ka":"Immo·Ka","vrai-prix":"Vrai-Prix","auto-ka":"Auto·Ka","food-ka":"Food·Ka","fabri-ka":"Fabri·Ka","resto-ka":"Resto·Ka","sorti-ka":"Sorti·Ka","crea-ka":"Créa·Ka","job-ka":"Job·Ka","trouve-ka":"Trouve·Ka"}; | 1444 | const SITE_LABEL = {"lou-ka":"Lou·Ka","immo-ka":"Immo·Ka","vrai-prix":"Vrai-Prix","auto-ka":"Auto·Ka","food-ka":"Food·Ka","fabri-ka":"Fabri·Ka","resto-ka":"Resto·Ka","sorti-ka":"Sorti·Ka","crea-ka":"Créa·Ka","job-ka":"Job·Ka","trouve-ka":"Trouve·Ka"}; | ||
| 1445 | + // overlay de chargement | ||||
| 1446 | + let ov; | ||||
| 1447 | + function showLoading(msg){ | ||||
| 1448 | + if(!ov){ ov=document.createElement('div'); ov.id='stLoad'; | ||||
| 1449 | + ov.innerHTML='<div class="stload-box"><div class="stload-spin"></div><div class="stload-msg"></div><div class="stload-sub">Ça peut prendre un moment…</div></div>'; | ||||
| 1450 | + document.body.appendChild(ov); } | ||||
| 1451 | + ov.querySelector('.stload-msg').textContent=msg||'Génération…'; | ||||
| 1452 | + ov.classList.add('on'); | ||||
| 1453 | + } | ||||
| 1454 | + function hideLoading(){ if(ov) ov.classList.remove('on'); } | ||||
| 1442 | async function copyText(txt, btn){ | 1455 | async function copyText(txt, btn){ | ||
| 1443 | try { await navigator.clipboard.writeText(txt); } | 1456 | try { await navigator.clipboard.writeText(txt); } | ||
| 1444 | catch(e){ const t=document.createElement('textarea'); t.value=txt; document.body.appendChild(t); t.select(); try{document.execCommand('copy');}catch(_){} | 1457 | catch(e){ const t=document.createElement('textarea'); t.value=txt; document.body.appendChild(t); t.select(); try{document.execCommand('copy');}catch(_){} | ||
@@ -1446,55 +1459,49 @@ | |||||
| 1446 | if(btn){ const o=btn.innerHTML; btn.innerHTML='✓ Copié'; setTimeout(()=>btn.innerHTML=o,1400); } | 1459 | if(btn){ const o=btn.innerHTML; btn.innerHTML='✓ Copié'; setTimeout(()=>btn.innerHTML=o,1400); } | ||
| 1447 | } | 1460 | } | ||
| 1448 | function esc(s){ return (s||'').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); } | 1461 | function esc(s){ return (s||'').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); } | ||
| 1449 | − function renderGallery(items){ | 1462 | + function renderGallery(items, generating){ | ||
| 1450 | const el=$('stGallery'); if(!el) return; | 1463 | const el=$('stGallery'); if(!el) return; | ||
| 1451 | if($('stCount')) $('stCount').textContent = items.length ? (items.length+' visuel'+(items.length>1?'s':'')) : ''; | 1464 | if($('stCount')) $('stCount').textContent = items.length ? (items.length+' visuel'+(items.length>1?'s':'')) : ''; | ||
| 1452 | − if(!items.length){ el.innerHTML='<div class="soc-sub">Aucun visuel encore — génère ton premier post ou reel ci-dessus.</div>'; return; } | 1465 | + let html = ''; | ||
| 1453 | − el.innerHTML = items.map(function(it,idx){ | 1466 | + if(generating){ html += '<div class="st-item st-skel"><div class="st-media"><div class="st-shimmer"></div><span class="st-badge">⏳ en création…</span></div><div class="st-cap">L\'Agent KA génère un visuel…</div></div>'; } | ||
| 1467 | + if(!items.length && !generating){ el.innerHTML='<div class="soc-sub">Aucun visuel encore — génère ton premier post ou reel ci-dessus.</div>'; return; } | ||||
| 1468 | + html += items.map(function(it,idx){ | ||||
| 1454 | const media = it.kind==='reel' | 1469 | const media = it.kind==='reel' | ||
| 1455 | ? '<video src="'+it.url+'" muted loop playsinline preload="metadata" onmouseover="this.play()" onmouseout="this.pause()"></video>' | 1470 | ? '<video src="'+it.url+'" muted loop playsinline preload="metadata" onmouseover="this.play()" onmouseout="this.pause()"></video>' | ||
| 1456 | : '<img src="'+it.url+'" loading="lazy">'; | 1471 | : '<img src="'+it.url+'" loading="lazy">'; | ||
| 1457 | const badge = (SITE_LABEL[it.site]||it.site)+(it.kind==='reel'?' · Reel':' · Post'); | 1472 | const badge = (SITE_LABEL[it.site]||it.site)+(it.kind==='reel'?' · Reel':' · Post'); | ||
| 1458 | − return '<div class="st-item">' | 1473 | + return '<div class="st-item"><div class="st-media">'+media+'<span class="st-badge">'+esc(badge)+'</span></div>' | ||
| 1459 | − + '<div class="st-media">'+media+'<span class="st-badge">'+esc(badge)+'</span></div>' | ||||
| 1460 | + '<div class="st-cap" id="stcap'+idx+'">'+esc(it.caption||'')+'</div>' | 1474 | + '<div class="st-cap" id="stcap'+idx+'">'+esc(it.caption||'')+'</div>' | ||
| 1461 | − + '<div class="st-item-actions">' | 1475 | + + '<div class="st-item-actions"><a class="btn-cta small" href="'+it.url+'" download="'+it.site+'-'+it.kind+(it.kind==='reel'?'.mp4':'.png')+'">⬇︎</a>' | ||
| 1462 | − + '<a class="btn-cta small" href="'+it.url+'" download="'+it.site+'-'+it.kind+(it.kind==='reel'?'.mp4':'.png')+'">⬇︎</a>' | 1476 | + + '<button class="btn-ghost small" data-copy="'+idx+'">📋 Copier</button></div></div>'; | ||
| 1463 | − + '<button class="btn-ghost small" data-copy="'+idx+'">📋 Copier</button>' | ||||
| 1464 | − + '</div></div>'; | ||||
| 1465 | }).join(''); | 1477 | }).join(''); | ||
| 1478 | + el.innerHTML = html; | ||||
| 1466 | el.querySelectorAll('[data-copy]').forEach(function(b){ b.onclick=function(){ copyText(items[+b.dataset.copy].caption||'', b); }; }); | 1479 | el.querySelectorAll('[data-copy]').forEach(function(b){ b.onclick=function(){ copyText(items[+b.dataset.copy].caption||'', b); }; }); | ||
| 1467 | } | 1480 | } | ||
| 1481 | + let polling=false; | ||||
| 1468 | async function loadGallery(){ | 1482 | async function loadGallery(){ | ||
| 1469 | try{ const d=await (await fetch('/api/social/gallery')).json(); | 1483 | try{ const d=await (await fetch('/api/social/gallery')).json(); | ||
| 1470 | − renderGallery(d.items||[]); | 1484 | + const gen = d.gen && d.gen.running; | ||
| 1471 | − if(d.gen && d.gen.running){ if($('stGenStatus')) $('stGenStatus').textContent='⏳ génération des reels en cours…'; setTimeout(loadGallery, 12000); } | 1485 | + renderGallery(d.items||[], gen); | ||
| 1472 | − else if($('stGenStatus')) $('stGenStatus').textContent=''; | 1486 | + if($('stGenStatus')) $('stGenStatus').textContent = gen ? '⏳ génération des reels en cours…' : ''; | ||
| 1487 | + if(gen && !polling){ polling=true; setTimeout(function(){ polling=false; loadGallery(); }, 10000); } | ||||
| 1473 | }catch(e){} | 1488 | }catch(e){} | ||
| 1474 | } | 1489 | } | ||
| 1475 | − async function genPost(){ | 1490 | + async function genOne(url, body, msg){ | ||
| 1476 | − const g=$('stPostGen'); if(!g) return; const prev=g.textContent; g.disabled=true; g.textContent='Génération…'; | 1491 | + showLoading(msg); | ||
| 1477 | − try{ const r=await fetch('/api/social/generate',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({site:$('stPostSite').value,prompt:$('stPostPrompt').value})}); | 1492 | + try{ const r=await fetch(url,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)}); | ||
| 1478 | − const d=await r.json(); if(!d.draft) alert('Échec : '+(d.error||'?')); else loadGallery(); | 1493 | + const d=await r.json(); if(!d.draft){ alert('Échec : '+(d.error||'?')); } else { await loadGallery(); } | ||
| 1479 | − }catch(e){ alert('Échec : '+e.message); } g.disabled=false; g.textContent=prev; | ||||
| 1480 | − } | ||||
| 1481 | − async function genReel(){ | ||||
| 1482 | − const g=$('stReelGen'); if(!g) return; const prev=g.textContent; g.disabled=true; g.textContent='Génération vidéo…'; | ||||
| 1483 | − try{ const r=await fetch('/api/social/reel/generate',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({site:$('stReelSite').value,prompt:$('stReelPrompt').value})}); | ||||
| 1484 | − const d=await r.json(); if(!d.draft) alert('Échec : '+(d.error||'?')); else loadGallery(); | ||||
| 1485 | − }catch(e){ alert('Échec : '+e.message); } g.disabled=false; g.textContent=prev; | ||||
| 1486 | − } | ||||
| 1487 | − async function gen10(){ | ||||
| 1488 | − const b=$('stReel10'); if(!b) return; | ||||
| 1489 | − try{ const r=await fetch('/api/social/reel/genbatch',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({})}); | ||||
| 1490 | − const d=await r.json(); if($('stGenStatus')) $('stGenStatus').textContent = d.started? '⏳ 10 reels en génération…' : (d.reason||'déjà en cours'); | ||||
| 1491 | − setTimeout(loadGallery, 8000); | ||||
| 1492 | }catch(e){ alert('Échec : '+e.message); } | 1494 | }catch(e){ alert('Échec : '+e.message); } | ||
| 1495 | + hideLoading(); | ||||
| 1493 | } | 1496 | } | ||
| 1494 | function bind(){ | 1497 | function bind(){ | ||
| 1495 | − if($('stPostGen')) $('stPostGen').onclick=genPost; | 1498 | + if($('stPostGen')) $('stPostGen').onclick=()=>genOne('/api/social/generate',{site:$('stPostSite').value,prompt:$('stPostPrompt').value},'🖼️ Génération de l\'image…'); | ||
| 1496 | − if($('stReelGen')) $('stReelGen').onclick=genReel; | 1499 | + if($('stReelGen')) $('stReelGen').onclick=()=>genOne('/api/social/reel/generate',{site:$('stReelSite').value,prompt:$('stReelPrompt').value},'🎬 Génération du reel (vidéo + musique)…'); | ||
| 1497 | − if($('stReel10')) $('stReel10').onclick=gen10; | 1500 | + if($('stReel10')) $('stReel10').onclick=async ()=>{ | ||
| 1501 | + try{ const r=await fetch('/api/social/reel/genbatch',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({})}); | ||||
| 1502 | + const d=await r.json(); if($('stGenStatus')) $('stGenStatus').textContent=d.started?'⏳ 10 reels en génération…':(d.reason||'déjà en cours'); loadGallery(); | ||||
| 1503 | + }catch(e){ alert('Échec : '+e.message); } | ||||
| 1504 | + }; | ||||
| 1498 | if($('stRefresh')) $('stRefresh').onclick=loadGallery; | 1505 | if($('stRefresh')) $('stRefresh').onclick=loadGallery; | ||
| 1499 | } | 1506 | } | ||
| 1500 | bind(); | 1507 | bind(); | ||
modified
public/style.css
+12 −0
@@ -677,3 +677,15 @@ details.thinking-box summary { color: var(--muted); font-size: 12.5px; cursor: p | |||||
| 677 | .st-item-actions{display:flex;gap:6px;padding:0 10px 10px;margin-top:auto} | 677 | .st-item-actions{display:flex;gap:6px;padding:0 10px 10px;margin-top:auto} | ||
| 678 | .st-item-actions .btn-cta.small{flex:0 0 auto;text-decoration:none;text-align:center} | 678 | .st-item-actions .btn-cta.small{flex:0 0 auto;text-decoration:none;text-align:center} | ||
| 679 | .st-item-actions .btn-ghost.small{flex:1} | 679 | .st-item-actions .btn-ghost.small{flex:1} | ||
| 680 | + | ||||
| 681 | +/* ---- Studio loading ---- */ | ||||
| 682 | +#stLoad{position:fixed;inset:0;z-index:9999;display:none;align-items:center;justify-content:center;background:rgba(8,8,10,.72);backdrop-filter:blur(4px)} | ||||
| 683 | +#stLoad.on{display:flex} | ||||
| 684 | +.stload-box{background:var(--card,#141414);border:2px solid var(--ink,#1a1a1a);border-radius:18px;padding:26px 30px;text-align:center;box-shadow:4px 4px 0 var(--ink,#1a1a1a);max-width:80vw} | ||||
| 685 | +.stload-spin{width:44px;height:44px;margin:0 auto 14px;border:5px solid rgba(127,127,127,.25);border-top-color:var(--accent,#f97316);border-radius:50%;animation:stspin .8s linear infinite} | ||||
| 686 | +@keyframes stspin{to{transform:rotate(360deg)}} | ||||
| 687 | +.stload-msg{font-weight:800;font-size:15px} | ||||
| 688 | +.stload-sub{font-size:12px;color:var(--muted,#888);margin-top:6px} | ||||
| 689 | +.st-skel .st-media{position:relative} | ||||
| 690 | +.st-shimmer{position:absolute;inset:0;background:linear-gradient(100deg,#1a1a1a 30%,#2a2a2a 50%,#1a1a1a 70%);background-size:200% 100%;animation:stshim 1.3s linear infinite} | ||||
| 691 | +@keyframes stshim{to{background-position:-200% 0}} | ||||
modified
server/social/make_reel.py
+43 −32
@@ -136,13 +136,16 @@ def build_html(ins): | |||||
| 136 | @keyframes fadeUp{to{opacity:1;transform:translateY(0)}} | 136 | @keyframes fadeUp{to{opacity:1;transform:translateY(0)}} | ||
| 137 | @keyframes pop{to{opacity:1;transform:rotate(-4deg) scale(1)}} | 137 | @keyframes pop{to{opacity:1;transform:rotate(-4deg) scale(1)}} | ||
| 138 | @keyframes heroIn{to{opacity:1;transform:translateY(0) scale(1)}} | 138 | @keyframes heroIn{to{opacity:1;transform:translateY(0) scale(1)}} | ||
| 139 | + .vign{position:absolute;inset:0;pointer-events:none;background:radial-gradient(120% 90% at 50% 42%, transparent 55%, rgba(0,0,0,.55) 100%)} | ||||
| 140 | + .numwrap{animation:heroIn .95s cubic-bezier(.2,1,.3,1) .95s forwards, pulse 3s ease-in-out 2s infinite} | ||||
| 141 | + @keyframes pulse{0%,100%{filter:none}50%{filter:brightness(1.12)}} | ||||
| 139 | """ | 142 | """ | ||
| 140 | css=(css.replace("__H__",str(H)).replace("__FONT__",FONT).replace("__A__",p["a"]) | 143 | css=(css.replace("__H__",str(H)).replace("__FONT__",FONT).replace("__A__",p["a"]) | ||
| 141 | .replace("__B__",p["b"]).replace("__G1__",p["g1"]).replace("__G2__",p["g2"]) | 144 | .replace("__B__",p["b"]).replace("__G1__",p["g1"]).replace("__G2__",p["g2"]) | ||
| 142 | .replace("__NUM__",str(numsize)).replace("__DUR__",str(DUR))) | 145 | .replace("__NUM__",str(numsize)).replace("__DUR__",str(DUR))) | ||
| 143 | tag = ('<div class="tag">'+esc(ins.get("tagline",""))+'</div>') if ins.get("tagline") else "" | 146 | tag = ('<div class="tag">'+esc(ins.get("tagline",""))+'</div>') if ins.get("tagline") else "" | ||
| 144 | tiles_block = ('<div class="tiles">'+tiles_html+'</div>') if tiles else "" | 147 | tiles_block = ('<div class="tiles">'+tiles_html+'</div>') if tiles else "" | ||
| 145 | − body = ('<div class="stage"><div class="mesh"></div><div class="pad">' | 148 | + body = ('<div class="stage"><div class="mesh"></div><div class="vign"></div><div class="pad">' | ||
| 146 | '<div class="eyebrow"><span class="dot"></span>'+esc(ins["label"].upper())+' · '+today().upper()+'</div>' | 149 | '<div class="eyebrow"><span class="dot"></span>'+esc(ins["label"].upper())+' · '+today().upper()+'</div>' | ||
| 147 | '<div class="kabadge">KA</div>' | 150 | '<div class="kabadge">KA</div>' | ||
| 148 | '<div class="site">'+esc(ins["label"])+'</div>'+tag+ | 151 | '<div class="site">'+esc(ins["label"])+'</div>'+tag+ | ||
@@ -194,45 +197,53 @@ def record_webm(html_path, out_webm, tmpdir): | |||||
| 194 | os.replace(path, out_webm) | 197 | os.replace(path, out_webm) | ||
| 195 | 198 | ||||
| 196 | def make_music(dur, out_wav): | 199 | def make_music(dur, out_wav): | ||
| 197 | − """Lit musical pro : progression Am-F-C-G (pad) + kick 4-temps + shaker + réverbe.""" | 200 | + """Lit musical riche : progression Am-F-C-G (pad) + basse + kick 4-temps + shaker | ||
| 201 | + + arpège/pluck brillant, avec réverbe/compresseur/limiteur.""" | ||||
| 198 | tmpdir=os.path.dirname(out_wav) | 202 | tmpdir=os.path.dirname(out_wav) | ||
| 199 | chords=[(220.00,261.63,329.63),(174.61,220.00,261.63),(261.63,329.63,392.00),(196.00,246.94,293.66)] | 203 | chords=[(220.00,261.63,329.63),(174.61,220.00,261.63),(261.63,329.63,392.00),(196.00,246.94,293.66)] | ||
| 204 | + roots=[110.00,87.31,130.81,98.00] | ||||
| 200 | seg=round(dur/len(chords),3) | 205 | seg=round(dur/len(chords),3) | ||
| 201 | − parts=[] | 206 | + def concat(paths,outp): | ||
| 207 | + lst=outp+".txt"; open(lst,"w").write("".join("file '"+x+"'\n" for x in paths)) | ||||
| 208 | + subprocess.run([FFMPEG,"-y","-f","concat","-safe","0","-i",lst,"-c","copy",outp],capture_output=True) | ||||
| 209 | + # pad (accords) | ||||
| 210 | + padparts=[] | ||||
| 202 | for i,(f1,f2,f3) in enumerate(chords): | 211 | for i,(f1,f2,f3) in enumerate(chords): | ||
| 203 | − w=os.path.join(tmpdir,f"ch{i}.wav") | 212 | + w=os.path.join(tmpdir,"ch%d.wav"%i) | ||
| 204 | − subprocess.run([FFMPEG,"-y", | 213 | + subprocess.run([FFMPEG,"-y","-f","lavfi","-i","sine=frequency=%s:duration=%s"%(f1,seg), | ||
| 205 | − "-f","lavfi","-i",f"sine=frequency={f1}:duration={seg}", | 214 | + "-f","lavfi","-i","sine=frequency=%s:duration=%s"%(f2,seg), | ||
| 206 | − "-f","lavfi","-i",f"sine=frequency={f2}:duration={seg}", | 215 | + "-f","lavfi","-i","sine=frequency=%s:duration=%s"%(f3,seg), | ||
| 207 | − "-f","lavfi","-i",f"sine=frequency={f3}:duration={seg}", | 216 | + "-filter_complex","[0][1][2]amix=inputs=3:normalize=0,volume=0.20,lowpass=f=1200,afade=t=in:st=0:d=0.3,afade=t=out:st=%s:d=0.4[a]"%(seg-0.4), | ||
| 208 | − "-filter_complex", | 217 | + "-map","[a]",w],capture_output=True); padparts.append(w) | ||
| 209 | − f"[0][1][2]amix=inputs=3:normalize=0,volume=0.22,lowpass=f=1300," | 218 | + pad=os.path.join(tmpdir,"pad.wav"); concat(padparts,pad) | ||
| 210 | − f"afade=t=in:st=0:d=0.25,afade=t=out:st={seg-0.35}:d=0.35[a]", | 219 | + # basse (fondamentale par accord) | ||
| 211 | − "-map","[a]",w],capture_output=True) | 220 | + bassparts=[] | ||
| 212 | − parts.append(w) | 221 | + for i,r in enumerate(roots): | ||
| 213 | − # concat des accords -> pad | 222 | + w=os.path.join(tmpdir,"b%d.wav"%i) | ||
| 214 | − lst=os.path.join(tmpdir,"chords.txt") | 223 | + subprocess.run([FFMPEG,"-y","-f","lavfi","-i","sine=frequency=%s:duration=%s"%(r,seg), | ||
| 215 | − open(lst,"w").write("".join(f"file '{w}'\n" for w in parts)) | 224 | + "-af","volume=0.34,lowpass=f=320,afade=t=in:st=0:d=0.06,afade=t=out:st=%s:d=0.12"%(seg-0.15), | ||
| 216 | − pad=os.path.join(tmpdir,"pad.wav") | 225 | + w],capture_output=True); bassparts.append(w) | ||
| 217 | − subprocess.run([FFMPEG,"-y","-f","concat","-safe","0","-i",lst,"-c","copy",pad],capture_output=True) | 226 | + bass=os.path.join(tmpdir,"bass.wav"); concat(bassparts,bass) | ||
| 218 | − # kick 4-temps (120 BPM) + shaker offbeat, synthétisés | 227 | + # kick 4-temps | ||
| 219 | kick=os.path.join(tmpdir,"kick.wav") | 228 | kick=os.path.join(tmpdir,"kick.wav") | ||
| 220 | − subprocess.run([FFMPEG,"-y","-f","lavfi", | 229 | + subprocess.run([FFMPEG,"-y","-f","lavfi","-i","aevalsrc=0.75*sin(2*PI*55*t)*exp(-9*mod(t\,0.5)):d=%s:s=44100"%dur,kick],capture_output=True) | ||
| 221 | − "-i",f"aevalsrc=0.7*sin(2*PI*55*t)*exp(-9*mod(t\,0.5)):d={dur}:s=44100", | 230 | + # shaker/hat contretemps | ||
| 222 | − kick],capture_output=True) | ||||
| 223 | hat=os.path.join(tmpdir,"hat.wav") | 231 | hat=os.path.join(tmpdir,"hat.wav") | ||
| 232 | + subprocess.run([FFMPEG,"-y","-f","lavfi","-i","aevalsrc=0.10*(random(0)*2-1)*exp(-45*mod(t+0.25\,0.5)):d=%s:s=44100"%dur,"-af","highpass=f=6500",hat],capture_output=True) | ||||
| 233 | + # arpège/pluck brillant (croches, deux notes en alternance) | ||||
| 234 | + arp=os.path.join(tmpdir,"arp.wav") | ||||
| 224 | subprocess.run([FFMPEG,"-y","-f","lavfi", | 235 | subprocess.run([FFMPEG,"-y","-f","lavfi", | ||
| 225 | − "-i",f"aevalsrc=0.12*(random(0)*2-1)*exp(-45*mod(t+0.25\,0.5)):d={dur}:s=44100", | 236 | + "-i","aevalsrc=0.13*sin(2*PI*880*t)*exp(-13*mod(t\,0.25))+0.10*sin(2*PI*659.25*(t-0.125))*exp(-13*mod(t+0.125\,0.25)):d=%s:s=44100"%dur, | ||
| 226 | − "-af","highpass=f=6000",hat],capture_output=True) | 237 | + "-af","highpass=f=350,lowpass=f=6000",arp],capture_output=True) | ||
| 227 | − # mix final : pad + kick + hat, réverbe douce, master fades | 238 | + # mix final | ||
| 228 | − subprocess.run([FFMPEG,"-y","-i",pad,"-i",kick,"-i",hat, | 239 | + subprocess.run([FFMPEG,"-y","-i",pad,"-i",bass,"-i",kick,"-i",hat,"-i",arp, | ||
| 229 | "-filter_complex", | 240 | "-filter_complex", | ||
| 230 | − f"[0]volume=1.0[p];[1]volume=0.9[k];[2]volume=0.5[h];" | 241 | + "[0]volume=1.0[p];[1]volume=1.0[b];[2]volume=0.9[k];[3]volume=0.5[h];[4]volume=0.55[ar];" | ||
| 231 | − f"[p][k][h]amix=inputs=3:normalize=0," | 242 | + "[p][b][k][h][ar]amix=inputs=5:normalize=0," | ||
| 232 | − f"aecho=0.8:0.85:60:0.25," | 243 | + "aecho=0.8:0.88:70:0.22," | ||
| 233 | − f"acompressor=threshold=-16dB:ratio=3:attack=8:release=180," | 244 | + "acompressor=threshold=-18dB:ratio=3.5:attack=6:release=160," | ||
| 234 | − f"alimiter=limit=0.95," | 245 | + "highpass=f=32,alimiter=limit=0.94," | ||
| 235 | − f"afade=t=in:st=0:d=0.5,afade=t=out:st={dur-1.6}:d=1.6[a]", | 246 | + "afade=t=in:st=0:d=0.5,afade=t=out:st=%s:d=1.6[a]"%(dur-1.6), | ||
| 236 | "-map","[a]","-ar","44100",out_wav],capture_output=True) | 247 | "-map","[a]","-ar","44100",out_wav],capture_output=True) | ||
| 237 | 248 | ||||
| 238 | def render(ins, out_dir): | 249 | def render(ins, out_dir): | ||
| 239 | 250 | ||||