Spaces:
Running
Running
File size: 9,695 Bytes
0ed77a7 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 | /* 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:<id>" -> 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) + '<span class="n">' + n + "</span>";
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 = '<span class="nm"></span><span class="ds"></span><span class="dur"></span><canvas aria-hidden="true"></canvas>';
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(); });
})();
|