diff --git a/Spectrum/Color.cs b/Spectrum/Color.cs
new file mode 100644
index 0000000..39e85bb
--- /dev/null
+++ b/Spectrum/Color.cs
@@ -0,0 +1,100 @@
+using System;
+
+namespace Spectrum {
+ internal class Color {
+
+ public byte R;
+ public byte G;
+ public byte B;
+ public double H;
+ public double S;
+ public double V;
+
+ public Color(byte r, byte g, byte b) {
+ R = r;
+ G = g;
+ B = b;
+ double max = Math.Max(Math.Max(r / 255.0d, g / 255.0d), b / 255.0d);
+ double min = Math.Max(Math.Max(r / 255.0d, g / 255.0d), b / 255.0d);
+
+ double d = max - min;
+ double s = max == 0 ? 0 : d / max;
+ double v = max;
+ double h = 0;
+
+ if (max != min) {
+ if (r > g) {
+ if (r > b) {
+ h = (g - b) / d + (g < b ? 6 : 0);
+ } else {
+ h = (r - g) / d + 4;
+ }
+ } else {
+ if (g > b) {
+ h = (b - r) / d + 2;
+ } else {
+ h = (r - g) / d + 4;
+ }
+ }
+
+ h /= 6;
+ }
+ H = h;
+ S = s;
+ V = v;
+ }
+
+ public Color(double h, double s, double v) {
+ H = h;
+ S = s;
+ V = v;
+ double r = 0, g = 0, b = 0;
+
+ int i = (int)Math.Floor(h * 6);
+ double f = h * 6 - i;
+ double p = v * (1 - s);
+ double q = v * (1 - f * s);
+ double t = v * (1 - (1 - f) * s);
+
+ switch (i % 6) {
+ case 0: r = v; g = t; b = p; break;
+ case 1: r = q; g = v; b = p; break;
+ case 2: r = p; g = v; b = t; break;
+ case 3: r = p; g = q; b = v; break;
+ case 4: r = t; g = p; b = v; break;
+ case 5: r = v; g = p; b = q; break;
+ }
+
+ R = (byte)(255 * r);
+ G = (byte)(255 * g);
+ B = (byte)(255 * b);
+ }
+
+ public override string ToString() {
+ return $"0x{R:x2}{G:x2}{B:x2}";
+ }
+
+ public int ToInt() {
+ return 256*256*(int)R + 256*(int)G + (int)B;
+ }
+
+ private static byte ToByte(double x) {
+ return (byte)Math.Min(Math.Max(Math.Round(x), 0), 255);
+ }
+
+ public static Color BlendRGB(double alpha, Color a, Color b) {
+ return new Color(
+ ToByte((b.R - a.R) * alpha + a.R),
+ ToByte((b.G - a.G) * alpha + a.G),
+ ToByte((b.B - a.B) * alpha + a.B)
+ );
+ }
+ public static Color BlendHSV(double alpha, Color a, Color b) {
+ return new Color(
+ ((b.H - a.H) * alpha + a.H),
+ ((b.S - a.S) * alpha + a.S),
+ ((b.V - a.V) * alpha + a.V)
+ );
+ }
+ }
+}
diff --git a/Spectrum/Converter/ColorConverter.cs b/Spectrum/Converter/ColorConverter.cs
index 12ee20f..f6120c7 100644
--- a/Spectrum/Converter/ColorConverter.cs
+++ b/Spectrum/Converter/ColorConverter.cs
@@ -21,7 +21,7 @@ System.Globalization.CultureInfo culture
return null;
}
int rgb = (int)value;
- Color color = new Color();
+ System.Windows.Media.Color color = new System.Windows.Media.Color();
color.R = (byte)(rgb >> 16);
color.G = (byte)(rgb >> 8);
color.B = (byte)rgb;
@@ -43,7 +43,7 @@ System.Globalization.CultureInfo culture
if (value == null) {
return null;
}
- Color color = (Color)value;
+ System.Windows.Media.Color color = (System.Windows.Media.Color)value;
return (int)color.R << 16
| (int)color.G << 8
| (int)color.B;
diff --git a/Spectrum/Operator.cs b/Spectrum/Operator.cs
index 00ddeef..85fb868 100644
--- a/Spectrum/Operator.cs
+++ b/Spectrum/Operator.cs
@@ -109,11 +109,28 @@ class Operator {
audio,
dome
));
+ this.visualizers.Add(new LEDDomeSplatVisualizer(
+ this.config,
+ audio,
+ dome
+ ));
this.visualizers.Add(new LEDDomeQuaternionTestVisualizer(
this.config,
orientation,
dome
));
+ this.visualizers.Add(new LEDDomeQuaternionPaintbrushVisualizer(
+ this.config,
+ audio,
+ orientation,
+ dome
+ ));
+ this.visualizers.Add(new LEDDomeQuaternionFocusVisualizer(
+ this.config,
+ audio,
+ orientation,
+ dome
+ ));
this.visualizers.Add(new LEDDomeRaceVisualizer(
this.config,
audio,
diff --git a/Spectrum/Spectrum.csproj b/Spectrum/Spectrum.csproj
index 0eeea77..93b4188 100644
--- a/Spectrum/Spectrum.csproj
+++ b/Spectrum/Spectrum.csproj
@@ -62,6 +62,7 @@
MSBuild:Compile
Designer
+
@@ -69,8 +70,11 @@
+
+
+
diff --git a/Spectrum/Visualizers/LEDDomeQuaternionFocusVisualizer.cs b/Spectrum/Visualizers/LEDDomeQuaternionFocusVisualizer.cs
new file mode 100644
index 0000000..063c3a6
--- /dev/null
+++ b/Spectrum/Visualizers/LEDDomeQuaternionFocusVisualizer.cs
@@ -0,0 +1,68 @@
+using Spectrum.Audio;
+using Spectrum.Base;
+using Spectrum.LEDs;
+using System;
+using System.Numerics;
+
+namespace Spectrum.Visualizers {
+ class LEDDomeQuaternionFocusVisualizer : Visualizer {
+
+
+ private Configuration config;
+ private AudioInput audio;
+ private OrientationInput orientation;
+ private LEDDomeOutput dome;
+ private Vector3 spot = new Vector3(0, 1, 0);
+
+ public LEDDomeQuaternionFocusVisualizer(
+ Configuration config,
+ AudioInput audio,
+ OrientationInput orientation,
+ LEDDomeOutput dome
+ ) {
+ this.config = config;
+ this.audio = audio;
+ this.orientation = orientation;
+ this.dome = dome;
+ this.dome.RegisterVisualizer(this);
+ }
+
+ public int Priority {
+ get {
+ return this.config.domeActiveVis == 6 ? 2 : 0;
+ }
+ }
+
+ public bool Enabled { get; set; }
+
+ public Input[] GetInputs() {
+ return new Input[] { this.orientation };
+ }
+
+ void Render() {
+ for (int i = 0; i < LEDDomeOutput.GetNumStruts(); i++) {
+ var leds = LEDDomeOutput.GetNumLEDs(i);
+ for (int j = 0; j < leds; j++) {
+ var p = StrutLayoutFactory.GetProjectedLEDPoint(i, j); // centered on (.5, .5), [0, 1] x [0, 1]
+ var x = 2 * p.Item1 - 1; // now centered on (0, 0) and with range [0, 1]
+ var y = 1 - 2 * p.Item2; // this is because in the original mapping x, y come "out of" the top left corner
+ float z = (float)Math.Sqrt(1 - x * x - y * y);
+ Vector3 pixelPoint = new Vector3((float)x, (float)y, z);
+ // Calibration assigns (0, 1, 0) to be 'forward'
+ // So we want the post-transformed pixel closest to (0, 1, 0)?
+ int color = 0;
+ if (Vector3.Distance(Vector3.Transform(pixelPoint, orientation.rotation), spot) < .25) {
+ color = 0xFFFFFF;
+ }
+
+ this.dome.SetPixel(i, j, color);
+ }
+ }
+ }
+ public void Visualize() {
+ this.Render();
+
+ this.dome.Flush();
+ }
+ }
+}
diff --git a/Spectrum/Visualizers/LEDDomeQuaternionPaintbrushVisualizer.cs b/Spectrum/Visualizers/LEDDomeQuaternionPaintbrushVisualizer.cs
new file mode 100644
index 0000000..b84ce9b
--- /dev/null
+++ b/Spectrum/Visualizers/LEDDomeQuaternionPaintbrushVisualizer.cs
@@ -0,0 +1,69 @@
+using Spectrum.Audio;
+using Spectrum.Base;
+using Spectrum.LEDs;
+using System;
+using System.Numerics;
+
+namespace Spectrum.Visualizers {
+ class LEDDomeQuaternionPaintbrushVisualizer : Visualizer {
+
+
+ private Configuration config;
+ private AudioInput audio;
+ private OrientationInput orientation;
+ private LEDDomeOutput dome;
+ private Vector3 spot = new Vector3(0, 1, 0);
+
+ public LEDDomeQuaternionPaintbrushVisualizer(
+ Configuration config,
+ AudioInput audio,
+ OrientationInput orientation,
+ LEDDomeOutput dome
+ ) {
+ this.config = config;
+ this.audio = audio;
+ this.orientation = orientation;
+ this.dome = dome;
+ this.dome.RegisterVisualizer(this);
+ }
+
+ public int Priority {
+ get {
+ return this.config.domeActiveVis == 5 ? 2 : 0;
+ }
+ }
+
+ public bool Enabled { get; set; }
+
+ public Input[] GetInputs() {
+ return new Input[] { this.orientation };
+ }
+
+ void Render() {
+ for (int i = 0; i < LEDDomeOutput.GetNumStruts(); i++) {
+ var leds = LEDDomeOutput.GetNumLEDs(i);
+ for (int j = 0; j < leds; j++) {
+ var p = StrutLayoutFactory.GetProjectedLEDPoint(i, j); // centered on (.5, .5), [0, 1] x [0, 1]
+ var x = 2 * p.Item1 - 1; // now centered on (0, 0) and with range [0, 1]
+ var y = 1 - 2 * p.Item2; // this is because in the original mapping x, y come "out of" the top left corner
+ float z = (float)Math.Sqrt(1 - x * x - y * y);
+ Vector3 pixelPoint = new Vector3((float)x, (float)y, z);
+ // Calibration assigns (0, 1, 0) to be 'forward'
+ // So we want the post-transformed pixel closest to (0, 1, 0)?
+ Color color = new Color(0, 0, 0);
+ if(Vector3.Distance(Vector3.Transform(pixelPoint, orientation.rotation), spot) < .25) {
+ color = new Color((256 * (orientation.rotation.W + 1)) / 256d, 1, 1);
+ }
+ // todo : rework to use jan karls buffer
+ this.dome.SetPixel(i, j, color.ToInt());
+ }
+ }
+ }
+
+ public void Visualize() {
+ this.Render();
+
+ this.dome.Flush();
+ }
+ }
+}
diff --git a/Spectrum/Visualizers/LEDDomeQuaternionTestVisualizer.cs b/Spectrum/Visualizers/LEDDomeQuaternionTestVisualizer.cs
index f5f983b..8668329 100644
--- a/Spectrum/Visualizers/LEDDomeQuaternionTestVisualizer.cs
+++ b/Spectrum/Visualizers/LEDDomeQuaternionTestVisualizer.cs
@@ -1,10 +1,6 @@
-using Spectrum.Audio;
-using Spectrum.Base;
+using Spectrum.Base;
using Spectrum.LEDs;
using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Linq;
using System.Numerics;
namespace Spectrum.Visualizers {
@@ -13,7 +9,6 @@ class LEDDomeQuaternionTestVisualizer : Visualizer{
private Configuration config;
private OrientationInput orientation;
private LEDDomeOutput dome;
- private Vector4 spot = new Vector4(0, 1, 0, 0);
public LEDDomeQuaternionTestVisualizer(
Configuration config,
@@ -39,8 +34,6 @@ LEDDomeOutput dome
}
void Render() {
- Vector4 newSpot = Vector4.Transform(spot, orientation.rotation);
- Tuple projectedSpot = Convert3D(newSpot);
for (int i = 0; i < LEDDomeOutput.GetNumStruts(); i++) {
var leds = LEDDomeOutput.GetNumLEDs(i);
for (int j = 0; j < leds; j++) {
@@ -52,30 +45,19 @@ LEDDomeOutput dome
Vector3 pixelPointQuat = Vector3.Transform(pixelPoint, orientation.rotation);
// Color maxes
int maxIndex = MaxBy(pixelPointQuat);
- int color = 0;
+ Color color = new Color(0, 0, 0);
if(maxIndex == 0) {
- color = 0xFF0000;
+ color = new Color(255, 0, 0);
} else if(maxIndex == 1) {
- color = 0x00FF00;
+ color = new Color(0, 255, 0);
} else if(maxIndex == 2) {
- color = 0x0000FF;
+ color = new Color(0, 0, 255);
}
-
- this.dome.SetPixel(i, j, color);
+ this.dome.SetPixel(i, j, color.ToInt());
}
}
}
- private static Tuple Convert3D(Vector4 vector) {
- // Lambert azimuthal equal-area projection
- double x = Math.Sqrt(2 / (1 + vector.X)) * vector.Y * -1;
- double y = Math.Sqrt(2 / (1 + vector.X)) * vector.Z;
- // Dome coordinate space is [0, 1] x [0, 1] centered at (.5, .5)
- x = (x + 1) / 2;
- y = (y + 1) / 2;
- return new Tuple(x, y);
- }
-
public void Visualize() {
this.Render();
diff --git a/Spectrum/Visualizers/LEDDomeSplatVisualizer.cs b/Spectrum/Visualizers/LEDDomeSplatVisualizer.cs
new file mode 100644
index 0000000..aa4012c
--- /dev/null
+++ b/Spectrum/Visualizers/LEDDomeSplatVisualizer.cs
@@ -0,0 +1,110 @@
+using Spectrum.Audio;
+using Spectrum.Base;
+using Spectrum.LEDs;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+
+namespace Spectrum {
+
+ class LEDDomeSplatVisualizer : Visualizer {
+
+ private Configuration config;
+ private AudioInput audio;
+ private LEDDomeOutput dome;
+ private LEDDomeOutputBuffer buffer;
+
+ private double currentAngle;
+ private double currentGradient;
+ private double currentCenterAngle;
+ private double lastProgress;
+
+ public LEDDomeSplatVisualizer(
+ Configuration config,
+ AudioInput audio,
+ LEDDomeOutput dome
+ ) {
+ this.config = config;
+ this.audio = audio;
+ this.dome = dome;
+ this.dome.RegisterVisualizer(this);
+ this.buffer = this.dome.MakeDomeOutputBuffer();
+ }
+
+ public int Priority {
+ get {
+ return this.config.domeActiveVis == 7 ? 2 : 0;
+ }
+ }
+
+ public bool Enabled { get; set; }
+
+ public Input[] GetInputs() {
+ return new Input[] { this.audio };
+ }
+
+ void Render() {
+
+ double level = this.audio.LevelForChannel(0);
+ // Sqrt makes values larger and gives more resolution for lower values
+ double adjustedLevel = Clamp(Math.Sqrt(level), 0.1, 1);
+
+ double progress = this.config.beatBroadcaster.ProgressThroughMeasure;
+
+ buffer.Fade(0.96, 0);
+
+ if (progress < this.lastProgress) {
+ var rand = new Random();
+ var cx = Map(rand.NextDouble(), 0, 1, 0.1, 0.9);
+ var cy = Map(rand.NextDouble(), 0, 1, 0.1, 0.9);
+ double radius = adjustedLevel * 0.25;
+ var color = rand.Next() % 8;
+
+ for (int i = 0; i < buffer.pixels.Length; i++) {
+ var pixel = buffer.pixels[i];
+
+ var dx = pixel.x - cx;
+ var dy = pixel.y - cy;
+ var dist = Math.Sqrt(dx * dx + dy * dy);
+ if (dist < radius) {
+ buffer.pixels[i].color = this.dome.GetGradientColor(
+ color,
+ dist/radius,
+ 0,
+ true
+ );
+ }
+ }
+ }
+
+ this.dome.WriteBuffer(buffer);
+ this.lastProgress = progress;
+ }
+
+ public void Visualize() {
+ this.Render();
+
+ this.dome.Flush();
+ }
+
+ // Clamp value x inside range a-b
+ private static double Clamp(double x, double a, double b) {
+ if (x < a) return a;
+ if (x > b) return b;
+ return x;
+ }
+
+ // Map value x from range a-b to range c-d
+ private static double Map(
+ double x,
+ double a,
+ double b,
+ double c,
+ double d
+ ) {
+ return (x - a) * (d - c) / (b - a) + c;
+ }
+ }
+
+}
diff --git a/Spectrum/Windows/BarSimulatorWindow.xaml.cs b/Spectrum/Windows/BarSimulatorWindow.xaml.cs
index bc8aeb0..acc13a9 100644
--- a/Spectrum/Windows/BarSimulatorWindow.xaml.cs
+++ b/Spectrum/Windows/BarSimulatorWindow.xaml.cs
@@ -114,7 +114,7 @@ public partial class BarSimulatorWindow : Window {
y * 10,
3,
3,
- Color.FromArgb(
+ System.Windows.Media.Color.FromArgb(
(byte)(color >> 24),
(byte)(color >> 16),
(byte)(color >> 8),
diff --git a/Spectrum/Windows/MainWindow.xaml b/Spectrum/Windows/MainWindow.xaml
index 0acebb4..0419c6e 100644
--- a/Spectrum/Windows/MainWindow.xaml
+++ b/Spectrum/Windows/MainWindow.xaml
@@ -371,6 +371,9 @@
+
+
+
diff --git a/Spectrum/Windows/MainWindow.xaml.cs b/Spectrum/Windows/MainWindow.xaml.cs
index a2dbb9b..6aa6dd6 100644
--- a/Spectrum/Windows/MainWindow.xaml.cs
+++ b/Spectrum/Windows/MainWindow.xaml.cs
@@ -194,7 +194,10 @@ private class MidiBindingEntry {
[1] = this.domeActiveVisualizerRadial,
[2] = this.domeActiveVisualizerRace,
[3] = this.domeActiveVisualizerSnakes,
- [4] = this.domeActiveVisualizerQuaternionTest
+ [4] = this.domeActiveVisualizerQuaternionTest,
+ [5] = this.domeActiveVisualizerQuaternionPaintbrush,
+ [6] = this.domeActiveVisualizerQuaternionFocus,
+ [7] = this.domeActiveVisualizerSplat
}, true));
this.Bind("boardBeagleboneOPCAddress", this.boardBeagleboneOPCHostAndPort, TextBox.TextProperty);
this.Bind("boardBeagleboneOPCFPS", this.boardBeagleboneOPCFPSLabel, Label.ContentProperty);
diff --git a/Spectrum/Windows/VJHUDWindow.xaml b/Spectrum/Windows/VJHUDWindow.xaml
index feb777a..be48b05 100644
--- a/Spectrum/Windows/VJHUDWindow.xaml
+++ b/Spectrum/Windows/VJHUDWindow.xaml
@@ -374,6 +374,9 @@
+
+
+
diff --git a/Spectrum/Windows/VJHUDWindow.xaml.cs b/Spectrum/Windows/VJHUDWindow.xaml.cs
index 2b2674d..f7aeccf 100644
--- a/Spectrum/Windows/VJHUDWindow.xaml.cs
+++ b/Spectrum/Windows/VJHUDWindow.xaml.cs
@@ -75,7 +75,7 @@ private class LevelDriverPresetEntry {
timeBuilder.Append(logMessage.time.ToLongTimeString());
timeBuilder.Append("] ");
Run timeRun = new Run(timeBuilder.ToString());
- timeRun.Foreground = new SolidColorBrush(Color.FromRgb(0, 0, 255));
+ timeRun.Foreground = new SolidColorBrush(System.Windows.Media.Color.FromRgb(0, 0, 255));
Run messageRun = new Run(logMessage.message);
Paragraph paragraph = new Paragraph();
paragraph.Inlines.Add(timeRun);
@@ -228,8 +228,10 @@ private class LevelDriverPresetEntry {
[1] = this.domeActiveVisualizerRadial,
[2] = this.domeActiveVisualizerRace,
[3] = this.domeActiveVisualizerSnakes,
- [4] = this.domeActiveVisualizerQuaternionTest
-
+ [4] = this.domeActiveVisualizerQuaternionTest,
+ [5] = this.domeActiveVisualizerQuaternionPaintbrush,
+ [6] = this.domeActiveVisualizerQuaternionFocus,
+ [7] = this.domeActiveVisualizerSplat
}, true));
this.Bind("domeVolumeRotationSpeed", this.domeVolumeRotationSpeed, ComboBox.SelectedItemProperty, BindingMode.TwoWay, new SpecificValuesConverter(new Dictionary { [0] = this.dprs0, [0.125] = this.dprs1, [0.25] = this.dprs2, [0.5] = this.dprs3, [1.0] = this.dprs4, [2.0] = this.dprs5, [4.0] = this.dprs6 }, true));
this.Bind("domeGradientSpeed", this.domeGradientSpeed, ComboBox.SelectedItemProperty, BindingMode.TwoWay, new SpecificValuesConverter(new Dictionary { [0] = this.dsrs0, [0.125] = this.dsrs1, [0.25] = this.dsrs2, [0.5] = this.dsrs3, [1.0] = this.dsrs4, [2.0] = this.dsrs5, [4.0] = this.dsrs6 }, true));