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();
}