diff --git a/src/OceanScene.js b/src/OceanScene.js
index cdbcc83..34c14a3 100644
--- a/src/OceanScene.js
+++ b/src/OceanScene.js
@@ -41,7 +41,11 @@ export class OceanScene {
this.fogLayers = [];
this.horizonFog = null;
this.skyHazeBand = null;
- this.rainAudio = null;
+ this.rainAudioPool = [];
+ this.rainAudioActiveIndex = 0;
+ this.rainAudioIsPlaying = false;
+ this.rainAudioCrossfading = false;
+ this.rainAudioCrossfadeDuration = 1.6;
this.thunderAudioPool = [];
this.thunderAudioIndex = 0;
this.scheduledThunder = [];
@@ -66,9 +70,9 @@ export class OceanScene {
rainVeilIntensity: 1.15,
rainDropSize: 1.0,
rainSpeed: 1.0,
- rainAudioEnabled: false,
+ rainAudioEnabled: true,
rainAudioVolume: 0.35,
- lightningEnabled: false,
+ lightningEnabled: true,
lightningIntensity: 0.75,
mieCoefficient: 0.005,
mieDirectionalG: 0.8
@@ -191,11 +195,14 @@ export class OceanScene {
}
initAudio() {
- this.rainAudio = new Audio(RAIN_AUDIO_URL);
- this.rainAudio.loop = true;
- this.rainAudio.preload = 'auto';
- this.rainAudio.volume = this.params.rainAudioVolume;
- this.rainAudio.crossOrigin = 'anonymous';
+ this.rainAudioPool = Array.from({ length: 2 }, () => {
+ const audio = new Audio(RAIN_AUDIO_URL);
+ audio.loop = false;
+ audio.preload = 'auto';
+ audio.volume = 0;
+ audio.crossOrigin = 'anonymous';
+ return audio;
+ });
this.thunderAudioPool = Array.from({ length: 3 }, () => {
const audio = new Audio(THUNDER_AUDIO_URL);
@@ -1054,6 +1061,16 @@ export class OceanScene {
this.rainPass.enabled = value;
}
this.updateRainAudioState();
+ if (!value) {
+ this.lightningFlash = 0;
+ this.lightningLocalFlash = 0;
+ this.lightningBurstEnd = 0;
+ this.nextLightningAt = 0;
+ this.lightningPulseSchedule = [];
+ this.scheduledThunder = [];
+ this.applyLightningState(0);
+ this.stopThunderAudio();
+ }
}
setRainScreenIntensity(value) {
@@ -1091,25 +1108,72 @@ export class OceanScene {
setRainAudioVolume(value) {
this.params.rainAudioVolume = value;
- if (this.rainAudio) {
- this.rainAudio.volume = value;
- }
this.updateRainAudioState();
}
updateRainAudioState() {
- if (!this.rainAudio) return;
+ if (this.rainAudioPool.length === 0) return;
const shouldPlay = this.params.rainEnabled && this.params.rainAudioEnabled && this.params.rainAudioVolume > 0.001;
if (shouldPlay) {
- this.rainAudio.volume = this.params.rainAudioVolume;
- const playPromise = this.rainAudio.play();
+ if (!this.rainAudioIsPlaying) {
+ const active = this.rainAudioPool[this.rainAudioActiveIndex];
+ active.currentTime = 0;
+ active.volume = this.params.rainAudioVolume;
+ const playPromise = active.play();
+ if (playPromise?.catch) {
+ playPromise.catch(() => {});
+ }
+ this.rainAudioIsPlaying = true;
+ this.rainAudioCrossfading = false;
+ }
+ } else {
+ for (const audio of this.rainAudioPool) {
+ audio.pause();
+ audio.currentTime = 0;
+ audio.volume = 0;
+ }
+ this.rainAudioIsPlaying = false;
+ this.rainAudioCrossfading = false;
+ }
+ }
+
+ updateRainAudioLoop() {
+ if (!this.rainAudioIsPlaying || this.rainAudioPool.length < 2) return;
+
+ const active = this.rainAudioPool[this.rainAudioActiveIndex];
+ const next = this.rainAudioPool[(this.rainAudioActiveIndex + 1) % this.rainAudioPool.length];
+ const duration = Number.isFinite(active.duration) ? active.duration : 0;
+ if (duration <= 0) {
+ active.volume = this.params.rainAudioVolume;
+ return;
+ }
+
+ const timeLeft = duration - active.currentTime;
+ if (!this.rainAudioCrossfading && timeLeft <= this.rainAudioCrossfadeDuration) {
+ next.currentTime = 0;
+ next.volume = 0;
+ const playPromise = next.play();
if (playPromise?.catch) {
playPromise.catch(() => {});
}
+ this.rainAudioCrossfading = true;
+ }
+
+ if (this.rainAudioCrossfading) {
+ const progress = THREE.MathUtils.clamp(1.0 - timeLeft / this.rainAudioCrossfadeDuration, 0, 1);
+ active.volume = this.params.rainAudioVolume * (1.0 - progress);
+ next.volume = this.params.rainAudioVolume * progress;
+
+ if (progress >= 0.999 || active.ended) {
+ active.pause();
+ active.currentTime = 0;
+ active.volume = 0;
+ this.rainAudioActiveIndex = (this.rainAudioActiveIndex + 1) % this.rainAudioPool.length;
+ this.rainAudioCrossfading = false;
+ }
} else {
- this.rainAudio.pause();
- this.rainAudio.currentTime = 0;
+ active.volume = this.params.rainAudioVolume;
}
}
@@ -1193,7 +1257,7 @@ export class OceanScene {
}
playThunder(volume, playbackRate) {
- if (!this.params.lightningEnabled || this.thunderAudioPool.length === 0) return;
+ if (!this.params.rainEnabled || !this.params.lightningEnabled || this.thunderAudioPool.length === 0) return;
const audio = this.thunderAudioPool[this.thunderAudioIndex % this.thunderAudioPool.length];
this.thunderAudioIndex += 1;
@@ -1208,7 +1272,7 @@ export class OceanScene {
}
updateThunder(time) {
- if (!this.params.lightningEnabled || this.scheduledThunder.length === 0) return;
+ if (!this.params.rainEnabled || !this.params.lightningEnabled || this.scheduledThunder.length === 0) return;
const pending = [];
for (const thunder of this.scheduledThunder) {
@@ -1222,7 +1286,7 @@ export class OceanScene {
}
updateLightning(time) {
- if (!this.params.lightningEnabled) return;
+ if (!this.params.rainEnabled || !this.params.lightningEnabled) return;
if (this.nextLightningAt === 0) {
this.scheduleNextLightning(time);
@@ -1361,6 +1425,7 @@ export class OceanScene {
const time = this.clock.getElapsedTime();
this.updateLightning(time);
this.updateThunder(time);
+ this.updateRainAudioLoop();
if (this.water) {
this.water.material.uniforms['time'].value += 1.0 / 60.0;