Skip to content

Commit

Permalink
moved mass and force sound generators from basics to full version, see
Browse files Browse the repository at this point in the history
  • Loading branch information
jbphet committed Oct 28, 2019
1 parent ccc9fbc commit 54419ea
Show file tree
Hide file tree
Showing 6 changed files with 229 additions and 0 deletions.
131 changes: 131 additions & 0 deletions js/gravity-force-lab/view/ForceSoundGenerator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// Copyright 2019, University of Colorado Boulder

/**
* sound generator for changes to the force between two masses
*
* @author John Blanco (PhET Interactive Simulations)
*/
define( require => {
'use strict';

// modules
const gravityForceLab = require( 'GRAVITY_FORCE_LAB/gravityForceLab' );
const merge = require( 'PHET_CORE/merge' );
const SoundClip = require( 'TAMBO/sound-generators/SoundClip' );

// constants
const FADE_START_DELAY = 0.2; // in seconds, time to wait before starting fade
const FADE_TIME = 0.15; // in seconds, duration of fade out
const DELAY_BEFORE_STOP = 0.1; // in seconds, amount of time from full fade to stop of sound, done to avoid glitches
const PITCH_RANGE_IN_SEMI_TONES = 36;
const PITCH_CENTER_OFFSET = 2;

// sounds

// The saturated sine loop is precisely optimized for good looping, which is why it is a .wav and not a .mp3 file
const forceSound = require( 'sound!GRAVITY_FORCE_LAB/saturated-sine-loop-trimmed.wav' );

class ForceSoundGenerator extends SoundClip {

/**
* @param {GFLBModel} model
* @param {BooleanProperty} resetInProgressProperty
* @param {Object} [options]
* @constructor
*/
constructor( model, options ) {

options = merge( {
initialOutputLevel: 0.7,
loop: true,
trimSilence: false
}, options );

// options checking
assert && assert( !options || !options.loop || options.loop === true, 'must be a loop to work correctly' );

super( forceSound, options );

// @private {number} - the output level before fade out starts
this.nonFadedOutputLevel = options.initialOutputLevel;

// @private {number} - countdown time used for fade out
this.fadeCountdownTime = 0;

// start with the output level at zero so that the initial sound generation has a bit of fade in
this.setOutputLevel( 0, 0 );

// function for starting the force sound or adjusting the volume
const forceListener = force => {

if ( !model.resetInProgressProperty.value ) {

// calculate the playback rate based on the amount of force, see the design document for detailed explanation
const normalizedForce = Math.log( force / model.getMinForce() ) / Math.log( model.getMaxForce() / model.getMinForce() );
const centerForce = normalizedForce - 0.5;
const midiNote = PITCH_RANGE_IN_SEMI_TONES / 2 * centerForce + PITCH_CENTER_OFFSET;
const playbackRate = Math.pow( 2, midiNote / 12 );

this.setPlaybackRate( playbackRate );
this.setOutputLevel( this.nonFadedOutputLevel );
if ( !this.playing ) {
this.play();
}

// reset the fade countdown
this.fadeCountdownTime = FADE_START_DELAY + FADE_TIME + DELAY_BEFORE_STOP;
}
};
model.forceProperty.lazyLink( forceListener );

// @private {function}
this.disposeForceSoundGenerator = () => { model.forceProperty.unlink( forceListener ); };
}

/**
* @public
*/
dispose() {
this.disposeForceSoundGenerator();
super.dispose();
}

/**
* step this sound generator, used for fading out the sound in the absence of user interaction
* @param dt
*/
step( dt ) {
if ( this.fadeCountdownTime > 0 ) {
this.fadeCountdownTime = Math.max( this.fadeCountdownTime - dt, 0 );

if ( this.fadeCountdownTime < FADE_TIME + DELAY_BEFORE_STOP && this.outputLevel > 0 ) {

// the sound is fading out, adjust the output level
const outputLevel = Math.max(
( this.fadeCountdownTime - DELAY_BEFORE_STOP ) / FADE_TIME * this.nonFadedOutputLevel,
0
);
this.setOutputLevel( outputLevel );
}

// fade out complete, stop playback
if ( this.fadeCountdownTime === 0 && this.isPlaying ) {
this.stop( 0 );
}
}
}

/**
* stop any in-progress sound generation
* @public
*/
reset() {
this.stop();
this.fadeCountdownTime = 0;
}
}

gravityForceLab.register( 'ForceSoundGenerator', ForceSoundGenerator );

return ForceSoundGenerator;
} );
72 changes: 72 additions & 0 deletions js/gravity-force-lab/view/MassSoundGenerator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright 2019, University of Colorado Boulder

/**
* sound generator for mass changes
*
* @author John Blanco (PhET Interactive Simulations)
*/
define( require => {
'use strict';

// modules
const SoundClip = require( 'TAMBO/sound-generators/SoundClip' );
const gravityForceLab = require( 'GRAVITY_FORCE_LAB/gravityForceLab' );

// sounds
const massSound = require( 'sound!GRAVITY_FORCE_LAB/rubber-band-v3.mp3' );

// constants
const PITCH_RANGE_IN_SEMI_TONES = 30;

class MassSoundGenerator extends SoundClip {

/**
* @param {NumberProperty} massProperty
* @param {Range} massRange
* @param {BooleanProperty} resetInProgressProperty
* @param {Object} [options]
* @constructor
*/
constructor( massProperty, massRange, resetInProgressProperty, options ) {

// Rate changes should never affect the mass sound that is already playing.
options.rateChangesAffectPlayingSounds = false;

super( massSound, options );

// function for playing the mass sound
const massListener = mass => {

// range checking
assert && assert( massRange.contains( mass ), 'mass value out of range' );

if ( !resetInProgressProperty.value ) {

// convert the mass to a playback rate, see the design document for an explanation
const normalizedMass = ( mass - massRange.min ) / ( massRange.max - massRange.min );
const centerAndFlippedNormMass = ( ( 1 - normalizedMass ) - 0.5 );
const midiNote = PITCH_RANGE_IN_SEMI_TONES / 2 * centerAndFlippedNormMass;
const playbackSpeed = Math.pow( 2, midiNote / 12 );
this.setPlaybackRate( playbackSpeed );
this.play();
}
};
massProperty.lazyLink( massListener );

// @private {function}
this.disposeMassSoundGenerator = () => { massProperty.unlink( massListener ); };
}

/**
* @public
*/
dispose() {
this.disposeMassSoundGenerator();
super.dispose();
}
}

gravityForceLab.register( 'MassSoundGenerator', MassSoundGenerator );

return MassSoundGenerator;
} );
26 changes: 26 additions & 0 deletions sounds/license.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"rubber-band-v3.mp3": {
"text": [
"Copyright 2002-2019 University of Colorado Boulder"
],
"projectURL": "http://phet.colorado.edu",
"license": "contact [email protected]",
"notes": "created by Ashton Morris (PhET Interactive Simulations)"
},
"saturated-sine-loop-trimmed.wav": {
"text": [
"Copyright 2002-2019 University of Colorado Boulder"
],
"projectURL": "http://phet.colorado.edu",
"license": "contact [email protected]",
"notes": "created by Ashton Morris (PhET Interactive Simulations), edited by John Blanco"
},
"scrunched-mass-collision-sonic-womp.mp3": {
"text": [
"Copyright 2002-2019 University of Colorado Boulder"
],
"projectURL": "http://phet.colorado.edu",
"license": "contact [email protected]",
"notes": "created by Ashton Morris (PhET Interactive Simulations)"
}
}
Binary file added sounds/rubber-band-v3.mp3
Binary file not shown.
Binary file added sounds/saturated-sine-loop-trimmed.wav
Binary file not shown.
Binary file added sounds/scrunched-mass-collision-sonic-womp.mp3
Binary file not shown.

0 comments on commit 54419ea

Please sign in to comment.