diff --git a/Mode Registry/ModeCircle.ino b/Mode Registry/ModeCircle.ino new file mode 100644 index 0000000..4fbb6f2 --- /dev/null +++ b/Mode Registry/ModeCircle.ino @@ -0,0 +1,78 @@ +class ModeCircle : public ModeBase +{ +private: + int circleActiveLedNumber = 0; + +public: + ModeCircle() {} + virtual void render() + { + // First bring our logical arrays into a list of led numbers to iterate over + int i; + int ledIter = 0; + int leds[NUM_LEDS]; + for (i = 0; i < bottomNumLeds; i++) + { + leds[ledIter++] = bottomLeds[i]; + } + for (i = leftNumLeds - 1; i >= 0; i--) + { + leds[ledIter++] = leftLeds[i]; + } + for (i = topNumLeds - 1; i >= 0; i--) + { + leds[ledIter++] = topLeds[i]; + } + for (i = rightNumLeds - 1; i >= 0; i--) + { + leds[ledIter++] = rightLeds[i]; + } + + // Update the active LED index + EVERY_N_MILLISECONDS(40) + { + circleActiveLedNumber += 1; + if (circleActiveLedNumber == NUM_LEDS) + circleActiveLedNumber = 0; + + // Serial.print("Active number: "); + // Serial.println(circleActiveLedNumber); + + // Darken all LEDs to slightly dim the previous active LEDs + fadeToBlackBy(ledString, NUM_LEDS, 80); + }; + + // And now highlight the active index + for (i = 0; i < NUM_LEDS; i++) + { + if (i == circleActiveLedNumber) + { + ledString[leds[i]] = CRGB::Red; + } + } + } + + virtual void applyConfig(JsonVariant &settings) {} + + virtual const char *getName() + { + return "Circle"; + } + + virtual const char *getTabHtml() + { + return PSTR("

Circle Mode<\\/h2>" + "

A simple dot moving round the lamp.<\\/p>"); + } + + virtual const char *getTabScript() + { + return PSTR("messageEventList.push(handleCircleMessage);\\r\\n" + "function handleCircleMessage(jsonMessage) {\\r\\n" + " if (\\\"Circle\\\" in jsonMessage) {\\r\\n" + " jsonMessage = jsonMessage.Circle\\r\\n" + " if (typeof jsonMessage === \\\"object\\\") {}\\r\\n" + " }\\r\\n" + " }\\r\\n"); + } +}; diff --git a/Mode Registry/ModeColorWipe.ino b/Mode Registry/ModeColorWipe.ino new file mode 100644 index 0000000..f6a9598 --- /dev/null +++ b/Mode Registry/ModeColorWipe.ino @@ -0,0 +1,155 @@ +class ModeColorWipe : public ModeBase +{ +private: + int colorWipePosition = -1; + bool TurningOn = true; + int colorWipeRed = 255; + int colorWipeGreen = 0; + int colorWipeBlue = 255; + int colorWipeSpeed = 20; + +public: + ModeColorWipe() {} + virtual void render() + { + EVERY_N_MILLISECONDS(colorWipeSpeed) + { + colorWipePosition++; + if (TurningOn) + { + fill_solid(ledString, colorWipePosition, CRGB(colorWipeRed, colorWipeGreen, colorWipeBlue)); + if (colorWipePosition == NUM_LEDS) + { + TurningOn = false; + colorWipePosition = -1; + } + } + else + { + fill_solid(ledString, colorWipePosition, CRGB(0, 0, 0)); + if (colorWipePosition == NUM_LEDS) + { + TurningOn = true; + colorWipePosition = -1; + } + } + } + } + + virtual void applyConfig(JsonVariant &settings) + { + settings["Red"] = colorWipeRed = settings["Red"] | colorWipeRed; + settings["Green"] = colorWipeGreen = settings["Green"] | colorWipeGreen; + settings["Blue"] = colorWipeBlue = settings["Blue"] | colorWipeBlue; + settings["Speed"] = colorWipeSpeed = settings["Speed"] | colorWipeSpeed; + } + + virtual const char *getName() + { + return "Colour Wipe"; + } + + virtual const char *getTabHtml() + { + return PSTR("

Color Wipe Mode<\\/h2>\\r\\n" + "

Color Wipe will fill the light with a color in a wiping fashion then wipe the light away.<\\/p>\\r\\n" + "

\\r\\n" + " <\\/input>\\r\\n" + "<\\/div>\\r\\n"); + } + + virtual const char *getTabScript() + { + return PSTR("var colorWipeLastMessage = \\\"\\\"\\r\\n" + "var colorWipeRed = 0\\r\\n" + "var colorWipeGreen = 0\\r\\n" + "var colorWipeBlue = 0\\r\\n" + "var colorWipeDebunce = Date.now()\\r\\n" + "\\r\\n" + "messageEventList.push(handleColorWipeMessage)\\r\\n" + "\\r\\n" + "var colorWipeSelectButton = $('#colorWipeSelectButton').colorPicker({\\r\\n" + " customBG: '#222',\\r\\n" + " margin: '4px -2px 0',\\r\\n" + " doRender: 'div div',\\r\\n" + " preventFocus: true,\\r\\n" + " animationSpeed: 150,\\r\\n" + " forceAlpha: false,\\r\\n" + "\\r\\n" + " // demo on how to make plugins... mobile support plugin\\r\\n" + " buildCallback: function ($elm) {\\r\\n" + " this.$colorPatch = $elm.prepend('
').find('.cp-disp');\\r\\n" + " },\\r\\n" + " cssAddon: '.cp-disp {padding:10px; margin-bottom:6px; font-size:19px; height:20px; line-height:20px}' +\\r\\n" + " '.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" + " onColorWipeButtonEvent(Math.round(colors.rgb.r * 255), Math.round(colors.rgb.g * 255), Math.round(colors.rgb.b * 255))\\r\\n" + "\\r\\n" + " setPickerColor(colorWipeSelectButton, Math.round(colors.rgb.r * 255), Math.round(colors.rgb.g * 255), Math.round(colors.rgb.b * 255))\\r\\n" + " }\\r\\n" + "})\\r\\n" + "\\r\\n" + "function handleColorWipeMessage(jsonMessage) {\\r\\n" + " if (\\\"Color Wipe\\\" in jsonMessage) {\\r\\n" + " jsonMessage = jsonMessage[\\\"Color Wipe\\\"]\\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(colorWipeSelectButton, newRed, newGreen, newBlue);\\r\\n" + " }\\r\\n" + " }\\r\\n" + "}\\r\\n" + "\\r\\n" + "function onColorWipeButtonEvent(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\\\": \\\"Color Wipe\\\",\\r\\n" + " \\\"Color Wipe\\\": {\\r\\n" + " \\\"Red\\\": red,\\r\\n" + " \\\"Green\\\": green,\\r\\n" + " \\\"Blue\\\": blue\\r\\n" + " }\\r\\n" + " }\\r\\n" + "\\r\\n" + " if (Date.now() - colorWipeDebunce > 50) {\\r\\n" + " colorWipeDebunce = Date.now()\\r\\n" + " sendMessage(msg)\\r\\n" + " }\\r\\n" + " if (msg != colorWipeLastMessage && Date.now() - colorWipeDebunce > 50) {\\r\\n" + " colorWipeDebunce = Date.now()\\r\\n" + " colorWipeLastMessage = msg\\r\\n" + " sendMessage(msg)\\r\\n" + " }\\r\\n" + " }\\r\\n" + "}\\r\\n"); + } +}; diff --git a/Mode Registry/ModeConfetti.ino b/Mode Registry/ModeConfetti.ino new file mode 100644 index 0000000..3bf064b --- /dev/null +++ b/Mode Registry/ModeConfetti.ino @@ -0,0 +1,156 @@ +class ModeConfetti : public ModeBase +{ +private: + bool confettiActive = true; + int confettiSpeed = 100; + int confettiPixel = random(NUM_LEDS); + +public: + ModeConfetti() {} + virtual void render() + { + EVERY_N_MILLISECONDS(confettiSpeed) + { + if (confettiActive) + { + confettiPixel = random(NUM_LEDS); + fadeToBlackBy(ledString, NUM_LEDS, 10); + uint8_t pos = random8(NUM_LEDS); + ledString[pos] += CHSV(random8(), random8(), random8()); + } + } + } + + virtual void applyConfig(JsonVariant &settings) + { + // Are not used + //settings["Red"] = confettiRed = settings["Red"] | confettiRed; + //settings["Green"]= confettiGreen = settings["Green"] | confettiGreen; + //settings["Blue"] = confettiBlue = settings["Blue"] | confettiBlue; + settings["Speed"] = confettiSpeed = settings["Speed"] | confettiSpeed; + } + + virtual const char *getName() + { + return "Confetti"; + } + + virtual const char *getTabHtml() + { + return PSTR("

Confetti Mode<\\/h2>\\r\\n" + "

Confetti will flash random colors to emulate confetti.<\\/p>\\r\\n"); + } + + virtual const char *getTabScript() + { + return PSTR("var confettiLastMessage = \\\"\\\"\\r\\n" + " var confettiRed = 0;\\r\\n" + " var confettiGreen = 0;\\r\\n" + " var confettiBlue = 0;\\r\\n" + " var confettiDebunce = Date.now()\\r\\n" + "\\r\\n" + " messageEventList.push(handleConfettiMessage)\\r\\n" + "\\r\\n" + " var confettiSelectButton = $('#confettiSelectButton').colorPicker({\\r\\n" + " customBG: '#222',\\r\\n" + " margin: '4px -2px 0',\\r\\n" + " doRender: 'div div',\\r\\n" + " preventFocus: true,\\r\\n" + " animationSpeed: 150,\\r\\n" + " forceAlpha: false,\\r\\n" + "\\r\\n" + " // demo on how to make plugins... mobile support plugin\\r\\n" + " buildCallback: function ($elm) {\\r\\n" + " this.$colorPatch = $elm.prepend('

').find('.cp-disp');\\r\\n" + " },\\r\\n" + " cssAddon:\\r\\n" + " '.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" + " onConfettiButtonEvent(Math.round(colors.rgb.r * 255), Math.round(colors.rgb.g * 255), Math.round(colors.rgb.b * 255))\\r\\n" + " setPickerColor(confettiSelectButton, Math.round(colors.rgb.r * 255), Math.round(colors.rgb.g * 255), Math.round(colors.rgb.b * 255))\\r\\n" + " }\\r\\n" + " })\\r\\n" + "\\r\\n" + " function onConfettiEvent() {\\r\\n" + " let currentSpeedValue = parseFloat($(\\\"#confettiSpeed\\\").val())\\r\\n" + "\\r\\n" + " $(\\\"#confettiSpeedLabel\\\").html(currentSpeedValue)\\r\\n" + "\\r\\n" + " msg = {\\r\\n" + " \\\"State\\\": true,\\r\\n" + " \\\"Mode\\\": \\\"Confetti\\\",\\r\\n" + " \\\"Confetti\\\": {\\r\\n" + " \\\"Speed\\\": currentSpeedValue,\\r\\n" + " }\\r\\n" + " }\\r\\n" + "\\r\\n" + " if (msg != confettiLastMessage && Date.now() - confettiDebunce > 50) {\\r\\n" + " confettiDebunce = Date.now()\\r\\n" + " confettiLastMessage = msg\\r\\n" + " sendMessage(msg)\\r\\n" + " }\\r\\n" + " }\\r\\n" + "\\r\\n" + " function handleConfettiMessage(jsonMessage) {\\r\\n" + " if (\\\"Confetti\\\" in jsonMessage) {\\r\\n" + " jsonMessage = jsonMessage.Confetti\\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(confettiSelectButton, newRed, newGreen, newBlue);\\r\\n" + " }\\r\\n" + " }\\r\\n" + " }\\r\\n" + "\\r\\n" + " function onConfettiButtonEvent(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\\\": \\\"Confetti\\\",\\r\\n" + " \\\"Confetti\\\": {\\r\\n" + " \\\"Red\\\": red,\\r\\n" + " \\\"Green\\\": green,\\r\\n" + " \\\"Blue\\\": blue\\r\\n" + " }\\r\\n" + " }\\r\\n" + " if (Date.now() - confettiDebunce > 50) {\\r\\n" + " confettiDebunce = Date.now()\\r\\n" + " sendMessage(msg)\\r\\n" + " }\\r\\n" + " }\\r\\n" + " if (msg != confettiLastMessage && Date.now() - confettiDebunce > 50) {\\r\\n" + " confettiDebunce = Date.now()\\r\\n" + " confettiLastMessage = msg\\r\\n" + " sendMessage(msg)\\r\\n" + " }\\r\\n" + " }\\r\\n"); + } +}; diff --git a/Mode Registry/ModeSparkle.ino b/Mode Registry/ModeSparkle.ino new file mode 100644 index 0000000..86e9f36 --- /dev/null +++ b/Mode Registry/ModeSparkle.ino @@ -0,0 +1,171 @@ +class ModeSparkle : public ModeBase +{ + +private: + int sparkleSpeed = 30; + bool sparkleActive = true; + int sparkleRed = 128; + int sparkleGreen = 128; + int sparkleBlue = 128; + int sparklePixel = random(NUM_LEDS); + +public: + ModeSparkle() {} + virtual void render() + { + EVERY_N_MILLISECONDS(sparkleSpeed) + { + if (sparkleActive) + { + sparklePixel = random(NUM_LEDS); + ledString[sparklePixel] = CRGB(sparkleRed, sparkleGreen, sparkleBlue); + } + else + { + ledString[sparklePixel] = CRGB(0, 0, 0); + } + sparkleActive = !sparkleActive; + } + } + + virtual void applyConfig(JsonVariant &settings) + { + settings["Red"] = sparkleRed = settings["Red"] | sparkleRed; + settings["Green"] = sparkleGreen = settings["Green"] | sparkleGreen; + settings["Blue"] = sparkleBlue = settings["Blue"] | sparkleBlue; + settings["Speed"] = sparkleSpeed = settings["Speed"] | sparkleSpeed; + } + + virtual const char *getName() + { + return "Sparkle"; + } + + virtual const char *getTabHtml() + { + return PSTR("

Sparkle Mode<\\/h2>\\r\\n" + "

This is the Sparkle mode.<\\/p>\\r\\n" + "

\\r\\n" + " <\\/input>\\r\\n" + "<\\/div>\\r\\n"); + } + + virtual const char *getTabScript() + { + return PSTR("var sparkleLastMessage = \\\"\\\"\\r\\n" + "var sparkleRed = 0;\\r\\n" + "var sparkleGreen = 0;\\r\\n" + "var sparkleBlue = 0;\\r\\n" + "var sparkleDebunce = Date.now()\\r\\n" + "\\r\\n" + "messageEventList.push(handleSparkleMessage)\\r\\n" + "\\r\\n" + "var sparkleSelectButton = $('#sparkleSelectButton').colorPicker({\\r\\n" + " customBG: '#222',\\r\\n" + " margin: '4px -2px 0',\\r\\n" + " doRender: 'div div',\\r\\n" + " preventFocus: true,\\r\\n" + " animationSpeed: 150,\\r\\n" + " forceAlpha: false,\\r\\n" + "\\r\\n" + " // demo on how to make plugins... mobile support plugin\\r\\n" + " buildCallback: function ($elm) {\\r\\n" + " this.$colorPatch = $elm.prepend('
').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" + "

\\r\\n" + "