').find('.cp-disp');\\r\\n"
+ " },\\r\\n"
+ " cssAddon: '.cp-xy-slider {width:200px; height:200px;}' +\\r\\n"
+ " '.cp-xy-cursor {width:16px; height:16px; border-width:2px; margin:-8px}' +\\r\\n"
+ " '.cp-z-slider {height:200px; width:40px;}' +\\r\\n"
+ " '.cp-z-cursor {border-width:8px; margin-top:-8px;}' +\\r\\n"
+ " '.cp-alpha {height:40px;}' +\\r\\n"
+ " '.cp-alpha-cursor {border-width:8px; margin-left:-8px;}',\\r\\n"
+ "\\r\\n"
+ " renderCallback: function ($elm, toggled) {\\r\\n"
+ " var colors = this.color.colors;\\r\\n"
+ " onSparkleButtonEvent(Math.round(colors.rgb.r * 255), Math.round(colors.rgb.g * 255), Math.round(colors.rgb.b * 255))\\r\\n"
+ " setPickerColor(sparkleSelectButton, Math.round(colors.rgb.r * 255), Math.round(colors.rgb.g * 255), Math.round(colors.rgb.b * 255))\\r\\n"
+ " }\\r\\n"
+ "})\\r\\n"
+ "$(\\\"#sparkleSpeed\\\").on(\\\"input\\\", function () {\\r\\n"
+ " onSparkleEvent()\\r\\n"
+ "});\\r\\n"
+ "$(\\\"#sparkleSpeed\\\").on(\\\"change\\\", function () {\\r\\n"
+ " onSparkleEvent()\\r\\n"
+ "});\\r\\n"
+ "\\r\\n"
+ "\\r\\n"
+ "function handleSparkleMessage(jsonMessage) {\\r\\n"
+ " if (\\\"Sparkle\\\" in jsonMessage) {\\r\\n"
+ " jsonMessage = jsonMessage.Sparkle\\r\\n"
+ " if (typeof jsonMessage === \\\"object\\\") {\\r\\n"
+ " var newRed = currentRed\\r\\n"
+ " var newGreen = currentGreen\\r\\n"
+ " var newBlue = currentBlue\\r\\n"
+ "\\r\\n"
+ " if ((\\\"Red\\\" in jsonMessage)) {\\r\\n"
+ " if (typeof jsonMessage.Red === \\\"number\\\") {\\r\\n"
+ " newRed = jsonMessage.Red\\r\\n"
+ " }\\r\\n"
+ " }\\r\\n"
+ " if ((\\\"Green\\\" in jsonMessage)) {\\r\\n"
+ " if (typeof jsonMessage.Green === \\\"number\\\") {\\r\\n"
+ " newGreen = jsonMessage.Green\\r\\n"
+ " }\\r\\n"
+ " }\\r\\n"
+ " if ((\\\"Blue\\\" in jsonMessage)) {\\r\\n"
+ " if (typeof jsonMessage.Blue === \\\"number\\\") {\\r\\n"
+ " newBlue = jsonMessage.Blue\\r\\n"
+ " }\\r\\n"
+ " }\\r\\n"
+ " setPickerColor(sparkleSelectButton, newRed, newGreen, newBlue);\\r\\n"
+ " }\\r\\n"
+ " }\\r\\n"
+ "}\\r\\n"
+ "\\r\\n"
+ "function onSparkleEvent() {\\r\\n"
+ " let currentSpeedValue = parseFloat($(\\\"#sparkleSpeed\\\").val())\\r\\n"
+ "\\r\\n"
+ " $(\\\"#sparkleSpeedLabel\\\").html(currentSpeedValue)\\r\\n"
+ "\\r\\n"
+ " msg = {\\r\\n"
+ " \\\"State\\\": true,\\r\\n"
+ " \\\"Mode\\\": \\\"Sparkle\\\",\\r\\n"
+ " \\\"Sparkle\\\": {\\r\\n"
+ " \\\"Speed\\\": currentSpeedValue,\\r\\n"
+ " }\\r\\n"
+ " }\\r\\n"
+ "\\r\\n"
+ " if (msg != sparkleLastMessage && Date.now() - sparkleDebunce > 50) {\\r\\n"
+ " sparkleDebunce = Date.now()\\r\\n"
+ " sparkleLastMessage = msg\\r\\n"
+ " sendMessage(msg)\\r\\n"
+ " }\\r\\n"
+ "}\\r\\n"
+ "\\r\\n"
+ "function onSparkleButtonEvent(red, green, blue) {\\r\\n"
+ " if (currentRed != red || currentGreen != green || currentBlue != blue) {\\r\\n"
+ " currentRed = red\\r\\n"
+ " currentGreen = green\\r\\n"
+ " currentBlue = blue\\r\\n"
+ "\\r\\n"
+ " msg = {\\r\\n"
+ " \\\"State\\\": true,\\r\\n"
+ " \\\"Mode\\\": \\\"Sparkle\\\",\\r\\n"
+ " \\\"Sparkle\\\": {\\r\\n"
+ " \\\"Red\\\": red,\\r\\n"
+ " \\\"Green\\\": green,\\r\\n"
+ " \\\"Blue\\\": blue\\r\\n"
+ " }\\r\\n"
+ " }\\r\\n"
+ " if (Date.now() - sparkleDebunce > 50) {\\r\\n"
+ " sparkleDebunce = Date.now()\\r\\n"
+ " sendMessage(msg)\\r\\n"
+ " }\\r\\n"
+ " }\\r\\n"
+ " if (msg != sparkleLastMessage && Date.now() - sparkleDebunce > 50) {\\r\\n"
+ " sparkleDebunce = Date.now()\\r\\n"
+ " sparkleLastMessage = msg\\r\\n"
+ " sendMessage(msg)\\r\\n"
+ " }\\r\\n"
+ "}\\r\\n");
+ }
+};
diff --git a/Mode Registry/ModeVisualiser.ino b/Mode Registry/ModeVisualiser.ino
new file mode 100644
index 0000000..0fbc6c5
--- /dev/null
+++ b/Mode Registry/ModeVisualiser.ino
@@ -0,0 +1,318 @@
+#define VISUALISER_NUM_SAMPLES 64
+
+class ModeVisualiser : public ModeBase
+{
+private:
+ ADC_MODE(ADC_TOUT);
+ arduinoFFT FFT = arduinoFFT();
+ double visualiserRealSamples[VISUALISER_NUM_SAMPLES];
+ double visualiserImaginarySamples[VISUALISER_NUM_SAMPLES];
+ unsigned long visualiserLastSampleTime = 0;
+ uint16_t visualiserPeriod = 250;
+ uint16_t visualiserMinThreshold = 100;
+ uint16_t visualiserMaxThreshold = 750;
+ uint8_t visualiserNumBinsToSkip = 3;
+ uint8_t visualiserFadeUp = 32;
+ uint8_t visualiserFadeDown = 32;
+ uint8_t visualiserHueOffset = 170;
+
+public:
+ ModeVisualiser() {}
+ virtual void render()
+ {
+ // Only use visualiser when not trying to access the NTP server
+ if (((WiFi.isConnected() && ntpTimeSet) || softApStarted) && !webSocketConnecting)
+ {
+ // ************* ADC Reading *************
+ // Turn off interupts
+ system_soft_wdt_stop();
+ ets_intr_lock(); //close interrupt
+ noInterrupts();
+
+ // Set up the buffer
+ int sampleNumber = 0;
+ uint16_t sampleBuffer[1];
+
+ // Read the first value - seems to be invalid on first read?
+ system_adc_read_fast(sampleBuffer, 1, 8);
+
+ // Constrain the period
+ visualiserPeriod = constrain(visualiserPeriod, 0, 2000);
+
+ // Sample the ADC until buffer is full
+ unsigned long adcBufferTime = micros();
+ while (sampleNumber < VISUALISER_NUM_SAMPLES)
+ {
+ // Get the ADC reading
+ adcBufferTime = micros();
+ system_adc_read_fast(sampleBuffer, 1, 8);
+
+ // If the correct period of time has passed store the reading
+ if (sampleNumber < VISUALISER_NUM_SAMPLES && adcBufferTime - visualiserLastSampleTime > visualiserPeriod)
+ {
+ visualiserRealSamples[sampleNumber] = sampleBuffer[0];
+ visualiserImaginarySamples[sampleNumber] = 0.0;
+ visualiserLastSampleTime = adcBufferTime;
+ sampleNumber++;
+ }
+ }
+
+ // Turn interupts back on
+ interrupts();
+ ets_intr_unlock(); //open interrupt
+ system_soft_wdt_restart();
+
+ // Debug for ADC - Should be centered around 512
+ // for (int i = 0; i < VISUALISER_NUM_SAMPLES; i++) {
+ // Serial.println(visualiserRealSamples[i]);
+ // }
+
+ // ************* Calculate FFT *************
+ FFT.Windowing(visualiserRealSamples, VISUALISER_NUM_SAMPLES, FFT_WIN_TYP_HAMMING, FFT_FORWARD);
+ FFT.Compute(visualiserRealSamples, visualiserImaginarySamples, VISUALISER_NUM_SAMPLES, FFT_FORWARD);
+ FFT.ComplexToMagnitude(visualiserRealSamples, visualiserImaginarySamples, VISUALISER_NUM_SAMPLES);
+
+ // ************* Set the LED's *************
+ // Set the colour of each light based on the values calculated
+ for (int ledNum = 0; ledNum < topNumLeds; ledNum++)
+ {
+ // Map to the bin number, skip all bins required. Start at the second bin to avoid DC
+ uint8_t binNumber = (ledNum < visualiserNumBinsToSkip) ? visualiserNumBinsToSkip : constrain(ledNum, 0, VISUALISER_NUM_SAMPLES / 2);
+ // Serial.print(binNumber);
+ // Serial.print("\t");
+
+ // Subract the minium value chosen for reduction of artifacts
+ double adjustedBinValue = (visualiserRealSamples[binNumber] > visualiserMinThreshold) ? visualiserRealSamples[binNumber] - visualiserMinThreshold : 0.0;
+ // Serial.print(adjustedBinValue);
+ // Serial.print("\t");
+
+ // Set if visualiserRealSamples[binNumber] is above 0
+ if (adjustedBinValue > 0)
+ {
+ // Map the float values to 8 bit integers
+ uint8_t brightnessValue = map(adjustedBinValue, 0, visualiserMaxThreshold, 0, 255);
+ // Serial.println(brightnessValue);
+
+ // Get the current hue of the rainbow for the specific LED
+ uint8_t ledHue = int(255.0 / (topNumLeds - 1) * ledNum + visualiserHueOffset) % 255;
+ CRGB newColour = CRGB(CHSV(ledHue, 255, 255)).nscale8(brightnessValue * (visualiserFadeUp / 255.00));
+
+ // Add the new colour to the current LED
+ ledString[topLeds[ledNum]] = ledString[bottomLeds[ledNum]] += newColour;
+
+ // If the LED num is the first or last, use it to set the sides
+ if (ledNum == 0)
+ {
+ for (int sideLedNum = 0; sideLedNum < rightNumLeds; sideLedNum++)
+ {
+ ledString[rightLeds[sideLedNum]] += newColour;
+ }
+ }
+ else if (ledNum == topNumLeds - 1)
+ {
+ for (int sideLedNum = 0; sideLedNum < leftNumLeds; sideLedNum++)
+ {
+ ledString[leftLeds[sideLedNum]] += newColour;
+ }
+ }
+ }
+ }
+
+ // Fade all leds gradually for a smooth effect
+ fadeToBlackBy(ledString, NUM_LEDS, visualiserFadeDown);
+ // fadeLightBy(ledString, NUM_LEDS, visualiserFadeDown);
+ }
+ else
+ {
+ // Serial.println("Websockets Connecting");
+ delay(1000);
+ }
+ }
+
+ virtual void applyConfig(JsonVariant &settings)
+ {
+ settings["Period"] = visualiserPeriod = settings["Period"] | visualiserPeriod;
+ settings["MinThreshold"] = visualiserMinThreshold = settings["MinThreshold"] | visualiserMinThreshold;
+ settings["MaxThreshold"] = visualiserMaxThreshold = settings["MaxThreshold"] | visualiserMaxThreshold;
+ settings["FadeUp"] = visualiserFadeUp = settings["FadeUp"] | visualiserFadeUp;
+ settings["FadeDown"] = visualiserFadeDown = settings["FadeDown"] | visualiserFadeDown;
+ settings["HueOffset"] = visualiserHueOffset = settings["HueOffset"] | visualiserHueOffset;
+ }
+
+ virtual const char *getName()
+ {
+ return "Visualiser";
+ }
+
+ virtual const char *getTabHtml()
+ {
+ return PSTR("
Visualiser Mode<\\/h2>\\r\\n"
+ "
Here you can set the mode to Visualiser. This mode does an FFT on the ADC of the ESP8266 and maps the\\r\\n"
+ " frequencies\\r\\n"
+ " to the number of top and bottom LED's. To use this mode, an input source must be present on the ADC such\\r\\n"
+ " as an amplified mic\\r\\n"
+ " or an input from a music source such as a Chromecast.\\r\\n"
+ "<\\/p>\\r\\n"
+ "