mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-18 21:16:47 +00:00
fix(dashboard): soften the pivot into 'but you have traveled' + audio review fixes
The transition out of the fear act was too brutal: the silence now arrives as a fast pull-away with the hall tail ringing out instead of a 100ms kill, the pivot line trades its impact boom for a slow warm A1 swell (the first calm breath), and the line itself breathes in over 3.4s. Audio review fixes: the wind's swell LFO moves to a series tremolo stage so the release can actually silence it before the cut (it used to warble through the silence and click on stop); the heartbeat's noise layer becomes the intended high-passed click instead of a dull lowpassed thud; dispose() pulls the master to zero before the deferred context close so leaving the show cannot pop.
This commit is contained in:
@@ -18,7 +18,9 @@ interface Cue {
|
||||
act?: NoFearAct
|
||||
line?: LineKey | null
|
||||
hard?: boolean
|
||||
soft?: boolean
|
||||
impact?: number
|
||||
swell?: boolean
|
||||
}
|
||||
|
||||
// The script. Times in seconds; the scene ramps below share the same clock.
|
||||
@@ -28,7 +30,7 @@ const CUES: Cue[] = [
|
||||
{ at: 12.0, act: 'dread', line: 'fearTool' },
|
||||
{ at: 18.5, line: 'hateTrade' },
|
||||
{ at: 26.0, act: 'silence', line: null },
|
||||
{ at: 28.5, line: 'butYouTraveled', impact: 0.5 },
|
||||
{ at: 28.5, line: 'butYouTraveled', soft: true, swell: true },
|
||||
{ at: 34.0, act: 'hope', line: 'tables' },
|
||||
{ at: 42.0, line: 'face' },
|
||||
{ at: 49.0, line: 'fences' },
|
||||
@@ -188,6 +190,7 @@ export default function NoFearShow({ onClose }: { onClose: () => void }) {
|
||||
const cue = CUES[idx]
|
||||
if (cue.act) audioRef.current?.setAct(cue.act)
|
||||
if (cue.impact) audioRef.current?.impact(cue.impact)
|
||||
if (cue.swell) audioRef.current?.swell()
|
||||
setCueIdx(idx)
|
||||
if (t >= ANTHEM_AT) setAnthem(true)
|
||||
if (t >= CREDITS_AT) setCredits(true)
|
||||
@@ -242,7 +245,7 @@ export default function NoFearShow({ onClose }: { onClose: () => void }) {
|
||||
{/* The lines. */}
|
||||
{!anthem && lineText && flashIdx < 0 && (
|
||||
<div className="fz-line-wrap" aria-live="polite">
|
||||
<p key={lineText} className={`fz-line ${hard ? 'fz-line-hard' : ''}`}>{lineText}</p>
|
||||
<p key={lineText} className={`fz-line ${hard ? 'fz-line-hard' : ''} ${cue?.soft ? 'fz-line-soft' : ''}`}>{lineText}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -66,6 +66,11 @@
|
||||
letter-spacing: 0.01em;
|
||||
}
|
||||
|
||||
/* The pivot line out of the silence breathes in slowly instead of arriving. */
|
||||
.fz-line-soft {
|
||||
animation-duration: 3.4s;
|
||||
}
|
||||
|
||||
@keyframes fzLineIn {
|
||||
from { opacity: 0; transform: translateY(14px); filter: blur(6px); }
|
||||
to { opacity: 1; transform: translateY(0); filter: blur(0); }
|
||||
|
||||
@@ -125,6 +125,22 @@ export class NoFearAudio {
|
||||
else void this.ctx.resume().catch(() => {})
|
||||
}
|
||||
|
||||
/** A soft warm swell out of the silence — the first calm breath, not a hit. */
|
||||
swell(): void {
|
||||
if (!this.ctx || !this.master) return
|
||||
const t = this.ctx.currentTime + 0.05
|
||||
const o = this.ctx.createOscillator()
|
||||
o.type = 'sine'
|
||||
o.frequency.value = 55 // A1 — the root the hope act will grow from
|
||||
const g = this.ctx.createGain()
|
||||
g.gain.setValueAtTime(0.0001, t)
|
||||
g.gain.exponentialRampToValueAtTime(0.22, t + 1.6)
|
||||
g.gain.exponentialRampToValueAtTime(0.0001, t + 5)
|
||||
o.connect(g)
|
||||
this.out(g, 0.6)
|
||||
o.start(t); o.stop(t + 5.2)
|
||||
}
|
||||
|
||||
/** A cue-driven cinema hit: sub boom + filtered noise burst, tail blooming in the hall. */
|
||||
impact(strength = 1): void {
|
||||
if (!this.ctx || !this.master) return
|
||||
@@ -161,7 +177,9 @@ export class NoFearAudio {
|
||||
if (!this.ctx || !this.master || this.act === act) return
|
||||
this.act = act
|
||||
const t = this.ctx.currentTime
|
||||
this.releaseVoices(act === 'silence' ? 0.1 : 1.6)
|
||||
// Into the silence: a fast pull-away rather than a hard kill — the fear's
|
||||
// hall tail is left ringing out on its own, like a room falling quiet.
|
||||
this.releaseVoices(act === 'silence' ? 0.6 : 1.6)
|
||||
this.stopChords()
|
||||
switch (act) {
|
||||
case 'fear':
|
||||
@@ -202,6 +220,9 @@ export class NoFearAudio {
|
||||
this.heartbeatTimer = null
|
||||
this.stopChords()
|
||||
this.releaseVoices(0.1)
|
||||
// Pull the master down fast BEFORE the deferred close — closing mid-release
|
||||
// (voices still ~30% hot plus the hall tail) would pop.
|
||||
if (this.master && this.ctx) this.master.gain.setTargetAtTime(0, this.ctx.currentTime, 0.05)
|
||||
const ctx = this.ctx
|
||||
this.ctx = null
|
||||
this.master = null
|
||||
@@ -303,13 +324,18 @@ export class NoFearAudio {
|
||||
const g = this.ctx.createGain()
|
||||
g.gain.setValueAtTime(0.0001, t)
|
||||
g.gain.exponentialRampToValueAtTime(level, t + 4)
|
||||
// The swell LFO modulates a SERIES tremolo stage, not g itself — an input
|
||||
// on g.gain would ADD to the release automation and the wind could never
|
||||
// be silenced for the hard cut into the silence act.
|
||||
const trem = this.ctx.createGain()
|
||||
trem.gain.value = 0.75
|
||||
const lfo = this.ctx.createOscillator()
|
||||
lfo.frequency.value = 0.07
|
||||
const lfoGain = this.ctx.createGain()
|
||||
lfoGain.gain.value = level * 0.55
|
||||
lfo.connect(lfoGain); lfoGain.connect(g.gain)
|
||||
lfoGain.gain.value = 0.4
|
||||
lfo.connect(lfoGain); lfoGain.connect(trem.gain)
|
||||
lfo.start(t)
|
||||
src.connect(bp); bp.connect(g)
|
||||
src.connect(bp); bp.connect(trem); trem.connect(g)
|
||||
this.out(g, 0.55)
|
||||
src.start(t)
|
||||
this.hold(g, [src, lfo])
|
||||
@@ -490,15 +516,15 @@ export class NoFearAudio {
|
||||
const n = this.ctx.createBufferSource()
|
||||
n.buffer = this.noiseBuf
|
||||
const hp = this.ctx.createBiquadFilter()
|
||||
hp.type = 'lowpass'
|
||||
hp.frequency.value = 340
|
||||
hp.type = 'highpass' // the CLICK of the valve — the sine above is the body
|
||||
hp.frequency.value = 1700
|
||||
const ng = this.ctx.createGain()
|
||||
ng.gain.setValueAtTime(0.0001, at)
|
||||
ng.gain.exponentialRampToValueAtTime(level * 0.5, at + 0.006)
|
||||
ng.gain.exponentialRampToValueAtTime(0.0001, at + 0.05)
|
||||
ng.gain.exponentialRampToValueAtTime(level * 0.3, at + 0.005)
|
||||
ng.gain.exponentialRampToValueAtTime(0.0001, at + 0.04)
|
||||
n.connect(hp); hp.connect(ng); ng.connect(this.master)
|
||||
n.start(at)
|
||||
n.stop(at + 0.08)
|
||||
n.stop(at + 0.07)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user