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:
@@ -1724,24 +1724,37 @@ async function adminGamGetUser(id) { return req('GET', `/gamifi
|
|||||||
master.gain.value = 0.16;
|
master.gain.value = 0.16;
|
||||||
const comp = ctx.createDynamicsCompressor(); // мягкий лимитер от перегруза
|
const comp = ctx.createDynamicsCompressor(); // мягкий лимитер от перегруза
|
||||||
master.connect(comp); comp.connect(ctx.destination);
|
master.connect(comp); comp.connect(ctx.destination);
|
||||||
// Нисходящий перезвон (Вестминстер-lite): E5 · D5 · C5 · G4 — «бим-бам-бом-бо-о-ом»
|
// Полный вестминстерский бой (школьно-часовой перезвон) — 5 фраз по 4 ноты
|
||||||
const notes = [659.25, 587.33, 523.25, 392.00];
|
// на нотах G4 · C5 · D5 · E5. Узнаваемый «вызов на урок».
|
||||||
const step = 0.30;
|
const G4 = 392.00, C5 = 523.25, D5 = 587.33, E5 = 659.25;
|
||||||
notes.forEach((freq, i) => {
|
const PHRASES = [
|
||||||
const start = t0 + i * step;
|
[E5, D5, C5, G4],
|
||||||
const dur = (i === notes.length - 1) ? 1.7 : 0.95; // последняя нота длиннее
|
[C5, E5, D5, G4],
|
||||||
// Колоколообразный тембр: основной тон + обертоны, экспон. затухание
|
[C5, D5, E5, C5],
|
||||||
[[1, 0.5], [2, 0.16], [3, 0.06]].forEach(([mult, amp]) => {
|
[E5, C5, D5, G4],
|
||||||
const osc = ctx.createOscillator();
|
[G4, D5, E5, C5],
|
||||||
const g = ctx.createGain();
|
];
|
||||||
osc.type = 'sine';
|
const step = 0.32, gap = 0.22; // шаг между нотами и пауза между фразами
|
||||||
osc.frequency.value = freq * mult;
|
let t = t0;
|
||||||
g.gain.setValueAtTime(0.0001, start);
|
PHRASES.forEach((ph, pi) => {
|
||||||
g.gain.exponentialRampToValueAtTime(amp, start + 0.012);
|
ph.forEach((freq, ni) => {
|
||||||
g.gain.exponentialRampToValueAtTime(0.0001, start + dur);
|
const lastOfPhrase = ni === ph.length - 1;
|
||||||
osc.connect(g); g.connect(master);
|
const lastOverall = pi === PHRASES.length - 1 && lastOfPhrase;
|
||||||
osc.start(start);
|
const dur = lastOverall ? 2.2 : (lastOfPhrase ? 1.2 : 0.9); // концы фраз длиннее
|
||||||
osc.stop(start + dur + 0.05);
|
// Колоколообразный тембр: основной тон + обертоны, экспон. затухание
|
||||||
|
[[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 {}
|
} catch {}
|
||||||
|
|||||||
Reference in New Issue
Block a user