Improve level-up animation and unit audio assets
This commit is contained in:
78
scripts/generate-sfx.mjs
Normal file
78
scripts/generate-sfx.mjs
Normal file
@@ -0,0 +1,78 @@
|
||||
import { mkdir, writeFile } from 'node:fs/promises';
|
||||
import { dirname, join } from 'node:path';
|
||||
|
||||
const sampleRate = 44100;
|
||||
const outputPath = join('src', 'assets', 'audio', 'sfx', 'level-up.wav');
|
||||
|
||||
function envelope(time, start, duration) {
|
||||
const local = time - start;
|
||||
if (local < 0 || local > duration) {
|
||||
return 0;
|
||||
}
|
||||
const attack = 0.018;
|
||||
const release = 0.22;
|
||||
if (local < attack) {
|
||||
return local / attack;
|
||||
}
|
||||
if (local > duration - release) {
|
||||
return Math.max(0, (duration - local) / release);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
function tone(time, frequency, start, duration, gain, wave = 'sine') {
|
||||
const amp = envelope(time, start, duration) * gain;
|
||||
if (amp <= 0) {
|
||||
return 0;
|
||||
}
|
||||
const phase = Math.PI * 2 * frequency * (time - start);
|
||||
if (wave === 'triangle') {
|
||||
return amp * (2 / Math.PI) * Math.asin(Math.sin(phase));
|
||||
}
|
||||
return amp * Math.sin(phase);
|
||||
}
|
||||
|
||||
function sampleAt(time) {
|
||||
const notes = [
|
||||
{ start: 0, frequency: 523.25, gain: 0.22, duration: 0.52 },
|
||||
{ start: 0.09, frequency: 659.25, gain: 0.2, duration: 0.56 },
|
||||
{ start: 0.18, frequency: 783.99, gain: 0.18, duration: 0.62 },
|
||||
{ start: 0.31, frequency: 1046.5, gain: 0.16, duration: 0.7 }
|
||||
];
|
||||
|
||||
const chord = notes.reduce((sum, note) => sum + tone(time, note.frequency, note.start, note.duration, note.gain), 0);
|
||||
const shimmer =
|
||||
tone(time, 1318.51, 0.42, 0.36, 0.055, 'triangle') +
|
||||
tone(time, 1567.98, 0.52, 0.28, 0.042, 'triangle');
|
||||
return Math.max(-0.92, Math.min(0.92, chord + shimmer));
|
||||
}
|
||||
|
||||
function wavBuffer(samples) {
|
||||
const bytesPerSample = 2;
|
||||
const dataSize = samples.length * bytesPerSample;
|
||||
const buffer = Buffer.alloc(44 + dataSize);
|
||||
buffer.write('RIFF', 0);
|
||||
buffer.writeUInt32LE(36 + dataSize, 4);
|
||||
buffer.write('WAVE', 8);
|
||||
buffer.write('fmt ', 12);
|
||||
buffer.writeUInt32LE(16, 16);
|
||||
buffer.writeUInt16LE(1, 20);
|
||||
buffer.writeUInt16LE(1, 22);
|
||||
buffer.writeUInt32LE(sampleRate, 24);
|
||||
buffer.writeUInt32LE(sampleRate * bytesPerSample, 28);
|
||||
buffer.writeUInt16LE(bytesPerSample, 32);
|
||||
buffer.writeUInt16LE(16, 34);
|
||||
buffer.write('data', 36);
|
||||
buffer.writeUInt32LE(dataSize, 40);
|
||||
|
||||
samples.forEach((sample, index) => {
|
||||
buffer.writeInt16LE(Math.round(sample * 32767), 44 + index * bytesPerSample);
|
||||
});
|
||||
return buffer;
|
||||
}
|
||||
|
||||
await mkdir(dirname(outputPath), { recursive: true });
|
||||
const duration = 1.12;
|
||||
const samples = Array.from({ length: Math.floor(sampleRate * duration) }, (_, index) => sampleAt(index / sampleRate));
|
||||
await writeFile(outputPath, wavBuffer(samples));
|
||||
console.log(`Generated ${outputPath}`);
|
||||
@@ -101,7 +101,15 @@ try {
|
||||
'unit-rebel-actions',
|
||||
'unit-rebel-archer-actions',
|
||||
'unit-rebel-cavalry-actions',
|
||||
'unit-rebel-leader-actions'
|
||||
'unit-rebel-leader-actions',
|
||||
'unit-shu-cavalry',
|
||||
'unit-shu-cavalry-actions',
|
||||
'unit-wei-infantry',
|
||||
'unit-wei-infantry-actions',
|
||||
'unit-wu-archer',
|
||||
'unit-wu-archer-actions',
|
||||
'unit-nanman-officer',
|
||||
'unit-nanman-officer-actions'
|
||||
].every((key) => textures?.exists(key));
|
||||
});
|
||||
if (!actionTexturesLoaded) {
|
||||
|
||||
Reference in New Issue
Block a user