{ "name": "Twinkling Classic Xmas Strands", "id": "HmcfJWiJTtXFYhrsF", "sources": { "main": "/*\n This patten is intended for strands, not strips or matrices. By strands,\n I mean setups where indicidual pixels on have a few inches of wire bewtween\n them. For this reason, the default preview image on the pattern library \n cheats and shows dark spaces that you will not see when you run the pattern on\n a strip. Want it back? Add `if (index % 5 != 2) hsv(0, 0, 0)` at the end of render()\n \n PixelLights (http://mypixellights.com/) currently makes a 24V (long run) \n outdoor dot strand with up to 9\" spacing. These can mount precisely in gutters \n and other thin sheet materials for permanent architectural installs.\n \n This pattern aims to provide a classic holiday multicolor look that takes\n advantage of Pixelblaze to enhance things in subtle ways.\n \n When you drive around looking at classic strands, you'll nottice that some \n don't include a purple or amber; some look pastel and washed out by age;\n some have warmer tones and some are cooler with muted reds and yellows.\n \n Jeff Vyduna 2020 / MIT License\n*/\n\n\nvar pixels = array(pixelCount) // Stores a color index for each pixel (0..colorCount)\n\nvar colorCount = 5\nvar paletteCount = 3\nvar palettes = array(paletteCount)\n\n// Setup 3-dimensioned array for storing colors\n// `palettes[0][2][1]`` -> First palette's third color's saturation\nfor (i = 0; i < paletteCount; i++) {\n palettes[i] = array(colorCount)\n for (color = 0; color < colorCount; color++) {\n palettes[i][color] = array(3)\n palettes[i][color][1] = 1 // Default full saturation\n palettes[i][color][2] = 1 // Default full brightness\n }\n}\n\nsetHSV = (pal, color, _h, _s, _v) => {\n palettes[pal][color][0] = _h\n palettes[pal][color][1] = _s\n palettes[pal][color][2] = _v\n}\n\n\n// User Controls\n\nvar elapsedTime, lifetime // seconds to cycle all palettes\nexport function sliderCycleTime (_v) { \n lifetime = 5.5 + _v * 30\n}\n\nvar twinkliness = 1\nexport function sliderTwinkles (_v) { \n twinkliness = _v\n}\n\nvar autoFadePalettes = 1\nexport function sliderAutoFadePalettes (_v) { autoFadePalettes = _v > .5 }\n\nvar palette // Currently selected palette. Can interpolate between adjascent palettes.\nvar palPct // Percent we are interpolating between this palette and the next\n// Set palette and palPct based on a 0-1 value for the percent through all the total palettes\nsetPal = (_v) => {\n palette = min(floor(_v * paletteCount), paletteCount - 1) \n palPct = paletteCount * _v % 1\n}\nexport function sliderManualPaletteSelect (_v) { \n setPal(_v % 1)\n}\n\n\n// These color values were specifically tuned for ROK LED (PixelLights) strands\n\n// Classic colors\nsetHSV(0, 0, fromH(.00), 1, 1) // Red\nsetHSV(0, 1, fromH(.33), 1, 1) // Green\nsetHSV(0, 2, fromH(.03), 1, 1) // Amber\nsetHSV(0, 3, fromH(.66), 1, 1) // Blue\nsetHSV(0, 4, fromH(.95), 1, 1) // Purple\n\n// Pastels, warmer\nsetHSV(1, 0, fromH(.999), .7 , 1) // Red\nsetHSV(1, 1, fromH(.33 ), .21, .2) // Green\nsetHSV(1, 2, fromH(.03 ), .7 , 1) // Amber\nsetHSV(1, 3, fromH(.50 ), .3 , .3) // Blue\nsetHSV(1, 4, fromH(.85 ), .9 , .8) // PurplePink\n\n// Cooler hues\nsetHSV(2, 0, fromH(0 ), .2, .4) // Red\nsetHSV(2, 1, fromH(.33), 1 , 1 ) // Green\nsetHSV(2, 2, fromH(.8 ), 0 , .6) // White\nsetHSV(2, 3, fromH(.66), 1 , 1 ) // Blue\nsetHSV(2, 4, fromH(.74), 1 , 1 ) // Purple\n\n\n\n\nfunction setup() {\n initBalanced()\n // Defaults for boards that haven't stored a slider value (but UI looks like 100%)\n autoFadePalettes = 1\n twinkliness = .5\n lifetime = 15\n}\n\nexport function beforeRender(delta) {\n elapsedTime += delta / 1000\n if (elapsedTime > lifetime) elapsedTime = 0\n runPct = elapsedTime / lifetime\n \n t1 = time(1 / 65.536)\n if (autoFadePalettes) setPal(runPct)\n}\n\nexport function render(index) {\n var color = palettes[palette][pixels[index]]\n var nextColor = palettes[(palette + 1) % paletteCount][pixels[index]]\n \n // Interpolate h, s, and v\n h = color[0] + palPct * hDistance(color[0], nextColor[0])\n h = h % 1 + (h < 0) // Wrap hues \n s = (1 - palPct) * color[1] + palPct * nextColor[1]\n v = (1 - palPct) * color[2] + palPct * nextColor[2]\n \n // on, length, phase offset, max peak\n twinkler = twinkle(1, pixelCount*(1-.9*twinkliness), randoms[index], 1)\n twinkler *= (random(1000) > 5 * twinkliness)\n if(twinkliness > .99) twinkler %= 1\n if(twinkliness == 0) twinkler = .25\n\n hsv(fixH(h), sqrt(s), v * v * twinkler)\n}\n\n\n// Distance around a hue circle\nfunction hDistance(y1, y2) { \n var distance = y2 - y1\n if (abs(distance) < 0.5) return distance\n if (y2 > y1) return distance - 1\n return distance + 1\n}\n\n\n// Even out hue circle to something more perceptually even\n// https://www.desmos.com/calculator/19vucj5gkg\nfunction fixH(pH) {\n pH = pH % 1 + (pH < 0) // Wrap inputs\n return wave((pH-0.5)/2)\n}\n// Inverse of the above for h in 0..1\nfunction fromH(_h) {\n _h = _h % 1 + (_h < 0) // Wrap inputs\n if (_h == 0) {\n return 0\n } else {\n return asin(2 * _h - 1) / PI + .5\n }\n}\n\n\n/*\n We want a random sequence of `colorCount` colors, subject to 2 \n constraints: Don't let two subsequent pixels be the same color,\n and make the probability of a color inversely proportional to \n how many times it's been picked in the last N pixels\n*/\nvar urnBallsOfColor = array(colorCount) // Probability urn with replacement\nvar totalBalls // Running sum of how many \"balls\" are in the urn\n\n\nfunction initBalanced() {\n // Initial number of balls in the urn of each color. Anything other than a multiple\n // of ColorCount will be biased towards lower colors. \n totalBalls = colorCount * 1 + 0\n for (i = 0; i < totalBalls; i++) urnBallsOfColor[i % colorCount]++ // Fill urn\n var replacementTail = colorCount - 1 // How many picks before we start replacing balls\n \n pixels[0] = pickAColor(-1) // Pick the initial pixel 0 color\n \n for (i = 1; i < pixelCount; i++) {\n if (i >= replacementTail) { // Replenish a color's probability from some pixels ago\n urnBallsOfColor[pixels[i - replacementTail]]++ \n totalBalls++\n }\n pixels[i] = pickAColor(pixels[i - 1])\n }\n}\n\n// Pick one of the `colorCount` colors from the `chancesPerColor[].flat`\n// urn, but exclude color number `exclude` from the possible choices, or\n// pass a negative value for exclude to include all colors.\nfunction pickAColor(exclude) {\n var i, ballsProcessed = 0\n var excludedCount = (exclude < 0) ? 0 : urnBallsOfColor[exclude]\n var pickedColor = floor(random(totalBalls - excludedCount))\n \n for (i = 0; i < colorCount; i++) {\n if (i == exclude || urnBallsOfColor[i] == 0) continue\n ballsProcessed += urnBallsOfColor[i]\n if (pickedColor < ballsProcessed) break\n }\n\n urnBallsOfColor[i]--\n totalBalls--\n return i\n}\n\n\n// Deterministic PRNG courtesy of Wizard in Static Random Colors\n// Used to consistently initialize an array which determines when \n// each pixel will twinkle in the cycle.\nvar seed = random(0xffff) \nvar xs = seed\nvar randoms = array(pixelCount)\n\nfunction xorshift() {\n xs ^= xs << 7\n xs ^= xs >> 9\n xs ^= xs << 8\n return xs\n}\n// Return a pseudorandom value between 0 and 1\nprf = () => abs(xorshift() / 100 % 1)\nfor (i = 0; i < pixelCount; i++) randoms[i] = prf()\n\n/*\n Twinkle from .25 to 1 (or higher)\n tD - twinkle duration, in seconds\n period - total cycle length in seconds\n offset - phase offset as a percent of `period`. Pass negative \n offsets to repeat twinkle for that percent of `period`\n peak - when 0, max return value is 1. When higher, peak is higher, \n and if limited to 1 (by clamp() or hsv()), you get longer peak duration \n \n See https://www.desmos.com/calculator/gedneqiqih\n*/\nfunction twinkle(tD, period, offset, peak) {\n var t = period * ((time(period / 65.536) + offset) % 1)\n if (t < tD) {\n var v = abs(sin((t / tD + .5) * PI)) + 1\n return (peak + 1.5) * (1 / v - .5) + .25\n } else {\n return .25\n }\n}\n\n\nsetup()\n" }, "preview": "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" }