/* Blipwren SFX player: Web Audio, no libraries. © 2026 CyberMax */ (function () { "use strict"; var CHECKOUT = "https://buy.polar.sh/polar_cl_AUuQ9JEVj98iOYnwpGKLQAqxklIRWpColFOBF1GXPFh"; // Polar checkout link (revenue/packs/polar/blipwren/polar.json) var BUNDLE = "https://buy.polar.sh/polar_cl_0VZrsUmgkJkVTDpll5E0TTHLI9eloGE5pmDyS4D9P8L"; // Indie Game Dev Bundle checkout link var $ = function (s) { return document.querySelector(s); }; var ctx = null, gain = null, meta = null, cat = "all", query = ""; var buffers = {}; // sprite id or "free:" -> AudioBuffer var tiles = {}; // sound id -> {el, canvas, s} var current = null, playAllToken = 0; function audio() { if (!ctx) { var AC = window.AudioContext || window.webkitAudioContext; ctx = new AC(); gain = ctx.createGain(); gain.gain.value = $("#vol").value / 100; gain.connect(ctx.destination); } return ctx; } function toast(msg) { var t = $("#toast"); t.textContent = msg; t.classList.add("on"); clearTimeout(toast._t); toast._t = setTimeout(function () { t.classList.remove("on"); }, 3200); } function decode(url) { return fetch(url).then(function (r) { if (!r.ok) throw new Error(url + " " + r.status); return r.arrayBuffer(); }) .then(function (ab) { return new Promise(function (res, rej) { audio().decodeAudioData(ab, res, rej); }); }); } function load(s) { // full quality for free sounds, category sprite for the rest var key = s.free ? "free:" + s.id : s.sprite; if (buffers[key]) return Promise.resolve(buffers[key]); var url = s.free ? "audio/free/" + s.id + ".wav" : "audio/preview/" + s.sprite.toLowerCase() + ".wav"; buffers[key] = decode(url).then(function (b) { buffers[key] = b; return b; }); return buffers[key]; } function play(s) { audio(); if (ctx.state === "suspended") ctx.resume(); return Promise.resolve(load(s)).then(function (buf) { if (current) { try { current.src.stop(); } catch (e) {} current.tile.classList.remove("playing"); } var src = ctx.createBufferSource(); src.buffer = buf; if ($("#vary").checked) src.playbackRate.value = 1 + (Math.random() * 0.1 - 0.05); src.connect(gain); var tile = tiles[s.id].el; tile.classList.add("playing"); var dur = s.free ? buf.duration : s.len; if (s.free) src.start(0); else src.start(0, s.at, s.len); var me = current = { src: src, tile: tile }; src.onended = function () { tile.classList.remove("playing"); if (current === me) current = null; }; return dur / src.playbackRate.value; }).catch(function (e) { toast("Couldn't play that sound: " + e.message); return 0; }); } function drawWave(s) { var t = tiles[s.id]; if (!t) return; var buf = buffers[s.free ? "free:" + s.id : s.sprite]; if (!buf || typeof buf.then === "function") return; var c = t.canvas, dpr = window.devicePixelRatio || 1; var w = c.clientWidth || 180, h = c.clientHeight || 28; c.width = Math.round(w * dpr); c.height = Math.round(h * dpr); var g = c.getContext("2d"); g.scale(dpr, dpr); var data = buf.getChannelData(0), sr = buf.sampleRate; var a = s.free ? 0 : Math.floor(s.at * sr), n = s.free ? data.length : Math.floor(s.len * sr); var cols = Math.max(20, Math.floor(w / 2)), step = n / cols; g.fillStyle = s.free ? "#34d399" : "#FFB020"; for (var i = 0; i < cols; i++) { var peak = 0, from = a + Math.floor(i * step), to = a + Math.floor((i + 1) * step); for (var j = from; j < to; j += 4) { var v = Math.abs(data[j] || 0); if (v > peak) peak = v; } var bh = Math.max(1, peak * (h - 2)); g.fillRect(i * 2, (h - bh) / 2, 1.4, bh); } } function label(c) { return c === "all" ? "All" : meta.categories.filter(function (x) { return x.id === c; })[0].label; } function buildChips() { var box = $("#cats"), list = ["all"].concat(meta.categories.map(function (c) { return c.id; })); list.forEach(function (c) { var b = document.createElement("button"); b.className = "chip" + (c === cat ? " on" : ""); b.dataset.cat = c; var n = c === "all" ? meta.count : meta.sounds.filter(function (s) { return s.cat === c; }).length; b.innerHTML = label(c) + '' + n + ""; b.addEventListener("click", function () { cat = c; Array.prototype.forEach.call(box.children, function (x) { x.classList.toggle("on", x.dataset.cat === c); }); filter(); }); box.appendChild(b); }); } function buildGrid() { var grid = $("#grid"); meta.sounds.forEach(function (s) { var el = document.createElement("button"); el.className = "snd"; el.type = "button"; el.setAttribute("aria-label", "Play " + s.name + (s.free ? " (free)" : "")); el.innerHTML = ''; el.querySelector(".nm").textContent = s.name; el.querySelector(".ds").textContent = s.desc; el.querySelector(".dur").textContent = s.dur.toFixed(2) + "s"; if (s.free) { var foot = document.createElement("span"); foot.className = "foot"; var badge = document.createElement("span"); badge.className = "free-badge"; badge.textContent = "FREE"; foot.appendChild(badge); var dl = document.createElement("a"); dl.className = "dl"; dl.href = "audio/free/" + s.id + ".wav"; dl.download = "blipwren_" + s.id + ".wav"; dl.textContent = "WAV ↓"; dl.addEventListener("click", function (e) { e.stopPropagation(); }); foot.appendChild(dl); el.appendChild(foot); } el.addEventListener("click", function () { playAllToken++; play(s); }); tiles[s.id] = { el: el, canvas: el.querySelector("canvas"), s: s }; grid.appendChild(el); }); } function shown() { return meta.sounds.filter(function (s) { return (cat === "all" || s.cat === cat) && (!query || (s.name + " " + s.desc + " " + s.cat).toLowerCase().indexOf(query) >= 0); }); } function filter() { var vis = {}; shown().forEach(function (s) { vis[s.id] = 1; }); Object.keys(tiles).forEach(function (id) { tiles[id].el.hidden = !vis[id]; }); var n = Object.keys(vis).length; $("#status").textContent = n ? n + " sound" + (n === 1 ? "" : "s") + " · tap to play" : "No sounds match “" + query + "”."; Object.keys(vis).forEach(function (id) { drawWave(tiles[id].s); }); } function playAll() { var token = ++playAllToken, list = shown(), i = 0; function next() { if (token !== playAllToken || i >= list.length) return; var s = list[i++]; tiles[s.id].el.scrollIntoView({ block: "nearest", behavior: "smooth" }); play(s).then(function (d) { setTimeout(next, (d + 0.25) * 1000); }); } next(); } function preload() { // decode sprites (works while the AudioContext is still suspended) to draw waveforms var jobs = meta.categories.map(function (c) { return c.id; }); meta.sounds.forEach(function (s) { if (s.free) jobs.push(s); }); var i = 0; function step() { if (i >= jobs.length) { window.__blipwrenReady = true; return; } var j = jobs[i++], s = typeof j === "string" ? meta.sounds.filter(function (x) { return x.sprite === j && !x.free; })[0] : j; if (!s) return step(); Promise.resolve(load(s)).then(function () { meta.sounds.forEach(function (x) { if ((x.free ? "free:" + x.id : x.sprite) === (s.free ? "free:" + s.id : s.sprite)) drawWave(x); }); }).catch(function () {}).then(step); } step(); } function tabs() { Array.prototype.forEach.call(document.querySelectorAll("#tabs .chip"), function (b) { b.addEventListener("click", function () { Array.prototype.forEach.call(document.querySelectorAll("#tabs .chip"), function (x) { x.classList.toggle("on", x === b); x.setAttribute("aria-selected", x === b ? "true" : "false"); }); Array.prototype.forEach.call(document.querySelectorAll("pre[data-pane]"), function (p) { p.hidden = p.dataset.pane !== b.dataset.tab; }); }); }); } function links() { var buy = $("#buy"); if (/^https:\/\//.test(CHECKOUT)) buy.href = CHECKOUT; else { buy.textContent = "Coming soon"; buy.removeAttribute("href"); buy.classList.add("ghost"); } var bundle = $("#bundle"); if (/^https:\/\//.test(BUNDLE)) bundle.href = BUNDLE; else bundle.parentNode.hidden = true; } $("#vol").addEventListener("input", function () { if (gain) gain.gain.value = this.value / 100; }); $("#q").addEventListener("input", function () { query = this.value.trim().toLowerCase(); filter(); }); $("#playall").addEventListener("click", playAll); document.addEventListener("keydown", function (e) { if (e.key === "Escape") { playAllToken++; if (current) try { current.src.stop(); } catch (x) {} } }); tabs(); links(); if (/[?&]thanks=1/.test(location.search)) setTimeout(function () { toast("Thanks for your order! Your download link is in the Polar receipt email."); }, 800); fetch("sounds.json").then(function (r) { return r.json(); }).then(function (m) { meta = m; buildChips(); buildGrid(); filter(); preload(); var hash = decodeURIComponent((location.hash.match(/^#s=([\w]+)/) || [])[1] || ""); if (hash && tiles[hash]) tiles[hash].el.scrollIntoView({ block: "center" }); }).catch(function () { $("#status").textContent = "Couldn't load the sound list. Please reload the page."; }); window.addEventListener("resize", function () { if (meta) filter(); }); })();