Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
1 change: 1 addition & 0 deletions wled00/const.h
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,7 @@ static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit");
//Playlist option byte
#define PL_OPTION_SHUFFLE 0x01
#define PL_OPTION_RESTORE 0x02
#define PL_OPTION_CLOCKED 0x04

// Segment capability byte
#define SEG_CAPABILITY_RGB 0x01
Expand Down
32 changes: 30 additions & 2 deletions wled00/data/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2008,6 +2008,7 @@ function plR(p)
{
var pl = plJson[p];
pl.r = gId(`pl${p}rtgl`).checked;
pl.clocked = gId(`pl${p}clocked`).checked;
if (gId(`pl${p}rptgl`).checked) { // infinite
pl.repeat = 0;
delete pl.end;
Expand All @@ -2019,6 +2020,25 @@ function plR(p)
}
}

function plClock(p)
{
const clocked = gId(`pl${p}clocked`).checked;
const manual = gId(`pl${p}manual`);
if (clocked && manual.checked) {
manual.checked = false;
plM(p);
} else if (clocked) {
plJson[p].dur.forEach((e,i)=>{
if (e > 0) return;
plJson[p].dur[i] = 100;
const d = gId(`pl${p}du${i}`);
if (d) d.value = 10;
});
}
manual.disabled = clocked;
plR(p);
}

function plM(p)
{
const man = gId(`pl${p}manual`).checked;
Expand All @@ -2041,17 +2061,24 @@ function makeP(i,pl)
transition: [tr],
repeat: 0,
r: false,
clocked: false,
end: 0
};
const rep = plJson[i].repeat ? plJson[i].repeat : 0;
const man = plJson[i].dur == 0;
const clocked = !!plJson[i].clocked;
if (clocked) plJson[i].dur = plJson[i].dur.map(d => d > 0 ? d : 100);
const man = !clocked && plJson[i].dur.every(d => d == 0);
content =
`<div id="ple${i}" style="margin-top:10px;"></div><label class="check revchkl">Shuffle
<input type="checkbox" id="pl${i}rtgl" onchange="plR(${i})" ${plJson[i].r||rep<0?"checked":""}>
<span class="checkmark"></span>
</label>
<label class="check revchkl">Clock sync
<input type="checkbox" id="pl${i}clocked" onchange="plClock(${i})" ${clocked?"checked":""}>
<span class="checkmark"></span>
</label>
<label class="check revchkl">Manual advance
<input type="checkbox" id="pl${i}manual" onchange="plM(${i})" ${man?"checked":""}>
<input type="checkbox" id="pl${i}manual" onchange="plM(${i})" ${man?"checked":""} ${clocked?"disabled":""}>
<span class="checkmark"></span>
</label>
<label class="check revchkl">Repeat indefinitely
Expand Down Expand Up @@ -3050,6 +3077,7 @@ function expand(i)
formatArr(plJson[p]);
if (isNaN(plJson[p].repeat)) plJson[p].repeat = 0;
if (!plJson[p].r) plJson[p].r = false;
if (!plJson[p].clocked) plJson[p].clocked = false;
if (isNaN(plJson[p].end)) plJson[p].end = 0;
gId('seg' +i).innerHTML = makeP(p,true);
refreshPlE(p);
Expand Down
103 changes: 103 additions & 0 deletions wled00/playlist.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ static PlaylistEntry *playlistEntries = nullptr;
static byte playlistLen; //number of playlist entries
static int8_t playlistIndex = -1;
static uint32_t playlistEntryDur = 0; //duration of the current entry in milliseconds
static bool clockedPlaylistSynced = false;

//values we need to keep about the parent playlist while inside sub-playlist
static int16_t parentPlaylistIndex = -1;
Expand All @@ -41,6 +42,24 @@ void shufflePlaylist() {
DEBUG_PRINTLN(F("Playlist shuffle."));
}

// AI: below section was generated by an AI
static uint32_t playlistShuffleRand(uint32_t &state) {
state ^= state << 13;
state ^= state >> 17;
state ^= state << 5;
return state;
}
Comment thread
softhack007 marked this conversation as resolved.
Outdated

static uint32_t clockedPlaylistSeed(uint32_t cycle) {
uint32_t seed = 2166136261UL;
seed = (seed ^ (uint32_t)currentPlaylist) * 16777619UL;
seed = (seed ^ cycle) * 16777619UL;
seed = (seed ^ playlistLen) * 16777619UL;
if (seed == 0) seed = 2166136261UL;
return seed;
}
// AI: end


void unloadPlaylist() {
if (playlistEntries != nullptr) {
Expand All @@ -51,9 +70,58 @@ void unloadPlaylist() {
playlistLen = 0;
playlistOptions = 0;
playlistEntryDur = 0;
clockedPlaylistSynced = false;
DEBUG_PRINTLN(F("Playlist unloaded."));
}

// AI: below section was generated by an AI
static bool getClockedPlaylistState(uint8_t &entryIndex, uint32_t &entryOffset) {
if (!(playlistOptions & PL_OPTION_CLOCKED) || playlistLen == 0 || playlistEntries == nullptr) return false;
if (toki.getTimeSource() == TOKI_TS_NONE) return false;

// Clocked playlists live on an absolute Toki timeline; every entry must have finite duration.
uint64_t cycleDur = 0;
for (byte i = 0; i < playlistLen; i++) {
if (playlistEntries[i].dur == 0) return false; // infinite entries cannot be placed on a repeating wall-clock timeline
cycleDur += playlistEntries[i].dur;
}
if (cycleDur == 0) return false;

Toki::Time tm = toki.getTime();
uint64_t absoluteMs = (uint64_t)tm.sec * 1000ULL + tm.ms;
uint32_t cycle = (uint32_t)(absoluteMs / cycleDur);
uint64_t cycleOffset = absoluteMs % cycleDur;

uint8_t order[100];
for (byte i = 0; i < playlistLen; i++) order[i] = i;
if (playlistOptions & PL_OPTION_SHUFFLE) {
// Keep shuffle deterministic for all controllers within the same wall-clock cycle.
uint32_t seed = clockedPlaylistSeed(cycle);
for (int i = playlistLen - 1; i > 0; i--) {
int j = playlistShuffleRand(seed) % (i + 1);
uint8_t tmp = order[i];
order[i] = order[j];
order[j] = tmp;
}
}

for (byte i = 0; i < playlistLen; i++) {
uint8_t orderedIndex = order[i];
uint32_t dur = playlistEntries[orderedIndex].dur;
if (cycleOffset < dur) {
entryIndex = orderedIndex;
entryOffset = (uint32_t)cycleOffset;
return true;
}
cycleOffset -= dur;
}

entryIndex = order[playlistLen - 1];
entryOffset = playlistEntries[entryIndex].dur - 1;
return true;
}
// AI: end


int16_t loadPlaylist(JsonObject playlistObj, byte presetId) {
if (currentPlaylist > 0 && parentPlaylistPresetId > 0) return -1; // we are already in nested playlist, do nothing
Expand Down Expand Up @@ -127,6 +195,7 @@ int16_t loadPlaylist(JsonObject playlistObj, byte presetId) {
}
if (playlistEndPreset > 250) playlistEndPreset = 0;
shuffle = shuffle || playlistObj["r"];
if (playlistObj[F("clocked")] | false) playlistOptions |= PL_OPTION_CLOCKED;
if (shuffle) playlistOptions |= PL_OPTION_SHUFFLE;

if (parentPlaylistPresetId == 0 && parentPlaylistIndex > -1) {
Expand All @@ -152,6 +221,39 @@ void handlePlaylist() {
static unsigned long presetCycledTime = 0;
if (currentPlaylist < 0 || playlistEntries == nullptr) return;

if (playlistOptions & PL_OPTION_CLOCKED) {
if (toki.getTimeSource() == TOKI_TS_NONE) {
if (playlistIndex < 0 && !nightlightActive) {
playlistIndex = 0;
jsonTransitionOnce = true;
strip.setTransition(playlistEntries[playlistIndex].tr * 100);
playlistEntryDur = playlistEntries[playlistIndex].dur > 0 ? playlistEntries[playlistIndex].dur : UINT32_MAX;
strip.resetTimebase();
applyPresetFromPlaylist(playlistEntries[playlistIndex].preset);
}
return;
}

uint8_t derivedIndex = 0;
uint32_t entryOffset = 0;
if (!getClockedPlaylistState(derivedIndex, entryOffset)) return;

if (nightlightActive) return;

strip.timebase = (unsigned long)entryOffset - millis();

if (!clockedPlaylistSynced || playlistIndex != (int8_t)derivedIndex) {
playlistIndex = derivedIndex;
jsonTransitionOnce = true;
strip.setTransition(playlistEntries[playlistIndex].tr * 100);
playlistEntryDur = playlistEntries[playlistIndex].dur;
applyPresetFromPlaylist(playlistEntries[playlistIndex].preset);
}
clockedPlaylistSynced = true;
doAdvancePlaylist = false;
return;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

if ((playlistEntryDur < UINT32_MAX && millis() - presetCycledTime > playlistEntryDur) || doAdvancePlaylist) {
presetCycledTime = millis();
if (bri == 0 || nightlightActive) return;
Expand Down Expand Up @@ -190,6 +292,7 @@ void serializePlaylist(JsonObject sObj) {
playlist[F("repeat")] = (playlistIndex < 0 && playlistRepeat > 0) ? playlistRepeat - 1 : playlistRepeat; // remove added repetition count (if not yet running)
playlist["end"] = playlistOptions & PL_OPTION_RESTORE ? 255 : playlistEndPreset;
playlist["r"] = playlistOptions & PL_OPTION_SHUFFLE;
playlist[F("clocked")] = (playlistOptions & PL_OPTION_CLOCKED) != 0;
for (int i=0; i<playlistLen; i++) {
ps.add(playlistEntries[i].preset);
dur.add((playlistEntries[i].dur) / 100); // convert ms back to tenths of seconds (backwards compatibility)
Expand Down