Skip to content

Commit

Permalink
changed SoundPlayer to ISoundPlayer, other TS cleanup, see #160
Browse files Browse the repository at this point in the history
  • Loading branch information
jbphet committed Apr 15, 2022
1 parent 456d8b7 commit c8f6549
Show file tree
Hide file tree
Showing 12 changed files with 75 additions and 80 deletions.
2 changes: 1 addition & 1 deletion js/AmplitudeModulator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ class AmplitudeModulator extends EnabledComponent {
};
this.enabledProperty.link( enabledListener );

const depthListener = ( depth: any ) => {
const depthListener = ( depth: number ) => {
if ( lowFrequencyOscillator ) {
lfoAttenuator.gain.setValueAtTime( depth / 2, phetAudioContext.currentTime );
this.modulatedGainNode.gain.setValueAtTime( 1 - depth / 2, phetAudioContext.currentTime );
Expand Down
11 changes: 5 additions & 6 deletions js/ISoundPlayer.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
// Copyright 2022, University of Colorado Boulder

/**
* SoundPlayer is a "definition" type, initially based off of PaintDef.js, that defines a type that is used in the tambo
* sound library but is not actually a base class. This is similar to the idea of an "interface" in Java. A
* SoundPlayer type is a sound generator that has just the most basic methods for playing a sound.
* ISoundPlayer defines a simple interface that can be used to support polymorphism when defining options and other
* API interfaces that include sound generation.
*
* @author John Blanco (PhET Interactive Simulations)
*/

type SoundPlayer = {
interface ISoundPlayer {
play: () => void;
stop: () => void;
};
}

export default SoundPlayer;
export default ISoundPlayer;
4 changes: 2 additions & 2 deletions js/PeakDetectorAudioNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
* @author John Blanco (PhET Interactive Simulations)
*/

import merge from '../../phet-core/js/merge.js';
import optionize from '../../phet-core/js/optionize.js';
import phetAudioContext from './phetAudioContext.js';
import tambo from './tambo.js';

Expand All @@ -37,7 +37,7 @@ class PeakDetectorAudioNode extends AudioWorkletNode {

constructor( providedOptions: PeakDetectorAudioNodeOptions ) {

const options = merge( {
const options = optionize<PeakDetectorAudioNodeOptions, PeakDetectorAudioNodeOptions>( {
logZeroValues: false
}, providedOptions );

Expand Down
34 changes: 0 additions & 34 deletions js/SoundPlayer.js

This file was deleted.

7 changes: 3 additions & 4 deletions js/demo/testing/view/CompositeSoundClipTestNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ import { VBox, VBoxOptions } from '../../../../../scenery/js/imports.js';
import TextPushButton from '../../../../../sun/js/buttons/TextPushButton.js';
import brightMarimba_mp3 from '../../../../sounds/brightMarimba_mp3.js';
import loonCall_mp3 from '../../../../sounds/demo-and-test/loonCall_mp3.js';
import SoundPlayer from '../../../SoundPlayer.js';
import CompositeSoundClip from '../../../sound-generators/CompositeSoundClip.js';
import soundManager from '../../../soundManager.js';
import tambo from '../../../tambo.js';
import nullSoundPlayer from '../../../shared-sound-players/nullSoundPlayer.js';

class CompositeSoundClipTestNode extends VBox {

Expand All @@ -38,15 +38,14 @@ class CompositeSoundClipTestNode extends VBox {
const playSoundClipChordButton = new TextPushButton( 'Play CompositeSoundClip', {
baseColor: '#aad6cc',
font: new PhetFont( 16 ),
soundPlayer: SoundPlayer.NO_SOUND, // turn off default sound generation
listener: () => { compositeSoundClip.play(); }
soundPlayer: compositeSoundClip
} );

// add button to stop the sound
const stopSoundClipChordButton = new TextPushButton( 'Stop CompositeSoundClip', {
baseColor: '#DBB1CD',
font: new PhetFont( 16 ),
soundPlayer: SoundPlayer.NO_SOUND, // turn off default sound generation
soundPlayer: nullSoundPlayer, // turn off default sound generation
listener: () => { compositeSoundClip.stop(); }
} );

Expand Down
7 changes: 2 additions & 5 deletions js/demo/testing/view/SoundClipChordTestNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import PhetFont from '../../../../../scenery-phet/js/PhetFont.js';
import { VBox, VBoxOptions } from '../../../../../scenery/js/imports.js';
import TextPushButton from '../../../../../sun/js/buttons/TextPushButton.js';
import brightMarimba_mp3 from '../../../../sounds/brightMarimba_mp3.js';
import SoundPlayer from '../../../SoundPlayer.js';
import SoundClipChord from '../../../sound-generators/SoundClipChord.js';
import soundManager from '../../../soundManager.js';
import tambo from '../../../tambo.js';
Expand All @@ -35,16 +34,14 @@ class SoundClipChordTestNode extends VBox {
const playChordButton = new TextPushButton( 'Play Chord', {
baseColor: '#aad6cc',
font: new PhetFont( 16 ),
soundPlayer: SoundPlayer.NO_SOUND, // turn off default sound generation
listener: () => { chordSoundClipChord.play(); }
soundPlayer: chordSoundClipChord
} );

// add button to play an arpeggio
const playArpeggioButton = new TextPushButton( 'Play Arpeggiated Chord', {
baseColor: '#DBB1CD',
font: new PhetFont( 16 ),
soundPlayer: SoundPlayer.NO_SOUND, // turn off default sound generation
listener: () => { arpeggioSoundClipChord.play(); }
soundPlayer: arpeggioSoundClipChord
} );

super( optionize<SoundClipChordTestNodeOptions, SelfOptions, VBoxOptions>( {
Expand Down
16 changes: 6 additions & 10 deletions js/demo/testing/view/TestingScreenView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import phetAudioContext from '../../../phetAudioContext.js';
import SoundClip from '../../../sound-generators/SoundClip.js';
import SoundLevelEnum from '../../../SoundLevelEnum.js';
import soundManager from '../../../soundManager.js';
import SoundPlayer from '../../../SoundPlayer.js';
import tambo from '../../../tambo.js';
import AmplitudeModulatorDemoNode from './AmplitudeModulatorDemoNode.js';
import CompositeSoundClipTestNode from './CompositeSoundClipTestNode.js';
Expand All @@ -37,6 +36,7 @@ import RemoveAndDisposeSoundGeneratorsTestPanel from './RemoveAndDisposeSoundGen
import SoundClipChordTestNode from './SoundClipChordTestNode.js';
import Bounds2 from '../../../../../dot/js/Bounds2.js';
import { TimerListener } from '../../../../../axon/js/Timer.js';
import nullSoundPlayer from '../../../shared-sound-players/nullSoundPlayer.js';

// constants
const CHECKBOX_SIZE = 16;
Expand Down Expand Up @@ -149,16 +149,14 @@ class BasicAndEnhancedSoundTestNode extends VBox {
const playBasicSoundButton = new TextPushButton( 'Play Basic-Level Sound', {
baseColor: '#aad6cc',
font: new PhetFont( 16 ),
soundPlayer: SoundPlayer.NO_SOUND, // turn off default sound generation
listener: () => { loonCallSoundClip.play(); }
soundPlayer: loonCallSoundClip
} );

// add button to play enhanced-mode sound
const playEnhancedSoundButton = new TextPushButton( 'Play Enhanced-Level Sound', {
baseColor: '#DBB1CD',
font: new PhetFont( 16 ),
soundPlayer: SoundPlayer.NO_SOUND, // turn off default sound generation
listener: () => { rhodesChordSoundClip.play(); }
soundPlayer: rhodesChordSoundClip
} );

super( merge( {
Expand Down Expand Up @@ -212,23 +210,21 @@ class AdditionalAudioNodesTestNode extends VBox {
const playNormalSoundButton = new TextPushButton( 'Normal Sound Clip', {
baseColor: '#CCFF00',
font: buttonFont,
soundPlayer: SoundPlayer.NO_SOUND, // turn off default sound generation
listener: () => { shortSoundNormal.play(); }
soundPlayer: shortSoundNormal
} );

// add button to play the sound with the reverb added in the signal path
const playSoundWithInsertedAudioNodeButton = new TextPushButton( 'Same Clip with In-Line Reverb Node', {
baseColor: '#CC99FF',
font: buttonFont,
soundPlayer: SoundPlayer.NO_SOUND, // turn off default sound generation
listener: () => {shortSoundWithReverb.play();}
soundPlayer: shortSoundWithReverb
} );

// add button to play both sounds at the same time
const playBothSounds = new TextPushButton( 'Both Clips Simultaneously', {
baseColor: '#FF9999',
font: buttonFont,
soundPlayer: SoundPlayer.NO_SOUND, // turn off default sound generation
soundPlayer: nullSoundPlayer, // turn off default sound generation
listener: () => {
shortSoundNormal.play();
shortSoundWithReverb.play();
Expand Down
27 changes: 27 additions & 0 deletions js/shared-sound-players/nullSoundPlayer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright 2020-2022, University of Colorado Boulder

/**
* The nullSoundPlayer is a singleton that implements the ISoundPlayer interface but produces no sound. It is most
* often used to turn off sound generation in an interactive component that produces sound by default.
*
* @author John Blanco (PhET Interactive Simulations)
*/

import tambo from '../tambo.js';
import ISoundPlayer from '../ISoundPlayer.js';

class NullSoundPlayer implements ISoundPlayer {

constructor() {}

public play() {}

public stop() {}
}

// Create the singleton instance.
const nullSoundPlayer = new NullSoundPlayer();

tambo.register( 'nullSoundPlayer', nullSoundPlayer );

export default nullSoundPlayer;
4 changes: 1 addition & 3 deletions js/sound-generators/SoundClip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,7 @@ class SoundClip extends SoundGenerator {
// For sounds that are created statically during the module load phase, this listener will interpret the audio
// data once the load of that data has completed. For all sounds constructed after the module load phase has
// completed, this will process right away.
// TODO: (for and from @jbphet) - Review the 'any' typespec below with a developer who understands this better, see https://github.com/phetsims/tambo/issues/160
// const setStartAndEndPoints = ( audioBuffer: AudioBuffer ) => {
const setStartAndEndPoints = ( audioBuffer: any ) => {
const setStartAndEndPoints = ( audioBuffer: AudioBuffer | null ) => {
if ( audioBuffer ) {
const loopBoundsInfo = SoundUtils.detectSoundBounds( audioBuffer );
this.soundStart = loopBoundsInfo.soundStart;
Expand Down
12 changes: 11 additions & 1 deletion js/sound-generators/SoundClipChord.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import SoundClip, { SoundClipOptions } from '../../../tambo/js/sound-generators/
import SoundGenerator, { SoundGeneratorOptions } from '../../../tambo/js/sound-generators/SoundGenerator.js';
import tambo from '../tambo.js';
import WrappedAudioBuffer from '../WrappedAudioBuffer.js';
import ISoundPlayer from '../ISoundPlayer.js';

type SelfOptions = {

Expand All @@ -33,7 +34,7 @@ type SelfOptions = {

export type SoundClipChordOptions = SelfOptions & SoundGeneratorOptions;

class SoundClipChord extends SoundGenerator {
class SoundClipChord extends SoundGenerator implements ISoundPlayer {

// whether to play the chord as an arpeggio
private readonly arpeggiate: boolean;
Expand Down Expand Up @@ -92,6 +93,15 @@ class SoundClipChord extends SoundGenerator {
} );
}

/**
* Stop the chord if it's playing. This is mostly here to complete the ISoundPlayer interface.
*/
public stop() {
this.playbackSoundClips.forEach( soundClip => {
soundClip.stop();
} );
}

/**
* Release any memory references in order to avoid memory leaks.
*/
Expand Down
8 changes: 4 additions & 4 deletions js/sound-generators/ValueChangeSoundGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import Range from '../../../dot/js/Range.js';
import optionize from '../../../phet-core/js/optionize.js';
import generalBoundaryBoopSoundPlayer from '../shared-sound-players/generalBoundaryBoopSoundPlayer.js';
import generalSoftClickSoundPlayer from '../shared-sound-players/generalSoftClickSoundPlayer.js';
import SoundPlayer from '../SoundPlayer.js';
import tambo from '../tambo.js';
import ISoundPlayer from '../ISoundPlayer.js';
import SoundGenerator, { SoundGeneratorOptions } from './SoundGenerator.js';
Expand All @@ -27,6 +26,7 @@ import generalBoundaryBoop_mp3 from '../../sounds/generalBoundaryBoop_mp3.js';
import generalSoftClick_mp3 from '../../sounds/generalSoftClick_mp3.js';
import Utils from '../../../dot/js/Utils.js';
import SoundClip from './SoundClip.js';
import nullSoundPlayer from '../shared-sound-players/nullSoundPlayer.js';

// constants
const DEFAULT_NUMBER_OF_MIDDLE_THRESHOLDS = 5; // fairly arbitrary
Expand Down Expand Up @@ -291,9 +291,9 @@ class ValueChangeSoundGenerator extends SoundGenerator {
* Static instance that makes no sound. This is generally used as an option value to turn off sound generation.
*/
static NO_SOUND = new ValueChangeSoundGenerator( new Range( 0, 1 ), {
middleMovingUpSoundPlayer: SoundPlayer.NO_SOUND,
minSoundPlayer: SoundPlayer.NO_SOUND,
maxSoundPlayer: SoundPlayer.NO_SOUND
middleMovingUpSoundPlayer: nullSoundPlayer,
minSoundPlayer: nullSoundPlayer,
maxSoundPlayer: nullSoundPlayer
} )
}

Expand Down
23 changes: 13 additions & 10 deletions js/soundManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,11 +200,11 @@ class SoundManager extends PhetioObject {
this.dryGainNode.connect( this.masterGainNode );

// Create and hook up gain nodes for each of the defined categories.
assert && assert( this.convolver !== null && this.dryGainNode !== null, 'some audio nodes have not been initialized' );
options.categories.forEach( categoryName => {
const gainNode = phetAudioContext.createGain();
// TODO: Why are the following casts necessary? See https://github.com/phetsims/tambo/issues/160.
gainNode.connect( this.convolver as AudioNode );
gainNode.connect( this.dryGainNode as AudioNode );
gainNode.connect( this.convolver! );
gainNode.connect( this.dryGainNode! );
this.gainNodesForCategories.set( categoryName, gainNode );
} );

Expand Down Expand Up @@ -351,6 +351,9 @@ class SoundManager extends PhetioObject {
return;
}

// state checking - make sure the needed nodes have been created
assert && assert( this.convolver !== null && this.dryGainNode !== null, 'some audio nodes have not been initialized' );

// Verify that this is not a duplicate addition.
const hasSoundGenerator = this.hasSoundGenerator( soundGenerator );
assert && assert( !hasSoundGenerator, 'can\'t add the same sound generator twice' );
Expand Down Expand Up @@ -379,15 +382,15 @@ class SoundManager extends PhetioObject {

// Connect the sound generator to an output path.
if ( options.categoryName === null ) {
soundGenerator.connect( this.convolver as AudioNode );
soundGenerator.connect( this.dryGainNode as AudioNode );
soundGenerator.connect( this.convolver! );
soundGenerator.connect( this.dryGainNode! );
}
else {
assert && assert(
this.gainNodesForCategories.has( options.categoryName! ),
`category does not exist : ${options.categoryName}`
);
soundGenerator.connect( this.gainNodesForCategories.get( options.categoryName! ) as AudioNode );
soundGenerator.connect( this.gainNodesForCategories.get( options.categoryName! )! );
}

// Keep a record of the sound generator along with additional information about it.
Expand Down Expand Up @@ -442,11 +445,11 @@ class SoundManager extends PhetioObject {
assert && assert( soundGeneratorInfo, 'unable to remove sound generator - not found' );

// disconnect the sound generator from any audio nodes to which it may be connected
if ( soundGenerator.isConnectedTo( this.convolver as AudioNode ) ) {
soundGenerator.disconnect( this.convolver as AudioNode );
if ( soundGenerator.isConnectedTo( this.convolver! ) ) {
soundGenerator.disconnect( this.convolver! );
}
if ( soundGenerator.isConnectedTo( this.dryGainNode as AudioNode ) ) {
soundGenerator.disconnect( this.dryGainNode as AudioNode );
if ( soundGenerator.isConnectedTo( this.dryGainNode! ) ) {
soundGenerator.disconnect( this.dryGainNode! );
}
this.gainNodesForCategories.forEach( gainNode => {
if ( soundGenerator.isConnectedTo( gainNode ) ) {
Expand Down

0 comments on commit c8f6549

Please sign in to comment.