Skip to content

Commit

Permalink
Merge pull request #11 from netmindz/Audio
Browse files Browse the repository at this point in the history
WLED Audio
  • Loading branch information
ewowi committed Aug 15, 2023
2 parents c171f25 + 511cb23 commit 068eb97
Show file tree
Hide file tree
Showing 6 changed files with 301 additions and 5 deletions.
8 changes: 8 additions & 0 deletions platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ lib_deps =
https://github.com/dawidchyrzynski/arduino-home-assistant.git#2.0.0
https://github.com/knolleary/pubsubclient.git#v2.8

[usermod_wledaudio]
build_flags =
-D USERMOD_WLEDAUDIO
lib_deps =
https://github.com/netmindz/WLED-sync#v0.14.0.b16

[env:esp32dev]
platform = espressif32
board = esp32dev
Expand All @@ -45,11 +51,13 @@ build_flags =
${appmod_leds.build_flags}
${usermod_e131.build_flags}
; ${usermod_ha.build_flags}
${usermod_wledaudio.build_flags}
lib_deps =
${starmod.lib_deps}
${appmod_leds.lib_deps}
${usermod_e131.lib_deps}
; ${usermod_ha.lib_deps}
${usermod_wledaudio.lib_deps}
; RAM: [== ] 15.6% (used 51124 bytes from 327680 bytes)
; Flash: [======= ] 68.1% (used 892033 bytes from 1310720 bytes)

Expand Down
198 changes: 198 additions & 0 deletions src/App/AppEffects.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,17 @@
@Copyright (c) 2023 Github StarMod Commit Authors
@license GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007
*/
#ifdef USERMOD_WLEDAUDIO
#include "User/UserModWLEDAudio.h"
#endif
#ifdef USERMOD_E131
#include "../User/UserModE131.h"
#endif


static uint8_t gHue = 0; // rotating "base color" used by many of the patterns
static unsigned long call = 0;
static unsigned long step = 0;

//should not contain variables/bytes to keep mem as small as possible!!
class Effect {
Expand Down Expand Up @@ -444,4 +452,194 @@ class BouncingBalls1D: public Effect {
}
}; // DistortionWaves2D

class RingEffect:public Effect {
protected:
CRGBPalette16 palette = PartyColors_p;
bool INWARD; // TODO: param
uint8_t hue[9]; // TODO: needs to match LedsV::nrOfLedsV

void setRing(int ring, CRGB colour) {
ledsV[ring] = colour;
}

};

class RingRandomFlow:public RingEffect {
public:
const char * name() {
return "RingRandomFlow 1D";
}
void setup() {}
void loop() {
hue[0] = random(0, 255);
for (int r = 0; r < LedsV::nrOfLedsV; r++) {
setRing(r, CHSV(hue[r], 255, 255));
}
for (int r = (LedsV::nrOfLedsV - 1); r >= 1; r--) {
hue[r] = hue[(r - 1)]; // set this ruing based on the inner
}
// FastLED.delay(SPEED);
}
};


#ifdef USERMOD_WLEDAUDIO

class GEQEffect:public Effect {
public:
byte previousBarHeight[1024];

const char * name() {
return "GEQ 2D";
}

void setup() {
fadeToBlackBy( ledsP, LedsV::nrOfLedsP, 16);
for (int i=0; i<LedsV::widthV; i++) previousBarHeight[i] = 0;
}

void loop() {

const int NUM_BANDS = NUM_GEQ_CHANNELS ; // map(SEGMENT.custom1, 0, 255, 1, 16);
const uint16_t cols = LedsV::widthV;
const uint16_t rows = LedsV::heightV;


uint8_t *fftResult = wledAudioMod->fftResults;
#ifdef SR_DEBUG
uint8_t samplePeak = *(uint8_t*)um_data->u_data[3];
#endif

uint8_t speed = mdl->getValue("speed");
uint8_t intensity = mdl->getValue("intensity");
bool colorBars = mdl->getValue("colorBars");
bool smoothBars = mdl->getValue("smoothBars");

bool rippleTime = false;
if (millis() - step >= (256U - intensity)) {
step = millis();
rippleTime = true;
}

int fadeoutDelay = (256 - speed) / 64;
if ((fadeoutDelay <= 1 ) || ((call % fadeoutDelay) == 0)) fadeToBlackBy( ledsP, LedsV::nrOfLedsP, speed);

uint16_t lastBandHeight = 0; // WLEDMM: for smoothing out bars

//WLEDMM: evenly ditribut bands
float bandwidth = (float)cols / NUM_BANDS;
float remaining = bandwidth;
uint8_t band = 0;
for (int x=0; x < cols; x++) {
//WLEDMM if not enough remaining
if (remaining < 1) {band++; remaining+= bandwidth;} //increase remaining but keep the current remaining
remaining--; //consume remaining

// Serial.printf("x %d b %d n %d w %f %f\n", x, band, NUM_BANDS, bandwidth, remaining);
uint8_t frBand = ((NUM_BANDS < 16) && (NUM_BANDS > 1)) ? map(band, 0, NUM_BANDS - 1, 0, 15):band; // always use full range. comment out this line to get the previous behaviour.
// frBand = constrain(frBand, 0, 15); //WLEDMM can never be out of bounds (I think...)
uint16_t colorIndex = frBand * 17; //WLEDMM 0.255
uint16_t bandHeight = fftResult[frBand]; // WLEDMM we use the original ffResult, to preserve accuracy

// WLEDMM begin - smooth out bars
if ((x > 0) && (x < (cols-1)) && (smoothBars)) {
// get height of next (right side) bar
uint8_t nextband = (remaining < 1)? band +1: band;
nextband = constrain(nextband, 0, 15); // just to be sure
frBand = ((NUM_BANDS < 16) && (NUM_BANDS > 1)) ? map(nextband, 0, NUM_BANDS - 1, 0, 15):nextband; // always use full range. comment out this line to get the previous behaviour.
uint16_t nextBandHeight = fftResult[frBand];
// smooth Band height
bandHeight = (7*bandHeight + 3*lastBandHeight + 3*nextBandHeight) / 12; // yeees, its 12 not 13 (10% amplification)
bandHeight = constrain(bandHeight, 0, 255); // remove potential over/underflows
colorIndex = map(x, 0, cols-1, 0, 255); //WLEDMM
}
lastBandHeight = bandHeight; // remember BandHeight (left side) for next iteration
uint16_t barHeight = map(bandHeight, 0, 255, 0, rows); // Now we map bandHeight to barHeight. do not subtract -1 from rows here
// WLEDMM end

if (barHeight > rows) barHeight = rows; // WLEDMM map() can "overshoot" due to rounding errors
if (barHeight > previousBarHeight[x]) previousBarHeight[x] = barHeight; //drive the peak up

CRGB ledColor = CRGB::Black;
for (int y=0; y < barHeight; y++) {
if (colorBars) //color_vertical / color bars toggle
colorIndex = map(y, 0, rows-1, 0, 255);

CRGBPalette16 palette = PartyColors_p;
ledColor = ColorFromPalette(palette, (uint8_t)colorIndex);

ledsV.setPixelColor(x + LedsV::widthV * (rows-1 - y), ledColor);
}

if ((intensity < 255) && (previousBarHeight[x] > 0) && (previousBarHeight[x] < rows)) // WLEDMM avoid "overshooting" into other segments
ledsV.setPixelColor(x + LedsV::widthV * (rows - previousBarHeight[x]), ledColor);

if (rippleTime && previousBarHeight[x]>0) previousBarHeight[x]--; //delay/ripple effect

}
}

bool controls(JsonObject parentVar) {
ui->initNumber(parentVar, "speed", 255, false);
ui->initNumber(parentVar, "intensity", 255, false);
ui->initCheckBox(parentVar, "colorBars", false, false); //
ui->initCheckBox(parentVar, "smoothBars", false, false);

// Nice an effect can register it's own DMX channel, but not a fan of repeating the range and type of the param

e131mod->patchChannel(3, "speed", 255); // TODO: add constant for name
e131mod->patchChannel(4, "intensity", 255);

return true;
}
};

class AudioRings:public RingEffect {
private:
uint8_t *fftResult = wledAudioMod->fftResults;

public:
const char * name() {
return "AudioRings 1D";
}
void setup() {}

void setRingFromFtt(int index, int ring) {
uint8_t val = fftResult[index];
// Visualize leds to the beat
CRGB color = ColorFromPalette(palette, val, 255);
color.nscale8_video(val);
setRing(ring, color);
}


void loop() {
for (int i = 0; i < 7; i++) { // 7 rings

uint8_t val;
if(INWARD) {
val = fftResult[(i*2)];
}
else {
int b = 14 -(i*2);
val = fftResult[b];
}

// Visualize leds to the beat
CRGB color = ColorFromPalette(palette, val, val);
// CRGB color = ColorFromPalette(currentPalette, val, 255, currentBlending);
// color.nscale8_video(val);
setRing(i, color);
// setRingFromFtt((i * 2), i);
}

setRingFromFtt(2, 7); // set outer ring to bass
setRingFromFtt(0, 8); // set outer ring to bass

}
};


#endif // End Audio Effects

static std::vector<Effect *> effects;
13 changes: 9 additions & 4 deletions src/App/AppModLeds.h
Original file line number Diff line number Diff line change
Expand Up @@ -195,13 +195,18 @@ class AppModLeds:public Module {
effects.push_back(new Lines2D);
effects.push_back(new DistortionWaves2D);
effects.push_back(new BouncingBalls1D);
effects.push_back(new RingRandomFlow);
#ifdef USERMOD_WLEDAUDIO
effects.push_back(new GEQEffect);
effects.push_back(new AudioRings);
#endif

#ifdef USERMOD_E131
e131mod->addWatch(1, "bri", 256);
e131mod->addWatch(2, "fx", effects.size());
e131mod->patchChannel(1, "bri", 255); //should be 256??
e131mod->patchChannel(2, "fx", effects.size());
// //add these temporary to test remote changing of this values do not crash the system
// e131mod->addWatch(3, "projection", Projections::count);
// e131mod->addWatch(4, "ledFix", 5); //assuming 5!!!
// e131mod->patchChannel(3, "projection", Projections::count);
// e131mod->patchChannel(4, "ledFix", 5); //assuming 5!!!
#endif

print->print("%s %s %s\n", __PRETTY_FUNCTION__, name, success?"success":"failed");
Expand Down
13 changes: 12 additions & 1 deletion src/User/UserModE131.h
Original file line number Diff line number Diff line change
Expand Up @@ -109,11 +109,22 @@ class UserModE131:public Module {
}
}

void addWatch(uint8_t channel, const char * id, uint16_t max) {
void patchChannel(uint8_t channel, const char * id, uint8_t max = 255) {
varsToWatch[channel].id = id;
varsToWatch[channel].savedValue = 0; // Always reset when (re)patching so variable gets set to DMX value even if unchanged
varsToWatch[channel].max = max;
}

// uint8_t getValue(const char * id) {
// for (int i=0; i < maxChannels; i++) {
// if(varsToWatch[i].id == id) {
// return varsToWatch[i].savedValue;
// }
// }
// print->print("ERROR: failed to find param %s\n", id);
// return 0;
// }

private:
ESPAsyncE131 e131;
boolean e131Created = false;
Expand Down
65 changes: 65 additions & 0 deletions src/User/UserModWLEDAudio.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
@title StarMod
@file UserModWLEDAudio.h
@date 20230810
@repo https://github.com/ewowi/StarMod
@Authors https://github.com/ewowi/StarMod/commits/main
@Copyright (c) 2023 Github StarMod Commit Authors
@license GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007
*/

#pragma once

#include <WLED-sync.h> // https://github.com/netmindz/WLED-sync

class UserModWLEDAudio:public Module {

public:

uint8_t fftResults[NUM_GEQ_CHANNELS]= {0};

UserModWLEDAudio() :Module("WLED Audio Sync Receiver") {
print->print("%s %s\n", __PRETTY_FUNCTION__, name);

isEnabled = false; //default off

print->print("%s %s %s\n", __PRETTY_FUNCTION__, name, success?"success":"failed");
};

//setup filesystem
void setup() {
Module::setup();
print->print("%s %s\n", __PRETTY_FUNCTION__, name);

print->print("%s %s %s\n", __PRETTY_FUNCTION__, name, success?"success":"failed");
}

void onOffChanged() {
if (SysModModules::isConnected && isEnabled) {
print->print("%s %s\n", __PRETTY_FUNCTION__, name);
sync.begin();
} else {
// sync.end();???
}
}

void loop(){
// Module::loop();
if (sync.read()) {
if(debug) print->print("WLED-Sync: ");
for (int b = 0; b < NUM_GEQ_CHANNELS; b++) {
uint8_t val = sync.fftResult[b];
fftResults[b] = val;
if(debug) print->print("%u ", val);
}
if(debug) print->print("\n");
}
}

private:
WLEDSync sync;
boolean debug = false;

};

static UserModWLEDAudio *wledAudioMod;
9 changes: 9 additions & 0 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@
#ifdef USERMOD_HA
#include "User/UserModHA.h"
#endif
#ifdef USERMOD_WLEDAUDIO
#include "User/UserModWLEDAudio.h"
#endif

//setup all modules
void setup() {
Expand Down Expand Up @@ -69,6 +72,9 @@ void setup() {
#ifdef USERMOD_HA
hamod = new UserModHA();
#endif
#ifdef USERMOD_WLEDAUDIO
wledAudioMod = new UserModWLEDAudio();
#endif

//prefered default order in the UI.
//Reorder with care! If changed make sure mdlEnabled.chFun executes var.createNestedArray("value"); and saveModel!
Expand Down Expand Up @@ -100,6 +106,9 @@ void setup() {
#endif
mdls->add(mdl);
mdls->add(ui);
#ifdef USERMOD_WLEDAUDIO
mdls->add(wledAudioMod);
#endif

//do not add mdls itself as it does setup and loop for itself!!! (it is the orchestrator)
mdls->setup();
Expand Down

0 comments on commit 068eb97

Please sign in to comment.