So, I’ve finally finished a pattern I’ve been wanting to do for quite some time, implementing several new concepts. Unfortunately there weren’t many stepping stones functional in PB - I had a few concept patterns in PB, but mostly wrote it in notepad++ then pasted it in.
PBv3.12 (edit: also v3.16) seems to get stuck on ‘generating’ - am I asking too much, or is there some sort of a bug I’m not seeing?
This is intended for a 2D map - a loop of lights attached to a stable structure, with the PB attached to the centre.
I am using the accelerometer, and whenever a total magnitude over a threshold is reached, it sends a two ‘waves’ (pulses) starting from the nearest point to the impact and running each way. I’ve also dipped into the microphone, and if when a wave is being created, there is enough sound, it sets the color based on the max frequency.
In total, I wrote capability for 8 different pulses, 4 going each way.
Every beforeRender, it advances the angle of each pulse by the ‘speed’*time
with rendering, it checks if each pulse is within the wave distance from the angle of each pixel to center.
After it finds all the ‘active’ pulses for each given pixel, it largely averages the hues, averages and bumps up the v values, and pulls back on the saturation for each additional overlapping pulse, and returns this to the render function.
I’ve pasted the code below.
Thanks for your help!
/*
Things that go bump (in the night)
This pattern works best with a 2D circle/rectangle of lights.
A pattern that uses the sensor expansion boards accellerometer and microphone.
Whenever the PB is bumped, two waves are generated, both beginning near the projected point of impact, and one running each way.
The color is locked in at wave creation time - if no sound, then it uses the base color. If the sound is over the threshold, then
the max frequency is used to set the hue.
Brightness/v follows a wave pattern, however this (peak value) degrades over time. Once the peak brightness is zero, it is no longer rendered/processed
I opted to use a polar rendering system so as to allow easy calculation regardless of where the wave's origin was.
Ideas for wave creation/integration/sensor usage from ZRanger1's Oasis and Roger Cheng's Accelerometer Test
- so far I have yet to de
*/
// constant wave descriptor array indices.
// !!!Be extremely careful when changing or adding indices to this list!!!
// Not used in beforeRender() or render() -- it makes enough of a performance
// difference here to matter. Static numbers are a hair faster.
var _vMax = 0; // peak brightness (amplitude equivalent)
var _hue = 1; // color of the wave
var _speed = 2; // base wave movement speed
var _curAngle = 3; // origin of the wave on the circle
var descriptorSize = 4;
// wave descriptors. the *b waves are the backwards ones.
var wave1 = array(descriptorSize);
var wave1b = array(descriptorSize);
var wave2 = array(descriptorSize);
var wave2b = array(descriptorSize);
var wave3 = array(descriptorSize);
var wave3b = array(descriptorSize);
var wave4 = array(descriptorSize);
var wave4b = array(descriptorSize);
var waveFront = 0.5 // in radians, front half of the wave
var waveBack = 1 // radians, back half of the wave
export var aura = 0.66667
var baseSpeed = 0.6 // radians/second
var degradationRate = 0.4 // 0.4 would have a wave last 2.2 seconds
var lastAdvanced = 0
// the below variables are largely exported for debugging purposes
export var accelerometer //3 element array with [x,y,z]
export var energyAverage // total audio volume
export var maxFrequency // loudest frequency from sound board.
export var magnitude // need to find out what the realistic range of this is, to proportionally adjust it in the newWave function ***********
export var horizMagnitude
export var virtMagnitude
export var horizAngle
export var virtAngle
export var lastX
export var lastY
export var lastZ
export var deltaX
export var deltaY
export var deltaZ
export var deltaHMag
export var deltaVMag
export var deltaHAngle
export var deltaVAngle
// sound thresholds and adjustments
export var soundThreshold = 0.002
var loudestSound = 0.06
var lowestSound = 150
var highestSound = 1000 //ranges 170-700 for talking between myself and Julia....
//this would give hues of 50-450/1000 or 0.05 for ben, 0.45 for julia
//accelerometer thresholds and adjustments
export var sensitivity = 0.02 // threshold for accelerometer reading to trigger event
var biggestMagnitude = 0.04 // tested max by simply tapping the accelerometer. Will likely need to be adjusted after it is mounted.
// axis adjustments for mounting orientation. +1 if upright, -1 if inverted
var adjustX = 1
var adjustY = -1
var adjustZ = 1
var debounceTime = 500 // time between events
var lastActivated = 0 // time of last event
// UI functions
export function sliderHue(v) {
aura = v
}
export function readAccelerometer(delta) {
a = accelerometer // left in for dampening equation below, should likely adapt
curX = accelerometer[0] * adjustX
curY = accelerometer[1] * adjustY
curZ = accelerometer[2] * adjustZ
// R = sqrt(x^2 + y^2 + z^2)
hyp = sqrt(pow(curX,2) + pow(curY,2) + pow(curZ,2) )
// trigger only if magnitude greater than sensitivity setting
if (hyp > sensitivity) {
magnitude = hyp
// initial calculation of momentary magnitudes/angles largely for debugging purposes
horizMagnitude = sqrt(pow(curX,2) + pow(curY,2))
horizAngle = (atan2(curY,curX) * 180)/PI; // in degrees
horizAngle = atan2(curY,curX) // in radians
//z and hypotinuse of x and y gives virt angle
virtAngle = (atan2(horizMagnitude,curZ) * 180)/PI; // in degrees
virtAngle = atan2(horizMagnitude,curZ) // in radians
virtMagnitude = sqrt(pow(horizMagnitude,2) + pow (curZ,2))
// calculate the delta values, intended to caluclate the impact angle and magnitude
deltaX = curX-lastX
deltaY = curY-lastY
deltaZ = curZ-lastZ
deltaHMag = sqrt(pow(deltaX,2) + pow(deltaY,2))
//deltaHAngle = (atan2(deltaY,deltaX) * 180)/PI; // in degrees
deltaHAngle = atan2(deltaY,deltaX) // in radians
//deltaVAngle = (atan2(deltaHMag,deltaZ) * 180)/PI; // in degrees
deltaVAngle = atan2(deltaHMag, deltaZ) // in radians. Not currenly used in 2D implementation, but could be added for 3D purposes
deltaVMag = sqrt(pow(deltaHMag,2) + pow (deltaZ,2))
lastActivated = delta
// create wave, with the horixontal angle, overall force, and timestamp
var originAngle = (deltaHAngle + PI)%(PI*2) // add 180 degrees or pi to the angle, then if over 2Pi, subtract 2Pi (divide by 2Pi and give the remainder). this gives the approximate angle the force came from.
newWave(originAngle, magnitude, delta)
}
else {
// continually set last values for sub-threshold readings, likely to be essentially the baseline
lastX = curX
lastY = curY
lastZ = curZ
}
}
var HALF_PI = PI/2
//NOTE: atan2 has a bug in V2.23, when fixed this can be replaces with atan2 directly
function arctan2(y, x) {
if (x > 0) return atan(y/x)
if (y > 0) return HALF_PI - atan(x/y)
if (y < 0) return -HALF_PI - atan(x/y)
if (x < 0) return PI + atan(y/x)
return 1.0
}
//return the angle in radians, can be negative
function getAngleInRads(x, y) {
//center the coordinate, then get the angle
return arctan2(x - .5, y - .5)
}
//return the angle as a value between 0 and 1.0
//most of Pixelblaze's animation language uses this range
//it also happens to rotate the angle so that 0 is north
function getUnitAngle(x, y) {
return (PI + arctan2(x - .5, y - .5))/PI2
}
function newWave (HAngle, magnitude, delta)
{
// read sound magnitude, if over threshold, then read max frequency and use those to create
// search through waves, find one with lowest magnitude, and replace that one
var goodWave = 1
var currLow = wave1[0]
var stop=false
var newHue = aura
var newV = magnitude/biggestMagnitude
var newSpeed = baseSpeed
var magSpeedThreshold = 0.6 // if magnitude over 60% of expected max, then increase speed above base rate
if (newV > magSpeedThreshold) {
newSpeed = baseSpeed * (1+newV) // may need to adjust this, especially if using radians for angles
}
if (wave1[0] = 0) { // if the first wave is off, just start with that one, skipping next evaluations)
goodWave = 1
curLow = 0
stop = true
}
else if (wave2[0]<wave1[0]) {
goodWave=2
curLow=wave2[0]
if (wave2[0]=0) stop = true
}
if ((stop=false) && (wave3[0]<curLow)) { // if wave3 is the lowest
goodWave=3
curLow=wave3[0]
if (wave3[0]=0) stop = true
}
if ((stop=false) && (wave4[0]<curLow)) { // if wave3 is the lowest
goodWave=4
curLow=wave4[0]
if (wave3[0]=0) stop = true
}
if (energyAverage>soundThreshold) {
if (lowestSound>maxFrequency) newHue = 1 // to prevent values over 1 if a freq higher than expected is detected
else newHue = (maxFrequency-lowestSound)/(highestSound-lowestSound) // gives a 0-1 value proportional to the space between lowst and highest expected maxFrequency.
newV = (newV + (energyAverage/loudestSound))/2 // loud music has an energy average of 0.06, so average magnitude (0-1) with the proportion of energyAverage with its' max
}
else newHue = aura
if (goodWave = 1) {
initWave(wave1, magnitude, newHue, newSpeed, HAngle)
initWave(wave1b, magnitude, newHue, -1*newSpeed, HAngle)
}
else if (goodWave = 2) {
initWave(wave2, magnitude, newHue, newSpeed, HAngle)
initWave(wave2b, magnitude, newHue, -1*newSpeed, HAngle)
}
else if (goodWave = 3) {
initWave(wave3, magnitude, newHue, newSpeed, HAngle)
initWave(wave3b, magnitude, newHue, -1*newSpeed, HAngle)
}
else if (goodWave = 4) {
initWave(wave4, magnitude, newHue, newSpeed, HAngle)
initWave(wave4b, magnitude, newHue, -1*newSpeed, HAngle)
}
lastActivated=delta
}
function initWave(w, vMax, hue, speed, curAngle) {
w[_vMax] = vMax;
w[_hue] = hue;
w[_speed] = speed;
w[_curAngle] = curAngle;
}
function advanceWaves (timeChange) {
// advance position of waves and degrade waves, if not already deactivated
//speed is in radians/sec, delta and timeChange is in miliseconds
if (wave1[_vMax] >0) {
wave1[_curAngle] = wave1[_curAngle] + wave1[_speed]* (timeChange/1000)
wave1b[_curAngle] = wave1b[_curAngle] + wave1b[_speed]* (timeChange/1000)
wave1[_vMax] = wave1[_vMax] - degradationRate* (timeChange/1000)
wave1b[_vMax] = wave1b[_vMax] - degradationRate* (timeChange/1000)
}
if (wave2[_vMax] >0) {
wave2[_curAngle] = wave2[_curAngle] + wave2[_speed]* (timeChange/1000)
wave2b[_curAngle] = wave2b[_curAngle] + wave2b[_speed]* (timeChange/1000)
wave2[_vMax] = wave2[_vMax] - degradationRate* (timeChange/1000)
wave2b[_vMax] = wave2b[_vMax] - degradationRate* (timeChange/1000)
}
if (wave3[_vMax] >0) {
wave3[_curAngle] = wave3[_curAngle] + wave3[_speed]* (timeChange/1000)
wave3b[_curAngle] = wave3b[_curAngle] + wave3b[_speed]* (timeChange/1000)
wave3[_vMax] = wave3[_vMax] - degradationRate* (timeChange/1000)
wave3b[_vMax] = wave3b[_vMax] - degradationRate* (timeChange/1000)
}
if (wave4[_vMax] >0) {
wave4[_curAngle] = wave4[_curAngle] + wave4[_speed]* (timeChange/1000)
wave4b[_curAngle] = wave4b[_curAngle] + wave4b[_speed]* (timeChange/1000)
wave4[_vMax] = wave4[_vMax] - degradationRate* (timeChange/1000)
wave4b[_vMax] = wave4b[_vMax] - degradationRate* (timeChange/1000)
}
}
function setup() {
// initialize all waves
initWave(wave1, 0, aura, baseSpeed, 0); //wave, vMax, hue, speed, currentAngle
initWave(wave1b, 0, aura, baseSpeed, 0);
initWave(wave2, 0, aura, baseSpeed, 0);
initWave(wave2b, 0, aura, baseSpeed, 0);
initWave(wave3, 0, aura, baseSpeed, 0);
initWave(wave3b, 0, aura, baseSpeed, 0);
initWave(wave4, 0, aura, baseSpeed, 0);
initWave(wave4b, 0, aura, baseSpeed, 0);
}
setup();
function waveStrength(wAngle,cAngle) {
var vStrength = 0
var backDist = -1
var frontDist = -1
var dAngle = cAngle-wAngle
if ((dAngle >=0) && (dAngle <= waveFront)) { // if cAngle is ahead of wAngle, and within waveFront distance
inFront = true
frontDist = dAngle
}
else if ((cAngle < waveFront)&&((2*PI-wAngle+cAngle) < waveFront)) { // if cAngle just past zero, but within wafeFront distance form wAngle
inFront= true
frontDist = (2*PI-wAngle+cAngle)
}
else if ((dAngle < 0)&&(abs(dAngle) <= waveBack)) { // if cAngle is behind wAngle, but within waveBack distance
inBack = true
backDist = abs(dAngle)
}
else if ((wAngle < waveBack)&&((2*PI-cAngle+wAngle) < waveBack)) { // if wAngle just past zero, but within waveBack distance of cAngle
inBack = true
backDist = (2*PI-cAngle+wAngle)
}
if (inFront) vStrength = sin((1/waveFront)*PI*frontDist) // for the front half, use a sine wave modified to be 1 at zero, and sero at waveFront distance from zero radians
if (inBack) vStrength = sin((1/waveBack)*PI*backDist) // similar, except scaled to waveBack
return vStrength
}
//integrator function:
function integratinator (delta, x, y) {
var newHSV = array(3)
var activeWaves =0
var cumulativeHue =0
var cumulativeV =0
var tempAngle =0
var tempStrength =0
if (wave1[_vMax] > 0) {
tempAngle = getAngleInRads(x,y)
tempStrength = wave1[_vMax]*waveStrength(wave1[_curAngle],tempAngle)
if (tempStrength>0) {
cumulativeV=cumulativeV+tempStrength
cumulativeHue = cumulativeHue + wave1[_hue]
activeWaves=activeWaves+1
}
}
if (wave1b[_vMax] > 0) {
tempAngle = getAngleInRads(x,y)
tempStrength = wave1b[_vMax]*waveStrength(wave1b[_curAngle],tempAngle)
if (tempStrength>0) {
cumulativeV=cumulativeV+tempStrength
cumulativeHue = cumulativeHue + wave1b[_hue]
activeWaves=activeWaves+1
}
}
if (wave2[_vMax] > 0) {
tempAngle = getAngleInRads(x,y)
tempStrength = wave2[_vMax]*waveStrength(wave2[_curAngle],tempAngle)
if (tempStrength>0) {
cumulativeV=cumulativeV+tempStrength
cumulativeHue = cumulativeHue + wave2[_hue]
activeWaves=activeWaves+1
}
}
if (wave2b[_vMax] > 0) {
tempAngle = getAngleInRads(x,y)
tempStrength = wave2b[_vMax]*waveStrength(wave2b[_curAngle],tempAngle)
if (tempStrength>0) {
cumulativeV=cumulativeV+tempStrength
cumulativeHue = cumulativeHue + wave2b[_hue]
activeWaves=activeWaves+1
}
}
if (wave3[_vMax] > 0) {
tempAngle = getAngleInRads(x,y)
tempStrength = wave3[_vMax]*waveStrength(wave3[_curAngle],tempAngle)
if (tempStrength>0) {
cumulativeV=cumulativeV+tempStrength
cumulativeHue = cumulativeHue + wave3[_hue]
activeWaves=activeWaves+1
}
}
if (wave3b[_vMax] > 0) {
tempAngle = getAngleInRads(x,y)
tempStrength = wave3b[_vMax]*waveStrength(wave3b[_curAngle],tempAngle)
if (tempStrength>0) {
cumulativeV=cumulativeV+tempStrength
cumulativeHue = cumulativeHue + wave3b[_hue]
activeWaves=activeWaves+1
}
}
if (wave4[_vMax] > 0) {
tempAngle = getAngleInRads(x,y)
tempStrength = wave4[_vMax]*waveStrength(wave4[_curAngle],tempAngle)
if (tempStrength>0) {
cumulativeV=cumulativeV+tempStrength
cumulativeHue = cumulativeHue + wave4[_hue]
activeWaves=activeWaves+1
}
}
if (wave4b[_vMax] > 0) {
tempAngle = getAngleInRads(x,y)
tempStrength = wave4b[_vMax]*waveStrength(wave4b[_curAngle],tempAngle)
if (tempStrength>0) {
cumulativeV=cumulativeV+tempStrength
cumulativeHue = cumulativeHue + wave4b[_hue]
activeWaves=activeWaves+1
}
}
newHSV[0] = cumulativeHue/activeWaves //hue - currently just a simple average of the hues. Can consider other blends in the future.
newHSV[1] = clamp((1-(activeWaves/10)),0,1) // have saturation decrease with the more concurrent waves at a given point - essentially a white cap
newHSV[2] = clamp((cumulativeV/activeWaves)+(activeWaves/8),0,1) // v - curently just a simple average with an additional 0.125 for every active wave. Could consider quadratic easing or other methods.
return newHSV
}
export function beforeRender(delta) {
if (delta < lastActivated) {
lastActivated = delta // if delta has wrapped around, set lastActivated to the new delta
lastAdvanced = delta // if delta has wrapped around, set lastAdvanced to the new delta
}
if (delta > (lastActivated + delta)) { // only read accelerometer if debounce time has passed
readAccelerometer(delta) // read accelerometer, and threshold reached a new wave will replace the smallest remaining wave
}
advanceWaves(delta-lastAdvanced)
lastAdvanced = delta
}
export function render(index) {
render2D(index, .5, index / pixelCount)
}
export function render2D(index, x, y) {
var newHSV = array(3);
newHSV = integratinator (x, y)
hsv(newHSV[0],newHSV[1],newHSV[2])
}
export function render3D(index,x,y,z) {
render2D(index,x,y)
}