PageSourceSearch

https://portal.sancos.md/js/chat-sound.js

js sancos.md collected 2026-09-24 13:21:59 UTC 3,124 bytes, 81 lines download raw bytes

1/*
2 * Chat notification tones, synthesized with the Web Audio API.
3 *
4 * Shared by the operator console and the chat widget so both sides use one definition of what
5 * "chime" means, and so neither needs an audio file deployed, cached or preloaded.
6 *
7 * Usage:  ChatSound.configure({ tone: 'chime', volume: 60 });  ChatSound.play();
8 *
9 * Browsers refuse to start audio before the user has interacted with the page, so the context is
10 * created lazily on the first play and the first attempt may be silently dropped — by then the
11 * user has almost always clicked something.
12 */
13(function (global) {
14    "use strict";
15
16    // note = [frequency Hz, start offset s, duration s]
17    var TONES = {
18        none: [],
19        soft: [[660, 0, 0.09]],
20        chime: [[880, 0, 0.11], [1175, 0.10, 0.16]],
21        ding: [[1320, 0, 0.22]],
22        pop: [[420, 0, 0.06], [700, 0.05, 0.07]],
23        alert: [[784, 0, 0.09], [988, 0.10, 0.09], [1319, 0.20, 0.17]]
24    };
25
26    var ctx = null;
27    var tone = "none";
28    var volume = 0.6;
29
30    function ensureContext() {
31        if (ctx) return ctx;
32        var AC = global.AudioContext || global.webkitAudioContext;
33        if (!AC) return null;
34        try { ctx = new AC(); } catch (e) { return null; }
35        return ctx;
36    }
37
38    function play() {
39        var notes = TONES[tone];
40        if (!notes || !notes.length || volume <= 0) return;
41        var ac = ensureContext();
42        if (!ac) return;
43        // Autoplay policy parks the context until a gesture; resuming is a no-op when it's running.
44        if (ac.state === "suspended" && ac.resume) { try { ac.resume(); } catch (e) {} }
45
46        var now = ac.currentTime;
47        notes.forEach(function (n) {
48            var osc = ac.createOscillator();
49            var gain = ac.createGain();
50            osc.type = "sine";
51            osc.frequency.value = n[0];
52            var start = now + n[1];
53            var end = start + n[2];
54            // Ramp in and out: a square-edged gain change is audible as a click.
55            gain.gain.setValueAtTime(0.0001, start);
56            gain.gain.exponentialRampToValueAtTime(Math.max(0.0001, volume), start + 0.012);
57            gain.gain.exponentialRampToValueAtTime(0.0001, end);
58            osc.connect(gain).connect(ac.destination);
59            osc.start(start);
60            osc.stop(end + 0.02);
61        });
62    }
63
64    global.ChatSound = {
65        configure: function (opts) {
66            opts = opts || {};
67            if (typeof opts.tone === "string" && TONES[opts.tone]) tone = opts.tone;
68            if (typeof opts.volume === "number") volume = Math.max(0, Math.min(100, opts.volume)) / 100;
69        },
70        play: play,
71        // Lets the settings page preview a tone without changing what's configured.
72        preview: function (name, vol) {
73            var prevTone = tone, prevVol = volume;
74            tone = TONES[name] ? name : "none";
75            if (typeof vol === "number") volume = Math.max(0, Math.min(100, vol)) / 100;
76            play();
77            tone = prevTone; volume = prevVol;
78        },
79        tones: Object.keys(TONES)
80    };
81})(window);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.