|
| 1 | +/* |
| 2 | + OpenDeck APDS9960 Ambient Light Theater |
| 3 | +
|
| 4 | + Receives OpenDeck OSC ambient light data: |
| 5 | + /opendeck/sensors/apds9960/ambient_light f |
| 6 | +*/ |
| 7 | + |
| 8 | +import oscP5.*; |
| 9 | +import netP5.*; |
| 10 | + |
| 11 | +OscP5 oscP5; |
| 12 | + |
| 13 | +int oscListenPort = 9000; |
| 14 | +String ambientPath = "/opendeck/sensors/apds9960/ambient_light"; |
| 15 | + |
| 16 | +float ambient = 0.0; |
| 17 | +float visualAmbient = 0.0; |
| 18 | +int lastPacketMs = 0; |
| 19 | +int packetCount = 0; |
| 20 | +boolean receivedData = false; |
| 21 | + |
| 22 | +ArrayList<Dust> dust = new ArrayList<Dust>(); |
| 23 | + |
| 24 | +void setup() { |
| 25 | + size(1200, 900, P2D); |
| 26 | + surface.setTitle("OpenDeck APDS9960 Ambient Light Theater"); |
| 27 | + |
| 28 | + oscP5 = new OscP5(this, oscListenPort); |
| 29 | + textFont(createFont("SansSerif", 18)); |
| 30 | + |
| 31 | + for (int i = 0; i < 220; i++) { |
| 32 | + dust.add(new Dust()); |
| 33 | + } |
| 34 | +} |
| 35 | + |
| 36 | +void draw() { |
| 37 | + visualAmbient += (ambient - visualAmbient) * 0.08; |
| 38 | + |
| 39 | + drawRoom(); |
| 40 | + drawWindowLight(); |
| 41 | + drawBlinds(); |
| 42 | + drawDust(); |
| 43 | + drawMeter(); |
| 44 | + drawHud(); |
| 45 | +} |
| 46 | + |
| 47 | +void drawRoom() { |
| 48 | + int topColor = lerpColor(color(4, 6, 12), color(24, 30, 42), visualAmbient); |
| 49 | + int bottomColor = lerpColor(color(10, 8, 13), color(70, 56, 40), visualAmbient); |
| 50 | + |
| 51 | + noStroke(); |
| 52 | + for (int y = 0; y < height; y++) { |
| 53 | + float t = y / float(height - 1); |
| 54 | + stroke(lerpColor(topColor, bottomColor, t)); |
| 55 | + line(0, y, width, y); |
| 56 | + } |
| 57 | + |
| 58 | + noStroke(); |
| 59 | + fill(0, 80 - visualAmbient * 48); |
| 60 | + rect(0, height * 0.72, width, height * 0.28); |
| 61 | +} |
| 62 | + |
| 63 | +void drawWindowLight() { |
| 64 | + float cx = width / 2.0; |
| 65 | + float cy = height / 2.0 - 30; |
| 66 | + float glow = 260 + visualAmbient * 520; |
| 67 | + float beamAlpha = 18 + visualAmbient * 96; |
| 68 | + |
| 69 | + noStroke(); |
| 70 | + for (int i = 9; i >= 1; i--) { |
| 71 | + float t = i / 9.0; |
| 72 | + fill(255, 220, 130, beamAlpha * 0.18 * t); |
| 73 | + ellipse(cx, cy, glow * t, glow * 0.72 * t); |
| 74 | + } |
| 75 | + |
| 76 | + fill(255, 220, 120, 20 + visualAmbient * 85); |
| 77 | + quad(cx - 142, cy - 144, cx + 142, cy - 144, width - 70, height, 70, height); |
| 78 | + |
| 79 | + fill(255, 235, 180, 30 + visualAmbient * 150); |
| 80 | + rect(cx - 150, cy - 150, 300, 300, 8); |
| 81 | + |
| 82 | + stroke(255, 245, 210, 80 + visualAmbient * 120); |
| 83 | + strokeWeight(4); |
| 84 | + noFill(); |
| 85 | + rect(cx - 150, cy - 150, 300, 300, 8); |
| 86 | + |
| 87 | + stroke(255, 245, 210, 70 + visualAmbient * 100); |
| 88 | + line(cx, cy - 150, cx, cy + 150); |
| 89 | + line(cx - 150, cy, cx + 150, cy); |
| 90 | +} |
| 91 | + |
| 92 | +void drawBlinds() { |
| 93 | + float cx = width / 2.0; |
| 94 | + float cy = height / 2.0 - 30; |
| 95 | + float open = visualAmbient; |
| 96 | + float slatHeight = 16; |
| 97 | + |
| 98 | + noStroke(); |
| 99 | + |
| 100 | + for (int i = 0; i < 12; i++) { |
| 101 | + float y = cy - 142 + i * 25; |
| 102 | + float tilt = map(open, 0.0, 1.0, 0, 18); |
| 103 | + |
| 104 | + fill(18, 22, 30, 210 - visualAmbient * 90); |
| 105 | + quad(cx - 156, y, |
| 106 | + cx + 156, y, |
| 107 | + cx + 150 - tilt, y + slatHeight, |
| 108 | + cx - 150 + tilt, y + slatHeight); |
| 109 | + } |
| 110 | +} |
| 111 | + |
| 112 | +void drawDust() { |
| 113 | + for (Dust particle : dust) { |
| 114 | + particle.update(visualAmbient); |
| 115 | + particle.draw(visualAmbient); |
| 116 | + } |
| 117 | +} |
| 118 | + |
| 119 | +void drawMeter() { |
| 120 | + float x = 54; |
| 121 | + float y = height - 90; |
| 122 | + float w = width - 108; |
| 123 | + float h = 24; |
| 124 | + |
| 125 | + noStroke(); |
| 126 | + fill(28, 30, 36); |
| 127 | + rect(x, y, w, h, 6); |
| 128 | + |
| 129 | + fill(255, 214, 96); |
| 130 | + rect(x, y, w * visualAmbient, h, 6); |
| 131 | + |
| 132 | + noFill(); |
| 133 | + stroke(255, 180); |
| 134 | + strokeWeight(2); |
| 135 | + rect(x, y, w, h, 6); |
| 136 | + |
| 137 | + fill(245); |
| 138 | + textAlign(LEFT, BOTTOM); |
| 139 | + textSize(18); |
| 140 | + text("ambient light " + nf(ambient, 1, 3), x, y - 10); |
| 141 | +} |
| 142 | + |
| 143 | +void drawHud() { |
| 144 | + fill(245); |
| 145 | + textAlign(LEFT, TOP); |
| 146 | + textSize(22); |
| 147 | + text("APDS9960 ambient light theater", 40, 36); |
| 148 | + |
| 149 | + textSize(18); |
| 150 | + text("Status: " + statusText(), 40, 72); |
| 151 | + text("Packets: " + packetCount, 40, 102); |
| 152 | + text("OSC: " + ambientPath, 40, 132); |
| 153 | +} |
| 154 | + |
| 155 | +String statusText() { |
| 156 | + if (!receivedData) { |
| 157 | + return "waiting for OSC"; |
| 158 | + } |
| 159 | + |
| 160 | + if (millis() - lastPacketMs > 1600) { |
| 161 | + return "stale"; |
| 162 | + } |
| 163 | + |
| 164 | + return "receiving OSC"; |
| 165 | +} |
| 166 | + |
| 167 | +void oscEvent(OscMessage message) { |
| 168 | + if (!message.checkAddrPattern(ambientPath)) { |
| 169 | + return; |
| 170 | + } |
| 171 | + |
| 172 | + if (message.typetag().length() < 1) { |
| 173 | + return; |
| 174 | + } |
| 175 | + |
| 176 | + if (message.typetag().charAt(0) == 'f') { |
| 177 | + ambient = constrain(message.get(0).floatValue(), 0.0, 1.0); |
| 178 | + } else if (message.typetag().charAt(0) == 'i') { |
| 179 | + ambient = constrain(message.get(0).intValue() / 255.0, 0.0, 1.0); |
| 180 | + } else { |
| 181 | + return; |
| 182 | + } |
| 183 | + |
| 184 | + receivedData = true; |
| 185 | + lastPacketMs = millis(); |
| 186 | + packetCount++; |
| 187 | +} |
| 188 | + |
| 189 | +class Dust { |
| 190 | + float x; |
| 191 | + float y; |
| 192 | + float size; |
| 193 | + float speed; |
| 194 | + float phase; |
| 195 | + |
| 196 | + Dust() { |
| 197 | + reset(); |
| 198 | + y = random(height); |
| 199 | + } |
| 200 | + |
| 201 | + void reset() { |
| 202 | + x = random(width); |
| 203 | + y = random(-80, height + 80); |
| 204 | + size = random(2, 8); |
| 205 | + speed = random(0.18, 0.85); |
| 206 | + phase = random(TWO_PI); |
| 207 | + } |
| 208 | + |
| 209 | + void update(float light) { |
| 210 | + y -= speed * (0.25 + light * 1.7); |
| 211 | + x += sin(frameCount * 0.018 + phase) * (0.1 + light * 0.42); |
| 212 | + |
| 213 | + if (y < -80 || x < -80 || x > width + 80) { |
| 214 | + reset(); |
| 215 | + y = height + random(20, 120); |
| 216 | + } |
| 217 | + } |
| 218 | + |
| 219 | + void draw(float light) { |
| 220 | + float alpha = (18 + light * 130) * map(y, height, 0, 0.35, 1.0); |
| 221 | + |
| 222 | + noStroke(); |
| 223 | + fill(255, 235, 170, alpha); |
| 224 | + ellipse(x, y, size + light * 4, size + light * 4); |
| 225 | + } |
| 226 | +} |
0 commit comments