diff --git a/src/Vixen.Core/Commands/Named8BitCommand.cs b/src/Vixen.Core/Commands/Named8BitCommand.cs index e6fc6cb07..24e284a98 100644 --- a/src/Vixen.Core/Commands/Named8BitCommand.cs +++ b/src/Vixen.Core/Commands/Named8BitCommand.cs @@ -58,6 +58,18 @@ public Named8BitCommand(double value) /// public string Label { get; set; } + /// + /// Minimum value of the index type range. + /// + /// This property only applies if the index type is part of a range + public byte RangeMinimum { get; set; } + + /// + /// Maximum value of the index type range. + /// + /// This property only applies if the index type is part of a range + public byte RangeMaximum { get; set; } + #endregion } } diff --git a/src/Vixen.Modules/Editor/FixtureGraphics/IMovingHead.cs b/src/Vixen.Modules/Editor/FixtureGraphics/IMovingHead.cs index 63fc5baff..a3722e4eb 100644 --- a/src/Vixen.Modules/Editor/FixtureGraphics/IMovingHead.cs +++ b/src/Vixen.Modules/Editor/FixtureGraphics/IMovingHead.cs @@ -103,5 +103,10 @@ public interface IMovingHead /// /// +1 or -1 based on the orientation of the fixture double GetOrientationSign(); + + /// + /// Strobe rate represented in milliseconds between pulses. + /// + int StrobeRate { get; set; } } } diff --git a/src/Vixen.Modules/Editor/FixtureGraphics/MovingHeadSettings.cs b/src/Vixen.Modules/Editor/FixtureGraphics/MovingHeadSettings.cs index 4279809e9..1b0e2f84b 100644 --- a/src/Vixen.Modules/Editor/FixtureGraphics/MovingHeadSettings.cs +++ b/src/Vixen.Modules/Editor/FixtureGraphics/MovingHeadSettings.cs @@ -107,6 +107,9 @@ public MovingHeadSettings() /// public int FixtureIntensity { get; set; } + /// + public int StrobeRate { get; set; } + /// /// Refer to interface documentation. /// @@ -130,6 +133,7 @@ public IMovingHead Clone() EnableGDI = EnableGDI, FixtureIntensity = FixtureIntensity, MountingPosition = MountingPosition, + StrobeRate = StrobeRate, }; } @@ -181,7 +185,8 @@ public override bool Equals(object obj) movingHead.IncludeLegend == IncludeLegend && movingHead.LegendColor == LegendColor && movingHead.FixtureIntensity == FixtureIntensity && - movingHead.MountingPosition == MountingPosition); + movingHead.MountingPosition == MountingPosition && + movingHead.StrobeRate == StrobeRate); } #endregion diff --git a/src/Vixen.Modules/Effect/Effect/FixtureEffectBase.cs b/src/Vixen.Modules/Effect/Effect/FixtureEffectBase.cs index 50b434860..9dee94d87 100644 --- a/src/Vixen.Modules/Effect/Effect/FixtureEffectBase.cs +++ b/src/Vixen.Modules/Effect/Effect/FixtureEffectBase.cs @@ -531,6 +531,10 @@ protected void RenderIndex( // Declare the index value to use for the command int indexValue; + // Default the index range + int minValue = 0; + int maxValue = 0; + // If the index uses a curve then... if (fixtureIndex.UseCurve) { @@ -542,6 +546,10 @@ protected void RenderIndex( // Scale the value based on the start and stop values of the index indexValue = (int)Math.Round(ScaleCurveToValue(curve.GetValue(intervalPosFactor), fixtureIndex.EndValue, fixtureIndex.StartValue)); + + // Save off the index range + minValue = fixtureIndex.StartValue; + maxValue = fixtureIndex.EndValue; } else { @@ -561,6 +569,10 @@ protected void RenderIndex( // Assign the label to the command namedCommand.Label = function.Label; + // Assign the index range to the command + namedCommand.RangeMinimum = (byte)minValue; + namedCommand.RangeMaximum = (byte)maxValue; + // Create the command value from the tagged command CommandValue commandValue = new CommandValue(namedCommand); diff --git a/src/Vixen.Modules/Preview/VixenPreview/OpenGL/OpenGLPreviewForm.cs b/src/Vixen.Modules/Preview/VixenPreview/OpenGL/OpenGLPreviewForm.cs index 1cea68348..fce6a3276 100644 --- a/src/Vixen.Modules/Preview/VixenPreview/OpenGL/OpenGLPreviewForm.cs +++ b/src/Vixen.Modules/Preview/VixenPreview/OpenGL/OpenGLPreviewForm.cs @@ -1,4 +1,6 @@ using System.Diagnostics; +using System.Timers; + using Common.Controls; using Common.Controls.Scaling; using Common.Controls.Theme; @@ -592,20 +594,24 @@ private Matrix4 CreatePerspective() /// /// Initializes the moving head render strategy with shapes that are made up of graphical volumes. /// - private void InitializeMovingHeadRenderStrategy() + /// Method to redraw the preview + private void InitializeMovingHeadRenderStrategy(Action redraw) { // If the moving head render strategy has not been created then... if (_movingHeadRenderStrategy == null) { + // Reset all strobe timers + MovingHeadIntentHandler.ResetStrobeTimers(); + // Create the moving head render strategy _movingHeadRenderStrategy = new MovingHeadRenderStrategy(); // Loop over the moving heads foreach (IOpenGLMovingHeadShape movingHeadVolumes in GetMovingHeadShapes()) { - // Initialize the moving head with the reference height + // Initialize the moving head with the reference height and redraw delegate int referenceHeight = _background.HasBackground ? _background.Height : Height; - movingHeadVolumes.Initialize(referenceHeight); + movingHeadVolumes.Initialize(referenceHeight, redraw); // Give the shape to the render strategy _movingHeadRenderStrategy.Shapes.Add(movingHeadVolumes.MovingHead); @@ -679,13 +685,26 @@ private void RenderStaticPreviewShapes(Matrix4 perspective) } } + /// + /// Renders the preview on the GUI thread. + /// + private void OnRenderFrameOnGUIThread() + { + // Render the preview on the GUI thread + BeginInvoke(() => + { + OnRenderFrame(); + }); + } + private void OnRenderFrame() - { + { + //Logging.Debug("Entering RenderFrame"); + if (_isRendering || _formLoading || WindowState == FormWindowState.Minimized) return; + // Initialize the moving head render strategy with the applicable display item shapes - InitializeMovingHeadRenderStrategy(); + InitializeMovingHeadRenderStrategy(OnRenderFrameOnGUIThread); - //Logging.Debug("Entering RenderFrame"); - if (_isRendering || _formLoading || WindowState==FormWindowState.Minimized) return; UpdateStatusDistance(_camera.Position.Z); _isRendering = true; _sw.Restart(); @@ -702,18 +721,18 @@ private void OnRenderFrame() { glControl.MakeCurrent(); ClearScreen(); - + _sw2.Restart(); _background.Draw(perspective, _camera.ViewMatrix); _backgroundDraw.Set(_sw2.ElapsedMilliseconds); //Logging.Info($"GL Error: {GL.GetError()}"); _sw2.Restart(); - + // Render static preview shapes (moving heads) RenderStaticPreviewShapes(perspective); - + DrawPoints(mvp); - + _pointsDraw.Set(_sw2.ElapsedMilliseconds); glControl.SwapBuffers(); @@ -726,11 +745,11 @@ private void OnRenderFrame() { glControl.MakeCurrent(); ClearScreen(); - + _sw2.Restart(); _background.Draw(perspective, _camera.ViewMatrix); _backgroundDraw.Set(_sw2.ElapsedMilliseconds); - + // Render static preview shapes (moving heads) RenderStaticPreviewShapes(perspective); @@ -738,9 +757,11 @@ private void OnRenderFrame() //glControl.Context.MakeCurrent(); } } + _isRendering = false; _previewUpdate.Set(_sw.ElapsedMilliseconds); UpdateFrameRate(); + //Logging.Debug("Exiting RenderFrame"); } diff --git a/src/Vixen.Modules/Preview/VixenPreview/Shapes/IOpenGLMovingHeadShape.cs b/src/Vixen.Modules/Preview/VixenPreview/Shapes/IOpenGLMovingHeadShape.cs index 0a04d33a5..6297c9871 100644 --- a/src/Vixen.Modules/Preview/VixenPreview/Shapes/IOpenGLMovingHeadShape.cs +++ b/src/Vixen.Modules/Preview/VixenPreview/Shapes/IOpenGLMovingHeadShape.cs @@ -12,7 +12,8 @@ public interface IOpenGLMovingHeadShape /// /// The reference height is used to determine the maximum beam length /// Height of the drawing area / background image - void Initialize(int referenceHeight); + /// Delegate that redraws the preview + void Initialize(int referenceHeight, Action redraw); /// /// Gets the OpenGL moving head associated with the shape. diff --git a/src/Vixen.Modules/Preview/VixenPreview/Shapes/MovingHeadIntentHandler.cs b/src/Vixen.Modules/Preview/VixenPreview/Shapes/MovingHeadIntentHandler.cs index 2786a9697..ce151ccd0 100644 --- a/src/Vixen.Modules/Preview/VixenPreview/Shapes/MovingHeadIntentHandler.cs +++ b/src/Vixen.Modules/Preview/VixenPreview/Shapes/MovingHeadIntentHandler.cs @@ -1,8 +1,11 @@ -using System.Diagnostics; +using System.Collections.Concurrent; +using System.Diagnostics; + using Vixen.Commands; using Vixen.Data.Value; using Vixen.Sys; using Vixen.Sys.Dispatch; + using VixenModules.App.Fixture; using VixenModules.Editor.FixtureGraphics; using VixenModules.Property.IntelligentFixture; @@ -15,41 +18,75 @@ namespace VixenModules.Preview.VixenPreview.Shapes public class MovingHeadIntentHandler : IntentStateDispatch, IHandler>> { - #region Constructor + #region Constructors /// /// Constructor /// - public MovingHeadIntentHandler() + /// Delegate to redraw the preview + public MovingHeadIntentHandler(Action redrawPreviewPreview) { // Default the beam to Off DefaultBeamColor = Color.Transparent; + + // Store off the preview redraw delegate + _redrawPreview = redrawPreviewPreview; + } + + #endregion + + #region Public Static Methods + + /// + /// Clears all strobe timers. + /// This method should be called after editing the preview + /// + static public void ResetStrobeTimers() + { + // Create a new timer dictionary + _timerDictionary = new ConcurrentDictionary>(); } #endregion + #region Private Static Fields + + /// + /// Dictionary of moving head strobe timers. + /// The first key is the strobe duration in ms. + /// The second or inner dictionary key is the timer interval in ms. + /// + private static ConcurrentDictionary> _timerDictionary; + + #endregion + #region Fields + /// + /// Delegate to redraw the preview. + /// + private Action _redrawPreview; + /// /// Dictionary of command legend name value pairs. /// private Dictionary _legendValues = new Dictionary(); /// - /// Flag indicates that a strobbing shutter index option was selected. + /// Flag indicates that a strobe shutter index option was selected. /// This flag prevents automation from opening the shutter. /// - private bool _strobbing; + private bool _strobeModeEnabled; /// - /// Flag indicates if color intents have been processed this frame. + /// This flag indicates that strobe shutter intents have been detected this frame. /// - private bool _colorPresent; + private bool _strobeIntentDetected; /// - /// Flag that indicates the beam should be turned on because of strobing as long as color intents are present. + /// Flag indicates if color intents have been processed this frame. /// - private bool _strobingOn; + private bool _colorPresent; /// /// Keeps track of the current color wheel slot when spinning the color wheel. @@ -66,6 +103,25 @@ public MovingHeadIntentHandler() /// private FixtureColorWheel _colorWheelEntry = null; + /// + /// The interval in ms between strobe pulses. + /// + private int _strobeInterval; + + /// + /// Reference to the active moving head strobe timer. + /// + private MovingHeadTimer _movingHeadStrobeTimer; + + #endregion + + #region Private Constants + + /// + /// Max time in ms that the strobe should be illuminated. + /// + private const int MaxStrobeDuration = 50; + #endregion #region Public Properties @@ -80,6 +136,33 @@ public MovingHeadIntentHandler() /// public IElementNode Node { get; set; } + /// + /// Strobe Rate Minimum in Hz. + /// + public int StrobeRateMinimum + { + get; + set; + } + + /// + /// Strobe Rate Maximum in Hz. + /// + public int StrobeRateMaximum + { + get; + set; + } + + /// + /// Maximum strobe duration in ms. + /// + public int MaximumStrobeDuration + { + get; + set; + } + /// /// Pan start angle in degrees. /// @@ -175,15 +258,18 @@ public void Reset() // Clear out the fixture intensity MovingHead.Intensity = 0; - // Turn off strobing - _strobbing = false; - // Reset whether color was detected _colorPresent = false; - _strobingOn = false; - // Turn off the moving head beam - MovingHead.OnOff = false; + // Reset the flag indicating a strobe intent was detected + _strobeIntentDetected = false; + + // If we are not in strobe mode then... + if (!_strobeModeEnabled) + { + // Turn off the moving head beam + MovingHead.OnOff = false; + } // Set the beam color of the moving head back to the default MovingHead.BeamColorLeft = DefaultBeamColor; @@ -205,6 +291,71 @@ public void Reset() MovingHead.PanAngle = PanStartPosition; } + /// + /// Determines the appropriate strobe timer for the moving head based on strobe rate. + /// + public void DetermineStrobeTimer() + { + // If there is not a timer associated with the moving head then... + if (_movingHeadStrobeTimer == null) + { + // Create or register with an existing timer + CreateOrRegisterWithTimer(); + } + else + { + // Otherwise if existing timer's interval does NOT match the fixture's strobe rate then... + if (_movingHeadStrobeTimer.Interval != _strobeInterval) + { + // Remove the moving head from the previous timer + _movingHeadStrobeTimer.RemoveMovingHead(new Tuple(MovingHead, DetermineStrobeTimer)); + + // Create or register with an existing timer based on the strobe interval + CreateOrRegisterWithTimer(); + } + } + } + + /// + /// Allows the moving head intent handler to examine all of the intents + /// received this frame to determine if the fixture should strobe. + /// + public void FinalizeStrobeState() + { + // If strobe intents and color has been detected then... + if (_strobeIntentDetected && _colorPresent) + { + // Enable strobe mode + _strobeModeEnabled = true; + + // Configure the strobe rate in ms + _strobeInterval = MovingHead.StrobeRate; + + // If this moving head does NOT have a strobe timer then... + if (_movingHeadStrobeTimer == null) + { + // Create or register with a moving head strobe timer + DetermineStrobeTimer(); + } + } + // If a strobe intent or color intent is NOT present then... + else if (!_strobeIntentDetected || !_colorPresent) + { + // Disable strobe mode + _strobeModeEnabled = false; + + // If the fixture was previously in strobe mode then... + if (_movingHeadStrobeTimer != null) + { + // Disassociate the moving head with the timer + _movingHeadStrobeTimer.RemoveMovingHead(new Tuple(MovingHead, DetermineStrobeTimer)); + + // Clear out the reference to the timer + _movingHeadStrobeTimer = null; + } + } + } + /// /// Dispatches intents to a specific handler method. /// @@ -221,7 +372,7 @@ public void Dispatch(IIntentStates states) state.Dispatch(this); } - // Increemnt the frame counter + // Increment the frame counter IncrementFrameCounter(); } } @@ -431,8 +582,12 @@ public override void Handle(IIntentState commandIntent) else if (((FixtureIndexType)taggedCommand.IndexType) == FixtureIndexType.LampOn || ((FixtureIndexType)taggedCommand.IndexType) == FixtureIndexType.ShutterOpen) { - // Turn on the moving head beam - MovingHead.OnOff = true; + // If not in strobe mode then... + if (!_strobeModeEnabled) + { + // Turn on the moving head beam + MovingHead.OnOff = true; + } } // If the command is a color wheel index command then... else if (((FixtureIndexType)taggedCommand.IndexType) == FixtureIndexType.ColorWheel) @@ -442,35 +597,38 @@ public override void Handle(IIntentState commandIntent) } // Otherwise if the command is to strobe then... else if (((FixtureIndexType)taggedCommand.IndexType) == FixtureIndexType.Strobe) - { - // Store off a flag that the fixture is strobing so that the shutter + { + // Store off a flag that the fixture is in strobe mode so that the shutter // doesn't get automatically opened - _strobbing = true; + _strobeModeEnabled = true; - // If this is an even frame then... - if (_frameCounter % 2 == 0) - { - // If color intents were detected then... - if (_colorPresent) - { - // Turn on the beam - MovingHead.OnOff = true; - } - // Otherwise a color intent has not been detected so - else - { - // Set a flag so that if a color intent is detected the beam is turned on - _strobingOn = true; - } - } - // Otherwise if this is an odd frame then... - else - { - // Turn off the beam - MovingHead.OnOff = false; - } - } + // Store off the a strobe intent was received + _strobeIntentDetected = true; + + // Retrieve the min and max strobe range constraints + int min = taggedCommand.RangeMinimum; + int max = taggedCommand.RangeMaximum; + + // Convert fixture strobe constraints from Hz to ms + double fixtureMinimumInMs = 1.0 / StrobeRateMinimum * 1000; + double fixtureMaximumInMs = 1.0 / StrobeRateMaximum * 1000; + + // Retrieve the strobe rate from the intent command + double sRate = (double)taggedCommand.CommandValue; + + // Determine how far away from the minimum the rate is + double distanceFromMinimum = sRate - min; + + // Determine penetration into the range as percent (0-1) + double penetrationRatio = distanceFromMinimum / (max - min); + + // Apply the ratio to strobe rate range + double strobeRateMs = penetrationRatio * (fixtureMinimumInMs - fixtureMaximumInMs) + fixtureMaximumInMs; + // Flip things around since the maximum strobe rate is really the smaller number + MovingHead.StrobeRate = (int)(fixtureMinimumInMs + fixtureMaximumInMs) - (int)strobeRateMs; + } + //TODO: Better place to put this code? MovingHead.BeamLength = (int)BeamLength; } @@ -479,6 +637,71 @@ public override void Handle(IIntentState commandIntent) #region Private Methods + /// + /// Creates or re-uses an existing moving head strobe timer. + /// + /// Interval of the strobe rate in ms + /// Maximum strobe duration in ms + private void CreateMovingHeadTimer(int strobeRate, int maxStrobeDuration) + { + // Create a new moving head timer + MovingHeadTimer timer = new MovingHeadTimer(_strobeInterval, maxStrobeDuration, _redrawPreview); + + // If successful adding the timer to the dictionary then... + if (_timerDictionary[MaximumStrobeDuration].TryAdd(_strobeInterval, timer)) + { + // Associate the moving head with the timer + timer.AddMovingHead(new Tuple(MovingHead, DetermineStrobeTimer)); + } + // Otherwise retrieve the existing timer based on the strobe interval + else + { + // Associate the moving head with the timer + _timerDictionary[MaximumStrobeDuration][_strobeInterval].AddMovingHead(new Tuple(MovingHead, DetermineStrobeTimer)); + } + + // Store off the reference to the timer + _movingHeadStrobeTimer = _timerDictionary[MaximumStrobeDuration][_strobeInterval]; + } + + /// + /// Registers a moving head with a shared strobe timer. + /// + private void RegisterWithExistingTimer() + { + // Associate the moving head with the strobe timer + _timerDictionary[MaximumStrobeDuration][_strobeInterval].AddMovingHead(new Tuple(MovingHead, DetermineStrobeTimer)); + + // Store off the reference to the timer + _movingHeadStrobeTimer = _timerDictionary[MaximumStrobeDuration][_strobeInterval]; + } + + /// + /// Creates or registers a moving head with an existing strobe timer. + /// + private void CreateOrRegisterWithTimer() + { + // If a dictionary does not exist for the specified strobe duration then... + if (!_timerDictionary.ContainsKey(MaximumStrobeDuration)) + { + // Create the internal dictionary for the specified strobe duration + _timerDictionary.TryAdd(MaximumStrobeDuration, new ConcurrentDictionary()); + } + + // If an existing strobe rate timer exists then... + if (_timerDictionary.ContainsKey(_strobeInterval)) + { + // Register the moving head with an existing strobe rate timer + RegisterWithExistingTimer(); + } + // Otherwise a strobe rate timer does NOT exist for the specified strobe interval + else + { + // Create a new strobe timer for the specified interval + CreateMovingHeadTimer(_strobeInterval, MaximumStrobeDuration); + } + } + /// /// Increments the internal frame counter. /// @@ -609,16 +832,8 @@ private void OpenShutter() // Remember that color was detected _colorPresent = true; - // If the fixture was configured to conver color intents into shutter intents then... - if (ConvertColorIntentsIntoShutter && !_strobbing) - { - // Turn on the beam - MovingHead.OnOff = true; - } - - // Since the strobe intent could come before the color intent; - // If we are strobing and this frame is the On frame then... - if (_strobbing && _strobingOn) + // If the fixture was configured to convert color intents into shutter intents then... + if (ConvertColorIntentsIntoShutter && !_strobeModeEnabled) { // Turn on the beam MovingHead.OnOff = true; diff --git a/src/Vixen.Modules/Preview/VixenPreview/Shapes/MovingHeadTimer.cs b/src/Vixen.Modules/Preview/VixenPreview/Shapes/MovingHeadTimer.cs new file mode 100644 index 000000000..a916fe03e --- /dev/null +++ b/src/Vixen.Modules/Preview/VixenPreview/Shapes/MovingHeadTimer.cs @@ -0,0 +1,210 @@ +using System.Timers; + +using VixenModules.Editor.FixtureGraphics; + +using Timer = System.Timers.Timer; + +namespace VixenModules.Preview.VixenPreview.Shapes +{ + /// + /// Maintains a moving head strobe timer. + /// + internal class MovingHeadTimer + { + #region Fields + + /// + /// Timer used to strobe the moving head beam. + /// + private Timer _timer; + + /// + /// Collection of moving heads registered with this timer. + /// + private List> _movingHeads; + + /// + /// Delegate to refresh the Vixen preview. + /// + private Action _redraw; + + /// + /// Duration of the strobe flash in milliseconds. + /// + private int _strobeDuration; + + /// + /// Lock to ensure thread safety with respect to the moving heads collection. + /// + private object _movingHeadsLock; + + #endregion + + #region Constructor + + /// + /// Constructor + /// + /// Interval of the strobe timer in ms + /// Maximum strobe duration in ms + /// Delegate to redraw the Vixen preview + public MovingHeadTimer( + int interval, + int maxStrobeDuration, + Action redraw) + { + // Store off the timer interval + Interval = interval; + + // Store off the maximum strobe duration + MaximumStrobeDuration = maxStrobeDuration; + + // Store off the delegate to refresh the Vixen preview + _redraw = redraw; + + // Initialize the moving heads collection lock + _movingHeadsLock = new object(); + + // Initialize the moving heads collection + _movingHeads = new List>(); + + // Create the OS timer + _timer = new Timer(interval); + + // Hook up the Elapsed event for the timer + _timer.Elapsed += OnStrobeTimerEvent; + + // Configure the timer to continuously fire + _timer.AutoReset = true; + + // Default the strobe duration to 25% of the interval + _strobeDuration = interval / 4; + + // If the duration is greater than the max duration then... + if (_strobeDuration > MaximumStrobeDuration) + { + // Limit the duration to 50 ms + _strobeDuration = MaximumStrobeDuration; + } + } + + #endregion + + #region Public Properties + + /// + /// Interval of the moving head strobe timer in milliseconds. + /// + public int Interval { get; set; } + + /// + /// Maximum strobe duration in ms. + /// + public int MaximumStrobeDuration { get; set; } + + #endregion + + #region Public Methods + + /// + /// Adds the specified moving head to the timer. + /// + /// Moving head to associate with the strobe timer + public void AddMovingHead(Tuple movingHead) + { + // Lock the collection + lock (_movingHeadsLock) + { + // Add the moving head to the collection + _movingHeads.Add(movingHead); + } + + // If the timer has NOT already been started then... + if (!_timer.Enabled) + { + // Start the timer + _timer.Start(); + } + } + + /// + /// Removes the specified moving head from the timer. + /// + /// Moving head to remove from the timer + public void RemoveMovingHead(Tuple movingHead) + { + // Lock the collection + lock (_movingHeadsLock) + { + // Remove the moving head from the collection + _movingHeads.Remove(movingHead); + } + + // If there are no moving heads associated with this timer then... + if (_movingHeads.Count == 0) + { + // Stop the OS timer + _timer.Stop(); + } + } + + #endregion + + #region Private Methods + + /// + /// Event handler when the strobe timer expires. + /// + /// Source of the event + /// Event arguments + private void OnStrobeTimerEvent(Object source, ElapsedEventArgs e) + { + // Declare a local collection of moving heads + List> heads; + + // Lock the moving head collection + lock (_movingHeadsLock) + { + // Make a copy of the moving heads collection to avoid + // threading issues while working with the collection + heads = _movingHeads.ToList(); + } + + // Loop over the moving heads + foreach (Tuple movingHead in heads) + { + // Turn on the beam + movingHead.Item1.OnOff = true; + } + + // Redraw the preview + _redraw(); + + // Sleep for the duration of the strobe flash + Thread.Sleep(_strobeDuration); + + // Loop over the moving heads + foreach (Tuple movingHead in heads) + { + // Turn off the beam + movingHead.Item1.OnOff = false; + } + + // Redraw the preview + _redraw(); + + // Loop over the moving heads + foreach (Tuple movingHead in heads) + { + // If the moving head strobe interval no longer matches this timer then... + if (movingHead.Item1.StrobeRate != Interval) + { + // Update the strobe rate timer associated with the moving head + movingHead.Item2(); + } + } + } + + #endregion + } +} diff --git a/src/Vixen.Modules/Preview/VixenPreview/Shapes/PreviewMovingHead.cs b/src/Vixen.Modules/Preview/VixenPreview/Shapes/PreviewMovingHead.cs index 50e9318ee..3f8586775 100644 --- a/src/Vixen.Modules/Preview/VixenPreview/Shapes/PreviewMovingHead.cs +++ b/src/Vixen.Modules/Preview/VixenPreview/Shapes/PreviewMovingHead.cs @@ -51,6 +51,10 @@ public PreviewMovingHead() TiltStartPosition = DefaultTiltStartPosition; TiltStartPosition = DefaultTiltStopPosition; + // Default the strobe min and max rates + StrobeRateMinimum = DefaultStrobeRateMinimum; + StrobeRateMaximum = DefaultStrobeRateMaximum; + // Default the beam length percentage BeamLength = DefaultBeamLength; @@ -93,6 +97,21 @@ public PreviewMovingHead() /// const int DefaultBeamLength = 100; + /// + /// Default minimum strobe rate in Hz. + /// + private const int DefaultStrobeRateMinimum = 1; + + /// + /// Default maximum strobe rate in Hz. + /// + private const int DefaultStrobeRateMaximum = 25; + + /// + /// Default the strobe flash duration to 50ms. + /// + private const int MaxStrobeDuration = 50; + #endregion #region Fields @@ -511,10 +530,11 @@ public override void Draw(FastPixel.FastPixel fp, bool editMode, HashSet h /// /// Creates the intent handler for the moving head position. /// - private void CreateIntentHandler() + /// Delegate to redraw the preview + private void CreateIntentHandler(Action redraw) { // Create the intent handler - _intentHandler = new MovingHeadIntentHandler(); + _intentHandler = new MovingHeadIntentHandler(redraw); // Give the intent handler the current movement constraints _intentHandler.PanStartPosition = PanStartPosition; @@ -523,6 +543,13 @@ private void CreateIntentHandler() _intentHandler.TiltStopPosition = TiltStopPosition; _intentHandler.InvertPanDirection = InvertPanDirection == YesNoType.Yes; _intentHandler.InvertTiltDirection = InvertTiltDirection == YesNoType.Yes; + + // Give the intent handler the strobe constraints + _intentHandler.StrobeRateMinimum = StrobeRateMinimum; + _intentHandler.StrobeRateMaximum = StrobeRateMaximum; + + // Give the intent handler the maximum strobe duration + _intentHandler.MaximumStrobeDuration = MaximumStrobeDuration; // Configure how the fixture zooms _intentHandler.ZoomNarrowToWide = ZoomNarrowToWide; @@ -583,7 +610,7 @@ private void CreateWPFMovingHead(Color beamColor) _movingHeadCurrentSettings = _movingHead.MovingHead; // Create the intent handler - CreateIntentHandler(); + CreateIntentHandler(null); } /// @@ -782,7 +809,7 @@ private void InitializeMovingHeadMovementConstraints(ElementNode selectedNode) /// /// Refer to interface documentation. /// - public void Initialize(int referenceHeight) + public void Initialize(int referenceHeight, Action redraw) { // Create the moving head OpenGL implementation _movingHeadOpenGL = new MovingHeadOpenGL(); @@ -791,7 +818,7 @@ public void Initialize(int referenceHeight) _movingHeadCurrentSettings = _movingHeadOpenGL.MovingHead; // Create the intent handler - CreateIntentHandler(); + CreateIntentHandler(redraw); // Initialize the moving head _movingHeadOpenGL.Initialize(CalculateDrawingLength(), referenceHeight, (100.0 - BeamTransparency) / 100.0, BeamWidthMultiplier, MountingPosition); @@ -823,6 +850,10 @@ public void UpdateVolumes(int maxBeamLength, int referenceHeight) _intentHandler.Dispatch(states); } + // After processing all the intents allow the intent handler + // to configure fixture strobe state + _intentHandler.FinalizeStrobeState(); + // Calculate the height of the drawing area int fixtureHeight = Bottom - Top; @@ -914,6 +945,33 @@ public Guid NodeId } } + /// + /// Strobe rate minimum in Hz. + /// + [DataMember(EmitDefaultValue = false), + Category("Settings"), + Description("The strobe rate minimum (in Hz)."), + DisplayName("Strobe Rate Minimum (Hz)")] + public int StrobeRateMinimum { get; set; } + + /// + /// Strobe rate maximum in Hz. + /// + [DataMember(EmitDefaultValue = false), + Category("Settings"), + Description("The strobe rate maximum (in Hz)."), + DisplayName("Strobe Rate Maximum (Hz)")] + public int StrobeRateMaximum { get; set; } + + /// + /// Strobe duration in ms. + /// + [DataMember(EmitDefaultValue = false), + Category("Settings"), + Description("The maximum strobe duration in ms."), + DisplayName("Maximum Strobe Duration (ms)")] + public int MaximumStrobeDuration { get; set; } + /// /// Pan start position in degrees. /// diff --git a/src/Vixen.Modules/Preview/VixenPreview/Shapes/PreviewMovingHeadPartial.cs b/src/Vixen.Modules/Preview/VixenPreview/Shapes/PreviewMovingHeadPartial.cs index c5e6c8a4b..442122c26 100644 --- a/src/Vixen.Modules/Preview/VixenPreview/Shapes/PreviewMovingHeadPartial.cs +++ b/src/Vixen.Modules/Preview/VixenPreview/Shapes/PreviewMovingHeadPartial.cs @@ -50,12 +50,40 @@ public PreviewMovingHead(PreviewPoint point1, ElementNode selectedNode, double z // Initialize the pan/tilt movement constraints InitializeMovingHeadMovementConstraints(selectedNode); + // Default the fixture strobe minimum and maximum + StrobeRateMinimum = DefaultStrobeRateMinimum; + StrobeRateMaximum = DefaultStrobeRateMaximum; + Reconfigure(selectedNode); } [OnDeserialized] private void OnDeserialized(StreamingContext context) - { + { + // If the strobe rate minimum is zero this must be + // an existing fixture + if (StrobeRateMinimum == 0) + { + // Default the strobe rate minimum + StrobeRateMinimum = DefaultStrobeRateMinimum; + } + + // If the strobe rate maximum is zero this must be + // an existing fixture + if (StrobeRateMaximum == 0) + { + // Default the strobe rate maximum + StrobeRateMaximum = DefaultStrobeRateMaximum; + } + + // If the maximum strobe duration is zero this must be an + // existing fixture + if (MaximumStrobeDuration == 0) + { + // Default the maximum strobe duration + MaximumStrobeDuration = MaxStrobeDuration; + } + Layout(); }