feat(classroom): мелодия-вызов длиннее — полный вестминстерский бой (5 фраз)

Расширил перезвон с одной нисходящей фразы до полного боя из 5 фраз по 4 ноты
(G4/C5/D5/E5) с паузами между фразами и протяжной финальной нотой (~7-8 с).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-06-01 09:03:52 +03:00
parent 7d8e2220ff
commit 63ceeaabc2
+31 -18
View File
@@ -1724,24 +1724,37 @@ async function adminGamGetUser(id) { return req('GET', `/gamifi
master.gain.value = 0.16;
const comp = ctx.createDynamicsCompressor(); // мягкий лимитер от перегруза
master.connect(comp); comp.connect(ctx.destination);
// Нисходящий перезвон (Вестминстер-lite): E5 · D5 · C5 · G4 — «бим-бам-бом-бо-о-ом»
const notes = [659.25, 587.33, 523.25, 392.00];
const step = 0.30;
notes.forEach((freq, i) => {
const start = t0 + i * step;
const dur = (i === notes.length - 1) ? 1.7 : 0.95; // последняя нота длиннее
// Колоколообразный тембр: основной тон + обертоны, экспон. затухание
[[1, 0.5], [2, 0.16], [3, 0.06]].forEach(([mult, amp]) => {
const osc = ctx.createOscillator();
const g = ctx.createGain();
osc.type = 'sine';
osc.frequency.value = freq * mult;
g.gain.setValueAtTime(0.0001, start);
g.gain.exponentialRampToValueAtTime(amp, start + 0.012);
g.gain.exponentialRampToValueAtTime(0.0001, start + dur);
osc.connect(g); g.connect(master);
osc.start(start);
osc.stop(start + dur + 0.05);
// Полный вестминстерский бой (школьно-часовой перезвон) — 5 фраз по 4 ноты
// на нотах G4 · C5 · D5 · E5. Узнаваемый «вызов на урок».
const G4 = 392.00, C5 = 523.25, D5 = 587.33, E5 = 659.25;
const PHRASES = [
[E5, D5, C5, G4],
[C5, E5, D5, G4],
[C5, D5, E5, C5],
[E5, C5, D5, G4],
[G4, D5, E5, C5],
];
const step = 0.32, gap = 0.22; // шаг между нотами и пауза между фразами
let t = t0;
PHRASES.forEach((ph, pi) => {
ph.forEach((freq, ni) => {
const lastOfPhrase = ni === ph.length - 1;
const lastOverall = pi === PHRASES.length - 1 && lastOfPhrase;
const dur = lastOverall ? 2.2 : (lastOfPhrase ? 1.2 : 0.9); // концы фраз длиннее
// Колоколообразный тембр: основной тон + обертоны, экспон. затухание
[[1, 0.5], [2, 0.16], [3, 0.06]].forEach(([mult, amp]) => {
const osc = ctx.createOscillator();
const g = ctx.createGain();
osc.type = 'sine';
osc.frequency.value = freq * mult;
g.gain.setValueAtTime(0.0001, t);
g.gain.exponentialRampToValueAtTime(amp, t + 0.012);
g.gain.exponentialRampToValueAtTime(0.0001, t + dur);
osc.connect(g); g.connect(master);
osc.start(t);
osc.stop(t + dur + 0.05);
});
t += step + (lastOfPhrase ? gap : 0);
});
});
} catch {}