This tech tip presents a collection of light shows designed for the speaker panel LEDs.
Programming LEDs is well documented in the SDK guide here file:///C:/P3/P3_SDK_V0.8/P3SampleApp/Documentation/html/_physical_features.html#LEDs
The RGB speaker kit is an option when you buy the base machine. It includes an LED strip around both speakers. Each strip is composed of 61 LEDs slightly recessed in a larger circle than the speaker grill. The light reflects on the speaker cone. To see the actual LEDs, you have to lean to the side. This arrangement is great for ambient light shows without obnoxious in-your-face lights.
See the Appendix below for the collected source code.
The LED simulator is documented in the SDK Guide here: file:///C:/P3/P3_SDK_V0.8/P3SampleApp/Documentation/html/_physical_features.html#LEDSimulations
The SDK Guide describes how to add the LED simulator to your application. Luckily, the LED Simulator is already included in P3SampleApp.
By default, the LED simulator only displays the playfield LEDs. To show the speaker panel LEDs, you need to configure an option in the LED Simulator Manager script. You must do this in all scenes. For P3SampleApp, that means both the Attract and Home scenes.
In the Project window of Unity, expand Assets and select Scenes. Double-click on the Attract scene to load it in the Hierarchy window. Select LEDSimulator in the Hierarchy window. In the Inspector window, scroll to the LED Simulator Manager script and enable "Simulate Speaker LEDs":
The Cabinet and Backbox LEDs were an experimental feature on a prototype machine. They are not available on production machines. You can enable the toggles if you are curious, but there is no point in programming those LEDs in your application.
Save the Attract scene. Repeat the operation for the Home scene.
Select the Bootstrap scene and start the application. Select Display #8 in the Game window to see a simulated view of the LEDs.
For example, this is the view when the CCR module is selected in .\Configuration\AppConfig.json
Unity 5.6.7 has an annoying feature that sometimes makes the cursor disappear over the Game window. Without a cursor, it is difficult to hit the play button to stop the application. My solution is to move the cursor to the left of the Game window. That makes the cursor reappear. Don’t go directly to the Play button now, because the cursor will disappear again. Instead, move straight up to the Unity ribbon and click the ribbon background (for example, just to the left of the Pivot button). The problem is now repaired and you can easily move to the Play button with the cursor visible.
The speaker LEDs are part of the base machine definition. They do not appear in the module definition file. The LED names are:
speakerLeft0 .. speakerLeft60
speakerRight0 .. speakerRight60
You can access an individual LED definition like this p3.LEDs["speakerLeft0"]
The simplest light show is a permanent solid color. On the physical machine, this looks surprisingly good.
for (int i = 0; i < 61; i++) { string ledName = "speakerLeft" + i; LEDScript script = new LEDScript(p3.LEDs[ledName], this.Priority); script.AddCommand(Multimorphic.P3.Colors.Color.red, 0, 10); p3.LEDController.AddScript(script, 0); } |
This light show sets the color red with zero fade time and 10 second duration. In the call to AddScript(), the runtime is 0 which means the script ends when all commands have executed.
When the runtime is -1, the script is run repeatedly until the script is removed.
AddScript() can take a third optional argument for the delay before the script becomes active. During the delay, the LED color is controlled by the lower priority scripts.
The loop above works, but there is no way to remove the scripts. It is better to collect all the scripts in a List and treat the list as the light show.
List<LEDScript> ledScripts = new List<LEDScript>(); for (int i = 0; i < 61; i++) { string ledName = "speakerLeft" + i; LEDScript script = new LEDScript(p3.LEDs[ledName], this.Priority); script.AddCommand(Multimorphic.P3.Colors.Color.red, 0, 10); ledScripts.Add(script); } foreach (LEDScript ledScript in ledScripts) { P3.LEDController.AddScript(ledScript, -1); } // later foreach (LEDScript ledScript in ledScripts) { p3.LEDController.RemoveScript(ledScript); } |
Let’s play this simple light show in Attract mode of P3SampleApp.
P3SAAttractMode displays light shows with the help of the LEDShowControllerMode. This class is in the file Assets\Scripts\Modes\Mechs\LightShows.cs
The purpose of LEDShowControllerMode is to maintain a static list of light shows. When the mode starts, it picks one light show to run at random. When the allocated time is over, another light show is picked at random and played. This continues until the mode is stopped. In our case, that would be when Attract mode is stopped.
In P3SampleApp, there is only one light show in the list, so Attract mode plays the RGBFadeMode light show forever.
We will remove RGBFadeMode from the list and add our light show instead. Before we can do that, we need to convert the solid light show to a Mode.
We might as well subclass the LEDShow class found at the top of LightShows.cs. This will give us the code to add the LEDScripts when the mode starts and remove the LEDScripts when the mode stops.
Add this class after LEDShowControllerMode. Lines in bold are lines that changed.
public class SolidLightShowMode : LEDShow { public SolidLightShowMode(P3Controller controller, int priority) : base(controller, priority) { for (int i = 0; i < 61; i++) { string ledName = "speakerLeft" + i; LEDScript script = new LEDScript(p3.LEDs[ledName], this.Priority); script.AddCommand(Multimorphic.P3.Colors.Color.red, 0, 10); ledScripts.Add(script); } } } |
Change the member variables and the constructor of LEDShowControllerMode like this:
public class LEDShowControllerMode : P3Mode { List<ShowData> shows; int showIndex; public LEDShowControllerMode (P3Controller controller, int priority) : base(controller, priority) { LEDShow solidLightShowMode = new SolidLightShowMode(controller, priority); shows = new List<ShowData>(); shows.Add(new ShowData(solidLightShowMode, 5)); } |
Play the application in Unity and select Display #8 in the Game window. When the Attract scene is fully loaded (this takes a while), the left speaker LEDs are red.
In a real application, the light show would take advantage of all the available LEDs. In this TechTip, we are more interested in experimenting with the speaker panel only.
All the LEDscripts are identical in SolidLightShowMode. We can replace the loop with an operator to copy the LEDScript as many times as necessary.
We can now create the left speaker light show in 3 lines and the right speaker light show in only 1 line. Notice the LED name used in the source LEDScript is irrelevant, but the LED must exist.
Edit LightShows.cs, add these two methods to the LEDShow class:
// Copy a LEDScript, i.e. create a new LEDScript with the same LEDCommands but for another LED // The LEDCommands can be shared because they are immutable data objects. protected LEDScript Clone(LEDScript ledScript, string newLedName) { LEDScript newScript = new LEDScript(p3.LEDs[newLedName], ledScript.priority); newScript.script.AddRange(ledScript.script); newScript.autoRemove = ledScript.autoRemove; newScript.scriptName = ledScript.scriptName; newScript.synchronizerIndex = ledScript.synchronizerIndex; return newScript; } // Copy the same LEDScript to a range of LEDs protected List<LEDScript> Copy(LEDScript script, int start, int count, string ledPrefix) { List<LEDScript> lightShow = new List<LEDScript>(); int end = start + count; for (int i = start; i < end; i++) { string ledName = ledPrefix + i; lightShow.Add(Clone(script, ledName)); } return lightShow; } |
Edit the SolidLightShowMode class like this:
public class SolidLightShowMode : LEDShow { public SolidLightShowMode(P3Controller controller, int priority) : base(controller, priority) { LEDScript ledScript = new LEDScript(p3.LEDs["speakerLeft0"], this.Priority); ledScript.AddCommand(Multimorphic.P3.Colors.Color.red, 0, 10); ledScripts.AddRange(Copy(ledScript, 0, 61, "speakerLeft")); ledScripts.AddRange(Copy(ledScript, 0, 61, "speakerRight")); } } |
Play the application in Unity and select Display #8 in the Game window. When the Attract scene is fully loaded, all speaker LEDs are red.
The light shows for the left and right speakers are identical. It would be better if we copied the whole left light show to create the right light show.
Edit LightShows.cs, add this line at the top of the file:
using System.Text.RegularExpressions; |
Add this method to the LEDShow class:
// Copy the LEDScripts in the lightShow renaming the LEDs with the new prefix. // newPrefix is the prefix of the LED names in the new light show. protected List<LEDScript> Copy(List<LEDScript> lightShow, string newPrefix) { List<LEDScript> newScripts = new List<LEDScript>(); foreach (LEDScript ledScript in lightShow) { Match match = Regex.Match(ledScript.led.Name, @"(\d+)$"); if (match.Success) { string newLedName = newPrefix + match.Value; LEDScript newScript = Clone(ledScript, newLedName); newScripts.Add(newScript); } } return newScripts; } |
Edit the SolidLightShowMode class like this:
public class SolidLightShowMode : LEDShow { public SolidLightShowMode(P3Controller controller, int priority) : base(controller, priority) { LEDScript ledScript = new LEDScript(p3.LEDs["speakerLeft0"], this.Priority); ledScript.AddCommand(Multimorphic.P3.Colors.Color.red, 0, 10); List<LEDScript> leftShow = Copy(ledScript, 0, 61, "speakerLeft"); List<LEDScript> rightShow = Copy(leftShow, "speakerRight"); ledScripts.AddRange(leftShow); ledScripts.AddRange(rightShow); } } |
Play the application in Unity and we get the same result: all speaker LEDs are red.
We can make the light show more dynamic by fading gradually from one color to the next. This is the same concept as RGBFadeMode in LightShows.cs
Let’s reimplement RGBFadeMode just for the speaker panel.
Edit LightShows.cs and change the LEDShowControllerMode constructor like this:
public LEDShowControllerMode (P3Controller controller, int priority) : base(controller, priority) { LEDShow fadeLightShowMode = new FadeLightShowMode(controller, priority); shows = new List<ShowData>(); shows.Add(new ShowData(fadeLightShowMode, 20)); } |
Add this class below SolidLightShowMode:
public class FadeLightShowMode : LEDShow { public FadeLightShowMode(P3Controller controller, int priority) : base(controller, priority) { LEDScript ledScript = new LEDScript(p3.LEDs["speakerLeft0"], this.Priority); ledScript.AddCommand(Multimorphic.P3.Colors.Color.red, 1, 1.25); ledScript.AddCommand(Multimorphic.P3.Colors.Color.green, 1, 1.25); ledScript.AddCommand(Multimorphic.P3.Colors.Color.blue, 1, 1.25); ledScript.AddCommand(Multimorphic.P3.Colors.Color.yellow, 1, 1.25); List<LEDScript> leftShow = Copy(ledScript, 0, 61, "speakerLeft"); List<LEDScript> rightShow = Copy(leftShow, "speakerRight"); ledScripts.AddRange(leftShow); ledScripts.AddRange(rightShow); } } |
Play the application in Unity and select Display #8 in the Game window. The speaker panel LEDs change color gradually.
A flashing pattern is just a variant of FadeLightShowMode with short fading time and bright colors. With the LEDs hidden behind the speaker grill, this is not as impressive as regular flasher lights. It gives more of a blinking effect.
Edit LightShows.cs and change the LEDShowControllerMode constructor like this:
public LEDShowControllerMode (P3Controller controller, int priority) : base(controller, priority) { LEDShow flashLightShowMode = new FlashLightShowMode(controller, priority); shows = new List<ShowData>(); shows.Add(new ShowData(flashLightShowMode, 6)); } |
Add this class below FadeLightShowMode:
public class FlashLightShowMode : LEDShow { public FlashLightShowMode(P3Controller controller, int priority) : base(controller, priority) { LEDScript ledScript = new LEDScript(p3.LEDs["speakerLeft0"], this.Priority); ledScript.AddCommand(Multimorphic.P3.Colors.Color.white, 0, 0.2); ledScript.AddCommand(Multimorphic.P3.Colors.Color.barelywhite, 0.05, 0.2); ledScript.AddCommand(Multimorphic.P3.Colors.Color.white, 0, 0.2); ledScript.AddCommand(Multimorphic.P3.Colors.Color.barelywhite, 0.05, 0.2); ledScript.AddCommand(Multimorphic.P3.Colors.Color.white, 0, 0.2); ledScript.AddCommand(Multimorphic.P3.Colors.Color.barelywhite, 0.05, 2); List<LEDScript> leftShow = Copy(ledScript, 0, 61, "speakerLeft"); List<LEDScript> rightShow = Copy(leftShow, "speakerRight"); ledScripts.AddRange(leftShow); ledScripts.AddRange(rightShow); } } |
Play the application in Unity and select Display #8 in the Game window. The speaker panel LEDs flash white 3 times, pause for short period and the cycle repeats.
Let’s create a light show using 4 solid colors like the Simon toy. On the physical machine, this looks surprisingly bad! Bear with me because it exposes a very important issue.
Edit LightShows.cs and change the LEDShowControllerMode constructor like this:
public LEDShowControllerMode (P3Controller controller, int priority) : base(controller, priority) { LEDShow quadrantsLightShowMode = new QuadrantsLightShowMode(controller, priority); shows = new List<ShowData>(); shows.Add(new ShowData(quadrantsLightShowMode, 5)); } |
Add this class below FlashLightShowMode:
public class QuadrantsLightShowMode : LEDShow { public QuadrantsLightShowMode(P3Controller controller, int priority) : base(controller, priority) { LEDScript redScript = new LEDScript(p3.LEDs["speakerLeft0"], this.Priority); redScript.AddCommand(Multimorphic.P3.Colors.Color.red, 0, 10); LEDScript greenScript = new LEDScript(p3.LEDs["speakerLeft0"], this.Priority); greenScript.AddCommand(Multimorphic.P3.Colors.Color.green, 0, 10); LEDScript blueScript = new LEDScript(p3.LEDs["speakerLeft0"], this.Priority); blueScript.AddCommand(Multimorphic.P3.Colors.Color.blue, 0, 10); LEDScript yellowScript = new LEDScript(p3.LEDs["speakerLeft0"], this.Priority); yellowScript.AddCommand(Multimorphic.P3.Colors.Color.yellow, 0, 10); List<LEDScript> leftShow = new List<LEDScript>(); leftShow.AddRange(Copy(redScript, 0, 15, "speakerLeft")); leftShow.AddRange(Copy(greenScript, 15, 15, "speakerLeft")); leftShow.AddRange(Copy(blueScript, 30, 15, "speakerLeft")); leftShow.AddRange(Copy(yellowScript, 45, 16, "speakerLeft")); List<LEDScript> rightShow = Copy(leftShow, "speakerRight"); ledScripts.AddRange(leftShow); ledScripts.AddRange(rightShow); } } |
Play the application in Unity and select Display #8 in the Game window. This is what you get:
The strip origins are not aligned. This is not a bug in the LED simulator. The physical machine has the same layout. Here is a diagram of the LED numbers for the left and right speakers:
Fun fact: There is a 1 LED gap at the origin for the electrical connection. Because of the recess, this is not apparent from the player’s perspective. When programming, you can safely ignore that little gap. It makes no difference.
To align the left and right speaker light shows, we could update the indices when calling Copy. The two light shows would no longer be identical.
We will try a more general approach and create a rotation operator for light shows.
Edit LightShows.cs and add this method to the LEDShow class:
// Rotate a light show by having each LEDScript be reassigned count LEDs further down the LED string. // This is particularly useful to align the left speaker movement against the right speaker movement. // count is the offset to add to the LED number, range is from -60 to + 60 protected void Rotate(List<LEDScript> lightShow, int count) { foreach (LEDScript ledScript in lightShow) { Match match = Regex.Match(ledScript.led.Name, @"^(.*?)(\d+)$"); if (match.Success) { string namePrefix = match.Groups[1].Value; int ledNumber = int.Parse(match.Groups[2].Value); string newLedName = namePrefix + ((ledNumber + count + 61) % 61); ledScript.led = p3.LEDs[newLedName]; } } } |
Change QuadrantsLightShowMode like this:
public class QuadrantsLightShowMode : LEDShow { public QuadrantsLightShowMode(P3Controller controller, int priority) : base(controller, priority) { LEDScript redScript = new LEDScript(p3.LEDs["speakerLeft0"], this.Priority); redScript.AddCommand(Multimorphic.P3.Colors.Color.red, 0, 10); LEDScript greenScript = new LEDScript(p3.LEDs["speakerLeft0"], this.Priority); greenScript.AddCommand(Multimorphic.P3.Colors.Color.green, 0, 10); LEDScript blueScript = new LEDScript(p3.LEDs["speakerLeft0"], this.Priority); blueScript.AddCommand(Multimorphic.P3.Colors.Color.blue, 0, 10); LEDScript yellowScript = new LEDScript(p3.LEDs["speakerLeft0"], this.Priority); yellowScript.AddCommand(Multimorphic.P3.Colors.Color.yellow, 0, 10); List<LEDScript> leftShow = new List<LEDScript>(); leftShow.AddRange(Copy(redScript, 0, 15, "speakerLeft")); leftShow.AddRange(Copy(greenScript, 15, 15, "speakerLeft")); leftShow.AddRange(Copy(blueScript, 30, 15, "speakerLeft")); leftShow.AddRange(Copy(yellowScript, 45, 16, "speakerLeft")); List<LEDScript> rightShow = Copy(leftShow, "speakerRight"); Rotate(leftShow, -10); Rotate(rightShow, 10); ledScripts.AddRange(leftShow); ledScripts.AddRange(rightShow); } } |
Play the application in Unity and select Display #8 in the Game window. This is what you get:
Let’s create a light show that spins. The underlying concept is to start the LEDscript of the next LED slightly after the previous one.
Edit LightShows.cs, add this line at the top of the file:
using System.Linq; |
Add this method to the LEDShow class:
// Spin a pattern of colors, if there are less than 61 colors, the color pattern repeats. // priority is the priority assigned to every LEDScript created. // fade is the fade time used for every LEDScript command. // period is how long it takes to make one rotation. // ledPrefix is the name of the LEDs before the LED number protected List<LEDScript> Spin(int priority, List<ushort[]> colors, double fade, double period, string ledPrefix) { List<LEDScript> newScripts = new List<LEDScript>(); double timeSlice = period / 61.0; int index = -1; for (int i = 0; i < 61; i++) { index = (index + 1) % colors.Count; string ledName = ledPrefix + i; LEDScript newScript = new LEDScript(p3.LEDs[ledName], priority); for (int c = index; c < colors.Count; c++) { newScript.AddCommand(colors[c], fade, timeSlice); } for (int c = 0; c < index; c++) { newScript.AddCommand(colors[c], fade, timeSlice); } newScripts.Add(newScript); } return newScripts; } |
Change the LEDShowControllerMode constructor like this:
public LEDShowControllerMode (P3Controller controller, int priority) : base(controller, priority) { LEDShow spinLightShowMode = new SpinLightShowMode(controller, priority); shows = new List<ShowData>(); shows.Add(new ShowData(spinLightShowMode, 20)); } |
Add this class below QuadrantsLightShowMode:
public class SpinLightShowMode : LEDShow { public SpinLightShowMode(P3Controller controller, int priority) : base(controller, priority) { List<ushort[]> colors = new List<ushort[]>(); colors.AddRange(Enumerable.Repeat(Multimorphic.P3.Colors.Color.red, 10)); colors.AddRange(Enumerable.Repeat(Multimorphic.P3.Colors.Color.green, 10)); colors.AddRange(Enumerable.Repeat(Multimorphic.P3.Colors.Color.blue, 10)); List<LEDScript> leftShow = Spin(this.Priority, colors, 0.2, 8.0, "speakerLeft"); List<LEDScript> rightShow = Copy(leftShow, "speakerRight"); Rotate(leftShow, -10); Rotate(rightShow, 10); ledScripts.AddRange(leftShow); ledScripts.AddRange(rightShow); } } |
Play the application in Unity and select Display #8 in the Game window. The two speaker LED strips spin in the same direction.
To make the right speaker LED strip spin in the other direction, we can make a mirror image of the light show across a symmetry line.
Edit LightShows.cs, add this method to the LEDShow class:
// Reassign every LEDScript in the lightShow such that its new LED number // is the mirror image from the dividing line traversing at the pivot. // pivot is the LED number where the symmetry line divides the Speaker Panel in half. // This reverses many light patterns. protected void Flip(List<LEDScript> lightShow, int pivot) { foreach (LEDScript ledScript in lightShow) { Match match = Regex.Match(ledScript.led.Name, @"^(.*?)(\d+)$"); if (match.Success) { string namePrefix = match.Groups[1].Value; int ledNumber = int.Parse(match.Groups[2].Value); int distance = ledNumber - pivot; string newLedName = namePrefix + ((pivot - distance + 61) % 61); ledScript.led = p3.LEDs[newLedName]; } } } |
Edit SpinLightShowMode like this:
public class SpinLightShowMode : LEDShow { public SpinLightShowMode(P3Controller controller, int priority) : base(controller, priority) { List<ushort[]> colors = new List<ushort[]>(); colors.AddRange(Enumerable.Repeat(Multimorphic.P3.Colors.Color.red, 10)); colors.AddRange(Enumerable.Repeat(Multimorphic.P3.Colors.Color.green, 10)); colors.AddRange(Enumerable.Repeat(Multimorphic.P3.Colors.Color.blue, 10)); List<LEDScript> leftShow = Spin(this.Priority, colors, 0.2, 8.0, "speakerLeft"); List<LEDScript> rightShow = Copy(leftShow, "speakerRight"); Flip(rightShow, 0); Rotate(leftShow, -10); Rotate(rightShow, 10); ledScripts.AddRange(leftShow); ledScripts.AddRange(rightShow); } } |
Play the application in Unity and select Display #8 in the Game window. The two speaker LED strips spin opposite directions.
You can get a radar sweep effect by spinning white together with black.
Edit LightShows.cs, change the LEDShowControllerMode constructor like this:
public LEDShowControllerMode (P3Controller controller, int priority) : base(controller, priority) { LEDShow radarLightShowMode = new RadarLightShowMode(controller, priority); shows = new List<ShowData>(); shows.Add(new ShowData(radarLightShowMode, 20)); } |
Add this class below SpinLightShowMode:
public class RadarLightShowMode : LEDShow { public RadarLightShowMode(P3Controller controller, int priority) : base(controller, priority) { List<ushort[]> colors = new List<ushort[]>(); colors.AddRange(Enumerable.Repeat(Multimorphic.P3.Colors.Color.white, 5)); colors.AddRange(Enumerable.Repeat(Multimorphic.P3.Colors.Color.barelywhite, 56)); List<LEDScript> leftShow = Spin(this.Priority, colors, 0.1, 6.0, "speakerLeft"); Flip(leftShow, 0); List<LEDScript> rightShow = Copy(leftShow, "speakerRight"); Rotate(leftShow, -10); Rotate(rightShow, 10); ledScripts.AddRange(leftShow); ledScripts.AddRange(rightShow); } } |
Play the application in Unity and select Display #8 in the Game window. The two speaker LED strips spin like a radar.
You can get an interesting fan effect if you let multiple arcs grow within their own fixed sector.
Edit LightShows.cs, add this method to the LEDShow class:
// Grow a pattern of colors from the start LED inclusive to the end LED exclusive // priority is the priority assigned to every LEDScript created. // fade is the fade time used for every LEDScript command. // period is how long it takes to make one full rotation. // step is 1 to increase the led numbers from start to end, or -1 to decrease from start to end. // ledPrefix is the name of the LEDs before the LED number protected List<LEDScript> Grow(int priority, List<ushort[]> colors, double fade, double period, int start, int end, int step, string ledPrefix) { if (colors.Count < 2) { throw new Exception("Grow requires at least 2 colors"); } List<LEDScript> newScripts = new List<LEDScript>(); double timeSlice = period / 61.0; int ledCount = Math.Abs(end - start); double segmentPeriod = timeSlice * ledCount; for (int i = 0; i < ledCount; i++) { string ledName = ledPrefix + ((61 + start + i * step) % 61); LEDScript newScript = new LEDScript(p3.LEDs[ledName], priority); double duration0 = i * timeSlice; newScript.AddCommand(colors[0], fade, duration0); for (int c = 1; c < colors.Count - 1; c++) { newScript.AddCommand(colors[c], fade, segmentPeriod); } newScript.AddCommand(colors[colors.Count - 1], fade, segmentPeriod - duration0); newScripts.Add(newScript); } return newScripts; } |
Change the LEDShowControllerMode constructor like this:
public LEDShowControllerMode (P3Controller controller, int priority) : base(controller, priority) { LEDShow spinBarrierLightShowMode = new SpinBarrierLightShowMode(controller, priority); shows = new List<ShowData>(); shows.Add(new ShowData(spinBarrierLightShowMode, 20)); } |
Add this class below RadarLightShowMode. The LEDScript for speakerLeft60 is a copy of the LEDScript for speakerLeft0. This makes the 3 sectors the same size with equal timings.
public class SpinBarrierLightShowMode : LEDShow { public SpinBarrierLightShowMode(P3Controller controller, int priority) : base(controller, priority) { List<ushort[]> colors = new List<ushort[]>() { Multimorphic.P3.Colors.Color.red, Multimorphic.P3.Colors.Color.green, Multimorphic.P3.Colors.Color.blue, Multimorphic.P3.Colors.Color.orange, Multimorphic.P3.Colors.Color.purple }; List<LEDScript> leftShow = new List<LEDScript>(); leftShow.AddRange(Grow(priority, colors, 0.1, 8.0, 0, 20, 1, "speakerLeft")); leftShow.AddRange(Grow(priority, colors, 0.1, 8.0, 20, 40, 1, "speakerLeft")); leftShow.AddRange(Grow(priority, colors, 0.1, 8.0, 40, 60, 1, "speakerLeft")); leftShow.Add(Clone(leftShow[0], "speakerLeft60")); List<LEDScript> rightShow = Copy(leftShow, "speakerRight"); Rotate(leftShow, -10); Rotate(rightShow, 10); ledScripts.AddRange(leftShow); ledScripts.AddRange(rightShow); } } |
Play the application in Unity and select Display #8 in the Game window. The speaker panel LEDs spin restricted within 3 sectors.
You can get a line scrolling effect using 2 arcs that grow in opposite directions.
Edit LightShows.cs, change the LEDShowControllerMode constructor like this:
public LEDShowControllerMode (P3Controller controller, int priority) : base(controller, priority) { LEDShow scrollLightShowMode = new ScrollLightShowMode(controller, priority); shows = new List<ShowData>(); shows.Add(new ShowData(scrollLightShowMode, 20)); } |
Add this class below SpinBarrierLightShowMode. The LEDScript for speakerLeft60 is a copy of the LEDScript for speakerLeft0. This makes the 2 sectors the same size with equal timings.
public class ScrollLightShowMode : LEDShow { public ScrollLightShowMode(P3Controller controller, int priority) : base(controller, priority) { List<ushort[]> colors = new List<ushort[]>() { Multimorphic.P3.Colors.Color.red, Multimorphic.P3.Colors.Color.green, Multimorphic.P3.Colors.Color.blue, Multimorphic.P3.Colors.Color.orange, Multimorphic.P3.Colors.Color.purple }; List<LEDScript> leftShow = new List<LEDScript>(); leftShow.AddRange(Grow(priority, colors, 2, 10.0, 0, 30, 1, "speakerLeft")); leftShow.AddRange(Grow(priority, colors, 2, 10.0, 59, 29, -1, "speakerLeft")); leftShow.Add(Clone(leftShow[0], "speakerLeft60")); List<LEDScript> rightShow = Copy(leftShow, "speakerRight"); Rotate(leftShow, -10); Rotate(rightShow, 10); ledScripts.AddRange(leftShow); ledScripts.AddRange(rightShow); } } |
Play the application in Unity and select Display #8 in the Game window. The speaker panel LEDs scroll from top to bottom.
You can also try these rotations to have the two speakers scroll away from each other:
Rotate(leftShow, 5); Rotate(rightShow, -5); |
Or these rotations to scroll towards each other:
Rotate(leftShow, 35); Rotate(rightShow, 25); |
Now that we have seen each individual light show, we can enable them all in LEDShowControllerMode.
Edit LightShows.cs, change the LEDShowControllerMode constructor like this:
public LEDShowControllerMode(P3Controller controller, int priority) : base(controller, priority) { LEDShow solidLightShowMode = new SolidLightShowMode(controller, priority); LEDShow fadeLightShowMode = new FadeLightShowMode(controller, priority); LEDShow flashLightShowMode = new FlashLightShowMode(controller, priority); LEDShow quadrantsLightShowMode = new QuadrantsLightShowMode(controller, priority); LEDShow spinLightShowMode = new SpinLightShowMode(controller, priority); LEDShow radarLightShowMode = new RadarLightShowMode(controller, priority); LEDShow spinBarrierLightShowMode = new SpinBarrierLightShowMode(controller, priority); LEDShow scrollLightShowMode = new ScrollLightShowMode(controller, priority); shows = new List<ShowData>(); shows.Add(new ShowData(solidLightShowMode, 5)); shows.Add(new ShowData(fadeLightShowMode, 20)); shows.Add(new ShowData(flashLightShowMode, 6)); shows.Add(new ShowData(quadrantsLightShowMode, 5)); shows.Add(new ShowData(spinLightShowMode, 20)); shows.Add(new ShowData(radarLightShowMode, 20)); shows.Add(new ShowData(spinBarrierLightShowMode, 20)); shows.Add(new ShowData(scrollLightShowMode, 20)); } |
Play the application in Unity and select Display #8 in the Game window. A random light show plays. After a short while, another random light show plays, and so on.
This is just a demo of the speaker panel light shows. A real Attract mode would have light shows that control all LEDs, including the side targets and the LEDs on the installed module. Where it makes sense, the light shows should also control the GUIInserts.
For even more complex light shows that must synchronize different effects, take a look at synchronizers as documented in the SDK Guide.
We did not use LEDHelpers in this TechTip because we created light shows to be played later. LEDHelpers methods are better suited for LEDs indicating a status or a shot to make because they play the script immediately.
A LEDScript will remove itself when its runtime expires provided autoRemove is true (true is the default). This is great if you want the lower priority scripts to regain control of the LEDs as soon as the LEDScript ends.
If you want the LEDScript to hold its last color, you can set autoRemove to false.
LEDScript ledScript = new LEDScript(p3.LEDs["ledName"], Priority);
ledScript.autoRemove = false;
The value of autoRemove is irrelevant for a LEDScript scheduled to run forever (with runtime=-1) because the runtime will never expire.
There are times when you must remove a LEDScript even if autoRemove is true. You must remove your local LEDScripts when stopping your mode. You must also remove a script before reusing it and changing its commands. For example, LEDHelpers is careful to remove a script before changing it.
P3Mode creates a LEDScript for every LED in p3.LEDs.Values. Those LEDScripts are stored in the members:
protected List<LEDScript> LEDScripts;
protected Dictionary<string, LEDScript> LEDScriptsDict;
This is similar to GUIInsertScripts and GUIInsertScriptsDict also created by P3Mode.
If you reuse the LEDScripts created by P3Mode, then you don’t need to remove them when the mode stops because P3Mode will do it automatically. For this to work, make sure to call base.mode_stopped() in your mode_stopped() method. Beware P3Mode creates the LEDScripts with autoRemove=false.
You don’t have to use the LEDScripts created by P3Mode. All examples in P3SampleApp create their own local LEDScripts instead of using those inherited from P3Mode.
This appendix contains the source code collected together. Because of licensing issues, only original source code is listed. Follow the instructions to merge with Assets\Scripts\Modes\Mechs\LightShows.cs
Insert these lines after the existing using directives.
using System.Text.RegularExpressions; using System.Linq; |
Insert these methods in the class LEDShow.
// Copy a LEDScript, i.e. create a new LEDScript with the same LEDCommands but for another LED // The LEDCommands can be shared because they are immutable data objects. protected LEDScript Clone(LEDScript ledScript, string newLedName) { LEDScript newScript = new LEDScript(p3.LEDs[newLedName], ledScript.priority); newScript.script.AddRange(ledScript.script); newScript.autoRemove = ledScript.autoRemove; newScript.scriptName = ledScript.scriptName; newScript.synchronizerIndex = ledScript.synchronizerIndex; return newScript; } // Copy the same LEDScript to a range of LEDs protected List<LEDScript> Copy(LEDScript script, int start, int count, string ledPrefix) { List<LEDScript> lightShow = new List<LEDScript>(); int end = start + count; for (int i = start; i < end; i++) { string ledName = ledPrefix + i; lightShow.Add(Clone(script, ledName)); } return lightShow; } // Copy the LEDScripts in the lightShow renaming the LEDs with the new prefix. // newPrefix is the prefix of the LED names in the new light show. protected List<LEDScript> Copy(List<LEDScript> lightShow, string newPrefix) { List<LEDScript> newScripts = new List<LEDScript>(); foreach (LEDScript ledScript in lightShow) { Match match = Regex.Match(ledScript.led.Name, @"(\d+)$"); if (match.Success) { string newLedName = newPrefix + match.Value; LEDScript newScript = Clone(ledScript, newLedName); newScripts.Add(newScript); } } return newScripts; } // Rotate a light show by having each LEDScript be reassigned count LEDs further down the LED string. // This is particularly useful to align the left speaker movement against the right speaker movement. // count is the offset to add to the LED number, range is from -60 to + 60 protected void Rotate(List<LEDScript> lightShow, int count) { foreach (LEDScript ledScript in lightShow) { Match match = Regex.Match(ledScript.led.Name, @"^(.*?)(\d+)$"); if (match.Success) { string namePrefix = match.Groups[1].Value; int ledNumber = int.Parse(match.Groups[2].Value); string newLedName = namePrefix + ((ledNumber + count + 61) % 61); ledScript.led = p3.LEDs[newLedName]; } } } // Spin a pattern of colors, if there are less than 61 colors, the color pattern repeats. // priority is the priority assigned to every LEDScript created. // fade is the fade time used for every LEDScript command. // period is how long it takes to make one rotation. // ledPrefix is the name of the LEDs before the LED number protected List<LEDScript> Spin(int priority, List<ushort[]> colors, double fade, double period, string ledPrefix) { List<LEDScript> newScripts = new List<LEDScript>(); double timeSlice = period / 61.0; int index = -1; for (int i = 0; i < 61; i++) { index = (index + 1) % colors.Count; string ledName = ledPrefix + i; LEDScript newScript = new LEDScript(p3.LEDs[ledName], priority); for (int c = index; c < colors.Count; c++) { newScript.AddCommand(colors[c], fade, timeSlice); } for (int c = 0; c < index; c++) { newScript.AddCommand(colors[c], fade, timeSlice); } newScripts.Add(newScript); } return newScripts; } // Reassign every LEDScript in the lightShow such that its new LED number // is the mirror image from the dividing line traversing at the pivot. // pivot is the LED number where the symmetry line divides the Speaker Panel in half. // This reverses many light patterns. protected void Flip(List<LEDScript> lightShow, int pivot) { foreach (LEDScript ledScript in lightShow) { Match match = Regex.Match(ledScript.led.Name, @"^(.*?)(\d+)$"); if (match.Success) { string namePrefix = match.Groups[1].Value; int ledNumber = int.Parse(match.Groups[2].Value); int distance = ledNumber - pivot; string newLedName = namePrefix + ((pivot - distance + 61) % 61); ledScript.led = p3.LEDs[newLedName]; } } } // Grow a pattern of colors from the start LED inclusive to the end LED exclusive // priority is the priority assigned to every LEDScript created. // fade is the fade time used for every LEDScript command. // period is how long it takes to make one full rotation. // step is 1 to increase the led numbers from start to end, or -1 to decrease from start to end. // ledPrefix is the name of the LEDs before the LED number protected List<LEDScript> Grow(int priority, List<ushort[]> colors, double fade, double period, int start, int end, int step, string ledPrefix) { if (colors.Count < 2) { throw new Exception("Grow requires at least 2 colors"); } List<LEDScript> newScripts = new List<LEDScript>(); double timeSlice = period / 61.0; int ledCount = Math.Abs(end - start); double segmentPeriod = timeSlice * ledCount; for (int i = 0; i < ledCount; i++) { string ledName = ledPrefix + ((61 + start + i * step) % 61); LEDScript newScript = new LEDScript(p3.LEDs[ledName], priority); double duration0 = i * timeSlice; newScript.AddCommand(colors[0], fade, duration0); for (int c = 1; c < colors.Count - 1; c++) { newScript.AddCommand(colors[c], fade, segmentPeriod); } newScript.AddCommand(colors[colors.Count - 1], fade, segmentPeriod - duration0); newScripts.Add(newScript); } return newScripts; } |
Change the LEDShowControllerMode constructor like this.
public LEDShowControllerMode(P3Controller controller, int priority) : base(controller, priority) { //LEDShow solidLightShowMode0 = new SolidLightShowMode0(controller, priority); //LEDShow solidLightShowMode1 = new SolidLightShowMode1(controller, priority); LEDShow solidLightShowMode = new SolidLightShowMode(controller, priority); LEDShow fadeLightShowMode = new FadeLightShowMode(controller, priority); LEDShow flashLightShowMode = new FlashLightShowMode(controller, priority); LEDShow quadrantsLightShowMode0 = new QuadrantsLightShowMode0(controller, priority); LEDShow quadrantsLightShowMode = new QuadrantsLightShowMode(controller, priority); LEDShow spinLightShowMode0 = new SpinLightShowMode0(controller, priority); LEDShow spinLightShowMode = new SpinLightShowMode(controller, priority); LEDShow radarLightShowMode = new RadarLightShowMode(controller, priority); LEDShow spinBarrierLightShowMode = new SpinBarrierLightShowMode(controller, priority); LEDShow scrollLightShowMode = new ScrollLightShowMode(controller, priority); shows = new List<ShowData>(); //shows.Add(new ShowData(solidLightShowMode0, 5)); //shows.Add(new ShowData(solidLightShowMode1, 5)); shows.Add(new ShowData(solidLightShowMode, 5)); shows.Add(new ShowData(fadeLightShowMode, 20)); shows.Add(new ShowData(flashLightShowMode, 6)); shows.Add(new ShowData(quadrantsLightShowMode0, 5)); shows.Add(new ShowData(quadrantsLightShowMode, 5)); shows.Add(new ShowData(spinLightShowMode0, 20)); shows.Add(new ShowData(spinLightShowMode, 20)); shows.Add(new ShowData(radarLightShowMode, 20)); shows.Add(new ShowData(spinBarrierLightShowMode, 20)); shows.Add(new ShowData(scrollLightShowMode, 20)); } |
Add these classes after LEDShowControllerMode.
public class SolidLightShowMode0 : LEDShow { public SolidLightShowMode0(P3Controller controller, int priority) : base(controller, priority) { for (int i = 0; i < 61; i++) { string ledName = "speakerLeft" + i; LEDScript script = new LEDScript(p3.LEDs[ledName], this.Priority); script.AddCommand(Multimorphic.P3.Colors.Color.red, 0, 10); ledScripts.Add(script); } } } public class SolidLightShowMode1 : LEDShow { public SolidLightShowMode1(P3Controller controller, int priority) : base(controller, priority) { LEDScript ledScript = new LEDScript(p3.LEDs["speakerLeft0"], this.Priority); ledScript.AddCommand(Multimorphic.P3.Colors.Color.red, 0, 10); ledScripts.AddRange(Copy(ledScript, 0, 61, "speakerLeft")); ledScripts.AddRange(Copy(ledScript, 0, 61, "speakerRight")); } } public class SolidLightShowMode : LEDShow { public SolidLightShowMode(P3Controller controller, int priority) : base(controller, priority) { LEDScript ledScript = new LEDScript(p3.LEDs["speakerLeft0"], this.Priority); ledScript.AddCommand(Multimorphic.P3.Colors.Color.red, 0, 10); List<LEDScript> leftShow = Copy(ledScript, 0, 61, "speakerLeft"); List<LEDScript> rightShow = Copy(leftShow, "speakerRight"); ledScripts.AddRange(leftShow); ledScripts.AddRange(rightShow); } } public class FadeLightShowMode : LEDShow { public FadeLightShowMode(P3Controller controller, int priority) : base(controller, priority) { LEDScript ledScript = new LEDScript(p3.LEDs["speakerLeft0"], this.Priority); ledScript.AddCommand(Multimorphic.P3.Colors.Color.red, 1, 1.25); ledScript.AddCommand(Multimorphic.P3.Colors.Color.green, 1, 1.25); ledScript.AddCommand(Multimorphic.P3.Colors.Color.blue, 1, 1.25); ledScript.AddCommand(Multimorphic.P3.Colors.Color.yellow, 1, 1.25); List<LEDScript> leftShow = Copy(ledScript, 0, 61, "speakerLeft"); List<LEDScript> rightShow = Copy(leftShow, "speakerRight"); ledScripts.AddRange(leftShow); ledScripts.AddRange(rightShow); } } public class FlashLightShowMode : LEDShow { public FlashLightShowMode(P3Controller controller, int priority) : base(controller, priority) { LEDScript ledScript = new LEDScript(p3.LEDs["speakerLeft0"], this.Priority); ledScript.AddCommand(Multimorphic.P3.Colors.Color.white, 0, 0.2); ledScript.AddCommand(Multimorphic.P3.Colors.Color.barelywhite, 0.05, 0.2); ledScript.AddCommand(Multimorphic.P3.Colors.Color.white, 0, 0.2); ledScript.AddCommand(Multimorphic.P3.Colors.Color.barelywhite, 0.05, 0.2); ledScript.AddCommand(Multimorphic.P3.Colors.Color.white, 0, 0.2); ledScript.AddCommand(Multimorphic.P3.Colors.Color.barelywhite, 0.05, 2); List<LEDScript> leftShow = Copy(ledScript, 0, 61, "speakerLeft"); List<LEDScript> rightShow = Copy(leftShow, "speakerRight"); ledScripts.AddRange(leftShow); ledScripts.AddRange(rightShow); } } public class QuadrantsLightShowMode0 : LEDShow { public QuadrantsLightShowMode0(P3Controller controller, int priority) : base(controller, priority) { LEDScript redScript = new LEDScript(p3.LEDs["speakerLeft0"], this.Priority); redScript.AddCommand(Multimorphic.P3.Colors.Color.red, 0, 10); LEDScript greenScript = new LEDScript(p3.LEDs["speakerLeft0"], this.Priority); greenScript.AddCommand(Multimorphic.P3.Colors.Color.green, 0, 10); LEDScript blueScript = new LEDScript(p3.LEDs["speakerLeft0"], this.Priority); blueScript.AddCommand(Multimorphic.P3.Colors.Color.blue, 0, 10); LEDScript yellowScript = new LEDScript(p3.LEDs["speakerLeft0"], this.Priority); yellowScript.AddCommand(Multimorphic.P3.Colors.Color.yellow, 0, 10); List<LEDScript> leftShow = new List<LEDScript>(); leftShow.AddRange(Copy(redScript, 0, 15, "speakerLeft")); leftShow.AddRange(Copy(greenScript, 15, 15, "speakerLeft")); leftShow.AddRange(Copy(blueScript, 30, 15, "speakerLeft")); leftShow.AddRange(Copy(yellowScript, 45, 16, "speakerLeft")); List<LEDScript> rightShow = Copy(leftShow, "speakerRight"); ledScripts.AddRange(leftShow); ledScripts.AddRange(rightShow); } } public class QuadrantsLightShowMode : LEDShow { public QuadrantsLightShowMode(P3Controller controller, int priority) : base(controller, priority) { LEDScript redScript = new LEDScript(p3.LEDs["speakerLeft0"], this.Priority); redScript.AddCommand(Multimorphic.P3.Colors.Color.red, 0, 10); LEDScript greenScript = new LEDScript(p3.LEDs["speakerLeft0"], this.Priority); greenScript.AddCommand(Multimorphic.P3.Colors.Color.green, 0, 10); LEDScript blueScript = new LEDScript(p3.LEDs["speakerLeft0"], this.Priority); blueScript.AddCommand(Multimorphic.P3.Colors.Color.blue, 0, 10); LEDScript yellowScript = new LEDScript(p3.LEDs["speakerLeft0"], this.Priority); yellowScript.AddCommand(Multimorphic.P3.Colors.Color.yellow, 0, 10); List<LEDScript> leftShow = new List<LEDScript>(); leftShow.AddRange(Copy(redScript, 0, 15, "speakerLeft")); leftShow.AddRange(Copy(greenScript, 15, 15, "speakerLeft")); leftShow.AddRange(Copy(blueScript, 30, 15, "speakerLeft")); leftShow.AddRange(Copy(yellowScript, 45, 16, "speakerLeft")); List<LEDScript> rightShow = Copy(leftShow, "speakerRight"); Rotate(leftShow, -10); Rotate(rightShow, 10); ledScripts.AddRange(leftShow); ledScripts.AddRange(rightShow); } } public class SpinLightShowMode0 : LEDShow { public SpinLightShowMode0(P3Controller controller, int priority) : base(controller, priority) { List<ushort[]> colors = new List<ushort[]>(); colors.AddRange(Enumerable.Repeat(Multimorphic.P3.Colors.Color.red, 10)); colors.AddRange(Enumerable.Repeat(Multimorphic.P3.Colors.Color.green, 10)); colors.AddRange(Enumerable.Repeat(Multimorphic.P3.Colors.Color.blue, 10)); List<LEDScript> leftShow = Spin(this.Priority, colors, 0.2, 8.0, "speakerLeft"); List<LEDScript> rightShow = Copy(leftShow, "speakerRight"); Rotate(leftShow, -10); Rotate(rightShow, 10); ledScripts.AddRange(leftShow); ledScripts.AddRange(rightShow); } } public class SpinLightShowMode : LEDShow { public SpinLightShowMode(P3Controller controller, int priority) : base(controller, priority) { List<ushort[]> colors = new List<ushort[]>(); colors.AddRange(Enumerable.Repeat(Multimorphic.P3.Colors.Color.red, 10)); colors.AddRange(Enumerable.Repeat(Multimorphic.P3.Colors.Color.green, 10)); colors.AddRange(Enumerable.Repeat(Multimorphic.P3.Colors.Color.blue, 10)); List<LEDScript> leftShow = Spin(this.Priority, colors, 0.2, 8.0, "speakerLeft"); List<LEDScript> rightShow = Copy(leftShow, "speakerRight"); Flip(rightShow, 0); Rotate(leftShow, -10); Rotate(rightShow, 10); ledScripts.AddRange(leftShow); ledScripts.AddRange(rightShow); } } public class RadarLightShowMode : LEDShow { public RadarLightShowMode(P3Controller controller, int priority) : base(controller, priority) { List<ushort[]> colors = new List<ushort[]>(); colors.AddRange(Enumerable.Repeat(Multimorphic.P3.Colors.Color.white, 5)); colors.AddRange(Enumerable.Repeat(Multimorphic.P3.Colors.Color.barelywhite, 56)); List<LEDScript> leftShow = Spin(this.Priority, colors, 0.1, 6.0, "speakerLeft"); Flip(leftShow, 0); List<LEDScript> rightShow = Copy(leftShow, "speakerRight"); Rotate(leftShow, -10); Rotate(rightShow, 10); ledScripts.AddRange(leftShow); ledScripts.AddRange(rightShow); } } public class SpinBarrierLightShowMode : LEDShow { public SpinBarrierLightShowMode(P3Controller controller, int priority) : base(controller, priority) { List<ushort[]> colors = new List<ushort[]>() { Multimorphic.P3.Colors.Color.red, Multimorphic.P3.Colors.Color.green, Multimorphic.P3.Colors.Color.blue, Multimorphic.P3.Colors.Color.orange, Multimorphic.P3.Colors.Color.purple }; List<LEDScript> leftShow = new List<LEDScript>(); leftShow.AddRange(Grow(priority, colors, 0.1, 8.0, 0, 20, 1, "speakerLeft")); leftShow.AddRange(Grow(priority, colors, 0.1, 8.0, 20, 40, 1, "speakerLeft")); leftShow.AddRange(Grow(priority, colors, 0.1, 8.0, 40, 60, 1, "speakerLeft")); leftShow.Add(Clone(leftShow[0], "speakerLeft60")); List<LEDScript> rightShow = Copy(leftShow, "speakerRight"); Rotate(leftShow, -10); Rotate(rightShow, 10); ledScripts.AddRange(leftShow); ledScripts.AddRange(rightShow); } } public class ScrollLightShowMode : LEDShow { public ScrollLightShowMode(P3Controller controller, int priority) : base(controller, priority) { List<ushort[]> colors = new List<ushort[]>() { Multimorphic.P3.Colors.Color.red, Multimorphic.P3.Colors.Color.green, Multimorphic.P3.Colors.Color.blue, Multimorphic.P3.Colors.Color.orange, Multimorphic.P3.Colors.Color.purple }; List<LEDScript> leftShow = new List<LEDScript>(); leftShow.AddRange(Grow(priority, colors, 2, 10.0, 0, 30, 1, "speakerLeft")); leftShow.AddRange(Grow(priority, colors, 2, 10.0, 59, 29, -1, "speakerLeft")); leftShow.Add(Clone(leftShow[0], "speakerLeft60")); List<LEDScript> rightShow = Copy(leftShow, "speakerRight"); Rotate(leftShow, -10); Rotate(rightShow, 10); ledScripts.AddRange(leftShow); ledScripts.AddRange(rightShow); } } |