RD5 GOLD WEB EDITOR
📄
📄 2-RB4-3CC.HTML
📄 222.JPG
📄 3CC-BASIC1.HTML
📄 Microsoft Paint.exe
📄 R2C-3.HTML
📄 R2C-4.HTML
📄 RB4-3C.HTML
📄 RB4-3CC.HTML
📄 RB4-RETRO.HTML
📄 RB4-RETRO.html
📄 RD5-3CC.html
📄 RL-4.html
📄 TARS.html
📄 TES10.mp3
📄 TES11.mp3
📄 TRISHA-OK.html
📄 TRISHA-WIN.HTML
📄 Tiny-demo.html
📄 Tinybj7-blindado.html
📄 Tinybj7-publica.html
📄 aplicaciones-rd5.html
📄 app1.html
📄 app2.html
📄 app3.html
📄 appb1.html
📄 appb2.html
📄 appb3.html
📄 appb4.html
📄 appb5.html
📄 appb6.html
📄 c-julio.JPG
📄 caratula.gif
📄 caratula02.gif
📄 caratula2.gif
📄 chokurei2.jpg
📄 columna-julio.html
📄 columna-stella.html
📄 columna-stellab.html
📄 demos.html
📄 df3.html
📄 dni.gif
📄 donaciones.gif
📄 ecualizador.html
📄 ed-podcast.JPG
📄 ed-radio.JPG
📄 editorv.html
📄 editorv1.html
📄 editorv2.html
📄 fondo5d.jpg
📄 fresi-clip.html
📄 gold.php
📄 gold62.php
📄 gold93.php
📄 googlefd161ffccea5f6ab.html
📄 gw.php
📄 index-anterior.html
📄 index.html
📄 index2.html
📄 logo.png
📄 logo.webp
📄 magazine.html
📄 manager3.php
📄 mic2.jpg
📄 mini1.html
📄 minib2a.html
📄 mn.php
📄 muestra.JPG
📄 p.html
📄 pm.html
📄 pm2.html
📄 pod-rd5.html
📄 podcast-stops.gif
📄 podcast2.mp3
📄 pp.html
📄 promo-tinybj7.html
📄 radio.html
📄 radio_gold.html
📄 rd5-fresia-4 canvashtml
📄 rd5-fresia-ok.html
📄 rd5-fresia.html
📄 rd5-plus-anterior.html
📄 rd5-plus.html
📄 rd5-plus2.html
📄 rd5-radioplayer.html
📄 rd5-tv.html
📄 rd5.html
📄 sst.html
📄 st.html
📄 starclip.html
📄 stella-2.JPG
📄 stella.JPG
📄 stt.html
📄 t-rd5.html.html
📄 t-rd6.html
📄 t-rd7.html
📄 t-tiny.html
📄 tes0.jpg
📄 tes1.jpg
📄 tes2.jpg
📄 tes4.jpg
📄 tes4.mp3
📄 tes5.jpg
📄 tes7.jpg
📄 tes8.jpg
📄 tienda.html
📄 tiendaaps.html
📄 tiendapps.html
📄 tiny-bj6.html
📄 tiny-bj7-full.html
📄 tiny-bj7.html
📄 tiny3-bj2.html
📄 tinybj7-blindado.html
📄 tinydemo.html
📄 titulo.jpg
📄 tnw2.html
📄 trisha-basic.html
📄 trisha-cafe.html
📄 trisha-mini.html
📄 tv-pantalla.php
📄 tv.html
📄 xdni.html
CÓDIGO (OSCURO GOLD)
VISTA PREVIA
💾 GUARDAR
Tema: Monokai
Tema: Dracula
Tema: Material
Tema: Blackboard
Tema: Abyss
Monospace
Courier
12px
14px
16px
18px
20px
22px
24px
26px
28px
30px
32px
↷ Rehacer
↶ Deshacer
🔍 BUSCAR
+ REPRO RADIO
SIG.
X
<!DOCTYPE html> <html lang="es"> <head> <meta charset="UTF-8"> <title>Trisha TINY-BJ2 - 8 Band EQ ARMOURED</title> <style> :root { --accent: orange; --bg-panel: #1a110f; --bg-body: BLACK; --border: #3a2c28; --text-muted: #888; --text-main: #ccc; } body { background: var(--bg-body); color:white; font-family:Arial, sans-serif; margin:15px; overflow-x: hidden; transition: background 0.3s; } #contador { font-size: 20px; margin-top:10px; color:white; font-family: monospace; border-top: 1px solid var(--border); padding-top: 10px; } .linea { display:flex; align-items:flex-start; gap:15px; flex-wrap:wrap; } .cont-sliders { display: flex; gap: 8px; align-items: flex-end; background: var(--bg-panel); padding: 10px; border-radius: 8px; border: 1px solid var(--border); box-shadow: inset 0 0 10px #000; } .control-vertical { display: flex; flex-direction: column; align-items: center; gap: 5px; width: 42px; } .slider-v { -webkit-appearance: slider-vertical; width: 8px; height: 60px; background: var(--border); outline: none; cursor: pointer; } .val-pct { font-size: 9px; color: var(--accent); font-family: monospace; min-height: 11px; } label { font-size: 8px; text-transform: uppercase; color: var(--text-muted); font-weight: bold; } canvas.vu { background:black; border:4px solid #888; border-radius:50%; box-shadow: 0 0 5px #000; } #lista { margin-top:10px; max-height:259px; overflow-y:auto; list-style:none; padding-left:0; user-select: none; border: 1px solid var(--border); background: var(--bg-panel); border-radius: 4px; } #lista li { padding:8px 12px; border-bottom: 1px solid #333; font-size: 13px; color: var(--text-main); cursor: grab; transition: background 0.2s; } #lista li:active { cursor: grabbing; } #lista li.dragging { opacity: 0.5; background: BLACK; } #lista li.active { background:GREEN; color: var(--accent); font-weight: bold; border-left: 4px solid var(--accent); } #prog { -webkit-appearance:none; appearance:none; width:100%; height:8px; background: var(--border); border-radius:5px; margin: 15px 0; cursor: pointer; } #prog::-webkit-slider-thumb { -webkit-appearance:none; width:14px; height:14px; background: var(--accent); border-radius:50%; border:2px solid black; } #btnLimpiar { padding: 5px; background: #e3a465; color: black; border: none; border-radius: 4px; cursor: pointer; font-weight: bold; writing-mode: vertical-lr; text-transform: uppercase; height: 105px; font-size: 11px; } .librero { display: flex; gap: 4px; } .btnTema { width: 25px; height: 105px; border: none; border-radius: 2px; cursor: pointer; font-weight: bold; writing-mode: vertical-lr; font-size: 9px; text-transform: uppercase; color: white; transition: 0.2s; display: flex; align-items: center; justify-content: center; } .btnTema:hover { transform: translateY(-3px); filter: brightness(1.2); } .t-azul { background: #2980b9; } .t-verde { background: #27ae60; } .t-rojo { background: #c0392b; } .t-custom { background: #444; position: relative; overflow: hidden; } #colorCustom { position: absolute; width: 150%; height: 150%; cursor: pointer; border: none; padding: 0; } </style> </head> <body> <div class="linea"> <div style="display:flex; flex-direction:column; gap:10px;"> <input type="file" id="archivos" multiple accept="audio/*"> <div style="display:flex; gap:10px;"> <canvas id="btnPlay" class="vu" width="70" height="70" style="cursor:pointer;"></canvas> <canvas id="vuL" class="vu" width="70" height="70"></canvas> <canvas id="vuR" class="vu" width="70" height="70"></canvas> <canvas id="vuECG" class="vu" width="70" height="70"></canvas> <canvas id="vuSpectrum" class="vu" width="70" height="70"></canvas> </div> </div> <div class="cont-sliders"> <div class="control-vertical"><label>VOL</label><input type="range" id="vol" class="slider-v" min="0" max="1" step="0.01" value="0.7"><span id="v-vol" class="val-pct">70%</span></div> <div class="control-vertical"><label>32Hz</label><input type="range" id="eq1" class="slider-v" min="-15" max="15" step="1" value="0"><span id="v-eq1" class="val-pct">0</span></div> <div class="control-vertical"><label>64Hz</label><input type="range" id="eq2" class="slider-v" min="-15" max="15" step="1" value="0"><span id="v-eq2" class="val-pct">0</span></div> <div class="control-vertical"><label>125Hz</label><input type="range" id="eq3" class="slider-v" min="-15" max="15" step="1" value="0"><span id="v-eq3" class="val-pct">0</span></div> <div class="control-vertical"><label>500Hz</label><input type="range" id="eq4" class="slider-v" min="-15" max="15" step="1" value="0"><span id="v-eq4" class="val-pct">0</span></div> <div class="control-vertical"><label>1kHz</label><input type="range" id="eq5" class="slider-v" min="-15" max="15" step="1" value="0"><span id="v-eq5" class="val-pct">0</span></div> <div class="control-vertical"><label>4kHz</label><input type="range" id="eq6" class="slider-v" min="-15" max="15" step="1" value="0"><span id="v-eq6" class="val-pct">0</span></div> <div class="control-vertical"><label>8kHz</label><input type="range" id="eq7" class="slider-v" min="-15" max="15" step="1" value="0"><span id="v-eq7" class="val-pct">0</span></div> <div class="control-vertical"><label>16kHz</label><input type="range" id="eq8" class="slider-v" min="-15" max="15" step="1" value="0"><span id="v-eq8" class="val-pct">0</span></div> <button id="btnLimpiar">Limpiar Lista</button> <div class="librero"> <button class="btnTema t-azul" onclick="cambiarTema('#3498db', '#1a1c2c', '#0d1117')"></button> <button class="btnTema t-verde" onclick="cambiarTema('#2ecc71', '#0f1a11', '#0d140f')"></button> <button class="btnTema t-rojo" onclick="cambiarTema('#e74c3c', '#1a0f0f', '#140d0d')"></button> <div class="btnTema t-custom"> <span style="pointer-events:none; z-index:1;">Custom</span> <input type="color" id="colorCustom" value="#ff8800"> </div> </div> </div> </div> <input type="range" id="prog" min="0" max="100" step="0.1" value="0"> <div id="contador">00:00 / 00:00 • <span id="currentTrack" style="font-weight:bold; color:var(--accent);">esperando motor...</span></div> <ul id="lista"></ul> <script> let audioCtx = new (window.AudioContext || window.webkitAudioContext)(); const player = document.createElement('audio'); const archivos = document.getElementById('archivos'), lista = document.getElementById('lista'), prog = document.getElementById('prog'), contador = document.getElementById('contador'), btnLimpiar = document.getElementById('btnLimpiar'); let playlist = [], index = 0, sourceNode, gainNode, filters = [], splitter, anaL, anaR; // --- TEMA Y COLORES --- function cambiarTema(color, bgPanel, bgBody) { document.documentElement.style.setProperty('--accent', color); if(bgPanel) document.documentElement.style.setProperty('--bg-panel', bgPanel); if(bgBody) document.documentElement.style.setProperty('--bg-body', bgBody); dibujarBtn(); } document.getElementById('colorCustom').oninput = (e) => cambiarTema(e.target.value); // --- NAVEGACIÓN Y TECLADO --- window.addEventListener('keydown', (e) => { if (playlist.length === 0) return; if (e.code === "Space") { e.preventDefault(); togglePlay(); } if (e.code === "ArrowDown") { e.preventDefault(); moverSeleccion(1); } if (e.code === "ArrowUp") { e.preventDefault(); moverSeleccion(-1); } if (e.code === "Enter") { cargar(); player.play(); } }); function moverSeleccion(dir) { index = (index + dir + playlist.length) % playlist.length; actualizarLista(); const activo = lista.querySelector('.active'); if (activo) activo.scrollIntoView({ block: "nearest", behavior: "smooth" }); } function togglePlay() { if (!player.src && playlist.length > 0) cargar(); if (player.src) player.paused ? player.play() : player.pause(); } // --- DRAG & DROP --- lista.addEventListener('dragstart', e => { if(e.target.tagName==='LI') e.target.classList.add('dragging'); }); lista.addEventListener('dragend', e => { if(e.target.tagName==='LI') { e.target.classList.remove('dragging'); reordenarPlaylist(); } }); lista.addEventListener('dragover', e => { e.preventDefault(); const dragging = document.querySelector('.dragging'); if (!dragging) return; const afterElement = getDragAfterElement(lista, e.clientY); if (afterElement == null) lista.appendChild(dragging); else lista.insertBefore(dragging, afterElement); }); function getDragAfterElement(container, y) { const elements = [...container.querySelectorAll('li:not(.dragging)')]; return elements.reduce((closest, child) => { const box = child.getBoundingClientRect(); const offset = y - box.top - box.height / 2; if (offset < 0 && offset > closest.offset) return { offset: offset, element: child }; else return closest; }, { offset: Number.NEGATIVE_INFINITY }).element; } function reordenarPlaylist() { const items = [...lista.querySelectorAll('li')]; const cancionActual = playlist[index]; const nuevaPlaylist = items.map(li => playlist[parseInt(li.getAttribute('data-id'))]); playlist = nuevaPlaylist; index = playlist.indexOf(cancionActual); renderLista(); // Refresca IDs } // --- AUDIO CORE --- player.onplay = () => { if(audioCtx.state === "suspended") audioCtx.resume(); initAudio(); dibujarBtn(); }; player.onpause = () => dibujarBtn(); function initAudio(){ if(sourceNode) return; sourceNode = audioCtx.createMediaElementSource(player); gainNode = audioCtx.createGain(); gainNode.gain.value = document.getElementById('vol').value; const freqs = [32, 64, 125, 500, 1000, 4000, 8000, 16000]; let lastNode = sourceNode; freqs.forEach((freq, i) => { const f = audioCtx.createBiquadFilter(); f.type = (i===0)?"lowshelf":(i===7)?"highshelf":"peaking"; f.frequency.value = freq; f.Q.value = 1.4; f.gain.value = document.getElementById('eq'+(i+1)).value; filters.push(f); lastNode.connect(f); lastNode = f; }); lastNode.connect(gainNode); gainNode.connect(audioCtx.destination); splitter = audioCtx.createChannelSplitter(2); anaL = audioCtx.createAnalyser(); anaR = audioCtx.createAnalyser(); anaL.fftSize = 256; anaR.fftSize = 256; gainNode.connect(splitter); splitter.connect(anaL,0); splitter.connect(anaR,1); } archivos.onchange = e => { const nuevos = [...e.target.files].filter(f=>f.type.startsWith("audio/")); if(!nuevos.length) return; playlist = playlist.concat(nuevos); renderLista(); if(!player.src) { index = 0; cargar(); } }; function renderLista(){ lista.innerHTML = ""; playlist.forEach((f,i)=>{ const li = document.createElement("li"); li.textContent = f.name; li.draggable = true; li.setAttribute('data-id', i); li.onclick = () => { index = i; cargar(); player.play(); }; lista.appendChild(li); }); actualizarLista(); } function actualizarLista(){ [...lista.children].forEach((li, i) => li.classList.toggle("active", i === index)); } function cargar(){ if(!playlist[index]) return; if(player.src) URL.revokeObjectURL(player.src); player.src = URL.createObjectURL(playlist[index]); player.load(); actualizarLista(); } // Sliders vol.oninput = e => { if(gainNode) gainNode.gain.value = e.target.value; document.getElementById('v-vol').textContent = Math.round(e.target.value*100)+'%'; }; [1,2,3,4,5,6,7,8].forEach(n => { document.getElementById('eq'+n).oninput = e => { if(filters[n-1]) filters[n-1].gain.value = e.target.value; document.getElementById('v-eq'+n).textContent = e.target.value; }; }); player.onended = () => { index = (index + 1) % playlist.length; cargar(); player.play(); }; player.ontimeupdate = () => { if(player.duration){ prog.max = player.duration; prog.value = player.currentTime; let m1=Math.floor(player.currentTime/60), s1=Math.floor(player.currentTime%60).toString().padStart(2,0); let m2=Math.floor(player.duration/60), s2=Math.floor(player.duration%60).toString().padStart(2,0); contador.innerHTML = `${m1}:${s1} / ${m2}:${s2} • <span style="color:var(--accent);">${playlist[index].name}</span>`; } }; prog.oninput = () => player.currentTime = prog.value; btnLimpiar.onclick = () => { playlist=[]; index=0; player.pause(); player.src=""; lista.innerHTML=""; }; // --- VISUALIZADORES --- const ctxL = document.getElementById("vuL").getContext("2d"), ctxR = document.getElementById("vuR").getContext("2d"), ctxECG = document.getElementById("vuECG").getContext("2d"), ctxSpec = document.getElementById("vuSpectrum").getContext("2d"), btnC = document.getElementById("btnPlay"), ctxBtn = btnC.getContext("2d"); let aL = -Math.PI, aR = -Math.PI; function aguja(ctx, ang){ ctx.clearRect(0,0,70,70); ctx.beginPath(); ctx.arc(35,35,28,-Math.PI,0,false); ctx.strokeStyle="#1a110f"; ctx.lineWidth=30; ctx.stroke(); ctx.save(); ctx.translate(35,35); ctx.rotate(ang); ctx.beginPath(); ctx.moveTo(0,0); ctx.lineTo(32,0); ctx.strokeStyle = getComputedStyle(document.documentElement).getPropertyValue('--accent'); ctx.lineWidth=4; ctx.stroke(); ctx.restore(); } function dibujarBtn(){ const acc = getComputedStyle(document.documentElement).getPropertyValue('--accent'); ctxBtn.clearRect(0,0,70,70); ctxBtn.beginPath(); ctxBtn.arc(35,35,30,0,Math.PI*2); ctxBtn.fillStyle = !player.paused?acc:"#42322f"; ctxBtn.fill(); ctxBtn.fillStyle="black"; if(player.paused){ ctxBtn.beginPath(); ctxBtn.moveTo(28,22); ctxBtn.lineTo(28,48); ctxBtn.lineTo(48,35); ctxBtn.fill(); } else { ctxBtn.fillRect(28,22,6,26); ctxBtn.fillRect(40,22,6,26); } } function animar(){ if(anaL){ let dE = new Uint8Array(anaL.fftSize); anaL.getByteTimeDomainData(dE); ctxECG.clearRect(0,0,70,70); ctxECG.beginPath(); ctxECG.moveTo(0,35); for(let i=0;i<dE.length;i++) ctxECG.lineTo(i/dE.length*70, 35+(dE[i]-128)/128*30); ctxECG.strokeStyle = getComputedStyle(document.documentElement).getPropertyValue('--accent'); ctxECG.stroke(); let dS = new Uint8Array(anaR.frequencyBinCount); anaR.getByteFrequencyData(dS); ctxSpec.clearRect(0,0,70,70); for(let i=0;i<dS.length;i++) { ctxSpec.fillStyle=`hsl(${i/dS.length*360},100%,50%)`; ctxSpec.fillRect(i*(70/dS.length),70-dS[i]/255*70,70/dS.length,dS[i]/255*70); } let L=new Uint8Array(anaL.frequencyBinCount), R=new Uint8Array(anaR.frequencyBinCount); anaL.getByteFrequencyData(L); anaR.getByteFrequencyData(R); aL += ((-1.57 + L.reduce((a,b)=>a+b,0)/L.length/255*3.14) - aL)*0.1; aR += ((-1.57 + R.reduce((a,b)=>a+b,0)/R.length/255*3.14) - aR)*0.1; aguja(ctxL,aL); aguja(ctxR,aR); } requestAnimationFrame(animar); } btnC.onclick = togglePlay; dibujarBtn(); animar(); </script> <div id="armoured-final-row" style="width:100%; margin-top:5px; background:#1a110f; border:1px solid #3a2c28; border-radius:4px; padding:8px; display:flex; align-items:center; gap:10px; box-shadow:inset 0 0 5px #000; box-sizing:border-box;"> <button id="z-mic" style="background:#c0392b; color:white; border:none; padding:6px 10px; border-radius:3px; font-weight:bold; cursor:pointer; font-size:9px;">🔴 MIC</button> <input type="range" id="z-l" min="-15" max="15" value="0" style="width:80px; height:4px; cursor:pointer; accent-color:orange;">low <input type="range" id="z-m" min="-15" max="15" value="0" style="width:80px; height:4px; cursor:pointer; accent-color:orange;">mid <input type="range" id="z-h" min="-15" max="15" value="0" style="width:80px; height:4px; cursor:pointer; accent-color:orange;">hi <div style="width:50px; height:10px; background:#000; border:1px solid #333; border-radius:2px; overflow:hidden;"> <div id="z-vu" style="height:100%; width:0%; background:linear-gradient(90deg, #27ae60, #f1c40f, #e74c3c); transition:width 0.05s;"></div> </div> <button id="z-pv" style="background:#333; color:#aaa; border:none; padding:6px 8px; border-radius:3px; font-size:9px; cursor:pointer;">OÍR</button> <div style="width:1px; height:20px; background:#333;"></div> <button id="z-cp" style="background:#e67e22; color:white; border:none; padding:6px 10px; border-radius:3px; font-weight:bold; cursor:pointer; font-size:9px;">🟠 COMPILAR</button> <button id="z-pp" style="background:#333; color:#aaa; border:none; padding:6px 8px; border-radius:3px; font-size:9px; cursor:pointer;">PREVIEW</button> <div style="width:100px; height:10px; background:#000; border:1px solid #333; border-radius:2px; overflow:hidden;"> <div id="z-vu-p" style="height:100%; width:0%; background:linear-gradient(90deg, #3498db, #9b59b6, #e74c3c); transition:width 0.05s;"></div> </div> <button id="z-dl" style="background:#27ae60; color:white; border:none; padding:6px 12px; border-radius:3px; font-weight:bold; cursor:pointer; font-size:9px; flex-grow:1;">💾 DESCARGAR MP3</button><h1 style="color:var(--accent); font-size: 12px; margin-top:20px; font-weight: bold; text-shadow: 2px 2px 5px black;">Trisha TINY podcaster-BJ7</h1><font color="#3a2c28">............. <audio id="z-am" style="display:none"></audio> <audio id="z-ac" style="display:none"></audio> <script> (function() { let s, r, ch = [], ctx, an, fl = [], micNode, pAn; const bM = document.getElementById('z-mic'), aM = document.getElementById('z-am'), vM = document.getElementById('z-vu'), vP = document.getElementById('z-vu-p'), aC = document.getElementById('z-ac'), btnOir = document.getElementById('z-pv'), btnPreview = document.getElementById('z-pp'); // --- Mic bM.onclick = async () => { if (!s) { try { s = await navigator.mediaDevices.getUserMedia({ audio: true }); ctx = (typeof audioCtx !== 'undefined') ? audioCtx : new AudioContext(); let src = ctx.createMediaStreamSource(s), n = src; an = ctx.createAnalyser(); [250, 1200, 5000].forEach((f,i)=>{ let flt = ctx.createBiquadFilter(); flt.type = i==1?'peaking':i==0?'lowshelf':'highshelf'; flt.frequency.value = f; fl[i] = flt; n.connect(flt); n = flt; }); micNode = n; const d = ctx.createMediaStreamDestination(); n.connect(an); n.connect(d); r = new MediaRecorder(d.stream); ch = []; r.ondataavailable = e => ch.push(e.data); r.onstop = () => aM.src = URL.createObjectURL(new Blob(ch, {type:'audio/mp3'})); r.start(); bM.textContent = "⏹ STOP"; // Vúmetro Mic const lp = () => { if(!s) { vM.style.width="0%"; return; } const dt = new Uint8Array(an.frequencyBinCount); an.getByteFrequencyData(dt); vM.style.width = (dt.reduce((a,b)=>a+b)/dt.length)*2.5 + "%"; requestAnimationFrame(lp); }; lp(); } catch(e){ console.error(e); } } else { r.stop(); s.getTracks().forEach(t=>t.stop()); s=null; bM.textContent="🔴 MIC"; vM.style.width="0%"; } }; // --- Compilar let rC, chC = []; document.getElementById('z-cp').onclick = function() { if(!rC || rC.state === "inactive") { if(typeof gainNode === 'undefined') return; const d = gainNode.context.createMediaStreamDestination(); gainNode.connect(d); if(s && micNode) micNode.connect(d); rC = new MediaRecorder(d.stream); chC=[]; rC.ondataavailable = e => chC.push(e.data); rC.onstop = () => aC.src = URL.createObjectURL(new Blob(chC, {type:'audio/mp3'})); rC.start(); this.textContent="⏹ STOP"; } else { rC.stop(); this.textContent="🟠 COMPILAR"; if(micNode) try{micNode.disconnect();}catch(e){} } }; // --- OÍR Mic Playback con Vúmetro btnOir.onclick = () => { if(!aM.src) return; if(aM.paused) aM.play(); else aM.pause(); btnOir.textContent = aM.paused ? "OÍR" : "⏹ STOP"; const updateVU = () => { if(aM.paused){ vM.style.width="0%"; return; } if(!ctx) ctx = (typeof audioCtx !== 'undefined') ? audioCtx : new AudioContext(); if(!an) { an = ctx.createAnalyser(); const src = ctx.createMediaElementSource(aM); src.connect(an); an.connect(ctx.destination); } const data = new Uint8Array(an.frequencyBinCount); an.getByteFrequencyData(data); vM.style.width = (data.reduce((a,b)=>a+b)/data.length)*2.5 + "%"; requestAnimationFrame(updateVU); }; updateVU(); }; // --- Preview Compilado con Vúmetro btnPreview.onclick = () => { if(!aC.src) return; if(aC.paused) aC.play(); else aC.pause(); btnPreview.textContent = aC.paused ? "PREVIEW" : "⏹ STOP"; if(!pAn) { const pCtx = (typeof audioCtx !== 'undefined') ? audioCtx : new AudioContext(); pAn = pCtx.createAnalyser(); const pSrc = pCtx.createMediaElementSource(aC); pSrc.connect(pAn); pAn.connect(pCtx.destination); } const loopVU = () => { if(aC.paused){ vP.style.width="0%"; return; } const data = new Uint8Array(pAn.frequencyBinCount); pAn.getByteFrequencyData(data); vP.style.width = (data.reduce((a,b)=>a+b)/data.length)*2.5 + "%"; requestAnimationFrame(loopVU); }; loopVU(); }; // --- Descargar document.getElementById('z-dl').onclick = () => { if(!aC.src) return; const a = document.createElement('a'); a.href=aC.src; a.download='mix_final.mp3'; a.click(); }; // --- EQ Mic ['z-l','z-m','z-h'].forEach((id,i)=> document.getElementById(id).oninput = e => fl[i] && (fl[i].gain.value = e.target.value)); })(); </script> <button id="btn-bj8-final" style="background:#2a2a2a; color:#fff; border:1px solid orange; padding:8px 15px; border-radius:4px; font-weight:bold; cursor:pointer; font-size:10px; text-transform:uppercase; transition: 0.2s;"> MONITOR </button> </div> <style> #evo-panel { display: none; position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: #000; z-index: 20000; padding: 20px; box-sizing: border-box; } .evo-container { display: flex; flex-direction: column; height: 100%; gap: 10px; max-width: 1400px; margin: 0 auto; } .evo-header { display: flex; justify-content: space-between; color: #0f0; background: #111; padding: 8px 15px; border: 1px solid #333; font-family: monospace; } /* Canvas del Mapeo (Scrolling) */ #evo-canvas-wave { width: 100%; height: 50%; background: #000500; border: 1px solid #004400; border-radius: 4px; image-rendering: pixelated; /* Para que se vea nítido como el software viejo */ } /* Canvas del EQ */ #evo-canvas-bars { width: 100%; height: 40%; background: #000; border: 1px solid #333; border-radius: 4px; } .evo-close { cursor: pointer; color: #fff; background: #c0392b; padding: 0 10px; border-radius: 3px; } </style> <script> document.addEventListener("DOMContentLoaded", function() { const row = document.getElementById('armoured-final-row'); const btn = document.createElement('button'); btn.className = 'btn-evo'; btn.style = "background:#333; color:#0f0; border:1px solid #0f0; padding:6px 12px; border-radius:3px; font-weight:bold; cursor:pointer; font-size:9px;"; btn.innerHTML = '📟 GOLDWAVE MAP'; btn.onclick = toggleEvoPanel; row.insertBefore(btn, document.getElementById('z-dl')); }); let evoAnalyser, waveCtx, barsCtx, waveCanvas; let xScroll = 0; function toggleEvoPanel() { const panel = document.getElementById('evo-panel'); panel.style.display = (panel.style.display === 'block') ? 'none' : 'block'; if (panel.style.display === 'block') setupEvoLogic(); } function setupEvoLogic() { if (evoAnalyser) return; if (typeof audioCtx === 'undefined') return; evoAnalyser = audioCtx.createAnalyser(); evoAnalyser.fftSize = 512; if (typeof gainNode !== 'undefined') gainNode.connect(evoAnalyser); waveCanvas = document.getElementById('evo-canvas-wave'); waveCtx = waveCanvas.getContext('2d'); barsCtx = document.getElementById('evo-canvas-bars').getContext('2d'); // Limpiamos fondo inicial waveCtx.fillStyle = '#000500'; waveCtx.fillRect(0, 0, waveCanvas.width, waveCanvas.height); function render() { requestAnimationFrame(render); const bufferLength = evoAnalyser.frequencyBinCount; const dataFreq = new Uint8Array(bufferLength); const dataWave = new Uint8Array(bufferLength); evoAnalyser.getByteFrequencyData(dataFreq); evoAnalyser.getByteTimeDomainData(dataWave); // --- 1. MAPEO SCROLLING (ESTILO GOLDWAVE) --- // Desplazamos lo dibujado 2 píxeles a la izquierda const imageData = waveCtx.getImageData(2, 0, waveCanvas.width - 2, waveCanvas.height); waveCtx.putImageData(imageData, 0, 0); // Limpiamos la franja nueva a la derecha waveCtx.fillStyle = '#000500'; waveCtx.fillRect(waveCanvas.width - 2, 0, 2, waveCanvas.height); // Dibujamos el pico de audio actual en el borde derecho let min = 255, max = 0; for(let i=0; i<bufferLength; i++) { if(dataWave[i] < min) min = dataWave[i]; if(dataWave[i] > max) max = dataWave[i]; } const yMin = (min / 255) * waveCanvas.height; const yMax = (max / 255) * waveCanvas.height; waveCtx.strokeStyle = '#00ff00'; waveCtx.lineWidth = 2; waveCtx.beginPath(); waveCtx.moveTo(waveCanvas.width - 1, yMin); waveCtx.lineTo(waveCanvas.width - 1, yMax); waveCtx.stroke(); // --- 2. EQ ARCOIRIS (BARRAS GRUESAS) --- const bC = document.getElementById('evo-canvas-bars'); barsCtx.fillStyle = '#000'; barsCtx.fillRect(0, 0, bC.width, bC.height); let barWidth = (bC.width / (bufferLength/2)); let xB = 0; for(let i = 0; i < bufferLength/2; i++) { let barHeight = (dataFreq[i] / 255) * bC.height; let hue = (i / (bufferLength/2)) * 300; barsCtx.fillStyle = `hsl(${hue}, 100%, 50%)`; barsCtx.fillRect(xB, bC.height - barHeight, barWidth - 2, barHeight); xB += barWidth; } } render(); } </script> <div id="evo-panel"> <div class="evo-container"> <div class="evo-header"> <span>GOLDWAVE REAL-TIME MAPPING // TINY-BJ7</span> <span class="evo-close" onclick="toggleEvoPanel()">EXIT [X]</span> </div> <label style="color:#0f0; font-size:10px; margin-left:5px;">SCROLLING WAVEFORM ANALYZER</label> <canvas id="evo-canvas-wave" width="1200" height="100"></canvas> <label style="color:#0f0; font-size:10px; margin-left:5px;">COLOR SPECTRUM BARS</label> <canvas id="evo-canvas-bars" width="1200" height="150"></canvas> </div> <style> #evo-transport{ position:absolute; bottom:0; left:0; width:100%; background:#000; display:flex; align-items:center; justify-content:center; gap:14px; padding:10px; border-top:2px solid #111; z-index:9999; } /* BOTONES */ #evo-transport button{ width:46px; height:46px; background:#000; border:2px solid #fff; border-radius:50%; color:#fff; font-size:18px; display:flex; align-items:center; justify-content:center; cursor:pointer; } #evo-transport button:hover{ background:#111; } /* SLIDERS */ .eq-h{ width:490px; accent-color:white; } .eq-label{ color:white; font-size:10px; font-family:monospace; } </style> <div id="evo-transport"> <button id="ev-prev">⏮</button> <button id="ev-play">▶</button> <button id="ev-pause">⏸</button> <button id="ev-next">⏭</button> <span class="eq-label">VOL</span> <input class="eq-h" id="ev-vol" type="range" min="0" max="1" step="0.01" value="0.7"> </div> <script> let eqLow, eqMid, eqHigh; function initMiniEQ(){ if(!audioCtx || !gainNode) return; eqLow = audioCtx.createBiquadFilter(); eqLow.type="lowshelf"; eqLow.frequency.value=200; eqMid = audioCtx.createBiquadFilter(); eqMid.type="peaking"; eqMid.frequency.value=1200; eqMid.Q.value=1; eqHigh = audioCtx.createBiquadFilter(); eqHigh.type="highshelf"; eqHigh.frequency.value=6000; gainNode.disconnect(); gainNode.connect(eqLow); eqLow.connect(eqMid); eqMid.connect(eqHigh); eqHigh.connect(audioCtx.destination); } setTimeout(initMiniEQ,1000); /* CONTROLES PLAYER */ document.getElementById("ev-play").onclick=()=>{ if(!player.src && playlist.length>0) cargar(); player.play(); } document.getElementById("ev-pause").onclick=()=>{ player.pause(); } document.getElementById("ev-prev").onclick=()=>{ if(playlist.length===0)return; index=(index-1+playlist.length)%playlist.length; cargar(); player.play(); } document.getElementById("ev-next").onclick=()=>{ if(playlist.length===0)return; index=(index+1)%playlist.length; cargar(); player.play(); } /* VOLUMEN */ document.getElementById("ev-vol").oninput=(e)=>{ player.volume=e.target.value; } /* EQ 3 BANDAS */ document.getElementById("ev-low").oninput=(e)=>{ if(eqLow) eqLow.gain.value=e.target.value; } document.getElementById("ev-mid").oninput=(e)=>{ if(eqMid) eqMid.gain.value=e.target.value; } document.getElementById("ev-high").oninput=(e)=>{ if(eqHigh) eqHigh.gain.value=e.target.value; } </script> </div> <style> #bj8-overlay { display: none; position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: #000; /* NEGRO AFUERA */ z-index: 999999; padding: 25px; box-sizing: border-box; font-family: 'Courier New', monospace; } .bj8-main { display: flex; flex-direction: column; height: 100%; max-width: 1200px; margin: 0 auto; gap: 15px; } /* CONTENEDORES GRIS LISO (COMO LA FOTO) */ .bj8-box { background: #333; /* GRIS LISO ADENTRO */ border: 1px solid #444; flex: 1; position: relative; overflow: hidden; border-radius: 5px; } .bj8-label { position: absolute; top: 10px; left: 15px; color: #fff; font-size: 11px; background: rgba(0,0,0,0.6); padding: 4px 8px; border-radius: 3px; font-weight: bold; z-index: 5; } canvas.bj8-canvas { width: 100%; height: 100%; display: block; } .bj8-footer { display: grid; grid-template-columns: repeat(4, 1fr); background: #111; padding: 15px; border: 1px solid #222; border-radius: 5px; color: #666; font-size: 11px; } </style> <div id="bj8-overlay"> <div class="bj8-main"> <div style="display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #333; padding-bottom: 10px;"> <span style="color: orange; font-weight: bold; letter-spacing: 2px;">SYSTEM: BJ8-ARMORED // MONITOR ACTIVE</span> <button onclick="document.getElementById('bj8-overlay').style.display='none'" style="background: #c0392b; color: white; border: none; padding: 8px 16px; cursor: pointer; font-weight: bold; border-radius: 4px;">EXIT [X]</button> </div> <div class="bj8-box"> <div class="bj8-label">CH 1 - LEFT SIDE</div> <canvas id="bj8-cv-l" class="bj8-canvas"></canvas> </div> <div class="bj8-box"> <div class="bj8-label">CH 2 - RIGHT SIDE</div> <canvas id="bj8-cv-r" class="bj8-canvas"></canvas> </div> <div class="bj8-footer"> <div>MODE: <span style="color:white">REAL_TIME</span></div> <div>SYNC: <span style="color:#0f0">LOCKED</span></div> <div>BUFFER: <span style="color:white">1024 SMPL</span></div> <div>ENGINE: <span style="color:orange">TRISHA-BJ8</span></div> </div> </div> </div> <script> (function() { let bj8Analizador, ctxL, ctxR, cL, cR; const scrollX = 4; document.getElementById('btn-bj8-final').onclick = function() { document.getElementById('bj8-overlay').style.display = 'block'; if (!bj8Analizador) arrancarBJ8(); }; function arrancarBJ8() { // BUSCAMOS TU MOTOR EXISTENTE if (typeof audioCtx === 'undefined') { console.error("No se detectó el Contexto de Audio."); return; } // Creamos un analizador nuevo bj8Analizador = audioCtx.createAnalyser(); bj8Analizador.fftSize = 2048; // SE CONECTA AL gainNode SIN DESCONECTAR EL VOLUMEN // El audio sigue saliendo por tus parlantes, pero le enviamos una "copia" al monitor if (typeof gainNode !== 'undefined') { gainNode.connect(bj8Analizador); } cL = document.getElementById('bj8-cv-l'); cR = document.getElementById('bj8-cv-r'); cL.width = cL.offsetWidth; cL.height = cL.offsetHeight; cR.width = cR.offsetWidth; cR.height = cR.offsetHeight; ctxL = cL.getContext('2d', { alpha: false }); ctxR = cR.getContext('2d', { alpha: false }); // Pintamos el gris liso inicial ctxL.fillStyle = ctxR.fillStyle = '#333'; ctxL.fillRect(0, 0, cL.width, cL.height); ctxR.fillRect(0, 0, cR.width, cR.height); dibujarBJ8(); } function dibujarBJ8() { requestAnimationFrame(dibujarBJ8); if (document.getElementById('bj8-overlay').style.display === 'none') return; const data = new Uint8Array(bj8Analizador.frequencyBinCount); bj8Analizador.getByteTimeDomainData(data); // Efecto Scroll [ctxL, ctxR].forEach(ctx => { const tempImg = ctx.getImageData(scrollX, 0, cL.width - scrollX, cL.height); ctx.putImageData(tempImg, 0, 0); ctx.fillStyle = '#333'; // Gris liso tapa el rastro ctx.fillRect(cL.width - scrollX, 0, scrollX, cL.height); }); // Buscar picos para la onda let min = 255, max = 0; for(let i=0; i < data.length; i++) { if(data[i] < min) min = data[i]; if(data[i] > max) max = data[i]; } const yMin = (min / 255) * cL.height; const yMax = (max / 255) * cL.height; // Dibujo de la línea Blanca (como en la foto) [ctxL, ctxR].forEach(ctx => { ctx.strokeStyle = '#ffffff'; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(cL.width - 2, yMin); ctx.lineTo(cL.width - 2, yMax); ctx.stroke(); }); } })(); </script> </div></div> <script> (function() { const canvas = document.getElementById('edit-canvas'); const ctx = canvas.getContext('2d'); const audioCompilado = document.getElementById('z-ac'); const btnCrop = document.getElementById('btn-crop'); const btnReset = document.getElementById('btn-reset-edit'); let selection = { start: 0, end: canvas.width, active: false }; let bufferOriginal = null; // Detectar cuando hay audio nuevo compilado para cargar la onda audioCompilado.addEventListener('canplaythrough', async () => { const response = await fetch(audioCompilado.src); const arrayBuffer = await response.arrayBuffer(); const aCtx = new (window.AudioContext || window.webkitAudioContext)(); bufferOriginal = await aCtx.decodeAudioData(arrayBuffer); dibujarOnda(bufferOriginal); }); function dibujarOnda(buffer) { const data = buffer.getChannelData(0); const step = Math.ceil(data.length / canvas.width); const amp = canvas.height / 2; ctx.fillStyle = "#000"; ctx.fillRect(0, 0, canvas.width, canvas.height); // Dibujar selección ctx.fillStyle = "rgba(255, 165, 0, 0.2)"; ctx.fillRect(selection.start, 0, selection.end - selection.start, canvas.height); ctx.beginPath(); ctx.strokeStyle = "orange"; for(let i=0; i < canvas.width; i++) { let min = 1.0, max = -1.0; for (let j=0; j<step; j++) { let datum = data[(i*step)+j]; if (datum < min) min = datum; if (datum > max) max = datum; } ctx.moveTo(i, (1+min)*amp); ctx.lineTo(i, (1+max)*amp); } ctx.stroke(); } // Interacción de Selección canvas.onmousedown = (e) => { const rect = canvas.getBoundingClientRect(); selection.start = e.clientX - rect.left; selection.active = true; }; window.onmousemove = (e) => { if (!selection.active) return; const rect = canvas.getBoundingClientRect(); selection.end = Math.max(0, Math.min(canvas.width, e.clientX - rect.left)); dibujarOnda(bufferOriginal); actualizarTiempos(); }; window.onmouseup = () => selection.active = false; function actualizarTiempos() { if (!bufferOriginal) return; const dur = bufferOriginal.duration; const tIn = (selection.start / canvas.width) * dur; const tOut = (selection.end / canvas.width) * dur; document.getElementById('edit-time-start').textContent = `IN: ${tIn.toFixed(2)}s`; document.getElementById('edit-time-end').textContent = `OUT: ${tOut.toFixed(2)}s`; } // Lógica de Recorte (Offline Audio Context) btnCrop.onclick = async () => { if (!bufferOriginal) return; const dur = bufferOriginal.duration; const tStart = Math.min(selection.start, selection.end) / canvas.width * dur; const tEnd = Math.max(selection.start, selection.end) / canvas.width * dur; const newDur = tEnd - tStart; const offlineCtx = new OfflineAudioContext(bufferOriginal.numberOfChannels, newDur * bufferOriginal.sampleRate, bufferOriginal.sampleRate); const source = offlineCtx.createBufferSource(); source.buffer = bufferOriginal; const gain = offlineCtx.createGain(); gain.gain.value = document.getElementById('edit-gain').value; source.connect(gain); gain.connect(offlineCtx.destination); source.start(0, tStart, newDur); const renderedBuffer = await offlineCtx.startRendering(); // Convertir de nuevo a Blob/URL const wavBlob = bufferToWave(renderedBuffer, renderedBuffer.length); audioCompilado.src = URL.createObjectURL(wavBlob); bufferOriginal = renderedBuffer; dibujarOnda(bufferOriginal); alert("Audio recortado y normalizado con éxito."); }; // Helper para exportar WAV (simplificado) function bufferToWave(abuffer, len) { let numOfChan = abuffer.numberOfChannels, length = len * numOfChan * 2 + 44, buffer = new ArrayBuffer(length), view = new DataView(buffer), channels = [], i, sample, offset = 0, pos = 0; setUint32(0x46464952); setUint32(length - 8); setUint32(0x45564157); setUint32(0x20746d66); setUint32(16); setUint16(1); setUint16(numOfChan); setUint32(abuffer.sampleRate); setUint32(abuffer.sampleRate * 2 * numOfChan); setUint16(numOfChan * 2); setUint16(16); setUint32(0x61746164); setUint32(length - pos - 4); for(i=0; i<abuffer.numberOfChannels; i++) channels.push(abuffer.getChannelData(i)); while(pos < len) { for(i=0; i<numOfChan; i++) { sample = Math.max(-1, Math.min(1, channels[i][pos])); view.setInt16(44 + offset, sample < 0 ? sample * 0x8000 : sample * 0x7FFF, true); offset += 2; } pos++; } return new Blob([view], {type: "audio/wav"}); function setUint16(data) { view.setUint16(pos, data, true); pos += 2; } function setUint32(data) { view.setUint32(pos, data, true); pos += 4; } } })(); </script> <div id="bj8-full-editor" style="display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:#080808; z-index:999999; font-family:'Courier New', monospace; color:#eee; flex-direction:column;"> <div style="background:#151515; padding:15px 25px; border-bottom:2px solid orange; display:flex; justify-content:space-between; align-items:center;"> <span style="letter-spacing:2px; font-weight:bold; color:orange;">📟 BJ8 ARMORED // STEREO STUDIO v3.0</span> <button onclick="closeStudio()" style="background:#c0392b; color:white; border:none; padding:8px 15px; cursor:pointer; font-weight:bold; border-radius:3px;">[X] SALIR</button> </div> <div style="flex:1; padding:20px; display:flex; flex-direction:column; overflow:hidden;"> <div id="st-viewport" style="flex:1; background:#000; border:1px solid #333; position:relative; overflow-x:auto; border-radius:4px;"> <canvas id="st-canvas" style="display:block; height:100%; cursor:crosshair;"></canvas> <div id="st-playhead" style="position:absolute; top:0; left:0; width:2px; height:100%; background:#0f0; box-shadow:0 0 10px #0f0; pointer-events:none;"></div> </div> <div style="margin-top:15px; display:flex; justify-content:space-between; align-items:center; background:#111; padding:15px; border-radius:5px;"> <div style="display:flex; gap:8px;"> <button class="st-btn" onclick="stProcess('cut')">✂ CORTAR</button> <button class="st-btn" onclick="stProcess('copy')">📋 COPIAR</button> <button class="st-btn" onclick="stProcess('paste')">📥 PEGAR</button> <button class="st-btn" onclick="stProcess('del')" style="color:#ff4444;">🗑 BORRAR</button> <div style="width:2px; background:#333; margin:0 5px;"></div> <button class="st-btn" onclick="stUndo()" style="border-color:#5dade2; color:#5dade2;">↩ UNDO</button> <button class="st-btn" onclick="stRedo()" style="border-color:#5dade2; color:#5dade2;">↷ REDO</button> </div> <div style="display:flex; align-items:center; gap:12px; font-size:11px;"> <span>ZOOM:</span> <input type="range" id="st-zoom" min="1" max="40" step="0.5" value="1" style="width:150px; accent-color:orange;"> <span id="st-status" style="color:orange; font-weight:bold;">READY</span> </div> </div> </div> <div style="background:#111; padding:20px 30px; border-top:1px solid #333; display:grid; grid-template-columns:1fr 1fr 1fr; align-items:center;"> <div> <div style="font-size:9px; color:#555;">STEREO PEAK LEVEL</div> <div style="width:100%; height:12px; background:#000; border:1px solid #333; border-radius:10px; overflow:hidden; margin-top:5px;"> <div id="st-vu" style="height:100%; width:0%; background:linear-gradient(90deg, #2ecc71, #f1c40f, #e74c3c); transition:width 0.05s;"></div> </div> </div> <div style="display:flex; justify-content:center; gap:15px; align-items:center;"> <button class="st-btn-round" onclick="stStop()">⏹</button> <button class="st-btn-round" id="st-play-btn" onclick="stTogglePlay()" style="width:60px; height:60px; border-color:orange; font-size:22px;">▶</button> <button class="st-btn" onclick="stExport()" style="background:orange; color:black; padding:12px 20px;">💾 EXPORTAR MP3 ESTÉREO</button> </div> <div style="text-align:right;"> <div id="st-clock" style="font-size:30px; color:white; font-weight:bold;">00:00.00</div> </div> </div> </div> <style> .st-btn { background:#1a1a1a; color:orange; border:1px solid #444; padding:8px 12px; cursor:pointer; font-weight:bold; font-size:10px; border-radius:3px; } .st-btn:hover { background:orange; color:black; } .st-btn-round { background:#151515; color:white; border:2px solid #333; border-radius:50%; width:50px; height:50px; cursor:pointer; display:flex; align-items:center; justify-content:center; } .st-btn-round:hover { border-color:orange; color:orange; } </style> <script src="https://cdnjs.cloudflare.com/ajax/libs/lamejs/1.2.1/lame.all.min.js"></script> <script> (function() { let audioCtx = new (window.AudioContext || window.webkitAudioContext)(); let buffer, source, analyser = audioCtx.createAnalyser(); let isPlaying = false, offset = 0, startTime = 0, zoom = 1; let selection = { start: 0, end: 0 }, clipboard = null; let undoStack = [], redoStack = []; const modal = document.getElementById('bj8-full-editor'); const canvas = document.getElementById('st-canvas'); const ctx = canvas.getContext('2d'); const playhead = document.getElementById('st-playhead'); // Botón de Apertura const btnOpen = document.createElement('button'); btnOpen.innerHTML = "✂ EDITOR"; btnOpen.style = "background:#000; color:orange; border:1px solid orange; padding:10px; cursor:pointer; font-weight:bold; margin:10px; border-radius:4px;"; btnOpen.onclick = async () => { const src = document.getElementById('z-ac').src; if(!src) return alert("❌ Primero compila el audio."); modal.style.display = 'flex'; const resp = await fetch(src); const ab = await resp.arrayBuffer(); buffer = await audioCtx.decodeAudioData(ab); draw(); }; document.getElementById('armoured-final-row').appendChild(btnOpen); window.closeStudio = () => { stStop(); modal.style.display = 'none'; }; function cloneBuffer(inB) { const out = audioCtx.createBuffer(inB.numberOfChannels, inB.length, inB.sampleRate); for(let i=0; i<inB.numberOfChannels; i++) out.copyToChannel(new Float32Array(inB.getChannelData(i)), i); return out; } function saveState() { redoStack = []; if(undoStack.length >= 15) undoStack.shift(); undoStack.push(cloneBuffer(buffer)); } function draw() { if(!buffer) return; const w = (window.innerWidth - 60) * zoom; canvas.width = w; canvas.height = canvas.parentElement.clientHeight; const leftData = buffer.getChannelData(0); const step = Math.ceil(leftData.length / w); const amp = canvas.height / 2; ctx.fillStyle = "#000"; ctx.fillRect(0,0,w,canvas.height); // Selección if(selection.start !== selection.end) { const x1 = (Math.min(selection.start, selection.end)/buffer.duration)*w; const x2 = (Math.max(selection.start, selection.end)/buffer.duration)*w; ctx.fillStyle = "rgba(255, 165, 0, 0.25)"; ctx.fillRect(x1,0,x2-x1,canvas.height); } ctx.strokeStyle = "orange"; ctx.beginPath(); for(let i=0; i<w; i++){ let min=1, max=-1; for(let j=0; j<step; j++){ let d = leftData[(i*step)+j]; if(d<min) min=d; if(d>max) max=d; } ctx.moveTo(i, (1+min)*amp); ctx.lineTo(i, (1+max)*amp); } ctx.stroke(); } window.stTogglePlay = () => { if(isPlaying) { stPause(); return; } source = audioCtx.createBufferSource(); source.buffer = buffer; source.connect(analyser); analyser.connect(audioCtx.destination); source.start(0, offset); startTime = audioCtx.currentTime - offset; isPlaying = true; document.getElementById('st-play-btn').innerText = "⏸"; loop(); }; function stPause() { if(source) source.stop(); isPlaying = false; document.getElementById('st-play-btn').innerText = "▶"; } window.stStop = () => { stPause(); offset = 0; playhead.style.left = "0px"; document.getElementById('st-clock').innerText = "00:00.00"; }; function loop() { if(!isPlaying) return; offset = audioCtx.currentTime - startTime; if(offset >= buffer.duration) { stStop(); return; } playhead.style.left = (offset/buffer.duration)*canvas.width + "px"; const m = Math.floor(offset/60), s = Math.floor(offset%60); document.getElementById('st-clock').innerText = `${m.toString().padStart(2,0)}:${s.toString().padStart(2,0)}`; const f = new Uint8Array(analyser.frequencyBinCount); analyser.getByteFrequencyData(f); document.getElementById('st-vu').style.width = Math.min(100, (f.reduce((a,b)=>a+b)/f.length)*1.8) + "%"; requestAnimationFrame(loop); } window.stUndo = () => { if(undoStack.length) { redoStack.push(cloneBuffer(buffer)); buffer = undoStack.pop(); draw(); updateStatus("UNDO"); } }; window.stRedo = () => { if(redoStack.length) { undoStack.push(cloneBuffer(buffer)); buffer = redoStack.pop(); draw(); updateStatus("REDO"); } }; function updateStatus(t) { const s=document.getElementById('st-status'); s.innerText=t; setTimeout(()=>s.innerText="READY", 1200); } window.stProcess = (type) => { const s = Math.floor(Math.min(selection.start, selection.end) * buffer.sampleRate); const e = Math.floor(Math.max(selection.start, selection.end) * buffer.sampleRate); const chans = buffer.numberOfChannels; if(type==='copy'||type==='cut') { if(s===e) return; clipboard = audioCtx.createBuffer(chans, e-s, buffer.sampleRate); for(let i=0; i<chans; i++) clipboard.copyToChannel(buffer.getChannelData(i).slice(s,e), i); if(type==='copy') return updateStatus("COPIED"); } if(type==='del'||type==='cut') { if(s===e) return; saveState(); const nb = audioCtx.createBuffer(chans, buffer.length-(e-s), buffer.sampleRate); for(let i=0; i<chans; i++){ const o = buffer.getChannelData(i), n = new Float32Array(nb.length); n.set(o.subarray(0,s)); n.set(o.subarray(e),s); nb.copyToChannel(n,i); } buffer = nb; selection.start = selection.end = 0; draw(); updateStatus("EDITED"); } if(type==='paste' && clipboard) { saveState(); const p = Math.floor(offset * buffer.sampleRate), nb = audioCtx.createBuffer(chans, buffer.length+clipboard.length, buffer.sampleRate); for(let i=0; i<chans; i++){ const o = buffer.getChannelData(i), c = clipboard.getChannelData(i), n = new Float32Array(nb.length); n.set(o.subarray(0,p)); n.set(c,p); n.set(o.subarray(p),p+c.length); nb.copyToChannel(n,i); } buffer = nb; draw(); updateStatus("PASTED"); } }; window.stExport = () => { updateStatus("ENCODING STEREO..."); const encoder = new lamejs.Mp3Encoder(buffer.numberOfChannels, buffer.sampleRate, 128); const mp3 = []; const L = buffer.getChannelData(0); const R = buffer.numberOfChannels > 1 ? buffer.getChannelData(1) : L; const lInt = new Int16Array(L.length); const rInt = new Int16Array(R.length); for(let i=0; i<L.length; i++){ lInt[i] = Math.max(-1, Math.min(1, L[i])) * 0x7FFF; rInt[i] = Math.max(-1, Math.min(1, R[i])) * 0x7FFF; } for(let i=0; i<lInt.length; i+=1152){ const buf = encoder.encodeBuffer(lInt.subarray(i, i+1152), rInt.subarray(i, i+1152)); if(buf.length > 0) mp3.push(buf); } const f = encoder.flush(); if(f.length > 0) mp3.push(f); const a = document.createElement('a'); a.href = URL.createObjectURL(new Blob(mp3, {type:'audio/mp3'})); a.download = 'BJ8_STEREO_MASTER.mp3'; a.click(); updateStatus("DONE!"); }; document.getElementById('st-zoom').oninput = (e) => { zoom = e.target.value; draw(); }; canvas.onmousedown = (e) => { const x = e.clientX - canvas.getBoundingClientRect().left + canvas.parentElement.scrollLeft; selection.start = (x/canvas.width)*buffer.duration; selection.end = selection.start; offset = selection.start; }; canvas.onmousemove = (e) => { if(e.buttons===1) { const x = e.clientX - canvas.getBoundingClientRect().left + canvas.parentElement.scrollLeft; selection.end = (x/canvas.width)*buffer.duration; draw(); }}; })(); /* ============================================================ MODULO RADIO BJ8 - PERSISTENTE (LOCALSTORAGE) ============================================================ */ // 1. GESTIÓN DE DATOS (JSON + DISCO LOCAL) let radiosJSON = JSON.parse(localStorage.getItem('rd5_radios')) || [ { "nombre": "Radio Rock 80s", "url": "https://stream.zeno.fm/0r0xa792kwzuv" } ]; function guardarRadiosEnDisco() { localStorage.setItem('rd5_radios', JSON.stringify(radiosJSON)); } // 2. INYECCIÓN DEL BOTÓN EN EL LIBRERO (function inyectarBotonRadio() { const librero = document.querySelector('.librero'); if (!librero) return; const btnRadio = document.createElement('button'); btnRadio.className = 'btnTema'; btnRadio.id = 'btn-open-radio'; btnRadio.style = "background: linear-gradient(180deg, #222, #000); border: 1px solid var(--accent); color: var(--accent);"; btnRadio.innerHTML = '<span style="font-size:14px;">📻</span><br><small>RADIO</small>'; btnRadio.onclick = () => { document.getElementById('radio-modal-bj8').style.display = 'block'; renderRadioList(); }; librero.appendChild(btnRadio); })(); // 3. CREACIÓN DE LA INTERFAZ MODAL const modalRadioHTML = ` <div id="radio-modal-bj8" style="display:none; position:fixed; top:50%; left:50%; transform:translate(-50%, -50%); background:#1a110f; border:2px solid ORANGE; padding:20px; z-index:999999; box-shadow:0 0 50px #000; border-radius:8px; width:340px; font-family:monospace; color:white;"> <div style="display:flex; justify-content:space-between; color:var(--accent); margin-bottom:15px; font-weight:bold; border-bottom:1px solid #333; padding-bottom:5px;"> <span>SYSTEM: RADIO_STREAM_BJ8</span> <span onclick="document.getElementById('radio-modal-bj8').style.display='none'" style="cursor:pointer; color:red;">[X]</span> </div> <div id="radio-list-render" style="max-height:180px; overflow-y:auto; margin-bottom:15px; border:1px solid #000; background:#0a0a0a; padding:5px; border-radius:4px;"> </div> <div style="display:flex; flex-direction:column; gap:10px; background:#231815; padding:12px; border-radius:4px; border:2px solid ORANGE;"> <div style="display:flex; flex-direction:column; gap:4px;"> <label style="font-size:9px; color:var(--accent); letter-spacing:1px;">STATION NAME:</label> <input type="text" id="rad-name" style="background:#000; border:1px solid #444; color:#fff; padding:6px; font-size:11px; border-radius:2px;" placeholder="Ej: Rock & Pop"> </div> <div style="display:flex; flex-direction:column; gap:4px;"> <label style="font-size:9px; color:var(--accent); letter-spacing:1px;">STREAMING URL:</label> <textarea id="rad-url" style="background:#000; border:1px solid #444; color:#0f0; padding:6px; font-size:10px; height:45px; resize:none; border-radius:2px;" placeholder="https://stream..."></textarea> </div> <button onclick="addRadioManual()" style="background:var(--accent); border:none; color:#000; padding:10px; font-weight:bold; cursor:pointer; margin-top:5px; text-transform:uppercase; font-size:10px; border-radius:2px; box-shadow: 0 2px 0 #884400;"> + Grabar en Memoria </button> </div> </div> `; document.body.insertAdjacentHTML('beforeend', modalRadioHTML); // 4. LÓGICA DE CONTROL function renderRadioList() { const container = document.getElementById('radio-list-render'); container.innerHTML = ""; radiosJSON.forEach((r, i) => { const item = document.createElement('div'); item.style = "padding:10px; border-bottom:1px solid #1a1a1a; display:flex; justify-content:space-between; align-items:center; font-size:11px; transition:0.2s;"; item.innerHTML = ` <span onclick="playRadioByIndex(${i})" style="cursor:pointer; color:#ccc; flex-grow:1;"> <span style="color:var(--accent)">▶</span> <b>${r.nombre}</b> </span> <button onclick="eliminarRadio(${i})" style="background:transparent; border:none; color:#600; cursor:pointer; font-weight:bold; padding:5px;">[BORRAR]</button> `; item.onmouseover = () => item.style.background = "#151515"; item.onmouseout = () => item.style.background = "transparent"; container.appendChild(item); }); } function addRadioManual() { const nameInput = document.getElementById('rad-name'); const urlInput = document.getElementById('rad-url'); const name = nameInput.value.trim(); const url = urlInput.value.trim(); if(name && url) { radiosJSON.push({ "nombre": name, "url": url }); guardarRadiosEnDisco(); nameInput.value = ""; urlInput.value = ""; renderRadioList(); } else { alert("Julio, faltan datos para el registro."); } } function eliminarRadio(i) { if(confirm(`¿Eliminar ${radiosJSON[i].nombre}?`)) { radiosJSON.splice(i, 1); guardarRadiosEnDisco(); renderRadioList(); } } function playRadioByIndex(i) { const radio = radiosJSON[i]; // Limpiar blob previo si existe if(player.src.startsWith("blob:")) URL.revokeObjectURL(player.src); // Inyectar en la playlist para que el motor TINY la procese const radioTrack = { name: "LIVE: " + radio.nombre, url: radio.url }; playlist.unshift(radioTrack); index = 0; // Configuración para el motor de audio player.crossOrigin = "anonymous"; player.src = radio.url; player.play().catch(e => console.warn("Error al conectar streaming. Verificá la URL o CORS.")); renderLista(); document.getElementById('radio-modal-bj8').style.display = 'none'; } // 5. PARCHE PARA EL MOTOR CARGAR ORIGINAL const originalCargar = cargar; cargar = function() { const item = playlist[index]; if (item && item.url) { player.src = item.url; player.crossOrigin = "anonymous"; player.load(); actualizarLista(); } else { originalCargar(); } }; // Carga inicial renderRadioList(); </script> </body> </html>