From 4fe46e4cd0ef865acc144b18b3d228855cc49bf1 Mon Sep 17 00:00:00 2001 From: jbphet Date: Tue, 4 Oct 2022 15:28:27 -0600 Subject: [PATCH] improve sound generation, see https://github.com/phetsims/greenhouse-effect/issues/185 --- js/common/view/FluxMeterNode.ts | 1 + js/common/view/FluxMeterSoundGenerator.ts | 72 +++++++++++++++-------- 2 files changed, 49 insertions(+), 24 deletions(-) diff --git a/js/common/view/FluxMeterNode.ts b/js/common/view/FluxMeterNode.ts index e5c3431d..b1cb2f0f 100644 --- a/js/common/view/FluxMeterNode.ts +++ b/js/common/view/FluxMeterNode.ts @@ -310,6 +310,7 @@ class FluxMeterNode extends Node { public reset(): void { this.wasDraggedProperty.reset(); this.zoomFactor.reset(); + this.soundGenerator.reset(); } } diff --git a/js/common/view/FluxMeterSoundGenerator.ts b/js/common/view/FluxMeterSoundGenerator.ts index 792c3b8f..2c8e9fbe 100644 --- a/js/common/view/FluxMeterSoundGenerator.ts +++ b/js/common/view/FluxMeterSoundGenerator.ts @@ -17,11 +17,16 @@ import greenhouseEffect from '../../greenhouseEffect.js'; // constants const DEFAULT_OUTPUT_LEVEL = 0.22; -// amount of time after a change before starting the fade out, in seconds -const PRE_FADE_TIME = 1; +// amount of time before starting to fade where flux change is below the threshold, in seconds +const PRE_FADE_TIME = 0.5; -// amount of time to fade from full volume to zero, in seconds -const FADE_TIME = 1; +// rate at which the sounds increase in volume, in proportion per second +const FADE_IN_RATE = 5; + +// rate at which sounds decrease in volume, in proportion per second +const FADE_OUT_RATE = 2; + +const FADE_OUT_TIME = 1 / FADE_OUT_RATE; // the rate of flux change considered necessary to produce sound, empirically determined const FLUX_CHANGE_RATE_THRESHOLD = 300000; @@ -60,7 +65,10 @@ class FluxMeterSoundGenerator extends SoundGenerator { this.irPreviousFluxUp = irFluxUpProperty.value; // Create the sound clip user for IR flux in the upward direction. - this.irUpFluxSoundClip = new SoundClip( irFluxUp_mp3, { rateChangesAffectPlayingSounds: false } ); + this.irUpFluxSoundClip = new SoundClip( irFluxUp_mp3, { + initialOutputLevel: 0, + loop: true + } ); this.irUpFluxSoundClip.connect( this.masterGainNode ); // Define the class-specific dispose function. @@ -78,40 +86,56 @@ class FluxMeterSoundGenerator extends SoundGenerator { const irFluxUpChangeRateThisStep = Math.abs( this.irPreviousFluxUp - this.irFluxUpProperty.value ) / dt; + // Adjust the countdown timer based on the rate of flux change. if ( irFluxUpChangeRateThisStep > FLUX_CHANGE_RATE_THRESHOLD ) { - - // The flux is changing at a rate high enough that the corresponding sound should be produced at full volume. - if ( !this.irUpFluxSoundClip.isPlaying ) { - this.irUpFluxSoundClip.play(); - } - this.irUpFluxSoundClip.outputLevel = 1; - this.irUpFluxChangedCountdownTimer = PRE_FADE_TIME + FADE_TIME; + this.irUpFluxChangedCountdownTimer = PRE_FADE_TIME + FADE_OUT_TIME; } else { - - // The change during this step is below the threshold, so fade the sound. this.irUpFluxChangedCountdownTimer = Math.max( this.irUpFluxChangedCountdownTimer - dt, 0 ); - if ( this.irUpFluxChangedCountdownTimer === 0 ) { + } + + // Adjust the output level of the IR flux up sound based on the corresponding countdown timer and the current output + // level. + let irUpFluxSoundOutputLevel = this.irUpFluxSoundClip.outputLevel; + if ( this.irUpFluxChangedCountdownTimer > FADE_OUT_TIME ) { - // It's time to stop this sound from playing. - this.irUpFluxSoundClip.outputLevel = 0; - this.irUpFluxSoundClip.stop(); - } - else if ( this.irUpFluxChangedCountdownTimer < PRE_FADE_TIME ) { + // Move towards full volume if not already there. + irUpFluxSoundOutputLevel = Math.min( irUpFluxSoundOutputLevel + FADE_IN_RATE * dt, 1 ); + } + else if ( this.irUpFluxChangedCountdownTimer > 0 ) { + irUpFluxSoundOutputLevel = Math.max( irUpFluxSoundOutputLevel - FADE_OUT_RATE * dt, 0 ); + } + else { + irUpFluxSoundOutputLevel = Math.max( irUpFluxSoundOutputLevel - 0.1 * dt, 0 ); + } - // Based on the countdown timer value, this sound is fading, so set the output level appropriately. - this.irUpFluxSoundClip.outputLevel = Math.max( this.irUpFluxChangedCountdownTimer - PRE_FADE_TIME / FADE_TIME, 0 ); - } + // Start or stop the sound clip if appropriate. + if ( irUpFluxSoundOutputLevel > 0 && !this.irUpFluxSoundClip.isPlaying ) { + this.irUpFluxSoundClip.play(); + } + else if ( irUpFluxSoundOutputLevel === 0 && this.irUpFluxSoundClip.isPlaying ) { + this.irUpFluxSoundClip.stop( 0.1 ); } + // Set the output level. + this.irUpFluxSoundClip.setOutputLevel( irUpFluxSoundOutputLevel ); + // Set the playback rate for the IR up sound. This happens regardless of whether it is playing. - const playbackRate = 1 + Math.min( this.irFluxUpProperty.value / MAX_EXPECTED_IR_FLUX, 1 ) * 2; + const playbackRate = 1 + Math.min( this.irFluxUpProperty.value / MAX_EXPECTED_IR_FLUX, 1 ) * 4; this.irUpFluxSoundClip.setPlaybackRate( playbackRate ); // Update the previous flux values for the next step. this.irPreviousFluxUp = this.irFluxUpProperty.value; } + public reset(): void { + this.irUpFluxChangedCountdownTimer = 0; + this.irUpFluxSoundClip.outputLevel = 0; + this.irUpFluxSoundClip.stop( 0.1 ); + this.irUpFluxSoundClip.setPlaybackRate( 1 ); + this.irPreviousFluxUp = 0; + } + public override dispose(): void { this.disposeFluxMeterSoundGenerator(); super.dispose();