Skip to content

Commit

Permalink
drag sounds for the angle controls, see #105
Browse files Browse the repository at this point in the history
  • Loading branch information
jessegreenberg committed Dec 6, 2024
1 parent 57e7695 commit 151018b
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 8 deletions.
25 changes: 25 additions & 0 deletions js/trig-tour/TrigTourQueryParameters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright 2024, University of Colorado Boulder

/**
* Query parameters for this simulation.
*
* @author Jesse Greenberg (PhET Interactive Simulations)
*/

import trigTour from '../trigTour.js';

const TrigTourQueryParameters = QueryStringMachine.getAll( {

// Reduces the number of rotations in the unit circle so that it is easier to find the limit.s
maxRotations: {
type: 'number',

// Value must be an integer becase it is used in a multiple of PI. It must be an even
// number because the drag handlers in this sim assume that in their calculations.
isValidValue: ( value: number ) => value < 60 && Number.isInteger( value ) && value % 2 === 0,
defaultValue: 50 // High number makes it difficult and whimsical to find the limit.
}
} );

trigTour.register( 'TrigTourQueryParameters', TrigTourQueryParameters );
export default TrigTourQueryParameters;
15 changes: 11 additions & 4 deletions js/trig-tour/model/TrigTourModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,13 @@ import Vector2 from '../../../../dot/js/Vector2.js';
import trigTour from '../../trigTour.js';
import SpecialAngles from '../SpecialAngles.js';
import TrigTourConstants from '../TrigTourConstants.js';
import TrigTourQueryParameters from '../TrigTourQueryParameters.js';

// constants
const MAX_SMALL_ANGLE_LIMIT = 0.5 * Math.PI;
const MAX_ANGLE_LIMIT = 50 * Math.PI + MAX_SMALL_ANGLE_LIMIT; // must be ( integer+0.5) number of full rotations

// must be ( integer+0.5) number of full rotations
const MAX_ANGLE_LIMIT = TrigTourQueryParameters.maxRotations * Math.PI + MAX_SMALL_ANGLE_LIMIT;

class TrigTourModel {

Expand Down Expand Up @@ -163,7 +166,11 @@ class TrigTourModel {
this.smallAngle = fullAngleInRads - this.rotationNumberFromPi * 2 * Math.PI;
remainderAngle = fullAngleInRads % ( Math.PI );
this._halfTurnCount = Utils.roundSymmetric( ( fullAngleInRads - remainderAngle ) / ( Math.PI ) );
this.fullAngleProperty.value = fullAngleInRads;
this.fullAngleProperty.value = this.constrainFullAngle( fullAngleInRads );
}

private constrainFullAngle( fullAngle: number ): number {
return Utils.clamp( fullAngle, -MAX_ANGLE_LIMIT, MAX_ANGLE_LIMIT );
}

/**
Expand Down Expand Up @@ -198,7 +205,7 @@ class TrigTourModel {
this._fullTurnCount = Utils.roundSymmetric( ( targetAngle - remainderAngle ) / ( 2 * Math.PI ) );
remainderAngle = targetAngle % ( Math.PI );
this._halfTurnCount = Utils.roundSymmetric( ( targetAngle - remainderAngle ) / ( Math.PI ) );
this.fullAngleProperty.value = targetAngle; // now can trigger angle update
this.fullAngleProperty.value = this.constrainFullAngle( targetAngle ); // now can trigger angle update
this.previousAngle = smallAngle;
}

Expand Down Expand Up @@ -324,7 +331,7 @@ class TrigTourModel {
public checkMaxAngleExceeded(): void {

// determine if max angle is exceeded and set the property.
this.maxAngleExceededProperty.value = ( Math.abs( this.getFullAngleInRadians() ) > MAX_ANGLE_LIMIT );
this.maxAngleExceededProperty.value = ( Math.abs( this.getFullAngleInRadians() ) >= MAX_ANGLE_LIMIT );
}

public static readonly MAX_SMALL_ANGLE_LIMIT = MAX_SMALL_ANGLE_LIMIT;
Expand Down
33 changes: 33 additions & 0 deletions js/trig-tour/view/AngleSoundGenerator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright 2024, University of Colorado Boulder

/**
* AngleSoundGenerator is a sound generator specifically designed to produce sounds for the
* controls that change the angle in trig-tour.
*
* @author Jesse Greenberg (PhET Interactive Simulations)
*/

import Range from '../../../../dot/js/Range.js';
import Utils from '../../../../dot/js/Utils.js';
import ValueChangeSoundPlayer from '../../../../tambo/js/sound-generators/ValueChangeSoundPlayer.js';
import trigTour from '../../trigTour.js';
import TrigTourModel from '../model/TrigTourModel.js';

class AngleSoundGenerator extends ValueChangeSoundPlayer {
public constructor() {

const range = new Range( -TrigTourModel.MAX_ANGLE_LIMIT, TrigTourModel.MAX_ANGLE_LIMIT );
super( range, {

// Limit precision so that comparison against the range limits works consistently.
constrainValue: ( value: number ) => Utils.toFixedNumber( value, 1 ),

// Arbitrary, but creates a consistent sound as the user interacts with the angle.
numberOfMiddleThresholds: 700
} );
}
}

trigTour.register( 'AngleSoundGenerator', AngleSoundGenerator );

export default AngleSoundGenerator;
17 changes: 16 additions & 1 deletion js/trig-tour/view/GraphView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import trigTour from '../../trigTour.js';
import TrigTourStrings from '../../TrigTourStrings.js';
import TrigTourModel from '../model/TrigTourModel.js';
import TrigTourConstants from '../TrigTourConstants.js';
import AngleSoundGenerator from './AngleSoundGenerator.js';
import TrigFunctionLabelText from './TrigFunctionLabelText.js';
import TrigIndicatorArrowNode from './TrigIndicatorArrowNode.js';
import TrigPlotsNode from './TrigPlotsNode.js';
Expand Down Expand Up @@ -87,8 +88,9 @@ class GraphView extends Node {
* @param height of y-axis on graph
* @param width of x-axis on graph
* @param viewProperties
* @param angleSoundGenerator
*/
public constructor( trigTourModel: TrigTourModel, height: number, width: number, viewProperties: ViewProperties ) {
public constructor( trigTourModel: TrigTourModel, height: number, width: number, viewProperties: ViewProperties, angleSoundGenerator: AngleSoundGenerator ) {
super();

this.trigTourModel = trigTourModel;
Expand Down Expand Up @@ -255,6 +257,8 @@ class GraphView extends Node {
// make sure the full angle does not exceed max allowed angle
trigTourModel.checkMaxAngleExceeded();

const oldValue = trigTourModel.getFullAngleInRadians();

// For alt input, use modelDelta to increment/decrement the full angle
if ( event.isFromPDOM() ) {
fullAngle = trigTourModel.getNextFullDeltaFromKeyboardInput( listener.modelDelta, viewProperties.specialAnglesVisibleProperty.value );
Expand All @@ -267,6 +271,17 @@ class GraphView extends Node {
}

trigTourModel.setNewFullAngle( fullAngle, viewProperties.specialAnglesVisibleProperty.value );

// After the new value has been computed, play the sound if the value has changed
const newValue = trigTourModel.getFullAngleInRadians();
if ( oldValue !== newValue ) {
if ( event.isFromPDOM() ) {
angleSoundGenerator.playSoundForValueChange( newValue, oldValue );
}
else {
angleSoundGenerator.playSoundIfThresholdReached( newValue, oldValue );
}
}
}
} );

Expand Down
8 changes: 6 additions & 2 deletions js/trig-tour/view/TrigTourScreenView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import dizzyPhetGirl_png from '../../../mipmaps/dizzyPhetGirl_png.js';
import trigTour from '../../trigTour.js';
import TrigTourStrings from '../../TrigTourStrings.js';
import TrigTourModel from '../model/TrigTourModel.js';
import AngleSoundGenerator from './AngleSoundGenerator.js';
import ControlPanel from './ControlPanel.js';
import GraphView from './GraphView.js';
import ValuesAccordionBox from './readout/ValuesAccordionBox.js';
Expand Down Expand Up @@ -59,10 +60,13 @@ class TrigTourScreenView extends ScreenView {
whiteSheet.x = this.layoutBounds.centerX;
whiteSheet.top = this.layoutBounds.top + 20;

const unitCircleView = new UnitCircleView( trigTourModel, whiteSheet, xOffset, viewProperties );
// A reusable sound generator for the UI components that can control the angle.
const angleSoundGenerator = new AngleSoundGenerator();

const unitCircleView = new UnitCircleView( trigTourModel, whiteSheet, xOffset, viewProperties, angleSoundGenerator );
unitCircleView.center = whiteSheet.center;

const graphView = new GraphView( trigTourModel, 0.25 * this.layoutBounds.height, 0.92 * this.layoutBounds.width, viewProperties );
const graphView = new GraphView( trigTourModel, 0.25 * this.layoutBounds.height, 0.92 * this.layoutBounds.width, viewProperties, angleSoundGenerator );
graphView.x = this.layoutBounds.centerX;
graphView.y = this.layoutBounds.bottom - graphView.graphAxesNode.bottom - 15;

Expand Down
17 changes: 16 additions & 1 deletion js/trig-tour/view/UnitCircleView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import TrigTourModel from '../model/TrigTourModel.js';
import SpecialAngles from '../SpecialAngles.js';
import TrigTourConstants from '../TrigTourConstants.js';
import TrigTourMathStrings from '../TrigTourMathStrings.js';
import AngleSoundGenerator from './AngleSoundGenerator.js';
import TrigIndicatorArrowNode from './TrigIndicatorArrowNode.js';
import TrigTourColors from './TrigTourColors.js';
import TrigTourSpiralNode from './TrigTourSpiralNode.js';
Expand Down Expand Up @@ -55,8 +56,9 @@ class UnitCircleView extends Node {
* @param viewRectangle - Rectangle for the background rectangle of the unit circle, including view properties like lineWidth
* @param backgroundOffset - Offset of the background rectangle behind the unit circle view
* @param viewProperties - collection of properties handling visibility of elements on screen
* @param angleSoundGenerator - sound generator for angle changes
*/
public constructor( trigTourModel: TrigTourModel, viewRectangle: Rectangle, backgroundOffset: number, viewProperties: ViewProperties ) {
public constructor( trigTourModel: TrigTourModel, viewRectangle: Rectangle, backgroundOffset: number, viewProperties: ViewProperties, angleSoundGenerator: AngleSoundGenerator ) {
super();

// Draw Unit Circle
Expand Down Expand Up @@ -190,6 +192,8 @@ class UnitCircleView extends Node {
// make sure the full angle does not exceed max allowed angle
trigTourModel.checkMaxAngleExceeded();

const oldValue = trigTourModel.getFullAngleInRadians();

if ( event.isFromPDOM() ) {
const newFullAngle = trigTourModel.getNextFullDeltaFromKeyboardInput( listener.modelDelta, viewProperties.specialAnglesVisibleProperty.value );
trigTourModel.setNewFullAngle( newFullAngle, viewProperties.specialAnglesVisibleProperty.value );
Expand Down Expand Up @@ -222,6 +226,17 @@ class UnitCircleView extends Node {
}
}
}

// After the new value has been computed, play the sound if the value has changed
const newValue = trigTourModel.getFullAngleInRadians();
if ( oldValue !== newValue ) {
if ( event.isFromPDOM() ) {
angleSoundGenerator.playSoundForValueChange( newValue, oldValue );
}
else {
angleSoundGenerator.playSoundIfThresholdReached( newValue, oldValue );
}
}
}
} ) );

Expand Down

0 comments on commit 151018b

Please sign in to comment.