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;
|
||||
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 {}
|
||||
|
||||
Reference in New Issue
Block a user