Generative Collaboration Bot — v1
Décris une intention musicale et clique Générer
Export .WAV (esquisse sélectionnée)
Brief / Chat
🎹 Générer 3 esquisses
el.classList.toggle('play', i===step)); step=(step+1)%16; nextT+=tStep; timer=setTimeout(tick, Math.max(0,(nextT-AC.currentTime-0.04)*1000)); } function start(){ if(AC.state==='suspended'&&AC.resume) AC.resume(); if(playing) return; playing=true; step=0; nextT=AC.currentTime+0.06; tick(); } function stop(){ playing=false; if(timer){clearTimeout(timer); timer=null} document.querySelectorAll(`#${sketchId} .pad .cell`).forEach(el=>el.classList.remove('play')); } // attach UI document.querySelectorAll(`#${sketchId} input[type=range]`).forEach(inp=> inp.addEventListener('input',applyMixerUI)); document.querySelector(`#${sketchId} .btn.play`).addEventListener('click',start); document.querySelector(`#${sketchId} .btn.stop`).addEventListener('click',stop); // expose for export return {start,stop,getPattern:()=>pattern, getMix:()=>({ vol:Object.fromEntries(Object.keys(mix).map(k=>[k,mix[k].gain.value])), pan:Object.fromEntries(Object.keys(panNodes).map(k=>[k, panNodes[k].pan? panNodes[k].pan.value : 0])) })}; } /* ===== Render sketches cards ===== */ let players={}; // id -> player function renderSketches(intention, opts, seeds){ const host=$('sketches'); host.innerHTML=''; players={}; seeds.forEach((seed,idx)=>{ const pat=genPattern(seed,opts); const id=`sk${idx}`; const card=document.createElement('div'); card.className='card'; card.id=id; card.innerHTML = `
Esquisse ${['A','B','C'][idx]}
seed:${seed.slice(0,8)} · ${opts.key} ${opts.mode} · ${opts.bpm} BPM · swing ${opts.swing}
▶
■
Sélectionner
Mixer
${['kick','snare','hat','bass','lead','arp'].map(k=>`
${k}
`).join('')}
${[...Array(16)].map((_,i)=>`
`).join('')}
`; host.appendChild(card); // colorize pad according to density const dens = (pat.tracks.kick.reduce((a,b)=>a+b,0) + ['bass','lead','arp'].reduce((s,k)=> s+pat.tracks[k].filter(x=>x!=null).length,0))/ (16*4); card.querySelectorAll('.cell').forEach((c,i)=>{ const on = pat.tracks.kick[i]|| pat.tracks.snare[i]|| pat.tracks.hat[i]|| pat.tracks.bass[i]!=null|| pat.tracks.lead[i]!=null || pat.tracks.arp[i]!=null; c.style.background = on ? `linear-gradient(180deg,#26306a,#1a2150)` : `#13183a`; c.style.opacity = on ? 1 : 0.5; }); players[id]=createPlayer(id,pat); // selection pour export card.querySelector('.select').addEventListener('click',()=>{ document.querySelectorAll('.card').forEach(el=> el.style.outline='none'); card.style.outline='2px solid var(--accent)'; $('status').textContent = `Esquisse sélectionnée: ${['A','B','C'][idx]} • seed ${seed.slice(0,8)}`; renderSketches.selected = {id, pattern:pat}; }); if(idx===0){ card.querySelector('.select').click(); } }); } /* ===== Export WAV (offline) ===== */ function floatTo16BitPCM(f32){ const out=new Int16Array(f32.length); for(let i=0;i
{for(let i=0;i
{ bus[k]=oac.createGain(); pan[k]=oac.createStereoPanner?oac.createStereoPanner():oac.createGain(); // récupérer les réglages actuels dans la carte sélectionnée const card=document.getElementById(sel.id); const vol=+card.querySelector(`input[data-k="${k}"][data-t="vol"]`).value; const pv = +card.querySelector(`input[data-k="${k}"][data-t="pan"]`).value; bus[k].gain.value=vol; if(pan[k].pan) pan[k].pan.value=pv; bus[k].connect(pan[k]).connect(master); }); function makeKick(t){ const o=oac.createOscillator(); o.type='sine'; const g=oac.createGain(); g.gain.setValueAtTime(0.0001,t); g.gain.exponentialRampToValueAtTime(0.9,t+0.01); g.gain.exponentialRampToValueAtTime(0.0001,t+0.35); o.frequency.setValueAtTime(130,t); o.frequency.exponentialRampToValueAtTime(55,t+0.25); o.connect(g).connect(bus.kick); o.start(t); o.stop(t+0.5); } function makeSnare(t){ const n=oac.createBufferSource(); const len=Math.floor(oac.sampleRate*0.25); const b=oac.createBuffer(1,len,oac.sampleRate); const d=b.getChannelData(0); for(let i=0;i
{ document.body.removeChild(a); URL.revokeObjectURL(url); }, 800); } $('btnExport').addEventListener('click', exportWav); /* ===== Wire up ===== */ function generateFromPrompt(text){ const intent=parseIntent(text); // appliquer aux contrôles $('bpm').value= intent.bpm; $('swing').value=intent.swing; $('mode').value=intent.mode; $('key').value=intent.key; $('status').textContent = `Intention: ${intent.key} ${intent.mode} • ${intent.bpm} BPM • swing ${intent.swing}`; // seeds A/B/C const base = text.trim().length? text.trim() : 'seed'; const seeds=[base+'-A-'+Date.now(), base+'-B-'+(Date.now()+1), base+'-C-'+(Date.now()+2)]; const opts={ bpm:intent.bpm, swing:intent.swing, key:intent.key, mode:intent.mode, instruments:intent.instruments, density:intent.density, variation:intent.variation }; renderSketches(text, opts, seeds); pushBot(`J'ai généré 3 esquisses (A/B/C) à ${opts.bpm} BPM, tonalité ${opts.key} ${opts.mode}, swing ${opts.swing}. Ajuste le mixer/pan, ou tape une nouvelle consigne pour raffiner.`); } $('btnGen').addEventListener('click', ()=>{ const text=$('prompt').value.trim(); if(!text) return; pushUser(text); generateFromPrompt(text); }); document.querySelectorAll('[data-quick]').forEach(b=> b.addEventListener('click',()=>{ const t=b.getAttribute('data-quick'); $('prompt').value=t; pushUser(t); generateFromPrompt(t); })); // Affinage rapide : quand on change, on régénère avec mêmes seeds (mais on garde UX simple: régénère à la demande via Générer) ['key','mode','bpm','swing'].forEach(id=>{ $(id).addEventListener('change', ()=>{ $('status').textContent = `Paramètres mis à jour • BPM ${$('bpm').value} • ${$('key').value} ${$('mode').value} • swing ${$('swing').value}`; }); });