Skip to content
Merged
Changes from 8 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
f06bf51
Added Morse Code effect to the user_fx usermod
BobLoeffler68 Dec 22, 2025
c2a0a2e
Added a few comments
BobLoeffler68 Dec 22, 2025
580584c
Added punctuation and end-of-message codes, and changing them will fo…
BobLoeffler68 Dec 28, 2025
ab806ab
Fixed mode name in addEffect
BobLoeffler68 Dec 28, 2025
a2efb16
cosmetic changes
BobLoeffler68 Dec 31, 2025
c5cfbe7
* removed PROGMEM from letters and numbers arrays.
BobLoeffler68 Jan 1, 2026
c8a7dd9
More bounds checking added.
BobLoeffler68 Jan 1, 2026
bd7ef64
Added a lookup table for punctuation morse codes.
BobLoeffler68 Jan 1, 2026
85d8099
Removed PALETTE_MOVING_WRAP macro as it's not used in this effect.
BobLoeffler68 Jan 2, 2026
5e0d52a
Moved all static variables to SEGENV.data
BobLoeffler68 Jan 3, 2026
3c1c4be
Now using a bit array instead of a bool array
BobLoeffler68 Jan 4, 2026
d05db80
added a check to see if the pattern is empty
BobLoeffler68 Jan 4, 2026
9c8b2b2
Added "color by word" option, moved Color modes to a slider and added…
BobLoeffler68 Jan 31, 2026
61b1b75
Removed return FRAMETIME
BobLoeffler68 Feb 11, 2026
44df743
Merge branch 'main' of https://github.com/Aircoookie/WLED into pr-mor…
BobLoeffler68 Feb 11, 2026
b3b5297
A few changes suggested by coderabbit.
BobLoeffler68 Feb 11, 2026
a37ccd4
A few changes suggested by coderabbit
BobLoeffler68 Feb 12, 2026
7de0344
Merge branch 'main' of https://github.com/Aircoookie/WLED into pr-mor…
BobLoeffler68 Mar 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
189 changes: 189 additions & 0 deletions usermods/user_fx/user_fx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

// for information how FX metadata strings work see https://kno.wled.ge/interfaces/json-api/#effect-metadata

// paletteBlend: 0 - wrap when moving, 1 - always wrap, 2 - never wrap, 3 - none (undefined)
#define PALETTE_SOLID_WRAP (strip.paletteBlend == 1 || strip.paletteBlend == 3)
#define PALETTE_MOVING_WRAP !(strip.paletteBlend == 2 || (strip.paletteBlend == 0 && SEGMENT.speed == 0))

// static effect, used if an effect fails to initialize
static uint16_t mode_static(void) {
SEGMENT.fill(SEGCOLOR(0));
Expand Down Expand Up @@ -89,6 +93,190 @@ unsigned dataSize = cols * rows; // SEGLEN (virtual length) is equivalent to vW
static const char _data_FX_MODE_DIFFUSIONFIRE[] PROGMEM = "Diffusion Fire@!,Spark rate,Diffusion Speed,Turbulence,,Use palette;;Color;;2;pal=35";


/*
/ Scrolling Morse Code by Bob Loeffler
* Adapted from code by automaticaddison.com and then optimized by claude.ai
* aux0 is the pattern offset for scrolling
* aux1 is the total pattern length
* The sx slider selects the scrolling speed
* Checkbox1 selects the color mode
* Checkbox2 displays punctuation or not
* Checkbox3 displays the End-of-message code or not
* We get the text from the SEGMENT.name and convert it to morse code
* Morse Code rules:
* - there is one space between each part of a letter or number
* - there are 3 spaces between each letter or number
* - there are 7 spaces between each word
*/
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// Build morse pattern into a buffer
void build_morsecode_pattern(const char *morse_code, bool *pattern, int &index, int maxSize) {
const char *c = morse_code;

// Build the dots and dashes into pattern array
while (*c != '\0') {
// it's a dot which is 1 pixel
if (*c == '.') {
if (index >= maxSize - 1) return; // Reserve space for spacing
pattern[index++] = true;
}
else { // Must be a dash which is 3 pixels
if (index >= maxSize - 3) return;
pattern[index++] = true;
pattern[index++] = true;
pattern[index++] = true;
}

// 1 space between parts of a letter/number
if (index >= maxSize) return;
pattern[index++] = false;
c++;
}

// 3 spaces between two letters
if (index >= maxSize - 2) return;
pattern[index++] = false;
if (index >= maxSize - 1) return;
pattern[index++] = false;
if (index >= maxSize) return;
pattern[index++] = false;
}

static uint16_t mode_morsecode(void) {
if (SEGLEN < 1) return mode_static();

// A-Z in Morse Code
static const char * letters[] = {".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....", "..", ".---", "-.-", ".-..", "--",
"-.", "---", ".--.", "--.-", ".-.", "...", "-", "..-", "...-", ".--", "-..-", "-.--", "--.."};
// 0-9 in Morse Code
static const char * numbers[] = {"-----", ".----", "..---", "...--", "....-", ".....", "-....", "--...", "---..", "----."};

// Punctuation in Morse Code
struct PunctuationMapping {
char character;
const char* code;
};

static const PunctuationMapping punctuation[] = {
{'.', ".-.-.-"}, {',', "--..--"}, {'?', "..--.."},
{':', "---..."}, {'-', "-....-"}, {'!', "-.-.--"},
{'&', ".-..."}, {'@', ".--.-."}, {')', "-.--.-"},
{'(', "-.--."}, {'/', "-..-."}, {'\'', ".----."}
};

// Get the text to display
char text[WLED_MAX_SEGNAME_LEN+1] = {'\0'};
size_t len = 0;

if (SEGMENT.name) len = strlen(SEGMENT.name);
if (len == 0) { // fallback if empty segment name
strcpy_P(text, PSTR("I Love WLED!"));
} else {
strcpy(text, SEGMENT.name);
}

// Convert to uppercase in place
for (char *p = text; *p; p++) {
*p = toupper(*p);
}

// Allocate per-segment storage for pattern
constexpr size_t MORSECODE_MAX_PATTERN_SIZE = 1024;
if (!SEGENV.allocateData(MORSECODE_MAX_PATTERN_SIZE)) return mode_static();
bool* morsecodePattern = reinterpret_cast<bool*>(SEGENV.data);

static bool lastCheck2 = false;
Comment thread
BobLoeffler68 marked this conversation as resolved.
Outdated
static bool lastCheck3 = false;
static char lastText[WLED_MAX_SEGNAME_LEN+1] = {'\0'}; // Track last text
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

bool settingsChanged = (SEGMENT.check2 != lastCheck2) || (SEGMENT.check3 != lastCheck3); // check if any checkbox settings were changed since last frame
bool textChanged = (strcmp(text, lastText) != 0); // check if the text has changed since the last frame

// Initialize on first call or rebuild pattern
if (SEGENV.call == 0 || textChanged || settingsChanged) {
strcpy(lastText, text); // Save current text
lastCheck2 = SEGMENT.check2; // Save current state
lastCheck3 = SEGMENT.check3; // Save current state
int patternLength = 0;

// Build complete morse code pattern
for (char *c = text; *c; c++) {
if (patternLength >= MORSECODE_MAX_PATTERN_SIZE - 10) break; // Reserve space for trailing pattern
// Check for letters
if (*c >= 'A' && *c <= 'Z') {
build_morsecode_pattern(letters[*c - 'A'], morsecodePattern, patternLength, MORSECODE_MAX_PATTERN_SIZE);
}
// Check for numbers
else if (*c >= '0' && *c <= '9') {
build_morsecode_pattern(numbers[*c - '0'], morsecodePattern, patternLength, MORSECODE_MAX_PATTERN_SIZE);
}
// Check for a space between words
else if (*c == ' ') {
for (int x = 0; x < 4; x++) { // 7 spaces after the morse code pattern (3 after the last character and now 4 more)
if (patternLength >= MORSECODE_MAX_PATTERN_SIZE) break;
morsecodePattern[patternLength++] = false;
}
}
// Check for punctuation
else if (SEGMENT.check2) {
const char *punctuationCode = nullptr;
for (const auto& p : punctuation) {
if (*c == p.character) {
punctuationCode = p.code;
break;
}
}
if (punctuationCode) {
build_morsecode_pattern(punctuationCode, morsecodePattern, patternLength, MORSECODE_MAX_PATTERN_SIZE);
}
}
}

// Build the End-of-message pattern
if (SEGMENT.check3) {
build_morsecode_pattern(".-.-.", morsecodePattern, patternLength, MORSECODE_MAX_PATTERN_SIZE);
}

for (int x = 0; x < 7; x++) { // 10 spaces after the last pattern (3 after the last character and now 7 more)
if (patternLength >= MORSECODE_MAX_PATTERN_SIZE) break;
morsecodePattern[patternLength++] = false;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

SEGENV.aux1 = patternLength; // Store pattern length
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// Update offset to make the morse code scroll
uint32_t cycleTime = 50 + (255 - SEGMENT.speed)*3;
uint32_t it = strip.now / cycleTime;
if (SEGENV.step != it) {
SEGENV.aux0++; // Increment scroll offset
SEGENV.step = it;
}

int patternLength = SEGENV.aux1;

// Clear background
SEGMENT.fill(BLACK);

// Draw the scrolling pattern
int offset = SEGENV.aux0 % patternLength;

for (int i = 0; i < SEGLEN; i++) {
int patternIndex = (offset + i) % patternLength;
if (morsecodePattern[patternIndex]) {
if (SEGMENT.check1)
SEGMENT.setPixelColor(i, SEGMENT.color_wheel(SEGENV.aux0 + i));
else
SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0));
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

return FRAMETIME;
}
static const char _data_FX_MODE_MORSECODE[] PROGMEM = "Morse Code@Speed,,,,,Color mode,Punctuation,EndOfMessage;;!;1;sx=128,o1=1,o2=1";



/////////////////////
// UserMod Class //
/////////////////////
Expand All @@ -98,6 +286,7 @@ class UserFxUsermod : public Usermod {
public:
void setup() override {
strip.addEffect(255, &mode_diffusionfire, _data_FX_MODE_DIFFUSIONFIRE);
strip.addEffect(255, &mode_morsecode, _data_FX_MODE_MORSECODE);

////////////////////////////////////////
// add your effect function(s) here //
Expand Down