web: in-app SERVICE MANUAL overlay
Adds a docs overlay that lives inside the rack chrome, opened from a new MANUAL hw-btn in the top bar (ESC also closes). Styled as a screen recess matching the editor — amber section headers, engraved labels, recessed code blocks — so it reads as a manual page of the instrument, not a help popup. Covers patch language, execution model, every node type (osc, trig, adsr, filter, noise, seq, delay, poly+voice) and the four control nodes (knob, fader, step_seq, piano_roll), plus practical tips on gain staging and hot-reload behavior. Each example block carries a TRY button that swaps the editor contents with the snippet so you can hear it immediately (Ctrl/Cmd-Z to bring your patch back).
This commit is contained in:
429
web/index.html
429
web/index.html
@@ -543,6 +543,152 @@
|
|||||||
box-shadow: none; animation: none; }
|
box-shadow: none; animation: none; }
|
||||||
.blink { animation: blink 1.1s step-end infinite; }
|
.blink { animation: blink 1.1s step-end infinite; }
|
||||||
@keyframes blink { 50% { opacity: 0; } }
|
@keyframes blink { 50% { opacity: 0; } }
|
||||||
|
|
||||||
|
/* ===================================================================== */
|
||||||
|
/* Manual overlay — in-app docs styled as a "service manual" */
|
||||||
|
/* Sits inside .hardware, between top-bar and bottom-bar. */
|
||||||
|
/* ===================================================================== */
|
||||||
|
#manual {
|
||||||
|
position: absolute;
|
||||||
|
/* sit between the top-bar and bottom-bar (each 30px + 10px gap = 40px). */
|
||||||
|
top: calc(14px + 30px + 10px);
|
||||||
|
left: 14px; right: 14px;
|
||||||
|
bottom: calc(14px + 30px + 10px);
|
||||||
|
z-index: 10;
|
||||||
|
display: none;
|
||||||
|
flex-direction: column;
|
||||||
|
background: var(--hw-screen);
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow:
|
||||||
|
inset 0 2px 4px rgba(0,0,0,0.7),
|
||||||
|
inset 0 -1px 0 rgba(255,220,180,0.03),
|
||||||
|
inset 0 0 0 1px var(--hw-edge),
|
||||||
|
inset 0 6px 14px rgba(0,0,0,0.5);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
#manual.open { display: flex; }
|
||||||
|
.manual-head {
|
||||||
|
display: flex; align-items: center; gap: 12px;
|
||||||
|
padding: 10px 14px 8px;
|
||||||
|
border-bottom: 1px solid rgba(255,220,180,0.06);
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
.manual-head .title {
|
||||||
|
color: var(--hw-amber); font-size: 11px; letter-spacing: 0.22em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
text-shadow: 0 0 5px var(--hw-amber-glow);
|
||||||
|
}
|
||||||
|
.manual-head .sub {
|
||||||
|
color: var(--hw-engrave); font-size: 9px; letter-spacing: 0.18em;
|
||||||
|
text-transform: uppercase; margin-left: auto;
|
||||||
|
}
|
||||||
|
.manual-close {
|
||||||
|
background: linear-gradient(180deg, #2c261f 0%, #14110d 100%);
|
||||||
|
border: 1px solid var(--hw-edge);
|
||||||
|
color: var(--hw-fg-hi); cursor: pointer;
|
||||||
|
width: 22px; height: 22px; border-radius: 3px;
|
||||||
|
font-family: inherit; font-size: 14px; line-height: 1;
|
||||||
|
box-shadow: inset 0 1px 0 rgba(255,220,180,0.10),
|
||||||
|
inset 0 -1px 0 rgba(0,0,0,0.55),
|
||||||
|
0 1px 1px rgba(0,0,0,0.4);
|
||||||
|
display: inline-flex; align-items: center; justify-content: center;
|
||||||
|
transition: color var(--t-fast);
|
||||||
|
}
|
||||||
|
.manual-close:hover { color: var(--hw-amber); }
|
||||||
|
|
||||||
|
.manual-body {
|
||||||
|
flex: 1; min-height: 0; overflow: auto;
|
||||||
|
padding: 14px 22px 24px;
|
||||||
|
color: var(--hw-fg);
|
||||||
|
font-size: 12px; line-height: 1.6;
|
||||||
|
max-width: 880px; margin: 0 auto;
|
||||||
|
}
|
||||||
|
.manual-body h2 {
|
||||||
|
color: var(--hw-amber);
|
||||||
|
font-size: 11px; letter-spacing: 0.20em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin: 22px 0 8px;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
border-bottom: 1px solid rgba(232, 160, 80, 0.22);
|
||||||
|
text-shadow: 0 0 4px var(--hw-amber-glow);
|
||||||
|
}
|
||||||
|
.manual-body h2:first-child { margin-top: 0; }
|
||||||
|
.manual-body h3 {
|
||||||
|
color: var(--hw-fg-hi); font-size: 11px;
|
||||||
|
letter-spacing: 0.10em; text-transform: uppercase;
|
||||||
|
margin: 14px 0 4px;
|
||||||
|
}
|
||||||
|
.manual-body p { margin: 6px 0 10px; color: var(--hw-fg); }
|
||||||
|
.manual-body code {
|
||||||
|
color: var(--hw-syn-num);
|
||||||
|
background: rgba(0,0,0,0.40);
|
||||||
|
padding: 1px 5px; border-radius: 2px;
|
||||||
|
font-size: 11px;
|
||||||
|
box-shadow: inset 0 1px 1px rgba(0,0,0,0.4);
|
||||||
|
}
|
||||||
|
.manual-body pre {
|
||||||
|
margin: 8px 0 14px;
|
||||||
|
background: linear-gradient(180deg, #0a0805 0%, #060403 100%);
|
||||||
|
border-radius: 3px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
color: var(--hw-fg);
|
||||||
|
font-size: 11px; line-height: 1.55;
|
||||||
|
overflow-x: auto;
|
||||||
|
box-shadow: inset 0 1px 3px rgba(0,0,0,0.85),
|
||||||
|
inset 0 0 0 1px var(--hw-edge);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.manual-body pre .syn-com { color: var(--hw-syn-com); font-style: italic; }
|
||||||
|
.manual-body pre .syn-kw { color: var(--hw-syn-kw); }
|
||||||
|
.manual-body pre .syn-num { color: var(--hw-syn-num); }
|
||||||
|
.manual-body pre .syn-fn { color: var(--hw-syn-fn); }
|
||||||
|
.manual-body pre .syn-id { color: var(--hw-syn-id); }
|
||||||
|
.manual-body pre .syn-op { color: var(--hw-syn-op); }
|
||||||
|
.manual-body pre .syn-arrow { color: var(--hw-amber); }
|
||||||
|
.manual-body pre .try-btn {
|
||||||
|
position: absolute; top: 6px; right: 6px;
|
||||||
|
background: linear-gradient(180deg, #2c261f 0%, #14110d 100%);
|
||||||
|
color: var(--hw-fg-dim); border: 1px solid var(--hw-edge);
|
||||||
|
padding: 2px 8px; border-radius: 2px;
|
||||||
|
font-family: inherit; font-size: 9px;
|
||||||
|
letter-spacing: 0.12em; text-transform: uppercase;
|
||||||
|
cursor: pointer;
|
||||||
|
box-shadow: inset 0 1px 0 rgba(255,220,180,0.08),
|
||||||
|
inset 0 -1px 0 rgba(0,0,0,0.55);
|
||||||
|
transition: color var(--t-fast);
|
||||||
|
}
|
||||||
|
.manual-body pre .try-btn:hover { color: var(--hw-amber); }
|
||||||
|
.manual-body table {
|
||||||
|
border-collapse: collapse; margin: 8px 0 14px;
|
||||||
|
font-size: 11px; width: 100%;
|
||||||
|
}
|
||||||
|
.manual-body th, .manual-body td {
|
||||||
|
border-bottom: 1px solid rgba(255,220,180,0.06);
|
||||||
|
padding: 5px 8px; text-align: left; vertical-align: top;
|
||||||
|
}
|
||||||
|
.manual-body th {
|
||||||
|
color: var(--hw-engrave);
|
||||||
|
font-weight: normal; letter-spacing: 0.12em;
|
||||||
|
text-transform: uppercase; font-size: 9px;
|
||||||
|
}
|
||||||
|
.manual-body td code { font-size: 10px; }
|
||||||
|
.manual-body ul { margin: 4px 0 10px; padding-left: 18px; }
|
||||||
|
.manual-body li { margin: 2px 0; }
|
||||||
|
.manual-body em { color: var(--hw-amber); font-style: normal; }
|
||||||
|
.manual-body a { color: var(--hw-amber); text-decoration: none;
|
||||||
|
border-bottom: 1px dashed rgba(232,160,80,0.4); }
|
||||||
|
.manual-body a:hover { border-bottom-style: solid; }
|
||||||
|
.manual-toc {
|
||||||
|
display: flex; flex-wrap: wrap; gap: 4px 14px;
|
||||||
|
margin: 0 0 18px; padding: 8px 12px;
|
||||||
|
background: rgba(0,0,0,0.35);
|
||||||
|
border-radius: 3px;
|
||||||
|
box-shadow: inset 0 1px 2px rgba(0,0,0,0.7);
|
||||||
|
font-size: 10px; letter-spacing: 0.10em;
|
||||||
|
}
|
||||||
|
.manual-toc a { color: var(--hw-fg-dim); border-bottom: none;
|
||||||
|
text-transform: uppercase; }
|
||||||
|
.manual-toc a:hover { color: var(--hw-amber); }
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -555,6 +701,7 @@
|
|||||||
<div class="top-bar">
|
<div class="top-bar">
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<button class="hw-btn" id="start"><span class="ico-play"></span><span id="start-label">RUN</span></button>
|
<button class="hw-btn" id="start"><span class="ico-play"></span><span id="start-label">RUN</span></button>
|
||||||
|
<button class="hw-btn" id="manual-toggle" title="Manual (?)">MANUAL</button>
|
||||||
</div>
|
</div>
|
||||||
<span><span id="dot" class="dot"></span><span id="status">stopped</span></span>
|
<span><span id="dot" class="dot"></span><span id="status">stopped</span></span>
|
||||||
<span class="header-sep"></span>
|
<span class="header-sep"></span>
|
||||||
@@ -576,6 +723,251 @@
|
|||||||
<div class="bottom-bar">
|
<div class="bottom-bar">
|
||||||
<span class="running-label idle" id="running"><span class="running-dot"></span><span id="running-text">IDLE</span><span class="blink">_</span></span>
|
<span class="running-label idle" id="running"><span class="running-dot"></span><span id="running-text">IDLE</span><span class="blink">_</span></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- ===================== manual overlay ===================== -->
|
||||||
|
<section id="manual" aria-hidden="true">
|
||||||
|
<div class="manual-head">
|
||||||
|
<span class="title">SERVICE MANUAL</span>
|
||||||
|
<span class="sub">code · sinth · v1</span>
|
||||||
|
<button class="manual-close" id="manual-close" title="Close (Esc)">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="manual-body">
|
||||||
|
<p>code-sinth es un sintetizador modular gobernado por un pequeño lenguaje
|
||||||
|
de patches. El editor de la izquierda es la fuente de verdad: cada
|
||||||
|
línea <code>node X = ...</code> declara un nodo del grafo, y
|
||||||
|
<code>out <- ...</code> elige qué señal sale por los altavoces.
|
||||||
|
Cualquier cambio en el editor se recarga en caliente preservando el
|
||||||
|
estado interno (fases de osciladores, posición de secuenciadores,
|
||||||
|
envolventes en vuelo).</p>
|
||||||
|
|
||||||
|
<div class="manual-toc">
|
||||||
|
<a href="#manual-language">Lenguaje</a>
|
||||||
|
<a href="#manual-flow">Cómo se ejecuta</a>
|
||||||
|
<a href="#manual-osc">Osciladores</a>
|
||||||
|
<a href="#manual-envelopes">ADSR / Trig</a>
|
||||||
|
<a href="#manual-filter">Filter</a>
|
||||||
|
<a href="#manual-noise">Noise</a>
|
||||||
|
<a href="#manual-seq">Seq</a>
|
||||||
|
<a href="#manual-delay">Delay</a>
|
||||||
|
<a href="#manual-poly">Poly · Voices</a>
|
||||||
|
<a href="#manual-controls">Knobs · Faders · Pads</a>
|
||||||
|
<a href="#manual-tips">Consejos</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 id="manual-language">Lenguaje del patch</h2>
|
||||||
|
<p>El patch es texto plano. Las construcciones son cuatro:</p>
|
||||||
|
<ul>
|
||||||
|
<li><code>node nombre = expresión</code> — declara un nodo con nombre.
|
||||||
|
Puedes referirlo por su nombre en cualquier expresión posterior.</li>
|
||||||
|
<li><code>out <- expresión</code> — la señal que se manda al
|
||||||
|
<em>output</em>. Debe haber exactamente una.</li>
|
||||||
|
<li><code>voice nombre { ... }</code> — bloque que define una
|
||||||
|
<em>plantilla de voz</em> reutilizable. Dentro tiene su propio
|
||||||
|
<code>node</code> y un <code>out <-</code> propio. Se instancia
|
||||||
|
desde <code>poly(voice=nombre, voices=N, ...)</code>.</li>
|
||||||
|
<li><code># comentario</code> — hasta fin de línea.</li>
|
||||||
|
</ul>
|
||||||
|
<p>Las expresiones admiten números (<code>0.5</code>, <code>440</code>),
|
||||||
|
identificadores, <code>+ - * /</code>, paréntesis, listas
|
||||||
|
<code>[1, 0, 1, 0]</code> y llamadas a funciones-nodo. Los argumentos
|
||||||
|
pueden ser posicionales o con nombre: <code>osc(saw, freq=220)</code>.
|
||||||
|
Las señales se mezclan sumándolas: <code>out <- a*0.5 + b*0.3</code>.
|
||||||
|
Multiplicar por una envolvente actúa como amplificador controlado:
|
||||||
|
<code>o1 * env</code>.</p>
|
||||||
|
|
||||||
|
<h2 id="manual-flow">Cómo se ejecuta</h2>
|
||||||
|
<p>El motor corre en un <em>AudioWorklet</em> y procesa bloques de 128
|
||||||
|
muestras a la sample-rate del navegador (típicamente 48 kHz). En cada
|
||||||
|
bloque se evalúa el grafo en orden topológico: cada nodo lee los
|
||||||
|
buffers de sus entradas, produce su buffer de salida, y al final el
|
||||||
|
buffer de <code>out</code> sale por los altavoces.</p>
|
||||||
|
<p>Pulsa <em>RUN</em> para arrancar el audio (los navegadores requieren
|
||||||
|
un click del usuario antes de abrir el AudioContext). El indicador
|
||||||
|
<em>RUNNING_</em> abajo se enciende cuando el motor está procesando.</p>
|
||||||
|
|
||||||
|
<h2 id="manual-osc">Osciladores · <code>osc</code></h2>
|
||||||
|
<p>Genera una forma de onda continua a la frecuencia indicada.
|
||||||
|
<code>freq</code> puede ser un número fijo o cualquier señal
|
||||||
|
(otro nodo) — eso da modulación.</p>
|
||||||
|
<pre data-snippet="node lfo = osc(sine, freq=4)
|
||||||
|
node pit = 220 + lfo*30
|
||||||
|
node main = osc(saw, freq=pit)
|
||||||
|
out <- main*0.4"><span class="syn-kw">node</span> <span class="syn-id">lfo</span> <span class="syn-op">=</span> <span class="syn-fn">osc</span><span class="syn-op">(</span><span class="syn-id">sine</span><span class="syn-op">,</span> <span class="syn-id">freq</span><span class="syn-op">=</span><span class="syn-num">4</span><span class="syn-op">)</span> <span class="syn-com"># 4 Hz</span>
|
||||||
|
<span class="syn-kw">node</span> <span class="syn-id">pit</span> <span class="syn-op">=</span> <span class="syn-num">220</span> <span class="syn-op">+</span> <span class="syn-id">lfo</span><span class="syn-op">*</span><span class="syn-num">30</span> <span class="syn-com"># vibrato +/-30 Hz</span>
|
||||||
|
<span class="syn-kw">node</span> <span class="syn-id">main</span> <span class="syn-op">=</span> <span class="syn-fn">osc</span><span class="syn-op">(</span><span class="syn-id">saw</span><span class="syn-op">,</span> <span class="syn-id">freq</span><span class="syn-op">=</span><span class="syn-id">pit</span><span class="syn-op">)</span>
|
||||||
|
<span class="syn-kw">out</span> <span class="syn-arrow"><-</span> <span class="syn-id">main</span><span class="syn-op">*</span><span class="syn-num">0.4</span></pre>
|
||||||
|
<table>
|
||||||
|
<tr><th>Forma</th><th>Carácter</th><th>Uso típico</th></tr>
|
||||||
|
<tr><td><code>sine</code></td><td>limpio, sin armónicos</td><td>LFO, sub-bass, pads</td></tr>
|
||||||
|
<tr><td><code>saw</code></td><td>brillante, todos los armónicos</td><td>leads, bajos, strings</td></tr>
|
||||||
|
<tr><td><code>square</code></td><td>solo armónicos impares</td><td>chiptune, bajos huecos</td></tr>
|
||||||
|
<tr><td><code>tri</code></td><td>cálido, armónicos impares decrecientes</td><td>flautas, sub-leads</td></tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h2 id="manual-envelopes">Envolventes · <code>adsr</code> y <code>trig</code></h2>
|
||||||
|
<p><code>trig(period, duration)</code> emite una <em>compuerta</em>
|
||||||
|
(gate) cíclica: alta durante <code>duration</code> segundos, luego
|
||||||
|
baja, repitiéndose cada <code>period</code> segundos.</p>
|
||||||
|
<p><code>adsr(a, d, s, r, gate)</code> es la envolvente clásica.
|
||||||
|
<code>a/d/r</code> en segundos, <code>s</code> nivel 0..1.
|
||||||
|
Mientras <code>gate</code> esté en 1 sube por la fase de attack,
|
||||||
|
cae al sustain y se queda; cuando vuelve a 0 entra en release.</p>
|
||||||
|
<pre data-snippet="node g1 = trig(period=0.5, duration=0.05)
|
||||||
|
node env = adsr(a=0.005, d=0.1, s=0.0, r=0.2, gate=g1)
|
||||||
|
node tone = osc(sine, freq=440)
|
||||||
|
out <- tone * env"><span class="syn-kw">node</span> <span class="syn-id">g1</span> <span class="syn-op">=</span> <span class="syn-fn">trig</span><span class="syn-op">(</span><span class="syn-id">period</span><span class="syn-op">=</span><span class="syn-num">0.5</span><span class="syn-op">,</span> <span class="syn-id">duration</span><span class="syn-op">=</span><span class="syn-num">0.05</span><span class="syn-op">)</span>
|
||||||
|
<span class="syn-kw">node</span> <span class="syn-id">env</span> <span class="syn-op">=</span> <span class="syn-fn">adsr</span><span class="syn-op">(</span><span class="syn-id">a</span><span class="syn-op">=</span><span class="syn-num">0.005</span><span class="syn-op">,</span> <span class="syn-id">d</span><span class="syn-op">=</span><span class="syn-num">0.1</span><span class="syn-op">,</span> <span class="syn-id">s</span><span class="syn-op">=</span><span class="syn-num">0.0</span><span class="syn-op">,</span> <span class="syn-id">r</span><span class="syn-op">=</span><span class="syn-num">0.2</span><span class="syn-op">,</span> <span class="syn-id">gate</span><span class="syn-op">=</span><span class="syn-id">g1</span><span class="syn-op">)</span>
|
||||||
|
<span class="syn-kw">node</span> <span class="syn-id">tone</span> <span class="syn-op">=</span> <span class="syn-fn">osc</span><span class="syn-op">(</span><span class="syn-id">sine</span><span class="syn-op">,</span> <span class="syn-id">freq</span><span class="syn-op">=</span><span class="syn-num">440</span><span class="syn-op">)</span>
|
||||||
|
<span class="syn-kw">out</span> <span class="syn-arrow"><-</span> <span class="syn-id">tone</span> <span class="syn-op">*</span> <span class="syn-id">env</span></pre>
|
||||||
|
<p>Truco: para una <em>percusión</em>, pon <code>s=0</code> y
|
||||||
|
<code>d</code> corto (~0.1 s). Para un <em>pad</em>, sube
|
||||||
|
<code>a</code> a 0.5–2 s y deja <code>s</code> alto.</p>
|
||||||
|
|
||||||
|
<h2 id="manual-filter">Filtro · <code>filter</code></h2>
|
||||||
|
<p>Biquad RBJ, tipos <code>lp</code> (low-pass), <code>hp</code>
|
||||||
|
(high-pass) o <code>bp</code> (band-pass). <code>cutoff</code> y
|
||||||
|
<code>q</code> (resonancia) pueden modularse a tasa de audio —
|
||||||
|
sumarle una envolvente al cutoff es la receta clásica del bajo
|
||||||
|
sintetizado.</p>
|
||||||
|
<pre data-snippet="node src = osc(saw, freq=110)
|
||||||
|
node g = trig(period=0.5, duration=0.2)
|
||||||
|
node e = adsr(a=0.005, d=0.2, s=0.0, r=0.1, gate=g)
|
||||||
|
node lp = filter(lp, in=src, cutoff=300 + e*2500, q=4)
|
||||||
|
out <- lp * e"><span class="syn-kw">node</span> <span class="syn-id">src</span> <span class="syn-op">=</span> <span class="syn-fn">osc</span><span class="syn-op">(</span><span class="syn-id">saw</span><span class="syn-op">,</span> <span class="syn-id">freq</span><span class="syn-op">=</span><span class="syn-num">110</span><span class="syn-op">)</span>
|
||||||
|
<span class="syn-kw">node</span> <span class="syn-id">g</span> <span class="syn-op">=</span> <span class="syn-fn">trig</span><span class="syn-op">(</span><span class="syn-id">period</span><span class="syn-op">=</span><span class="syn-num">0.5</span><span class="syn-op">,</span> <span class="syn-id">duration</span><span class="syn-op">=</span><span class="syn-num">0.2</span><span class="syn-op">)</span>
|
||||||
|
<span class="syn-kw">node</span> <span class="syn-id">e</span> <span class="syn-op">=</span> <span class="syn-fn">adsr</span><span class="syn-op">(</span><span class="syn-id">a</span><span class="syn-op">=</span><span class="syn-num">0.005</span><span class="syn-op">,</span> <span class="syn-id">d</span><span class="syn-op">=</span><span class="syn-num">0.2</span><span class="syn-op">,</span> <span class="syn-id">s</span><span class="syn-op">=</span><span class="syn-num">0</span><span class="syn-op">,</span> <span class="syn-id">r</span><span class="syn-op">=</span><span class="syn-num">0.1</span><span class="syn-op">,</span> <span class="syn-id">gate</span><span class="syn-op">=</span><span class="syn-id">g</span><span class="syn-op">)</span>
|
||||||
|
<span class="syn-kw">node</span> <span class="syn-id">lp</span> <span class="syn-op">=</span> <span class="syn-fn">filter</span><span class="syn-op">(</span><span class="syn-id">lp</span><span class="syn-op">,</span> <span class="syn-id">in</span><span class="syn-op">=</span><span class="syn-id">src</span><span class="syn-op">,</span> <span class="syn-id">cutoff</span><span class="syn-op">=</span><span class="syn-num">300</span> <span class="syn-op">+</span> <span class="syn-id">e</span><span class="syn-op">*</span><span class="syn-num">2500</span><span class="syn-op">,</span> <span class="syn-id">q</span><span class="syn-op">=</span><span class="syn-num">4</span><span class="syn-op">)</span>
|
||||||
|
<span class="syn-kw">out</span> <span class="syn-arrow"><-</span> <span class="syn-id">lp</span> <span class="syn-op">*</span> <span class="syn-id">e</span></pre>
|
||||||
|
|
||||||
|
<h2 id="manual-noise">Ruido · <code>noise</code></h2>
|
||||||
|
<p>Ruido blanco en [-1, 1]. Filtrado con <code>hp</code> alto y una
|
||||||
|
envolvente cortita, da un hi-hat. Filtrado con <code>bp</code> con
|
||||||
|
cutoff bajo, una caja.</p>
|
||||||
|
<pre data-snippet="node n = noise()
|
||||||
|
node g = trig(period=0.25, duration=0.02)
|
||||||
|
node e = adsr(a=0.001, d=0.04, s=0.0, r=0.03, gate=g)
|
||||||
|
node hp = filter(hp, in=n, cutoff=5000, q=1.5)
|
||||||
|
out <- hp * e * 0.4"><span class="syn-kw">node</span> <span class="syn-id">n</span> <span class="syn-op">=</span> <span class="syn-fn">noise</span><span class="syn-op">()</span>
|
||||||
|
<span class="syn-kw">node</span> <span class="syn-id">g</span> <span class="syn-op">=</span> <span class="syn-fn">trig</span><span class="syn-op">(</span><span class="syn-id">period</span><span class="syn-op">=</span><span class="syn-num">0.25</span><span class="syn-op">,</span> <span class="syn-id">duration</span><span class="syn-op">=</span><span class="syn-num">0.02</span><span class="syn-op">)</span>
|
||||||
|
<span class="syn-kw">node</span> <span class="syn-id">e</span> <span class="syn-op">=</span> <span class="syn-fn">adsr</span><span class="syn-op">(</span><span class="syn-id">a</span><span class="syn-op">=</span><span class="syn-num">0.001</span><span class="syn-op">,</span> <span class="syn-id">d</span><span class="syn-op">=</span><span class="syn-num">0.04</span><span class="syn-op">,</span> <span class="syn-id">s</span><span class="syn-op">=</span><span class="syn-num">0</span><span class="syn-op">,</span> <span class="syn-id">r</span><span class="syn-op">=</span><span class="syn-num">0.03</span><span class="syn-op">,</span> <span class="syn-id">gate</span><span class="syn-op">=</span><span class="syn-id">g</span><span class="syn-op">)</span>
|
||||||
|
<span class="syn-kw">node</span> <span class="syn-id">hp</span> <span class="syn-op">=</span> <span class="syn-fn">filter</span><span class="syn-op">(</span><span class="syn-id">hp</span><span class="syn-op">,</span> <span class="syn-id">in</span><span class="syn-op">=</span><span class="syn-id">n</span><span class="syn-op">,</span> <span class="syn-id">cutoff</span><span class="syn-op">=</span><span class="syn-num">5000</span><span class="syn-op">,</span> <span class="syn-id">q</span><span class="syn-op">=</span><span class="syn-num">1.5</span><span class="syn-op">)</span>
|
||||||
|
<span class="syn-kw">out</span> <span class="syn-arrow"><-</span> <span class="syn-id">hp</span> <span class="syn-op">*</span> <span class="syn-id">e</span> <span class="syn-op">*</span> <span class="syn-num">0.4</span></pre>
|
||||||
|
|
||||||
|
<h2 id="manual-seq">Secuenciador básico · <code>seq</code></h2>
|
||||||
|
<p>Recorre una lista de valores a <code>rate</code> pasos por segundo,
|
||||||
|
manteniendo cada valor hasta el siguiente. Útil para listas de
|
||||||
|
frecuencias o de niveles de control.</p>
|
||||||
|
<pre data-snippet="node freqs = seq(rate=4, steps=[220, 277, 330, 220])
|
||||||
|
node tone = osc(saw, freq=freqs)
|
||||||
|
node g = trig(period=0.25, duration=0.18)
|
||||||
|
node e = adsr(a=0.005, d=0.1, s=0.6, r=0.2, gate=g)
|
||||||
|
out <- tone * e * 0.4"><span class="syn-kw">node</span> <span class="syn-id">freqs</span> <span class="syn-op">=</span> <span class="syn-fn">seq</span><span class="syn-op">(</span><span class="syn-id">rate</span><span class="syn-op">=</span><span class="syn-num">4</span><span class="syn-op">,</span> <span class="syn-id">steps</span><span class="syn-op">=[</span><span class="syn-num">220</span><span class="syn-op">,</span> <span class="syn-num">277</span><span class="syn-op">,</span> <span class="syn-num">330</span><span class="syn-op">,</span> <span class="syn-num">220</span><span class="syn-op">])</span>
|
||||||
|
<span class="syn-kw">node</span> <span class="syn-id">tone</span> <span class="syn-op">=</span> <span class="syn-fn">osc</span><span class="syn-op">(</span><span class="syn-id">saw</span><span class="syn-op">,</span> <span class="syn-id">freq</span><span class="syn-op">=</span><span class="syn-id">freqs</span><span class="syn-op">)</span>
|
||||||
|
<span class="syn-kw">node</span> <span class="syn-id">g</span> <span class="syn-op">=</span> <span class="syn-fn">trig</span><span class="syn-op">(</span><span class="syn-id">period</span><span class="syn-op">=</span><span class="syn-num">0.25</span><span class="syn-op">,</span> <span class="syn-id">duration</span><span class="syn-op">=</span><span class="syn-num">0.18</span><span class="syn-op">)</span>
|
||||||
|
<span class="syn-kw">node</span> <span class="syn-id">e</span> <span class="syn-op">=</span> <span class="syn-fn">adsr</span><span class="syn-op">(</span><span class="syn-id">a</span><span class="syn-op">=</span><span class="syn-num">0.005</span><span class="syn-op">,</span> <span class="syn-id">d</span><span class="syn-op">=</span><span class="syn-num">0.1</span><span class="syn-op">,</span> <span class="syn-id">s</span><span class="syn-op">=</span><span class="syn-num">0.6</span><span class="syn-op">,</span> <span class="syn-id">r</span><span class="syn-op">=</span><span class="syn-num">0.2</span><span class="syn-op">,</span> <span class="syn-id">gate</span><span class="syn-op">=</span><span class="syn-id">g</span><span class="syn-op">)</span>
|
||||||
|
<span class="syn-kw">out</span> <span class="syn-arrow"><-</span> <span class="syn-id">tone</span> <span class="syn-op">*</span> <span class="syn-id">e</span> <span class="syn-op">*</span> <span class="syn-num">0.4</span></pre>
|
||||||
|
|
||||||
|
<h2 id="manual-delay">Delay · <code>delay</code></h2>
|
||||||
|
<p>Línea de delay con feedback. <code>time</code> en segundos,
|
||||||
|
<code>feedback</code> 0..0.99, <code>mix</code> 0..1
|
||||||
|
(0 = solo seco, 1 = solo retardado).</p>
|
||||||
|
<pre data-snippet="node src = osc(tri, freq=330)
|
||||||
|
node g = trig(period=1.0, duration=0.05)
|
||||||
|
node e = adsr(a=0.005, d=0.2, s=0, r=0.1, gate=g)
|
||||||
|
node dry = src * e
|
||||||
|
node dl = delay(in=dry, time=0.375, feedback=0.55, mix=0.5)
|
||||||
|
out <- dl * 0.5"><span class="syn-kw">node</span> <span class="syn-id">src</span> <span class="syn-op">=</span> <span class="syn-fn">osc</span><span class="syn-op">(</span><span class="syn-id">tri</span><span class="syn-op">,</span> <span class="syn-id">freq</span><span class="syn-op">=</span><span class="syn-num">330</span><span class="syn-op">)</span>
|
||||||
|
<span class="syn-kw">node</span> <span class="syn-id">g</span> <span class="syn-op">=</span> <span class="syn-fn">trig</span><span class="syn-op">(</span><span class="syn-id">period</span><span class="syn-op">=</span><span class="syn-num">1.0</span><span class="syn-op">,</span> <span class="syn-id">duration</span><span class="syn-op">=</span><span class="syn-num">0.05</span><span class="syn-op">)</span>
|
||||||
|
<span class="syn-kw">node</span> <span class="syn-id">e</span> <span class="syn-op">=</span> <span class="syn-fn">adsr</span><span class="syn-op">(</span><span class="syn-id">a</span><span class="syn-op">=</span><span class="syn-num">0.005</span><span class="syn-op">,</span> <span class="syn-id">d</span><span class="syn-op">=</span><span class="syn-num">0.2</span><span class="syn-op">,</span> <span class="syn-id">s</span><span class="syn-op">=</span><span class="syn-num">0</span><span class="syn-op">,</span> <span class="syn-id">r</span><span class="syn-op">=</span><span class="syn-num">0.1</span><span class="syn-op">,</span> <span class="syn-id">gate</span><span class="syn-op">=</span><span class="syn-id">g</span><span class="syn-op">)</span>
|
||||||
|
<span class="syn-kw">node</span> <span class="syn-id">dry</span> <span class="syn-op">=</span> <span class="syn-id">src</span> <span class="syn-op">*</span> <span class="syn-id">e</span>
|
||||||
|
<span class="syn-kw">node</span> <span class="syn-id">dl</span> <span class="syn-op">=</span> <span class="syn-fn">delay</span><span class="syn-op">(</span><span class="syn-id">in</span><span class="syn-op">=</span><span class="syn-id">dry</span><span class="syn-op">,</span> <span class="syn-id">time</span><span class="syn-op">=</span><span class="syn-num">0.375</span><span class="syn-op">,</span> <span class="syn-id">feedback</span><span class="syn-op">=</span><span class="syn-num">0.55</span><span class="syn-op">,</span> <span class="syn-id">mix</span><span class="syn-op">=</span><span class="syn-num">0.5</span><span class="syn-op">)</span>
|
||||||
|
<span class="syn-kw">out</span> <span class="syn-arrow"><-</span> <span class="syn-id">dl</span> <span class="syn-op">*</span> <span class="syn-num">0.5</span></pre>
|
||||||
|
|
||||||
|
<h2 id="manual-poly">Polifonía · <code>voice</code> + <code>poly</code></h2>
|
||||||
|
<p>Define una <em>plantilla de voz</em> con un bloque
|
||||||
|
<code>voice nombre { ... }</code>: dentro tendrás dos identificadores
|
||||||
|
especiales — <code>freq</code> (la frecuencia que el allocator
|
||||||
|
asigna a la voz) y <code>gate</code> (la compuerta que se abre
|
||||||
|
durante <code>gate_duration</code> segundos por cada nota).</p>
|
||||||
|
<p>Luego instancia con <code>poly(voice=nombre, voices=N, rate, notes,
|
||||||
|
gate_duration)</code>. <code>notes</code> es una lista de frecuencias;
|
||||||
|
<code>0</code> es silencio. El allocator asigna la voz menos reciente
|
||||||
|
(LRU) a cada nota nueva.</p>
|
||||||
|
<pre data-snippet="voice synth {
|
||||||
|
node o = osc(saw, freq=freq)
|
||||||
|
node e = adsr(a=0.005, d=0.2, s=0.4, r=0.3, gate=gate)
|
||||||
|
node f = filter(lp, in=o, cutoff=600 + e*1800, q=2.0)
|
||||||
|
out <- f * e
|
||||||
|
}
|
||||||
|
node mel = poly(voice=synth, voices=4, rate=4, gate_duration=0.18,
|
||||||
|
notes=[220, 277, 330, 0, 220, 330, 277, 0])
|
||||||
|
out <- mel * 0.4"><span class="syn-kw">voice</span> <span class="syn-id">synth</span> <span class="syn-op">{</span>
|
||||||
|
<span class="syn-kw">node</span> <span class="syn-id">o</span> <span class="syn-op">=</span> <span class="syn-fn">osc</span><span class="syn-op">(</span><span class="syn-id">saw</span><span class="syn-op">,</span> <span class="syn-id">freq</span><span class="syn-op">=</span><span class="syn-id">freq</span><span class="syn-op">)</span>
|
||||||
|
<span class="syn-kw">node</span> <span class="syn-id">e</span> <span class="syn-op">=</span> <span class="syn-fn">adsr</span><span class="syn-op">(</span><span class="syn-id">a</span><span class="syn-op">=</span><span class="syn-num">0.005</span><span class="syn-op">,</span> <span class="syn-id">d</span><span class="syn-op">=</span><span class="syn-num">0.2</span><span class="syn-op">,</span> <span class="syn-id">s</span><span class="syn-op">=</span><span class="syn-num">0.4</span><span class="syn-op">,</span> <span class="syn-id">r</span><span class="syn-op">=</span><span class="syn-num">0.3</span><span class="syn-op">,</span> <span class="syn-id">gate</span><span class="syn-op">=</span><span class="syn-id">gate</span><span class="syn-op">)</span>
|
||||||
|
<span class="syn-kw">node</span> <span class="syn-id">f</span> <span class="syn-op">=</span> <span class="syn-fn">filter</span><span class="syn-op">(</span><span class="syn-id">lp</span><span class="syn-op">,</span> <span class="syn-id">in</span><span class="syn-op">=</span><span class="syn-id">o</span><span class="syn-op">,</span> <span class="syn-id">cutoff</span><span class="syn-op">=</span><span class="syn-num">600</span> <span class="syn-op">+</span> <span class="syn-id">e</span><span class="syn-op">*</span><span class="syn-num">1800</span><span class="syn-op">,</span> <span class="syn-id">q</span><span class="syn-op">=</span><span class="syn-num">2.0</span><span class="syn-op">)</span>
|
||||||
|
<span class="syn-kw">out</span> <span class="syn-arrow"><-</span> <span class="syn-id">f</span> <span class="syn-op">*</span> <span class="syn-id">e</span>
|
||||||
|
<span class="syn-op">}</span>
|
||||||
|
<span class="syn-kw">node</span> <span class="syn-id">mel</span> <span class="syn-op">=</span> <span class="syn-fn">poly</span><span class="syn-op">(</span><span class="syn-id">voice</span><span class="syn-op">=</span><span class="syn-id">synth</span><span class="syn-op">,</span> <span class="syn-id">voices</span><span class="syn-op">=</span><span class="syn-num">4</span><span class="syn-op">,</span> <span class="syn-id">rate</span><span class="syn-op">=</span><span class="syn-num">4</span><span class="syn-op">,</span> <span class="syn-id">gate_duration</span><span class="syn-op">=</span><span class="syn-num">0.18</span><span class="syn-op">,</span>
|
||||||
|
<span class="syn-id">notes</span><span class="syn-op">=[</span><span class="syn-num">220</span><span class="syn-op">,</span> <span class="syn-num">277</span><span class="syn-op">,</span> <span class="syn-num">330</span><span class="syn-op">,</span> <span class="syn-num">0</span><span class="syn-op">,</span> <span class="syn-num">220</span><span class="syn-op">,</span> <span class="syn-num">330</span><span class="syn-op">,</span> <span class="syn-num">277</span><span class="syn-op">,</span> <span class="syn-num">0</span><span class="syn-op">])</span>
|
||||||
|
<span class="syn-kw">out</span> <span class="syn-arrow"><-</span> <span class="syn-id">mel</span> <span class="syn-op">*</span> <span class="syn-num">0.4</span></pre>
|
||||||
|
|
||||||
|
<h2 id="manual-controls">Knobs · faders · pads</h2>
|
||||||
|
<p>Hay cuatro tipos de nodo cuyo valor lo decide el panel derecho en
|
||||||
|
vez del código. Al declararlos aparece un widget físico al que
|
||||||
|
puedes interactuar con el ratón.</p>
|
||||||
|
|
||||||
|
<h3>Knob</h3>
|
||||||
|
<pre data-snippet="node cutoff = knob(min=200, max=4000, default=900)"><span class="syn-kw">node</span> <span class="syn-id">cutoff</span> <span class="syn-op">=</span> <span class="syn-fn">knob</span><span class="syn-op">(</span><span class="syn-id">min</span><span class="syn-op">=</span><span class="syn-num">200</span><span class="syn-op">,</span> <span class="syn-id">max</span><span class="syn-op">=</span><span class="syn-num">4000</span><span class="syn-op">,</span> <span class="syn-id">default</span><span class="syn-op">=</span><span class="syn-num">900</span><span class="syn-op">)</span></pre>
|
||||||
|
<p>Arrastra arriba/abajo para girarlo, doble-click para volver al
|
||||||
|
centro, <em>Shift</em> + arrastrar para ajuste fino.</p>
|
||||||
|
|
||||||
|
<h3>Fader</h3>
|
||||||
|
<pre data-snippet="node mix = fader(min=0, max=1, default=0.5)"><span class="syn-kw">node</span> <span class="syn-id">mix</span> <span class="syn-op">=</span> <span class="syn-fn">fader</span><span class="syn-op">(</span><span class="syn-id">min</span><span class="syn-op">=</span><span class="syn-num">0</span><span class="syn-op">,</span> <span class="syn-id">max</span><span class="syn-op">=</span><span class="syn-num">1</span><span class="syn-op">,</span> <span class="syn-id">default</span><span class="syn-op">=</span><span class="syn-num">0.5</span><span class="syn-op">)</span></pre>
|
||||||
|
|
||||||
|
<h3>Step sequencer</h3>
|
||||||
|
<pre data-snippet="node kick = step_seq(rate=8, steps=16,
|
||||||
|
default=[1,0,0,0, 1,0,0,0, 1,0,0,0, 1,0,0,0])"><span class="syn-kw">node</span> <span class="syn-id">kick</span> <span class="syn-op">=</span> <span class="syn-fn">step_seq</span><span class="syn-op">(</span><span class="syn-id">rate</span><span class="syn-op">=</span><span class="syn-num">8</span><span class="syn-op">,</span> <span class="syn-id">steps</span><span class="syn-op">=</span><span class="syn-num">16</span><span class="syn-op">,</span>
|
||||||
|
<span class="syn-id">default</span><span class="syn-op">=[</span><span class="syn-num">1</span><span class="syn-op">,</span><span class="syn-num">0</span><span class="syn-op">,</span><span class="syn-num">0</span><span class="syn-op">,</span><span class="syn-num">0</span><span class="syn-op">,</span> <span class="syn-num">1</span><span class="syn-op">,</span><span class="syn-num">0</span><span class="syn-op">,</span><span class="syn-num">0</span><span class="syn-op">,</span><span class="syn-num">0</span><span class="syn-op">,</span> <span class="syn-num">1</span><span class="syn-op">,</span><span class="syn-num">0</span><span class="syn-op">,</span><span class="syn-num">0</span><span class="syn-op">,</span><span class="syn-num">0</span><span class="syn-op">,</span> <span class="syn-num">1</span><span class="syn-op">,</span><span class="syn-num">0</span><span class="syn-op">,</span><span class="syn-num">0</span><span class="syn-op">,</span><span class="syn-num">0</span><span class="syn-op">])</span></pre>
|
||||||
|
<p>Click en una celda para encender/apagar; arrastra para pintar
|
||||||
|
muchas a la vez. La salida es 0/1, perfecta como
|
||||||
|
<code>gate=</code> de un ADSR.</p>
|
||||||
|
|
||||||
|
<h3>Piano roll</h3>
|
||||||
|
<pre data-snippet="node mel = piano_roll(voice=synth, voices=4,
|
||||||
|
rate=8, length=16, octaves=2, base=220,
|
||||||
|
gate_duration=0.18)"><span class="syn-kw">node</span> <span class="syn-id">mel</span> <span class="syn-op">=</span> <span class="syn-fn">piano_roll</span><span class="syn-op">(</span><span class="syn-id">voice</span><span class="syn-op">=</span><span class="syn-id">synth</span><span class="syn-op">,</span> <span class="syn-id">voices</span><span class="syn-op">=</span><span class="syn-num">4</span><span class="syn-op">,</span>
|
||||||
|
<span class="syn-id">rate</span><span class="syn-op">=</span><span class="syn-num">8</span><span class="syn-op">,</span> <span class="syn-id">length</span><span class="syn-op">=</span><span class="syn-num">16</span><span class="syn-op">,</span> <span class="syn-id">octaves</span><span class="syn-op">=</span><span class="syn-num">2</span><span class="syn-op">,</span> <span class="syn-id">base</span><span class="syn-op">=</span><span class="syn-num">220</span><span class="syn-op">,</span>
|
||||||
|
<span class="syn-id">gate_duration</span><span class="syn-op">=</span><span class="syn-num">0.18</span><span class="syn-op">)</span></pre>
|
||||||
|
<p>Es un <code>poly()</code> con las notas dispuestas en una rejilla
|
||||||
|
de pasos × semitonos. Click en cualquier celda para colocar una
|
||||||
|
nota. La columna 0 es <code>base</code> Hz, cada fila es un
|
||||||
|
semitono más arriba. Necesita un <code>voice</code> previo, igual
|
||||||
|
que <code>poly</code>.</p>
|
||||||
|
|
||||||
|
<h2 id="manual-tips">Consejos</h2>
|
||||||
|
<ul>
|
||||||
|
<li><em>Empieza bajo.</em> El gain del header es 0.30 por algo:
|
||||||
|
sumar varias señales sin escalar satura rápido.</li>
|
||||||
|
<li><em>Multiplica por la envolvente al final.</em>
|
||||||
|
<code>filter(...) * env</code> evita clics al inicio/final
|
||||||
|
mejor que poner la envolvente solo en el cutoff.</li>
|
||||||
|
<li><em>Hot-reload preserva el estado.</em> Los osciladores no
|
||||||
|
saltan de fase, los secuenciadores no resetean. Borra y reescribe
|
||||||
|
el nodo si quieres reiniciarlo.</li>
|
||||||
|
<li><em>El waveform inline después de cada nodo</em> es lo que está
|
||||||
|
saliendo realmente del grafo en ese punto. Útil para ver dónde
|
||||||
|
se está rompiendo el sonido.</li>
|
||||||
|
<li><em>Para mezclar varias fuentes</em>, súmalas con
|
||||||
|
<code>+</code> y multiplica el total por un factor pequeño:
|
||||||
|
<code>out <- bass*0.6 + drum*0.5 + lead*0.4</code>.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p style="color: var(--hw-engrave); font-size: 10px; margin-top: 28px;
|
||||||
|
letter-spacing: 0.18em; text-align: center; text-transform: uppercase;">
|
||||||
|
— fin del manual —
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script type="module">
|
<script type="module">
|
||||||
@@ -1506,6 +1898,43 @@ requestAnimationFrame(tick);
|
|||||||
try { localStorage.removeItem(KEY); } catch {}
|
try { localStorage.removeItem(KEY); } catch {}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =====================================================================
|
||||||
|
// manual — in-app docs overlay. Toggle button in top bar, ESC closes.
|
||||||
|
// Each <pre data-snippet="..."> in the manual gets a TRY button that
|
||||||
|
// replaces the editor doc with that snippet (Ctrl/Cmd-Z to undo).
|
||||||
|
// =====================================================================
|
||||||
|
{
|
||||||
|
const manual = document.getElementById('manual');
|
||||||
|
const toggle = document.getElementById('manual-toggle');
|
||||||
|
const closeBtn = document.getElementById('manual-close');
|
||||||
|
function open() { manual.classList.add('open'); manual.setAttribute('aria-hidden', 'false'); }
|
||||||
|
function close() { manual.classList.remove('open'); manual.setAttribute('aria-hidden', 'true'); }
|
||||||
|
toggle.addEventListener('click', () => {
|
||||||
|
if (manual.classList.contains('open')) close(); else open();
|
||||||
|
});
|
||||||
|
closeBtn.addEventListener('click', close);
|
||||||
|
document.addEventListener('keydown', (e) => {
|
||||||
|
if (e.key === 'Escape' && manual.classList.contains('open')) close();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Inject TRY buttons on every snippet block.
|
||||||
|
for (const pre of manual.querySelectorAll('pre[data-snippet]')) {
|
||||||
|
const btn = document.createElement('button');
|
||||||
|
btn.className = 'try-btn';
|
||||||
|
btn.textContent = 'TRY';
|
||||||
|
btn.title = 'Replace the editor with this snippet (Ctrl/Cmd-Z to undo)';
|
||||||
|
btn.addEventListener('click', () => {
|
||||||
|
const snippet = pre.getAttribute('data-snippet');
|
||||||
|
view.dispatch({
|
||||||
|
changes: { from: 0, to: view.state.doc.length, insert: snippet + '\n' },
|
||||||
|
});
|
||||||
|
close();
|
||||||
|
view.focus();
|
||||||
|
});
|
||||||
|
pre.appendChild(btn);
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user