COOLDOWN_FACTOR = 0.9995 OVERDRIVE = 1.70 EXPONENT = 1.5 M = 1.7 -- mass D = 1 -- spring strength DAMPING = {} -- filled in init() num_modules = 128 center_module = 64 num_masses = math.floor(num_modules/2) excitement_pos = 1 -- maximum energy values for each band maxRedEnergy = 1 maxGreenEnergy = 1 maxBlueEnergy = 1 -- spring-mass-grid values pos_r = {} pos_g = {} pos_b = {} vel_r = {} vel_g = {} vel_b = {} acc_r = {} acc_g = {} acc_b = {} -- output color buffers red = {} green = {} blue = {} r_tmp = {} g_tmp = {} b_tmp = {} function limit(val) if val > 1 then return 1 elseif val < 0 then return 0 else return val end end function periodic() local redEnergy = get_energy_in_band(0, 400); local greenEnergy = get_energy_in_band(400, 4000); local blueEnergy = get_energy_in_band(4000, 22000); local centerIndex = 2 * center_module + 1; maxRedEnergy = maxRedEnergy * COOLDOWN_FACTOR if redEnergy > maxRedEnergy then maxRedEnergy = redEnergy end maxGreenEnergy = maxGreenEnergy * COOLDOWN_FACTOR if greenEnergy > maxGreenEnergy then maxGreenEnergy = greenEnergy end maxBlueEnergy = maxBlueEnergy * COOLDOWN_FACTOR if blueEnergy > maxBlueEnergy then maxBlueEnergy = blueEnergy end -- update the spring-mass string -- the outside masses are special, as they are auto-returned to 0 position -- { spring-mass pendulum } { friction } --acc_r[1] = (-pos_r[1] + (pos_r[2] - pos_r[1])) * D / M --acc_g[1] = (-pos_g[1] + (pos_g[2] - pos_g[1])) * D / M --acc_b[1] = (-pos_b[1] + (pos_b[2] - pos_b[1])) * D / M acc_r[num_masses] = (-pos_r[num_masses] + (pos_r[num_masses-1] - pos_r[num_masses])) * D / M acc_g[num_masses] = (-pos_g[num_masses] + (pos_g[num_masses-1] - pos_g[num_masses])) * D / M acc_b[num_masses] = (-pos_b[num_masses] + (pos_b[num_masses-1] - pos_b[num_masses])) * D / M -- inside masses are only influenced by their neighbors for i = 2,num_masses-1 do acc_r[i] = (pos_r[i-1] + pos_r[i+1] - 2 * pos_r[i]) * D / M acc_g[i] = (pos_g[i-1] + pos_g[i+1] - 2 * pos_g[i]) * D / M acc_b[i] = (pos_b[i-1] + pos_b[i+1] - 2 * pos_b[i]) * D / M end -- update velocity and position for i = 1,num_masses do vel_r[i] = DAMPING[i] * (vel_r[i] + acc_r[i]) vel_g[i] = DAMPING[i] * (vel_g[i] + acc_g[i]) vel_b[i] = DAMPING[i] * (vel_b[i] + acc_b[i]) pos_r[i] = pos_r[i] + vel_r[i] pos_g[i] = pos_g[i] + vel_g[i] pos_b[i] = pos_b[i] + vel_b[i] end -- set the new position for the center module newRed = redEnergy / maxRedEnergy pos_r[excitement_pos] = newRed vel_r[excitement_pos] = 0 acc_r[excitement_pos] = 0 newGreen = greenEnergy / maxGreenEnergy pos_g[excitement_pos] = newGreen vel_b[excitement_pos] = 0 acc_b[excitement_pos] = 0 newBlue = blueEnergy / maxBlueEnergy pos_b[excitement_pos] = newBlue vel_b[excitement_pos] = 0 acc_b[excitement_pos] = 0 -- map to LED modules for i = 1,num_masses do r_tmp[i] = pos_r[i] g_tmp[i] = pos_g[i] b_tmp[i] = pos_b[i] r_tmp[num_modules-i+1] = pos_r[i] g_tmp[num_modules-i+1] = pos_g[i] b_tmp[num_modules-i+1] = pos_b[i] --print(i, pos_r[i]) end -- make colors more exciting for i = 1,num_modules do red[i] = limit(OVERDRIVE * math.pow(r_tmp[i], EXPONENT)) green[i] = limit(OVERDRIVE * math.pow(g_tmp[i], EXPONENT)) blue[i] = limit(OVERDRIVE * math.pow(b_tmp[i], EXPONENT)) end -- return the 3 color arrays return red, green, blue end function init(nmod, cmod) num_modules = nmod center_module = cmod num_masses = math.floor(nmod/2) excitement_pos = 1 for i = 1,nmod do red[i] = 0 green[i] = 0 blue[i] = 0 end for i = 1,num_masses do pos_r[i] = 0 pos_g[i] = 0 pos_b[i] = 0 vel_r[i] = 0 vel_g[i] = 0 vel_b[i] = 0 acc_r[i] = 0 acc_g[i] = 0 acc_b[i] = 0 DAMPING[i] = 1 - 0.08 * math.pow(math.abs((i - excitement_pos) / num_masses), 2) end -- don't use fading return 0 end