diff --git a/CUE.NET.csproj b/CUE.NET.csproj
index 7d7793d..3932d0e 100644
--- a/CUE.NET.csproj
+++ b/CUE.NET.csproj
@@ -55,6 +55,8 @@
+
+
diff --git a/Devices/Keyboard/Effects/AbstractEffect.cs b/Devices/Keyboard/Effects/AbstractEffect.cs
new file mode 100644
index 0000000..3b9d0a9
--- /dev/null
+++ b/Devices/Keyboard/Effects/AbstractEffect.cs
@@ -0,0 +1,37 @@
+using System.Collections.Generic;
+using System.Linq;
+using CUE.NET.Devices.Keyboard.Brushes;
+using CUE.NET.Devices.Keyboard.Keys;
+
+namespace CUE.NET.Devices.Keyboard.Effects
+{
+ public abstract class AbstractEffect : IEffect
+ {
+ #region Properties & Fields
+
+ public abstract IBrush EffectBrush { get; }
+
+ public IEnumerable KeyList { get; protected set; }
+
+ public bool IsDone { get; protected set; }
+
+ #endregion
+
+ #region Methods
+
+ public void SetTarget(IKeyGroup keyGroup)
+ {
+ KeyList = keyGroup.Keys.ToList();
+ }
+
+ public abstract void Update(float deltaTime);
+
+ public virtual void OnAttach()
+ { }
+
+ public virtual void OnDetach()
+ { }
+
+ #endregion
+ }
+}
diff --git a/Devices/Keyboard/Effects/FlashEffect.cs b/Devices/Keyboard/Effects/FlashEffect.cs
new file mode 100644
index 0000000..5af00e7
--- /dev/null
+++ b/Devices/Keyboard/Effects/FlashEffect.cs
@@ -0,0 +1,123 @@
+using System;
+using System.Drawing;
+using CUE.NET.Devices.Keyboard.Brushes;
+
+namespace CUE.NET.Devices.Keyboard.Effects
+{
+ public class FlashEffect : AbstractEffect
+ {
+ #region Properties & Fields
+
+ public override IBrush EffectBrush { get; }
+
+ // Settings are close to a synthesizer envelope (sustain is different for consequent naming): https://en.wikipedia.org/wiki/Synthesizer#ADSR_envelope
+ public float Attack { get; set; } = 0.2f;
+ public float Decay { get; set; } = 0f;
+ public float Sustain { get; set; } = 0.3f;
+ public float Release { get; set; } = 0.2f;
+
+ public float SustainValue { get; set; } = 1f;
+ public float AttackValue { get; set; } = 1f;
+
+ public float Interval { get; set; } = 1f;
+
+ public int Repetitions { get; set; } = 0;
+
+ private ADSRPhase _currentPhase;
+ private float _currentPhaseValue;
+ private int _repetitionCount;
+
+ #endregion
+
+ #region Constructors
+
+ public FlashEffect(Color flashColor)
+ : this(new SolidColorBrush(flashColor))
+ { }
+
+ public FlashEffect(IBrush effectBrush)
+ {
+ this.EffectBrush = effectBrush;
+ }
+
+ #endregion
+
+ #region Methods
+
+ public override void Update(float deltaTime)
+ {
+ _currentPhaseValue -= deltaTime;
+
+ // Using ifs instead of a switch allows to skip phases with time 0.
+
+ if (_currentPhase == ADSRPhase.Attack)
+ if (_currentPhaseValue > 0f)
+ EffectBrush.Opacity = Math.Min(1f, (Attack - _currentPhaseValue) / Attack) * AttackValue;
+ else
+ {
+ _currentPhaseValue = Decay;
+ _currentPhase = ADSRPhase.Decay;
+ }
+
+ if (_currentPhase == ADSRPhase.Decay)
+ if (_currentPhaseValue > 0f)
+ EffectBrush.Opacity = SustainValue + (Math.Min(1f, _currentPhaseValue / Decay) * (AttackValue - SustainValue));
+ else
+ {
+ _currentPhaseValue = Sustain;
+ _currentPhase = ADSRPhase.Sustain;
+ }
+
+ if (_currentPhase == ADSRPhase.Sustain)
+ if (_currentPhaseValue > 0f)
+ EffectBrush.Opacity = SustainValue;
+ else
+ {
+ _currentPhaseValue = Release;
+ _currentPhase = ADSRPhase.Release;
+ }
+
+ if (_currentPhase == ADSRPhase.Release)
+ if (_currentPhaseValue > 0f)
+ EffectBrush.Opacity = Math.Min(1f, _currentPhaseValue / Release) * SustainValue;
+ else
+ {
+ _currentPhaseValue = Interval;
+ _currentPhase = ADSRPhase.Pause;
+ }
+
+ if (_currentPhase == ADSRPhase.Pause)
+ if (_currentPhaseValue > 0f)
+ EffectBrush.Opacity = 0f;
+ else
+ {
+ if (++_repetitionCount >= Repetitions && Repetitions > 0)
+ IsDone = true;
+ _currentPhaseValue = Attack;
+ _currentPhase = ADSRPhase.Attack;
+ }
+ }
+
+ public override void OnAttach()
+ {
+ base.OnAttach();
+
+ _currentPhase = ADSRPhase.Attack;
+ _currentPhaseValue = Attack;
+ _repetitionCount = 0;
+ EffectBrush.Opacity = 0f;
+ }
+
+ #endregion
+
+ // ReSharper disable once InconsistentNaming
+ private enum ADSRPhase
+ {
+ Attack,
+ Decay,
+ Sustain,
+ Release,
+ Pause,
+ }
+ }
+}