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