import type { AudioManager } from '@rpg/engine';
import { bandNoise, decay, normalize, synth } from '@rpg/engine/tools/synth.mjs';
/**
* Вариативные шаги: 3 процедурных варианта на поверхность, синтезируются
* в рантайме из примитивов wav.mjs (те же полосы/огибающие, что в
* tools/audio/gen.mjs, сиды смещены — звучат похоже, но не идентично WAV).
* Выбор варианта — по кругу. До unlock буферы не создать (createBuffer →
* null) — играем файловый шаг, варианты синтезируются заново при следующих
* шагах, пока не получится (обычно вторая попытка, после первого play).
*/
export type StepSurface = 'grass' | 'ash' | 'water' | 'floor';
/** Параметры поверхности — как в gen.mjs (шум + низкий тон). */
const SURFACES: Record<StepSurface, { seed: number; low: number; high: number; dur: number; power: number; noise: number; hum: number; humGain: number }> = {
grass: { seed: 253, low: 400, high: 2000, dur: 0.12, power: 2, noise: 1, hum: 0, humGain: 0 },
ash: { seed: 254, low: 200, high: 1100, dur: 0.14, power: 2.5, noise: 1, hum: 0, humGain: 0 },
water: { seed: 255, low: 600, high: 3200, dur: 0.18, power: 2, noise: 0.6, hum: 300, humGain: 0.2 },
floor: { seed: 256, low: 150, high: 800, dur: 0.1, power: 3, noise: 0.4, hum: 140, humGain: 0.5 }
};
const VARIANTS = 3;
export class StepVariants {
/** Буферы по поверхности (синтезируются при первом успешном шаге). */
private cache = new Map<StepSurface, AudioBuffer[]>();
private seq = 0;
constructor(private audio: AudioManager) {}
/** Играть очередной вариант шага (до готовности буферов — файловый шаг). */
play(surface: StepSurface, volume: number): void {
const key = `sfx/step_${surface}`;
const list = this.ensure(surface);
if (list.length > 0) {
const i = this.seq++ % list.length;
void this.audio.playBuffer(`${key}#${i}`, list[i]!, { volume });
} else {
void this.audio.play(key, { volume });
}
}
/** Синтезировать варианты поверхности (если контекст ещё не готов — пустой список). */
private ensure(surface: StepSurface): AudioBuffer[] {
const existing = this.cache.get(surface);
if (existing) return existing;
const s = SURFACES[surface];
const list: AudioBuffer[] = [];
for (let i = 0; i < VARIANTS; i++) {
const noise = bandNoise(s.seed + i * 10, s.low, s.high, s.dur);
const samples = normalize(
synth(
s.dur,
(t) => (s.noise * noise() + s.humGain * Math.sin(2 * Math.PI * s.hum * t)) * decay(t, s.dur, s.power)
),
0.3
);
const buf = this.audio.createBuffer(samples, 22050);
if (!buf) return []; // контекста нет — попробуем на следующем шаге
list.push(buf);
}
this.cache.set(surface, list);
return list;
}
}