From 3f598ce68aa790198f7910fc5684e375a3a22947 Mon Sep 17 00:00:00 2001 From: Richard Lu Date: Tue, 3 Nov 2015 20:29:49 -0500 Subject: [PATCH 1/5] Implement TimeManager, remove shifting logic from Visuals --- src/index.html | 2 + src/lecture_model.js | 5 + src/recording/recording_controller.js | 58 +++++++++++ src/time/TimeManager.js | 100 +++++++++++++++++++ src/visuals/visuals_controller.js | 6 +- src/visuals/visuals_model.js | 135 +++++--------------------- 6 files changed, 193 insertions(+), 113 deletions(-) create mode 100644 src/recording/recording_controller.js create mode 100644 src/time/TimeManager.js diff --git a/src/index.html b/src/index.html index e860a1c..843c9ae 100644 --- a/src/index.html +++ b/src/index.html @@ -27,6 +27,8 @@ + + diff --git a/src/lecture_model.js b/src/lecture_model.js index 7e506cc..e7ed2d1 100644 --- a/src/lecture_model.js +++ b/src/lecture_model.js @@ -8,6 +8,8 @@ var LectureModel = function() { var retimerModel = null; var init = function() { + TimeManager.getVisualInstance().clear(); + TimeManager.getAudioInstance().clear(); visualsModel = new VisualsModel(800, 500); audioModel = new AudioModel(); @@ -29,6 +31,9 @@ var LectureModel = function() { // Loading the model from JSON this.loadFromJSON = function(json_object) { + TimeManager.getVisualInstance().clear(); + TimeManager.getAudioInstance().clear(); + visualsModel = VisualsModel.loadFromJSON(json_object['visuals_model']); audioModel = AudioModel.loadFromJSON(json_object['audio_model']); retimerModel = RetimerModel.loadFromJSON(json_object['retimer_model']); diff --git a/src/recording/recording_controller.js b/src/recording/recording_controller.js new file mode 100644 index 0000000..4cff0bb --- /dev/null +++ b/src/recording/recording_controller.js @@ -0,0 +1,58 @@ +"use strict"; + +var RecordingController = function() { + var dirtyVisuals = []; + + // Creates wrappers around the visuals that keeps track of their previous time + // and the times of their vertices. Then move the visuals to positive infinity. + // Used at the end of a recording so that the visuals will not overlap with the ones being recorded. + // Only processes visuals in the current slide after the current time. + this.setDirtyVisuals = function(currentVisualTime) { + + var currentSlide = self.getSlideAtTime(currentVisualTime); + + // Iterate over all the visuals + var visuals_iterator = currentSlide.getVisualsIterator(); + while (visuals_iterator.hasNext()) { + var visual = visuals_iterator.next(); + + // Only make the visual dirty if the time is greater than the current time + if(visual.getTMin() <= currentVisualTime) { + continue; + }; + + // Create the wrapper + var wrapper = { + visual: visual, + tMin: visual.getTMin(), + }; + + // Move tMin to infinity + visual.setTMin(Number.POSITIVE_INFINITY); //could alternatively say Number.MAX_VALUE or Number.MAX_SAFE_INTEGER + + // Add the wrapper to dirty visuals + dirtyVisuals.push(wrapper); + + }; // end of iterating over visuals + }; + + // Restore to the previous time and add the shift amount. + // Used at the end of a recording during insertion to shift visuals forward. + this.cleanVisuals = function(shift_amount) { + + // Restore the original times from the wrapper + var visuals = []; + for(var i in dirtyVisuals) { + var dirtyWrapper = dirtyVisuals[i]; + var visual = dirtyWrapper.visual; + visuals.push(visual); + visual.setTMin(dirtyWrapper.tMin); + }; + + // Perform a shift on all of the visuals that were dirty + self.shiftVisuals(visuals, shift_amount); + + // Clear the dirty visuals + dirtyVisuals = []; + }; +} diff --git a/src/time/TimeManager.js b/src/time/TimeManager.js new file mode 100644 index 0000000..e63cf42 --- /dev/null +++ b/src/time/TimeManager.js @@ -0,0 +1,100 @@ +/** + * Manages any "time" field - all model fields which represent a lecture + * timestamp should be created through this class, and destructed whenever + * the parent model object is removed from the lecture model. + */ +var TimeManager = function() { + var timeManager = {}; + + var timeInstances = []; + + /** + * Inner class to represent a "time" field. Registers itself + * with the parent TimeManager upon instantiation, and removes + * itself when destruct() is called. + */ + var TimeInstance = function(time) { + var timeInstance = {}; + + /** + * @return the timestamp represented by this instance + */ + timeInstance.get = function() { + return time; + }; + + /** + * @param newTime the new timestamp for this instance + */ + timeInstance.set = function(newTime) { + time = newTime; + }; + + /** + * @param deltaTime the amount of time to shift this timestamp by + */ + timeInstance.shift = function(deltaTime) { + time += deltaTime; + }; + + /** + * destructs this timestamp instance; removes itself + * from the parent TimeManager + */ + timeInstance.destruct = function() { + timeInstances.splice(timeInstances.indexOf(timeInstance), 1); + }; + + // register upon creation + timeInstances.push(timeInstance); + + return timeInstance; + }; + + /** + * @param time the floating-point timestamp that the returned TimeInstance should represent + * @return a TimeInstance initialized to the given timestamp, registered with this TimeManager + */ + timeManager.getAndRegisterTimeInstance = function(time) { + return new TimeInstance(time); + }; + + /** + * @param startShiftTime every registered TimeInstance from this timestamp onward will be shifted + * @param shiftDeltaTime every TimeInstance to be shifted will be shifted by this much + */ + timeManager.shiftAfterBy = function(startShiftTime, shiftDeltaTime) { + for (var i in timeInstances) { + var timeInstance = timeInstances[i]; + if (timeInstance.get() >= startShiftTime) { + timeInstance.shift(shiftDeltaTime); + } + } + }; + + timeManager.clear = function() { + timeInstances = []; + }; + + return timeManager; +}; + +/** + * @return a singleton instance of TimeManager for visuals + */ +TimeManager.getVisualInstance = function() { + if (TimeManager._v_instance === undefined) { + TimeManager._v_instance = new TimeManager(); + } + return TimeManager._v_instance; +}; + +/** + * @return a singleton instance of TimeManager for audio + */ +TimeManager.getAudioInstance = function() { + if (TimeManager._a_instance === undefined) { + TimeManager._a_instance = new TimeManager(); + } + return TimeManager._a_instance; +}; diff --git a/src/visuals/visuals_controller.js b/src/visuals/visuals_controller.js index 20d2ef9..12e7619 100644 --- a/src/visuals/visuals_controller.js +++ b/src/visuals/visuals_controller.js @@ -79,7 +79,8 @@ var VisualsController = function(visuals_model, retimer_model) { // Keep the origin slides and set visuals dirty so we can shift the visuals in these slides when recording ends originSlide = visualsModel.getSlideAtTime(currentTime); originSlideDuration = originSlide.getDuration(); - visualsModel.setDirtyVisuals(slideBeginTime); + // TODO move to recording controller + TimeManager.getVisualInstance().shiftAfterBy(slideBeginTime, 24*60*60*1000); // Signal the tools controller toolsController.startRecording(); @@ -94,7 +95,8 @@ var VisualsController = function(visuals_model, retimer_model) { currentSlide.setDuration(currentSlide.getDuration() + slideRecordDuration); // Restores the dirty visuals to their former places and adds a shift. - visualsModel.cleanVisuals(originSlide.getDuration() - originSlideDuration); + var shiftAmount = originSlide.getDuration() - originSlideDuration; + TimeManager.getVisualInstance().shiftAfterBy(24*60*60*1000, -24*60*60*1000 + shiftAmount); // Reset recording variables slideBeginTime = NaN; diff --git a/src/visuals/visuals_model.js b/src/visuals/visuals_model.js index 7c5d07f..5daaca2 100644 --- a/src/visuals/visuals_model.js +++ b/src/visuals/visuals_model.js @@ -12,8 +12,6 @@ var VisualsModel = function(canvas_width, canvas_height) { var canvasWidth = canvas_width; var canvasHeight = canvas_height; - var dirtyVisuals = []; - // Gets the size of the canvas where the visuals are being recorded this.getCanvasSize = function() { return { 'width':canvasWidth, 'height':canvasHeight }; @@ -154,96 +152,6 @@ var VisualsModel = function(canvas_width, canvas_height) { undoManager.endGrouping(); }; - // Creates wrappers around the visuals that keeps track of their previous time - // and the times of their vertices. Then move the visuals to positive infinity. - // Used at the end of a recording so that the visuals will not overlap with the ones being recorded. - // Only processes visuals in the current slide after the current time. - this.setDirtyVisuals = function(currentVisualTime) { - - var currentSlide = self.getSlideAtTime(currentVisualTime); - - // Iterate over all the visuals - var visuals_iterator = currentSlide.getVisualsIterator(); - while (visuals_iterator.hasNext()) { - var visual = visuals_iterator.next(); - - // Only make the visual dirty if the time is greater than the current time - if(visual.getTMin() <= currentVisualTime) { - continue; - }; - - // Create the wrapper - var wrapper = { - visual: visual, - tMin: visual.getTMin(), - }; - - // Move tMin to infinity - visual.setTMin(Number.POSITIVE_INFINITY); //could alternatively say Number.MAX_VALUE or Number.MAX_SAFE_INTEGER - - // Add the wrapper to dirty visuals - dirtyVisuals.push(wrapper); - - }; // end of iterating over visuals - }; - - // Restore to the previous time and add the shift amount. - // Used at the end of a recording during insertion to shift visuals forward. - this.cleanVisuals = function(shift_amount) { - - // Restore the original times from the wrapper - var visuals = []; - for(var i in dirtyVisuals) { - var dirtyWrapper = dirtyVisuals[i]; - var visual = dirtyWrapper.visual; - visuals.push(visual); - visual.setTMin(dirtyWrapper.tMin); - }; - - // Perform a shift on all of the visuals that were dirty - self.shiftVisuals(visuals, shift_amount); - - // Clear the dirty visuals - dirtyVisuals = []; - }; - - // Shift the visual by an amount, which shifts all vertices and transforms - var doShiftVisual = function(visual, amount) { - visual.setTMin(visual.getTMin() + amount); - var vertIter = visual.getVerticesIterator(); - while(vertIter.hasNext()) { - var vert = vertIter.next(); - vert.setT(vert.getT() + amount); - } - if(visual.getTDeletion() != null) { - visual.setTDeletion(visual.getTDeletion() + amount); - } - - var propTransIter = visual.getPropertyTransformsIterator(); - while(propTransIter.hasNext()) { - var propTrans = propTransIter.next(); - propTrans.setT(propTrans.getT() + amount); - } - - var spatTransIter = visual.getSpatialTransformsIterator(); - while(spatTransIter.hasNext()) { - var spatTrans = spatTransIter.next(); - spatTrans.setT(spatTrans.getT() + amount); - } - } - - // Shift an array of visuals by the given amount (visuals time) - this.shiftVisuals = function(visuals, amount) { - if(visuals.length==0) { - return; - }; - for(var i in visuals) { - doShiftVisual(visuals[i], amount); - }; - - undoManager.registerUndoAction(self, self.shiftVisuals, [visuals, -amount]); - } - /////////////////////////////////////////////////////////////////////////////// // Helper functions /////////////////////////////////////////////////////////////////////////////// @@ -467,23 +375,28 @@ var Visual = function(tmin, props) { var tDeletion = null; var propertyTransforms = []; var spatialTransforms = []; - var tMin = tmin; + var tMin = TimeManager.getVisualInstance().getAndRegisterTimeInstance(tmin); var properties = props; this.getType = function() { return type; } this.getHyperlink = function() { return hyperlink; } - this.getTDeletion = function() { return tDeletion; } + this.getTDeletion = function() { return tDeletion ? tDeletion.get() : null; } this.getPropertyTransforms = function() { return propertyTransforms; } this.getSpatialTransforms = function() { return spatialTransforms; } - this.getTMin = function() { return tMin; } + this.getTMin = function() { return tMin.get(); } this.getProperties = function() { return properties; } this.setType = function(newType) { type = newType; } this.setHyperlink = function(newHyperlink) { hyperlink = newHyperlink; } - this.setTDeletion = function(newTDeletion) { tDeletion = newTDeletion; } + this.setTDeletion = function(newTDeletion) { + if (tDeletion) + tDeletion.set(newTDeletion); + else + tDeletion = TimeManager.getVisualInstance().getAndRegisterTimeInstance(newTDeletion); + } this.setPropertyTransforms = function(newTransforms) { propertyTransforms = newTransforms; } this.setSpatialTransforms = function(newTransforms) { spatialTransforms = newTransforms; } - this.setTMin = function(newTMin) { tMin = newTMin; } + this.setTMin = function(newTMin) { tMin.set(newTMin); } this.setProperties = function(newProperties) { properties = newProperties; } this.getPropertyTransformsIterator = function() { return new Iterator(propertyTransforms); } @@ -640,10 +553,10 @@ var Visual = function(tmin, props) { // Likewise, visuals are deleted ON their tDeletion, not later // Therefore, when time his tDeletion, the visual is no longer visible this.isVisible = function(tVisual) { - if (tMin > tVisual) { + if (tMin.get() > tVisual) { return false; } - if (tDeletion != null && tDeletion <= tVisual) { + if (tDeletion != null && tDeletion.get() <= tVisual) { return false; } return true; @@ -811,19 +724,19 @@ var VisualPropertyTransform = function(property_name, new_val, time) { var self = this; var propertyName = property_name; var value = new_val; - var t = time; + var t = TimeManager.getVisualInstance().getAndRegisterTimeInstance(time); //no setter for the property, users should instead just create a new transform for a different property this.getPropertyName = function() { return propertyName; } this.getValue = function() { return value; } - this.getTime = function() { return t; } + this.getTime = function() { return t.get(); } // Saving the model to JSON this.saveToJSON = function() { var json_object = { property_name: propertyName, value: value, - t: t + t: t.get() }; return json_object; @@ -843,18 +756,18 @@ VisualPropertyTransform.propertyNames = { var VisualSpatialTransform = function(mat, time) { var self = this; var matrix = mat; // math.js matrix - var t = time; + var t = TimeManager.getVisualInstance().getAndRegisterTimeInstance(time); this.getMatrix = function() { return matrix; } this.setMatrix = function(newMatrix) { matrix = newMatrix; } - this.getTime = function() { return time; } - this.setTime = function(newTime) { t = newTime; } + this.getTime = function() { return t.get(); } + this.setTime = function(newTime) { t.set(newTime); } // Saving the model to JSON this.saveToJSON = function() { var json_object = { matrix: matrix, - t: t + t: t.get() }; return json_object; @@ -874,22 +787,22 @@ var Vertex = function(myX, myY, myT, myP) { var self = this; var x = myX; var y = myY; - var t = myT; + var t = TimeManager.getVisualInstance().getAndRegisterTimeInstance(myT); var p = myP; this.getX = function() { return x; } this.getY = function() { return y; } - this.getT = function() { return t; } + this.getT = function() { return t.get(); } this.getP = function() { return p; } this.setX = function(newX) { x = newX; } this.setY = function(newY) { y = newY; } - this.setT = function(newT) { t = newT; } + this.setT = function(newT) { t.set(newT); } this.setP = function(newP) { p = newP; } // Returns a boolean indicating whether the vertex is visible at the given time this.isVisible = function(tVisual) { - return t <= tVisual; + return t.get() <= tVisual; }; // Saving the model to JSON @@ -897,7 +810,7 @@ var Vertex = function(myX, myY, myT, myP) { var json_object = { x: x, y: y, - t: t, + t: t.get(), p: p }; From e6bf0f3590e7786e067fb4068c40c521c7593937 Mon Sep 17 00:00:00 2001 From: Richard Lu Date: Tue, 10 Nov 2015 23:16:54 -0500 Subject: [PATCH 2/5] WIP --- src/index.html | 1 + src/lecture_controller.js | 98 ++++----------------- src/recording/recording_controller.js | 122 +++++++++++++++++--------- 3 files changed, 100 insertions(+), 121 deletions(-) diff --git a/src/index.html b/src/index.html index 843c9ae..cd223b1 100644 --- a/src/index.html +++ b/src/index.html @@ -28,6 +28,7 @@ + diff --git a/src/lecture_controller.js b/src/lecture_controller.js index 2ea6b76..daec48e 100644 --- a/src/lecture_controller.js +++ b/src/lecture_controller.js @@ -10,6 +10,7 @@ var LectureController = function() { var visualsController = null; var audioController = null; var retimerController = null; + var recordingController = null; // State for pen parameters this.pressure = false; @@ -73,6 +74,7 @@ var LectureController = function() { visualsController = new VisualsController(lectureModel.getVisualsModel(), lectureModel.getRetimerModel()); audioController = new AudioController(lectureModel.getAudioModel()); retimerController = new RetimerController(lectureModel.getRetimerModel(), visualsController, audioController); + recordingController = new RecordingController(visualsController, audioController, retimerController, timeController, undoManager); // Setup input loadInputHandlers(); @@ -316,7 +318,7 @@ var LectureController = function() { // Returns true if a recording is in progress this.isRecording = function() { - return (timeController.isTiming() && !self.isPlaying()); + return recordingController ? recordingController.isRecording() : false; }; // Returns true if a playback is in progress @@ -324,70 +326,18 @@ var LectureController = function() { return (timeController.isTiming() && playbackEndTime >= 0 && playbackEndTimeout); }; - // Start recording and notify other controllers - // Returns true if it succeeds - this.startRecording = function() { - - // Start the timing and exit if it fails - if (!timeController.startTiming()) { - return false; - }; - - // Start the undo hierarchy so that an undo after recording ends will undo the entire recording - undoManager.beginGrouping(); - - var beginTime = timeController.getBeginTime(); - - // On undo, revert to the begin time - undoManager.registerUndoAction(self, changeTime, [beginTime]); - - // Notify controllers depending on the recording types - if (self.recordingTypeIsVisuals()) { // visuals - visualsController.startRecording(beginTime); - }; - if (self.recordingTypeIsAudio()) { // audio - audioController.startRecording(beginTime); - }; - retimerController.beginRecording(beginTime); - - // Update the UI buttons - updateButtons(); - - return true; + // Start recording; return true if it succeeds + var startRecording = function() { + return recordingController.startRecording( + self.recordingTypeIsVisuals(), + self.recordingTypeIsAudio(), + updateButtons + ); }; - // Stop recording and notify other controllers - // Returns true if it succeeds - this.stopRecording = function() { - - // Only stop if we are currently recording - if (!self.isRecording()) { - return false; - }; - - // Stop the timing and exit if it fails - if (!timeController.stopTiming()) { - return false; - }; - - var endTime = timeController.getEndTime(); - - // Notify controllers depending on the recording types - if (self.recordingTypeIsVisuals()) { // visuals - visualsController.stopRecording(endTime); - }; - if (self.recordingTypeIsAudio()) { // audio - audioController.stopRecording(endTime); - }; - retimerController.endRecording(endTime); - - // End the undo hierarchy so that an undo will undo the entire recording - undoManager.endGrouping(); - - // Update the UI buttons - updateButtons(); - - return true; + // Stop recording; return true if it succeeds + var stopRecording = function() { + return recordingController.stopRecording(updateButtons); }; // Start playback and and notify other controllers @@ -413,7 +363,7 @@ var LectureController = function() { console.log('playback begin time: ' + beginTime); console.log('playback end time: ' + playbackEndTime); - // Set the timeout to stop the recording + // Set the timeout to stop playback playbackEndTimeout = setTimeout(self.stopPlayback, playbackEndTime - beginTime); // Notify controllers @@ -491,18 +441,6 @@ var LectureController = function() { draw(); }; - // When undoing or redoing a recording, the time should also revert back to - // the previous time. This function helps achieve that by wrapping around - // a call to the time controller and the undo manager. - var changeTime = function(time) { - - // Create an undo call to revert to the previous time - undoManager.registerUndoAction(self, changeTime, [timeController.getTime()]); - - // Update the time - timeController.updateTime(time); - }; - /////////////////////////////////////////////////////////////////////////////// // Input Handlers and Tools // @@ -529,10 +467,10 @@ var LectureController = function() { }; // Start recording button handler - $('#'+startRecordButtonID).click(self.startRecording); + $('#'+startRecordButtonID).click(startRecording); // Stop recording button handler - $('#'+stopRecordButtonID).click(self.stopRecording); + $('#'+stopRecordButtonID).click(stopRecording); // Start playback button handler $('#'+startPlaybackButtonID).click(self.startPlayback); @@ -579,6 +517,8 @@ var LectureController = function() { $('#'+startRecordButtonID).removeClass(hiddenClass); $('#'+stopRecordButtonID).addClass(hiddenClass); }; + + // Hide/unhide the playback start/stop buttons if (self.isPlaying()) { $('#'+startPlaybackButtonID).addClass(hiddenClass); $('#'+stopPlaybackButtonID).removeClass(hiddenClass); @@ -587,8 +527,6 @@ var LectureController = function() { $('#'+stopPlaybackButtonID).addClass(hiddenClass); }; - // Hide/unhide the playback start/stop buttons - // Enable or disable the undo/redo buttons if (undoManager.canUndo()) { $('#'+undoButtonID).removeClass(hiddenClass); diff --git a/src/recording/recording_controller.js b/src/recording/recording_controller.js index 4cff0bb..e191737 100644 --- a/src/recording/recording_controller.js +++ b/src/recording/recording_controller.js @@ -1,58 +1,98 @@ "use strict"; -var RecordingController = function() { - var dirtyVisuals = []; +var RecordingController = function(visualsController, audioController, retimerController, timeController, undoManager) { + var self = {}; - // Creates wrappers around the visuals that keeps track of their previous time - // and the times of their vertices. Then move the visuals to positive infinity. - // Used at the end of a recording so that the visuals will not overlap with the ones being recorded. - // Only processes visuals in the current slide after the current time. - this.setDirtyVisuals = function(currentVisualTime) { + var isRecording = false; + var isRecordingVisuals = false; + var isRecordingAudio = false; - var currentSlide = self.getSlideAtTime(currentVisualTime); + // Returns true if a recording is in progress + self.isRecording = function() { + return isRecording; + }; + + // Start recording and notify other controllers + // Returns true and executes callback if it succeeds + self.startRecording = function(shouldRecordVisuals, shouldRecordAudio, callback) { - // Iterate over all the visuals - var visuals_iterator = currentSlide.getVisualsIterator(); - while (visuals_iterator.hasNext()) { - var visual = visuals_iterator.next(); + // Start the timing and exit if it fails + if (!timeController.startTiming()) { + return false; + }; - // Only make the visual dirty if the time is greater than the current time - if(visual.getTMin() <= currentVisualTime) { - continue; - }; + isRecording = true; - // Create the wrapper - var wrapper = { - visual: visual, - tMin: visual.getTMin(), - }; + // Start the undo hierarchy so that an undo after recording ends will undo the entire recording + undoManager.beginGrouping(); - // Move tMin to infinity - visual.setTMin(Number.POSITIVE_INFINITY); //could alternatively say Number.MAX_VALUE or Number.MAX_SAFE_INTEGER + var beginTime = timeController.getBeginTime(); - // Add the wrapper to dirty visuals - dirtyVisuals.push(wrapper); + // On undo, revert to the begin time + undoManager.registerUndoAction(self, changeTime, [beginTime]); + + // Notify controllers depending on the recording types + if (shouldRecordVisuals) { // visuals + visualsController.startRecording(beginTime); + isRecordingVisuals = true; + }; + if (shouldRecordAudio) { // audio + audioController.startRecording(beginTime); + isRecordingAudio = true; + }; + retimerController.beginRecording(beginTime); - }; // end of iterating over visuals + // Execute the callback + callback(); + return true; }; - // Restore to the previous time and add the shift amount. - // Used at the end of a recording during insertion to shift visuals forward. - this.cleanVisuals = function(shift_amount) { - - // Restore the original times from the wrapper - var visuals = []; - for(var i in dirtyVisuals) { - var dirtyWrapper = dirtyVisuals[i]; - var visual = dirtyWrapper.visual; - visuals.push(visual); - visual.setTMin(dirtyWrapper.tMin); + // Stop recording and notify other controllers + // Returns true and executes callback if it succeeds + self.stopRecording = function(callback) { + + // Only stop if we are currently recording + if (!isRecording) { + return false; }; - // Perform a shift on all of the visuals that were dirty - self.shiftVisuals(visuals, shift_amount); + // Stop the timing and exit if it fails + if (!timeController.stopTiming()) { + return false; + }; + + isRecording = false; + + var endTime = timeController.getEndTime(); - // Clear the dirty visuals - dirtyVisuals = []; + // Notify controllers depending on the recording types + if (isRecordingVisuals) { // visuals + visualsController.stopRecording(endTime); + }; + if (isRecordingAudio) { // audio + audioController.stopRecording(endTime); + }; + retimerController.endRecording(endTime); + + // End the undo hierarchy so that an undo will undo the entire recording + undoManager.endGrouping(); + + // Execute the callback + callback(); + return true; }; + + // When undoing or redoing a recording, the time should also revert back to + // the previous time. This function helps achieve that by wrapping around + // a call to the time controller and the undo manager. + var changeTime = function(time) { + + // Create an undo call to revert to the previous time + undoManager.registerUndoAction(self, changeTime, [timeController.getTime()]); + + // Update the time + timeController.updateTime(time); + }; + + return self; } From 3cd13a4aa11db21bc59fe732044c2ff3d2ce00fd Mon Sep 17 00:00:00 2001 From: Richard Lu Date: Wed, 11 Nov 2015 00:02:12 -0500 Subject: [PATCH 3/5] de-globalize lecture controller, move tools controller to top level --- src/audio/audio_controller.js | 28 ++++++++-------- src/lecture_controller.js | 48 ++++++++++++++------------- src/recording/recording_controller.js | 8 ++++- src/retimer/retimer_controller.js | 33 ++++-------------- src/retimer/thumbnails_controller.js | 4 +-- src/visuals/tools_controller.js | 20 +++++------ src/visuals/visuals_controller.js | 30 ++--------------- 7 files changed, 67 insertions(+), 104 deletions(-) diff --git a/src/audio/audio_controller.js b/src/audio/audio_controller.js index 6894697..81f816d 100644 --- a/src/audio/audio_controller.js +++ b/src/audio/audio_controller.js @@ -5,7 +5,7 @@ // Translates user input into actions that modify the audio model // Responsible for drawing the audio timeline and displaying updates "use strict"; -var AudioController = function(audio_model) { +var AudioController = function(audio_model, timeController, globalState) { /////////////////////////////////////////////////////////////////////////////// // Member vars @@ -170,7 +170,7 @@ var AudioController = function(audio_model) { this.startRecording = function(currentTime) { // This method can only be called if the time controller is recording and a recording is not currently in progress - if ( !lectureController.isRecording() || isAudioRecording ) { + if ( !globalState.isRecording() || isAudioRecording ) { console.error("Cannot begin recording"); return; }; @@ -189,12 +189,12 @@ var AudioController = function(audio_model) { // End the recording (only applies if there is an ongoing recording) // Callback method registered to the time controller this.stopRecording = function(currentTime) { - var beginTime = lectureController.getTimeController().getBeginTime(); + var beginTime = timeController.getBeginTime(); var endTime = currentTime; console.log("End audio recording (" + beginTime + ", " + endTime + ")"); // This method can only be called if the time controller is not recording and a recording is currently in progress - if ( lectureController.isRecording() || !isAudioRecording ) { + if ( globalState.isRecording() || !isAudioRecording ) { console.error("Cannot end recording"); return; }; @@ -228,7 +228,7 @@ var AudioController = function(audio_model) { console.log("AudioController: Start playback"); // This method can only be called if the time controller is playing and a recording is not currently in progress - if ( !lectureController.isPlaying() || isAudioRecording ) { + if ( !globalState.isPlaying() || isAudioRecording ) { console.error("Cannot begin playback"); return; }; @@ -248,7 +248,7 @@ var AudioController = function(audio_model) { console.log("AudioController: Stop playback"); // This method can only be called if the time controller is not playing and a recording is not currently in progress - if ( lectureController.isPlaying() || isAudioRecording ) { + if ( globalState.isPlaying() || isAudioRecording ) { console.error("Cannot end playback"); return; }; @@ -366,17 +366,17 @@ var AudioController = function(audio_model) { // Calculate the length of the timeline in seconds. // This should be twice as long as the lecture length, or at least 100 seconds. // During a recording, the time controller cursor also counts as length. - var lecture_length = lectureController.getLectureModel().getLectureDuration(); + var lecture_length = globalState.getLectureDuration(); - if (lectureController.isRecording()) { - lecture_length = Math.max(lectureController.getTimeController().getTime(), lecture_length); + if (globalState.isRecording()) { + lecture_length = Math.max(timeController.getTime(), lecture_length); } var old_timeline_length = timelineLengthSeconds; timelineLengthSeconds = Math.max(2*lecture_length/1000, minumum_timeline_seconds); // During a recording, if the new timeline length is less than the old timeline length, // then the old value of the timeline is used. - if (lectureController.isRecording() && timelineLengthSeconds < old_timeline_length) { + if (globalState.isRecording() && timelineLengthSeconds < old_timeline_length) { timelineLengthSeconds = old_timeline_length; }; @@ -481,7 +481,7 @@ var AudioController = function(audio_model) { drag: function() { // Update the time controller during dragging var newTrackTime = self.pixelsToMilliseconds($('#'+playheadID).position().left); - lectureController.getTimeController().updateTime(newTrackTime); + timeController.updateTime(newTrackTime); } }); @@ -682,7 +682,7 @@ var AudioController = function(audio_model) { var tracksContainer = $('#'+tracksContainerID); // Clicking to update time is not allowed during a timing - if (lectureController.getTimeController().isTiming()) { + if (timeController.isTiming()) { return; } @@ -707,7 +707,7 @@ var AudioController = function(audio_model) { // Use the position to calculate and update the time that is represented by the click var time = self.pixelsToMilliseconds(x); - lectureController.getTimeController().updateTime(time); + timeController.updateTime(time); }; @@ -782,7 +782,7 @@ var AudioController = function(audio_model) { ); // Register callbacks with the time controller - lectureController.getTimeController().addUpdateTimeCallback(updatePlayheadTime); + timeController.addUpdateTimeCallback(updatePlayheadTime); // Mousedown listener for audio timeline $('#'+timelineID).click(timelineClicked); diff --git a/src/lecture_controller.js b/src/lecture_controller.js index daec48e..7f25e46 100644 --- a/src/lecture_controller.js +++ b/src/lecture_controller.js @@ -11,6 +11,7 @@ var LectureController = function() { var audioController = null; var retimerController = null; var recordingController = null; + var toolsController = null; // State for pen parameters this.pressure = false; @@ -57,6 +58,18 @@ var LectureController = function() { // The lecture controller can be initialized from scratch or from a saved file. /////////////////////////////////////////////////////////////////////////////// + var globalState = { + isRecording: function() { + return recordingController ? recordingController.isRecording() : false; + }, + getLectureDuration: function() { + return lectureModel ? lectureModel.getLectureDuration() : 0; + }, + isPlaying: function() { + return timeController ? (timeController.isTiming() && playbackEndTime >= 0 && playbackEndTimeout) : false; + } + }; + this.init = function() { // Create the time controller, which is responsible for handling the current lecture time (also known as audio time) @@ -71,10 +84,11 @@ var LectureController = function() { // Initialize the controllers with their respective models. // These controllers might register for time controller or undo manager callbacks, so they should be initialized // after initializing the time controller and undo manager. - visualsController = new VisualsController(lectureModel.getVisualsModel(), lectureModel.getRetimerModel()); - audioController = new AudioController(lectureModel.getAudioModel()); - retimerController = new RetimerController(lectureModel.getRetimerModel(), visualsController, audioController); - recordingController = new RecordingController(visualsController, audioController, retimerController, timeController, undoManager); + visualsController = new VisualsController(lectureModel.getVisualsModel(), lectureModel.getRetimerModel(), timeController); + audioController = new AudioController(lectureModel.getAudioModel(), timeController, globalState); + retimerController = new RetimerController(lectureModel.getRetimerModel(), visualsController, audioController, timeController, globalState); + toolsController = new ToolsController(visualsController, globalState); + recordingController = new RecordingController(visualsController, audioController, retimerController, toolsController, timeController, undoManager); // Setup input loadInputHandlers(); @@ -316,16 +330,6 @@ var LectureController = function() { // methods in the visuals/retimer/audio controllers to signal the event. /////////////////////////////////////////////////////////////////////////////// - // Returns true if a recording is in progress - this.isRecording = function() { - return recordingController ? recordingController.isRecording() : false; - }; - - // Returns true if a playback is in progress - this.isPlaying = function() { - return (timeController.isTiming() && playbackEndTime >= 0 && playbackEndTimeout); - }; - // Start recording; return true if it succeeds var startRecording = function() { return recordingController.startRecording( @@ -367,8 +371,8 @@ var LectureController = function() { playbackEndTimeout = setTimeout(self.stopPlayback, playbackEndTime - beginTime); // Notify controllers - visualsController.startPlayback(beginTime); audioController.startPlayback(beginTime); + toolsController.startPlayback(); // Update the UI buttons updateButtons(); @@ -381,7 +385,7 @@ var LectureController = function() { this.stopPlayback = function() { // Only stop if we are currently playing - if (!self.isPlaying()) { + if (!globalState.isPlaying()) { return false; }; @@ -398,8 +402,8 @@ var LectureController = function() { var endTime = timeController.getEndTime(); // Notify controllers - visualsController.stopPlayback(endTime); audioController.stopPlayback(endTime); + toolsController.stopPlayback(); // Update the UI buttons updateButtons(); @@ -510,7 +514,7 @@ var LectureController = function() { var updateButtons = function() { // Hide/unhide the record start/stop buttons - if (self.isRecording()) { + if (recordingController.isRecording()) { $('#'+startRecordButtonID).addClass(hiddenClass); $('#'+stopRecordButtonID).removeClass(hiddenClass); } else { @@ -519,7 +523,7 @@ var LectureController = function() { }; // Hide/unhide the playback start/stop buttons - if (self.isPlaying()) { + if (globalState.isPlaying()) { $('#'+startPlaybackButtonID).addClass(hiddenClass); $('#'+stopPlaybackButtonID).removeClass(hiddenClass); } else { @@ -609,8 +613,7 @@ var LectureController = function() { // Main: The single entry point for the entire application /////////////////////////////////////////////////////////////////////////////// -// Define the global lecture controller and undo manager objects -var lectureController; +// Define the global and undo manager objects var undoManager; // Objects and controllers should only be created after the document is ready @@ -620,6 +623,5 @@ $(document).ready(function() { undoManager = new UndoManager(); // Create and initialize the lecture controller - lectureController = new LectureController(); - lectureController.init(); + new LectureController().init(); }); diff --git a/src/recording/recording_controller.js b/src/recording/recording_controller.js index e191737..f992e13 100644 --- a/src/recording/recording_controller.js +++ b/src/recording/recording_controller.js @@ -1,6 +1,6 @@ "use strict"; -var RecordingController = function(visualsController, audioController, retimerController, timeController, undoManager) { +var RecordingController = function(visualsController, audioController, retimerController, toolsController, timeController, undoManager) { var self = {}; var isRecording = false; @@ -42,6 +42,9 @@ var RecordingController = function(visualsController, audioController, retimerCo }; retimerController.beginRecording(beginTime); + // Signal the tools controller + toolsController.startRecording(); + // Execute the callback callback(); return true; @@ -74,6 +77,9 @@ var RecordingController = function(visualsController, audioController, retimerCo }; retimerController.endRecording(endTime); + // Signal the tools controller + toolsController.stopRecording(); + // End the undo hierarchy so that an undo will undo the entire recording undoManager.endGrouping(); diff --git a/src/retimer/retimer_controller.js b/src/retimer/retimer_controller.js index c18868f..ee1c8ec 100644 --- a/src/retimer/retimer_controller.js +++ b/src/retimer/retimer_controller.js @@ -5,13 +5,13 @@ */ "use strict"; -var RetimerController = function(retimer_model, visuals_controller, audio_controller) { +var RetimerController = function(retimer_model, visuals_controller, audio_controller, timeController, globalState) { var self = this; var retimerModel = retimer_model; var audioController = audio_controller; - var thumbnailsController = new ThumbnailsController(visuals_controller, audio_controller, retimer_model); + var thumbnailsController = new ThumbnailsController(visuals_controller, audio_controller, retimer_model, globalState); // Selection dragging var selectionX; @@ -91,7 +91,7 @@ var RetimerController = function(retimer_model, visuals_controller, audio_contro var drawTickMarks = function(){ var canvas = $('#'+constraintsCanvasID); - var max_tVis = retimerModel.getVisualTime(lectureController.getLectureModel().getLectureDuration()); + var max_tVis = retimerModel.getVisualTime(globalState.getLectureDuration()); for(var tVis = 0; tVis < max_tVis; tVis += 150) { var tAud = retimerModel.getAudioTime(tVis); @@ -130,7 +130,7 @@ var RetimerController = function(retimer_model, visuals_controller, audio_contro // $('#'+constraintsCanvasID).clearCanvas(); // Calculate the width of the canvas based on the total lecture duration - var max_time = lectureController.getLectureModel().getLectureDuration(); + var max_time = globalState.getLectureDuration(); var new_width = audioController.millisecondsToPixels(max_time); // Create and add the new canvas @@ -246,27 +246,6 @@ var RetimerController = function(retimer_model, visuals_controller, audio_contro // TODO: figure out if this is working properly with the interpolation (possible with getting the visual from audio) var addConstraint = function(audio_time, constraint_type) { - // FORUMULAS - // // interp_factor = (curr_time-prev_time)/(next_time-prevX) - // // constraint_tVis = (next_time-prev_time)*interp_factor + prev_tVis - // // constraint_tAud = (next_time-prev_time)*interp_factor + prev_tAud - - // // Make sure to convert this from the lecture duration to audio duration - // var audio_scale = pentimento.lectureController.getLectureDuration()/$('#retimer_constraints').width(); - // var tAud = xVal * audio_scale; - // var tVis = pentimento.lectureController.retimingController.getVisualTime(tAud); - // // var prev_const = window.opener.pentimento.lectureController.retimingController.getPreviousConstraint(curr_audio_time, "Audio"); - // // var next_const = window.opener.pentimento.lectureController.retimingController.getNextConstraint(curr_audio_time, "Audio"); - // // var prevTime = prev_const.getTVisual(); - // // var nextTime = next_const.getTVisual(); - // // var prevX = 0; - // // var nextX = $('#retimer_constraints').width(); - // // var interp = (nextTime-prevTime)/(nextX-prevX); - // // var tVis = interp*xVal; - // // var tAud = interp*xVal; - // var constraint = new Constraint(tVis, tAud, ConstraintTypes.Manual); - // pentimento.lectureController.retimingController.addConstraint(constraint); - // Convert to visual time var visual_time = retimerModel.getVisualTime(audio_time); @@ -533,7 +512,7 @@ var RetimerController = function(retimer_model, visuals_controller, audio_contro }); // Update the time so the visuals will be drawn at the current time - lectureController.getTimeController().updateTime(newTAud); + timeController.updateTime(newTAud); }; // When dragging stops, update the visuals or audio depending on whether the drag is top or bottom @@ -619,7 +598,7 @@ var RetimerController = function(retimer_model, visuals_controller, audio_contro addConstraint(currentTime, ConstraintTypes.Automatic); // If recording is an insertion - if (currentTime < lectureController.getLectureModel().getLectureDuration()){ + if (currentTime < globalState.getLectureDuration()){ insertionStartTime = currentTime; } } diff --git a/src/retimer/thumbnails_controller.js b/src/retimer/thumbnails_controller.js index fc048e7..58df039 100644 --- a/src/retimer/thumbnails_controller.js +++ b/src/retimer/thumbnails_controller.js @@ -5,7 +5,7 @@ */ "use strict"; -var ThumbnailsController = function(visuals_controller, audio_controller, retimer_model) { +var ThumbnailsController = function(visuals_controller, audio_controller, retimer_model, globalState) { var self = this; var visualsController = visuals_controller; @@ -37,7 +37,7 @@ var ThumbnailsController = function(visuals_controller, audio_controller, retime // Clear the thumbnails div $('#'+thumbnailsDivID).html(''); - var audioMaxTime = lectureController.getLectureModel().getLectureDuration(); + var audioMaxTime = globalState.getLectureDuration(); var total_width = audioController.millisecondsToPixels(audioMaxTime); if (total_width <= 0) { return; diff --git a/src/visuals/tools_controller.js b/src/visuals/tools_controller.js index 59dc120..8079c3b 100644 --- a/src/visuals/tools_controller.js +++ b/src/visuals/tools_controller.js @@ -3,7 +3,7 @@ // The responsibility of actually modifying the visuals is delegated to the visuals controller. "use strict"; -var ToolsController = function(visuals_controller) { +var ToolsController = function(visuals_controller, globalState) { var self = this; var visualsController = null; @@ -131,7 +131,7 @@ var ToolsController = function(visuals_controller) { case selectTool: // In recording mode, save the tool as the recording tool, // and in editing mode, save the tool as the editing tool. - if (lectureController.isRecording()) { + if (globalState.isRecording()) { recordingTool = tool; } else { editingTool = tool; @@ -157,7 +157,7 @@ var ToolsController = function(visuals_controller) { break; case deleteTool: - if (lectureController.isRecording()) { + if (globalState.isRecording()) { visualsController.recordingDeleteSelection(); } else { visualsController.editingDeleteSelection(); @@ -193,7 +193,7 @@ var ToolsController = function(visuals_controller) { visualsController.canvas.off('touchend'); // In recording mode, activate the recording tool, // and in editing mode, activate the editing tool. - var toolToActivate = ( lectureController.isRecording() ? recordingTool : editingTool ); + var toolToActivate = ( globalState.isRecording() ? recordingTool : editingTool ); console.log('tool to activate: ' + toolToActivate); // Register the callback depending on which tool is active @@ -398,7 +398,7 @@ var ToolsController = function(visuals_controller) { }; // If it is not during a recording, then we manually need to tell the controller to redraw - if (!lectureController.isRecording()) { + if (!globalState.isRecording()) { visualsController.drawVisuals(currentTime); }; @@ -445,7 +445,7 @@ var ToolsController = function(visuals_controller) { var transform_matrix = calculateTranslateMatrix(originalTranslatePosition, new_position); // Apply the matrix to the selected visuals - if (lectureController.isRecording()) { + if (globalState.isRecording()) { visualsController.recordingSpatialTransformSelection(transform_matrix); } else { visualsController.editingSpatialTransformSelection(transform_matrix); @@ -464,7 +464,7 @@ var ToolsController = function(visuals_controller) { var transform_matrix = calculateScaleMatrix(ui.originalPosition, ui.originalSize, ui.position, ui.size); // Apply the matrix to the selected visuals - if (lectureController.isRecording()) { + if (globalState.isRecording()) { visualsController.recordingSpatialTransformSelection(transform_matrix); } else { visualsController.editingSpatialTransformSelection(transform_matrix); @@ -479,7 +479,7 @@ var ToolsController = function(visuals_controller) { // Changes the width of the selection if there is a selection if (visualsController.selection.length !== 0) { - if (lectureController.isRecording()) { + if (globalState.isRecording()) { var transform = new VisualPropertyTransform('width', new_width, visualsController.currentVisualTime()); visualsController.recordingPropertyTransformSelection(transform); @@ -496,7 +496,7 @@ var ToolsController = function(visuals_controller) { // Change the color of the drawing tool var new_color = new_spectrum_color.toHexString(); - if (lectureController.isRecording()) { + if (globalState.isRecording()) { strokeColor = new_color; }; @@ -504,7 +504,7 @@ var ToolsController = function(visuals_controller) { // This check needs to happen in order to fix the problem with double events being triggered. if (visualsController.selection.length !== 0) { - if (lectureController.isRecording()) { + if (globalState.isRecording()) { var transform = new VisualPropertyTransform('color', new_color, visualsController.currentVisualTime()); visualsController.recordingPropertyTransformSelection(transform); } else { diff --git a/src/visuals/visuals_controller.js b/src/visuals/visuals_controller.js index 12e7619..db82fae 100644 --- a/src/visuals/visuals_controller.js +++ b/src/visuals/visuals_controller.js @@ -3,11 +3,10 @@ //um.add is called, it should have an updateVisuals inside of the function if necessary "use strict"; -var VisualsController = function(visuals_model, retimer_model) { +var VisualsController = function(visuals_model, retimer_model, timeController) { var self = this; var visualsModel = null; var retimerModel = null; - var toolsController = null; var renderer = null; // Variables used for keeping track of recording information @@ -61,7 +60,6 @@ var VisualsController = function(visuals_model, retimer_model) { // // Handlers for when recording begins and ends. // Includes helper functions for recording logic. - // Informs the tools controller of changes in recording status. /////////////////////////////////////////////////////////////////////////////// this.startRecording = function(currentTime) { @@ -81,9 +79,6 @@ var VisualsController = function(visuals_model, retimer_model) { originSlideDuration = originSlide.getDuration(); // TODO move to recording controller TimeManager.getVisualInstance().shiftAfterBy(slideBeginTime, 24*60*60*1000); - - // Signal the tools controller - toolsController.startRecording(); }; this.stopRecording = function(currentTime) { @@ -102,23 +97,6 @@ var VisualsController = function(visuals_model, retimer_model) { slideBeginTime = NaN; originSlide = null; originSlideDuration = null; - - // Signal the tools controller - toolsController.stopRecording(); - }; - - /////////////////////////////////////////////////////////////////////////////// - // Playback of Visuals - // - // Nothing needs to be done except letting the tools controller know - /////////////////////////////////////////////////////////////////////////////// - - this.startPlayback = function(currentTime) { - toolsController.startPlayback(); - }; - - this.stopPlayback = function(currentTime) { - toolsController.stopPlayback(); }; @@ -128,7 +106,7 @@ var VisualsController = function(visuals_model, retimer_model) { // Shortcut for the time controller time converted to visual time through the retimer this.currentVisualTime = function() { - return retimerModel.getVisualTime(lectureController.getTimeController().getTime()); + return retimerModel.getVisualTime(timeController.getTime()); }; // Get the slide at the current time @@ -343,12 +321,10 @@ var VisualsController = function(visuals_model, retimer_model) { // Get the context from the canvas self.context = self.canvas[0].getContext('2d'); - // Initialize other controllers - toolsController = new ToolsController(self); renderer = new Renderer(self); // Register callbacks for the time controller - lectureController.getTimeController().addUpdateTimeCallback(self.drawVisuals); + timeController.addUpdateTimeCallback(self.drawVisuals); }; From 4abeea1254baec544113d82d3b5614a86734aa24 Mon Sep 17 00:00:00 2001 From: Richard Lu Date: Wed, 25 Nov 2015 00:25:43 -0500 Subject: [PATCH 4/5] factor out recording state from retimer and visuals --- src/recording/recording_controller.js | 31 ++++++++++++-- src/retimer/retimer_controller.js | 55 +------------------------ src/retimer/retimer_model.js | 36 +++-------------- src/visuals/visuals_controller.js | 58 ++------------------------- src/visuals/visuals_model.js | 9 +++++ 5 files changed, 48 insertions(+), 141 deletions(-) diff --git a/src/recording/recording_controller.js b/src/recording/recording_controller.js index f992e13..a2d5566 100644 --- a/src/recording/recording_controller.js +++ b/src/recording/recording_controller.js @@ -33,14 +33,26 @@ var RecordingController = function(visualsController, audioController, retimerCo // Notify controllers depending on the recording types if (shouldRecordVisuals) { // visuals - visualsController.startRecording(beginTime); + + if (!visualsController.currentSlide()) { + console.error("there is no current slide"); + return; + } + + visualsController.selection = []; + + TimeManager.getVisualInstance().shiftAfterBy(beginTime, 24*60*60*1000); + isRecordingVisuals = true; }; + if (shouldRecordAudio) { // audio audioController.startRecording(beginTime); isRecordingAudio = true; }; - retimerController.beginRecording(beginTime); + + TimeManager.getAudioInstance().shiftAfterBy(beginTime, 24*60*60*1000); + retimerController.addConstraint(beginTime, ConstraintTypes.Automatic); // Signal the tools controller toolsController.startRecording(); @@ -67,15 +79,26 @@ var RecordingController = function(visualsController, audioController, retimerCo isRecording = false; var endTime = timeController.getEndTime(); + var recordDuration = endTime - timeController.getBeginTime(); // Notify controllers depending on the recording types if (isRecordingVisuals) { // visuals - visualsController.stopRecording(endTime); + visualsController.selection = []; + + var currentSlide = visualsController.currentSlide(); + + currentSlide.setDuration(currentSlide.getDuration() + recordDuration); + + TimeManager.getVisualInstance().shiftAfterBy(24*60*60*1000, -24*60*60*1000 + recordDuration); }; + if (isRecordingAudio) { // audio audioController.stopRecording(endTime); }; - retimerController.endRecording(endTime); + + TimeManager.getAudioInstance().shiftAfterBy(24*60*60*1000, -24*60*60*1000 + recordDuration); + retimerController.addConstraint(endTime, ConstraintTypes.Automatic); + retimerController.redrawConstraints(); // Signal the tools controller toolsController.stopRecording(); diff --git a/src/retimer/retimer_controller.js b/src/retimer/retimer_controller.js index ee1c8ec..7e84e04 100644 --- a/src/retimer/retimer_controller.js +++ b/src/retimer/retimer_controller.js @@ -39,9 +39,6 @@ var RetimerController = function(retimer_model, visuals_controller, audio_contro // Canvas IDs var constraintIDBase = 'constraint_'; - // Insertion begin time (-1 indictes no insertion is occurring) - var insertionStartTime = -1; - /////////////////////////////////////////////////////////////////////////////// // Draw Methods /////////////////////////////////////////////////////////////////////////////// @@ -79,7 +76,7 @@ var RetimerController = function(retimer_model, visuals_controller, audio_contro y -= canvas.offset().top; // Add the constraint to the model and refresh the view - addConstraint(audioController.pixelsToMilliseconds(x), ConstraintTypes.Manual); + self.addConstraint(audioController.pixelsToMilliseconds(x), ConstraintTypes.Manual); // Unbind the click event from the constraints canvas (so that clicking can be used for other functions) canvas.unbind('mousedown', addArrowHandler); @@ -244,7 +241,7 @@ var RetimerController = function(retimer_model, visuals_controller, audio_contro // When a user adds a constraint, add the constraint to the lecture // TODO: figure out if this is working properly with the interpolation (possible with getting the visual from audio) - var addConstraint = function(audio_time, constraint_type) { + this.addConstraint = function(audio_time, constraint_type) { // Convert to visual time var visual_time = retimerModel.getVisualTime(audio_time); @@ -567,54 +564,6 @@ var RetimerController = function(retimer_model, visuals_controller, audio_contro // $('#' + constraintsCanvasID).on('mousedown', selectArea); }; - // Dealing with insertions - var insertionShifting = function(insertionEndTime){ - var insertionDuration = insertionEndTime - insertionStartTime; - - var constraintsToShift = []; - - var constraints = retimerModel.getConstraintsIterator(); - - // Iterate through the constraints and shift them - while(constraints.hasNext()){ - var constraint = constraints.next(); - if (constraint.getTAudio() > insertionStartTime){ - constraintsToShift.push(constraint); - } - }; - - retimerModel.shiftConstraints(constraintsToShift, insertionDuration); - - self.redrawConstraints(); - - insertionStartTime = -1; - }; - - /////////////////////////////////////////////////////////////////////////////// - // Recording Handling - /////////////////////////////////////////////////////////////////////////////// - - this.beginRecording = function(currentTime) { - addConstraint(currentTime, ConstraintTypes.Automatic); - - // If recording is an insertion - if (currentTime < globalState.getLectureDuration()){ - insertionStartTime = currentTime; - } - } - - this.endRecording = function(currentTime) { - // If recording is an insertion, shift things back after the insertion start time - if (insertionStartTime != -1){ - insertionShifting(currentTime); - } - - addConstraint(currentTime, ConstraintTypes.Automatic); - - // Redraw the constraints - self.redrawConstraints(); - } - /////////////////////////////////////////////////////////////////////////////// // Initialization // diff --git a/src/retimer/retimer_model.js b/src/retimer/retimer_model.js index df0f616..c1275f7 100644 --- a/src/retimer/retimer_model.js +++ b/src/retimer/retimer_model.js @@ -11,19 +11,6 @@ var RetimerModel = function() { return constraints; }; - this.makeConstraintDirty = function(constraint) { - constraint.setDisabled(true); - return constraint; - } - - this.cleanConstraints = function(constraints, amount) { - for(var i in constraints) { - var constraint = constraints[i]; - doShiftConstraint(constraint, amount); - constraint.setDisabled(false); - } - }; - // Check to see if the constraint is in a valid position this.checkConstraint = function(constraint) { @@ -158,17 +145,6 @@ var RetimerModel = function() { undoManager.registerUndoAction(self, self.addConstraint, [constraint]); }; - this.shiftConstraints = function(constraints, amount) { - for(var i = 0; i < constraints.length; i++) { - var constraint = constraints[i]; - constraint.setTVisual(constraint.getTVisual()+amount); - constraint.setTAudio(constraint.getTAudio()+amount); - }; - - // For the undo action, reverse shift the constraints - undoManager.registerUndoAction(self, self.shiftConstraints, [constraints, -amount]); - }; - this.getConstraintsIterator = function() { return new Iterator(constraints); }; @@ -292,22 +268,22 @@ var ConstraintTypes = { var Constraint = function(tvis, taud, mytype) { var self = this; - var tVis = tvis; - var tAud = taud; + var tVis = TimeManager.getVisualInstance().getAndRegisterTimeInstance(tvis); + var tAud = TimeManager.getAudioInstance().getAndRegisterTimeInstance(taud); var type = mytype; var disabled = false; - this.getTVisual = function() { return tVis; } - this.getTAudio = function() { return tAud; } + this.getTVisual = function() { return tVis.get(); } + this.getTAudio = function() { return tAud.get(); } this.getType = function() { return type; } this.getDisabled = function() { return disabled; } this.setTVisual = function(newTVis) { - tVis = Math.round(newTVis); + tVis.set(Math.round(newTVis)); }; this.setTAudio = function(newTAud) { - tAud = Math.round(newTAud); + tAud.set(Math.round(newTAud)); }; this.setType = function(newType) { type = newType; } diff --git a/src/visuals/visuals_controller.js b/src/visuals/visuals_controller.js index db82fae..d518306 100644 --- a/src/visuals/visuals_controller.js +++ b/src/visuals/visuals_controller.js @@ -9,11 +9,6 @@ var VisualsController = function(visuals_model, retimer_model, timeController) { var retimerModel = null; var renderer = null; - // Variables used for keeping track of recording information - var originSlide = null; - var originSlideDuration = null; - var slideBeginTime = NaN; - // DOM elements var canvasContainerID = 'sketchpadWrap'; var canvasID = 'sketchpad'; @@ -55,50 +50,6 @@ var VisualsController = function(visuals_model, retimer_model, timeController) { renderer.drawCanvas(self.canvas, context, 0, visuals_time); }; - /////////////////////////////////////////////////////////////////////////////// - // Recording of Visuals - // - // Handlers for when recording begins and ends. - // Includes helper functions for recording logic. - /////////////////////////////////////////////////////////////////////////////// - - this.startRecording = function(currentTime) { - - if (!visualsModel.getSlideAtTime(currentTime)) { - console.error("there is no current slide"); - return; - } - - self.selection = []; - - // slideBeginTime starts as the visuals time that recording began - slideBeginTime = retimerModel.getVisualTime(currentTime); - - // Keep the origin slides and set visuals dirty so we can shift the visuals in these slides when recording ends - originSlide = visualsModel.getSlideAtTime(currentTime); - originSlideDuration = originSlide.getDuration(); - // TODO move to recording controller - TimeManager.getVisualInstance().shiftAfterBy(slideBeginTime, 24*60*60*1000); - }; - - this.stopRecording = function(currentTime) { - self.selection = []; - - var currentSlide = visualsModel.getSlideAtTime(currentTime); - - var slideRecordDuration = retimerModel.getVisualTime(currentTime) - slideBeginTime; - currentSlide.setDuration(currentSlide.getDuration() + slideRecordDuration); - - // Restores the dirty visuals to their former places and adds a shift. - var shiftAmount = originSlide.getDuration() - originSlideDuration; - TimeManager.getVisualInstance().shiftAfterBy(24*60*60*1000, -24*60*60*1000 + shiftAmount); - - // Reset recording variables - slideBeginTime = NaN; - originSlide = null; - originSlideDuration = null; - }; - /////////////////////////////////////////////////////////////////////////////// // Modifying Slides @@ -116,14 +67,13 @@ var VisualsController = function(visuals_model, retimer_model, timeController) { this.addSlide = function() { - // Get the difference in time for when the slide began recording to the current time - var time = self.currentVisualTime(); - var diff = time - slideBeginTime; - // Get the previous slide and create a new slide - var previousSlide = visualsModel.getSlideAtTime(slideBeginTime); + var previousSlide = self.currentSlide(); var previousSlideIndex = visualsModel.getIndexOfSlide(previousSlide); + // Get the difference in time for when the slide began recording to the current time + var diff = self.currentVisualTime() = visualsModel.getSlideBeginTime(previousSlide); + var newSlide = new Slide(); if (!previousSlide) { console.error('previous slide missing'); diff --git a/src/visuals/visuals_model.js b/src/visuals/visuals_model.js index 5daaca2..28bbdc7 100644 --- a/src/visuals/visuals_model.js +++ b/src/visuals/visuals_model.js @@ -68,6 +68,15 @@ var VisualsModel = function(canvas_width, canvas_height) { this.getIndexOfSlide = function(slide) { return slides.indexOf(slide); } + + this.getSlideBeginTime = function(slide) { + var index = self.getIndexOfSlide(slide); + var slideBeginTime = 0; + for (var i = 0; i < index; i++) { + slideBeginTime += slide.getDuration(); + } + return slideBeginTime; + } this.insertSlide = function(newSlide, insert_index) { if (typeof insert_index === 'undefined') { From 4f16aa66928e960455ceb710e9ff28ed23147dc2 Mon Sep 17 00:00:00 2001 From: Richard Lu Date: Wed, 25 Nov 2015 00:55:27 -0500 Subject: [PATCH 5/5] Implement automatic constraint pruning, do it at the end of record --- src/recording/recording_controller.js | 1 + src/retimer/retimer_controller.js | 4 +++ src/retimer/retimer_model.js | 46 +++++++++++++++++++++++++++ 3 files changed, 51 insertions(+) diff --git a/src/recording/recording_controller.js b/src/recording/recording_controller.js index a2d5566..35512c2 100644 --- a/src/recording/recording_controller.js +++ b/src/recording/recording_controller.js @@ -98,6 +98,7 @@ var RecordingController = function(visualsController, audioController, retimerCo TimeManager.getAudioInstance().shiftAfterBy(24*60*60*1000, -24*60*60*1000 + recordDuration); retimerController.addConstraint(endTime, ConstraintTypes.Automatic); + retimerController.pruneAutomaticConstraints(); retimerController.redrawConstraints(); // Signal the tools controller diff --git a/src/retimer/retimer_controller.js b/src/retimer/retimer_controller.js index 7e84e04..74478ec 100644 --- a/src/retimer/retimer_controller.js +++ b/src/retimer/retimer_controller.js @@ -259,6 +259,10 @@ var RetimerController = function(retimer_model, visuals_controller, audio_contro }; }; + this.pruneAutomaticConstraints = function() { + retimerModel.pruneConstraints(ConstraintTypes.Automatic); + }; + /////////////////////////////////////////////////////////////////////////////// // Selection/Deletion handling /////////////////////////////////////////////////////////////////////////////// diff --git a/src/retimer/retimer_model.js b/src/retimer/retimer_model.js index c1275f7..aa53c46 100644 --- a/src/retimer/retimer_model.js +++ b/src/retimer/retimer_model.js @@ -145,6 +145,52 @@ var RetimerModel = function() { undoManager.registerUndoAction(self, self.addConstraint, [constraint]); }; + /** + * Delete all constraints of type type, and which has both a preceding and anteceding constraint of any + * type, and whose presence is redundant to preserving the visual:audio playback ratio between its + * preceding and anteceding constraints. + * + * @param type type of constraint to prune; see ConstraintTypes + */ + this.pruneConstraints = function(type) { + var constraintsToPrune = []; + + // track 3 consecutive constraints, precious current next, to ensure we don't prune the ends + // (only the middle one, current, is considered for pruning) + var previous = undefined; + var current = undefined; + for (var i = 0; i < constraints.length; i++) { + // next = i, current = i-1, previous = i-2 + var next = constraints[i]; + if (previous) { + if (current) { + // only check pruning conditions if matching type + if (current.getType() === type) { + var deltaVisualPrevious = current.getTVisual() - previous.getTVisual(); + var deltaAudioPrevious = current.getTAudio() - previous.getTAudio(); + var deltaVisualCurrent = next.getTVisual() - current.getTVisual(); + var deltaAudioCurrent = next.getTAudio() - current.getTAudio(); + // check identical visual:audio ratio between previous/current and current/next; if so, prune current + if (Math.abs(deltaVisualCurrent/deltaAudioCurrent - deltaVisualPrevious/deltaAudioPrevious) < 0.0001) { + constraintsToPrune.push(current); + } + } + + previous = current; + current = next; + } else { + current = next; + } + } else { + previous = next; + } + } + + for (var i in constraintsToPrune) { + self.deleteConstraint(constraintsToPrune[i]); + } + }; + this.getConstraintsIterator = function() { return new Iterator(constraints); };