teaser v9 final: music reworked for the tight cut (drives from frame 0, an accent on every cut, tension in the pile and the rejection, a short drop into the request, layers building through the montage, the stop on Submit, the resolve on Waiting for review, a confident end under the card fading over the last 1 s; -16 LUFS, true peak <= -1.5 dBTP); 1920x1080 master, web version and posters from the end card; site teaser and poster (cache key v=5), README poster
This commit is contained in:
parent
c608b9f63e
commit
0c4689a81b
7 changed files with 57 additions and 35 deletions
|
|
@ -1,13 +1,15 @@
|
|||
// Original teaser music and sound design (v8, a story arc), synthesized from nothing with the Web Audio API
|
||||
// Original teaser music and sound design (v9, the tight cut), synthesized from nothing with the Web Audio API
|
||||
// (OfflineAudioContext): no samples, no downloaded audio. 120 BPM (a beat every half second; every cut lands on one).
|
||||
// calm game a warm D major pad, a slow soft pluck
|
||||
// tension pile B minor, a low pulse in eighths that grows, a ticking clock, a riser; a thud per card
|
||||
// dark reject a low boom, a dark drone that sinks to almost nothing
|
||||
// silence ask room tone, the key clicks of the request, one soft note when it is sent; a swell into the drop
|
||||
// calm game drives from frame 0: kick on every beat, D major bass eighths, a bright arpeggio (no slow intro)
|
||||
// tension pile B minor, a low pulse in eighths that grows, kicks, a ticking clock, a riser; a thud per card
|
||||
// dark reject a low boom, a dark drone, the pulse keeps ticking underneath
|
||||
// silence ask a short drop: an impact on the cut, then a filtered half-time beat, the key clicks, a swell
|
||||
// (every cut) an accent: a short noise crash and a low hit
|
||||
// drive montage+scan kick, bass, arpeggio, hats, claps: one layer more per montage beat (I-vi-IV-V, a chord per bar);
|
||||
// a chime per fix, rising
|
||||
// stop button the music stops on the cut; only a quiet low pulse, like holding your breath
|
||||
// resolve waiting+end the click, then a wide D major add9 swell, bells, a slow arpeggio under the end card
|
||||
// resolve waiting+end the click, then a wide D major add9 swell, bells; under the end card a confident kick and
|
||||
// bass on D, the final chord; render.ts fades the last 1 s
|
||||
// Deterministic. renderWav() returns a 32-bit float stereo WAV (48 kHz) as base64; render.ts masters it with ffmpeg.
|
||||
import { CLICK_T, CUTS, DURATION, keyTimes, MONTAGE, scene, ticks } from './script.js';
|
||||
|
||||
|
|
@ -133,10 +135,10 @@ export async function renderMusic() {
|
|||
const v = ctx.createGain(); env(v, t, 0.001, g, open ? 0.14 : 0.035); n.connect(hp); hp.connect(v); v.connect(p); p.connect(drive);
|
||||
}
|
||||
/** The bass: a filtered saw + sine, one note. */
|
||||
function bass(t, m, len, g = 0.11, cutoff = 520) {
|
||||
function bass(t, m, len, g = 0.11, cutoff = 520, to = drive) {
|
||||
const lp = ctx.createBiquadFilter(); lp.type = 'lowpass'; lp.frequency.setValueAtTime(cutoff * 2.2, t); lp.frequency.exponentialRampToValueAtTime(cutoff, t + len * 0.8); lp.Q.value = 2;
|
||||
const v = ctx.createGain(); v.gain.setValueAtTime(0, t); v.gain.linearRampToValueAtTime(g, t + 0.008); v.gain.setValueAtTime(g * 0.8, t + len * 0.7); v.gain.linearRampToValueAtTime(0, t + len);
|
||||
lp.connect(v); v.connect(drive);
|
||||
lp.connect(v); v.connect(to);
|
||||
for (const [type, mul] of [['sawtooth', 0.6], ['sine', 1]]) {
|
||||
const o = ctx.createOscillator(); o.type = type; o.frequency.value = midi(m);
|
||||
const og = ctx.createGain(); og.gain.value = mul; o.connect(og); og.connect(lp); o.start(t); o.stop(t + len + 0.05);
|
||||
|
|
@ -160,13 +162,18 @@ export async function renderMusic() {
|
|||
tick(t, 0.09, 4200); tick(t + 0.055, 0.05, 2800);
|
||||
}
|
||||
|
||||
// ---------- calm: the game is done ----------
|
||||
// ---------- calm: the game is done (drives from frame 0) ----------
|
||||
{
|
||||
const [a, b] = S.calm;
|
||||
const [r, iv] = CH.Dmaj9;
|
||||
pad(0, S.calm[1] + 0.2, [r, r + iv[1], r + iv[2], r + iv[3]], 0.032, 900, 1.6, 1.2);
|
||||
pad(0, S.calm[1] + 0.2, [r - 12], 0.02, 300, 1.6, 1.0);
|
||||
const tones = [62, 66, 69, 73, 74, 73, 69, 66];
|
||||
for (let t = 0.5, k = 0; t < S.calm[1] - 0.1; t += 2 * STEP, k++) pluck(t, tones[k % tones.length], 0.036, k % 2 ? 0.25 : -0.25, 1.6);
|
||||
pad(0, b + 0.05, [r, r + iv[1], r + iv[2], r + iv[3]], 0.026, 1400, 0.02, 0.15, master, 2600);
|
||||
for (let t = a, k = 0; t < b - 0.01; t += STEP, k++) {
|
||||
kick(t, k % 4 === 0 ? 0.52 : 0.44, master);
|
||||
hat(t + STEP / 2, 0.024);
|
||||
bass(t, 38, STEP / 2 * 0.85, 0.1); bass(t + STEP / 2, k % 4 === 3 ? 45 : 38, STEP / 2 * 0.85, 0.085);
|
||||
const tones = [74, 78, 81, 85];
|
||||
for (let j = 0; j < 4; j++) pluck(t + j * STEP / 4, tones[(k * 4 + j) % 4], 0.028 * (j === 0 ? 1.2 : 1), j % 2 ? 0.3 : -0.3, 0.3, master);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- tension: the stores pile up ----------
|
||||
|
|
@ -179,10 +186,11 @@ export async function renderMusic() {
|
|||
// the low pulse: eighths on B, louder and brighter towards the rejection
|
||||
for (let t = a, k = 0; t < b - 0.01; t += STEP / 2, k++) {
|
||||
const p = (t - a) / (b - a);
|
||||
bass(t, k % 8 === 7 ? 42 : 35, STEP / 2 * 0.9, 0.05 + 0.07 * p, 260 + 900 * p);
|
||||
bass(t, k % 8 === 7 ? 42 : 35, STEP / 2 * 0.9, 0.07 + 0.06 * p, 300 + 900 * p);
|
||||
if (k % 2 === 0) kick(t, 0.42 + 0.1 * p, master);
|
||||
}
|
||||
// a clock ticking in sixteenths over the second half
|
||||
for (let t = a + 3, k = 0; t < b - 0.01; t += STEP / 4, k++) tick(t, (k % 4 === 0 ? 0.03 : 0.016) * (0.6 + (t - a) / (b - a) * 0.6), k % 4 === 0 ? 5200 : 6800);
|
||||
for (let t = a + 1, k = 0; t < b - 0.01; t += STEP / 4, k++) tick(t, (k % 4 === 0 ? 0.03 : 0.016) * (0.6 + (t - a) / (b - a) * 0.6), k % 4 === 0 ? 5200 : 6800);
|
||||
riser(b, 3, 0.07, 300, 6000);
|
||||
}
|
||||
|
||||
|
|
@ -193,17 +201,23 @@ export async function renderMusic() {
|
|||
const v = pad(a, b - 1.2, [35, 38, 42, 47], 0.04, 420, 0.1, 1.6, master, 260);
|
||||
void v;
|
||||
pad(a, b - 1.4, [59, 60], 0.008, 1800, 1.0, 1.4); // a faint, uneasy cluster above
|
||||
// the clock keeps ticking: nothing stands still
|
||||
for (let t = a + STEP, k = 0; t < b - 0.01; t += STEP / 2, k++) tick(t, k % 2 ? 0.014 : 0.026, k % 2 ? 6800 : 5200);
|
||||
for (let t = a + 2 * STEP; t < b - 0.01; t += STEP) bass(t, 35, STEP * 0.4, 0.06, 240);
|
||||
}
|
||||
|
||||
// ---------- near silence: the request ----------
|
||||
// ---------- the drop: the request ----------
|
||||
{
|
||||
const [a, b] = S.silence;
|
||||
// room tone: very quiet, dark noise
|
||||
const n = noiseSrc(a, b - a); const lp = ctx.createBiquadFilter(); lp.type = 'lowpass'; lp.frequency.value = 500;
|
||||
const v = ctx.createGain(); v.gain.setValueAtTime(0, a); v.gain.linearRampToValueAtTime(0.012, a + 0.8); v.gain.setValueAtTime(0.012, b - 0.6); v.gain.linearRampToValueAtTime(0, b);
|
||||
n.connect(lp); lp.connect(v); v.connect(master);
|
||||
pluck(a + 0.5, 66, 0.022, 0, 2.5);
|
||||
// the swell into the drop
|
||||
// a short drop on the cut: one impact, then a filtered half-time beat under the key clicks
|
||||
kick(a, 0.6, master); boom(a, 0.25);
|
||||
const lp = ctx.createBiquadFilter(); lp.type = 'lowpass'; lp.frequency.setValueAtTime(500, a); lp.frequency.exponentialRampToValueAtTime(5000, b); lp.connect(master);
|
||||
for (let t = a + STEP, k = 1; t < b - 0.01; t += STEP, k++) {
|
||||
if (k % 2 === 0) kick(t, 0.4, master);
|
||||
hat(t + STEP / 2, 0.012 + 0.012 * (t - a) / (b - a));
|
||||
for (let j = 0; j < 2; j++) pluck(t + j * STEP / 2, [62, 66, 69, 74][(k * 2 + j) % 4] + 12, 0.03, j ? 0.25 : -0.25, 0.4, lp);
|
||||
}
|
||||
// the swell into the montage
|
||||
riser(b, 1.5, 0.08, 250, 7000);
|
||||
pad(b - 1.5, b, [62, 69, 74], 0.02, 600, 1.4, 0.05, master, 3000);
|
||||
}
|
||||
|
|
@ -266,13 +280,21 @@ export async function renderMusic() {
|
|||
const e = scene('end').start;
|
||||
bell(e + 0.3, r + 24, 0.05);
|
||||
bell(e + 0.55, r + 31, 0.035, 0.2);
|
||||
// a confident ending: kick and bass on D under the card, the full chord on the last bar
|
||||
for (let t = e, k = 0; t < b - 0.01; t += STEP, k++) {
|
||||
kick(t, k % 4 === 0 ? 0.48 : 0.36, master);
|
||||
bass(t, 38, STEP * 0.8, 0.09, 600, master);
|
||||
if (k % 2) tick(t, 0.02, 8000);
|
||||
}
|
||||
pad(e, b + 0.6, [r, r + 7, r + 12, r + 16, r + 19], 0.03, 2400, 0.05, 1.0);
|
||||
}
|
||||
|
||||
// ---------- soft accents on the story cuts ----------
|
||||
// ---------- an accent on every cut ----------
|
||||
for (const c of CUTS) {
|
||||
if (MONTAGE.includes(c.id) || c.id === 'scan') continue;
|
||||
if (c.id === 'reject' || c.id === 'builds') continue; // the boom and the drop are the accents
|
||||
bell(c.t + 0.01, c.id === 'pile' ? 71 : 74, 0.03, 0.1);
|
||||
if (c.id === 'button') { bell(c.t + 0.01, 86, 0.04); continue; } // the stop: the music is cut, one bell
|
||||
const n = noiseSrc(c.t, 0.6); const hp = ctx.createBiquadFilter(); hp.type = 'highpass'; hp.frequency.value = 3500;
|
||||
const v = ctx.createGain(); env(v, c.t, 0.002, 0.07, 0.45); n.connect(hp); hp.connect(v); v.connect(sfx); v.connect(sfxVerb);
|
||||
if (c.id !== 'reject') thud(c.t, 0.14);
|
||||
}
|
||||
|
||||
// ---------- UI sounds ----------
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue