PageSourceSearch

https://fynthesizer.github.io/Drip/sketch.js

js fynthesizer.github.io collected 2026-10-03 09:13:21 UTC 30,315 bytes, 1,078 lines download raw bytes

1var Engine = Matter.Engine,
2  Events = Matter.Events,
3  Render = Matter.Render,
4  Runner = Matter.Runner,
5  Composite = Matter.Composite,
6  Composites = Matter.Composites,
7  Common = Matter.Common,
8  MouseConstraint = Matter.MouseConstraint,
9  Mouse = Matter.Mouse,
10  World = Matter.World,
11  Bounds = Matter.Bounds,
12  Vertices = Matter.Vertices,
13  Bodies = Matter.Bodies,
14  Body = Matter.Body;
15
16// create engine
17var engine, world;
18
19var mouseBounds;
20
21var mouseStartX;
22var mouseStartY;
23
24var dripSamples = ["drip0", "drip1", "drip2"];
25
26var drippers = [];
27var droplets = [];
28var bars = [];
29var waves = [];
30
31var reverb, delay;
32
33var collisionThreshold = 0.1;
34
35var tuneNotes = true;
36var keySig = [0, 2, 4, 5, 7, 9, 10];
37var drawWaves = true;
38
39var zoneY = 50;
40
41var maxFreq = 100000;
42var modMultiplier = 3;
43var modIndex = 20;
44var widthRange = {
45  min: 50,
46  max: 1000,
47};
48
49var backgroundColour;
50
51var tempo = 15;
52
53var isMobile = false;
54
55for (var i = 1; i <= 8; i++) {
56  setInterval(metronome, 60000 / tempo / i, i);
57}
58
59function preload() {
60  soundFormats("wav");
61}
62
63function setup() {
64  engine = Engine.create({
65    enableSleeping: true,
66  });
67  world = engine.world;
68  reverb = new p5.Reverb();
69  reverb.set(6, 2);
70
71  delay = new p5.Delay();
72  delay.delayTime(0.5);
73  delay.feedback(0.5);
74  Engine.run(engine);
75
76  backgroundColour = color(19, 21, 23);
77  smooth();
78  cursor(CROSS);
79  mouseBounds = Bounds.create();
80
81  if (
82    /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|ipad|iris|kindle|Android|Silk|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(
83      navigator.userAgent
84    ) ||
85    /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(
86      navigator.userAgent.substr(0, 4)
87    )
88  ) {
89    isMobile = true;
90  }
91
92  createCanvas(window.innerWidth, window.innerHeight);
93
94  drippers.push(
95    new Dripper(random(width * 0.33, width * 0.66), round(random(2, 4)))
96  );
97}
98
99function draw() {
100  background(backgroundColour);
101  stroke(255);
102  strokeWeight(1);
103  line(0, zoneY, width, zoneY);
104
105  if (!isMobile || (isMobile && touches.length > 0)) checkMouseTarget();
106
107  for (let w of waves) {
108    w.update();
109  }
110  for (let d of droplets) {
111    d.update();
112  }
113  for (let b of bars) {
114    b.update();
115  }
116  for (let d of drippers) {
117    d.update();
118  }
119}
120
121class Dripper {
122  constructor(x, rate) {
123    this.x = x;
124    this.rate = rate;
125
126    this.mouseOver = false;
127    this.readyToDelete = false;
128
129    this.mouseTarget = null;
130    this.transformMode = null;
131
132    this.rotation = Math.PI / 2;
133
134    this.dripSounds = [];
135    for (let i = 0; i < dripSamples.length; i++) {
136      this.dripSounds[i] = loadSound(dripSamples[i]);
137      this.dripSounds[i].amp(0.5);
138      this.dripSounds[i].connect(reverb);
139    }
140  }
141
142  update() {
143    if (this.rotation > Math.PI * 1.5) {
144      this.rotation = Math.PI / 2;
145      this.rate--;
146    } else if (this.rotation < Math.PI / 2) {
147      this.rotation = Math.PI * 1.5;
148      this.rate++;
149    }
150    this.rate = constrain(this.rate, 0, 8);
151    fill(255);
152    noStroke();
153    textAlign(CENTER, CENTER);
154    text(this.rate, this.x, zoneY - 35);
155    fill(backgroundColour);
156    if (this.readyToDelete) stroke(255, 127);
157    else stroke(255);
158    if (this.mouseTarget == "Move" || this.transformMode == "Move")
159      strokeWeight(2);
160    else strokeWeight(1);
161
162    var phase = sin(this.rotation);
163    var xPositions = [
164      map(phase, -1, 1, 25, 35),
165      map(phase, -1, 1, 15, 45),
166      map(phase, -1, 1, 35, 25),
167      map(phase, -1, 1, 45, 15),
168    ];
169    fill(backgroundColour);
170    beginShape();
171    vertex(this.x + 5, zoneY + 10);
172    vertex(this.x + 5, zoneY - 10);
173    vertex(this.x + 25, zoneY - 10);
174    vertex(this.x + 25, zoneY);
175    vertex(this.x + 35, zoneY);
176    vertex(this.x + 35, zoneY - 20);
177    vertex(this.x - 5, zoneY - 20);
178    vertex(this.x - 5, zoneY + 10);
179    endShape(CLOSE);
180
181    beginShape();
182    vertex(this.x + 32, zoneY - 20);
183    vertex(this.x + 32, zoneY - 30);
184    vertex(this.x + 28, zoneY - 30);
185    vertex(this.x + 28, zoneY - 20);
186    endShape(CLOSE);
187
188    if (this.mouseTarget == "Turn" || this.transformMode == "Turn")
189      strokeWeight(2);
190    else if (this.mouseTarget == null && this.transformMode == null)
191      strokeWeight(1);
192
193    beginShape();
194    vertex(this.x + xPositions[2], zoneY - 33);
195    vertex(this.x + xPositions[2], zoneY - 37);
196    vertex(this.x + xPositions[3], zoneY - 37);
197    vertex(this.x + xPositions[3], zoneY - 33);
198    endShape(CLOSE);
199
200    if (this.mouseTarget == null && this.transformMode == null) strokeWeight(1);
201
202    beginShape();
203    vertex(this.x + 35, zoneY - 30);
204    vertex(this.x + 35, zoneY - 40);
205    vertex(this.x + 25, zoneY - 40);
206    vertex(this.x + 25, zoneY - 30);
207    endShape(CLOSE);
208
209    if (this.mouseTarget == "Turn" || this.transformMode == "Turn")
210      strokeWeight(2);
211    else if (this.mouseTarget == null && this.transformMode == null)
212      strokeWeight(1);
213
214    beginShape();
215    vertex(this.x + xPositions[1], zoneY - 33);
216    vertex(this.x + xPositions[1], zoneY - 37);
217    vertex(this.x + xPositions[0], zoneY - 37);
218    vertex(this.x + xPositions[0], zoneY - 33);
219    endShape(CLOSE);
220
221    if (this.transformMode == "Move") {
222      if (!isMobile) this.x += mouseX - pmouseX;
223      else this.x = mouseX;
224      if (mouseX <= 5 || mouseX >= width - 5) this.readyToDelete = true;
225      else this.readyToDelete = false;
226
227      //this.dripSound.pan(map(this.x, 0, width, -1, 1));
228    }
229    if (this.transformMode == "Turn") {
230      let delta = (mouseX - pmouseX) / -15;
231      if (!(delta < 0 && this.rate >= 8) && !(delta > 0 && this.rate <= 0))
232        this.rotation += delta;
233    }
234  }
235
236  playDripSound() {
237    let dripSound =
238      this.dripSounds[Math.floor(Math.random() * this.dripSounds.length)];
239    dripSound.rate(random(0.5, 1.5));
240    dripSound.pan(map(this.x, 0, width, -1, 1));
241    dripSound.play();
242  }
243
244  checkMouseTarget() {
245    if (mouseX > this.x - 5 && mouseX < this.x + 45) {
246      if (mouseY < zoneY && mouseY > zoneY - 20) this.mouseTarget = "Move";
247      else if (mouseY < zoneY - 20) this.mouseTarget = "Turn";
248      else this.mouseTarget = null;
249    } else this.mouseTarget = null;
250  }
251
252  checkIfClicked() {
253    if (isMobile) this.checkMouseTarget();
254    this.transformMode = this.mouseTarget;
255    if (this.transformMode != null) return true;
256  }
257
258  mouseUp() {
259    this.transformMode = null;
260    if (isMobile) this.mouseTarget = null;
261    if (this.readyToDelete) this.destroy();
262  }
263
264  destroy() {
265    drippers.splice(drippers.indexOf(this), 1);
266  }
267
268  drip() {
269    droplets.push(new Droplet(this.x, zoneY, 6));
270    this.playDripSound();
271  }
272}
273
274class Droplet {
275  constructor(x, y, size) {
276    this.size = size;
277    this.trailX = [];
278    this.trailY = [];
279    this.body = Bodies.circle(x, y, size / 2, {
280      restitution: 1.0,
281      friction: 0.01,
282      collisionFilter: {
283        category: 0o1,
284        mask: 0o2,
285      },
286    });
287    World.add(world, this.body);
288  }
289
290  update() {
291    var pos = this.body.position;
292
293    this.trailX.unshift(pos.x);
294    this.trailY.unshift(pos.y);
295    if (this.trailX.length > this.size) this.trailX.pop();
296    if (this.trailY.length > this.size) this.trailY.pop();
297
298    var angle = this.body.angle;
299
300    for (let i = 1; i < this.trailX.length; i++) {
301      strokeWeight(this.size - i);
302      stroke(255, 255, 255, 127 - i * 20);
303
304      line(
305        this.trailX[i],
306        this.trailY[i],
307        this.trailX[i - 1],
308        this.trailY[i - 1]
309      );
310    }
311
312    strokeWeight(this.size);
313    stroke(255);
314    point(pos.x, pos.y);
315
316    if (pos.y > height) this.destroy();
317    if (this.body.isSleeping) this.destroy();
318  }
319
320  destroy() {
321    World.remove(world, this.body);
322    droplets.splice(droplets.indexOf(this), 1);
323  }
324}
325
326class Bar {
327  constructor(x, y, w, h, a) {
328    this.w = w;
329    this.h = h;
330    this.a = a;
331    this.struck = 0.0;
332
333    this.mouseA;
334    this.pmouseA;
335    this.mouseMoveA;
336
337    //Main Envelope
338    this.env = new p5.Env();
339
340    this.sound = loadSound("glockenspiel");
341    this.sound.connect(reverb);
342    this.sound.connect(delay);
343    this.sound.amp(this.env);
344
345    //Main Oscillator
346    this.osc = new p5.Oscillator();
347    this.osc.amp(this.env);
348    this.osc.connect(reverb);
349    this.osc.connect(delay);
350    this.osc.start();
351
352    this.body = Bodies.rectangle(x, y, w, h, {
353      isStatic: true,
354      collisionFilter: {
355        category: 0o2,
356      },
357    });
358
359    Body.setAngle(this.body, a);
360    World.add(world, this.body);
361
362    this.vertices = this.body.vertices;
363    this.edges = [
364      {
365        x0: this.vertices[0].x,
366        y0: this.vertices[0].y,
367        x1: this.vertices[1].x,
368        y1: this.vertices[1].y,
369        hovered: false,
370      },
371      {
372        x0: this.vertices[1].x,
373        y0: this.vertices[1].y,
374        x1: this.vertices[2].x,
375        y1: this.vertices[2].y,
376        hovered: false,
377      },
378      {
379        x0: this.vertices[2].x,
380        y0: this.vertices[2].y,
381        x1: this.vertices[3].x,
382        y1: this.vertices[3].y,
383        hovered: false,
384      },
385      {
386        x0: this.vertices[3].x,
387        y0: this.vertices[3].y,
388        x1: this.vertices[0].x,
389        y1: this.vertices[0].y,
390        hovered: false,
391      },
392    ];
393
394    this.mouseOver = false;
395    this.mouseSelected = false;
396    this.transformMode = null;
397    this.mouseTarget = null;
398    this.readyToDelete = false;
399
400    this.startVertices = this.vertices;
401    this.newVertices = this.vertices;
402
403    this.updateParameters();
404  }
405
406  updateParameters() {
407    var pos = Vertices.centre(this.body.vertices);
408
409    this.decay = map(this.h, 15, 50, 0.3, 0.75);
410    this.env.setADSR(0.01, this.decay, 0.0, this.decay);
411    this.freq = maxFreq / this.w;
412    if (tuneNotes) {
413      this.pitch = freqToMidi(this.freq);
414      this.pitch = tuneNote(this.pitch);
415      this.freq = midiToFreq(this.pitch);
416    }
417
418    this.osc.freq(this.freq);
419    this.osc.pan(map(pos.x, 0, width, -1, 1));
420    this.sound.rate(this.freq / 523.25);
421    this.sound.pan(map(pos.x, 0, width, -1, 1));
422  }
423
424  update() {
425    var colour = lerpColor(backgroundColour, color(255), this.struck);
426    this.vertices = this.body.vertices;
427
428    this.edges = [
429      {
430        x0: this.vertices[0].x,
431        y0: this.vertices[0].y,
432        x1: this.vertices[1].x,
433        y1: this.vertices[1].y,
434      },
435      {
436        x0: this.vertices[1].x,
437        y0: this.vertices[1].y,
438        x1: this.vertices[2].x,
439        y1: this.vertices[2].y,
440      },
441      {
442        x0: this.vertices[2].x,
443        y0: this.vertices[2].y,
444        x1: this.vertices[3].x,
445        y1: this.vertices[3].y,
446      },
447      {
448        x0: this.vertices[3].x,
449        y0: this.vertices[3].y,
450        x1: this.vertices[0].x,
451        y1: this.vertices[0].y,
452      },
453    ];
454
455    if (this.readyToDelete) stroke(255, 127);
456    else stroke(255);
457    strokeWeight(1);
458
459    strokeWeight(2);
460    if (
461      this.mouseTarget == "ScaleHeight" ||
462      this.transformMode == "ScaleHeight"
463    ) {
464      this.drawArrow("Top");
465      this.drawArrow("Bottom");
466      strokeWeight(1);
467    } else if (
468      this.mouseTarget == "ScaleWidth" ||
469      this.transformMode == "ScaleWidth"
470    ) {
471      this.drawArrow("Left");
472      this.drawArrow("Right");
473      strokeWeight(1);
474    } else if (this.mouseTarget == "Move" || this.transformMode == "Move") {
475      strokeWeight(2);
476      //this.drawArrow("Left");
477      //this.drawArrow("Right");
478      //this.drawArrow("Top");
479      //this.drawArrow("Bottom");
480      //strokeWeight(1);
481    } else if (this.mouseTarget == "Turn" || this.transformMode == "Turn") {
482      strokeWeight(2);
483      stroke(255);
484      noFill();
485      this.drawArrow("Rotation");
486      strokeWeight(1);
487    } else strokeWeight(1);
488
489    fill(colour);
490    beginShape();
491    vertex(this.vertices[0].x, this.vertices[0].y);
492    vertex(this.vertices[1].x, this.vertices[1].y);
493    vertex(this.vertices[2].x, this.vertices[2].y);
494    vertex(this.vertices[3].x, this.vertices[3].y);
495    endShape(CLOSE);
496
497    if (this.struck > 0.0) {
498      this.struck -= 0.05;
499    }
500
501    for (let d of droplets) {
502      var collision = Matter.SAT.collides(this.body, d.body);
503
504      if (collision.collided) {
505        var magnitude = dotProduct(collision.normal, collision.bodyB.velocity);
506        if (magnitude < 0) magnitude *= -1;
507
508        this.strike(
509          magnitude,
510          collision.bodyB.position.x,
511          collision.bodyB.position.y
512        );
513      }
514    }
515  }
516
517  checkMouseTarget() {
518    var centre = Vertices.centre(this.body.vertices);
519    var mouseDistance = dist(centre.x, centre.y, mouseX, mouseY);
520    if (this.transformMode == null) {
521      if (isMouseOnLine(this.edges[0], 6) || isMouseOnLine(this.edges[2], 6)) {
522        this.mouseTarget = "ScaleHeight";
523        return true;
524      } else if (
525        isMouseOnLine(this.edges[1], 6) ||
526        isMouseOnLine(this.edges[3], 6)
527      ) {
528        this.mouseTarget = "ScaleWidth";
529        return true;
530      } else if (
531        Vertices.contains(this.body.vertices, {
532          x: mouseX,
533          y: mouseY,
534        })
535      ) {
536        this.mouseTarget = "Move";
537        return true;
538      } else if (mouseDistance < 75) {
539        strokeWeight(1);
540        stroke(255);
541        noFill();
542        this.drawArrow("Rotation");
543        if (mouseDistance < 57 && mouseDistance > 43) {
544          this.mouseTarget = "Turn";
545          return true;
546        } else {
547          this.mouseTarget = null;
548          return true;
549        }
550      } else if (this.transformMode != null) {
551        this.mouseTarget = transformMode;
552        return true;
553      } else {
554        this.mouseTarget = null;
555        return false;
556      }
557    } else return false;
558  }
559
560  drawArrow(side) {
561    switch (side) {
562      case "Top":
563        line(
564          this.edges[0].x0,
565          this.edges[0].y0,
566          this.edges[0].x1,
567          this.edges[0].y1
568        );
569        var midpoint = {
570          x: (this.edges[0].x0 + this.edges[0].x1) / 2,
571          y: (this.edges[0].y0 + this.edges[0].y1) / 2,
572        };
573        var target = {
574          x: midpoint.x + 50 * cos(this.body.angle - Math.PI / 2),
575          y: midpoint.y + 50 * sin(this.body.angle - Math.PI / 2),
576        };
577        line(midpoint.x, midpoint.y, target.x, target.y);
578        line(
579          target.x,
580          target.y,
581          target.x + 10 * cos(this.body.angle + Math.PI / 4),
582          target.y + 10 * sin(this.body.angle + Math.PI / 4)
583        );
584        line(
585          target.x,
586          target.y,
587          target.x + 10 * cos(this.body.angle + (Math.PI / 4) * 3),
588          target.y + 10 * sin(this.body.angle + (Math.PI / 4) * 3)
589        );
590        break;
591      case "Right":
592        line(
593          this.edges[1].x0,
594          this.edges[1].y0,
595          this.edges[1].x1,
596          this.edges[1].y1
597        );
598        var midpoint = {
599          x: (this.edges[1].x0 + this.edges[1].x1) / 2,
600          y: (this.edges[1].y0 + this.edges[1].y1) / 2,
601        };
602        var target = {
603          x: midpoint.x + 50 * cos(this.body.angle),
604          y: midpoint.y + 50 * sin(this.body.angle),
605        };
606        line(midpoint.x, midpoint.y, target.x, target.y);
607        line(
608          target.x,
609          target.y,
610          target.x + 10 * cos(this.body.angle + (Math.PI / 4) * 5),
611          target.y + 10 * sin(this.body.angle + (Math.PI / 4) * 5)
612        );
613        line(
614          target.x,
615          target.y,
616          target.x + 10 * cos(this.body.angle + (Math.PI / 4) * 3),
617          target.y + 10 * sin(this.body.angle + (Math.PI / 4) * 3)
618        );
619        break;
620      case "Bottom":
621        line(
622          this.edges[2].x0,
623          this.edges[2].y0,
624          this.edges[2].x1,
625          this.edges[2].y1
626        );
627        var midpoint = {
628          x: (this.edges[2].x0 + this.edges[2].x1) / 2,
629          y: (this.edges[2].y0 + this.edges[2].y1) / 2,
630        };
631        var target = {
632          x: midpoint.x + 50 * cos(this.body.angle + Math.PI / 2),
633          y: midpoint.y + 50 * sin(this.body.angle + Math.PI / 2),
634        };
635        line(midpoint.x, midpoint.y, target.x, target.y);
636        line(
637          target.x,
638          target.y,
639          target.x + 10 * cos(this.body.angle - Math.PI / 4),
640          target.y + 10 * sin(this.body.angle - Math.PI / 4)
641        );
642        line(
643          target.x,
644          target.y,
645          target.x + 10 * cos(this.body.angle - (Math.PI / 4) * 3),
646          target.y + 10 * sin(this.body.angle - (Math.PI / 4) * 3)
647        );
648        break;
649      case "Left":
650        line(
651          this.edges[3].x0,
652          this.edges[3].y0,
653          this.edges[3].x1,
654          this.edges[3].y1
655        );
656        var midpoint = {
657          x: (this.edges[3].x0 + this.edges[3].x1) / 2,
658          y: (this.edges[3].y0 + this.edges[3].y1) / 2,
659        };
660        var target = {
661          x: midpoint.x + 50 * cos(this.body.angle + Math.PI),
662          y: midpoint.y + 50 * sin(this.body.angle + Math.PI),
663        };
664        line(midpoint.x, midpoint.y, target.x, target.y);
665        line(
666          target.x,
667          target.y,
668          target.x + 10 * cos(this.body.angle - (Math.PI / 4) * 7),
669          target.y + 10 * sin(this.body.angle - (Math.PI / 4) * 7)
670        );
671        line(
672          target.x,
673          target.y,
674          target.x + 10 * cos(this.body.angle - Math.PI / 4),
675          target.y + 10 * sin(this.body.angle - Math.PI / 4)
676        );
677        break;
678      case "Rotation":
679        var centre = Vertices.centre(this.body.vertices);
680        var arrowLength = Math.PI / 40;
681        ellipse(centre.x, centre.y, 100, 100);
682        5;
683        line(
684          centre.x + 50 * cos(this.body.angle - Math.PI / 2 + arrowLength),
685          centre.y + 50 * sin(this.body.angle - Math.PI / 2 + arrowLength),
686          centre.x + 57 * cos(this.body.angle - Math.PI / 2 - arrowLength),
687          centre.y + 57 * sin(this.body.angle - Math.PI / 2 - arrowLength)
688        );
689        line(
690          centre.x + 50 * cos(this.body.angle - Math.PI / 2 + arrowLength),
691          centre.y + 50 * sin(this.body.angle - Math.PI / 2 + arrowLength),
692          centre.x + 43 * cos(this.body.angle - Math.PI / 2 - arrowLength),
693          centre.y + 43 * sin(this.body.angle - Math.PI / 2 - arrowLength)
694        );
695        line(
696          centre.x + 50 * cos(this.body.angle + Math.PI / 2 + arrowLength),
697          centre.y + 50 * sin(this.body.angle + Math.PI / 2 + arrowLength),
698          centre.x + 57 * cos(this.body.angle + Math.PI / 2 - arrowLength),
699          centre.y + 57 * sin(this.body.angle + Math.PI / 2 - arrowLength)
700        );
701        line(
702          centre.x + 50 * cos(this.body.angle + Math.PI / 2 + arrowLength),
703          centre.y + 50 * sin(this.body.angle + Math.PI / 2 + arrowLength),
704          centre.x + 43 * cos(this.body.angle + Math.PI / 2 - arrowLength),
705          centre.y + 43 * sin(this.body.angle + Math.PI / 2 - arrowLength)
706        );
707        break;
708    }
709  }
710
711  updateTransform(mode) {
712    var mouseMoveX = mouseX - pmouseX;
713    var mouseMoveY = mouseY - pmouseY;
714
715    var centre = Vertices.centre(this.body.vertices);
716
717    this.mouseA = atan2(mouseY - centre.y, mouseX - centre.x);
718    if (this.pmouseA != undefined) this.mouseMoveA = this.mouseA - this.pmouseA;
719    else this.mouseMoveA = 0;
720    this.pmouseA = this.mouseA;
721
722    switch (mode) {
723      case "Move":
724        Body.setPosition(this.body, {
725          x: mouseX,
726          y: mouseY,
727        });
728        if (
729          mouseX <= 5 ||
730          mouseX >= width - 5 ||
731          mouseY <= zoneY ||
732          mouseY >= height - 5
733        )
734          this.readyToDelete = true;
735        else this.readyToDelete = false;
736        break;
737      case "ScaleHeight":
738        var nearestPoint = nearestPointOnLine({
739          x0: (this.edges[3].x0 + this.edges[3].x1) / 2,
740          y0: (this.edges[3].y0 + this.edges[3].y1) / 2,
741          x1: (this.edges[1].x0 + this.edges[1].x1) / 2,
742          y1: (this.edges[1].y0 + this.edges[1].y1) / 2,
743        });
744        this.h = dist(nearestPoint.x, nearestPoint.y, mouseX, mouseY) * 2;
745        //this.h -= mouseMoveY;
746        this.h = constrain(this.h, 15, 50);
747        var newX1 =
748          this.body.vertices[2].x + this.h * cos(this.body.angle - Math.PI / 2);
749        var newY1 =
750          this.body.vertices[2].y + this.h * sin(this.body.angle - Math.PI / 2);
751        var newX2 =
752          this.body.vertices[3].x + this.h * cos(this.body.angle - Math.PI / 2);
753        var newY2 =
754          this.body.vertices[3].y + this.h * sin(this.body.angle - Math.PI / 2);
755
756        Body.setVertices(this.body, [
757          {
758            x: centre.x + newX2,
759            y: centre.y + newY2,
760          },
761          {
762            x: centre.x + newX1,
763            y: centre.y + newY1,
764          },
765          {
766            x: centre.x + this.body.vertices[2].x,
767            y: centre.y + this.body.vertices[2].y,
768          },
769          {
770            x: centre.x + this.body.vertices[3].x,
771            y: centre.y + this.body.vertices[3].y,
772          },
773        ]);
774        break;
775      case "ScaleWidth":
776        var nearestPoint = nearestPointOnLine({
777          x0: (this.edges[0].x0 + this.edges[0].x1) / 2,
778          y0: (this.edges[0].y0 + this.edges[0].y1) / 2,
779          x1: (this.edges[2].x0 + this.edges[2].x1) / 2,
780          y1: (this.edges[2].y0 + this.edges[2].y1) / 2,
781        });
782        this.w = dist(nearestPoint.x, nearestPoint.y, mouseX, mouseY) * 2;
783        this.w = constrain(this.w, widthRange.min, widthRange.max);
784        var newX1 = this.body.vertices[3].x + this.w * cos(this.body.angle);
785        var newY1 = this.body.vertices[3].y + this.w * sin(this.body.angle);
786        var newX2 = this.body.vertices[0].x + this.w * cos(this.body.angle);
787        var newY2 = this.body.vertices[0].y + this.w * sin(this.body.angle);
788
789        Body.setVertices(this.body, [
790          {
791            x: centre.x + this.body.vertices[0].x,
792            y: centre.y + this.body.vertices[0].y,
793          },
794          {
795            x: centre.x + newX2,
796            y: centre.y + newY2,
797          },
798          {
799            x: centre.x + newX1,
800            y: centre.y + newY1,
801          },
802          {
803            x: centre.x + this.body.vertices[3].x,
804            y: centre.y + this.body.vertices[3].y,
805          },
806        ]);
807        break;
808      case "Turn":
809        Body.rotate(this.body, this.mouseMoveA);
810        break;
811      default:
812        break;
813    }
814
815    this.updateParameters();
816  }
817
818  checkIfClicked() {
819    if (isMobile) this.checkMouseTarget();
820    this.transformMode = this.mouseTarget;
821    this.mouseA = atan2(
822      mouseY - Vertices.centre(this.body.vertices).y,
823      mouseX - Vertices.centre(this.body.vertices).x
824    );
825    //if (this.pmouseA != undefined) this.mouseMoveA = this.mouseA - this.pmouseA;
826    //else this.mouseMoveA = 0;
827    this.pmouseA = this.mouseA;
828    if (this.transformMode != null) return true;
829  }
830
831  mouseUp() {
832    this.transformMode = null;
833    if (isMobile) this.mouseTarget = null;
834    if (this.readyToDelete) this.destroy();
835  }
836
837  strike(magnitude, x, y) {
838    if (magnitude > collisionThreshold) {
839      if (this.sound.isLoaded()) this.sound.play();
840      this.env.setRange(map(magnitude, 0, 10, 0.1, 0.3), 0.0);
841      let attack = map(magnitude, 0, 2, 0.3, 0.01);
842      if (attack < 0.01) attack = 0.01;
843      if (attack <= 0.05) attack = 0.01;
844      this.env.setADSR(attack, this.decay, 0.0, this.decay);
845      this.env.play();
846      //this.env2.play();
847      this.struck = map(magnitude, 0, 20, 0.0, 1.0);
848      if (drawWaves) waves.push(new wave(x, y, magnitude));
849    }
850
851    //Bug fix for magnitude 0 collisions when bar first spawns
852    else if (magnitude == 0) {
853      if (this.sound.isLoaded()) this.sound.play();
854      this.env.setRange(0.15);
855      //this.pitchEnv.play();
856      this.env.play();
857      //this.env2.play();
858      this.struck = 0.5;
859      if (drawWaves) waves.push(new wave(x, y, 10));
860    }
861  }
862
863  destroy() {
864    World.remove(world, this.body);
865    this.osc.stop();
866    bars.splice(bars.indexOf(this), 1);
867  }
868}
869
870class wave {
871  constructor(x, y, magnitude) {
872    this.x = x;
873    this.y = y;
874    this.size = 0;
875    this.alpha = map(magnitude, 0, 20, 0, 200);
876  }
877
878  update() {
879    if (this.alpha > 0) this.alpha -= 2;
880    else this.destroy();
881    this.size += 10;
882    stroke(255, this.alpha);
883    noFill();
884    ellipse(this.x, this.y, this.size, this.size);
885  }
886
887  destroy() {
888    waves.splice(waves.indexOf(this), 1);
889  }
890}
891
892function mousePressed() {
893  if (
894    (!isMobile && mouseButton != RIGHT) ||
895    (isMobile && event.type == "touchstart")
896  ) {
897    mouseStartX = mouseX;
898    mouseStartY = mouseY;
899
900    for (let b of bars) {
901      if (b.checkIfClicked()) return;
902    }
903    for (let d of drippers) {
904      if (d.checkIfClicked()) return;
905    }
906
907    if (mouseY < zoneY) {
908      drippers.push(new Dripper(mouseX, round(random(2, 6))));
909      return;
910    } else {
911      bars.push(
912        new Bar(
913          mouseX,
914          mouseY,
915          pitchToWidth(findUnusedPitch()),
916          random(25, 50),
917          random(Math.PI / -4, Math.PI / 4)
918        )
919      );
920      return;
921    }
922  }
923}
924
925function mouseReleased() {
926  if (!isMobile || isMobile) {
927    for (let b of bars) {
928      b.mouseUp();
929    }
930    for (let d of drippers) {
931      d.mouseUp();
932    }
933  }
934}
935
936function touchMoved() {
937  for (let b of bars) {
938    if (b.transformMode != null) b.updateTransform(b.transformMode);
939  }
940}
941
942function checkMouseTarget() {
943  var selectedBar;
944  for (i = bars.length - 1; i >= 0; i--) {
945    bars[i].mouseTarget = null;
946  }
947  for (i = bars.length - 1; i >= 0; i--) {
948    if (bars[i].checkMouseTarget()) {
949      selectedBar = i;
950      break;
951    }
952  }
953  for (i = drippers.length - 1; i >= 0; i--) {
954    if (drippers[i].checkMouseTarget()) {
955      break;
956    }
957  }
958  return;
959}
960
961function keyPressed() {
962  if (keyCode == 66) {
963    //bars.push(new Bar(mouseX,mouseY,random(widthRange.min,widthRange.max/2),random(20,40),random(-0.5,0.5)));
964    bars.push(
965      new Bar(
966        mouseX,
967        mouseY,
968        random(50, 200),
969        random(20, 50),
970        random(Math.PI / -4, Math.PI / 4)
971      )
972    );
973  }
974  if (keyCode == 68) {
975    drippers.push(new Dripper(mouseX, round(random(2, 6))));
976  }
977}
978
979function keyReleased() {}
980
981function isMouseOnLine(line, tolerance) {
982  //
983  lerp = function (a, b, x) {
984    return a + x * (b - a);
985  };
986  var dx = line.x1 - line.x0;
987  var dy = line.y1 - line.y0;
988  var t =
989    ((mouseX - line.x0) * dx + (mouseY - line.y0) * dy) / (dx * dx + dy * dy);
990  t = constrain(t, 0, 1);
991  var lineX = lerp(line.x0, line.x1, t);
992  var lineY = lerp(line.y0, line.y1, t);
993  var dx = mouseX - lineX;
994  var dy = mouseY - lineY;
995  var distance = Math.abs(Math.sqrt(dx * dx + dy * dy));
996  if (distance < tolerance) {
997    return true;
998  } else {
999    return false;
1000  }
1001}
1002
1003function nearestPointOnLine(line) {
1004  //
1005  lerp = function (a, b, x) {
1006    return a + x * (b - a);
1007  };
1008  var dx = line.x1 - line.x0;
1009  var dy = line.y1 - line.y0;
1010  var t =
1011    ((mouseX - line.x0) * dx + (mouseY - line.y0) * dy) / (dx * dx + dy * dy);
1012  t = constrain(t, 0, 1);
1013  var lineX = lerp(line.x0, line.x1, t);
1014  var lineY = lerp(line.y0, line.y1, t);
1015  return {
1016    x: lineX,
1017    y: lineY,
1018  };
1019}
1020
1021function metronome(step) {
1022  for (let d of drippers) {
1023    if (d.rate == step) d.drip();
1024  }
1025}
1026
1027function printTotalObjects() {
1028  print("Bars: " + bars.length);
1029  print("Droplets: " + droplets.length);
1030  print("Drippers: " + drippers.length);
1031  print("Waves: " + waves.length);
1032  print(
1033    "Total: " + (bars.length + droplets.length + drippers.length + waves.length)
1034  );
1035}
1036
1037function tuneNote(note) {
1038  var keys = keySig.slice();
1039  var nearestA = Math.floor(note / 12) * 12;
1040  for (i = 0; i < keys.length; i++) {
1041    keys[i] += nearestA;
1042  }
1043  var closest = keys.reduce(function (prev, curr) {
1044    return Math.abs(curr - note) < Math.abs(prev - note) ? curr : prev;
1045  });
1046
1047  return closest;
1048}
1049
1050function windowResized() {
1051  resizeCanvas(windowWidth, windowHeight);
1052}
1053
1054function dotProduct(vector1, vector2) {
1055  return (vector1.x * vector2.x + vector1.y * vector2.y) * -1;
1056}
1057
1058function findUnusedPitch() {
1059  let usedPitches = [];
1060  let attempts = 0;
1061  for (let i = 0; i < bars.length; i++) {
1062    usedPitches.push(bars[i].pitch);
1063  }
1064  let randomPitch;
1065
1066  while (attempts < 40) {
1067    randomPitch = random(60, 95);
1068    if (tuneNotes) randomPitch = tuneNote(randomPitch);
1069    if (!usedPitches.includes(randomPitch)) break;
1070  }
1071
1072  return randomPitch;
1073}
1074
1075function pitchToWidth(pitch) {
1076  let freq = midiToFreq(pitch);
1077  return maxFreq / freq;
1078}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.