Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>Permutation Music</title>
- <style>
- * {
- margin: 0;
- padding: 0;
- box-sizing: border-box;
- }
- body {
- background: #000;
- color: #0f0;
- font-family: monospace;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- height: 100vh;
- margin: 0;
- overflow: hidden;
- }
- .container {
- text-align: center;
- background: #111;
- padding: 16px 20px;
- border: 1px solid #0f0;
- border-radius: 4px;
- width: 98vw;
- max-width: 1200px;
- }
- canvas {
- background: #020202;
- border: 1px solid #0f0;
- display: block;
- margin: 0 auto 12px;
- width: 100%;
- height: auto;
- image-rendering: crisp-edges;
- }
- button {
- background: #0f0;
- color: #000;
- border: none;
- padding: 12px 28px;
- font-family: monospace;
- font-weight: bold;
- font-size: 15px;
- cursor: pointer;
- letter-spacing: 1px;
- transition: background 0.15s;
- }
- button:hover {
- background: #0c0;
- }
- .controls {
- margin-top: 12px;
- display: flex;
- flex-direction: column;
- gap: 6px;
- font-size: 13px;
- }
- input[type=range] {
- accent-color: #0f0;
- width: 100%;
- max-width: 360px;
- cursor: pointer;
- }
- label {
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 10px;
- }
- </style>
- </head>
- <body>
- <div class="container">
- <canvas id="c" width="1000" height="260"></canvas>
- <button id="b">START COUNTER</button>
- <div class="controls">
- <label>BPM: <span id="bv">174</span></label>
- <input type="range" id="bs" min="40" max="400" value="174">
- <div>COUNT: <span id="nv">0</span></div>
- <label>VOL: <span id="vv">100</span>%</label>
- <input type="range" id="vs" min="0" max="100" value="100">
- </div>
- </div>
- <script>
- const c = document.getElementById('c'),
- ctx = c.getContext('2d'),
- b = document.getElementById('b'),
- bs = document.getElementById('bs'),
- bv = document.getElementById('bv'),
- nv = document.getElementById('nv'),
- vs = document.getElementById('vs'),
- vv = document.getElementById('vv');
- let W = c.width,
- H = c.height,
- CY = H / 2;
- let a = null,
- an = null,
- g = null,
- d = null,
- p = !1,
- bpm = 174,
- s = 0,
- n = 0,
- t = 0,
- tid = null;
- const rc = new Float32Array(16),
- ic = new Float32Array(16);
- let ad = new Uint8Array(0);
- d = new Float32Array(44100);
- const k = 42,
- deg = Math.PI / 180;
- for (let i = 0; i < 44100; i++) {
- const x = (i * 2) / 44100 - 1;
- d[i] = ((3 + k) * x * 20 * deg) / (Math.PI + k * Math.abs(x));
- }
- const MAX_PERM = 12000;
- const permSeq = Array.from({ length: MAX_PERM }, (_, i) => i);
- const popcnt = x => {
- let c = 0;
- while (x) { c += x & 1;
- x >>>= 1; }
- return c;
- };
- permSeq.sort((a, b) => popcnt(a) - popcnt(b) || a - b);
- let permIdx = 0;
- function resizeCanvas() {
- const container = c.parentNode;
- const newWidth = Math.min(container.clientWidth - 4, 1200);
- if (newWidth !== W) {
- W = newWidth;
- c.width = W;
- CY = H / 2;
- }
- }
- window.addEventListener('resize', resizeCanvas);
- resizeCanvas();
- bs.addEventListener('input', e => { bpm = +e.target.value;
- bv.textContent = bpm; });
- vs.addEventListener('input', e => {
- vv.textContent = e.target.value;
- if (g) g.gain.setValueAtTime(+e.target.value / 100, a.currentTime);
- });
- b.addEventListener('click', () => {
- if (!a) {
- a = new(window.AudioContext || window.webkitAudioContext)();
- an = a.createAnalyser();
- an.fftSize = 2048;
- ad = new Uint8Array(an.frequencyBinCount);
- g = a.createGain();
- g.gain.setValueAtTime(+vs.value / 100, a.currentTime);
- g.connect(a.destination);
- }
- if (a.state === 'suspended') a.resume();
- if (p) {
- p = !1;
- b.textContent = 'START COUNTER';
- clearTimeout(tid);
- } else {
- p = !0;
- b.textContent = 'STOP COUNTER';
- t = a.currentTime;
- s = 0;
- sch();
- }
- });
- function sch() {
- while (t < a.currentTime + 0.12) {
- q(s, t);
- const st = (60 / bpm) / 2;
- t += st;
- s++;
- if (s >= 8) {
- s = 0;
- n = permSeq[permIdx % MAX_PERM];
- permIdx++;
- if (permIdx >= MAX_PERM) permIdx = 0;
- nv.textContent = n;
- }
- }
- tid = setTimeout(sch, 20);
- }
- function q(S, T) {
- const p0 = (n ^ (n >> 5) ^ (S * 0x4D)) >>> 0;
- const p1 = ((n * 2654435761) >>> 0) ^ (S << 5);
- const p2 = (n >> (S & 7)) ^ (n << (13 - (S & 7)));
- const m = (p0 >> 3) & 7;
- const isDrum = ((n >> (S & 15)) & 7) === m || ((p1 >> 4) & 7) === (S & 7);
- const baseF = 38 + ((p0 ^ (p0 >> 6)) % 4200);
- const fA = Math.min(6800, Math.max(28, baseF));
- const ratios = [1, 1.006, 1.498, 2.001, 0.499, 0.751, 1.334, 0.667];
- const ri = (p1 >> (S * 3)) & 7;
- const fB = Math.min(7200, Math.max(26, fA * ratios[ri]));
- const dA = isDrum ? 0.025 + ((p2 & 15) * 0.008) : 0.04 + ((p0 >> (S + 2)) & 15) * 0.045;
- const dB = dA * (0.55 + ((p1 >> 6) & 7) * 0.09);
- rc.fill(0);
- ic.fill(0);
- for (let i = 1; i < 16; i++) {
- const b0 = (p0 >> ((i + S) & 15)) & 1;
- const b1 = (p1 >> ((i * 3 + S + 5) & 15)) & 1;
- const b2 = (p2 >> ((i * 2 + S + 11) & 15)) & 1;
- const base = 0.25 / i;
- rc[i] = base + (b0 ? 1.4 / i : 0) + (b1 ? 0.55 / i : 0) + (b2 ? 0.2 / i : 0);
- ic[i] = ((p0 >> ((i + S + 7) & 15)) & 1) ? 0.7 / i : ((p1 >> ((i * 2 + S) & 15)) & 1) ? 0.35 / i : 0;
- }
- const wA = a.createPeriodicWave(rc, ic);
- const wB = a.createPeriodicWave(
- new Float32Array(rc.map((v, i) => v * (0.7 + ((p2 >> i) & 1) * 0.5))),
- new Float32Array(ic.map((v, i) => v * (0.6 + ((p0 >> (i + 3)) & 1) * 0.6)))
- );
- const sh = a.createWaveShaper();
- sh.curve = d;
- sh.oversample = '4x';
- const gA = a.createGain(),
- gB = a.createGain();
- const volA = isDrum ? 0.5 + ((p1 >> 2) & 3) * 0.12 : 0.28 + ((p0 >> 5) & 7) * 0.05;
- const volB = volA * (0.4 + ((p2 >> 3) & 7) * 0.08);
- gA.gain.setValueAtTime(volA, T);
- gA.gain.exponentialRampToValueAtTime(0.0008, T + dA);
- gB.gain.setValueAtTime(volB, T);
- gB.gain.exponentialRampToValueAtTime(0.0006, T + dB);
- const oA = a.createOscillator(),
- oB = a.createOscillator();
- oA.setPeriodicWave(wA);
- oA.frequency.setValueAtTime(fA, T);
- oB.setPeriodicWave(wB);
- oB.frequency.setValueAtTime(fB, T);
- oA.connect(gA);
- oB.connect(gB);
- gA.connect(sh);
- gB.connect(sh);
- sh.connect(an);
- an.connect(g);
- oA.start(T);
- oA.stop(T + dA + 0.02);
- oB.start(T);
- oB.stop(T + dB + 0.02);
- }
- function draw() {
- resizeCanvas();
- ctx.clearRect(0, 0, W, H);
- if (p && an && ad.length) {
- an.getByteTimeDomainData(ad);
- ctx.strokeStyle = '#f00';
- ctx.lineWidth = 1;
- ctx.beginPath();
- const len = ad.length;
- for (let i = 0; i < len; i++) {
- const x = (i / (len - 1)) * W;
- const y = (ad[i] / 510) * H * 2;
- if (i === 0) ctx.moveTo(x, y);
- else ctx.lineTo(x, y);
- }
- ctx.stroke();
- } else {
- ctx.strokeStyle = '#0f0';
- ctx.lineWidth = 1;
- ctx.beginPath();
- ctx.moveTo(0, CY);
- ctx.lineTo(W, CY);
- ctx.stroke();
- }
- requestAnimationFrame(draw);
- }
- draw();
- </script>
- </body>
- </html>
Advertisement
Add Comment
Please, Sign In to add comment