(function() { 'use strict'; var event = window.mheContpract.libs['event'].user; var KEYCODE = { ARROW: { LEFT: 37, TOP: 38, RIGHT: 39, BOTTOM: 40 } }; var KEY_OFFSET = { 37: -10, 38: -10, 39: 10, 40: 10 }; var ActiveGraph = function(section) { this.section = section; this.sectionNode = section.jq[0]; this.section.points_possible = 1; this.section.points_earned = 0; this.section.section_completed = false; this.section.jq.addClass( "activity-graph" ); this.drawCurve(); _init.call(this); _defineProperties.call(this); this.transform(); this.updateStrokeWidth(); }; ActiveGraph.prototype.drawCurve = function() { this.sine = {}; this.sine.path = this.sectionNode.querySelector( ".sinus" ); this.svg = this.sectionNode.querySelector( "svg" ); // matrix transformation have next form: //[scaleX, skewX, skewY, scaleY, translateX, translateY] this.sine.matrix = [1,0,0,1,0,0]; this.sine.amplitude = 1; this.sine.frequency = 0.05; this.sine.offsetY = this.svg.height.baseVal.value / 2; this.sine.strokeWidth = 0; var width = this.svg.width.baseVal.value, height = this.svg.height.baseVal.value, pathData = []; for( var y, x = 0; x < width * 10; x++ ){ y = -Math.sin( x * this.sine.frequency) * this.sine.amplitude; y = "," + ( y + height / 2 ) + " "; pathData.push( x, y ); } this.sine.path.setAttributeNS( null, "d", "M" + pathData.join("") ); }; ActiveGraph.prototype.transform = function(){ var scaleY = Math.round(this.sine.amplitude); var offsetY = this.sine.offsetY - this.sine.offsetY * scaleY; var scaleX = 0.05/ this.sine.frequency; this.sine.matrix[0] = scaleX; this.sine.matrix[3] = scaleY; this.sine.matrix[5] = offsetY; this.sine.path.setAttributeNS( null, "transform", this.sine._matrix ); }; ActiveGraph.prototype.animate = function() { var self = this; var offsetX = -self.sine.period; var step = 5; this.intervalID = setInterval( function(){ offsetX += step; if( offsetX >= 0 ){ offsetX -= self.sine.period; } self.sine.matrix[4] = offsetX; self.transform(); }, 10 ); }; ActiveGraph.prototype.stop = function() { clearInterval( this.intervalID ); }; ActiveGraph.prototype.play = function(){ this.audio.pause(); this.audio.volume = this.volume; this.audio.play(); this.finishSlide(); }; ActiveGraph.prototype.finishSlide = function() { this.section.points_earned = this.section.points_possible; this.section.section_completed = true; player.activity.grade_activity(); player.update_section_status(); }; /** * amplitude * @param {Number} v - percentage value from 0 to 100 */ ActiveGraph.prototype.updateAmplitude = function(v){ v = Math.min( Math.max( v, 0 ), 100 ); var max = 160; var min = 10; this.sine.amplitudePercent = 100 - v; this.sine.amplitude = ( 100 - v ) * ( max - min ) / 100 + min; // get amplitude from 10 to 150 // accessibility part // get text from live region var ampPercent = Math.round( (100 - v) * 100 ) / 100; var amplitude = "The amplitude value is " + ampPercent + " percent. "; var playBtnAria = "Press the button to hear the sound of the sine wave with amplitude "; playBtnAria += ampPercent + " and frequency " + this.sine.frequencyPercent + " percent."; this.liveRegion.innerHTML = "
" + amplitude + "
"; this.playBtn.setAttribute( "aria-label", playBtnAria ); }; /** * frequency * @param {Number} v - percentage value from 0 to 100 */ ActiveGraph.prototype.updateFrequency = function(v){ v = Math.min( Math.max( v, 0 ), 100 ); var max = 140; var min = 10; this.sine.frequencyPercent = v; this.sine.frequency = ( v * ( max - min ) / 100 + min ) / 1000; // accessibility part // get text from live region var freqPercent = Math.round( v * 100 )/ 100; var frequency = "The frequency value is " + freqPercent + " percent. "; var playBtnAria = "Press the button to hear the sound of the sine wave with amplitude "; playBtnAria += this.sine.amplitudePercent + " and frequency " + freqPercent + " percent."; this.liveRegion.innerHTML = "" + frequency + "
"; this.playBtn.setAttribute( "aria-label", playBtnAria ); }; ActiveGraph.prototype.updateStrokeWidth = function(){ var strokeWidth, defaultStrokeWidth = 2; strokeWidth = defaultStrokeWidth / this.sine.matrix[3]; strokeWidth += defaultStrokeWidth / this.sine.matrix[0] / 100; this.sine.path.setAttributeNS( null, "stroke-width", strokeWidth ); }; var _init = function(){ this.sliders = {}; this.liveRegion = this.sectionNode.querySelector(".live_region"); this.audioTracks = this.sectionNode.querySelectorAll("audio"); this.playBtn = this.sectionNode.querySelector(".play_music"); this.playBtn.addEventListener("click", this.play.bind(this)); var self = this; var amplitudeBar = this.sectionNode.querySelector(".amplitude .slider"), frequencyBar = this.sectionNode.querySelector(".frequency .slider"), amplitudeHandle = amplitudeBar.querySelector(".handle"), frequencyHandle = frequencyBar.querySelector(".handle"); var ampOptions = { orientation: "vertical", value: 50, onStop: self.updateAmplitude.bind(self), onCreate: self.updateAmplitude.bind(self) }, freqOptions = { orientation: "horizontal", value: 50, onStop: self.updateFrequency.bind(self), onCreate: self.updateFrequency.bind(self) }; var amplitudeCallbacks = [ this.updateAmplitude.bind(this), this.transform.bind(this) ]; var frequencyCallbacks = [ this.updateFrequency.bind(this), this.transform.bind(this) ]; if( this.sine.path.style.vectorEffect === undefined ){ var updateStroke = this.updateStrokeWidth.bind(this); amplitudeCallbacks.push(updateStroke); frequencyCallbacks.push(updateStroke); } else { this.sine.path.style.strokeWidth = 2; this.sine.path.style.vectorEffect = "non-scaling-stroke"; } this.sliders.amplitude = new Slider( amplitudeHandle, amplitudeBar , amplitudeCallbacks, ampOptions); this.sliders.frequency = new Slider( frequencyHandle, frequencyBar, frequencyCallbacks, freqOptions ); amplitudeHandle.addEventListener( "keydown", function(e){ var keyCode = e.keyCode || e.which; if( keyCode === KEYCODE.ARROW.TOP || keyCode === KEYCODE.ARROW.BOTTOM ){ var value = 100 - self.sine.amplitudePercent + KEY_OFFSET[keyCode]; value = Math.max( Math.min( value, 100 ), 0 ); self.updateAmplitude( value ); self.sliders.amplitude.setPositionFromValue( value ); } }); frequencyHandle.addEventListener( "keydown", function(e){ var keyCode = e.keyCode || e.which; if( keyCode === KEYCODE.ARROW.LEFT || keyCode === KEYCODE.ARROW.RIGHT ){ var value = self.sine.frequencyPercent + KEY_OFFSET[keyCode]; value = Math.max( Math.min( value, 100 ), 0 ); self.updateFrequency( value ); self.sliders.frequency.setPositionFromValue( value ); } }); }; var _defineProperties = function(){ // define a string representation of the matrix Object.defineProperty( this.sine, "_matrix", { get: function(){ return "matrix(" + this.matrix.join(",") + ")"; } }); Object.defineProperty( this.sine, "period", { get: function(){ return 2 * Math.PI / this.frequency; } }); Object.defineProperty( this, "volume", { get: function(){ var volume = 100 - this.sine.amplitudePercent; volume = ( 100 - volume ) * 40 / 100 + 10; volume = volume / 100; // get volume from 0.1 to 0.5 volume = Math.round( volume * 100 ) / 100; return volume; } }); Object.defineProperty( this, "audio", { get: function(){ // calculate index to getting audio // index has range from 8 to 0 var max = ( this.audioTracks.length - 1 ) * 10; var ind = Math.round( this.sine.frequencyPercent * max / 100 / 10 ); return this.audioTracks[ind]; } }); }; var emptyFunc = function(){}; /** * Slider * @param slider - slider's node * @param sliderBar * @param [callback] * @param [options] * @constructor */ var Slider = function( slider, sliderBar, callback, options ) { var self = this; var axis, side, position; options = options || {}; this.orientation = options.orientation || "horizontal"; this.callback = Array.isArray(callback) ? callback : [callback]; this.onCreate = options.onCreate || emptyFunc; this.onStart = options.onStart || emptyFunc; this.onStop = options.onStop || emptyFunc; this.value = options.value || 0; var $sliderBar = $( sliderBar ); var $slider = $( slider ); if ( this.orientation === "horizontal" ) { this.min = 0; this.max = $sliderBar.width() - $slider.width(); axis = 'x'; side = 'left'; } else { this.min = 0; this.max = $sliderBar.height() - $slider.height(); axis = 'y'; side = 'top'; } this.side = side; this.$slider = $slider; this.$slider.css({ cursor: 'pointer' }); $slider.draggable( { create: function() { position = (self.value / 100) * (self.max - self.min); $slider.css( side, Math.round( position ) ); self.onCreate(); }, start: function( event, ui ) { ui.position[side] = self.determineCorrectPosition( ui.position[side] ); self.value = self.determineValue( ui.position[side] ); self.onStart( self.value ); }, drag: function( event, ui ) { ui.position[side] = self.determineCorrectPosition( ui.position[side] ); self.value = self.determineValue( ui.position[side] ); self.callback.forEach( function( callback ) { callback( self.value ); } ); }, stop: function( event, ui ) { self.onStop( self.value ); }, axis: axis }); this.onCreate( this.value ); }; Slider.prototype.determineCorrectPosition = function( position ) { position = Math.max( this.min, position / player._scaleFactor ); position = Math.min( this.max, position ); position = Math.round( position ); return position; }; Slider.prototype.determineValue = function( position ) { return position / (this.max - this.min) * 100; }; Slider.prototype.addCallback = function( callback ) { this.callback.push( callback ); }; Slider.prototype.removeCallback = function( i ) { if ( i === undefined ) { this.callback.pop(); } else { this.callback.splice( i, 1 ); } }; Slider.prototype.setPositionFromValue = function( value ) { this.value = value; var position = (this.value / 100) * (this.max - this.min); this.$slider.css( this.side, Math.round( position ) ); this.callback.forEach(function(callback){ callback( value ) }); }; window.mheContpract = window.mheContpract || {}; window.mheContpract['active_graph_1'] = { initialize: function(section) { section.slide = new ActiveGraph(section); }, show: function(section) { section.slide.animate(); }, hide: function(section) { section.slide.stop(); } }; })();