Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <h1>LED Strip Simulator</h1>
<p>A software simulation of individually addressable RGB LED strips 
  (WS2811/WS2812), often used with Arduino.</p>
<p><b>NOTE:</b> <a href="https://dougalcampbell.github.io/LEDStrip/">An updated version of my libraries is available.</a></p>
<div class="ledstrip">
    <div class="ledlight"></div>
</div>
<br />
<form>
  <select id="animselect">
    <option value="chasers">Chasers</option>
    <option value="flares">Flares</option>
    <option value="torture">Water Torture</option>
    <option value="stop">Stop</option>
  </select>
</form>
<p id="output"></p>
<p>For more information:</p>
<ul>
  <li><a href="https://github.com/DannyHavenith/ws2811">Danny Havenith's WS2811 Arduino library</a></li>
  <li><a href="http://rurandom.org/justintime/index.php?title=Driving_the_WS2811_at_800_kHz_with_an_8_MHz_AVR">Detailed description and videos</a></li>
</ul>

              
            
!

CSS

              
                body {
  background-color: #333;
  color: #aaa;
}

br { clear: both; }

form {display: block;}

a,a:link {
  color: #77f;
}

a:visited {
  color: #88c;
}

li {
  margin-bottom: 0.25em;
}

.ledstrip {
    display: block;
    float: left;
    height: 16px;
    border: 1px solid #888;
    background-color: #aaa;
    clear: right;
  margin: 1em 1em;
}
.ledstrip:after {
  float: none;
  display: block;
  clear: both;
}

.ledlight {
    display: block;
    width: 10px;
    height: 10px;
    float: left;
    border: 1px solid #ddd;
    background-color: #000;
    margin: 2px;
}

#output {
  clear: left;
  margin-top: 1em;
  
}
              
            
!

JS

              
                /** 
 * LEDStrip lib
 * 
 * Simulate fucntionality of individually-addressable RGB LED strips 
 * (WS2811/WS2812)
 * 
 * The physical LED strips are serially chained LED modules, which use
 * a simple, but timing-critical 800kHz protocol for handling data. The
 * controller pushes RGB values into the first module, bit-by-bit, until
 * all 24 bits are in. When the next RGB triplet comes in, the first 
 * module will push the previous value out to the next module in the chain.
 * After the controller pushes all of the values onto the stack, it latches
 * the data line low for an extended time, signaling the chain to display
 * the new color values.
 *
 * Each bit is sent in a 1250ns (1.25 us) timeframe. A zero is represented 
 * by pulling the data line high for the first 250ns, then going low for 
 * 1000ns. For a one, the line is pulled high for 1000ns, then low for 250ns.
 * After every three bytes of data is sent, the first module pushes the 
 * previous values out to the next module, and so forth down the chain. When 
 * latch signal is sent, every module displays its new color. This gives an 
 * update rate of 33,333 LED/sec. Or to put it another way, you could drive 
 * about 500 LEDs with a 60 frames/sec update rate.
 */

/**
 * Strip container -- houses one or more LED instances
 * 
 * We cheat a little by accepting an RGB triplet all at once, rather than 
 * just pushing individual byte values in sequence. As a consequence, we 
 * also don't attempt to simulate GRB ordering issues. Maybe later.
 */
function LEDStrip(count, stripElem, ledElem) {
	this.elem = {}; // HTML element the strip is bound to
	this.len = count || 30; // default to 30 lights
	this.lights = []; // Array of LED objects
	this.leds = Array(this.len - 1); // Array of color values to send

	this.elem = stripElem;
	// REFACTOR: eliminate jQuery dependency
	$(this.elem).addClass('LEDStrip');

	/**
	 * remove any old lights
	 */
	while (this.lights.length) {
		var rem = this.lights.pop();
		rem.next = undefined; // help with garbage collection?
	}

	$(ledElem).detach();

	/**
	 * add new lights
	 */
	for (var i = 0; i < this.len; i++) {
		var light = new LED;
		// REFACTOR: eliminate jQuery dependency
    	light.elem = $(ledElem).clone().addClass('LEDLight');
    	$(this.elem).append(light.elem);
		this.lights.push(light);
		if (i) { // > zero
			this.lights[i-1].next = this.lights[i]; // pointer
		}
	}
}

LEDStrip.prototype.pushRGB = function (r, g, b) {
		if (this.lights[0] && this.lights[0].datain) {
			this.lights[0].datain(r, g, b);
		}
}

LEDStrip.prototype.latch = function () {
		if (this.lights[0] && this.lights[0].latch) {
			this.lights[0].latch();
		}
}

// Set colors in one big batch. Array of RGB triplets.
LEDStrip.prototype.send = function() {
	for (var i = 0; i < this.leds.length; i++) {
		this.pushRGB(this.leds[i][0], this.leds[i][1], this.leds[i][2]);
	}
	this.latch();
}

LEDStrip.prototype.clearLeds = function () {
	for (i=0; i < this.leds.length; i++)
		this.leds[i] = [0,0,0];
}

/**
 * Individual LED module
 */
function LED() {
	this.elem = {}; // HTML element the LED is bound to
	this.next = undefined; // next LED instance in chain
	this.rgb = [];
}

LED.prototype.latch = function () {
	// REFACTOR: eliminate jQuery dependency
	$(this.elem).css('background-color', 
				'rgb(' + this.rgb[0] + ',' + this.rgb[1] + ',' + this.rgb[2] + ')'
			);
	this.rgb = []; // clear buffer
	if ('next' in this && this.next) {
		this.next.latch();
	}
}

LED.prototype.datain = function (r, g, b) {
	if (this.rgb.length) {
		this.dataout(this.rgb[0], this.rgb[1], this.rgb[2]);
	}
	this.rgb = [r, g, b];
}

LED.prototype.dataout = function (r, g, b) {
	if ('next' in this && this.next) {
		this.next.datain(r, g, b);
	}
};

// http://paulirish.com/2011/requestanimationframe-for-smart-animating/
// http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
 
// requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel
 
// MIT license

(function() {
    var lastTime = 0;
    var vendors = ['ms', 'moz', 'webkit', 'o'];
    for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
        window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
        window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] 
                                   || window[vendors[x]+'CancelRequestAnimationFrame'];
    }
 
    if (!window.requestAnimationFrame)
        window.requestAnimationFrame = function(callback, element) {
            var currTime = new Date().getTime();
            var timeToCall = Math.max(0, 16 - (currTime - lastTime));
            var id = window.setTimeout(function() { callback(currTime + timeToCall); }, timeToCall);
            lastTime = currTime + timeToCall;
            return id;
        };
 
    if (!window.cancelAnimationFrame)
        window.cancelAnimationFrame = function(id) {
            clearTimeout(id);
        };
}());

/****
 * Do eet!
 * 
 * This code currently relies on some global variables, which I don't like.
 * It's a side-effect of my translation of the original AVR C++ code. I 
 * need to wrap it up and encapsulate it better, maybe with some IoC for
 * the connection between my LEDStrip class and this controller code.
 */

var strip, animation;
var length = 48;
var r = g = b = count = 0;
var flare_count = 16;
var current_flare = 0;
var flare_pause = 1;
var flares = Array(flare_count);

var chasers = [
	new Chaser([200, 75, 75], 0, true),
	new Chaser([75, 200, 75], Math.floor(length * 0.2), false),
	new Chaser([75, 75, 200], Math.floor(length * 0.4), true),
	new Chaser([200, 200, 75], Math.floor(length * 0.6), false),
	new Chaser([220, 75, 175], Math.floor(length * 0.8), true),
	];

for (var i = 0; i < flare_count; i++) {
	//flares[i] = new Flare([255, 255, 255], Math.floor(length/i), i, i*2);
	flares[i] = new Flare();
}

var torture;

$(document).ready(function() {
	strip = new LEDStrip(length, $('.ledstrip'), $('.ledlight'));
	animation = requestAnimationFrame(chase);
	torture = new water_torture(strip);
	torture.init();
  
  $('#animselect').change(function(e) {
  var newanim = $(e.target).val();
  //strip.clearLeds();
  strip.send();
  console.log('change! ' + newanim); 
  cancelAnimationFrame(animation);
  switch(newanim) {
    case 'torture': 
      animation = requestAnimationFrame(torture.animate.bind(torture));
      console.log('torture ' + animation);
      break;
    case 'chasers':
      animation = requestAnimationFrame(chase);
      console.log('chasers ' + animation);
      break;
    case 'flares':
	  strip.clearLeds();
    
      animation = requestAnimationFrame(flare);
      console.log('flares ' + animation);
      break;
    case 'stop':
      console.log('stop ' + animation);
      break;
  }
  });
});

function chase() {
	animation = requestAnimationFrame(chase);
	// slow things down. 1 == full speed
    if ((count++ % 3)) return;
	strip.clearLeds();
	for ( var i = 0; i < chasers.length; i++ ) {
		chasers[i].step(strip.leds);
	}
	strip.send();
}

/**
 * CHASERS: https://github.com/DannyHavenith/ws2811
 */
/*
function clearLeds(buf) {
	for (i=0; i < length; i++)
		buf[i] = [0,0,0];
}
*/
function Chaser(color, position, forward) {
	this.color = color;
	this.position = position;
	this.forward = forward;
  /* I changed these scaling values from the originals, because they were
   * too dark to show a good trail on a computer monitor.
   */
	this.amplitudes = [256, 200, 175, 150, 125, 100, 80, 60, 50, 40, 30, 20, 15, 10, 6, 1];

	return this;
}

Chaser.prototype.step = function(buf) {
	this._step(length);
	this._draw(buf);
}

Chaser.prototype._step = function(size) {
	if (this.forward) {
		if (++this.position > size) {
			this.position = size - 1;
			this.forward = false;
		}
	} else {
		if (!--this.position) {
			this.forward = true;
		}
	}
}

Chaser.prototype._draw = function(buf) {
	var step = this.forward ? -1 : 1;
	var pos = this.position;

	for (var count = 0; count < this.amplitudes.length; ++count) {
		var value = this._scale(this.color, this.amplitudes[count]);
		buf[pos] = this._addClipped(buf[pos], value);
		pos += step;

		if (pos == length) {
			step = -step;
			pos = length - 1;
		}
	}
}

Chaser.prototype._scale = function(color, amp) {
	var r, g, b;

	r = (color[0] * amp) >> 8;
	g = (color[1] * amp) >> 8;
	b = (color[2] * amp) >> 8;

	return [r, g, b];
}

Chaser.prototype._addClipped = function(rgb1, rgb2) {
	var newrgb = Array(3);
  // for some reason, we sometimes get undefined values. Error check.
	if (! Array.isArray(rgb1)) rgb1 = [0,0,0];
	if (! Array.isArray(rgb2)) rgb2 = [0,0,0];

	newrgb[0] = rgb1[0] + rgb2[0];
	newrgb[1] = rgb1[1] + rgb2[1];
	newrgb[2] = rgb1[2] + rgb2[2];

	newrgb[0] = newrgb[0] > 255 ? 255 : newrgb[0];
	newrgb[1] = newrgb[1] > 255 ? 255 : newrgb[1];
	newrgb[2] = newrgb[2] > 255 ? 255 : newrgb[2];

	return newrgb;	
}

function Flare(color, position, amplitude, speed) {
	this.color = color || [255,255,255];
	this.position = position || 0;
	this.amplitude = amplitude || 0;
	this.speed = speed || 0;

	return this;
}

Flare.prototype.step = function (buf) {
	this._step();
	this._set(buf);
}

Flare.prototype._step = function() {
	if (this.speed < 0 && -this.speed > this.amplitude) {
		this.amplitude = 0;
	} else {
		this.amplitude += this.speed;
		if (this.amplitude > 256) {
			this.amplitude = 256;
			this.speed = -this.speed >> 2 + 1;
		}
	}
}

Flare.prototype._set = function (buf) {
	buf[this.position] = this._scale(this.color);
}

Flare.prototype._scale = function(color) {
	var r, g, b;
	var amp = this.amplitude;

	r = (color[0] * amp) >> 8;
	g = (color[1] * amp) >> 8;
	b = (color[2] * amp) >> 8;

	return [r, g, b];
}

// Modified from original.
Flare.prototype._randomBrightness = function () {
	return 250 - Math.floor((Math.random() * 125));
}

Flare.prototype._randomize = function (count) {
	this.color = [this._randomBrightness(), this._randomBrightness(), this._randomBrightness()];
	this.amplitude = Math.floor(Math.random() * 100) + 100;
	this.position = Math.floor(Math.random() * count);
	this.speed = 2 * Math.floor(Math.random() * 8) + 4;
}

function flare() {
	animation = requestAnimationFrame(flare);

	// slow things down. 1 == full speed
    if ((count++ % 3)) return;

	if (flare_pause) {
		--flare_pause;
	} else {
		//console.log(flares);
		if(!flares[current_flare].amplitude) {
			flares[current_flare]._randomize(length);
			++current_flare;
			if (current_flare >= flare_count) current_flare = 0;
			//flare_pause = Math.floor(Math.random() * 80);
		}
	}

	for (var i = 0; i < flare_count; i++) {
		flares[i].step(strip.leds);
	}

	strip.send();
}

/*
 * water_torture.js
 *
 *  Created on: Feb 12, 2013
 *      Author: danny
 * 
 * Converted to JavaScript by Dougal Campbell, July 11, 2013
 */

function water_torture(ledstrip) {
	this.ledstrip = ledstrip;
	return this;
}

water_torture.prototype.init = function () {
	this.leds = this.ledstrip.leds;
	this.ledcount = this.ledstrip.leds.length;
    this.droplet_count = 4;
    this.droplets = Array(this.droplet_count - 1); // droplets that can animate simultaneously.
    this.current_droplet = 0; // index of the next droplet to be created
    this.droplet_pause = 1; // how long to wait for the next one

    for (var i = 0; i < this.droplet_count; i++) {
    	this.droplets[i] = new this.droplet();
    	this.droplets[i].parent = this;
    }
}

water_torture.prototype.mult = function (value, multiplier) {
		return ((value * multiplier) >> 8);
}

/// This class maintains the state and calculates the animations to render a falling water droplet
/// Objects of this class can have three states:
///    - inactive: this object does nothing
///    - swelling: the droplet is at the top of the led strip and swells in intensity
///    - falling: the droplet falls downwards and accelerates
///    - bouncing: the droplet has bounced of the ground. A smaller, less intensive droplet bounces up
///      while a part of the drop remains on the ground.
/// After going through the swelling, falling and bouncing phases, the droplet automatically returns to the
/// inactive state.
water_torture.prototype.droplet = function (color, gravity) {
	this.color = color || [100,100,200];
	this.gravity = gravity || 5;
	this.position = 0;
	this.speed = 0;

	this.inactive = 'INACTIVE';
	this.swelling = 'SWELLING';
	this.falling = 'FALLING';
	this.bouncing = 'BOUNCING';

	this.state = this.swelling;

	this.parent = {};
}

/// calculate the next step in the animation for this droplet
water_torture.prototype.droplet.prototype._step = function () {
		if (this.state == this.falling || this.state == this.bouncing) {
			this.position += this.speed;
			this.speed += this.gravity;

			// if we hit the bottom...
			var maxpos16 = this.parent.ledcount << 8;
			if (this.position > maxpos16)
			{
				if (this.state == this.bouncing)
				{
					// this is the second collision,
					// deactivate.
					this.state = this.inactive;
				}
				else
				{
					// reverse direction and dampen the speed
					this.position = maxpos16 - (this.position - maxpos16);
					this.speed = Math.floor(-this.speed/4);
					this.color = this.scale( this.color, 10);
					this.state = this.bouncing;
				}
			}
		}
		else if (this.state == this.swelling)
		{
			++this.position;
			if ( this.color[2] <= 10 || this.color[2] - this.position <= 10)
			{
				this.state = this.falling;
				this.position = 0;
			}

		}
	}

/// perform one step and draw.
water_torture.prototype.droplet.prototype.step = function () {
		this._step();
		this._draw();
}

/// Draw the droplet on the led string
/// This will "smear" the light of this droplet between two leds. The closer
/// the droplets position is to that of a particular led, the brighter that
/// led will be
water_torture.prototype.droplet.prototype._draw = function () {
	//console.log('drawing - state = ' + this.state);
	if (this.state == this.falling || this.state == this.bouncing)
	{
		var position8 = this.position >> 8;
		var remainder = this.position; // get the lower bits

		this.add_clipped_to(this.parent.leds[position8], this.scale( this.color, 256 - remainder ));
		if (remainder)
		{
			this.add_clipped_to(this.parent.leds[position8+1], this.scale(this.color, remainder));
		}

		if (this.state == this.bouncing)
		{
			this.add_clipped_to(this.parent.leds[this.parent.leds.length - 1], this.color);
		}
	}
	else if (this.state == this.swelling)
	{
		this.add_clipped_to(this.parent.leds[0], this.scale(this.color, this.position));
	}
	//console.log('after draw, droplet = ', this);
}

water_torture.prototype.droplet.prototype.is_active = function () {
	return this.state != this.inactive;
}

/// Add  two numbers and clip the result at 255.
water_torture.prototype.droplet.prototype.add_clipped = function (left, right) {
	var result = left + right;
	if (result > 255) result = 255;
	return result;
}

/// Add the right rgb value to the left one, clipping if necessary
water_torture.prototype.droplet.prototype.add_clipped_to = function (left, right) {
			left[0] = this.add_clipped(left[0], right[0]);
			left[1] = this.add_clipped(left[1], right[1]);
			left[2] = this.add_clipped(left[2], right[2]);
}

/// multiply an 8-bit value with an 8.8 bit fixed point number.
/// multiplier should not be higher than 1.00 (or 256).
water_torture.prototype.droplet.prototype.mult = function (value, multiplier) {
	return (value * multiplier) >> 8;
}

/// scale an rgb value up or down. amplitude > 256 means scaling up, while
/// amplitude < 256 means scaling down.
water_torture.prototype.droplet.prototype.scale = function (value, amplitude) {
	return [
			this.mult(value[0], amplitude),
			this.mult(value[1], amplitude),
			this.mult(value[2], amplitude)
			]
}

water_torture.prototype.droplet.prototype.random_scale = function () {
	return Math.floor((Math.random() * 256));
}

water_torture.prototype.droplet.prototype.randomize_droplet = function () {
	this.color = [	this.mult(100, this.random_scale()),
					this.mult(100, this.random_scale()),
					this.mult(255, this.random_scale())
					];
	this.gravity = 10;
	this.state = this.swelling;
}

/// Create the complete water torture animation.
/// This will render droplets at random intervals, up to a given maximum number of droplets.
/// The maximum led count is 256
water_torture.prototype.animate = function animate() {
	//console.log(this);
    animation = requestAnimationFrame(this.animate.bind(this));

	// slow things down. 1 == full speed
    //if ((count++ % 3)) return;

	if (this.droplet_pause)
	{
		--this.droplet_pause;
	}
	else
	{
		if (! this.droplets[this.current_droplet].is_active() )
		{
			this.droplets[this.current_droplet].randomize_droplet();
			++this.current_droplet;
			if (this.current_droplet >= this.droplet_count) this.current_droplet = 0;
			this.droplet_pause = 100 + Math.floor(Math.random() * 80);
		}
	}

	this.ledstrip.clearLeds();

	for (var idx = 0; idx < this.droplet_count; ++idx)
	{
		this.droplets[idx].step();
	}

	this.ledstrip.send(this.leds);
}


              
            
!
999px

Console