Newer
Older
rpg / apps / game / src / data / music.ts
import { createRng, type MusicSpec, type NoteEvent } from '@rpg/engine';

/**
 * Музыкальные темы: правило-базовый генератор (детерминированный, сиды) +
 * компиляция движковым секвенсором (renderMusic → playLoopBuffer) прямо
 * в рантайме — WAV-файлов для музыки нет. Тема лугов — пепельная
 * ля-минорная пентатоника: пэд-дрон + редкая плак-мелодия восьмыми.
 */

/** Ля-минорная пентатоника от A3 (A C D E G + октава). */
const PENTATONIC = [57, 60, 62, 64, 67, 69, 72] as const;

/** Дрон-пэд: тоника/субмедианта по половинам лупа. */
function drone(from: number, to: number, beats: number): NoteEvent[] {
    return [
        { beat: 0, dur: beats / 2, midi: from, vel: 0.5 },
        { beat: beats / 2, dur: beats / 2, midi: to, vel: 0.5 }
    ];
}

/** Мелодия восьмыми с паузами (вероятность ноты 0.55) — сид фиксирует рисунок. */
function melody(seed: number, beats: number): NoteEvent[] {
    const rng = createRng(seed);
    const notes: NoteEvent[] = [];
    for (let eighth = 0; eighth < beats * 2; eighth++) {
        if (!rng.chance(0.55)) continue;
        notes.push({
            beat: eighth * 0.5,
            dur: 0.5,
            midi: PENTATONIC[Math.floor(rng.next() * PENTATONIC.length)]!,
            vel: 0.35 + rng.next() * 0.25
        });
    }
    return notes;
}

/** Тема лугов: 16 долей на 70 bpm, дрон A2→G2 + плак по пентатонике. */
export const MEADOW_THEME: MusicSpec = {
    bpm: 70,
    beats: 16,
    tracks: [
        { instrument: 'pad', notes: drone(45, 43, 16), gain: 0.9 },
        { instrument: 'pluck', notes: melody(20260907, 16) }
    ]
};

/** Реестр тем (ключ → спек); область ссылается на тему полем `theme`. */
export const THEMES: Record<string, MusicSpec> = {
    meadows: MEADOW_THEME
};