49 lines
2.4 KiB
JavaScript
49 lines
2.4 KiB
JavaScript
// Génère les sons de navigation du thème Orbe (WAV 16 bits mono, 44,1 kHz) : sobres, courts, sans dépendance.
|
|
// Usage : node tools/gen-sounds.mjs
|
|
import { mkdirSync, writeFileSync } from 'node:fs'
|
|
import { join } from 'node:path'
|
|
|
|
const RATE = 44100
|
|
const out = join(import.meta.dirname, '..', 'themes', 'orbe', 'sounds')
|
|
|
|
/* voix : fréquence qui glisse de f0 à f1, enveloppe attaque/déclin, timbre sinus + un peu d'harmonique */
|
|
function tone(dur, f0, f1, { gain = 0.5, attack = 0.004, harm = 0.15, delay = 0 } = {}) {
|
|
const n = Math.round((dur + delay) * RATE), s = new Float32Array(n)
|
|
let phase = 0
|
|
for (let i = Math.round(delay * RATE); i < n; i++) {
|
|
const t = i / RATE - delay, p = t / dur
|
|
const f = f0 * Math.pow(f1 / f0, p)
|
|
phase += (2 * Math.PI * f) / RATE
|
|
const env = Math.min(1, t / attack) * Math.pow(1 - p, 2.2)
|
|
s[i] = gain * env * (Math.sin(phase) + harm * Math.sin(2 * phase))
|
|
}
|
|
return s
|
|
}
|
|
|
|
const mix = (...parts) => {
|
|
const n = Math.max(...parts.map((p) => p.length)), s = new Float32Array(n)
|
|
for (const p of parts) for (let i = 0; i < p.length; i++) s[i] += p[i]
|
|
return s
|
|
}
|
|
|
|
function wav(samples) {
|
|
const b = Buffer.alloc(44 + samples.length * 2)
|
|
b.write('RIFF', 0); b.writeUInt32LE(36 + samples.length * 2, 4); b.write('WAVE', 8)
|
|
b.write('fmt ', 12); b.writeUInt32LE(16, 16); b.writeUInt16LE(1, 20); b.writeUInt16LE(1, 22)
|
|
b.writeUInt32LE(RATE, 24); b.writeUInt32LE(RATE * 2, 28); b.writeUInt16LE(2, 32); b.writeUInt16LE(16, 34)
|
|
b.write('data', 36); b.writeUInt32LE(samples.length * 2, 40)
|
|
samples.forEach((v, i) => b.writeInt16LE(Math.round(Math.max(-1, Math.min(1, v)) * 32767), 44 + i * 2))
|
|
return b
|
|
}
|
|
|
|
const sounds = {
|
|
move: tone(0.035, 2400, 2200, { gain: 0.18, harm: 0 }), // petit tic
|
|
select: mix(tone(0.07, 660, 680, { gain: 0.32 }), tone(0.09, 990, 1000, { gain: 0.28, delay: 0.045 })), // deux notes qui montent
|
|
back: mix(tone(0.07, 740, 720, { gain: 0.28 }), tone(0.09, 494, 480, { gain: 0.26, delay: 0.045 })), // deux notes qui descendent
|
|
error: mix(tone(0.08, 180, 170, { gain: 0.35, harm: 0.4 }), tone(0.08, 180, 170, { gain: 0.35, harm: 0.4, delay: 0.11 })),
|
|
launch: mix(tone(0.45, 330, 1320, { gain: 0.3, attack: 0.02 }), tone(0.5, 495, 1980, { gain: 0.12, attack: 0.05, delay: 0.06 }))
|
|
}
|
|
|
|
mkdirSync(out, { recursive: true })
|
|
for (const [name, s] of Object.entries(sounds)) writeFileSync(join(out, name + '.wav'), wav(s))
|
|
console.log('sons écrits dans', out)
|