feat: add the procedural synthwave soundtrack
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import type { AudioContextLifecycleState, AudioGraph } from '@/audio/types';
|
||||
import { createMusicSequencer } from '@/audio/music-sequencer';
|
||||
import { isTestFault, recordTestFaultEvent } from '@/testing/fault-hooks';
|
||||
|
||||
declare const __PORTFOLIO_TEST_BUILD__: boolean;
|
||||
@@ -46,7 +47,16 @@ export function createSynthEngine(createContext: AudioContextFactory): AudioGrap
|
||||
const master = context.createGain();
|
||||
const dynamics = context.createDynamicsCompressor();
|
||||
master.gain.setValueAtTime(0, context.currentTime);
|
||||
master.connect(dynamics);
|
||||
dynamics.connect(master);
|
||||
|
||||
const musicFilter = context.createBiquadFilter();
|
||||
musicFilter.type = 'lowpass';
|
||||
musicFilter.frequency.setValueAtTime(900, context.currentTime);
|
||||
musicFilter.Q.setValueAtTime(0.72, context.currentTime);
|
||||
const musicGain = context.createGain();
|
||||
musicGain.gain.setValueAtTime(0.38, context.currentTime);
|
||||
musicFilter.connect(musicGain);
|
||||
musicGain.connect(dynamics);
|
||||
|
||||
const engineFilter = context.createBiquadFilter();
|
||||
engineFilter.type = 'lowpass';
|
||||
@@ -54,9 +64,9 @@ export function createSynthEngine(createContext: AudioContextFactory): AudioGrap
|
||||
engineFilter.Q.setValueAtTime(1.25, context.currentTime);
|
||||
|
||||
const engineGain = context.createGain();
|
||||
engineGain.gain.setValueAtTime(0.045, context.currentTime);
|
||||
engineGain.gain.setValueAtTime(0.24, context.currentTime);
|
||||
engineFilter.connect(engineGain);
|
||||
engineGain.connect(master);
|
||||
engineGain.connect(dynamics);
|
||||
|
||||
const fundamental = createOscillator(context, 'sawtooth', 42, 0.16, engineFilter);
|
||||
const harmonic = createOscillator(context, 'sine', 84, 0.055, engineFilter);
|
||||
@@ -66,9 +76,9 @@ export function createSynthEngine(createContext: AudioContextFactory): AudioGrap
|
||||
roadFilter.frequency.setValueAtTime(410, context.currentTime);
|
||||
roadFilter.Q.setValueAtTime(0.55, context.currentTime);
|
||||
const roadGain = context.createGain();
|
||||
roadGain.gain.setValueAtTime(0.015, context.currentTime);
|
||||
roadGain.gain.setValueAtTime(0.045, context.currentTime);
|
||||
roadFilter.connect(roadGain);
|
||||
roadGain.connect(master);
|
||||
roadGain.connect(dynamics);
|
||||
|
||||
const noise = context.createBufferSource();
|
||||
noise.buffer = createNoiseBuffer(context, 2);
|
||||
@@ -83,10 +93,12 @@ export function createSynthEngine(createContext: AudioContextFactory): AudioGrap
|
||||
ignitionGain.gain.exponentialRampToValueAtTime(0.07, context.currentTime + 0.12);
|
||||
ignitionGain.gain.exponentialRampToValueAtTime(0.0001, context.currentTime + 1.15);
|
||||
ignition.connect(ignitionGain);
|
||||
ignitionGain.connect(master);
|
||||
ignitionGain.connect(engineFilter);
|
||||
|
||||
const scheduledNodes: Array<AudioScheduledSourceNode> = [fundamental.source, harmonic.source, noise, ignition];
|
||||
for (const node of scheduledNodes) node.start();
|
||||
const music = createMusicSequencer(context, musicFilter);
|
||||
music.start(context.currentTime);
|
||||
|
||||
let outputConnected = false;
|
||||
let closed = false;
|
||||
@@ -97,24 +109,36 @@ export function createSynthEngine(createContext: AudioContextFactory): AudioGrap
|
||||
async close() {
|
||||
if (closed) return;
|
||||
closed = true;
|
||||
music.stop();
|
||||
for (const node of scheduledNodes) {
|
||||
try { node.stop(); } catch { /* A source may already have ended. */ }
|
||||
node.disconnect();
|
||||
}
|
||||
if (outputConnected) {
|
||||
dynamics.disconnect(context.destination);
|
||||
master.disconnect(context.destination);
|
||||
outputConnected = false;
|
||||
}
|
||||
fundamental.gain.disconnect();
|
||||
harmonic.gain.disconnect();
|
||||
ignitionGain.disconnect();
|
||||
musicFilter.disconnect();
|
||||
musicGain.disconnect();
|
||||
engineFilter.disconnect();
|
||||
engineGain.disconnect();
|
||||
roadFilter.disconnect();
|
||||
roadGain.disconnect();
|
||||
dynamics.disconnect();
|
||||
master.disconnect();
|
||||
await context.close();
|
||||
},
|
||||
connectOutput() {
|
||||
if (closed || outputConnected) return;
|
||||
dynamics.connect(context.destination);
|
||||
master.connect(context.destination);
|
||||
outputConnected = true;
|
||||
},
|
||||
disconnectOutput() {
|
||||
if (!outputConnected) return;
|
||||
dynamics.disconnect(context.destination);
|
||||
master.disconnect(context.destination);
|
||||
outputConnected = false;
|
||||
},
|
||||
setOutputGain(value) {
|
||||
@@ -129,8 +153,10 @@ export function createSynthEngine(createContext: AudioContextFactory): AudioGrap
|
||||
harmonic.source.frequency.setTargetAtTime(84 + next * 56, time, 0.32);
|
||||
engineFilter.frequency.setTargetAtTime(260 + next * 670, time, 0.38);
|
||||
roadFilter.frequency.setTargetAtTime(410 + next * 520, time, 0.42);
|
||||
engineGain.gain.setTargetAtTime(0.045 + next * 0.035, time, 0.4);
|
||||
roadGain.gain.setTargetAtTime(0.015 + next * 0.03, time, 0.45);
|
||||
musicFilter.frequency.setTargetAtTime(900 + next * 2_400, time, 0.42);
|
||||
engineGain.gain.setTargetAtTime(0.24 + next * 0.12, time, 0.4);
|
||||
roadGain.gain.setTargetAtTime(0.045 + next * 0.06, time, 0.45);
|
||||
music.setIntensity(next, time);
|
||||
},
|
||||
onContextStateChange(listener) {
|
||||
const onStateChange = (): void => listener(context.state as AudioContextLifecycleState);
|
||||
|
||||
Reference in New Issue
Block a user