not sure
This commit is contained in:
parent
560398fefb
commit
80ed37298b
73 changed files with 3619 additions and 1295 deletions
|
|
@ -1,16 +1,19 @@
|
|||
import { clamp } from '../utils/clamp';
|
||||
import { GardenAudioConfig, GardenAudioVibeProfile } from './garden-audio-config';
|
||||
import type { PianoNoteRole } from './garden-audio-types';
|
||||
|
||||
export class GardenAudioGraph {
|
||||
public context: AudioContext | null = null;
|
||||
public eventBus: GainNode | null = null;
|
||||
public delayInput: GainNode | null = null;
|
||||
public noiseBus: GainNode | null = null;
|
||||
public noiseBuffer: AudioBuffer | null = null;
|
||||
|
||||
private masterGain: GainNode | null = null;
|
||||
private delayNode: DelayNode | null = null;
|
||||
private delayFeedback: GainNode | null = null;
|
||||
private delayOutput: GainNode | null = null;
|
||||
private readonly pianoBuses = new Map<PianoNoteRole, GainNode>();
|
||||
|
||||
public constructor(private readonly config: GardenAudioConfig) {}
|
||||
|
||||
|
|
@ -30,19 +33,28 @@ export class GardenAudioGraph {
|
|||
|
||||
let context: AudioContext;
|
||||
try {
|
||||
context = new AudioContextConstructor({ latencyHint: 'interactive' });
|
||||
context = new AudioContextConstructor({
|
||||
latencyHint: this.config.graph.latencyHint,
|
||||
});
|
||||
} catch {
|
||||
context = new AudioContextConstructor();
|
||||
}
|
||||
const masterGain = context.createGain();
|
||||
const highPass = context.createBiquadFilter();
|
||||
const compressor = context.createDynamicsCompressor();
|
||||
|
||||
masterGain.gain.value = 0;
|
||||
highPass.type = 'highpass';
|
||||
highPass.type = this.config.graph.outputFilterType;
|
||||
highPass.frequency.value = this.config.highPassFrequencyHz;
|
||||
compressor.threshold.value = this.config.graph.compressor.thresholdDb;
|
||||
compressor.knee.value = this.config.graph.compressor.kneeDb;
|
||||
compressor.ratio.value = this.config.graph.compressor.ratio;
|
||||
compressor.attack.value = this.config.graph.compressor.attackSeconds;
|
||||
compressor.release.value = this.config.graph.compressor.releaseSeconds;
|
||||
|
||||
masterGain.connect(highPass);
|
||||
highPass.connect(context.destination);
|
||||
highPass.connect(compressor);
|
||||
compressor.connect(context.destination);
|
||||
|
||||
this.context = context;
|
||||
this.masterGain = masterGain;
|
||||
|
|
@ -65,7 +77,7 @@ export class GardenAudioGraph {
|
|||
const source = this.context.createOscillator();
|
||||
const gain = this.context.createGain();
|
||||
|
||||
source.type = 'sine';
|
||||
source.type = this.config.graph.unlockTickType;
|
||||
source.frequency.setValueAtTime(this.config.graph.unlockTickFrequencyHz, now);
|
||||
gain.gain.setValueAtTime(this.config.piano.minGain, now);
|
||||
gain.gain.exponentialRampToValueAtTime(
|
||||
|
|
@ -116,6 +128,7 @@ export class GardenAudioGraph {
|
|||
}
|
||||
|
||||
const now = this.context.currentTime;
|
||||
const normalizedActivity = clamp(activity, 0, 1);
|
||||
this.delayNode.delayTime.setTargetAtTime(
|
||||
this.config.delay.timeSeconds * profile.delayTimeMultiplier,
|
||||
now,
|
||||
|
|
@ -123,7 +136,8 @@ export class GardenAudioGraph {
|
|||
);
|
||||
this.delayFeedback.gain.setTargetAtTime(
|
||||
clamp(
|
||||
this.config.delay.feedback + activity * this.config.delay.activityFeedbackWeight,
|
||||
this.config.delay.feedback +
|
||||
normalizedActivity * this.config.delay.activityFeedbackWeight,
|
||||
this.config.delay.feedbackMin,
|
||||
this.config.delay.feedbackMax
|
||||
),
|
||||
|
|
@ -133,10 +147,16 @@ export class GardenAudioGraph {
|
|||
this.delayOutput.gain.setTargetAtTime(
|
||||
this.config.delay.wetGain *
|
||||
(this.config.delay.outputBase +
|
||||
activity * this.config.delay.outputActivityWeight),
|
||||
normalizedActivity * this.config.delay.outputActivityWeight) *
|
||||
(1 - normalizedActivity * this.config.delay.outputActivityDuck),
|
||||
now,
|
||||
this.config.updateRampSeconds
|
||||
);
|
||||
this.updatePianoBusGains(normalizedActivity, now);
|
||||
}
|
||||
|
||||
public getPianoBus(role: PianoNoteRole | undefined): GainNode | null {
|
||||
return this.pianoBuses.get(role ?? 'gesture') ?? this.eventBus;
|
||||
}
|
||||
|
||||
public async close(): Promise<void> {
|
||||
|
|
@ -165,15 +185,27 @@ export class GardenAudioGraph {
|
|||
const delayNode = context.createDelay(this.config.graph.delayMaxSeconds);
|
||||
const delayFeedback = context.createGain();
|
||||
const delayOutput = context.createGain();
|
||||
const feedbackHighPass = context.createBiquadFilter();
|
||||
const feedbackLowPass = context.createBiquadFilter();
|
||||
const returnLowPass = context.createBiquadFilter();
|
||||
|
||||
delayNode.delayTime.value = this.config.delay.timeSeconds;
|
||||
delayFeedback.gain.value = this.config.delay.feedback;
|
||||
delayOutput.gain.value = this.config.delay.wetGain;
|
||||
feedbackHighPass.type = 'highpass';
|
||||
feedbackHighPass.frequency.value = this.config.delay.feedbackHighPassHz;
|
||||
feedbackLowPass.type = 'lowpass';
|
||||
feedbackLowPass.frequency.value = this.config.delay.feedbackLowPassHz;
|
||||
returnLowPass.type = 'lowpass';
|
||||
returnLowPass.frequency.value = this.config.delay.returnLowPassHz;
|
||||
|
||||
delayInput.connect(delayNode);
|
||||
delayNode.connect(delayFeedback);
|
||||
delayNode.connect(feedbackHighPass);
|
||||
feedbackHighPass.connect(feedbackLowPass);
|
||||
feedbackLowPass.connect(delayFeedback);
|
||||
delayFeedback.connect(delayNode);
|
||||
delayNode.connect(delayOutput);
|
||||
delayNode.connect(returnLowPass);
|
||||
returnLowPass.connect(delayOutput);
|
||||
delayOutput.connect(masterGain);
|
||||
|
||||
this.delayInput = delayInput;
|
||||
|
|
@ -183,13 +215,50 @@ export class GardenAudioGraph {
|
|||
}
|
||||
|
||||
private createBuses(context: AudioContext, masterGain: GainNode): void {
|
||||
this.eventBus = context.createGain();
|
||||
this.eventBus.gain.value = this.config.graph.eventBusGain;
|
||||
this.eventBus.connect(masterGain);
|
||||
const eventBus = context.createGain();
|
||||
eventBus.gain.value = this.config.graph.eventBusGain;
|
||||
eventBus.connect(masterGain);
|
||||
this.eventBus = eventBus;
|
||||
this.pianoBuses.clear();
|
||||
|
||||
(Object.keys(this.config.graph.pianoBusGains) as Array<PianoNoteRole>).forEach(
|
||||
(role) => {
|
||||
const bus = context.createGain();
|
||||
bus.gain.value = this.config.graph.pianoBusGains[role];
|
||||
bus.connect(eventBus);
|
||||
this.pianoBuses.set(role, bus);
|
||||
}
|
||||
);
|
||||
|
||||
this.noiseBus = context.createGain();
|
||||
this.noiseBus.gain.value = this.config.graph.noiseBusGain;
|
||||
this.noiseBus.connect(eventBus);
|
||||
}
|
||||
|
||||
private updatePianoBusGains(activity: number, now: number): void {
|
||||
this.pianoBuses.forEach((bus, role) => {
|
||||
const baseGain = this.config.graph.pianoBusGains[role];
|
||||
const ducking = this.config.graph.pianoBusActivityDucking[role];
|
||||
bus.gain.setTargetAtTime(
|
||||
Math.max(0, baseGain * (1 - activity * ducking)),
|
||||
now,
|
||||
this.config.updateRampSeconds
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
private createNoiseBuffer(context: AudioContext): AudioBuffer {
|
||||
const buffer = context.createBuffer(1, context.sampleRate, context.sampleRate);
|
||||
const buffer = context.createBuffer(
|
||||
appPositiveInteger(this.config.graph.noiseBufferChannels),
|
||||
Math.max(
|
||||
1,
|
||||
Math.floor(
|
||||
context.sampleRate *
|
||||
Math.max(0.001, this.config.graph.noiseBufferDurationSeconds)
|
||||
)
|
||||
),
|
||||
context.sampleRate
|
||||
);
|
||||
const data = buffer.getChannelData(0);
|
||||
|
||||
for (let index = 0; index < data.length; index++) {
|
||||
|
|
@ -205,10 +274,15 @@ export class GardenAudioGraph {
|
|||
this.context = null;
|
||||
this.eventBus = null;
|
||||
this.delayInput = null;
|
||||
this.noiseBus = null;
|
||||
this.noiseBuffer = null;
|
||||
this.masterGain = null;
|
||||
this.delayNode = null;
|
||||
this.delayFeedback = null;
|
||||
this.delayOutput = null;
|
||||
this.pianoBuses.clear();
|
||||
}
|
||||
}
|
||||
|
||||
const appPositiveInteger = (value: number): number =>
|
||||
Math.max(1, Math.floor(Number.isFinite(value) ? value : 1));
|
||||
|
|
|
|||
Loading…
Reference in a new issue