Add a MIDI-responding piano key pattern that lights up cubes based on notes
[SugarCubes.git] / MarkSlee.pde
1 class SpaceTime extends SCPattern {
2
3 SinLFO pos = new SinLFO(0, 15, 3000);
4 SinLFO rate = new SinLFO(1000, 9000, 13000);
5 SinLFO falloff = new SinLFO(10, 70, 5000);
6 float angle = 0;
7
8 BasicParameter rateParameter = new BasicParameter("RATE", 0.5);
9 BasicParameter sizeParameter = new BasicParameter("SIZE", 0.5);
10
11 public SpaceTime(GLucose glucose) {
12 super(glucose);
13 addModulator(pos).trigger();
14 addModulator(rate).trigger();
15 addModulator(falloff).trigger();
16 pos.modulateDurationBy(rate);
17 addParameter(rateParameter);
18 addParameter(sizeParameter);
19 }
20
21 public void onParameterChanged(LXParameter parameter) {
22 if (parameter == rateParameter) {
23 rate.stop().setValue(9000 - 8000*parameter.getValuef());
24 } else if (parameter == sizeParameter) {
25 falloff.stop().setValue(70 - 60*parameter.getValuef());
26 }
27 }
28
29 void run(int deltaMs) {
30 angle += deltaMs * 0.0007;
31 float sVal1 = Strip.list.size() * (0.5 + 0.5*sin(angle));
32 float sVal2 = Strip.list.size() * (0.5 + 0.5*cos(angle));
33
34 float pVal = pos.getValuef();
35 float fVal = falloff.getValuef();
36
37 int s = 0;
38 for (Strip strip : Strip.list) {
39 int i = 0;
40 for (Point p : strip.points) {
41 colors[p.index] = color(
42 (lx.getBaseHuef() + 360 - p.fy*.2 + p.fz * .3) % 360,
43 constrain(.4 * min(abs(s - sVal1), abs(s - sVal2)), 20, 100),
44 max(0, 100 - fVal*abs(i - pVal))
45 );
46 ++i;
47 }
48 ++s;
49 }
50 }
51 }
52
53 class Swarm extends SCPattern {
54
55 SawLFO offset = new SawLFO(0, 16, 1000);
56 SinLFO rate = new SinLFO(350, 1200, 63000);
57 SinLFO falloff = new SinLFO(15, 50, 17000);
58 SinLFO fY = new SinLFO(0, 250, 19000);
59 SinLFO fZ = new SinLFO(0, 127, 11000);
60 SinLFO hOffY = new SinLFO(0, 255, 13000);
61
62 public Swarm(GLucose glucose) {
63 super(glucose);
64 addModulator(offset).trigger();
65 addModulator(rate).trigger();
66 addModulator(falloff).trigger();
67 addModulator(fY).trigger();
68 addModulator(fZ).trigger();
69 addModulator(hOffY).trigger();
70 offset.modulateDurationBy(rate);
71 }
72
73 float modDist(float v1, float v2, float mod) {
74 v1 = v1 % mod;
75 v2 = v2 % mod;
76 if (v2 > v1) {
77 return min(v2-v1, v1+mod-v2);
78 }
79 else {
80 return min(v1-v2, v2+mod-v1);
81 }
82 }
83
84 void run(int deltaMs) {
85 float s = 0;
86 for (Strip strip : Strip.list) {
87 int i = 0;
88 for (Point p : strip.points) {
89 float fV = max(-1, 1 - dist(p.fy/2., p.fz, fY.getValuef()/2., fZ.getValuef()) / 64.);
90 colors[p.index] = color(
91 (lx.getBaseHuef() + 0.3 * abs(p.fy - hOffY.getValuef())) % 360,
92 constrain(80 + 40 * fV, 0, 100),
93 constrain(100 - (30 - fV * falloff.getValuef()) * modDist(i + (s*63)%61, offset.getValuef(), 16), 0, 100)
94 );
95 ++i;
96 }
97 ++s;
98 }
99 }
100 }
101
102 class SwipeTransition extends SCTransition {
103
104 final BasicParameter bleed = new BasicParameter("WIDTH", 0.5);
105
106 SwipeTransition(GLucose glucose) {
107 super(glucose);
108 setDuration(5000);
109 addParameter(bleed);
110 }
111
112 void computeBlend(int[] c1, int[] c2, double progress) {
113 float bleedf = 10 + bleed.getValuef() * 200.;
114 float yPos = (float) (-bleedf + progress * (255. + bleedf));
115 for (Point p : Point.list) {
116 float d = (p.fy - yPos) / bleedf;
117 if (d < 0) {
118 colors[p.index] = c2[p.index];
119 }
120 else if (d > 1) {
121 colors[p.index] = c1[p.index];
122 }
123 else {
124 colors[p.index] = lerpColor(c2[p.index], c1[p.index], d, RGB);
125 }
126 }
127 }
128 }
129
130 class CubeEQ extends SCPattern {
131
132 private FFT fft = null;
133 private LinearEnvelope[] bandVals = null;
134 private int avgSize;
135
136 private final BasicParameter thrsh = new BasicParameter("LVL", 0.35);
137 private final BasicParameter range = new BasicParameter("RANG", 0.45);
138 private final BasicParameter edge = new BasicParameter("EDGE", 0.5);
139 private final BasicParameter speed = new BasicParameter("SPD", 0.5);
140 private final BasicParameter tone = new BasicParameter("TONE", 0.5);
141 private final BasicParameter clr = new BasicParameter("CLR", 0.5);
142
143 public CubeEQ(GLucose glucose) {
144 super(glucose);
145 addParameter(thrsh);
146 addParameter(range);
147 addParameter(edge);
148 addParameter(speed);
149 addParameter(tone);
150 addParameter(clr);
151 }
152
153 protected void onActive() {
154 if (this.fft == null) {
155 this.fft = new FFT(lx.audioInput().bufferSize(), lx.audioInput().sampleRate());
156 this.fft.window(FFT.HAMMING);
157 this.fft.logAverages(40, 1);
158 this.avgSize = this.fft.avgSize();
159 this.bandVals = new LinearEnvelope[this.avgSize];
160 for (int i = 0; i < this.bandVals.length; ++i) {
161 this.addModulator(this.bandVals[i] = (new LinearEnvelope(0, 0, 700+i*4))).trigger();
162 }
163 }
164 }
165
166 public void run(int deltaMs) {
167 this.fft.forward(this.lx.audioInput().mix);
168 float toneConst = .35 + .4 * (tone.getValuef() - 0.5);
169 float edgeConst = 2 + 30*(edge.getValuef()*edge.getValuef()*edge.getValuef());
170
171 for (int i = 0; i < avgSize; ++i) {
172 float value = this.fft.getAvg(i);
173 value = 20*log(1 + sqrt(value));
174 float sqdist = avgSize - i;
175 value -= toneConst*sqdist*sqdist + .5*sqdist;
176 value *= 6;
177 if (value > this.bandVals[i].getValue()) {
178 this.bandVals[i].setEndVal(value, 40).trigger();
179 }
180 else {
181 this.bandVals[i].setEndVal(value, 1000 - 900*speed.getValuef()).trigger();
182 }
183 }
184
185 float jBase = 120 - 360*thrsh.getValuef();
186 float jConst = 300.*(1-range.getValuef());
187 float clrConst = 1.1 + clr.getValuef();
188
189 for (Point p : Point.list) {
190 float avgIndex = constrain((p.fy / 256. * avgSize), 0, avgSize-2);
191 int avgFloor = (int) avgIndex;
192 float j = jBase + jConst * (p.fz / 128.);
193 float value = lerp(
194 this.bandVals[avgFloor].getValuef(),
195 this.bandVals[avgFloor+1].getValuef(),
196 avgIndex-avgFloor
197 );
198
199 float b = constrain(edgeConst * (value - j), 0, 100);
200 colors[p.index] = color(
201 (480 + lx.getBaseHuef() - min(clrConst*p.fz, 120)) % 360,
202 100,
203 b);
204 }
205 }
206 }
207
208 class BoomEffect extends SCEffect {
209
210 final BasicParameter falloff = new BasicParameter("WIDTH", 0.5);
211 final BasicParameter speed = new BasicParameter("SPD", 0.5);
212 final BasicParameter bright = new BasicParameter("BRT", 1.0);
213 final BasicParameter sat = new BasicParameter("SAT", 0.2);
214 List<Layer> layers = new ArrayList<Layer>();
215
216 class Layer {
217 LinearEnvelope boom = new LinearEnvelope(-40, 500, 1300);
218
219 Layer() {
220 addModulator(boom);
221 trigger();
222 }
223
224 void trigger() {
225 float falloffv = falloffv();
226 boom.setRange(-100 / falloffv, 500 + 100/falloffv, 4000 - speed.getValuef() * 3300);
227 boom.trigger();
228 }
229
230 void doApply(int[] colors) {
231 float brightv = 100 * bright.getValuef();
232 float falloffv = falloffv();
233 float satv = sat.getValuef() * 100;
234 float huev = lx.getBaseHuef();
235 for (Point p : Point.list) {
236 colors[p.index] = blendColor(
237 colors[p.index],
238 color(huev, satv, constrain(brightv - falloffv*abs(boom.getValuef() - dist(2*p.fx, p.fy, 2*p.fz, 128, 128, 128)), 0, 100)),
239 ADD);
240 }
241 }
242 }
243
244 BoomEffect(GLucose glucose) {
245 super(glucose, true);
246 addParameter(falloff);
247 addParameter(speed);
248 addParameter(bright);
249 addParameter(sat);
250 }
251
252 public void onEnable() {
253 for (Layer l : layers) {
254 if (!l.boom.isRunning()) {
255 l.trigger();
256 return;
257 }
258 }
259 layers.add(new Layer());
260 }
261
262 private float falloffv() {
263 return 20 - 19 * falloff.getValuef();
264 }
265
266 public void onTrigger() {
267 onEnable();
268 }
269
270 public void doApply(int[] colors) {
271 for (Layer l : layers) {
272 if (l.boom.isRunning()) {
273 l.doApply(colors);
274 }
275 }
276 }
277 }
278
279 public class PianoKeyPattern extends SCPattern {
280
281 final LinearEnvelope[] cubeBrt;
282 final SinLFO base[];
283 final BasicParameter attack = new BasicParameter("ATK", 0.1);
284 final BasicParameter release = new BasicParameter("REL", 0.5);
285 final BasicParameter level = new BasicParameter("AMB", 0.6);
286
287 PianoKeyPattern(GLucose glucose) {
288 super(glucose);
289
290 for (MidiInputDevice input : RWMidi.getInputDevices()) {
291 input.createInput(this);
292 }
293
294 addParameter(attack);
295 addParameter(release);
296 addParameter(level);
297 cubeBrt = new LinearEnvelope[Cube.list.size() / 4];
298 for (int i = 0; i < cubeBrt.length; ++i) {
299 addModulator(cubeBrt[i] = new LinearEnvelope(0, 0, 100));
300 }
301 base = new SinLFO[Cube.list.size() / 12];
302 for (int i = 0; i < base.length; ++i) {
303 addModulator(base[i] = new SinLFO(0, 1, 7000 + 1000*i)).trigger();
304 }
305 }
306
307 private float getAttackTime() {
308 return 15 + attack.getValuef()*attack.getValuef() * 2000;
309 }
310
311 private float getReleaseTime() {
312 return 15 + release.getValuef() * 3000;
313 }
314
315 private LinearEnvelope getEnvelope(int index) {
316 return cubeBrt[index % cubeBrt.length];
317 }
318
319 private SinLFO getBase(int index) {
320 return base[index % base.length];
321 }
322
323 public void noteOnReceived(Note note) {
324 LinearEnvelope env = getEnvelope(note.getPitch());
325 env.setEndVal(min(1, env.getValuef() + (note.getVelocity() / 127.)), getAttackTime()).start();
326 }
327
328 public void noteOffReceived(Note note) {
329 getEnvelope(note.getPitch()).setEndVal(0, getReleaseTime()).start();
330 }
331
332 public void run(int deltaMs) {
333 int i = 0;
334 float huef = lx.getBaseHuef();
335 float levelf = level.getValuef();
336 for (Cube c : Cube.list) {
337 float v = max(getBase(i).getValuef() * levelf/4., getEnvelope(i++).getValuef());
338 setColor(c, color(
339 (huef + 20*v + abs(c.fy-128.)*.3 + c.fz) % 360,
340 min(100, 120*v),
341 100*v
342 ));
343 }
344 }
345 }
346