Add resonance and result sound effects
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
import { mkdir, writeFile } from 'node:fs/promises';
|
import { mkdir, writeFile } from 'node:fs/promises';
|
||||||
import { dirname, join } from 'node:path';
|
import { join } from 'node:path';
|
||||||
|
|
||||||
const sampleRate = 44100;
|
const sampleRate = 44100;
|
||||||
const outputPath = join('src', 'assets', 'audio', 'sfx', 'level-up.wav');
|
const outputDir = join('src', 'assets', 'audio', 'sfx');
|
||||||
|
|
||||||
function envelope(time, start, duration) {
|
function envelope(time, start, duration) {
|
||||||
const local = time - start;
|
const local = time - start;
|
||||||
@@ -10,7 +10,7 @@ function envelope(time, start, duration) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
const attack = 0.018;
|
const attack = 0.018;
|
||||||
const release = 0.22;
|
const release = Math.min(0.24, duration * 0.42);
|
||||||
if (local < attack) {
|
if (local < attack) {
|
||||||
return local / attack;
|
return local / attack;
|
||||||
}
|
}
|
||||||
@@ -29,10 +29,17 @@ function tone(time, frequency, start, duration, gain, wave = 'sine') {
|
|||||||
if (wave === 'triangle') {
|
if (wave === 'triangle') {
|
||||||
return amp * (2 / Math.PI) * Math.asin(Math.sin(phase));
|
return amp * (2 / Math.PI) * Math.asin(Math.sin(phase));
|
||||||
}
|
}
|
||||||
|
if (wave === 'square') {
|
||||||
|
return amp * Math.sign(Math.sin(phase));
|
||||||
|
}
|
||||||
return amp * Math.sin(phase);
|
return amp * Math.sin(phase);
|
||||||
}
|
}
|
||||||
|
|
||||||
function sampleAt(time) {
|
function clamp(sample) {
|
||||||
|
return Math.max(-0.92, Math.min(0.92, sample));
|
||||||
|
}
|
||||||
|
|
||||||
|
function levelUpSample(time) {
|
||||||
const notes = [
|
const notes = [
|
||||||
{ start: 0, frequency: 523.25, gain: 0.22, duration: 0.52 },
|
{ 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.09, frequency: 659.25, gain: 0.2, duration: 0.56 },
|
||||||
@@ -44,7 +51,44 @@ function sampleAt(time) {
|
|||||||
const shimmer =
|
const shimmer =
|
||||||
tone(time, 1318.51, 0.42, 0.36, 0.055, 'triangle') +
|
tone(time, 1318.51, 0.42, 0.36, 0.055, 'triangle') +
|
||||||
tone(time, 1567.98, 0.52, 0.28, 0.042, 'triangle');
|
tone(time, 1567.98, 0.52, 0.28, 0.042, 'triangle');
|
||||||
return Math.max(-0.92, Math.min(0.92, chord + shimmer));
|
return clamp(chord + shimmer);
|
||||||
|
}
|
||||||
|
|
||||||
|
function bondResonanceSample(time) {
|
||||||
|
const pulse =
|
||||||
|
tone(time, 392, 0, 0.34, 0.1, 'triangle') +
|
||||||
|
tone(time, 587.33, 0.04, 0.42, 0.13, 'triangle') +
|
||||||
|
tone(time, 783.99, 0.1, 0.42, 0.11, 'sine') +
|
||||||
|
tone(time, 1174.66, 0.2, 0.38, 0.065, 'sine');
|
||||||
|
const sparkle =
|
||||||
|
tone(time, 1567.98, 0.34, 0.24, 0.045, 'triangle') +
|
||||||
|
tone(time, 1760, 0.42, 0.2, 0.035, 'triangle');
|
||||||
|
const tremolo = 1 + Math.sin(time * Math.PI * 18) * 0.08;
|
||||||
|
return clamp((pulse + sparkle) * tremolo);
|
||||||
|
}
|
||||||
|
|
||||||
|
function victoryFanfareSample(time) {
|
||||||
|
const brass =
|
||||||
|
tone(time, 392, 0, 0.26, 0.14, 'triangle') +
|
||||||
|
tone(time, 493.88, 0.16, 0.26, 0.14, 'triangle') +
|
||||||
|
tone(time, 587.33, 0.32, 0.28, 0.14, 'triangle') +
|
||||||
|
tone(time, 783.99, 0.5, 0.58, 0.16, 'triangle');
|
||||||
|
const harmony =
|
||||||
|
tone(time, 196, 0, 0.92, 0.07, 'sine') +
|
||||||
|
tone(time, 293.66, 0.5, 0.64, 0.06, 'sine') +
|
||||||
|
tone(time, 987.77, 0.72, 0.34, 0.055, 'sine');
|
||||||
|
return clamp(brass + harmony);
|
||||||
|
}
|
||||||
|
|
||||||
|
function defeatStingSample(time) {
|
||||||
|
const low =
|
||||||
|
tone(time, 174.61, 0, 0.46, 0.16, 'triangle') +
|
||||||
|
tone(time, 146.83, 0.24, 0.52, 0.15, 'triangle') +
|
||||||
|
tone(time, 110, 0.48, 0.56, 0.15, 'sine');
|
||||||
|
const strike =
|
||||||
|
tone(time, 87.31, 0, 0.18, 0.13, 'square') +
|
||||||
|
tone(time, 261.63, 0.02, 0.16, 0.06, 'triangle');
|
||||||
|
return clamp(low + strike);
|
||||||
}
|
}
|
||||||
|
|
||||||
function wavBuffer(samples) {
|
function wavBuffer(samples) {
|
||||||
@@ -71,8 +115,17 @@ function wavBuffer(samples) {
|
|||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
await mkdir(dirname(outputPath), { recursive: true });
|
const clips = [
|
||||||
const duration = 1.12;
|
{ name: 'level-up', duration: 1.12, sampleAt: levelUpSample },
|
||||||
const samples = Array.from({ length: Math.floor(sampleRate * duration) }, (_, index) => sampleAt(index / sampleRate));
|
{ name: 'bond-resonance', duration: 0.82, sampleAt: bondResonanceSample },
|
||||||
await writeFile(outputPath, wavBuffer(samples));
|
{ name: 'victory-fanfare', duration: 1.2, sampleAt: victoryFanfareSample },
|
||||||
console.log(`Generated ${outputPath}`);
|
{ name: 'defeat-sting', duration: 1.08, sampleAt: defeatStingSample }
|
||||||
|
];
|
||||||
|
|
||||||
|
await mkdir(outputDir, { recursive: true });
|
||||||
|
for (const clip of clips) {
|
||||||
|
const samples = Array.from({ length: Math.floor(sampleRate * clip.duration) }, (_, index) => clip.sampleAt(index / sampleRate));
|
||||||
|
const outputPath = join(outputDir, `${clip.name}.wav`);
|
||||||
|
await writeFile(outputPath, wavBuffer(samples));
|
||||||
|
console.log(`Generated ${outputPath}`);
|
||||||
|
}
|
||||||
|
|||||||
BIN
src/assets/audio/sfx/bond-resonance.wav
Normal file
BIN
src/assets/audio/sfx/bond-resonance.wav
Normal file
Binary file not shown.
BIN
src/assets/audio/sfx/defeat-sting.wav
Normal file
BIN
src/assets/audio/sfx/defeat-sting.wav
Normal file
Binary file not shown.
Binary file not shown.
BIN
src/assets/audio/sfx/victory-fanfare.wav
Normal file
BIN
src/assets/audio/sfx/victory-fanfare.wav
Normal file
Binary file not shown.
@@ -3,8 +3,10 @@ import militiaThemeUrl from '../../assets/audio/bgm/militia-theme.mp3';
|
|||||||
import oathThemeUrl from '../../assets/audio/bgm/oath-theme.mp3';
|
import oathThemeUrl from '../../assets/audio/bgm/oath-theme.mp3';
|
||||||
import storyDarkUrl from '../../assets/audio/bgm/story-dark.mp3';
|
import storyDarkUrl from '../../assets/audio/bgm/story-dark.mp3';
|
||||||
import titleThemeUrl from '../../assets/audio/bgm/title-theme.mp3';
|
import titleThemeUrl from '../../assets/audio/bgm/title-theme.mp3';
|
||||||
|
import bondResonanceUrl from '../../assets/audio/sfx/bond-resonance.wav';
|
||||||
import cartRollUrl from '../../assets/audio/sfx/cart-roll.mp3';
|
import cartRollUrl from '../../assets/audio/sfx/cart-roll.mp3';
|
||||||
import combatImpactUrl from '../../assets/audio/sfx/combat-impact.mp3';
|
import combatImpactUrl from '../../assets/audio/sfx/combat-impact.mp3';
|
||||||
|
import defeatStingUrl from '../../assets/audio/sfx/defeat-sting.wav';
|
||||||
import expGainUrl from '../../assets/audio/sfx/exp-gain.mp3';
|
import expGainUrl from '../../assets/audio/sfx/exp-gain.mp3';
|
||||||
import footstepWalkUrl from '../../assets/audio/sfx/footstep-walk.mp3';
|
import footstepWalkUrl from '../../assets/audio/sfx/footstep-walk.mp3';
|
||||||
import horseGallopUrl from '../../assets/audio/sfx/horse-gallop.mp3';
|
import horseGallopUrl from '../../assets/audio/sfx/horse-gallop.mp3';
|
||||||
@@ -12,6 +14,7 @@ import levelUpUrl from '../../assets/audio/sfx/level-up.wav';
|
|||||||
import strategyCastUrl from '../../assets/audio/sfx/strategy-cast.mp3';
|
import strategyCastUrl from '../../assets/audio/sfx/strategy-cast.mp3';
|
||||||
import swordSlashUrl from '../../assets/audio/sfx/sword-slash.mp3';
|
import swordSlashUrl from '../../assets/audio/sfx/sword-slash.mp3';
|
||||||
import uiSelectUrl from '../../assets/audio/sfx/ui-select.mp3';
|
import uiSelectUrl from '../../assets/audio/sfx/ui-select.mp3';
|
||||||
|
import victoryFanfareUrl from '../../assets/audio/sfx/victory-fanfare.wav';
|
||||||
|
|
||||||
export const musicTracks = {
|
export const musicTracks = {
|
||||||
'title-theme': titleThemeUrl,
|
'title-theme': titleThemeUrl,
|
||||||
@@ -29,6 +32,9 @@ export const effectTracks = {
|
|||||||
'cart-roll': cartRollUrl,
|
'cart-roll': cartRollUrl,
|
||||||
'exp-gain': expGainUrl,
|
'exp-gain': expGainUrl,
|
||||||
'level-up': levelUpUrl,
|
'level-up': levelUpUrl,
|
||||||
|
'bond-resonance': bondResonanceUrl,
|
||||||
|
'victory-fanfare': victoryFanfareUrl,
|
||||||
|
'defeat-sting': defeatStingUrl,
|
||||||
'footstep-walk': footstepWalkUrl,
|
'footstep-walk': footstepWalkUrl,
|
||||||
'horse-gallop': horseGallopUrl
|
'horse-gallop': horseGallopUrl
|
||||||
} as const;
|
} as const;
|
||||||
|
|||||||
@@ -4340,8 +4340,8 @@ export class BattleScene extends Phaser.Scene {
|
|||||||
: `패배... ${battleScenario.defeatConditionLabel} 조건이 발생했습니다.`;
|
: `패배... ${battleScenario.defeatConditionLabel} 조건이 발생했습니다.`;
|
||||||
this.pushBattleLog(message);
|
this.pushBattleLog(message);
|
||||||
this.renderSituationPanel(message);
|
this.renderSituationPanel(message);
|
||||||
soundDirector.playEffect(outcome === 'victory' ? 'exp-gain' : 'combat-impact', {
|
soundDirector.playEffect(outcome === 'victory' ? 'victory-fanfare' : 'defeat-sting', {
|
||||||
volume: outcome === 'victory' ? 0.38 : 0.28,
|
volume: outcome === 'victory' ? 0.34 : 0.26,
|
||||||
stopAfterMs: 1200
|
stopAfterMs: 1200
|
||||||
});
|
});
|
||||||
this.publishFirstBattleReport(outcome);
|
this.publishFirstBattleReport(outcome);
|
||||||
@@ -7335,7 +7335,7 @@ export class BattleScene extends Phaser.Scene {
|
|||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
soundDirector.playEffect('strategy-cast', { volume: 0.18, rate: 1.14, stopAfterMs: 520 });
|
soundDirector.playEffect('bond-resonance', { volume: 0.22, rate: 1.04, stopAfterMs: 620 });
|
||||||
|
|
||||||
const objects: Phaser.GameObjects.GameObject[] = [];
|
const objects: Phaser.GameObjects.GameObject[] = [];
|
||||||
const line = this.add.graphics();
|
const line = this.add.graphics();
|
||||||
@@ -7385,7 +7385,7 @@ export class BattleScene extends Phaser.Scene {
|
|||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
soundDirector.playEffect('strategy-cast', { volume: 0.22, rate: 1.08, stopAfterMs: 720 });
|
soundDirector.playEffect('bond-resonance', { volume: 0.24, rate: 1, stopAfterMs: 760 });
|
||||||
|
|
||||||
const aura = this.trackCombatObject(this.add.circle(auraX, auraY, 42));
|
const aura = this.trackCombatObject(this.add.circle(auraX, auraY, 42));
|
||||||
aura.setStrokeStyle(4, 0xffdf7b, 0.88);
|
aura.setStrokeStyle(4, 0xffdf7b, 0.88);
|
||||||
|
|||||||
Reference in New Issue
Block a user