~jojo/jojos-hue

862f967b869a400d818cff758372167df8772417 — JoJo 2 years ago fd25d62
bunch of stuff yolo, party tomorrow, in a hurry
M client/client.ino => client/client.ino +29 -4
@@ 1,8 1,12 @@
#include <FastLED.h>

#define UNO_WS2812B
//#define ESP32_WS2801
//#define ESP32_WS2813
// #define UNO_WS2812B // Behind primary desk
// #define ESP32_WS2801 // Around bookcase
// #define ESP32_WS2813 // Below bedside window
// #define ESP32_WS2812B // Behind secondary desk
#define ESP32_WS2812B_2 // Bed - painting - doorway corner

#define BRIGHTNESS 255

// vvvvvvv UNIT vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
#if                               defined(UNO_WS2812B)


@@ 31,6 35,26 @@ static byte mymac[] = { 0x74,0x69,0x69,0x2D,0x30,0x31 };
#define MAX_MILLIAMPS 2000
#define DUALCORE_FASTLED
#define CONN_WIFI
#elif                             defined(ESP32_WS2812B)
#define LED_PINS G33
#define LED_TYPE WS2812B
#define COLOR_ORDER GRB
#define CORRECTION CRGB(230, 250, 255)
#define NUM_LEDS 49
#define MAX_MILLIAMPS 2000
#define BRIGHTNESS 225
#define DUALCORE_FASTLED
#define CONN_WIFI
#elif                             defined(ESP32_WS2812B_2)
#define LED_PINS G26
#define LED_TYPE WS2812B
#define COLOR_ORDER GRB
#define CORRECTION CRGB(235, 250, 255)
#define NUM_LEDS 50
#define MAX_MILLIAMPS 1450
#define BRIGHTNESS 255
#define DUALCORE_FASTLED
#define CONN_WIFI
#                                 else
#error No unit defined (e.g. ESP32_WS2801)
#endif


@@ 54,7 78,7 @@ WiFiUDP udp;
#endif

#ifdef NET
#define SERVER_IP "192.168.0.41"
#define SERVER_IP "10.30.0.11"
#define SERVER_PORT 7017
#define LOCAL_PORT 7711
#endif


@@ 137,6 161,7 @@ void setup() {

    FastLED.addLeds<LED_TYPE, LED_PINS, COLOR_ORDER>(leds, NUM_LEDS)
        .setCorrection(CORRECTION);
    FastLED.setBrightness(BRIGHTNESS);
    FastLED.setMaxPowerInVoltsAndMilliamps(5, MAX_MILLIAMPS);
#ifdef DUALCORE_FASTLED
    xTaskCreatePinnedToCore(showTask, "showTask", 2048, NULL, 2, &showTaskHandle, FASTLED_SHOW_CORE);

M server/Cargo.lock => server/Cargo.lock +255 -1
@@ 3,6 3,18 @@
version = 3

[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"

[[package]]
name = "adler32"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"

[[package]]
name = "aead"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 337,6 349,12 @@ dependencies = [
]

[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"

[[package]]
name = "blake3"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 391,6 409,12 @@ dependencies = [
]

[[package]]
name = "bytemuck"
version = "1.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72957246c41db82b8ef88a5486143830adeb8227ef9837740bdec67724cf2c5b"

[[package]]
name = "byteorder"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 444,6 468,12 @@ dependencies = [
]

[[package]]
name = "color_quant"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"

[[package]]
name = "concurrent-queue"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 497,6 527,49 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcb25d077389e53838a8158c8e99174c5a9d902dee4904320db714f3c653ffba"

[[package]]
name = "crc32fast"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a"
dependencies = [
 "cfg-if 1.0.0",
]

[[package]]
name = "crossbeam-channel"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4"
dependencies = [
 "cfg-if 1.0.0",
 "crossbeam-utils",
]

[[package]]
name = "crossbeam-deque"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e"
dependencies = [
 "cfg-if 1.0.0",
 "crossbeam-epoch",
 "crossbeam-utils",
]

[[package]]
name = "crossbeam-epoch"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd"
dependencies = [
 "cfg-if 1.0.0",
 "crossbeam-utils",
 "lazy_static",
 "memoffset",
 "scopeguard",
]

[[package]]
name = "crossbeam-queue"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 566,6 639,16 @@ dependencies = [
]

[[package]]
name = "deflate"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174"
dependencies = [
 "adler32",
 "byteorder",
]

[[package]]
name = "digest"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 581,6 664,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0"

[[package]]
name = "either"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"

[[package]]
name = "event-listener"
version = "2.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 745,6 834,16 @@ dependencies = [
]

[[package]]
name = "gif"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a668f699973d0f573d15749b7002a9ac9e1f9c6b220e7b165601334c173d8de"
dependencies = [
 "color_quant",
 "weezl",
]

[[package]]
name = "gloo-timers"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 849,6 948,25 @@ dependencies = [
]

[[package]]
name = "image"
version = "0.23.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1"
dependencies = [
 "bytemuck",
 "byteorder",
 "color_quant",
 "gif",
 "jpeg-decoder",
 "num-iter",
 "num-rational 0.3.2",
 "num-traits",
 "png",
 "scoped_threadpool",
 "tiff",
]

[[package]]
name = "infer"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 875,15 993,26 @@ version = "0.1.0"
dependencies = [
 "async-std",
 "byteorder",
 "noise",
 "num",
 "palette",
 "pulse-simple",
 "rand 0.8.4",
 "rustfft",
 "serde",
 "tide",
]

[[package]]
name = "jpeg-decoder"
version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2"
dependencies = [
 "rayon",
]

[[package]]
name = "js-sys"
version = "0.3.54"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 959,6 1088,45 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"

[[package]]
name = "memoffset"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9"
dependencies = [
 "autocfg",
]

[[package]]
name = "miniz_oxide"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435"
dependencies = [
 "adler32",
]

[[package]]
name = "miniz_oxide"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b"
dependencies = [
 "adler",
 "autocfg",
]

[[package]]
name = "noise"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82051dd6745d5184c6efb7bc8be14892a7f6d4f3ad6dbf754d1c7d7d5fe24b43"
dependencies = [
 "image",
 "rand 0.7.3",
 "rand_xorshift",
]

[[package]]
name = "num"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 968,7 1136,7 @@ dependencies = [
 "num-complex",
 "num-integer",
 "num-iter",
 "num-rational",
 "num-rational 0.4.0",
 "num-traits",
]



@@ 1026,6 1194,17 @@ dependencies = [

[[package]]
name = "num-rational"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07"
dependencies = [
 "autocfg",
 "num-integer",
 "num-traits",
]

[[package]]
name = "num-rational"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a"


@@ 1192,6 1371,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"

[[package]]
name = "png"
version = "0.16.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6"
dependencies = [
 "bitflags",
 "crc32fast",
 "deflate",
 "miniz_oxide 0.3.7",
]

[[package]]
name = "polling"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 1353,6 1544,40 @@ dependencies = [
]

[[package]]
name = "rand_xorshift"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77d416b86801d23dde1aa643023b775c3a462efc0ed96443add11546cdf1dca8"
dependencies = [
 "rand_core 0.5.1",
]

[[package]]
name = "rayon"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90"
dependencies = [
 "autocfg",
 "crossbeam-deque",
 "either",
 "rayon-core",
]

[[package]]
name = "rayon-core"
version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e"
dependencies = [
 "crossbeam-channel",
 "crossbeam-deque",
 "crossbeam-utils",
 "lazy_static",
 "num_cpus",
]

[[package]]
name = "route-recognizer"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 1388,6 1613,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"

[[package]]
name = "scoped_threadpool"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8"

[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"

[[package]]
name = "semver"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 1662,6 1899,17 @@ dependencies = [
]

[[package]]
name = "tiff"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a53f4706d65497df0c4349241deddf35f84cee19c87ed86ea8ca590f4464437"
dependencies = [
 "jpeg-decoder",
 "miniz_oxide 0.4.4",
 "weezl",
]

[[package]]
name = "time"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 1908,6 2156,12 @@ dependencies = [
]

[[package]]
name = "weezl"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8b77fdfd5a253be4ab714e4ffa3c49caf146b4de743e97510c0656cf90f1e8e"

[[package]]
name = "wepoll-ffi"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"

M server/Cargo.toml => server/Cargo.toml +2 -0
@@ 14,3 14,5 @@ palette = "0.6"
rustfft = "6.0"
pulse-simple = "1.0"
num = "0.4"
rand = { version = "0.8", features = ["small_rng"] }
noise = "0.7"

A server/deploy.sh => server/deploy.sh +1 -0
@@ 0,0 1,1 @@
rsync -r target/release/jojos-hue-server static arangar:hue/

M server/src/audiovis.rs => server/src/audiovis.rs +30 -8
@@ 1,27 1,40 @@
#![allow(non_snake_case)]

use num::Complex;
use palette::{FromColor, Hsv, Mix, Saturate, Srgb};
use palette::{FromColor, Hsl, Hsv, Hue, Mix, RgbHue, Saturate, Shade, Srgb};
use pulse_simple::Record;
use rustfft::FftPlanner;
use std::{
    f32::consts as f32,
    sync::mpsc::{channel, Receiver},
    thread,
    time::Instant,
};

const SAMPLE_RATE: usize = 48000;
// Must be power of 2
const SAMPLES_PER_PERIOD: usize = 256;

const BASS_CUTOFF: f32 = 400.0;
const BASS_CUTOFF: f32 = 350.0;
const HIGH_CUTOFF: f32 = 3000.0;

const MAX_FREQ: f32 = 20_000.0;

pub struct AudioVis {
    rx: Receiver<Hsv>,
    rx: Receiver<(f32, f32, f32)>,
    base: Hsv,
    color: Hsv,
    beat: bool,
}

impl AudioVis {
    pub fn new_beat(base: Hsv) -> Self {
        Self {
            beat: true,
            ..Self::new(base)
        }
    }

    pub fn new(base: Hsv) -> Self {
        let (tx, rx) = channel();
        thread::spawn(move || {


@@ 34,7 47,7 @@ impl AudioVis {
            let mut stereo_data = [[0.0f32; 2]; SAMPLES_PER_PERIOD];
            let (bass_cutoff_bin, high_cutoff_bin) =
                (freq_to_bin(BASS_CUTOFF), freq_to_bin(HIGH_CUTOFF));
            let mut i = 0u16;
            // let mut i = 0u16;
            loop {
                recorder.read(&mut stereo_data);
                let bin_amps = stereo_pcm_to_bins(&stereo_data);


@@ 59,7 72,6 @@ impl AudioVis {
                    norm_amp(preamp * amp_mid * 1.4).powf(2.55),
                    norm_amp(preamp * amp_high * 3.5).powf(2.3),
                );
                let color = Hsv::from_color(Srgb::new(bass_lvl, mid_lvl, high_lvl)).saturate(0.4);

                // i += 1;
                // if i > 40 {


@@ 76,18 88,28 @@ impl AudioVis {
                //     );
                // }

                tx.send(color).unwrap();
                tx.send((bass_lvl, mid_lvl, high_lvl)).unwrap();
            }
        });
        Self {
            rx,
            base,
            color: Hsv::new(0.0, 0.0, 0.0),
            beat: false,
        }
    }
    pub fn color(&mut self) -> Hsv {
        if let Some(c2) = self.rx.try_iter().last() {
        if let Some((bass_lvl, mid_lvl, high_lvl)) = self.rx.try_iter().last() {
            let c1 = self.color;
            self.color = c1.mix(&c2, 0.1).saturate(0.2);
            self.color = if self.beat {
                let c2 = Hsl::from_color(self.base).darken(0.8 * bass_lvl.powf(1.8));
                // .shift_hue(RgbHue::from_radians(-0.1 * f32::PI * bass_lvl.powf(1.8)));

                c1.mix(&Hsv::from_color(c2), 0.11)
            } else {
                let c2 = Hsv::from_color(Srgb::new(bass_lvl, mid_lvl, high_lvl)).saturate(0.26);
                c1.mix(&c2, 0.07).saturate(0.13)
            }
        }
        self.color
    }

M server/src/main.rs => server/src/main.rs +56 -9
@@ 1,9 1,11 @@
use audiovis::AudioVis;
use byteorder::{ByteOrder, LittleEndian};
use palette::{FromColor, Hsv, Hue, RgbHue, Shade, Srgb};
use noise::{NoiseFn, OpenSimplex, Perlin, Seedable};
use palette::{FromColor, Hsl, Hsv, Hue, RgbHue, Shade, Srgb};
use std::{
    collections::HashMap,
    collections::{hash_map::DefaultHasher, HashMap},
    f32::consts as f32,
    hash::{Hash, Hasher},
    iter::once,
    net::{SocketAddr, UdpSocket},
    sync::{


@@ 17,7 19,7 @@ use tide::{prelude::*, Request};

mod audiovis;

const FPS: f32 = 100.0;
pub const FPS: f32 = 90.0;
const DEADLINE: Duration = Duration::from_secs(10);

type Heartbeat = (SocketAddr, u16);


@@ 35,7 37,9 @@ enum QueryCmd {
    Off,
    Cycle,
    Breathing,
    Tinkling,
    AudioVis,
    BeatVis,
}

enum Cmd {


@@ 43,6 47,7 @@ enum Cmd {
    Off,
    Cycle,
    Breathing,
    Tinkling,
    AudioVis(AudioVis),
}



@@ 57,6 62,7 @@ impl Cmd {
            Cmd::Breathing => RenderCmd::All(Srgb::from_color(
                base.darken(0.9 * (t / 2.0).sin().powi(2) as f32),
            )),
            Cmd::Tinkling => RenderCmd::Tinkling(Hsl::from_color(base), t),
            Cmd::AudioVis(av) => RenderCmd::All(Srgb::from_color(av.color())),
        }
    }


@@ 65,6 71,7 @@ impl Cmd {
#[derive(Clone, Copy)]
enum RenderCmd {
    All(Srgb),
    Tinkling(Hsl, f64),
}

#[async_std::main]


@@ 73,7 80,7 @@ async fn main() -> tide::Result<()> {
    let (cmd_tx, cmd_rx) = sync_channel(8);

    thread::spawn(move || {
        let listener = UdpSocket::bind("192.168.0.41:7017").unwrap();
        let listener = UdpSocket::bind("0.0.0.0:7017").unwrap();
        loop {
            let mut msg = [0; 13];
            if let Some((_, addr)) = listener.recv_from(&mut msg).ok() {


@@ 101,7 108,9 @@ async fn main() -> tide::Result<()> {
        .get(move |req| all_handler(req, cmd_tx.clone()));
    web.at("/proc/:name")
        .get(move |req| proc_handler(req, cmd_tx1.clone()));
    web.listen("192.168.0.41:8558").await?;

    web.listen("0.0.0.0:8558").await?;

    Ok(())
}



@@ 117,14 126,16 @@ async fn proc_handler(req: Request<()>, tx: SyncSender<QueryCmd>) -> tide::Resul
        "off" => tx.send(QueryCmd::Off).unwrap(),
        "cycle" => tx.send(QueryCmd::Cycle).unwrap(),
        "breathing" => tx.send(QueryCmd::Breathing).unwrap(),
        "tinkling" => tx.send(QueryCmd::Tinkling).unwrap(),
        "audiovis" => tx.send(QueryCmd::AudioVis).unwrap(),
        "beatvis" => tx.send(QueryCmd::BeatVis).unwrap(),
        _ => {}
    }
    Ok("cool, ty".into())
}

fn controller(heartbeat_rx: Receiver<Heartbeat>, cmd_rx: Receiver<QueryCmd>) {
    let sender = Arc::new(UdpSocket::bind("192.168.0.41:7018").unwrap());
    let sender = Arc::new(UdpSocket::bind("0.0.0.0:7018").unwrap());
    let mut clients = HashMap::<SocketAddr, (Instant, Sender<_>)>::new();
    let mut tprev = Instant::now();
    let mut tcmd = Instant::now();


@@ 161,19 172,23 @@ fn controller(heartbeat_rx: Receiver<Heartbeat>, cmd_rx: Receiver<QueryCmd>) {
                QueryCmd::Off => cmd = Cmd::Off,
                QueryCmd::Cycle => cmd = Cmd::Cycle,
                QueryCmd::Breathing => cmd = Cmd::Breathing,
                QueryCmd::Tinkling => cmd = Cmd::Tinkling,
                QueryCmd::AudioVis => cmd = Cmd::AudioVis(AudioVis::new(base)),
                QueryCmd::BeatVis => cmd = Cmd::AudioVis(AudioVis::new_beat(base)),
            }
        }
        let res = cmd.render_cmd(base, tcmd.elapsed().as_secs_f64());
        for (_, tx) in clients.values() {
            let t = tcmd.elapsed().as_secs_f64();
            tx.send(cmd.render_cmd(base, t)).ok();
            tx.send(res.clone()).ok();
        }
        let dt = tprev.elapsed();
        tprev = Instant::now();
        let period = Duration::from_secs_f32(1.0 / FPS);
        if dt < period {
            thread::sleep(period - dt);
        } else {
            println!("not sleeping, dt: {}ms", dt.as_millis())
        }
        tprev = Instant::now();
    }
}



@@ 210,6 225,38 @@ fn parse_heartbeat(msg: &[u8; 13], addr: SocketAddr) -> Option<Heartbeat> {
fn lightf(cmd: RenderCmd, nleds: u16) -> Vec<Srgb> {
    match cmd {
        RenderCmd::All(color) => vec![color; nleds as usize],
        RenderCmd::Tinkling(base, t) => {
            let noise = {
                let mut hasher = DefaultHasher::new();
                nleds.hash(&mut hasher);
                // [-1, 1]
                Perlin::new().set_seed(hasher.finish() as u32)
                // ca. [-0.53, 0.53]
                // OpenSimplex::new().set_seed(hasher.finish() as u32)
            };
            (0..nleds)
                .map(|i| {
                    // perlin:
                    let x = noise.get([i as f64 * 0.4, t * 0.8]) as f32;
                    // opensimplex:
                    // let f = (noise.get([i as f64 * 0.6, t * 0.4]) as f32 / 0.53)
                    //     .max(-1.0)
                    //     .min(1.0);
                    // let l = base.lightness;
                    // let x = x * (0.35 + 0.65 * l * (2.0 - l));
                    // let c = if x > 0.0 {
                    //     base.lighten(x)
                    // } else {
                    //     base.darken(-x)
                    // };
                    let x = 0.5 + 0.5 * x;
                    let c = base
                        .lighten(0.5 * x.powf(1.3))
                        .shift_hue(RgbHue::from_radians(-0.18 * f32::PI * x.powf(1.8)));
                    Srgb::from_color(c)
                })
                .collect()
        }
    }
}


M server/static/index.html => server/static/index.html +2 -0
@@ 13,7 13,9 @@
      <button id="off-button">OFF</button>
      <button id="cycle-button">Cycle</button>
      <button id="breathing-button">Breathing</button>
      <button id="tinkling-button">Tinkling</button>
      <button id="audiovis-button">AudioVis</button>
      <button id="beatvis-button">BeatVis</button>
    </main>

    <script lang="javascript" src="/static/reinvented-color-wheel.js"></script>

M server/static/main.js => server/static/main.js +3 -1
@@ 32,7 32,9 @@ let nullaryButtons =
    [["off-button", "off"],
     ["cycle-button", "cycle"],
     ["breathing-button", "breathing"],
     ["audiovis-button", "audiovis"]];
     ["tinkling-button", "tinkling"],
     ["audiovis-button", "audiovis"],
     ["beatvis-button", "beatvis"]];

nullaryButtons.forEach(([btn, proc]) => {
    document.getElementById(btn).onclick = () => {