Expand piano drum-kit voices and polish instrument synthesis
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
// Maintainer-controlled web client version.
|
// Maintainer-controlled web client version.
|
||||||
// Format: YYYY.MM.DD Rn (example: 2026.02.20 R2)
|
// Format: YYYY.MM.DD Rn (example: 2026.02.20 R2)
|
||||||
window.CHGRID_WEB_VERSION = "2026.02.22 R200";
|
window.CHGRID_WEB_VERSION = "2026.02.22 R201";
|
||||||
// Optional display timezone for timestamps. Falls back to America/Detroit if unset/invalid.
|
// Optional display timezone for timestamps. Falls back to America/Detroit if unset/invalid.
|
||||||
window.CHGRID_TIME_ZONE = "America/Detroit";
|
window.CHGRID_TIME_ZONE = "America/Detroit";
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ type VoiceRuntime = {
|
|||||||
gain: GainNode;
|
gain: GainNode;
|
||||||
panner: StereoPannerNode | null;
|
panner: StereoPannerNode | null;
|
||||||
oscillators: OscillatorNode[];
|
oscillators: OscillatorNode[];
|
||||||
|
modulators: OscillatorNode[];
|
||||||
releaseSeconds: number;
|
releaseSeconds: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -36,7 +37,9 @@ type InstrumentPreset = {
|
|||||||
oscillators: Array<{ type: OscillatorType; detune?: number; gain?: number; ratio?: number }>;
|
oscillators: Array<{ type: OscillatorType; detune?: number; gain?: number; ratio?: number }>;
|
||||||
filter?: { type: BiquadFilterType; frequency: number; q?: number };
|
filter?: { type: BiquadFilterType; frequency: number; q?: number };
|
||||||
gain: number;
|
gain: number;
|
||||||
|
sustainRatio?: number;
|
||||||
releaseScale?: number;
|
releaseScale?: number;
|
||||||
|
vibrato?: { rateHz: number; depthCents: number };
|
||||||
};
|
};
|
||||||
|
|
||||||
const PRESETS: Record<Exclude<PianoInstrumentId, 'drum_kit'>, InstrumentPreset> = {
|
const PRESETS: Record<Exclude<PianoInstrumentId, 'drum_kit'>, InstrumentPreset> = {
|
||||||
@@ -47,6 +50,7 @@ const PRESETS: Record<Exclude<PianoInstrumentId, 'drum_kit'>, InstrumentPreset>
|
|||||||
],
|
],
|
||||||
filter: { type: 'lowpass', frequency: 5200, q: 0.7 },
|
filter: { type: 'lowpass', frequency: 5200, q: 0.7 },
|
||||||
gain: 0.32,
|
gain: 0.32,
|
||||||
|
sustainRatio: 0.5,
|
||||||
releaseScale: 0.9,
|
releaseScale: 0.9,
|
||||||
},
|
},
|
||||||
electric_piano: {
|
electric_piano: {
|
||||||
@@ -56,6 +60,7 @@ const PRESETS: Record<Exclude<PianoInstrumentId, 'drum_kit'>, InstrumentPreset>
|
|||||||
],
|
],
|
||||||
filter: { type: 'lowpass', frequency: 4200, q: 0.8 },
|
filter: { type: 'lowpass', frequency: 4200, q: 0.8 },
|
||||||
gain: 0.3,
|
gain: 0.3,
|
||||||
|
sustainRatio: 0.52,
|
||||||
releaseScale: 0.8,
|
releaseScale: 0.8,
|
||||||
},
|
},
|
||||||
guitar: {
|
guitar: {
|
||||||
@@ -65,6 +70,7 @@ const PRESETS: Record<Exclude<PianoInstrumentId, 'drum_kit'>, InstrumentPreset>
|
|||||||
],
|
],
|
||||||
filter: { type: 'lowpass', frequency: 3200, q: 0.9 },
|
filter: { type: 'lowpass', frequency: 3200, q: 0.9 },
|
||||||
gain: 0.24,
|
gain: 0.24,
|
||||||
|
sustainRatio: 0.48,
|
||||||
releaseScale: 0.7,
|
releaseScale: 0.7,
|
||||||
},
|
},
|
||||||
organ: {
|
organ: {
|
||||||
@@ -75,6 +81,7 @@ const PRESETS: Record<Exclude<PianoInstrumentId, 'drum_kit'>, InstrumentPreset>
|
|||||||
],
|
],
|
||||||
filter: { type: 'lowpass', frequency: 6500, q: 0.6 },
|
filter: { type: 'lowpass', frequency: 6500, q: 0.6 },
|
||||||
gain: 0.18,
|
gain: 0.18,
|
||||||
|
sustainRatio: 0.72,
|
||||||
releaseScale: 1.4,
|
releaseScale: 1.4,
|
||||||
},
|
},
|
||||||
bass: {
|
bass: {
|
||||||
@@ -84,6 +91,7 @@ const PRESETS: Record<Exclude<PianoInstrumentId, 'drum_kit'>, InstrumentPreset>
|
|||||||
],
|
],
|
||||||
filter: { type: 'lowpass', frequency: 1500, q: 1.1 },
|
filter: { type: 'lowpass', frequency: 1500, q: 1.1 },
|
||||||
gain: 0.28,
|
gain: 0.28,
|
||||||
|
sustainRatio: 0.45,
|
||||||
releaseScale: 0.9,
|
releaseScale: 0.9,
|
||||||
},
|
},
|
||||||
violin: {
|
violin: {
|
||||||
@@ -93,7 +101,9 @@ const PRESETS: Record<Exclude<PianoInstrumentId, 'drum_kit'>, InstrumentPreset>
|
|||||||
],
|
],
|
||||||
filter: { type: 'lowpass', frequency: 3600, q: 1.0 },
|
filter: { type: 'lowpass', frequency: 3600, q: 1.0 },
|
||||||
gain: 0.24,
|
gain: 0.24,
|
||||||
|
sustainRatio: 0.68,
|
||||||
releaseScale: 1.5,
|
releaseScale: 1.5,
|
||||||
|
vibrato: { rateHz: 5.7, depthCents: 12 },
|
||||||
},
|
},
|
||||||
synth_lead: {
|
synth_lead: {
|
||||||
oscillators: [
|
oscillators: [
|
||||||
@@ -102,7 +112,9 @@ const PRESETS: Record<Exclude<PianoInstrumentId, 'drum_kit'>, InstrumentPreset>
|
|||||||
],
|
],
|
||||||
filter: { type: 'lowpass', frequency: 5400, q: 0.9 },
|
filter: { type: 'lowpass', frequency: 5400, q: 0.9 },
|
||||||
gain: 0.2,
|
gain: 0.2,
|
||||||
|
sustainRatio: 0.6,
|
||||||
releaseScale: 1,
|
releaseScale: 1,
|
||||||
|
vibrato: { rateHz: 6.8, depthCents: 9 },
|
||||||
},
|
},
|
||||||
nintendo: {
|
nintendo: {
|
||||||
oscillators: [
|
oscillators: [
|
||||||
@@ -111,6 +123,7 @@ const PRESETS: Record<Exclude<PianoInstrumentId, 'drum_kit'>, InstrumentPreset>
|
|||||||
],
|
],
|
||||||
filter: { type: 'lowpass', frequency: 5200, q: 1.2 },
|
filter: { type: 'lowpass', frequency: 5200, q: 1.2 },
|
||||||
gain: 0.22,
|
gain: 0.22,
|
||||||
|
sustainRatio: 0.62,
|
||||||
releaseScale: 0.65,
|
releaseScale: 0.65,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -153,9 +166,13 @@ function safeStop(oscillator: OscillatorNode, when: number): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DrumVariant = 'kick_808' | 'snare' | 'clap' | 'hat_closed' | 'hat_open' | 'tom_low' | 'tom_high' | 'noise_8bit';
|
||||||
|
const DRUM_VARIANTS: DrumVariant[] = ['kick_808', 'snare', 'clap', 'hat_closed', 'hat_open', 'tom_low', 'tom_high', 'noise_8bit'];
|
||||||
|
|
||||||
export class PianoSynth {
|
export class PianoSynth {
|
||||||
private readonly voices = new Map<string, VoiceRuntime>();
|
private readonly voices = new Map<string, VoiceRuntime>();
|
||||||
private readonly drumNoiseBuffers = new WeakMap<AudioContext, AudioBuffer>();
|
private readonly drumNoiseBuffers = new WeakMap<AudioContext, AudioBuffer>();
|
||||||
|
private readonly bitNoiseBuffers = new WeakMap<AudioContext, AudioBuffer>();
|
||||||
|
|
||||||
/** Stops and disconnects all active notes. */
|
/** Stops and disconnects all active notes. */
|
||||||
stopAll(): void {
|
stopAll(): void {
|
||||||
@@ -176,7 +193,7 @@ export class PianoSynth {
|
|||||||
): void {
|
): void {
|
||||||
if (this.voices.has(keyId)) return;
|
if (this.voices.has(keyId)) return;
|
||||||
if (instrument === 'drum_kit') {
|
if (instrument === 'drum_kit') {
|
||||||
this.playDrumHit(keyId, context, spatial, attackPercent, decayPercent);
|
this.playDrumHit(keyId, midi, context, spatial, attackPercent, decayPercent);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,7 +214,7 @@ export class PianoSynth {
|
|||||||
const voiceGain = context.audioCtx.createGain();
|
const voiceGain = context.audioCtx.createGain();
|
||||||
voiceGain.gain.setValueAtTime(0.0001, now);
|
voiceGain.gain.setValueAtTime(0.0001, now);
|
||||||
const peakGain = Math.max(0.0001, preset.gain * spatialMix.gain);
|
const peakGain = Math.max(0.0001, preset.gain * spatialMix.gain);
|
||||||
const sustainGain = Math.max(0.0001, peakGain * 0.55);
|
const sustainGain = Math.max(0.0001, peakGain * (preset.sustainRatio ?? 0.55));
|
||||||
voiceGain.gain.exponentialRampToValueAtTime(peakGain, now + attackSeconds);
|
voiceGain.gain.exponentialRampToValueAtTime(peakGain, now + attackSeconds);
|
||||||
voiceGain.gain.exponentialRampToValueAtTime(sustainGain, now + attackSeconds + decaySeconds * 0.6);
|
voiceGain.gain.exponentialRampToValueAtTime(sustainGain, now + attackSeconds + decaySeconds * 0.6);
|
||||||
|
|
||||||
@@ -222,6 +239,7 @@ export class PianoSynth {
|
|||||||
|
|
||||||
const frequency = midiToFrequency(midi);
|
const frequency = midiToFrequency(midi);
|
||||||
const oscillators: OscillatorNode[] = [];
|
const oscillators: OscillatorNode[] = [];
|
||||||
|
const modulators: OscillatorNode[] = [];
|
||||||
for (const partial of preset.oscillators) {
|
for (const partial of preset.oscillators) {
|
||||||
const oscillator = context.audioCtx.createOscillator();
|
const oscillator = context.audioCtx.createOscillator();
|
||||||
oscillator.type = partial.type;
|
oscillator.type = partial.type;
|
||||||
@@ -232,12 +250,22 @@ export class PianoSynth {
|
|||||||
oscillator.connect(oscGain).connect(voiceGain);
|
oscillator.connect(oscGain).connect(voiceGain);
|
||||||
oscillator.start(now);
|
oscillator.start(now);
|
||||||
oscillators.push(oscillator);
|
oscillators.push(oscillator);
|
||||||
|
if (preset.vibrato) {
|
||||||
|
const lfo = context.audioCtx.createOscillator();
|
||||||
|
const lfoGain = context.audioCtx.createGain();
|
||||||
|
lfo.frequency.setValueAtTime(preset.vibrato.rateHz, now);
|
||||||
|
lfoGain.gain.setValueAtTime(preset.vibrato.depthCents, now);
|
||||||
|
lfo.connect(lfoGain).connect(oscillator.detune);
|
||||||
|
lfo.start(now);
|
||||||
|
modulators.push(lfo);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.voices.set(keyId, {
|
this.voices.set(keyId, {
|
||||||
gain: voiceGain,
|
gain: voiceGain,
|
||||||
panner,
|
panner,
|
||||||
oscillators,
|
oscillators,
|
||||||
|
modulators,
|
||||||
releaseSeconds,
|
releaseSeconds,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -255,6 +283,9 @@ export class PianoSynth {
|
|||||||
for (const oscillator of voice.oscillators) {
|
for (const oscillator of voice.oscillators) {
|
||||||
safeStop(oscillator, now + voice.releaseSeconds + 0.02);
|
safeStop(oscillator, now + voice.releaseSeconds + 0.02);
|
||||||
}
|
}
|
||||||
|
for (const oscillator of voice.modulators) {
|
||||||
|
safeStop(oscillator, now + voice.releaseSeconds + 0.02);
|
||||||
|
}
|
||||||
window.setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
try {
|
try {
|
||||||
voice.gain.disconnect();
|
voice.gain.disconnect();
|
||||||
@@ -274,6 +305,7 @@ export class PianoSynth {
|
|||||||
/** Plays one synthesized drum hit for drum-kit instrument mode. */
|
/** Plays one synthesized drum hit for drum-kit instrument mode. */
|
||||||
private playDrumHit(
|
private playDrumHit(
|
||||||
keyId: string,
|
keyId: string,
|
||||||
|
midi: number,
|
||||||
context: PianoContext,
|
context: PianoContext,
|
||||||
spatial: PianoSpatialSource,
|
spatial: PianoSpatialSource,
|
||||||
attackPercent: number,
|
attackPercent: number,
|
||||||
@@ -287,9 +319,10 @@ export class PianoSynth {
|
|||||||
baseGain: 1,
|
baseGain: 1,
|
||||||
});
|
});
|
||||||
if (!spatialMix || spatialMix.gain <= 0) return;
|
if (!spatialMix || spatialMix.gain <= 0) return;
|
||||||
const typeIndex = Math.abs(this.hashKey(keyId)) % 4;
|
const typeIndex = Math.abs((midi % DRUM_VARIANTS.length) + this.hashKey(keyId)) % DRUM_VARIANTS.length;
|
||||||
const decaySeconds = 0.03 + decayPercentToSeconds(decayPercent) * 0.45;
|
const variant = DRUM_VARIANTS[typeIndex];
|
||||||
const attackSeconds = Math.max(0.001, attackPercentToSeconds(attackPercent) * 0.2);
|
const decaySeconds = 0.03 + decayPercentToSeconds(decayPercent) * 0.5;
|
||||||
|
const attackSeconds = Math.max(0.001, attackPercentToSeconds(attackPercent) * 0.18);
|
||||||
|
|
||||||
const gain = context.audioCtx.createGain();
|
const gain = context.audioCtx.createGain();
|
||||||
gain.gain.setValueAtTime(0.0001, now);
|
gain.gain.setValueAtTime(0.0001, now);
|
||||||
@@ -306,25 +339,117 @@ export class PianoSynth {
|
|||||||
tailNode.connect(context.destination);
|
tailNode.connect(context.destination);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeIndex === 0) {
|
if (variant === 'kick_808') {
|
||||||
const kick = context.audioCtx.createOscillator();
|
this.playKick808(context, gain, now, decaySeconds);
|
||||||
kick.type = 'sine';
|
|
||||||
kick.frequency.setValueAtTime(150, now);
|
|
||||||
kick.frequency.exponentialRampToValueAtTime(45, now + decaySeconds * 0.85);
|
|
||||||
kick.connect(gain);
|
|
||||||
kick.start(now);
|
|
||||||
safeStop(kick, now + decaySeconds + 0.04);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (variant === 'tom_low') {
|
||||||
|
this.playTom(context, gain, now, 120, 68, decaySeconds * 0.95);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (variant === 'tom_high') {
|
||||||
|
this.playTom(context, gain, now, 220, 125, decaySeconds * 0.8);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (variant === 'hat_closed') {
|
||||||
|
this.playNoiseDrum(context, gain, now, decaySeconds * 0.25, 'highpass', 6500, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (variant === 'hat_open') {
|
||||||
|
this.playNoiseDrum(context, gain, now, decaySeconds * 0.8, 'highpass', 5200, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (variant === 'noise_8bit') {
|
||||||
|
this.playNoiseDrum(context, gain, now, decaySeconds * 0.45, 'bandpass', 2700, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (variant === 'clap') {
|
||||||
|
this.playClap(context, gain, now, decaySeconds);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.playSnare(context, gain, now, decaySeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 808-like kick: deep sine sweep with long-ish tail. */
|
||||||
|
private playKick808(context: PianoContext, gain: GainNode, now: number, decaySeconds: number): void {
|
||||||
|
const kick = context.audioCtx.createOscillator();
|
||||||
|
kick.type = 'sine';
|
||||||
|
kick.frequency.setValueAtTime(160, now);
|
||||||
|
kick.frequency.exponentialRampToValueAtTime(42, now + Math.max(0.07, decaySeconds * 0.95));
|
||||||
|
const body = context.audioCtx.createGain();
|
||||||
|
body.gain.setValueAtTime(1, now);
|
||||||
|
body.gain.exponentialRampToValueAtTime(0.0001, now + Math.max(0.08, decaySeconds));
|
||||||
|
kick.connect(body).connect(gain);
|
||||||
|
kick.start(now);
|
||||||
|
safeStop(kick, now + Math.max(0.1, decaySeconds) + 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Simple tom synthesis with tuned sine drop. */
|
||||||
|
private playTom(context: PianoContext, gain: GainNode, now: number, startHz: number, endHz: number, decaySeconds: number): void {
|
||||||
|
const tom = context.audioCtx.createOscillator();
|
||||||
|
tom.type = 'sine';
|
||||||
|
tom.frequency.setValueAtTime(startHz, now);
|
||||||
|
tom.frequency.exponentialRampToValueAtTime(endHz, now + Math.max(0.05, decaySeconds * 0.85));
|
||||||
|
const body = context.audioCtx.createGain();
|
||||||
|
body.gain.setValueAtTime(1, now);
|
||||||
|
body.gain.exponentialRampToValueAtTime(0.0001, now + Math.max(0.07, decaySeconds));
|
||||||
|
tom.connect(body).connect(gain);
|
||||||
|
tom.start(now);
|
||||||
|
safeStop(tom, now + Math.max(0.1, decaySeconds) + 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** White-noise percussion core used by hats/snare/noise blips. */
|
||||||
|
private playNoiseDrum(
|
||||||
|
context: PianoContext,
|
||||||
|
gain: GainNode,
|
||||||
|
now: number,
|
||||||
|
decaySeconds: number,
|
||||||
|
filterType: BiquadFilterType,
|
||||||
|
filterHz: number,
|
||||||
|
bitStyle: boolean,
|
||||||
|
): void {
|
||||||
const noise = context.audioCtx.createBufferSource();
|
const noise = context.audioCtx.createBufferSource();
|
||||||
noise.buffer = this.getNoiseBuffer(context.audioCtx);
|
noise.buffer = bitStyle ? this.getBitNoiseBuffer(context.audioCtx) : this.getNoiseBuffer(context.audioCtx);
|
||||||
const noiseFilter = context.audioCtx.createBiquadFilter();
|
const noiseFilter = context.audioCtx.createBiquadFilter();
|
||||||
noiseFilter.type = typeIndex === 1 ? 'highpass' : typeIndex === 2 ? 'bandpass' : 'lowpass';
|
noiseFilter.type = filterType;
|
||||||
noiseFilter.frequency.setValueAtTime(typeIndex === 1 ? 1700 : typeIndex === 2 ? 900 : 1300, now);
|
noiseFilter.frequency.setValueAtTime(filterHz, now);
|
||||||
noise.connect(noiseFilter).connect(gain);
|
noise.connect(noiseFilter).connect(gain);
|
||||||
noise.start(now);
|
noise.start(now);
|
||||||
safeStop(noise, now + decaySeconds + 0.03);
|
safeStop(noise, now + Math.max(0.02, decaySeconds) + 0.03);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Snare: short tone + filtered noise burst. */
|
||||||
|
private playSnare(context: PianoContext, gain: GainNode, now: number, decaySeconds: number): void {
|
||||||
|
const tone = context.audioCtx.createOscillator();
|
||||||
|
tone.type = 'triangle';
|
||||||
|
tone.frequency.setValueAtTime(220, now);
|
||||||
|
tone.frequency.exponentialRampToValueAtTime(130, now + Math.max(0.03, decaySeconds * 0.45));
|
||||||
|
const toneGain = context.audioCtx.createGain();
|
||||||
|
toneGain.gain.setValueAtTime(0.45, now);
|
||||||
|
toneGain.gain.exponentialRampToValueAtTime(0.0001, now + Math.max(0.04, decaySeconds * 0.55));
|
||||||
|
tone.connect(toneGain).connect(gain);
|
||||||
|
tone.start(now);
|
||||||
|
safeStop(tone, now + Math.max(0.06, decaySeconds * 0.6) + 0.03);
|
||||||
|
this.playNoiseDrum(context, gain, now, decaySeconds * 0.65, 'highpass', 1800, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Clap: layered short filtered noise bursts. */
|
||||||
|
private playClap(context: PianoContext, gain: GainNode, now: number, decaySeconds: number): void {
|
||||||
|
const burstTimes = [0, 0.018, 0.035];
|
||||||
|
for (const burstOffset of burstTimes) {
|
||||||
|
const noise = context.audioCtx.createBufferSource();
|
||||||
|
noise.buffer = this.getNoiseBuffer(context.audioCtx);
|
||||||
|
const filter = context.audioCtx.createBiquadFilter();
|
||||||
|
filter.type = 'bandpass';
|
||||||
|
filter.frequency.setValueAtTime(2100, now + burstOffset);
|
||||||
|
const burstGain = context.audioCtx.createGain();
|
||||||
|
burstGain.gain.setValueAtTime(0.0001, now + burstOffset);
|
||||||
|
burstGain.gain.exponentialRampToValueAtTime(0.85, now + burstOffset + 0.002);
|
||||||
|
burstGain.gain.exponentialRampToValueAtTime(0.0001, now + burstOffset + Math.max(0.03, decaySeconds * 0.25));
|
||||||
|
noise.connect(filter).connect(burstGain).connect(gain);
|
||||||
|
noise.start(now + burstOffset);
|
||||||
|
safeStop(noise, now + burstOffset + Math.max(0.05, decaySeconds * 0.32));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns deterministic hash for key ids to map drum voice variants. */
|
/** Returns deterministic hash for key ids to map drum voice variants. */
|
||||||
@@ -349,4 +474,23 @@ export class PianoSynth {
|
|||||||
this.drumNoiseBuffers.set(audioCtx, buffer);
|
this.drumNoiseBuffers.set(audioCtx, buffer);
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns quantized 8-bit style noise buffer for retro percussion. */
|
||||||
|
private getBitNoiseBuffer(audioCtx: AudioContext): AudioBuffer {
|
||||||
|
const existing = this.bitNoiseBuffers.get(audioCtx);
|
||||||
|
if (existing) return existing;
|
||||||
|
const length = Math.max(1, Math.floor(audioCtx.sampleRate * 0.45));
|
||||||
|
const buffer = audioCtx.createBuffer(1, length, audioCtx.sampleRate);
|
||||||
|
const data = buffer.getChannelData(0);
|
||||||
|
let held = 0;
|
||||||
|
for (let index = 0; index < length; index += 1) {
|
||||||
|
if (index % 16 === 0) {
|
||||||
|
const raw = Math.random() * 2 - 1;
|
||||||
|
held = Math.round(raw * 8) / 8;
|
||||||
|
}
|
||||||
|
data[index] = held;
|
||||||
|
}
|
||||||
|
this.bitNoiseBuffers.set(audioCtx, buffer);
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user