feat(theseus/screenshot): 0.6.4 — Polaroid sounds, trash icon, centred cluster, filename footer + open-in-folder
Rolling every user report from the 0.6.3 rollout into one bundle: Sounds — the Web-Audio synth palette matches the metaphor now: - Screenshot: Polaroid shutter — sharp metallic tick + curtain-close click chained to a film-advance whir (band-passed noise sweeping 900→400 Hz). - Copy: printer "chika-chika-chika" — three descending percussive noise bursts pinned by short sine ticks. Reads as a print-head sweep. - Discard: paper crumple — three overlapping band-limited noise beds with per-sample random-amplitude crackle, descending centre freq. No more descending sine "boop". - Save: soft "photo dispensing" hiss (Polaroid ejects) + a small click. - Both the panel and editor share the design so nothing sounds different depending on which surface fired it. UI polish: - Discard button now carries a trash-can icon so it's obviously not the same as the close-sidebar X (they both used to be plain X's). - Toolbar drawing tools centre themselves via a new .tool-cluster wrapper (flex:1 1 auto, justify-content:center); the Copy/Save actions stay right-anchored via margin-left:auto on their own tgroup. Fixes the maximized-sidebar case where the drawing groups all crowded the left with a big empty gap before Copy/Save on the right. - Filename moves out of the topbar into a dedicated footer strip under the canvas board, alongside a new "Open in folder" button. The topbar is now flex-wrap:nowrap and holds only fixed-width window controls, so a long filename can never push discard / sound / max / close onto a second row (the filename ellipsises instead). - "Open in folder" invokes a new "openFolder" addon message that calls Electron's shell.showItemInFolder() to open the OS file explorer with the specific scratch PNG highlighted (falls back to shell.openPath() on the scratch dir when no capture is named). Version bump so the OTA update endpoint picks it up on the next tick.
This commit is contained in:
parent
8eda0433d7
commit
7b8539fb8d
6 changed files with 204 additions and 49 deletions
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "screenshot",
|
||||
"name": "Screenshot",
|
||||
"version": "0.6.3",
|
||||
"version": "0.6.4",
|
||||
"description": "Capture the current tab — visible viewport, full page, or a rectangle you draw. Preview + annotate editor (crop, arrow, rect, ellipse, pen, text, blur/mosaic redaction, undo, copy, save) live inside the sidebar. Expand the sidebar to full window for a canvas-sized editor.",
|
||||
"author": "Silent Mode",
|
||||
"icon": "📸",
|
||||
|
|
|
|||
|
|
@ -23,13 +23,24 @@ body { background: var(--bg); color: var(--ink);
|
|||
.topbar, .toolbar { display: flex; align-items: center; gap: 4px; padding: 6px 8px;
|
||||
border-bottom: 1px solid var(--line); background: var(--panel);
|
||||
user-select: none; flex-wrap: wrap; }
|
||||
/* Toolbar layout: drawing groups pack left/centre, the Copy/Save actions
|
||||
pin to the right. When the sidebar is too narrow to hold everything in
|
||||
one row, each group wraps as a unit (never a single dot orphaned) and
|
||||
the actions cluster wraps as its own row on the right. */
|
||||
.toolbar { justify-content: flex-start; gap: 10px; row-gap: 6px; }
|
||||
/* The topbar has fixed-size window controls only; no reason to ever wrap
|
||||
its row. Filename lives in the footer now, so nothing can grow the row
|
||||
past what the icons need. */
|
||||
.topbar { flex-wrap: nowrap; }
|
||||
/* Toolbar layout: drawing tools live inside `.tool-cluster` which is a
|
||||
flex-grow container centring its own contents. Copy/Save (`.tgroup
|
||||
.actions`) sit outside it and pin to the right edge. On a maximized
|
||||
sidebar the cluster centres in the empty space between name-area and
|
||||
the actions; at narrow widths the whole cluster shrinks and wraps by
|
||||
category, and the actions can wrap to their own row on the right. */
|
||||
.toolbar { align-items: flex-start; gap: 8px; row-gap: 6px; }
|
||||
.tool-cluster {
|
||||
display: flex; flex-wrap: wrap; align-items: center;
|
||||
gap: 10px; row-gap: 6px;
|
||||
flex: 1 1 auto; justify-content: center;
|
||||
}
|
||||
.tgroup { display: inline-flex; align-items: center; gap: 4px; flex: 0 0 auto; }
|
||||
.tgroup.actions { margin-left: auto; }
|
||||
.tgroup.actions { flex: 0 0 auto; align-self: center; }
|
||||
|
||||
/* Close-sidebar button — reads as danger without shouting. */
|
||||
#close-sidebar:hover,
|
||||
|
|
@ -38,6 +49,21 @@ body { background: var(--bg); color: var(--ink);
|
|||
.btn.danger:hover { border-color: rgba(255,91,91,.55); }
|
||||
.topbar .name { color: var(--dim); font-size: 11.5px; overflow: hidden;
|
||||
text-overflow: ellipsis; white-space: nowrap; max-width: 200px; margin: 0 4px; }
|
||||
|
||||
/* Footer strip that carries the filename + "Open in folder" affordance,
|
||||
sitting under the canvas board. The filename truncates first so the
|
||||
button never falls off the edge; the button never wraps. */
|
||||
.footer { display: flex; align-items: center; gap: 8px;
|
||||
padding: 6px 10px; border-top: 1px solid var(--line);
|
||||
background: var(--panel); font-size: 11.5px; }
|
||||
.footer .fname { color: var(--dim); flex: 1 1 auto; min-width: 0;
|
||||
overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.footer .fbtn { border: 1px solid var(--line); background: var(--panel2);
|
||||
color: var(--ink); border-radius: 6px; cursor: pointer;
|
||||
padding: 4px 8px; display: inline-flex; gap: 6px; align-items: center;
|
||||
flex: 0 0 auto; font: inherit; line-height: 1; }
|
||||
.footer .fbtn svg { width: 14px; height: 14px; display: block; }
|
||||
.footer .fbtn:hover { border-color: rgb(from var(--acid) r g b / .55); }
|
||||
.topbar .spacer, .toolbar .spacer { flex: 1; }
|
||||
|
||||
.btn, .tool {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@
|
|||
<button class="btn" id="back" title="Back to sidebar panel">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M13 8H3M6 5L3 8l3 3"/></svg>
|
||||
</button>
|
||||
<span class="name" id="name">screenshot</span>
|
||||
|
||||
<!-- Crop mode splits the topbar into a confirm/cancel prompt while the
|
||||
user is dragging a crop rectangle. Copy / Save / Discard live in
|
||||
|
|
@ -27,7 +26,12 @@
|
|||
|
||||
<span class="spacer"></span>
|
||||
<button class="btn danger" id="discard" title="Discard this capture and return to the panel">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"><path d="M4 4l8 8M12 4l-8 8"/></svg>
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M3 4h10"/>
|
||||
<path d="M6 4V2h4v2"/>
|
||||
<path d="M4 4l1 10h6l1-10"/>
|
||||
<path d="M7 6.5v5M9 6.5v5"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="btn" id="toggle-sound" title="Toggle sound"></button>
|
||||
<button class="btn" id="toggle-max" title="Expand the sidebar to full window / restore">
|
||||
|
|
@ -41,8 +45,13 @@
|
|||
<!-- Toolbar rows are grouped so that when the sidebar is too narrow to
|
||||
hold everything, whole categories wrap together (tools stay with
|
||||
tools, swatches with swatches, widths with widths, history with
|
||||
history) instead of one dot spilling to a lonely second row. -->
|
||||
history) instead of one dot spilling to a lonely second row.
|
||||
`.tool-cluster` centres itself so on a maximized sidebar the drawing
|
||||
groups sit in the middle of the row and the Copy/Save actions
|
||||
right-anchor cleanly against the edge, instead of the tools crowding
|
||||
the left when there's a lot of empty space. -->
|
||||
<div class="toolbar">
|
||||
<div class="tool-cluster">
|
||||
<div class="tgroup" role="group" aria-label="Tools">
|
||||
<button class="tool" data-tool="crop" title="Crop (C)">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M4 1v11h11M1 4h11v11"/></svg>
|
||||
|
|
@ -90,6 +99,7 @@
|
|||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"><path d="M13 8c0-3-2-5-5-5S3 5 3 8s2 5 5 5"/><path d="M10 5l3 3-3 3"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions cluster — Copy / Save — anchored to the right of the toolbar.
|
||||
Uses margin-left:auto so on wide sidebars it lives out at the end
|
||||
|
|
@ -118,6 +128,18 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer strip: the current capture's filename lives here now (moved out
|
||||
of the topbar so its length can never push the window-control icons
|
||||
to a second row), with an "Open in folder" affordance for jumping to
|
||||
the scratch dir in the OS file explorer. -->
|
||||
<div class="footer">
|
||||
<span class="fname" id="name">screenshot</span>
|
||||
<button class="fbtn" id="open-folder" title="Open the scratch folder in the file explorer">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M2 5h4l1.5 2H14v6H2z"/><path d="M2 5V3h5"/></svg>
|
||||
<span>Open in folder</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="toast" id="toast"></div>
|
||||
<a id="download-link" style="display:none"></a>
|
||||
|
||||
|
|
|
|||
|
|
@ -44,17 +44,21 @@ function _tone(freq, dur, type, vol, at) {
|
|||
osc.start(t0);
|
||||
osc.stop(t0 + dur + 0.02);
|
||||
}
|
||||
// A short filtered noise burst — the mechanical component of the shutter
|
||||
// click and (paired lower down) the "paper feed" texture of the print
|
||||
// sound. `bp` is the band-pass centre frequency, `q` how tight the band.
|
||||
function _noise(dur, vol, bp, q, at) {
|
||||
// Filtered noise burst. `bp` = band-pass centre freq, `q` = sharpness of
|
||||
// the band. Optional `pan` gives a random-amplitude micro-modulation
|
||||
// texture — used by the paper-crumple pass.
|
||||
function _noise(dur, vol, bp, q, at, texture) {
|
||||
if (!soundOn) return;
|
||||
const c = _actx(); if (!c) return;
|
||||
const t0 = c.currentTime + (at || 0);
|
||||
const n = Math.max(1, Math.floor(c.sampleRate * dur));
|
||||
const buf = c.createBuffer(1, n, c.sampleRate);
|
||||
const d = buf.getChannelData(0);
|
||||
for (let i = 0; i < n; i++) d[i] = Math.random() * 2 - 1;
|
||||
for (let i = 0; i < n; i++) {
|
||||
let s = Math.random() * 2 - 1;
|
||||
if (texture) s *= 0.4 + Math.random() * 0.6; // crackle
|
||||
d[i] = s;
|
||||
}
|
||||
const src = c.createBufferSource(); src.buffer = buf;
|
||||
const flt = c.createBiquadFilter(); flt.type = "bandpass";
|
||||
flt.frequency.setValueAtTime(bp || 2000, t0);
|
||||
|
|
@ -66,26 +70,64 @@ function _noise(dur, vol, bp, q, at) {
|
|||
src.start(t0);
|
||||
src.stop(t0 + dur + 0.02);
|
||||
}
|
||||
// Photoshoot: two clicks — mirror-slap up, mirror-slap down. A short
|
||||
// high-band noise burst is the metallic ping, a low-band burst is the
|
||||
// mechanical thud, and a very short low tone fills in the body of the
|
||||
// shutter closing.
|
||||
// Slowly-sweeping band-pass noise — used as the film-advance "whir" tail
|
||||
// under the shutter click.
|
||||
function _sweep(dur, vol, fromHz, toHz, q, at) {
|
||||
if (!soundOn) return;
|
||||
const c = _actx(); if (!c) return;
|
||||
const t0 = c.currentTime + (at || 0);
|
||||
const n = Math.max(1, Math.floor(c.sampleRate * dur));
|
||||
const buf = c.createBuffer(1, n, c.sampleRate);
|
||||
const d = buf.getChannelData(0);
|
||||
for (let i = 0; i < n; i++) d[i] = Math.random() * 2 - 1;
|
||||
const src = c.createBufferSource(); src.buffer = buf;
|
||||
const flt = c.createBiquadFilter(); flt.type = "bandpass";
|
||||
flt.frequency.setValueAtTime(fromHz, t0);
|
||||
flt.frequency.exponentialRampToValueAtTime(toHz, t0 + dur);
|
||||
flt.Q.setValueAtTime(q || 8, t0);
|
||||
const g = c.createGain();
|
||||
g.gain.setValueAtTime(0.0001, t0);
|
||||
g.gain.exponentialRampToValueAtTime(vol, t0 + dur * 0.2);
|
||||
g.gain.exponentialRampToValueAtTime(0.0001, t0 + dur);
|
||||
src.connect(flt); flt.connect(g); g.connect(c.destination);
|
||||
src.start(t0);
|
||||
src.stop(t0 + dur + 0.02);
|
||||
}
|
||||
// Polaroid: sharp mechanical click (shutter mirror + curtain) followed by
|
||||
// a short film-advance whir. Reads unmistakably as "camera taking a
|
||||
// picture" rather than a UI beep.
|
||||
function playShutter() {
|
||||
_noise(0.03, 0.28, 4200, 6, 0); // first click, up
|
||||
_tone(180, 0.04, "square", 0.10, 0.005);
|
||||
_noise(0.04, 0.32, 3600, 5, 0.08); // second click, down
|
||||
_tone(140, 0.05, "square", 0.12, 0.09);
|
||||
_noise(0.02, 0.32, 5200, 8, 0); // sharp metallic tick
|
||||
_tone(160, 0.03, "square", 0.14, 0.003); // mirror thud
|
||||
_noise(0.03, 0.24, 3200, 5, 0.03); // curtain close
|
||||
_tone(120, 0.04, "square", 0.10, 0.035);
|
||||
_sweep(0.28, 0.10, 900, 400, 12, 0.07); // film-advance whir tail
|
||||
}
|
||||
// Copy: two-chirp "printer feed" — matches Firefox's Easy Screenshot copy
|
||||
// feedback (staccato ascending pair, no residual hum).
|
||||
// Printer chika-chika-chika — three descending percussive bursts modelled
|
||||
// on a print head sweeping across a page. Fast and unmistakably "action
|
||||
// happened", no residual hum.
|
||||
function playCopy() {
|
||||
_noise(0.02, 0.14, 3200, 8, 0);
|
||||
_tone(1400, 0.05, "sine", 0.14, 0);
|
||||
_noise(0.02, 0.14, 4200, 8, 0.06);
|
||||
_tone(2000, 0.06, "sine", 0.14, 0.06);
|
||||
_noise(0.028, 0.22, 3800, 9, 0.00);
|
||||
_tone(1200, 0.03, "sine", 0.10, 0.00);
|
||||
_noise(0.028, 0.22, 3200, 9, 0.06);
|
||||
_tone(1000, 0.03, "sine", 0.10, 0.06);
|
||||
_noise(0.028, 0.22, 2600, 9, 0.12);
|
||||
_tone(800, 0.03, "sine", 0.10, 0.12);
|
||||
}
|
||||
// Photo dispensing — a soft pneumatic hiss with a small final click, the
|
||||
// way a Polaroid ejects its print.
|
||||
function playSave() {
|
||||
_sweep(0.18, 0.14, 3800, 1600, 3, 0);
|
||||
_noise(0.02, 0.20, 2400, 8, 0.18);
|
||||
_tone(900, 0.04, "sine", 0.12, 0.20);
|
||||
}
|
||||
// Paper crumple — a long band-limited noise with texture crackle and a
|
||||
// descending centre frequency, reading as "a page being scrunched".
|
||||
function playDiscard() {
|
||||
_noise(0.16, 0.28, 2600, 3, 0.00, true); // main body
|
||||
_noise(0.12, 0.22, 1800, 3, 0.06, true); // trailing scrunch
|
||||
_noise(0.08, 0.16, 1200, 3, 0.12, true);
|
||||
}
|
||||
function playSave() { _tone(700, 0.06, "sine", 0.16, 0); _tone(1100, 0.08, "sine", 0.16, 0.05); _tone(1500, 0.09, "sine", 0.16, 0.10); }
|
||||
function playDiscard() { _tone(500, 0.06, "sine", 0.18, 0); _tone(280, 0.10, "sine", 0.14, 0.05); }
|
||||
function playCropApply() { _tone(1200, 0.05, "sine", 0.14, 0); _tone(1600, 0.07, "sine", 0.14, 0.04); }
|
||||
|
||||
const base = $("base");
|
||||
|
|
@ -618,6 +660,21 @@ if (cancelCropBtn) cancelCropBtn.addEventListener("click", cancelCrop);
|
|||
// -- top-bar navigation ----------------------------------------------
|
||||
$("back").addEventListener("click", () => { location.href = "panel.html"; });
|
||||
|
||||
// "Open in folder" — sends the current capture's name to the addon's
|
||||
// openFolder handler, which calls shell.showItemInFolder() so the file
|
||||
// explorer opens with the exact scratch PNG highlighted.
|
||||
const openFolderBtn = document.getElementById("open-folder");
|
||||
if (openFolderBtn) openFolderBtn.addEventListener("click", async () => {
|
||||
try {
|
||||
if (window.silentmode?.invoke) {
|
||||
await window.silentmode.invoke("openFolder", { name: NAME || "" });
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("open-folder failed:", e);
|
||||
toast("Couldn't open folder: " + (e && e.message || e), true);
|
||||
}
|
||||
});
|
||||
|
||||
// Discard — throw away this capture entirely (delete it from the Recent
|
||||
// ring) and return to the panel. Back is non-destructive; Discard is not.
|
||||
const discardBtn = document.getElementById("discard");
|
||||
|
|
|
|||
|
|
@ -126,6 +126,22 @@ module.exports = {
|
|||
return { name: hit.name, dataUrl: readAsDataUrl(hit.path), bytes: hit.bytes, at: hit.at, mode: hit.mode };
|
||||
});
|
||||
|
||||
// Show the scratch dir (or a specific capture inside it) in the OS
|
||||
// file explorer. `api.require("electron").shell.openPath` opens the
|
||||
// folder; `showItemInFolder` opens the folder AND highlights the
|
||||
// file — used when the panel/editor knows a specific capture.
|
||||
api.onMessage("openFolder", (payload) => {
|
||||
const { shell } = api.require("electron");
|
||||
const wantedName = payload && payload.name ? String(payload.name) : "";
|
||||
if (wantedName) {
|
||||
const recent = api.storage.get("recent", []) || [];
|
||||
const hit = recent.find((r) => r.id === wantedName);
|
||||
if (hit) { shell.showItemInFolder(hit.path); return { ok: true, path: hit.path }; }
|
||||
}
|
||||
shell.openPath(scratchDir);
|
||||
return { ok: true, path: scratchDir };
|
||||
});
|
||||
|
||||
api.onMessage("clearRecent", (payload) => {
|
||||
const wantedName = payload && payload.name ? String(payload.name) : "";
|
||||
let recent = api.storage.get("recent", []) || [];
|
||||
|
|
|
|||
|
|
@ -236,17 +236,21 @@
|
|||
osc.start(t0);
|
||||
osc.stop(t0 + dur + 0.02);
|
||||
}
|
||||
// Filtered noise burst — the metallic "click" of the shutter and the
|
||||
// paper-feed texture of the print sound. Band-pass around `bp` with
|
||||
// sharpness `q` picks a resonance so we don't just play white noise.
|
||||
function noise(dur, vol, bp, q, at) {
|
||||
// Filtered noise burst — camera clicks, printer thwacks, and (with
|
||||
// texture=true) the crackle of crumpled paper. `bp` = band-pass centre
|
||||
// freq, `q` = sharpness of the band.
|
||||
function noise(dur, vol, bp, q, at, texture) {
|
||||
if (!soundOn) return;
|
||||
const c = actx(); if (!c) return;
|
||||
const t0 = c.currentTime + (at || 0);
|
||||
const n = Math.max(1, Math.floor(c.sampleRate * dur));
|
||||
const buf = c.createBuffer(1, n, c.sampleRate);
|
||||
const d = buf.getChannelData(0);
|
||||
for (let i = 0; i < n; i++) d[i] = Math.random() * 2 - 1;
|
||||
for (let i = 0; i < n; i++) {
|
||||
let s = Math.random() * 2 - 1;
|
||||
if (texture) s *= 0.4 + Math.random() * 0.6;
|
||||
d[i] = s;
|
||||
}
|
||||
const src = c.createBufferSource(); src.buffer = buf;
|
||||
const flt = c.createBiquadFilter(); flt.type = "bandpass";
|
||||
flt.frequency.setValueAtTime(bp || 2000, t0);
|
||||
|
|
@ -258,23 +262,53 @@
|
|||
src.start(t0);
|
||||
src.stop(t0 + dur + 0.02);
|
||||
}
|
||||
// Photoshoot shutter — mirror-slap up (metallic ping + soft thud), then
|
||||
// mirror-slap down. Reads as a real camera click rather than a beep.
|
||||
// Sweeping band-pass noise — used as the film-advance whir tail of the
|
||||
// Polaroid shutter.
|
||||
function sweep(dur, vol, fromHz, toHz, q, at) {
|
||||
if (!soundOn) return;
|
||||
const c = actx(); if (!c) return;
|
||||
const t0 = c.currentTime + (at || 0);
|
||||
const n = Math.max(1, Math.floor(c.sampleRate * dur));
|
||||
const buf = c.createBuffer(1, n, c.sampleRate);
|
||||
const d = buf.getChannelData(0);
|
||||
for (let i = 0; i < n; i++) d[i] = Math.random() * 2 - 1;
|
||||
const src = c.createBufferSource(); src.buffer = buf;
|
||||
const flt = c.createBiquadFilter(); flt.type = "bandpass";
|
||||
flt.frequency.setValueAtTime(fromHz, t0);
|
||||
flt.frequency.exponentialRampToValueAtTime(toHz, t0 + dur);
|
||||
flt.Q.setValueAtTime(q || 8, t0);
|
||||
const g = c.createGain();
|
||||
g.gain.setValueAtTime(0.0001, t0);
|
||||
g.gain.exponentialRampToValueAtTime(vol, t0 + dur * 0.2);
|
||||
g.gain.exponentialRampToValueAtTime(0.0001, t0 + dur);
|
||||
src.connect(flt); flt.connect(g); g.connect(c.destination);
|
||||
src.start(t0);
|
||||
src.stop(t0 + dur + 0.02);
|
||||
}
|
||||
// Polaroid: mirror + curtain click, then a film-advance whir.
|
||||
function playShutter() {
|
||||
noise(0.03, 0.28, 4200, 6, 0);
|
||||
tone(180, 0.04, "square", 0.10, 0.005);
|
||||
noise(0.04, 0.32, 3600, 5, 0.08);
|
||||
tone(140, 0.05, "square", 0.12, 0.09);
|
||||
noise(0.02, 0.32, 5200, 8, 0);
|
||||
tone(160, 0.03, "square", 0.14, 0.003);
|
||||
noise(0.03, 0.24, 3200, 5, 0.03);
|
||||
tone(120, 0.04, "square", 0.10, 0.035);
|
||||
sweep(0.28, 0.10, 900, 400, 12, 0.07);
|
||||
}
|
||||
// Copy — two-chirp "printer feed", modeled on Firefox Easy Screenshot's
|
||||
// copy feedback: fast staccato ascending pair, no residual hum.
|
||||
// Printer chika-chika-chika — 3 descending percussive bursts.
|
||||
function playCopy() {
|
||||
noise(0.02, 0.14, 3200, 8, 0);
|
||||
tone(1400, 0.05, "sine", 0.14, 0);
|
||||
noise(0.02, 0.14, 4200, 8, 0.06);
|
||||
tone(2000, 0.06, "sine", 0.14, 0.06);
|
||||
noise(0.028, 0.22, 3800, 9, 0.00);
|
||||
tone(1200, 0.03, "sine", 0.10, 0.00);
|
||||
noise(0.028, 0.22, 3200, 9, 0.06);
|
||||
tone(1000, 0.03, "sine", 0.10, 0.06);
|
||||
noise(0.028, 0.22, 2600, 9, 0.12);
|
||||
tone(800, 0.03, "sine", 0.10, 0.12);
|
||||
}
|
||||
// Paper crumple — long band-limited noise with crackle texture and a
|
||||
// descending centre frequency.
|
||||
function playDiscard() {
|
||||
noise(0.16, 0.28, 2600, 3, 0.00, true);
|
||||
noise(0.12, 0.22, 1800, 3, 0.06, true);
|
||||
noise(0.08, 0.16, 1200, 3, 0.12, true);
|
||||
}
|
||||
function playDiscard() { tone(500, 0.06, "sine", 0.18, 0); tone(280, 0.10, "sine", 0.14, 0.05); }
|
||||
function paintSound() {
|
||||
btnSound.title = soundOn ? "Sounds on — click to mute" : "Sounds off — click to enable";
|
||||
btnSound.innerHTML = soundOn
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue