{"id":456,"date":"2026-03-21T13:29:58","date_gmt":"2026-03-21T18:29:58","guid":{"rendered":"https:\/\/marcellolaquale.com\/?page_id=456"},"modified":"2026-03-23T13:00:28","modified_gmt":"2026-03-23T18:00:28","slug":"intensidad-y-frecuencia-tono","status":"publish","type":"page","link":"https:\/\/marcellolaquale.com\/index.php\/intensidad-y-frecuencia-tono\/","title":{"rendered":"Intensidad y Frecuencia\/Tono"},"content":{"rendered":"\n<!DOCTYPE html>\n<html lang=\"es\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Fundamentos del Sonido \u2014 Frecuencia e Intensidad<\/title>\n  <style>\n    *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }\n\n    body {\n      background: #0a0a0a;\n      color: #e8e8e8;\n      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n      min-height: 100vh;\n      padding: 24px 16px 40px;\n    }\n\n    .container {\n      max-width: 860px;\n      margin: 0 auto;\n    }\n\n    \/* Header *\/\n    .header {\n      text-align: center;\n      margin-bottom: 28px;\n    }\n    .header h1 {\n      font-size: 22px;\n      font-weight: 500;\n      color: #888888;\n      margin-bottom: 6px;\n    }\n    .header p {\n      font-size: 13px;\n      color: #888;\n    }\n\n    \/* Guide *\/\n    .guide {\n      background: #141414;\n      border: 0.5px solid #2a2a2a;\n      border-radius: 12px;\n      padding: 1rem 1.25rem;\n      margin-bottom: 20px;\n    }\n    .guide h2 {\n      font-size: 13px;\n      font-weight: 500;\n      color: #ccc;\n      margin-bottom: 8px;\n    }\n    .guide > p {\n      font-size: 12px;\n      color: #888;\n      line-height: 1.7;\n      margin-bottom: 12px;\n    }\n    .guide-grid {\n      display: grid;\n      grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n      gap: 10px;\n      margin-bottom: 12px;\n    }\n    .guide-item {\n      background: #1a1a1a;\n      border: 0.5px solid #2a2a2a;\n      border-radius: 8px;\n      padding: 10px 12px;\n    }\n    .guide-item .gi-title {\n      font-size: 12px;\n      font-weight: 500;\n      color: #ddd;\n      margin-bottom: 4px;\n    }\n    .guide-item .gi-desc {\n      font-size: 11px;\n      color: #777;\n      line-height: 1.6;\n    }\n    .guide-tip {\n      font-size: 11px;\n      color: #666;\n      line-height: 1.7;\n      padding-top: 10px;\n      border-top: 0.5px solid #222;\n    }\n    .guide-tip span {\n      color: #aaa;\n    }\n\n    \/* Wave panels *\/\n    .wp {\n      background: #111;\n      border: 0.5px solid #252525;\n      border-radius: 12px;\n      padding: 0.9rem 1.1rem;\n      margin-bottom: 10px;\n      transition: opacity 0.2s;\n    }\n    .wp.off { opacity: 0.35; }\n    .wh {\n      display: flex;\n      align-items: center;\n      gap: 8px;\n      margin-bottom: 10px;\n      flex-wrap: wrap;\n    }\n    .tog {\n      width: 36px; height: 20px;\n      border-radius: 999px;\n      border: 1.5px solid transparent;\n      cursor: pointer;\n      position: relative;\n      transition: background 0.2s;\n      flex-shrink: 0;\n    }\n    .tog::after {\n      content:'';\n      position: absolute;\n      top: 2px; left: 2px;\n      width: 14px; height: 14px;\n      border-radius: 50%;\n      background: #fff;\n      transition: transform 0.2s;\n    }\n    .tog.on::after { transform: translateX(16px); }\n\n    .badge { color: #ffffff !important;\n      font-size: 11px;\n      padding: 2px 8px;\n      border-radius: 999px;\n      font-weight: 500;\n    }\n    .phase-btn {\n      font-size: 11px;\n      padding: 3px 10px;\n      border-radius: 8px;\n      cursor: pointer;\n      border: 1.5px solid;\n      transition: background 0.15s, color 0.15s;\n      white-space: nowrap;\n      background: transparent;\n    }\n\n\n    .audio-btn {\n      font-size: 11px;\n      padding: 3px 10px;\n      border-radius: 8px;\n      cursor: pointer;\n      border: 1.5px solid;\n      transition: background 0.15s, color 0.15s;\n      white-space: nowrap;\n      background: transparent;\n    }\n    .ctrl-block { margin-bottom: 8px; }\n    .ctrl-row { display: flex; align-items: center; gap: 8px; }\n    .cl { font-size: 12px; color: #cccccc; min-width: 78px; }\n\n    .num-box {\n      width: 62px;\n      font-size: 14px;\n      font-weight: 500;\n      text-align: center;\n      padding: 4px 6px;\n      border-radius: 8px;\n      border: 1.5px solid #333;\n      background: #1a1a1a;\n      color: #ffffff !important;\n    }\n    .num-box:focus { outline: none; background: #222; border-color: #666; }\n\n    \/* Remove number input arrows *\/\n    .num-box::-webkit-inner-spin-button,\n    .num-box::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }\n    .num-box[type=number] { -moz-appearance: textfield; }\n\n    .unit { font-size: 12px; color: #aaaaaa; }\n    .cv { font-size: 13px; font-weight: 500; min-width: 52px; text-align: right; color: #ffffff; }\n\n    \/* Sliders *\/\n    input[type=range] {\n      -webkit-appearance: none;\n      appearance: none;\n      height: 4px;\n      border-radius: 2px;\n      background: #2a2a2a;\n      outline: none;\n      cursor: pointer;\n    }\n    input[type=range]::-webkit-slider-thumb {\n      -webkit-appearance: none;\n      width: 16px; height: 16px;\n      border-radius: 50%;\n      background: #ffffff;\n      border: 2px solid #0a0a0a;\n      cursor: pointer;\n    }\n    input[type=range]::-moz-range-thumb {\n      width: 16px; height: 16px;\n      border-radius: 50%;\n      border: 2px solid #0a0a0a;\n      cursor: pointer;\n    }\n\n    \/* Graphs *\/\n    .graph-section { margin-bottom: 6px; }\n    .graph-wrap { display: flex; gap: 0; margin-bottom: 6px; }\n    .y-label {\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      writing-mode: vertical-rl;\n      transform: rotate(180deg);\n      font-size: 11px;\n      color: #555;\n      min-width: 30px;\n      line-height: 1.3;\n      text-align: center;\n    }\n    .graph-box {\n      position: relative;\n      width: 100%;\n      background: #0e0e0e;\n      border: 0.5px solid #222;\n      border-radius: 12px;\n      overflow: hidden;\n    }\n    .x-label { text-align: center; font-size: 11px; color: #555; margin-top: 5px; }\n    .graph-title {\n      font-size: 12px;\n      font-weight: 500;\n      color: #777;\n      margin-bottom: 6px;\n      padding-left: 30px;\n    }\n    #sum-info {\n      font-size: 12px;\n      color: #666;\n      margin: 4px 0 10px 30px;\n      min-height: 16px;\n    }\n\n    \/* Copyright *\/\n    .copyright {\n      text-align: center;\n      font-size: 11px;\n      color: #444;\n      margin-top: 28px;\n      padding-top: 14px;\n      border-top: 0.5px solid #1e1e1e;\n    }\n  <\/style>\n<\/head>\n<body>\n<div class=\"container\">\n\n  <div class=\"header\">\n    <h1>Fundamentos del Sonido<\/h1>\n    <p>Frecuencia (agudo \/ grave) e Intensidad (fuerte \/ d\u00e9bil)<\/p>\n  <\/div>\n\n  <div class=\"guide\">\n    <h2>Gu\u00eda de uso<\/h2>\n    <p>Explora los par\u00e1metros fundamentales del sonido: <strong style=\"font-weight:500;color:#bbb;\">frecuencia<\/strong> (agudo \/ grave, 1\u201321.000 Hz) e <strong style=\"font-weight:500;color:#bbb;\">intensidad<\/strong> (fuerte \/ d\u00e9bil, 0\u2013100 dB). Cada onda se configura con el slider o escribiendo el n\u00famero y confirmando con Enter.<\/p>\n    <div class=\"guide-grid\" style=\"grid-template-columns:repeat(auto-fit,minmax(180px,1fr));\">\n      <div class=\"guide-item\">\n        <div class=\"gi-title\">Interruptor<\/div>\n        <div class=\"gi-desc\">Activa o desactiva la onda. Al apagarla desaparece del gr\u00e1fico y su audio se detiene.<\/div>\n      <\/div>\n      <div class=\"guide-item\">\n        <div class=\"gi-title\">\u25b6 Escuchar<\/div>\n        <div class=\"gi-desc\">Reproduce el tono real en el navegador. El volumen responde a los dB. Varias ondas se mezclan en tiempo real.<\/div>\n      <\/div>\n      <div class=\"guide-item\">\n        <div class=\"gi-title\">\u2195 Invertir fase<\/div>\n        <div class=\"gi-desc\">Invierte la polaridad (\u00d7\u22121). Misma frecuencia + fase opuesta = cancelaci\u00f3n total en gr\u00e1fico y audio.<\/div>\n      <\/div>\n      <div class=\"guide-item\">\n        <div class=\"gi-title\">Gr\u00e1ficos<\/div>\n        <div class=\"gi-desc\">Gr\u00e1fico 1: ondas individuales. Gr\u00e1fico 2: suma en dB real. Dos ondas iguales en fase \u2192 +6 dB.<\/div>\n      <\/div>\n    <\/div>\n    <div class=\"guide-tip\">\n      <span style=\"color:#aaa;\">Experimentos \u2014 <\/span>\n      <span style=\"color:#777;\"><strong style=\"font-weight:500;color:#ccc;\">Suma:<\/strong> misma frecuencia e intensidad en dos ondas \u2192 +6 dB en la resultante. &nbsp;\u00b7&nbsp; <strong style=\"font-weight:500;color:#ccc;\">Cancelaci\u00f3n:<\/strong> activa \u2195 Fase invertida \u2192 silencio total (principio del noise cancelling). &nbsp;\u00b7&nbsp; <strong style=\"font-weight:500;color:#ccc;\">Batidos:<\/strong> frecuencias cercanas (ej. 440 Hz y 442 Hz) \u2192 pulsaci\u00f3n r\u00edtmica audible.<\/span>\n    <\/div>\n  <\/div>\n\n  <div id=\"panels\"><\/div>\n\n  <div class=\"graph-section\">\n    <div class=\"graph-title\">Ondas activas<\/div>\n    <div class=\"graph-wrap\">\n      <div class=\"y-label\">Amplitud \u00b7 Intensidad (dB)<\/div>\n      <div style=\"flex:1;\">\n        <div class=\"graph-box\" style=\"height:230px;\"><canvas id=\"wc1\"><\/canvas><\/div>\n        <div class=\"x-label\">Frecuencias por segundo (Hz) \u2014 1 segundo de tiempo<\/div>\n      <\/div>\n    <\/div>\n  <\/div>\n\n  <div style=\"margin-top:18px;\"><\/div>\n\n  <div class=\"graph-section\">\n    <div class=\"graph-title\">Onda resultante (suma de ondas activas)<\/div>\n    <div id=\"sum-info\"><\/div>\n    <div class=\"graph-wrap\">\n      <div class=\"y-label\">Amplitud \u00b7 Intensidad (dB)<\/div>\n      <div style=\"flex:1;\">\n        <div class=\"graph-box\" style=\"height:230px;\"><canvas id=\"wc2\"><\/canvas><\/div>\n        <div class=\"x-label\">Frecuencias por segundo (Hz) \u2014 1 segundo de tiempo<\/div>\n      <\/div>\n    <\/div>\n  <\/div>\n\n  <div class=\"copyright\">\n    \u00a9 Marcello Laquale &#8211; 2026\n    \n  <\/div>\n\n<\/div>\n\n<script>\nconst WAVES = [\n  { id:0, freq:1, db:20, on:true,  inverted:false, color:'#7c6ef5', name:'Onda 1' },\n  { id:1, freq:2, db:20, on:false, inverted:false, color:'#34c98a', name:'Onda 2' },\n  { id:2, freq:3, db:20, on:false, inverted:false, color:'#f07040', name:'Onda 3' },\n];\nconst SUM_COLOR = '#38a8e8';\nconst N = 4000, MAX_FREQ = 21000, DB_MIN = 0, DB_MAX = 100, DB_REF = 100;\n\nconst BADGE_BG  = ['#2a2560','#0d3d28','#3d1e0e'];\nconst BADGE_CLR = ['#ffffff','#ffffff','#ffffff'];\n\nfunction dbToAmp(db){ return Math.pow(10,(db-DB_REF)\/20); }\nfunction ampToDb(a){ return DB_REF+20*Math.log10(Math.max(a,1e-10)); }\nfunction dbToPx(db,maxPx){ return Math.pow(Math.max(0,Math.min(db,DB_MAX))\/DB_MAX,0.6)*maxPx; }\nfunction freqLabel(f){\n  let zona;\n  if(f < 20)         zona = 'Infrasonido';\n  else if(f < 80)    zona = 'Grave';\n  else if(f < 300)   zona = 'Medio grave';\n  else if(f < 1000)  zona = 'Medio';\n  else if(f < 4000)  zona = 'Medio agudo';\n  else if(f < 20000) zona = 'Agudo';\n  else               zona = 'Ultrasonido';\n  return `${zona} \u00b7 ${f} Hz`;\n}\nfunction dbLabel(d){ return d>=60?`fuerte \u00b7 ${d} dB`:`d\u00e9bil \u00b7 ${d} dB`; }\n\nfunction sliderStyle(color){\n  \/\/ Inject per-wave thumb color via a style tag\n  return `accent-color:${color};`;\n}\n\nfunction renderPanels(){\n  document.getElementById('panels').innerHTML = WAVES.map(w=>`\n    <div class=\"wp ${w.on?'':'off'}\" id=\"panel${w.id}\">\n      <div class=\"wh\">\n        <div class=\"tog ${w.on?'on':''}\" id=\"tog${w.id}\"\n          style=\"background:${w.on?w.color:'#1e1e1e'};border-color:${w.color};\"\n          onclick=\"toggleWave(${w.id})\"><\/div>\n        <span style=\"font-size:13px;font-weight:500;color:${w.color}\">${w.name}<\/span>\n        <span class=\"badge\" style=\"background:${BADGE_BG[w.id]};color:${BADGE_CLR[w.id]};\" id=\"flabel${w.id}\">${freqLabel(w.freq)}<\/span>\n        <span class=\"badge\" style=\"background:${BADGE_BG[w.id]};color:${BADGE_CLR[w.id]};\" id=\"dlabel${w.id}\">${dbLabel(w.db)}<\/span>\n        <button class=\"audio-btn\" id=\"audiobtn${w.id}\"\n          style=\"border-color:${w.color};color:${w.color};background:transparent;\"\n          onclick=\"toggleAudio(${w.id})\">&#9654; Escuchar<\/button>\n        <button class=\"phase-btn\" id=\"phasebtn${w.id}\"\n          style=\"border-color:${w.color};color:${w.inverted?'#0a0a0a':w.color};background:${w.inverted?w.color:'transparent'};\"\n          onclick=\"togglePhase(${w.id})\">${w.inverted?'\u2195 Fase invertida':'\u2195 Fase normal'}<\/button>\n      <\/div>\n      <div class=\"ctrl-block\">\n        <div class=\"ctrl-row\" style=\"margin-bottom:5px;\">\n          <span class=\"cl\">Frecuencia<\/span>\n          <input id=\"fnum${w.id}\" type=\"number\" class=\"num-box\" min=\"1\" max=\"${MAX_FREQ}\" step=\"1\" value=\"${w.freq}\"\n            style=\"border-color:${w.color}44;\"\n            oninput=\"onFreqInput(${w.id},this.value)\" onblur=\"onFreqBlur(${w.id})\" onkeydown=\"if(event.key==='Enter'){onFreqBlur(${w.id});this.blur();}\">\n          <span class=\"unit\">Hz<\/span>\n        <\/div>\n        <div class=\"ctrl-row\">\n          <span class=\"cl\"><\/span>\n          <input id=\"fslider${w.id}\" type=\"range\" min=\"1\" max=\"${MAX_FREQ}\" step=\"1\" value=\"${w.freq}\"\n            style=\"flex:1;${sliderStyle(w.color)}\"\n            oninput=\"onFreqSlider(${w.id},+this.value)\">\n        <\/div>\n      <\/div>\n      <div class=\"ctrl-block\">\n        <div class=\"ctrl-row\" style=\"margin-bottom:5px;\">\n          <span class=\"cl\">Intensidad<\/span>\n          <input id=\"dnum${w.id}\" type=\"number\" class=\"num-box\" min=\"${DB_MIN}\" max=\"${DB_MAX}\" step=\"1\" value=\"${w.db}\"\n            style=\"border-color:${w.color}44;\"\n            oninput=\"onDbInput(${w.id},this.value)\" onblur=\"onDbBlur(${w.id})\" onkeydown=\"if(event.key==='Enter'){onDbBlur(${w.id});this.blur();}\">\n          <span class=\"unit\">dB<\/span>\n        <\/div>\n        <div class=\"ctrl-row\">\n          <span class=\"cl\"><\/span>\n          <input id=\"dslider${w.id}\" type=\"range\" min=\"${DB_MIN}\" max=\"${DB_MAX}\" step=\"1\" value=\"${w.db}\"\n            style=\"flex:1;${sliderStyle(w.color)}\"\n            oninput=\"onDb(${w.id},+this.value)\">\n        <\/div>\n      <\/div>\n    <\/div>\n  `).join('');\n}\n\nfunction togglePhase(id){\n  const w=WAVES[id]; w.inverted=!w.inverted;\n  const btn=document.getElementById('phasebtn'+id);\n  btn.style.background=w.inverted?w.color:'transparent';\n  btn.style.color=w.inverted?'#0a0a0a':w.color;\n  btn.textContent=w.inverted?'\u2195 Fase invertida':'\u2195 Fase normal';\n  syncToneParams(id);\n  draw();\n}\nfunction onFreqSlider(id,val){\n  val=Math.round(val); WAVES[id].freq=val;\n  document.getElementById('fnum'+id).value=val;\n  document.getElementById('flabel'+id).textContent=freqLabel(val);\n  syncToneParams(id);\n  draw();\n}\nfunction onFreqInput(id,raw){\n  const val=parseInt(raw); if(isNaN(val)||val<1) return;\n  const c=Math.min(val,MAX_FREQ); WAVES[id].freq=c;\n  document.getElementById('fslider'+id).value=c;\n  document.getElementById('flabel'+id).textContent=freqLabel(c);\n  syncToneParams(id);\n  draw();\n}\nfunction onFreqBlur(id){\n  const inp=document.getElementById('fnum'+id);\n  let val=parseInt(inp.value);\n  if(isNaN(val)||val<1) val=1;\n  if(val>MAX_FREQ) val=MAX_FREQ;\n  inp.value=val; WAVES[id].freq=val;\n  document.getElementById('fslider'+id).value=val;\n  document.getElementById('flabel'+id).textContent=freqLabel(val);\n  syncToneParams(id);\n  draw();\n}\nfunction onDb(id,val){\n  val = Math.round(Math.max(0, Math.min(100, val)));\n  WAVES[id].db = val;\n  document.getElementById('dnum'+id).value = val;\n  document.getElementById('dslider'+id).value = val;\n  document.getElementById('dlabel'+id).textContent = dbLabel(val);\n  syncToneParams(id);\n  draw();\n}\nfunction onDbInput(id, raw){\n  const val = parseInt(raw);\n  if(isNaN(val) || val < 0) return;\n  const c = Math.min(val, 100);\n  WAVES[id].db = c;\n  document.getElementById('dslider'+id).value = c;\n  document.getElementById('dlabel'+id).textContent = dbLabel(c);\n  syncToneParams(id);\n  draw();\n}\nfunction onDbBlur(id){\n  const inp = document.getElementById('dnum'+id);\n  let val = parseInt(inp.value);\n  if(isNaN(val) || val < 0) val = 0;\n  if(val > 100) val = 100;\n  inp.value = val;\n  WAVES[id].db = val;\n  document.getElementById('dslider'+id).value = val;\n  document.getElementById('dlabel'+id).textContent = dbLabel(val);\n  syncToneParams(id);\n  draw();\n}\nfunction toggleWave(id){\n  const w=WAVES[id]; w.on=!w.on;\n  const tog=document.getElementById('tog'+id);\n  tog.classList.toggle('on',w.on);\n  tog.style.background=w.on?w.color:'#1e1e1e';\n  document.getElementById('panel'+id).classList.toggle('off',!w.on);\n  if(!w.on && audioNodes[id]) stopTone(id);\n  draw();\n}\n\nfunction setupCanvas(id){\n  const canvas=document.getElementById(id);\n  const wrap=canvas.parentElement;\n  const W=wrap.clientWidth, H=wrap.clientHeight;\n  if(!W||!H) return null;\n  const dpr=devicePixelRatio||1;\n  canvas.width=W*dpr; canvas.height=H*dpr;\n  canvas.style.width=W+'px'; canvas.style.height=H+'px';\n  const ctx=canvas.getContext('2d');\n  ctx.scale(dpr,dpr);\n  return {ctx,W,H};\n}\n\nfunction drawAxes(ctx,W,H,PL,PR,PT,PB,maxPx){\n  const gW=W-PL-PR, gH=H-PT-PB, mid=PT+gH\/2;\n  const axC='rgba(255,255,255,0.25)';\n  const txtC='rgba(255,255,255,0.9)';\n\n  [25,50,75,100].forEach(db=>{\n    const py=dbToPx(db,maxPx);\n    const yPos=mid-py, yNeg=mid+py;\n    ctx.strokeStyle=axC; ctx.lineWidth=0.5; ctx.setLineDash([3,4]);\n    ctx.beginPath(); ctx.moveTo(PL,yPos); ctx.lineTo(PL+gW,yPos); ctx.stroke();\n    if(db<100){ctx.beginPath(); ctx.moveTo(PL,yNeg); ctx.lineTo(PL+gW,yNeg); ctx.stroke();}\n    ctx.setLineDash([]);\n    ctx.fillStyle=txtC; ctx.font='9px sans-serif'; ctx.textAlign='right';\n    ctx.fillText(db+' dB',PL-3,yPos+3);\n    if(db<100) ctx.fillText('\u2212'+db+' dB',PL-3,yNeg+3);\n  });\n\n  ctx.strokeStyle='rgba(255,255,255,0.8)'; ctx.lineWidth=0.8;\n  ctx.beginPath(); ctx.moveTo(PL,PT); ctx.lineTo(PL,PT+gH); ctx.stroke();\n  ctx.beginPath(); ctx.moveTo(PL,mid); ctx.lineTo(PL+gW,mid); ctx.stroke();\n\n  ctx.fillStyle=txtC; ctx.font='10px sans-serif'; ctx.textAlign='right';\n  ctx.fillText('0',PL-3,mid+4);\n  ctx.textAlign='center';\n  ctx.fillText('0 s',PL,mid+13);\n  ctx.fillText('0.5 s',PL+gW\/2,mid+13);\n  ctx.fillText('1 s',PL+gW,mid+13);\n\n  return {gW,gH,mid};\n}\n\nfunction draw(){\n  const active=WAVES.filter(w=>w.on);\n  const PL=58,PR=10,PT=14,PB=20;\n\n  const c1=setupCanvas('wc1');\n  if(c1){\n    const {ctx,W,H}=c1;\n    ctx.clearRect(0,0,W,H);\n    const maxPx=(H-PT-PB)*0.46;\n    const {gW,gH,mid}=drawAxes(ctx,W,H,PL,PR,PT,PB,maxPx);\n    if(active.length===0){\n      ctx.fillStyle='rgba(255,255,255,0.2)'; ctx.font='13px sans-serif'; ctx.textAlign='center';\n      ctx.fillText('Activa al menos una onda',W\/2,H\/2);\n    } else {\n      active.forEach(w=>{\n        const ampPx=dbToPx(w.db,maxPx);\n        const sign=w.inverted?-1:1;\n        ctx.strokeStyle=w.color; ctx.lineWidth=2; ctx.globalAlpha=0.9;\n        ctx.beginPath();\n        for(let n=0;n<=N;n++){\n          const t=n\/N;\n          const y=mid-sign*ampPx*Math.sin(2*Math.PI*w.freq*t);\n          const x=PL+t*gW;\n          n===0?ctx.moveTo(x,y):ctx.lineTo(x,y);\n        }\n        ctx.stroke(); ctx.globalAlpha=1;\n      });\n      let lx=PL+8;\n      active.forEach(w=>{\n        ctx.fillStyle=w.color; ctx.font='500 11px sans-serif'; ctx.textAlign='left';\n        ctx.fillRect(lx,PT+6,14,3);\n        ctx.fillText(w.name+(w.inverted?' (inv.)':''),lx+18,PT+14);\n        lx+=w.inverted?90:72;\n      });\n    }\n  }\n\n  const c2=setupCanvas('wc2');\n  if(c2){\n    const {ctx,W,H}=c2;\n    ctx.clearRect(0,0,W,H);\n    const maxPx=(H-PT-PB)*0.46;\n    const {gW,gH,mid}=drawAxes(ctx,W,H,PL,PR,PT,PB,maxPx);\n    if(active.length===0){\n      ctx.fillStyle='rgba(255,255,255,0.2)'; ctx.font='13px sans-serif'; ctx.textAlign='center';\n      ctx.fillText('Activa al menos una onda',W\/2,H\/2);\n      document.getElementById('sum-info').textContent='';\n    } else {\n      const sumPts=new Float32Array(N+1);\n      active.forEach(w=>{\n        const ampLinear=dbToAmp(w.db);\n        const sign=w.inverted?-1:1;\n        for(let n=0;n<=N;n++) sumPts[n]+=sign*ampLinear*Math.sin(2*Math.PI*w.freq*n\/N);\n      });\n      const peakAmp=Math.max(...sumPts.map(Math.abs));\n      const peakDb=peakAmp>0?ampToDb(peakAmp):0;\n      const scaledMaxPx=dbToPx(Math.min(peakDb,DB_MAX),maxPx);\n      const sc=peakAmp>0?scaledMaxPx\/peakAmp:1;\n      ctx.strokeStyle=SUM_COLOR; ctx.lineWidth=2.5;\n      ctx.beginPath();\n      for(let n=0;n<=N;n++){\n        const x=PL+(n\/N)*gW, y=mid-sumPts[n]*sc;\n        n===0?ctx.moveTo(x,y):ctx.lineTo(x,y);\n      }\n      ctx.stroke();\n      ctx.fillStyle=SUM_COLOR; ctx.font='500 11px sans-serif'; ctx.textAlign='left';\n      ctx.fillRect(PL+8,PT+6,14,3);\n      ctx.fillText('Suma: '+active.map(w=>w.name+(w.inverted?' (inv.)':'')).join(' + '),PL+26,PT+14);\n      document.getElementById('sum-info').textContent=peakDb>0\n        ?`Intensidad resultante: ${peakDb.toFixed(1)} dB`\n        :'Cancelaci\u00f3n total (0 dB)';\n    }\n  }\n}\n\n\n\/\/ \u2500\u2500 Web Audio API \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\/\/ Architecture: one shared oscillator per unique frequency.\n\/\/ Each wave taps into the shared osc via its own phaseInverter+gain chain.\n\/\/ This guarantees perfect phase coherence \u2014 real cancellation when phase is inverted.\n\nlet audioCtx = null;\nconst audioNodes = {};   \/\/ id  -> { gainNode, phaseNode } \u2014 per-wave nodes\nconst sharedOscs = {};   \/\/ freq -> { osc, sourceGain, refCount } \u2014 shared oscillators\n\nfunction getAudioCtx(){\n  if(!audioCtx) audioCtx = new (window.AudioContext || window.webkitAudioContext)();\n  return audioCtx;\n}\n\nfunction dbToGain(db){\n  return Math.pow(Math.max(0, db) \/ 100, 2) * 0.6;\n}\n\nfunction getOrCreateOsc(freq){\n  const key = freq;\n  if(!sharedOscs[key]){\n    const ctx = getAudioCtx();\n    const osc = ctx.createOscillator();\n    const sourceGain = ctx.createGain();\n    osc.type = 'sine';\n    osc.frequency.setValueAtTime(freq, ctx.currentTime);\n    sourceGain.gain.setValueAtTime(1, ctx.currentTime);\n    osc.connect(sourceGain);\n    osc.start();\n    sharedOscs[key] = { osc, sourceGain, refCount: 0 };\n  }\n  sharedOscs[key].refCount++;\n  return sharedOscs[key];\n}\n\nfunction releaseOsc(freq){\n  const key = freq;\n  if(!sharedOscs[key]) return;\n  sharedOscs[key].refCount--;\n  if(sharedOscs[key].refCount <= 0){\n    try { sharedOscs[key].osc.stop(); } catch(e){}\n    sharedOscs[key].osc.disconnect();\n    sharedOscs[key].sourceGain.disconnect();\n    delete sharedOscs[key];\n  }\n}\n\nfunction startTone(id){\n  const w = WAVES[id];\n  const ctx = getAudioCtx();\n  if(audioNodes[id]) stopTone(id);\n\n  const shared = getOrCreateOsc(w.freq);\n\n  \/\/ phaseNode: +1 normal, -1 inverted \u2014 connected to same shared osc as other waves\n  const phaseNode = ctx.createGain();\n  const gainNode  = ctx.createGain();\n\n  phaseNode.gain.setValueAtTime(w.inverted ? -1 : 1, ctx.currentTime);\n  gainNode.gain.setValueAtTime(dbToGain(w.db), ctx.currentTime);\n\n  shared.sourceGain.connect(phaseNode);\n  phaseNode.connect(gainNode);\n  gainNode.connect(ctx.destination);\n\n  audioNodes[id] = { gainNode, phaseNode, freq: w.freq };\n  updateAudioBtn(id, true);\n}\n\nfunction stopTone(id){\n  if(audioNodes[id]){\n    const { gainNode, phaseNode, freq } = audioNodes[id];\n    gainNode.gain.setValueAtTime(0, getAudioCtx().currentTime);\n    phaseNode.disconnect();\n    gainNode.disconnect();\n    releaseOsc(freq);\n    delete audioNodes[id];\n  }\n  updateAudioBtn(id, false);\n}\n\nfunction toggleAudio(id){\n  if(audioNodes[id]) stopTone(id);\n  else startTone(id);\n}\n\nfunction updateAudioBtn(id, playing){\n  const w = WAVES[id];\n  const btn = document.getElementById('audiobtn'+id);\n  if(!btn) return;\n  if(playing){\n    btn.textContent = '\\u25A0 Detener';\n    btn.style.background = w.color;\n    btn.style.color = '#0a0a0a';\n  } else {\n    btn.textContent = '\\u25B6 Escuchar';\n    btn.style.background = 'transparent';\n    btn.style.color = w.color;\n  }\n}\n\nfunction syncToneParams(id){\n  const w = WAVES[id];\n  const ctx = getAudioCtx();\n  if(!audioNodes[id]) return;\n\n  const node = audioNodes[id];\n  const oldFreq = node.freq;\n  const newFreq = w.freq;\n\n  \/\/ If frequency changed, reconnect to new shared oscillator\n  if(oldFreq !== newFreq){\n    \/\/ Disconnect from old shared osc\n    if(sharedOscs[oldFreq]){\n      sharedOscs[oldFreq].sourceGain.disconnect(node.phaseNode);\n    }\n    releaseOsc(oldFreq);\n    \/\/ Connect to new shared osc\n    const shared = getOrCreateOsc(newFreq);\n    shared.sourceGain.connect(node.phaseNode);\n    node.freq = newFreq;\n  }\n\n  \/\/ Update phase and volume in real time \u2014 instant, glitch-free\n  node.phaseNode.gain.setValueAtTime(w.inverted ? -1 : 1, ctx.currentTime);\n  node.gainNode.gain.setValueAtTime(dbToGain(w.db), ctx.currentTime);\n}\n\/\/ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nrenderPanels();\nrequestAnimationFrame(draw);\nwindow.addEventListener('resize',draw);\n<\/script>\n\n<script>\nfetch('https:\/\/api.countapi.xyz\/hit\/ondas-sonido-laquale-2026\/visitas')\n  .then(r => r.json())\n  .then(data => {\n    const el = document.getElementById('visit-counter');\n    if(el) el.textContent = 'Visitas: ' + data.value.toLocaleString();\n  })\n  .catch(() => {\n    const el = document.getElementById('visit-counter');\n    if(el) el.textContent = '';\n  });\n<\/script>\n<\/body>\n<\/html>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Fundamentos del Sonido \u2014 Frecuencia e Intensidad Fundamentos del Sonido Frecuencia (agudo \/ grave) e Intensidad (fuerte \/ d\u00e9bil) Gu\u00eda de uso Explora los par\u00e1metros fundamentales del sonido: frecuencia (agudo<a href=\"https:\/\/marcellolaquale.com\/index.php\/intensidad-y-frecuencia-tono\/\" class=\"more-link\"><span class=\"readmore\">Continue reading<span class=\"screen-reader-text\">Intensidad y Frecuencia\/Tono<\/span><\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-456","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/marcellolaquale.com\/index.php\/wp-json\/wp\/v2\/pages\/456","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/marcellolaquale.com\/index.php\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/marcellolaquale.com\/index.php\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/marcellolaquale.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/marcellolaquale.com\/index.php\/wp-json\/wp\/v2\/comments?post=456"}],"version-history":[{"count":13,"href":"https:\/\/marcellolaquale.com\/index.php\/wp-json\/wp\/v2\/pages\/456\/revisions"}],"predecessor-version":[{"id":502,"href":"https:\/\/marcellolaquale.com\/index.php\/wp-json\/wp\/v2\/pages\/456\/revisions\/502"}],"wp:attachment":[{"href":"https:\/\/marcellolaquale.com\/index.php\/wp-json\/wp\/v2\/media?parent=456"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}