[
  {
    "path": ".gitignore",
    "content": "distribution\ndata\nlib\ntest\n.DS_Store\n.project\n.classpath\ntmp\n/bin/\n_sandbox\nexamples_removed\n.settings\n"
  },
  {
    "path": "README.md",
    "content": "# XYscope\nv 3.0.0  \ncc [teddavis.org](https://teddavis.org) 2017 – 2023\n\nProcessing library to render vector graphics on vector displays.   \n\nXYscope converts the coordinates of primative shapes (point, line, rect, ellipse, vertex, box, sphere, torus...)to audio waveforms (oscillators with custom wavetables) which are sent to an analog display, revealing their graphics. This process of generating real-time audio from vector drawings is built upon the amazing Minim library. Vector graphics shine on a vector display and now we can view our generative works like never before! Tested on MacOS, Windows, Linux (RPi!).\n\n[teddavis.org/xyscope](http://www.teddavis.org/xyscope)  \n[github.com/ffd8/xyscope](https://github.com/ffd8/xyscope)\n\n## Table of Contents\n- [Installation](#installation)\n- [Audio Interfaces](#audio-interfaces)\n- [Vector Displays](#vector-displays)\n- [Getting Started](#getting-started)\n- [Additive Synthesis](#additive-synthesis)\n- [References](#references)\n- [Extras ](#extras)\n\n## Installation\n\nAdd via Processing's Library Manager:  \n\n- Sketch menu » Import Library... » Add Library... (v4 Manage Libraries...)  \n- Search for 'XYscope' and click `Install`.  \n- Search for 'Minim' and click `Install`.\n\nThis library relies on and is infinitely thankful for [Minim](https://github.com/ddf/Minim)!  \nSearch for and add the following libraries for various examples:  \n[Geomerative](https://github.com/rikrd/geomerative), [OpenCV for Processing](https://github.com/atduskgreg/opencv-processing), [openkinect](https://github.com/shiffman/OpenKinect-for-Processing), [video](https://github.com/processing/processing-video), [syphon](https://github.com/Syphon/Processing)\n\nAdd manually, [download latest release](https://github.com/ffd8/xyscope/releases/latest) and expand into `~/Processing/libraries`:\n\n## Audio Interfaces\n\n*< $5*  \nAt the very least you can use your computer's headphone jack with an [`1/8\" to RCA`](https://duckduckgo.com/?q=1%2F8%22+to+RCA&t=h_&iax=images&ia=images) cable. However you'll soon want to get a [DC-Coupled](https://www.expert-sleepers.co.uk/siwacompatibility.html) audio interface for a cleaner and more stable visual (not wobbling/centering constantly). \n\n*< $20*  \nFor workshops I like to use some variant of the 48Khz [Delock 61645](https://www.delock.com/produkt/61645/merkmale.html) or 96Khz [Delock 63926](https://www.delock.com/produkt/63926/merkmale.html) – you'll find the same chip under many different brands and casings. For modern laptops, there's also a very compact 96Khz [Delock USB-C](https://www.delock.com/produkt/66304/merkmale.html) version.\n\n*> $150+*  \nMany of us in the community found the [MOTU Ultralite Mk3 Hybrid](https://motu.com/products/motuaudio/ultralite-mk3) (or newer versions) to be an ideal audio interface. Price varies from used to new. It offers, 10-channels of DC-Coupled 196Khz output, which is useful to drive multiple oscilloscopes or RGB lasers (X, Y, R, G, B).\n\n### List\nCheck which interface is plugged in and how to reference it.\n\n```\nxy.getMixerInfo(); // lists available audio devices\n```\n\n### Select\nBy default XYscope uses the system settings audio-output, however we can specify a custom sound card (Digital Analog Converter – DAC) and sample rate.\n\n```\nxy = new XYscope(this); // system settings soundcard \nxy = new XYscope(this, \"MOTU 1_2\"); // specify a soundcard/channel to use \nxy = new XYscope(this, 96000); // custom sample rate ( > finer detail if card supports) \nxy = new XYscope(this, \"MOTU 1_2\", 96000); // custom soundcard, custom sample rate \n```\n\nOr select a previous XYscope instance as the output for additive-synthesis:\n```\nxy2 = new XYscope(this, xy.outXY); // send 2nd instance to 1st\n```\n\n### Aggregate Device\nIf you have a multi-channel DAC, we need to create multiple 2ch stereo pairs, since Processing/Minim can't handel multi-channel devices. Especially useful with a great DAC like the MOTU Ultralite Mk3 Hybrid (or newer), offering 10ch of DC-Coupled outputs, so one could control 5 oscillscopes at once! Have only tested using MacOS:\n\n- Press `CMD + Spacebar` (opens Spotlight Search)\n- Type `Audio Midi Setup`, press `ENTER`\n- Click `+` in lower left, select `Create Aggregate Device`\n- Tick checkbox of your multi-channel audio device\n- Click `Configure Speakers...` in lower right\n- Set configuration to `Stereo`, select channels desired\n- Rename to something like: `MOTU 1_2`\n- Repeat above for each stereo pair, ie, `MOTU 3_4`, `MOTU 5_6`...\n- They can now be selected by name when initializing XYscope!\n\n### Multi-Channel Output Device\nWithin `Audio Midi Setup`, you can `Create Multi-Output Device` to send your output to multiple devices, ie. [Blackhole](https://existential.audio/blackhole/) + DAC + Speakers, so you can view it [virtually](https://www.oscilloscopemusic.com/software/oscilloscope/), on analog device, and hear it. \n\n- Press `CMD + Spacebar` (opens Spotlight Search)\n- Type `Audio Midi Setup`, press `ENTER`\n- Click `+` in lower left, select `Create Multi-Channel Device`\n- Tick checkbox of best device first, then select additional\n- Set sample rate to highest available, 48000 or 96000+\n- Rename for clarity, ie `DAC + SPEAKERS` or `BLACKHOLE + SPEAKERS`.\n\n## Vector Displays\nNow we need a vector display to see our glowing output!\n\n### Virtual\n[Oscilloscope](https://www.oscilloscopemusic.com/software/oscilloscope/) by Hansi Raber (adopting [m1el's woscope](http://m1el.github.io/woscope-how/) 'physical rendering') is a fantastic way to see your XYscope drawings while on the go without a physical device. You'll want to install [Blackhole](https://existential.audio/blackhole/) (MacOS) or [VB-CABLE](https://vb-audio.com/Cable/index.htm) (Windows) to re-route your system audio to a virtual source for Oscilloscope to render it.\n\n### Analog Oscilloscope\nThis is what we really want! They have a Cathode-ray Tube (CRT) that is the magic behind this obsession. You'll find them for ~$50 used on auction websites – be sure it has 2-channels (z-axis input is a bonus) and that they show images of a sharp working beam. You'll need a few [`RCA to BNC`](https://duckduckgo.com/?q=RCA+to+BNC&t=h_&iar=images&iax=images&ia=images) adaptors to interface with it. Have fun playing with all the knobs to put it into `XY Mode` so that the 2-channels drive the beam X/Y (Horizontal/Vertical).\n\n### X-Y Monitor\nSimilar to an analog oscilloscope, but usually has a larger display and reduced controls for X-Y (+Z) input, leaving away many of the features on an oscilloscope we won't use. They're more rare, expensive, but great if you stumble upon one. Don't confuse these with a 'vector monitor' which is used for TV broadcast and won't draw X-Y coordinates.\n\n### Vectrex\nA vector-graphics video game system of the 1980s, these amazing 9\" displays can be [very carefully modified](http://users.sussex.ac.uk/~ad207/adweb/assets/vectrexminijackinputmod2014.pdf) (**CAREFUL - at own risk**) to override (on-demand) the videogame control of the monitor's XYZ inputs. It's ideal to use [switching jacks](https://www.youtube.com/watch?v=q3sKZA2r7qk) so videogames still works when cables are unplugged. You'll also want to apply the [SPOT KILLER MOD](https://www.facebook.com/groups/vectorsynthesis/posts/832237263652516/), but BE SURE to apply an appropriately high-voltage rated switch, so it can be toggled on and off.\n\nXYscope has a special mode when using a Vectrex for the aspect ratio. See example `vextrex` within `5_displays`, but the main notes are:\n\n```\n/* within setup() */\nxy.vectrex(90); // -90/90 for landscape, 0 for portrait\n\n// optionally z-axis for blanking\nxy.z(\"MOTU 3-4\"); // use custom 3rd channel for z-axis\n//xy.zRange(.5, 0); // set min/max values for beam on/off\n```\n\n### Laser\nOnce you want something bigger than most screens, you'll want to move to an RGB Laser. They're BIG and BRIGHT, but also much slower and more dangerous! It's slower because it mechanically moves galvos/mirrors for the X-Y and dangerous, because, LASERS! Nevertheless, they can be controlled via the ILDA analog input, for which I've developed an easy to build [dac_ilda adaptor](https://github.com/ffd8/dac_ilda). To control a laser, you'll need a DAC (sound card) with a minimum of 5-channels, for sending `X, Y, R, G, B` signals. See example `laser` within `5_displays`, but the main notes are:\n\n```\n/* within setup() */\n// only stereo pairs in processing, so it's broken to R, GB\n// incase 2nd channel of R pair is useful for blanking/etc.\nxy.laser(\"MOTU 3-4\", \"MOTU 5-6\"); //xy.laser(mixerR, mixerGB);\n\n/* within setup() or draw() */\n// RGB waveforms have own freq, so we can them out of sync\nxy.strokeFreq(50.05, 50.1, 50.2);\n  \nxy.strokeDash(8); // optional dashes in the RGB stroke\n\nxy.stroke(0, 255, 255); // set RGB stroke before shapes (0-255)\n\nxy.limitPath(0); // avoid drawing any forms beyond view\n```\n\n## Getting Started\nOur basic template for an XYscope sketch involves:\n\n```\nimport ddf.minim.*; // minim req to gen audio\nimport xyscope.*;   // import XYscope\nXYscope xy;         // create XYscope instance\n \nvoid setup(){ \n  size(512, 512); // window size, optionally add P3D\n \n  xy = new XYscope(this, \"\"); // define XYscope instance, \"custom_dac\" optional\n  xy.getMixerInfo(); // lists all audio devices\n} \n \nvoid draw(){ \n  background(0); \n  xy.clearWaves();  // clear waves from previous drawing\n  \n  // draw shapes here\n  xy.circle(width/2, height/2, width); // it all began with a circle....\n  \n  xy.buildWaves(); // build shapes to audio waves\n  xy.drawAll();\n} \n```\n\n## Additive Synthesis\nSomething very unique to this workflow is sending multiple audio signals to the oscilloscope, which interfer and modulate one another. As these waves combine, their amp and freq determine its influence on other waves. Key is having different frequencies and amplitudes to modulate off one another. The lower the frequency, the more it will push other waves around. The lower the amp, the less influence it has on the additive waveform. We can simply open two sketches, both being sent to the same sound card to see this in action, however we can also achieve it within a single sketch. \n\n```\nimport ddf.minim.*; // minim req to gen audio\nimport xyscope.*;   // import XYscope\nXYscope xy, xy2;    // create 2x XYscope instances\n\nvoid setup() {\n  size(512, 512); // declares size of output window\n  xy = new XYscope(this);\n  xy2 = new XYscope(this, xy.outXY); // patch 2nd instance to 1st output\n}\n\nvoid draw() {\n  background(0);\n  // set main shapes\n  xy.clearWaves();\n  xy.circle(width/2, height/2, width/2);\n  xy.buildWaves();\n  xy.drawPath(0, 255, 255); // custom stroke color\n  xy.drawXY(); // displays combined output\n\n  // additive synth on 2nd instance of XYscope\n  xy2.clearWaves();\n  xy2.freq(mouseX); // without speakers, test really high freqs!\n  //xy2.amp(.2); // experiment with high freq, low amp modulation\n  xy2.circle(width/2, height/2, mouseY);\n  xy2.buildWaves();\n  xy2.drawPath(255, 255, 0); // custom stroke color\n}\n```\nAdditional tips:\n\n- No need to stop at just 2, add as many as needed!  \n- Ratio between freqs is crucial, diff of +/- .1 animates things. \n- Really low frequencies animate shapes over that path.  \n- Really high frequencies display shape made of 2nd shape.\n- Play with position of 2nd shape, from center to corners.\n\n## References\nXYscope is a class, so after an instance has been defined to a variable, we'll use that prefix in front of every function listed below, ie: `xy.ellipse()`. This enables us to have multiple XYscope instances running parallel, which will reveal wild and crazy audio/visuals. All examples below use `xy` as the instance prefix.\n\n- [Initialize XYscope](#initialize-xyscope)\n- [Z-axis](#z-axis)\n  * [z()](#z())\n  * [zAuto()](#zauto())\n- [Audio Interface Settings](#audio-interface-settings)\n  * [getMixerInfo()](#getmixerinfo())\n  * [sampleRate()](#samplerate())\n  * [bufferSize()](#buffersize())\n- [Waves](#waves)\n  * [resetWaves()](#resetwaves())\n  * [clearWaves()](#clearwaves())\n  * [buildWaves()](#buildwaves())\n  * [buildX()](#buildx())\n  * [buildY()](#buildy())\n  * [buildZ()](#buildz())\n  * [waveSize()](#wavesize())\n  * [setWaveforms()](#setwaveforms())\n- [Points](#points)\n  * [wavePoints()](#wavepoints())\n  * [steps()](#steps())\n  * [limitPoints()](#limitpoints())\n  * [limitPath()](#limitpath())\n- [Record Audio](#record-audio)\n  * [recorderBegin()](#recorderbegin())\n  * [recorderEnd()](#recorderend())\n- [Primitive Shapes](#primitive-shapes)\n  * [point()](#point())\n  * [line()](#line())\n  * [square()](#square())\n  * [rect()](#rect())\n  * [circle()](#circle())\n  * [ellipse()](#ellipse())\n  * [complex shape](#complex-shape)\n  * [lissajous()](#lissajous())\n  * [box()](#box())\n  * [sphere()](#sphere())\n  * [ellipsoid()](#ellipsoid())\n  * [torus()](#torus())\n- [Text](#text)\n  * [text()](#text())\n  * [textPaths()](#textpaths())\n  * [textFont()](#textfont())\n  * [textSize()](#textsize())\n  * [textLeading()](#textleading())\n  * [textAlign()](#textalign())\n  * [textWidth()](#textwidth())\n- [Modulation](#modulation)\n  * [freq()](#freq())\n  * [amp()](#amp())\n  * [pan()](#pan())\n- [Vectrex](#vectrex)\n- [Laser](#laser)\n- [Rendering](#rendering)\n- [Vars](#vars)\n\n---\n### Initialize XYscope\n```\nxy = new XYscope(this); // system audio settings\nxy = new XYscope(this, \"custom_dac\"); // custom audio card\nxy = new XYscope(this, xy_instance.outXY); // another XYscope out\nxy = new XYscope(this, sampleRate); // default sampleRate is 41000\nxy = new XYscope(this, \"custom_dac\", sampleRate); // default sampleRate is 41000\nxy = new XYscope(this, \"custom_dac\", sampleRate, bufferSize); // default bufferSize is 512\n```\n---\n### Z-axis\n#### z()\n```\nxy.z(\"custom_dac\"); // set audio out for z-axis (blanking)\nxy.z(\"custom_dac\", sampleRate); // add custom sampleRate\n```\n#### zAuto()\n```\n// somewhat deprecated.. may return\nxy.zAuto(); // get setting for using z-axis\nxy.zAuto(boolean); // set auto use of z-axis \n```\n---\n### Audio Interface Settings\n#### getMixerInfo()\n```\nxy.getMixerInfo(); // list connected audio interfaces\n```\n#### sampleRate()\n```\nxy.sampleRate(); // get current sampleRate, default 41000\nxy.sampleRate(newRate); // set new sampleRate (int)\n```\n#### bufferSize()\n```\nxy.bufferSize(); // get current bufferSize, default 512\nxy.bufferSize(newSize); // set  new bufferSize (int)\n```\n---\n### Waves\n#### resetWaves()\n```\nxy.resetWaves(); // fix any sync issues with phase of waves\n```\n#### clearWaves()\n```\nxy.clearWaves(); // clear wavetables from any previous drawing\n```\n#### buildWaves()\n```\nxy.buildWaves(); // compile drawn shapes into new wavetables/audio\nxy.buildWaves(-1); // draw in v1 style\nxy.buildWaves(-2); // draw in v2 style\nxy.buildWaves(-3); // draw in v3 style\n```\n#### buildX()\n```\nxy.buildX(newWave); // build wavetableX with float[] array\n```\n#### buildY()\n```\nxy.buildY(newWave); // build wavetableY with float[] array\n```\n#### buildZ()\n```\nxy.buildZ(newWave); // build wavetableZ with float[] array\n```\n#### waveSize()\n```\nxy.waveSize(); // get current size of wavetables\nxy.waveSize(newSize); // set new wavetable size, default is 512 \n```\n#### setWaveforms()\n```\nxy.setWaveforms(wfX, wfY, wfZ); // set wavetables with float[] arrays \n```\n---\n### Points\n#### wavePoints()\n```\nxy.wavePoints(); // get PVector ArrayList of all drawn coordinates (0.0 – 1.0)\n```\n#### steps()\n```\nxy.steps(); // get current steps between points, default 24\nxy.steps(newVal); // set step multiplier, segments, between points\n```\n#### limitPoints()\n```\nxy.limitPoints(); // get number of limited drawing points\nxy.limitPoints(newLimit); // set new limit of points for drawing\n```\n#### limitPath()\n```\nxy.limitPath(); // get current limit of drawn path\nxy.limitPath(newLimit); // avoid drawn vectors beyond border edges (px)\n```\n---\n### Record Audio\nEasily save your drawings as .wav audio files! Just use a keyPress to initiate starting and stopping the recording (see demo). Recording uses our set sampleRate.\n#### recorderBegin()\n```\nxy.recorderBegin(); // will name file \"XYscope_timestamp.wav\"\nxy.recorderBegin(\"customName\"); // set name, timestamp added\n```\n#### recorderEnd()\n```\nxy.recorderEnd(); // complete recording\n```\n---\n### Primitive Shapes\nMost primitives from Processing have been ported, so you only need to add `xy.` in front of them! They can also be used without parameters, for quickly testing.  \n\n#### point()\n```\nxy.point();\nxy.point(x, y);\nxy.point(x, y, z);\n```\n#### line()\n```\nxy.line();\nxy.line(x1, y1, x2, y2);\nxy.line(x1, y1, z1, x2, y2, z2);\n```\n#### square()\n```\nxy.rectMode(); // default CORNER, CENTER to draw center out\n\nxy.square();\nxy.square(x, y, w);\n```\n#### rect()\n```\nxy.rectMode(); // default CORNER, CENTER to draw center out\n\nxy.rect();\nxy.rect(x, y, w);\nxy.rect(x, y, w, h);\n```\n#### circle()\n```\nxy.ellipseDetail(); // get current facets of ellipse\nxy.ellipseDetail(newVal); // set new count of facets, default 30\n\nxy.circle();\nxy.circle(x, y, w);\n```\n#### ellipse()\n```\nxy.ellipseDetail(); // get current facets of ellipse\nxy.ellipseDetail(newVal); // set new count of facets, default 30\n\nxy.ellipse();\nxy.ellipse(x, y, w);\nxy.ellipse(x, y, w, h);\n```\n#### complex shape\n```\nxy.beginShape();\n\nxy.vertex();\nxy.vertex(x, y);\nxy.vertex(x, y, z); // if in P3D mode\n// ...\n\nxy.endShape();\nxy.endShape(CLOSE); // closes form\n```\n#### lissajous()\n```\nxy.lissajous();\nxy.lissajous(x, y, radius, ratioA, ratioB, phase, resolution);\n```\n#### box()\n```\nxy.box();\nxy.box(size);\nxy.box(rx, ryz);\nxy.box(rx, ry, rz);\n```\n#### sphere()\n```\nxy.sphere();\nxy.sphere(size);\nxy.sphere(size, detailXY); // default 24\nxy.sphere(size, detailX, detailY); // default 24, 24\n```\n#### ellipsoid()\n```\nxy.ellipsoid();\nxy.ellipsoid(rx, ry, rz);\nxy.ellipsoid(rx, ry, rz, detailXY); // default 24\nxy.ellipsoid(rx, ry, rz, detailX, detailY); // default 24, 24\n```\n#### torus()\n```\nxy.torus();\nxy.torus(radius, tubeRadius);\nxy.torus(radius, tubeRadius, detailXY); // default 24\nxy.torus(radius, tubeRadius, detailX, detailY); // default 24, 24\n```\n---\n### Text\n#### text()\n```\nxy.text(); // paramless text drawing\nxy.text(\"string\", x, y); // use '\\n' for multi-line text\n```\n#### textPaths()\nUse coordinates of Hershey text for manipulating type!\n\n```\nxy.textPaths(\"string\", x, y); // return 2D Array of PVector coords\n```\n#### textFont()\n```\nprintln(xy.fonts); // print list of avilable Hershey fonts\nxy.textFont(\"fontname\"); // set active Hershey font\n```\n#### textSize()\n```\nxy.textSize(); // get current textSize\nxy.textSize(newSize); // set new textSize\n```\n#### textLeading()\n```\nxy.textLeading(); // get current textLeading\nxy.textLeading(newSize); // set new textLeading\n```\n#### textAlign()\n```\nxy.textAlign(hAlign); // Horz: LEFT (default) / CENTER / RIGHT\nxy.textAlign(hAlign, vAlign); // Vert options: TOP / CENTER / BOTTOM\n```\n#### textWidth()\n```\nxy.textWidth(int); // from 32 characters, get width of char\nxy.textWidth(\"string\"); // get width of text for positioning\n```\n---\n### Modulation\n#### freq()\nFrequency of oscillators\n\n```\n// get\nxy.freq(); // get PVector (.x, .y, .z) of freqs\nxy.freq().x;   // get frequency of x oscillator \n\n// set\nxy.freq(freqXY);  // default is 50.0 \nxy.freq(freqX, freqY); // set x, y frequencies \nxy.freq(freqX, freqY, freqZ); // set x, y, z frequencies \nxy.freq(PVector freqXYZ); // set as PVector \n \nxy.resetWaves(); // occasionally needed if they slip out of phase \n```\n#### amp()\nAmplitude of oscillators\n\n```\n// get\nxy.amp();   // get PVector (.x, .y, .z) of amps\nxy.amp().x; // get amplitude of x oscillator\n\n// set\nxy.amp(ampXY); // default is 1.0 \nxy.amp(ampX, ampY); // set x, y to specific amplitudes \nxy.amp(ampX, ampY, ampZ); // set x, y, z to specific amplitudes \nxy.amp(PVector ampXYZ); // set as PVector\n```\n#### pan()\nOptionally swap the hard pan of XY rather than physical cables\n\n```\nxy.pan(leftPan, rightPan); // default is -1.0, 1.0\n```\n---\n### Vectrex\n```\nxy.vectrex(0); // 0 for portrait, -90/90 for landscape orientation\nxy.vectrex(vw, vh, vamp, vrot); // custom width, height, amp, rotation\n\nxy.vectrexRatio(); // get current ratio\nxy.vectrexRatio(newRatio); // set new ratio, default is .82\n```\n---\n### Laser\n```\nxy.laser(\"dac_Red\", \"dac_GreenBlue\"); // custom 2ch pairs for R, GB\n\n// be cautious of laser galvos, can help smooth graphics\nxy.laserLPF(); // get value of laser's LowPassFilter\nxy.laserLPF(newLPF); // set mew value of LowPassFilter (0.1 – 20000.0)\n\n// avoid dangerous hotspot, only draw laser if shape is big enough\nxy.spotKiller(); // get current min size of drawing for laser\nxy.spotKiller(newVal); // set min drawing size to prevent laser hotspot\n\nxy.stroke(r, g, b); // set RGB laser stroke color\nxy.stroke(PVector rgb); // set as PVector\n\nxy.strokeFreq(); // get laser RGB channel frequencies\nxy.strokeFreq(freqRGB); // set laser RGB freq as group\nxy.strokeFreq(freqR, freqG, freqB); // set laser R, G, B separately\nxy.strokeFreq(PVector freqRGB); // set as PVector\n\n// some lasers ignore values below __ when setting colors, \n// use this to set lowest point when color mixing\nxy.strokeMin(); // get curret min RGB values for laser stroke\nxy.strokeMin(minR, minG, minB); // set new min values (0.0 - 255.0)\nxy.strokeMin(PVector minPV); // set new min as PVector\n\nxy.strokeWB(wbR, wbG, wbB); // set values needed for white light\nxy.strokeWB(PVector wbRGB); // set values as PVector\n\nxy.strokeDash(); // get current dash setting\nxy.strokeDash(sdRGB); // set laser dash count for RGB strokes\nxy.strokeDash(sdR, sdG, sdB); // set dashes per R, G, B\nxy.strokeDash(PVector sdRGB); // set as PVector\n```\n---\n### Rendering\n```\nxy.drawAll(); // all views below \nxy.drawPath(); // shapes being drawn \nxy.drawPoints(); // points along paths of drawing \nxy.drawWaveform(); // drawing as oscillator's waveform \nxy.drawWave(); // waveform over time at frequency \nxy.drawXY(); // simulated xy-mode oscilloscope \n\nxy.debugView(); // check if debugView active\nxy.debugView(boolean); // compare drawXY() to drawWaveform()\n```\n### Vars\nLastly, many variables within XYscope are public for your vector hacking needs.\n\n```\nshapes; // XYShapeList, processed by buildWaves()\nminim, minimZ; // instances of Minim\nrecorder; // instance for audio recording\noutXY, outZ; // AudioOutput (used to patch for additive-synth)\nsumXY, sumZ; // Summer for patching filters\nwaveX, waveY, waveZ; // Oscil for each XYZ oscillators\ntableX, tableY, tableZ; // XYWavetable, applied to oscillator\nshapeX, shapeY, shapeZ; // float[] used for wavetables\nfonts; // list of Hershey fonts\n\nminimR, minimBG; // instances of Minim\nwaveR, waveG, waveB; // Oscil for each RGB oscillators\ntableR, tableG, tableB; // XYWavetable, applied to oscillator\nshapeR, shapeG, shapeB; // float[] used for wavetables\n\n```\n\n## Extras \n### Contributing\nFound a bug, missing feature, and/or created a project with XYscope? Let me know!  \nCreate an [issue on GitHub](https://github.com/ffd8/xyscope/issues).\n\n### License\n\nThis project is licensed under the LGPL License - see [LICENSE.md](https://github.com/ffd8/xyscope/blob/master/license.txt) for details.\n\n### Shoutouts\n\n* [Just Van Rossum](http://dailydrawbot.tumblr.com), the enlightening conversation on my X-Y attempts.\n* [Stefanie Bräuer](https://stefaniebraeuer.ch/), feeding the obsession with crucial theory + context.\n* [Hansi Raber](https://asdfg.me), java meta insights + finding external WaveTable bug!\n* [Processing Library Template](https://github.com/processing/processing-library-template)"
  },
  {
    "path": "changelog.txt",
    "content": "+ new\n* changed\n- removed\n\n////////////////////////////////////////////////////////////\nXYScope 3.0.0 (REV 5) - 21.02.2023\n\n+ NEWER & IMPROVEDER buildWaves() technique (added -3, to those who like previous)\n+ Added Hershey Fonts (single line) w/ many functions (textFont, textSize, textAlign, textPaths, textWidth, textLeading, multi-line text, ...)\n+ Added circle() + square() to match p5/Processing\n+ Added steps() for multiplier of segments between points\n+ resetWaves() alias of waveReset(), to keep style of clearWaves() and buildWaves()\n+ XYZ WaveTables are public to grab values for other usage\n+ pan(left, right) to swap pans of channels incase cables physically swapped\n+ box(), sphere(), ellipsoid(), torus() 3D primitives added\n+ lissajous(), to draw such forms\n+ primitives can be called without params for quick test, ie xy.circle()\n+ audio recorder, to save visuals as .wav!\n+ lots of new demos, sharing tricks/tips from past years playing with it\n+ newly designed references as tutorial website\n\n* ellipse() + rect() now work with 3 params like Processing\n* ellipse()/circle() fixed connecting point\n* point() fixed \n* more functions accept float for random/sin play\n* limitPath() improved to only draw necessary vector points\n* endShape() now has CLOSE\n* drawXY(), drawPath(), drawAudio(), accept r,g,b values for custom coloring\n* ellipseDetail() abs() for negative numbers\n\n\n////////////////////////////////////////////////////////////\nXYScope 2.2.0 (REV 4) - 20.12.2018\n\n+ NEW & IMPROVED buildWaves() technique (added -2, to those who like previous)\n+ Fixed Minim WaveTable bug that caused crash since REV 2 (thanks Hansi Raber!)\n+ limitPath() only draw if shape within border from edges\n+ Laser: RGB Laser compatibility (using 2x stereo audio pairs for RGB control)\n+ Laser: stroke() to control RGB color\n+ Laser: strokeFreq() to control RGB frequencies\n+ Laser: strokeDash() to add dashes to RGB waves\n+ Laser: spotKiller() to only draw if image bigger than spotKiller() size (default 20)\n+ Laser: laserLPF() default low-pass-filter is 10k hz, use laserLPF() to customize\n+ Examples: added xtra_obj + xtra_video + xtra_laser\n\n* minor vertex() code changes\n* minor changes to examples\n\n\n////////////////////////////////////////////////////////////\nXYScope 2.1.0 (REV 3) - 25.07.2018\n\n+ Vectrex compatibility (custom aspect ratio and +/- 90° rotation)\n+ Vectrex example\n\n* BuildWaves + drawXY to work with Vectrex mode\n\n\n////////////////////////////////////////////////////////////\nXYScope 2.0.0 (REV 2) - 08.05.2018\n\nAdded/dropped/refactored enough features to need 2.0\n\n+ Brand new drawing to waveform technique (smoother)! Prefer old way? use buildWaves(-1)\n  If points matches buffer (1024), previously technique is used to maintain speed.\n+ debugView(), for seeing drawWaveform() to drawXY() relationship (thanks Hansi3D!)\n+ waveSize() function for dynamically changing wavetable size\n+ XYscope instance for custom sample rates (41000, 48000, 96000, 192000, etc)\n+ waveReset() now public incase time-step of oscillators slip when changing freq()\n\n* Technique for adding z-axis, \n* Refactored audio out, so minim filters (lowpass) can be used, see xtra_filters example \n* Minor bugs in drawWaveform() rendering\n* Drawings collect as a shapesList vs arrayList, clever suggestion by Hansi3D\n* Stopped performing waveReset() on freq change, which preventing nice beam walk-around\n\n- XYscope instance with z-axis, now has dedicated function \n- XYscope instance with int for mixerID, can change too often, select mixer by name\n- addPoint(), just use point()\n- wavePoints(set), new structure doesn't allow, use custom buildWaves()\n- sortPoints(), was a rough test, do within sketch instead\n- freqX(), freqY(), freqZ(), ampX(), ampY(), ampZ()\n  now combined into freq(), amp() for 1 + optional 2, 3 values\n\n////////////////////////////////////////////////////////////\nXYScope 1.0.2 (REV 1) - 11.09.2017\n\n+ audioMix as instance output, enabling additive-synthesis\n\n\n////////////////////////////////////////////////////////////\nXYScope 1.0.1 (REV 1) - 91.08.2017\n\n+ xtra_syphon example, similar to webcam, load syphon textures\n\n\n////////////////////////////////////////////////////////////\nXYScope 1.0.0 (REV 1) - 21.06.2017\n\nXYscope enters the world! \n\n+ Everything\n\n"
  },
  {
    "path": "examples/1_getting_started/basic_drawing/basic_drawing.pde",
    "content": "/* \n basic_drawing\n Draw to the scope by throwing more and more lines, clearing on demand\n cc teddavis.org 2017-23\n */\n\nimport ddf.minim.*; // minim req to gen audio\nimport xyscope.*;   // import XYscope\nXYscope xy;         // create XYscope instance\n\nvoid setup() {\n  size(512, 512);\n\n  xy = new XYscope(this); // define XYscope instance\n  //xy.getMixerInfo(); // lists all audio devices\n}\n\n\nvoid draw() {\n  background(0);\n\n  \n  xy.buildWaves(); // build audio from shapes\n  xy.drawAll(); // draw all analytics\n}\n\nvoid mouseDragged() {\n  // add point based on width/height\n  xy.line(mouseX, mouseY, pmouseX, pmouseY);\n}\n\nvoid keyPressed() {\n  if (keyCode == 8) { // DELETE\n    xy.clearWaves(); // clear waves similar to background\n  }\n}\n"
  },
  {
    "path": "examples/1_getting_started/calibration/calibration.pde",
    "content": "/*\n calibration\n circle + square + lines to help center/adjust oscilloscpe display\n cc teddavis.org 2023\n */\n\nimport ddf.minim.*; // minim req to gen audio\nimport xyscope.*;   // import XYscope\nXYscope xy;         // create XYscope instance\n\nvoid setup() {\n  size(512, 512);\n\n  xy = new XYscope(this); // define XYscope instance\n  //xy.getMixerInfo(); // lists all audio devices\n}\n\nvoid draw() {\n  background(0);\n  xy.clearWaves(); // clear waves similar to background\n\n  pushMatrix();\n  translate(width/2, height/2);\n  pushMatrix();\n  rotate(radians(180));\n  xy.circle(0, 0, width);\n  popMatrix();\n  xy.rectMode(CENTER);\n  xy.rect(0, 0, width);\n  xy.line(-width/2, 0, width/2, 0);\n  xy.line(0, height/2, 0, -height/2);\n  popMatrix();\n\n  xy.buildWaves(); // build audio from shapes\n  xy.drawAll(); // draw all analytics\n}\n"
  },
  {
    "path": "examples/1_getting_started/clock/clock.pde",
    "content": "/*\n clock\n cc teddavis.org 2018-23\n */\n\nimport ddf.minim.*; // minim req to gen audio\nimport xyscope.*;   // import XYscope\nXYscope xy;         // create XYscope instance\n\nfloat sec = 0;\n\nvoid setup() {\n  size(512, 512);\n\n  xy = new XYscope(this); // define XYscope instance\n  //xy.getMixerInfo(); // lists all audio devices\n  \n  sec = map(second(), 0, 60, 0, 360);\n}\n\nvoid draw() {\n  background(0);\n  xy.freq(second());\n  //xy.freq(sec%360/6); // smooth ramp\n  xy.ellipseDetail(24);\n\n  // clear waves like refreshing background\n  xy.clearWaves();\n\n  // draw clock face\n  xy.ellipse(width/2, height/2, width, width);\n  pushMatrix();\n  translate(width/2, height/2);\n  for (int i=0; i < 12; i++) {\n    rotate(radians(360/12));\n    xy.line(0, height*.49, 0, height*.4);\n  }\n  popMatrix();\n\n  //second\n  float s = map(second(), 0, 60, 0, 360);\n  sec += .1;//24/millis()/1000;\n  pushMatrix();\n  translate(width/2, height/2);\n  rotate(radians(s)); // sec for smooth\n  xy.line(0, -height*.35, 0, 0);\n  popMatrix();\n\n  //minute\n  float m = map(minute(), 0, 60, 0, 360);\n  pushMatrix();\n  translate(width/2, height/2);\n  rotate(radians(m));\n  xy.line(0, -height*.25, 0, 0);\n  popMatrix();\n\n  //hour\n  float h = map(hour(), 0, 24, 0, 360);\n  pushMatrix();\n  translate(width/2, height/2);\n  rotate(radians(h));\n  xy.line(0, -height*.15, 0, 0);\n  popMatrix();\n\n  // build audio from shapes\n  xy.buildWaves();\n\n  // draw all analytics\n  xy.drawAll();\n}\n"
  },
  {
    "path": "examples/1_getting_started/custom_soundcard/custom_soundcard.pde",
    "content": "/*\n custom_soundcard\n Most AC-Coupled DACs will wiggle towards the center.\n For 'better' results, find a DC-coupled DAC.\n http://www.expert-sleepers.co.uk/siwacompatibility.html\n \n + cheapo $20 solution:\n https://www.delock.com/produkt/61645/merkmale.html\n https://www.youtube.com/live/VjRTLviVBxo?feature=share&t=2491\n \n cc teddavis.org 2017-23\n */\n\nimport ddf.minim.*; // minim req to gen audio\nimport xyscope.*;   // import XYscope\nXYscope xy;         // create XYscope instance\n\nvoid setup() {\n  size(512, 512);\n\n  xy = new XYscope(this); // init XYscope with default sound out\n  xy.getMixerInfo(); // list available sound cards\n\n  //xy = new XYscope(this, 0); // select by port\n  //xy = new XYscope(this, \"BlackHole 2ch\"); // select by name\n  //xy = new XYscope(this, \"BlackHole 2ch\", 48000); // custom card + sampeRate\n}\n\nvoid draw() {\n  background(0);\n  xy.clearWaves(); // clear waves like refreshing background\n\n  xy.rectMode(CENTER);\n  xy.rect(mouseX, mouseY, width/4, width/4);\n\n  xy.buildWaves(); // build audio from shapes\n  xy.drawAll(); // draw all analytics\n}\n"
  },
  {
    "path": "examples/1_getting_started/lissapong/lissapong.pde",
    "content": "/*\n lissapong\n Let the epic crt battle over lissajous begin!\n mouseY, move user paddle\n cc teddavis.org 2023\n */\n\nimport ddf.minim.*; // minim req to gen audio\nimport xyscope.*;   // import XYscope\nXYscope xy;         // create XYscope instance\n\nboolean autoPlay = true, gameover = true, initScore = false, demoMode = true;\nPVector c, p;\nfloat cx = 1, cy = 2, s = 30, f = 10.5, paddleW = 10, paddleH = 150;\nint gameTimer = 0, score1 = 0, score2 = 0, winner = 0;\n\nvoid setup() {\n  size(512, 512);\n\n  xy = new XYscope(this);\n  xy.rectMode(CENTER);\n  \n  pongReset();\n}\n\nvoid draw() {\n  background(0);\n  xy.clearWaves();\n\n  // render scores\n  float s1 = 25;\n  float s2 = 25;\n  if (winner == -1) {\n    s1 += sin(frameCount*.5)*20;\n  } else if (winner == 1) {\n    s2 += sin(frameCount*.5)*20;\n  }\n\n  xy.textSize(s1);\n  xy.text(score1, 50, 50);\n  xy.textSize(s2);\n  xy.text(score2, width-50, 50);\n\n  // gameplay vs gameover\n  if (!gameover) {\n    pongUpdate();\n    xy.lissajous(c.x, c.y, s, cx, cy, frameCount, 60);\n\n    float ly = c.y + (noise(frameCount*.023)*height-c.y)*c.x/width;\n    xy.rect(paddleW/2, ly, paddleW, paddleH);\n\n    float ry = c.y + (noise(frameCount*.02)*height-c.y)*(1 - c.x/width);\n    if (!autoPlay) {\n      ry = p.y;\n    }\n    xy.rect(width-paddleW/2, ry, paddleW, paddleH);\n\n    if (frameCount%30==0 && autoPlay) {\n      demoMode = !demoMode;\n    }\n    if (demoMode && autoPlay) {\n      xy.textSize(15);\n      xy.textAlign(CENTER, CENTER);\n      xy.text(\"CLICK TO PLAY\", width/2, 50);\n    }\n  } else {\n    pushMatrix();\n    xy.textSize(40 + sin(frameCount*.05)*20);\n    xy.textAlign(CENTER, CENTER);\n    translate(width/2, height/2);\n    rotate(radians(cos(frameCount*.01)*15));\n    xy.text(\"GAME OVER\", 0, 0);\n    popMatrix();\n  }\n\n  xy.buildWaves();\n\n  xy.drawXY();\n  xy.drawWaveform();\n}\n\nvoid mousePressed() {\n  if (gameover) {\n    pongReset();\n  }\n\n  if (autoPlay) {\n    autoPlay = false;\n    pongReset();\n  }\n}\n\nvoid pongReset() {\n  gameover = false;\n  c = new PVector(width/2, height/2);\n  p = new PVector(0, c.y);\n  cx = 1;\n  cy = 2;\n  xy.freq(cx * cy * 10);\n  f = 10.5;\n  gameTimer = 0;\n  score1 = 0;\n  score2 = 0;\n  winner = 0;\n  paddleH = 150;\n  demoMode = true;\n}\n\nvoid pongUpdate() {\n  p.y = mouseY;\n  c.x += cx;\n  c.y += cy;\n\n  if (!autoPlay && (abs(c.y-p.y) > (paddleH/2 + s) && c.x >= (width-paddleW/2-s)) ) {\n    println(abs(c.y - p.y) + \" / \"+  (paddleH/2 + s));\n    gameover = true;\n    if (score1 >= score2) {\n      winner = -1;\n    } else {\n      winner = 1;\n    }\n    xy.freq(random(4, 20));\n    xy.resetWaves();\n  }\n\n  if ((c.x > (width-paddleW/2-s) || c.x < (s-paddleW))) {\n    changeX();\n    changeY();\n    xy.freq(cx*cy);\n    xy.resetWaves();\n    cx *= -1;\n    initScore = true;\n    if (paddleH > paddleW) {\n      paddleH--;\n    }\n  }\n\n  if (c.y > height-s/2 || c.y < s/2) {\n    cy *= -1;\n  }\n  if (cy == height-s/2) {\n    cy -= s/2;\n  } else if (cy == s/2) {\n    cy += s/2;\n  }\n\n\n  gameTimer++;\n}\n\nvoid changeX() {\n  float fx = ceil(random(1, 10));\n  if (c.x < s)\n    fx *=-1;\n\n  if (fx > 0) {\n    score2++;\n  } else {\n    score1++;\n  }\n  cx = fx;\n}\n\nvoid changeY() {\n  float fy = ceil(random(1, 10));\n  if (!autoPlay && c.x > width/2) {\n    fy = abs(c.y - p.y)/10;\n  }\n  if (c.y > height-s/2) {\n    fy *=-1;\n  }\n  cy = fy;\n}\n"
  },
  {
    "path": "examples/1_getting_started/template/template.pde",
    "content": "/*\n  template\n  Recommended template for Processing when working with XYscope often.\n  Save into ~/Documents/Processing/templates/Java/ (as `sketch.pde`)\n  cc teddavis.org 2023\n*/\n\nimport ddf.minim.*; // minim req to gen audio\nimport xyscope.*;   // import XYscope\nXYscope xy;         // create XYscope instance\n \nvoid setup(){ \n  size(512, 512); // window size, optionally add P3D\n \n  xy = new XYscope(this, \"\"); // define XYscope instance, \"custom_dac\" optional\n  xy.getMixerInfo(); // lists all audio devices\n} \n \nvoid draw(){ \n  background(0); \n  xy.clearWaves();  // clear waves from previous drawing\n  \n  // draw shapes here\n  xy.circle(width/2, height/2, width); // it all began with a circle....\n  \n  xy.buildWaves(); // build shapes to audio waves\n  xy.drawWaveform();\n  xy.drawXY();\n} \n"
  },
  {
    "path": "examples/2_shapes/a_walkthrough_primatives/a_walkthrough_primatives.pde",
    "content": "/*\n a_walkthrough_primatives\n demo-run of available shapes\n cc teddavis.org 2023\n */\n\nimport ddf.minim.*; // minim req to gen audio\nimport xyscope.*;   // import XYscope\nXYscope xy;         // create XYscope instance\n\nint shapeSel = 0;\n\nvoid setup() {\n  size(512, 512, P3D);\n\n  // initialize XYscope with default sound out\n  xy = new XYscope(this);\n}\n\nvoid draw() {\n  background(0);\n  xy.limitPath(0); // limit drawing any points outside of view\n  xy.steps(100); // interpolated points between points\n\n  // clear waves like refreshing background\n  xy.clearWaves();\n\n  if (frameCount%240==0) {\n    shapeSel++;\n  }\n\n  pushMatrix();\n  translate(width/2, height/2);\n \n  // use most primative shapes with class instance infront\n  switch(shapeSel%10) {\n  case 0:\n    xy.freq(1);\n    xy.point(random(-width/2, width/2), random(-height/2, height/2));\n    break;\n  case 1:\n    xy.freq(12);\n    for (int i=0; i<4; i++) {\n      pushMatrix();\n      rotate(radians(frameCount*i*.2));\n      float lineLen = sin(frameCount*.01 + i*1)*width/2;\n      xy.line(0, -lineLen, 0, lineLen);\n      popMatrix();\n    }\n    break;\n  case 2:\n    float efreq = frameCount*.1%30;//sin(frameCount*.005)*30;\n    xy.freq(25);\n    for (int i=0; i < 3; i++) {\n      pushMatrix();\n      rotate(radians(i*120+frameCount*.1));\n      translate(width/4, 0);\n      xy.ellipseDetail(noise(i*2+frameCount*.01)*12);\n      xy.ellipse(0, 0, width/4);\n      popMatrix();\n    }\n  break;\ncase 3:\n  xy.freq(50);\n  xy.rectMode(CENTER);\n  pushMatrix();\n  rotateY(radians(frameCount));\n  xy.rect(0, 0, width/2, height/2);\n  rotateX(radians(frameCount));\n  xy.rect(0, 0, width/3, height/3);\n  popMatrix();\n  break;\ncase 4:\n  xy.freq(75);\n  //randomSeed(1);\n  pushMatrix();\n  rotateY(radians(frameCount/3));\n  xy.beginShape();\n  for (int i=0; i < 10; i++) {\n    float d = width/2;\n    xy.vertex(noise(i*5+frameCount*.001)*d*2 - d, noise(i*20+frameCount*.003)*d*2 - d, noise(i*10+frameCount*.002)*d*2 - d);\n  }\n  xy.endShape(CLOSE);\n  popMatrix();\n  break;\ncase 5:\n  xy.freq(50);\n  xy.lissajous(0, 0, height/2, 1, 2, frameCount, 180);\n  break;\ncase 6:\n  xy.freq(50);\n  pushMatrix();\n  rotateY(radians(frameCount));\n  rotateX(radians(frameCount/2));\n  float bsize = width/2;\n  xy.box(sin(frameCount*.01)*bsize, bsize, cos(frameCount*.01)*bsize);\n  popMatrix();\n  break;\ncase 7:\n  pushMatrix();\n  rotateY(radians(frameCount));\n  xy.sphere(width/2.5, floor(noise(frameCount*.01)*24), floor(noise(10+frameCount*.011)*24));\n  popMatrix();\n  break;\ncase 8:\n  pushMatrix();\n  rotateY(radians(frameCount));\n  xy.torus(width/4, width/6, floor(noise(frameCount*.01)*12), floor(noise(10+frameCount*.011)*24));\n  popMatrix();\n  break;\ncase 9:\n  xy.freq(8);\n  xy.textAlign(CENTER, CENTER);\n  xy.textSize(50 + sin(frameCount*.025)*25);\n  pushMatrix();\n  xy.text(\"XYscope\", 0, sin(frameCount*.05)*height/3);\n  popMatrix();\n  break;\n}\npopMatrix();\n\n// build audio from shapes\nxy.buildWaves();\n\n// draw analytics\nxy.drawWaveform();\nxy.drawXY();\n}\n\nvoid keyPressed() {\n  if (key == ' ') {\n    shapeSel++;\n  }\n}\n"
  },
  {
    "path": "examples/2_shapes/additive_synth_shapes/additive_synth_shapes.pde",
    "content": "/*\n  additive-synth shapes\n  an amazing aspect of vector graphics, \n  is sending multiple shapes on the same audio = additive synth!\n  create wild forms, animation, effects, through various freq/amp play.\n  cc teddavis.org 2023\n */\n\nimport ddf.minim.*; // minim req to gen audio\nimport xyscope.*;   // import XYscope\nXYscope xy, xy2;         // create 2x XYscope instances\n\nvoid setup() {\n  size(512, 512); // declares size of output window\n\n  xy = new XYscope(this);\n  xy2 = new XYscope(this, xy.outXY); // patch 2nd instance to 1st output\n  //xy.getMixerInfo(); // lists all audio devices\n}\n\nvoid draw() {\n  background(0);\n  xy.clearWaves();\n  xy.circle(width/2, height/2, width/2);\n  xy.buildWaves();\n  xy.drawPath(0, 255, 255); // custom stroke color\n  xy.drawXY();\n\n  // additive synth on 2nd instance of XYscope\n  xy2.clearWaves();\n  xy2.freq(mouseX); // without speakers, test really high freqs!\n  //xy2.amp(.2); // experiment with high freq, low amp modulation\n  //xy2.circle(width/2, height/2, mouseY);\n  xy2.circle(mouseX, mouseY, 50);\n  //xy2.line(width/2, height/2, mouseX, mouseY);\n  //xy2.lissajous(width/2, height/2, mouseY/2, 1, 2, frameCount, 180);\n  xy2.buildWaves();\n  xy2.drawPath(255, 255, 0); // custom stroke color\n}\n"
  },
  {
    "path": "examples/2_shapes/lissajous/lissajous.pde",
    "content": "/*\n shapes_lissajous\n Sometimes you just want some lissajous curves!\n cc teddavis.org 2023\n */\n\nimport ddf.minim.*; // minim req to gen audio\nimport xyscope.*;   // import XYscope\nXYscope xy;         // create XYscope instance\n\nvoid setup() {\n  size(512, 512);\n  background(0);\n\n  // initialize custom local minim\n  xy = new XYscope(this);\n}\n\n\nvoid draw() {\n  background(0);\n  xy.clearWaves(); // clear waves\n  \n  int res = 180;\n  if(mousePressed){\n    res = mouseX/2;\n    println(\"resolution: \" + res);\n  }\n\n  // xy.lissajous(xPos, yPos, radius, ratioA, ratioB, phase, resolution);\n  xy.lissajous(width*.25, height*.25, height/4.5, 1, 1, frameCount, res);\n  xy.lissajous(width*.75, height*.25, height/4.5, 1, 2, frameCount, res);\n  xy.lissajous(width*.25, height*.75, height/4.5, 1, 3, frameCount, res);\n  xy.lissajous(width*.75, height*.75, height/4.5, 1, 4, frameCount, res);\n \n  xy.buildWaves(); // build waves\n  xy.drawAll(); // draw analytics\n\n}\n"
  },
  {
    "path": "examples/2_shapes/paramless_shapes/paramless_shapes.pde",
    "content": "/*\n paramless shapes\n incase you're quickly testing/sketching,\n you can create primatives without values\n cc teddavis.org 2023\n */\n\nimport xyscope.*;\nXYscope xy;\nimport ddf.minim.*;\n\nvoid setup() {\n  size(512, 512, P3D); // declares size of output window\n\n  xy = new XYscope(this);\n  //xy.getMixerInfo(); // lists all audio devices\n}\n\nvoid draw() {\n  background(0);\n  if (frameCount%60==0) {\n    xy.freq(random(-100, 100));\n    xy.resetWaves(); // helps if slipping out of phase from freq changes\n  }\n  xy.clearWaves();\n\n  switch(floor(frameCount*.1)%10) {\n  case 0:\n    xy.line();\n    break;\n  case 1:\n    xy.rectMode(CENTER);\n    xy.rect();\n    break;\n  case 2:\n    xy.square();\n    break;\n  case 3:\n    xy.circle();\n    break;\n  case 4:\n    xy.ellipse();\n    break;\n  case 5:\n    xy.lissajous();\n    break;\n  case 6:\n    xy.textAlign(CENTER, CENTER);\n    xy.textSize(80);\n    xy.text();\n    break;\n  case 7:\n    pushMatrix();\n    translate(width/2, height/2);\n    xy.box();\n    popMatrix();\n    break;\n  case 8:\n    pushMatrix();\n    translate(width/2, height/2);\n    xy.sphere();\n    popMatrix();\n    break;\n  case 9:\n    pushMatrix();\n    translate(width/2, height/2);\n    xy.torus();\n    popMatrix();\n    break;\n  }\n\n  xy.buildWaves();\n  xy.drawXY();\n}\n"
  },
  {
    "path": "examples/2_shapes/sphere/sphere.pde",
    "content": "/*\n  sphere\n  mouseX/Y, adjust detailX, detailY\n  SHIFT + mouseDragged, freq based on distance from initial click\n*/\n\nimport ddf.minim.*; // minim req to gen audio\nimport xyscope.*;   // import XYscope\nXYscope xy;         // create XYscope instance\n\nint pmx = 0, pmy = 0;\n\nvoid setup() {\n  size(512, 512, P3D); // declares size of output window\n\n  xy = new XYscope(this);\n  //xy.getMixerInfo(); // lists all audio devices\n}\n\nvoid draw() {\n  background(0);\n  xy.clearWaves();\n  //xy.resetWaves();\n  if (keyPressed && keyCode == 16) { // SHIFT\n    float freq = dist(mouseX, mouseY, pmx, pmy);\n    xy.freq(freq);\n  }\n\n  // draw shapes here\n  pushMatrix();\n  translate(width/2, height/2);\n  rotateY(radians(frameCount/3));\n  rotateX(radians(frameCount/2));\n  xy.sphere(width*.4, mouseX/5, mouseY/5); // xy.sphere(size, xVert, yVert)\n  popMatrix();\n\n  xy.buildWaves();\n  xy.drawXY();\n}\n\nvoid mousePressed() {\n  pmx = mouseX;\n  pmy = mouseY;\n}\n"
  },
  {
    "path": "examples/2_shapes/torus/torus.pde",
    "content": "/*\n shape_torus\n Based upon Ira Greenbergs Toroid example\n https://processing.org/examples/toroid.html\n - mouseX » adjust dx\n - mouseY » adjust dy\n cc teddavis.org 2023\n */\n\nimport ddf.minim.*; // minim req to gen audio\nimport xyscope.*;   // import XYscope\nXYscope xy;         // create XYscope instance\n\n// initial detailX, detailY values\nint dx = 12, dy = 12;\n\nvoid setup() {\n  size(512, 512, P3D);\n\n  // initialize XYscope with default sound out\n  xy = new XYscope(this);\n}\n\nvoid draw() {\n  background(0);\n  xy.clearWaves(); // clear waves\n\n  //center and spin toroid\n  pushMatrix();\n  translate(width/2, height/2, -100);\n  rotateX(radians(frameCount/2));\n  rotateY(radians(frameCount));\n  rotateZ(radians(frameCount/3));\n\n  if (mousePressed) {\n    dx = (int)map(mouseX, 0, width, 1, 50);\n    dy = (int)map(mouseY, 0, height, 1, 50);\n  }\n\n  xy.torus(height/3, height/6, dx, dy);\n  popMatrix();\n\n  xy.buildWaves(); // build audio from shapes\n  xy.drawAll(); // draw all analytics\n}\n"
  },
  {
    "path": "examples/3_typography/hershey_fonts/hershey_fonts.pde",
    "content": "/*\n text_hershey_fonts\n draw single-line text with hershey fonts!\n lots of functions to learn from below\n big thanks for hershey font lib: https://github.com/ixd-hof/HersheyFont\n cc teddavis.org 2023\n */\n\nimport ddf.minim.*; // minim req to gen audio\nimport xyscope.*;   // import XYscope\nXYscope xy;         // create XYscope instance\n\nString txt = \"XYscope\";\n\nvoid setup() {\n  size(512, 512, P3D);\n  xy = new XYscope(this);\n\n  // list all available fonts\n  println(\"Available Hershey Fonts:\\n\" + join(xy.fonts, \", \")); // list available fonts\n\n  // set random font\n  //xy.textFont(xy.fonts[floor(random(xy.fonts.length))]);\n}\n\nvoid draw() {\n  background(0);\n  xy.clearWaves();\n  xy.steps(50); // set segment multiplier\n\n  // draw text\n  xy.textSize(50);\n  xy.textAlign(CENTER, CENTER);\n  xy.text(txt, width/2, height*.25);\n\n  // draw text per character\n  float xoff = -xy.textWidth(txt)/2.5;\n  for (int i=0; i<txt.length(); i++) {\n    push();\n    translate(width/2+xoff, height*.5);\n    rotateX(radians(i*15+frameCount));\n    xy.text(txt.charAt(i)+\"\", 0, 0);\n    pop();\n    xoff += xy.textWidth(txt.charAt(i));\n  }\n\n\n  // get and draw paths of text\n  PVector[][] textPaths = xy.textPaths(txt, width/2, floor(height*.75));\n  for (int i = 0; i < textPaths.length; i++) {\n    xy.beginShape();\n    for (int j = 0; j < textPaths[i].length; j++) {\n      float offset = 10;\n      xy.vertex(textPaths[i][j].x+sin(j*.2+frameCount*.05)*offset, textPaths[i][j].y);\n    }\n    xy.endShape();\n  }\n\n  xy.buildWaves();\n\n  xy.drawWaveform(); // wavetable\n  xy.drawXY(); // scope viewer\n}\n"
  },
  {
    "path": "examples/3_typography/scopewriter/scopewriter.pde",
    "content": "/*\n scopewriter\n use your oscilloscope as a word processor\n with additive synth (add-synth) drawing to animate + distort it\n \n DEL, clear last char\n SHIFT + DEL, clear whole text\n CTRL, generate new freq of add-synth\n mouseDragged, draw shape for add-synth (use + to center drawing)\n doubleCLick, clear add-synth drawing\n \n cc teddavis.org 2023\n */\n\nimport ddf.minim.*; // minim req to gen audio\nimport xyscope.*;   // import XYscope\nXYscope xy, xy2;         // create XYscope instance\n\nString txtString = \"XYscope\";\nboolean shifted = false;\nint doubleClicker = 0;\n\nvoid setup() {\n  size(512, 512);\n\n  xy = new XYscope(this);\n  //xy.getMixerInfo(); // lists all audio devices\n\n  xy.textAlign(LEFT, TOP);\n  xy.textSize(30);\n  xy.freq(40);\n\n  // add-synth instance of XYscope\n  xy2 = new XYscope(this, xy.outXY);\n  xy2.freq(39.7);\n  xy2.line(width/2, height/2, width/2+50, height/2+50);\n}\n\nvoid draw() {\n  background(0);\n  xy.clearWaves();\n\n  // draw shapes here\n  xy.text(txtString, 10, 0);\n\n  xy.buildWaves(); // build main waves\n  xy2.buildWaves(); // build add-synth waves\n\n  xy.drawXY(); // draw main XY visuals\n  xy2.drawPath(); // draw add-synth path\n\n  // grid to help center add-synth drawing\n  noFill();\n  pushMatrix();\n  translate(width/2, height/2);\n  stroke(0, 255, 255);\n  line(0, -100, 0, 100);\n  line(-100, 0, 100, 0);\n  popMatrix();\n}\n\nvoid keyPressed() {\n  //println(keyCode);\n\n  // scopewriter logic\n  if (keyCode == 8) {\n    xy.clearWaves();\n    if (shifted) {\n      txtString = \"\";\n    } else {\n      if (txtString.length() > 0) {\n        txtString = txtString.substring(0, txtString.length()-1);\n      }\n    }\n  } else if (keyCode == 10) {\n    txtString += \"\\r\";\n  } else if (keyCode != 27 && keyCode != 0 && keyCode != 65406 && keyCode != 9 && keyCode != 16 && keyCode != 20 && keyCode != 17 && keyCode != 18 && keyCode != 157 && keyCode != 37 && keyCode != 38 && keyCode != 39 && keyCode != 40) {\n    if (xy.textWidth(txtString + key) > width) {\n      String[] t = split(txtString, \" \");\n      if (key == ' ') {\n        txtString += \"\\r\";\n        return;\n      } else if (t.length == 1) {\n        txtString += \"\\r\";\n      } else {\n        txtString = txtString.replaceAll(\" (?=[^ ]*$)\", \"\\n\");\n      }\n    }\n    txtString += key+\"\";\n  }\n\n  // change freq of add-synth\n  if (keyCode == 17) {\n    xy2.freq(floor(random(25))*5+.5);\n    println(xy2.freq().x);\n  }\n\n  if (keyCode == 16) {\n    shifted = true;\n  }\n}\n\n\nvoid keyReleased() {\n  if (keyCode == 16) {\n    shifted = false;\n  }\n}\n\nvoid mouseDragged() {\n  // draw add-synth shape\n  xy2.line(mouseX, mouseY, pmouseX, pmouseY);\n}\n\nvoid mousePressed() {\n  // double-clicker\n  if (millis()-  doubleClicker < 500) {\n    xy2.clearWaves();\n  }\n  doubleClicker = millis();\n}\n"
  },
  {
    "path": "examples/4_inputs/fonts/fonts.pde",
    "content": "/* \n fonts\n Let's draw type on the scope! \n ANYKEY - type out\n DELETE - clear text\n\n » Requires Geomerative library\n cc teddavis.org 2017\n */\n\nimport ddf.minim.*; // minim req to gen audio\nimport xyscope.*;   // import XYscope\nXYscope xy;         // create XYscope instance\n\n// geomerative is required to generate text points\nimport geomerative.*; \nRShape grp; \nRPoint[][] pointPaths;\n\n// store our text to draw\nString txtString = \"XYscope\";\nString fontName = \"FreeSans.ttf\";\n\nvoid setup() {\n  size(512, 512);\n\n  // initialize XYscope with default/custom sound out\n  xy = new XYscope(this, \"\");\n\n  // initialize Geomerative\n  RG.init(this);\n}\n\nvoid draw() {\n  background(0);\n\n  // clear waves like refreshing background\n  xy.clearWaves(); \n\n  // render type with Geomerative\n  grp = RG.getText(txtString, fontName, width/2, CENTER); \n  grp.centerIn(g, 30);\n  RG.setPolygonizer(RG.UNIFORMSTEP);\n  RG.setPolygonizerStep(10);\n  pointPaths = grp.getPointsInPaths();\n\n  pushMatrix();\n  translate(width/2, height/2); \n  if (pointPaths != null) { // only draw if we have points \n    for (int i = 0; i < pointPaths.length; i++) { \n      xy.beginShape(); \n      for (int j=0; j < pointPaths[i].length; j++) { \n        xy.vertex(pointPaths[i][j].x, pointPaths[i][j].y);\n      } \n      xy.endShape();\n    }\n  } \n  popMatrix();\n\n  // build audio from shapes\n  xy.buildWaves();\n\n  // draw Wave + XY analytics\n  xy.drawWave();\n  xy.drawXY();\n}\n\nvoid keyPressed() {\n  if (keyCode == 8) {\n    xy.clearWaves();\n    txtString = \"\";\n  } else if (keyCode != 16 && keyCode != 17 && keyCode != 18 && keyCode != 157 && keyCode != 37 && keyCode != 38 && keyCode != 39 && keyCode != 40) {\n    txtString += key+\"\";\n  }\n}\n"
  },
  {
    "path": "examples/4_inputs/kinect/kinect.pde",
    "content": "/* \n kinect\n Turn the kinect depth image into a nice silhouette! \n \n » Requires OpenCV for Processing + openkinect libraries\n \n cc teddavis.org 2017\n */\n\n/* PREFS */\nint kinectVer = 1; // 1 = xbox 360 vs 2 = xbox one\n\nimport ddf.minim.*; // minim req to gen audio\nimport xyscope.*;   // import XYscope\nXYscope xy;         // create XYscope instance\n\n// libs required for point sorting (efficient drawing)\nimport java.util.Collections;\nimport java.util.Comparator;\n\n// libs + settings for kinect\nimport org.openkinect.freenect.*;\nimport org.openkinect.processing.*;\nKinect kinect;\nKinect2 kinect2;\n\n// Depth image\nPImage depthImg;\n\n// Which pixels do we care about?\nint minDepth =  60;\nint maxDepth = 960;\n\n// What is the kinect's angle\nfloat angle;\n\n//opencv + settings\nimport gab.opencv.*;\nimport java.awt.*;\nOpenCV opencv;\nArrayList<Contour> contours;\nint thres = 30;\nint cutoff = 100;\n\nvoid setup() {\n  size(512, 512);\n\n  // initialize XYscope with default/custom sound out\n  xy = new XYscope(this, \"\");\n\n  // pick kinect version (1 works great)\n  if (kinectVer == 1) {\n    kinect = new Kinect(this);\n    kinect.initDepth();\n    angle = kinect.getTilt();\n    depthImg = new PImage(kinect.width, kinect.height);\n  } else if (kinectVer == 2) {\n    kinect2 = new Kinect2(this);\n    kinect2.initDepth();\n    kinect2.initDevice();\n    depthImg = new PImage(kinect2.depthWidth, kinect2.depthHeight);\n  }\n\n  // initialize OpenCV (used to convert webcam to single line)\n  opencv = new OpenCV(this, depthImg.width, depthImg.height);\n}\n\n\nvoid draw() {\n  background(0);\n\n  // clear waves like refreshing background\n  xy.clearWaves();\n\n  // kinect depth data\n  int[] rawDepth = {};\n  if (kinectVer == 1) {\n    rawDepth = kinect.getRawDepth();\n  } else if (kinectVer == 2) {\n    rawDepth = kinect2.getRawDepth();\n  }\n\n  // create b/w threshold image from depth data\n  for (int i=0; i < rawDepth.length; i++) {\n    if (rawDepth[i] >= minDepth && rawDepth[i] <= maxDepth) {\n      depthImg.pixels[i] = color(255);\n    } else {\n      depthImg.pixels[i] = color(0);\n    }\n  }\n\n  // draw the thresholded image\n  depthImg.updatePixels();\n  //image(depthImg, 0, 0);\n\n  // process threshold to single line\n  opencv.loadImage(depthImg);\n  opencv.dilate();\n  opencv.flip(OpenCV.HORIZONTAL);\n  contours = opencv.findContours(true, false);\n\n  // sort group of lines for effeciant drawing\n  Collections.sort(contours, new MyComparator());\n\n  // draw shapes on scope\n  for (Contour contour : contours) {\n    if (contours.size() > 0) {\n      contour.setPolygonApproximationFactor(1);\n      xy.beginShape();\n      for (PVector point : contour.getPolygonApproximation().getPoints()) {\n        xy.vertex(point.x, point.y);\n      }\n      xy.endShape();\n    }\n  }\n\n  // build audio from shapes\n  if(contours.size() > 0)\n  xy.buildWaves();\n\n  // draw Path + XY analytics\n  xy.drawXY();\n  xy.drawPath();\n}\n\n// used for sorting points\nclass MyComparator implements Comparator<Contour> {\n  @Override\n    public int compare(Contour o1, Contour o2) {\n    if (o1.numPoints() > o2.numPoints()) {\n      return -1;\n    } else if (o1.numPoints() < o2.numPoints()) {\n      return 1;\n    }\n    return 0;\n  }\n}\n"
  },
  {
    "path": "examples/4_inputs/obj/obj.pde",
    "content": "/* \n obj\n Import and render 3D obj files!\n  \n cc teddavis.org 2018\n */\n \nimport ddf.minim.*; // minim req to gen audio\nimport xyscope.*;   // import XYscope\nXYscope xy;         // create XYscope instance\n\n// obj file instance\nPShape obj;\n\npublic void setup() {\n  size(512, 512, P3D);\n\n  // load 3D obj file\n  obj = loadShape(\"teapot.obj\");\n\n  // initialize XYscope\n  xy = new XYscope(this);\n}\n\npublic void draw() {\n  background(0);\n\n  // modulate freq to make beats out of shapes!?\n  if(mousePressed){\n    xy.freq(map(mouseX, 0, width, 0, 50));\n  }\n\n  // clear XYscope waves\n  xy.clearWaves();\n\n  pushMatrix();\n  translate(width/2, height/2);\n  scale(2);\n  rotateZ(PI);\n  rotateY(radians(frameCount));\n\n  // break 3D obj into its pieces\n  int children = obj.getChildCount();\n\n  // render vertices in XYscope\n  xy.beginShape();\n  for (int i = 0; i < children; i++) {\n    PShape child = obj.getChild(i);\n    int total = child.getVertexCount();\n\n    for (int j = 0; j < total; j++) {\n      PVector v = child.getVertex(j);\n      xy.vertex(v.x, v.y, v.z);\n    }\n  }\n  xy.endShape();\n\n  // generate waveform\n  xy.buildWaves();\n  popMatrix();\n\n  // draw waveform + xy simulation\n  xy.drawWaveform();\n  xy.drawXY();\n}\n"
  },
  {
    "path": "examples/4_inputs/svg/svg.pde",
    "content": "/*\n svg\n import any existing vector graphic as an SVG!\n » Requires Geomerative library\n \n cc teddavis.org 2023\n */\n\nimport ddf.minim.*; // minim req to gen audio\nimport xyscope.*;   // import XYscope\nXYscope xy;         // create XYscope instance\n\n// geomerative is required to generate svg points\nimport geomerative.*;\nRShape svg;\nRPoint[][] pointPaths;\n\nvoid setup() {\n  size(512, 512, P3D);\n\n  // initialize XYscope [with default/custom sound out]\n  xy = new XYscope(this, \"\");\n\n  // initialize Geomerative\n  RG.init(this);\n  svg = RG.loadShape(\"oscilloscope.svg\");\n  svg.centerIn(g, 30);\n  pointPaths = svg.getPointsInPaths();\n}\n\nvoid draw() {\n  background(0);\n  xy.clearWaves(); // clear waves like refreshing background\n\n  pushMatrix();\n  translate(width/2, height/2);\n  rotateY(radians(frameCount/2));\n  if (pointPaths != null) { // only draw if we have points\n    for (int i = 0; i < pointPaths.length; i++) {\n      pushMatrix();\n      translate(0, 0, sin(i*5 + frameCount*.02)*100);\n      xy.beginShape();\n      for (int j=0; j < pointPaths[i].length; j++) {\n        xy.vertex(pointPaths[i][j].x, pointPaths[i][j].y);\n      }\n      xy.endShape();\n      popMatrix();\n    }\n  }\n  popMatrix();\n\n  xy.buildWaves(); // build audio from shapes\n\n  // draw Wave + XY analytics\n  xy.drawWave();\n  xy.drawXY();\n}\n"
  },
  {
    "path": "examples/4_inputs/syphon/syphon.pde",
    "content": "/* \n syphon\n Images from any syphon source on the scope! \n mouseX - threshold\n mouseY - threshold distance\n \n » Requires OpenCV for Processing + Syphon libraries\n \n cc teddavis.org + jankenpopp.com 2017\n */\n\n//PREFS\nint threshold = 138;\nfloat thresholdDist = 43;\nint cutoff = 91; // limit min size contour\n\nimport ddf.minim.*; // minim req to gen audio\nimport xyscope.*;   // import XYscope\nXYscope xy;         // create XYscope instance \n\n// syphon is required for passing imagery\nimport codeanticode.syphon.*;\nSyphonClient client;\nPImage imgs; // syphon client\nPImage imgo; // syphon resize\nPImage imgf; // syphon threshold\n\n// libs required for point sorting (efficient drawing)\nimport java.util.Collections;\nimport java.util.Comparator;\n\n//opencv\nimport gab.opencv.*;\nimport java.awt.*;\nOpenCV opencv;\nArrayList<Contour> contours;\n\nvoid setup() {\n  size(512, 512, P3D);\n\n  // initialize XYscope with default/custom sound out\n  xy = new XYscope(this, \"\");\n\n  // initialize syphon client\n  client = new SyphonClient(this);\n\n  // initialize OpenCV (used to convert syphon to single line)\n  opencv = new OpenCV(this, width, height);\n}\n\nvoid draw() {\n  background(0);\n\n  // only up date when sypon sends new image\n  if (client.newFrame()) {\n    imgs = client.getImage(imgs);\n  }\n  if (imgs != null) {\n    imgf = imgs.get();\n    if (imgf.width > imgf.height) {\n      imgf.resize(width, 0);\n    } else {\n      imgf.resize(0, height);\n    }\n    imgo = imgf.get();\n    // clear waves like refreshing background\n    xy.clearWaves();\n\n    // adjust threshold of image for selective lines\n    if (mousePressed) {\n      threshold = floor(map(mouseX, 0, width, 0, 255));\n      thresholdDist = map(mouseY, 0, height, 0, 255-threshold);\n\n      // replace variable defaults at top if you find better ones\n      println(\"threshold: \"+threshold +\" / thresholdDist: \"+ thresholdDist);\n    }\n\n    // convert syphon to high contrast threshold\n    for (int i=0; i<imgf.width*imgf.height; i++) {\n      if (brightness(imgf.pixels[i]) > threshold && brightness(imgf.pixels[i]) < threshold+thresholdDist) {\n        imgf.pixels[i]  = color(200); // White\n      } else {\n        imgf.pixels[i]  = color(0); // Black\n      }\n    }\n\n    // process threshold to single line\n    opencv.loadImage(imgf);\n    opencv.dilate();\n    opencv.erode();\n\n    // display syphon original, threshold, opencv images – w/ keys 1, 2, 3\n    if (keyPressed) {\n      if (key == '1')\n        image(imgo, 0, 0);\n      if (key == '2')\n        image(imgf, 0, 0);\n      if (key == '3') {\n        PImage otemp = opencv.getSnapshot();\n        image(otemp, 0, 0);\n      }\n    }\n\n    contours = opencv.findContours(true, false);\n\n    // sort group of lines for effeciant drawing\n    Collections.sort(contours, new MyComparator());\n\n    // draw shapes on scope\n    for (Contour contour : contours) {\n      if (contours.size() > 0) {\n        contour.setPolygonApproximationFactor(1);\n        if (contour.numPoints() > cutoff) {        \n          xy.beginShape();\n          for (PVector point : contour.getPolygonApproximation().getPoints()) {\n            xy.vertex(point.x, point.y);\n          }\n          xy.endShape();\n        }\n      }\n    }\n\n    // build audio from shapes\n    xy.buildWaves();\n  }\n  // draw XY analytics\n  xy.drawXY();\n}\n\n// used for sorting points\nclass MyComparator implements Comparator<Contour> {\n  @Override\n    public int compare(Contour o1, Contour o2) {\n    if (o1.numPoints() > o2.numPoints()) {\n      return -1;\n    } else if (o1.numPoints() < o2.numPoints()) {\n      return 1;\n    }\n    return 0;\n  }\n}\n"
  },
  {
    "path": "examples/4_inputs/video/video.pde",
    "content": "/* \n video\n Import and render videos in XYscope\n \n mouseX - threshold\n mouseY - threshold distance\n keys 1, 2, 3 - view original, threshold, opencv image\n \n ** some strange behavior with Processing 4+...\n \n » Requires OpenCV for Processing + Video libraries\n cc teddavis.org 2018\n */\n\n//PREFS\nString moviePath = \"BigBuckBunny_320x180_trim.mp4\";\nint threshold = 108;\nfloat thresholdDist = 80;\nint cutoff = 91; // limit min size contour\n\nimport ddf.minim.*; // minim req to gen audio\nimport xyscope.*;   // import XYscope\nXYscope xy;         // create XYscope instance\n\n// video library and instance\nimport processing.video.*;\nMovie movie;\n\n// OpenCV imagesteps\nPImage imgResize; // movie resize\nPImage imgThres; // movie threshold\n\n// libs required for point sorting (efficient drawing)\nimport java.util.Collections;\nimport java.util.Comparator;\n\n//opencv\nimport gab.opencv.*;\nimport java.awt.*;\nOpenCV opencv;\nArrayList<Contour> contours;\n\nvoid setup() {\n  size(512, 512);\n\n  // initialize XYscope with default/custom sound out\n  xy = new XYscope(this, \"\");\n\n  // initialize video\n  movie = new Movie(this, moviePath);\n  movie.play();\n  movie.loop();\n  movie.volume(0);\n\n  // initialize OpenCV (used to convert syphon to single line)\n  opencv = new OpenCV(this, width, height);\n}\n\nvoid draw() {\n  background(0);\n\n  if (movie.available() == true) {\n    movie.read();\n  }\n\n  if (movie != null) {\n    // clear waves like refreshing background\n    xy.clearWaves();\n\n    // resize movie to fit canvas\n    imgResize = movie.get();\n    if (imgResize.width > imgResize.height) {\n      imgResize.resize(width, 0);\n    } else {\n      imgResize.resize(0, height);\n    }\n    imgThres = imgResize.get();\n\n    // adjust threshold of image for selective lines\n    if (mousePressed) {\n      threshold = floor(map(mouseX, 0, width, 0, 255));\n      thresholdDist = map(mouseY, 0, height, 0, 255-threshold);\n\n      // replace variable defaults at top if video needs specific ones\n      println(\"threshold: \"+threshold +\" / thresholdDist: \"+ thresholdDist);\n    }\n\n    // convert video to high contrast threshold\n    for (int i=0; i<imgThres.width*imgThres.height; i++) {\n      if (brightness(imgThres.pixels[i]) > threshold && brightness(imgThres.pixels[i]) < threshold+thresholdDist) {\n        imgThres.pixels[i]  = color(255); // White\n      } else {\n        imgThres.pixels[i]  = color(0); // Black\n      }\n    }\n\n    // process threshold to single line\n    opencv.loadImage(imgThres);\n    opencv.dilate();\n    opencv.erode();\n    contours = opencv.findContours(true, false);\n\n    // display video original, threshold, opencv images – w/ keys 1, 2, 3\n    if (keyPressed) {\n      if (key == '1')\n        image(imgResize, 0, 0);\n      if (key == '2')\n        image(imgThres, 0, 0);\n      if (key == '3') {\n        PImage otemp = opencv.getSnapshot();\n        image(otemp, 0, 0);\n      }\n    }\n\n\n    // sort group of lines for effeciant drawing\n    Collections.sort(contours, new MyComparator());\n\n    // draw shapes on scope\n    for (Contour contour : contours) {\n      if (contours.size() > 0) {\n        contour.setPolygonApproximationFactor(1);\n        if (contour.numPoints() > cutoff) {        \n          xy.beginShape();\n          for (PVector point : contour.getPolygonApproximation().getPoints()) {\n            xy.vertex(point.x, point.y);\n          }\n          xy.endShape();\n        }\n      }\n    }\n\n    // build audio from shapes\n    xy.buildWaves();\n  }\n\n  // draw XY analytics\n  xy.drawXY();\n}\n\n// used for sorting points\nclass MyComparator implements Comparator<Contour> {\n  @Override\n    public int compare(Contour o1, Contour o2) {\n    if (o1.numPoints() > o2.numPoints()) {\n      return -1;\n    } else if (o1.numPoints() < o2.numPoints()) {\n      return 1;\n    }\n    return 0;\n  }\n}\n"
  },
  {
    "path": "examples/4_inputs/webcam/webcam.pde",
    "content": "/* \n webcam\n You via your webcam on the scope! \n mouseX - threshold\n mouseY - threshold distance\n \n » Requires OpenCV for Processing + Video libraries\n \n cc teddavis.org 2017 – 2023\n */\n\n//PREFS\nint threshold = 65;\nfloat thresholdDist = 115;\n\nimport ddf.minim.*; // minim req to gen audio\nimport xyscope.*;   // import XYscope\nXYscope xy;         // create XYscope instance\n\n// video is required for webcam\nimport processing.video.*;\nCapture video;\n\n// libs required for point sorting (efficient drawing)\nimport java.util.Collections;\nimport java.util.Comparator;\n\n//opencv\nimport gab.opencv.*;\nimport java.awt.*;\nOpenCV opencv;\nArrayList<Contour> contours;\nint cutoff = 91;\nPImage p;\n\nvoid setup() {\n  size(512, 512, P3D);\n\n  // initialize XYscope with default/custom sound out\n  xy = new XYscope(this, \"\");\n\n  // initialize video capture\n  video = new Capture(this, 640, 480);\n  video.start();\n  p = new PImage(video.width, video.height);\n\n  // initialize OpenCV (used to convert webcam to single line)\n  opencv = new OpenCV(this, p.width, p.height);\n}\n\nvoid draw() {\n  background(0);\n  if (video.available()) {\n    video.read();\n  }\n\n  // clear waves like refreshing background\n  xy.clearWaves();\n\n  // adjust threshold of image for selective lines\n  if (mousePressed) {\n    threshold = floor(map(mouseX, 0, width, 0, 255));\n    thresholdDist = map(mouseY, 0, height, 0, 255-threshold);\n\n    // replace variable defaults at top if you find better ones\n    println(\"threshold: \"+threshold +\" / thresholdDist: \"+ thresholdDist);\n  }\n\n  // convert video to high contrast threshold\n  video.loadPixels();\n  if (video.pixels.length == 0) {\n    return;\n  }\n  for (int i=0; i<p.width*p.height; i++) {\n    if (brightness(video.pixels[i]) > threshold && brightness(video.pixels[i]) < threshold+thresholdDist) {\n      p.pixels[i]  = color(0); // White\n    } else {\n      p.pixels[i]  = color(255); // Black\n    }\n    p.updatePixels();\n  }\n\n  // process threshold to single line\n  opencv.loadImage(p);\n  opencv.flip(OpenCV.HORIZONTAL);\n  opencv.dilate();\n  contours = opencv.findContours(true, false);\n  \n   // display video original, threshold, opencv images – w/ keys 1, 2, 3\n  if (keyPressed) {\n    pushMatrix();\n    if (key == '1') {\n      scale(-1, 1);\n      image(video, -video.width, 0);\n    }\n    if (key == '2') {\n      scale(-1, 1);\n      image(p, -p.width, 0);\n    }\n    if (key == '3') {\n      PImage otemp = opencv.getSnapshot();\n      image(otemp, 0, 0);\n    }\n    popMatrix();\n  }\n\n  // sort group of lines for effeciant drawing\n  Collections.sort(contours, new MyComparator());\n\n  // draw shapes on scope\n  for (Contour contour : contours) {\n    if (contours.size() > 0) {\n      contour.setPolygonApproximationFactor(1);\n      if (contour.numPoints() > cutoff) {        \n        xy.beginShape();\n        for (PVector point : contour.getPolygonApproximation().getPoints()) {\n          xy.vertex(point.x, point.y);\n        }\n        xy.endShape();\n      }\n    }\n  }\n\n  // build audio from shapes\n  xy.buildWaves();\n\n  // draw XY analytics\n  xy.drawXY();\n}\n\n// used for sorting points\nclass MyComparator implements Comparator<Contour> {\n  @Override\n    public int compare(Contour o1, Contour o2) {\n    if (o1.numPoints() > o2.numPoints()) {\n      return -1;\n    } else if (o1.numPoints() < o2.numPoints()) {\n      return 1;\n    }\n    return 0;\n  }\n}\n"
  },
  {
    "path": "examples/4_inputs/webcam_processing4/webcam_processing4.pde",
    "content": "/*\n webcam processing4\n You via your webcam on the scope!\n mouseX - threshold\n mouseY - threshold distance\n \n * Updated version solving hiccups with Processing4 + Apple Silicon\n » Requires 'OpenCV for Processing' + 'Video Library for Processing 4' libraries\n \n cc teddavis.org 2017 – 23\n */\n\n//PREFS\nint threshold = 65;\nfloat thresholdDist = 115;\n\nimport ddf.minim.*; // minim req to gen audio\nimport xyscope.*;   // import XYscope\nXYscope xy;         // create XYscope instance\n\n// video is required for webcam\nimport processing.video.*;\nCapture video;\n\n// libs required for point sorting (efficient drawing)\nimport java.util.Collections;\nimport java.util.Comparator;\n\n//opencv\nimport gab.opencv.*;\nimport java.awt.*;\nOpenCV opencv;\nArrayList<Contour> contours;\nint cutoff = 91;\nPImage p;\n\nvoid setup() {\n  size(512, 512, P3D);\n\n  // initialize XYscope with default/custom sound out\n  xy = new XYscope(this, \"\");\n  xy.limitPath(2); // remove box around whole thing\n\n  // initialize video capture\n  video = new Capture(this, 640, 480);\n  video.start();\n  image(video, 0, 0);\n\n  p = new PImage(video.width, video.height);\n\n  // initialize OpenCV (used to convert webcam to single line)\n  opencv = new OpenCV(this, p.width, p.height);\n}\n\nvoid draw() {\n  background(0);\n  if (video.available()) {\n    video.read();\n  }\n\n  // clear waves like refreshing background\n  xy.clearWaves();\n\n  // adjust threshold of image for selective lines\n  if (mousePressed) {\n    threshold = floor(map(mouseX, 0, width, 0, 255));\n    thresholdDist = map(mouseY, 0, height, 0, 255-threshold);\n\n    // replace variable defaults at top if you find better ones\n    println(\"threshold: \"+threshold +\" / thresholdDist: \"+ thresholdDist);\n  }\n\n  // convert video to high contrast threshold\n  video.loadPixels();\n  if (video.pixels.length == 0) {\n    return;\n  }\n  \n  for (int i=0; i<p.width*p.height; i++) {\n    if (brightness(video.pixels[i]) > threshold && brightness(video.pixels[i]) < threshold+thresholdDist) {\n      p.pixels[i]  = color(0); // White\n    } else {\n      p.pixels[i]  = color(255); // Black\n    }\n  }\n  p.updatePixels();\n\n  // process threshold to single line\n  opencv.loadImage(p);\n  opencv.flip(OpenCV.HORIZONTAL);\n  opencv.dilate();\n  contours = opencv.findContours(true, false);\n\n  // display video original, threshold, opencv images – w/ keys 1, 2, 3\n  if (keyPressed) {\n    pushMatrix();\n    if (key == '1') {\n      scale(-1, 1);\n      image(video, -video.width, 0);\n    }\n    if (key == '2') {\n      scale(-1, 1);\n      image(p, -p.width, 0);\n    }\n    if (key == '3') {\n      PImage otemp = opencv.getSnapshot();\n      image(otemp, 0, 0);\n    }\n    popMatrix();\n  }\n\n  // sort group of lines for effeciant drawing\n  Collections.sort(contours, new MyComparator());\n\n  // draw shapes on scope\n  for (Contour contour : contours) {\n    if (contours.size() > 0) {\n      contour.setPolygonApproximationFactor(1);\n      if (contour.numPoints() > cutoff) {\n        xy.beginShape();\n        for (PVector point : contour.getPolygonApproximation().getPoints()) {\n          xy.vertex(point.x, point.y);\n        }\n        xy.endShape();\n      }\n    }\n  }\n\n\n  // build audio from shapes\n  xy.buildWaves();\n\n  // draw XY analytics\n  xy.drawXY();\n}\n\n// used for sorting points\nclass MyComparator implements Comparator<Contour> {\n  @Override\n    public int compare(Contour o1, Contour o2) {\n    if (o1.numPoints() > o2.numPoints()) {\n      return -1;\n    } else if (o1.numPoints() < o2.numPoints()) {\n      return 1;\n    }\n    return 0;\n  }\n}\n"
  },
  {
    "path": "examples/5_displays/laser/laser.pde",
    "content": "/*\n laser\n Guide to driving RGB laser with XYscope.\n cc teddavis.org 2018 – 23\n */\n\nimport ddf.minim.*; // minim req to gen audio\nimport xyscope.*;   // import XYscope\nXYscope xy;         // create XYscope instance\n\nvoid setup() {\n  size(640, 480, P3D);\n  background(0);\n\n  // initialize XYscope with default/custom sound out\n  xy = new XYscope(this, \"MOTU 1-2\");\n\n  // only stereo pairs allowed in processing, so it's broken to R, GB\n  // incase 2nd channel of R pair is useful for blanking/etc.\n  xy.laser(\"MOTU 3-4\", \"MOTU 5-6\"); //xy.laser(mixerR, mixerGB);\n\n  // RGB waveforms have own freq, so we can them out of sync\n  xy.strokeFreq(50.05, 50.1, 50.2);\n}\n\nvoid draw() {\n  background(0);\n\n  // clear waves like refreshing background\n  xy.clearWaves();\n\n  xy.limitPath(0); // avoid drawing any forms beyond view\n\n  xy.strokeDash(8); // optional dashes in the RGB stroke\n\n  if (mousePressed) {\n    // set low-pass filter of laser\n    float newLPF = map(mouseX, 0, width, 1, 10000);\n    xy.laserLPF(newLPF);\n\n    // limit number of points it can draw with\n    xy.limitPoints(floor(map(mouseY, 0, height, 0, 100)));\n  }\n\n  pushMatrix();\n  translate(width/2, height/2);\n  rotateY(radians(frameCount*.2));\n  float s = height*.25;\n  xy.stroke(0, 255, 255); // set RGB stroke before shapes (0-255)\n  xy.ellipse(-s, 0, s, s);\n\n  // rotating for infinity movement\n  rotateY(radians(frameCount*.2));\n  xy.stroke(255, 0, 255);\n  rotate(radians(180)); // match ellipse startings points\n  rotateX(radians(180)); // match ellipse startings points\n  xy.ellipse(-s, 0, s, s);\n\n  popMatrix();\n\n  // build audio from shapes\n  xy.buildWaves();\n\n  // draw RGB Waveforms + XY simulation\n  xy.drawRGB();\n  //xy.drawWaveform();\n  xy.drawXY();\n}\n"
  },
  {
    "path": "examples/5_displays/vectrex/vectrex.pde",
    "content": "/*\n vectrex\n Basics settings for using a modded Vectrex monitor.\n cc teddavis.org 2018\n */\n\nimport ddf.minim.*; // minim req to gen audio\nimport xyscope.*;   // import XYscope\nXYscope xy;         // create XYscope instance\n\nvoid setup() {\n  size(512, 512);\n\n  // initialize XYscope with default sound out\n  xy = new XYscope(this, \"\");\n\n  // set XYscope into Vectrex aspect-ratio mode\n  xy.vectrex(90); // 90 for landscape, 0 for portrait\n\n  /*\n   If the SPOT-KILLER MOD was applied (z/brightness is always on),\n   this auto sets the brightness (from way turned down) when the sketch runs.\n   */\n  //xy.z(\"MOTU 3-4\"); // use custom 3rd channel audio device\n  //xy.zRange(.5, 0);\n}\n\nvoid draw() {\n  background(0);\n\n  // clear waves like refreshing background\n  xy.clearWaves();\n\n  // set detail of vertex ellipse\n  xy.ellipseDetail(30);\n\n  // draw two primative shapes, testing boundry of screen\n  xy.rect(0, 0, width, height);\n  float s = map(mouseX, 0, width, -height/2, height/2);\n  xy.ellipse(width/2, height/2, s, s);\n\n  // build audio from shapes\n  xy.buildWaves();\n\n  // draw all analytics\n  xy.drawAll();\n}\n"
  },
  {
    "path": "examples/6_outputs/audio_recorder/audio_recorder.pde",
    "content": "/*\n audio recorder\n Export your XYscope as .wav audio files!\n just use: xy.recorderBegin(filename) + xy.recorderEnd()\n cc teddavis.org 2023\n */\n\nimport ddf.minim.*; // minim req to gen audio\nimport xyscope.*;   // import XYscope\nXYscope xy;         // create XYscope instance\n\nvoid setup() {\n  size(512, 512, P3D); // declares size of output window\n\n  xy = new XYscope(this, 48000); // use desired sampleRate\n  //xy.getMixerInfo(); // lists all audio devices\n}\n\nvoid draw() {\n  background(0);\n  xy.clearWaves();\n\n  push();\n  translate(width/2, height/2);\n  rotateY(radians(frameCount/2));\n  rotateZ(radians(frameCount/2));\n  xy.box(height/2);\n\n  float off = width/5;\n  translate(random(-off, off), random(-off, off), random(-off, off));\n  xy.sphere(random(50));\n  pop();\n\n  xy.buildWaves();\n  xy.drawAll();\n}\n\nvoid keyPressed() {\n  if (key == 'r') {\n    if (xy.recorder != null && xy.recorder.isRecording()) {\n      xy.recorderEnd();\n    } else {\n      xy.recorderBegin(\"box-spheres\"); // leave blank for \"XYscope\"\n    }\n  }\n}\n"
  },
  {
    "path": "examples/6_outputs/osc_wavetables/osc_wavetables.pde",
    "content": "/*\n osc wavetables\n transport your XYscope custom wavetables via OSC to other tools\n to visual forms drawn, add effects, pass into custom oscillators.\n \n format: [x1, y1, z1], [x2, y2, z2], ...];\n \n + import /data/P5L_xyscope-osc_wavetables.json to offline P5LIVE for demo\n * not working for your tool of choice? join discussion:\n   https://github.com/ffd8/xyscope/issues/5\n \n cc teddavis.org 2023\n */\n\nimport ddf.minim.*; // minim req to gen audio\nimport xyscope.*;   // import XYscope\nXYscope xy;         // create XYscope instance\n\nimport java.util.Arrays; // needed for 2D Array to string\n\n// OSC code\nimport oscP5.*;\nimport netP5.*;\nOscP5 oscP5;\nNetAddress myRemoteLocation;\n\nfloat fc = 0;\n\nvoid setup() {\n  size(512, 512, P3D); // declares size of output window\n\n  xy = new XYscope(this); // define XYscope instance\n  //xy.getMixerInfo(); // lists all audio devices\n\n  /* start oscP5, sending outgoing messages */\n  oscP5 = new OscP5(this, 12001); // receiving port\n  myRemoteLocation = new NetAddress(\"127.0.0.1\", 12000); // sending port\n}\n\nvoid draw() {\n  background(0);\n  xy.clearWaves();\n  xy.limitPath(0);\n  xy.steps(20);\n\n  pushMatrix();\n  translate(width/2, height/2);\n  rotateX(radians(-frameCount));\n  rotateY(radians(-frameCount/2));\n  xy.textSize(72);\n  xy.textAlign(CENTER, CENTER);\n  xy.text(\"XYscope\", 0, 0);\n\n  xy.textSize(32);\n  float tw = xy.textWidth(\"XYscope\");\n  fc += mouseX;\n  for (int i=0; i<3; i++) {\n    pushMatrix();\n    rotateX(radians(-fc+i*50));\n    translate(map(i, 0, 3, -tw, tw), -height*.1, height*.1);\n    xy.text(\"OSC\", 0, 0);\n    popMatrix();\n  }\n  popMatrix();\n\n  xy.buildWaves();\n  xy.drawAll();\n\n  sendWaves(); // send OSC message of waves [x1, y1, z1], [x2, y2, z2], ...];\n}\n\nvoid sendWaves() {\n  float[][] getWaves = new float[xy.tableX.size()][3];\n  for (int i=0; i < xy.tableX.size(); i++) {\n    getWaves[i][0] = xy.tableX.get(i);\n    getWaves[i][1] = xy.tableY.get(i);\n  }\n\n  //System.out.println(Arrays.deepToString(getWaves));\n  String xyWavetables = Arrays.deepToString(getWaves);\n\n  OscMessage myMessage = new OscMessage(\"/XYscope\");\n  myMessage.add(xyWavetables); /* add an int to the osc message */\n\n  //    /* send the message */\n  oscP5.send(myMessage, myRemoteLocation);\n}\n\nvoid keyPressed() {\n  // optionally only send on demand, disable within draw()\n  if (keyCode == 32) {\n    sendWaves();\n  }\n}\n"
  },
  {
    "path": "examples/7_custom_waves/customWaves_drawingXYZ/customWaves_drawingXYZ.pde",
    "content": "/* \n customWaves_drawing\n Test out our sound cards by manually drawing the wave form.\n Very useful for testing just the z-axis blanking parallel to another sketch\n DELETE - resets waveforms\n cc teddavis.org 2017\n */\n\nimport ddf.minim.*; // minim req to gen audio\nimport xyscope.*;   // import XYscope\nXYscope xy;         // create XYscope instance\n\n// setup local waveforms\nfloat[] x, y, z;\nint dragDist = 14;\nint dragRegion = 0;\n\nvoid setup() {\n  size(400, 600, P3D);\n  background(0);\n\n  // initialize XYscope with custom outXY\n  // left blank so it runs, but fill in with your audio card\n  xy = new XYscope(this, \"\");\n  \n  // custom z\n  xy.z(\"\");\n\n  // initialize waves\n  resetWaves();\n}\n\n\nvoid draw() {\n  background(0);\n\n  // display text and color boxes\n  displayInfo();\n\n  // build each wave custom from our local waveform\n  xy.buildX(x);\n  xy.buildY(y);\n  xy.buildZ(z);\n\n  // draw waveform analytics\n  xy.drawWaveform();\n}\n\nvoid mousePressed() {\n  // only draw in region clicked on\n  if (mouseY > height*.125 && mouseY < height*.375) {\n    dragRegion = 1;\n  } else if (mouseY > height*.375 && mouseY < height*.625) {\n    dragRegion = 2;\n  } else if (mouseY > height*.625 && mouseY < height*.875) {\n    dragRegion = 3;\n  }\n}\n\nvoid mouseReleased() {\n  // release custom region\n  dragRegion = 0;\n}\n\nvoid mouseDragged() {\n  // modify waveform based on drawing\n  for (int i=0; i < x.length; i++) { \n    int mappedX = floor(map(mouseX, 0, width, 0, x.length));\n    if (abs(i-mappedX) < dragDist) {\n      if (mouseY > height*.125 && mouseY < height*.375 && dragRegion == 1) {\n        float mappedY = map(mouseY, height*.375, height*.125, 0, 1);\n        x[i] = mappedY;\n      } else if (mouseY > height*.375 && mouseY < height*.625 && dragRegion == 2) {\n        float mappedY = map(mouseY, height*.375, height*.625, 0, 1);\n        z[i] = mappedY;\n      } else if (mouseY > height*.625 && mouseY < height*.875 && dragRegion == 3) {\n        float mappedY = map(mouseY, height*.625, height*.875, 0, 1);\n        y[i] = mappedY;\n      }\n    }\n  }\n}\n\nvoid keyPressed() {\n  // reset waves on DELETE\n  if (keyCode == 8) { // DELETE\n    resetWaves();\n  }\n}\n\nvoid resetWaves() {\n  x = new float[xy.waveSize()];\n  y = new float[xy.waveSize()];\n  z = new float[xy.waveSize()];\n\n  for (int i=0; i < x.length; i++) {\n    x[i] = .5;\n    y[i] = .5;\n    z[i] = .5;\n  }\n}\n\nvoid displayInfo() {\n  noStroke();\n  fill(255, 0, 0, 50);\n  rect(0, height*.125, width, height*.25);\n  fill(0, 255, 0, 50);\n  rect(0, height*.375, width, height*.25);\n  fill(0, 0, 255, 50);\n  rect(0, height*.625, width, height*.25);\n  fill(255, 0, 0);\n  text(\"X - LEFT/HORIZONTAL\", 5, height*.125, width, 100);\n  fill(0, 255, 0);\n  text(\"Z - BLANKING\", 5, height*.375, width, 100);\n  fill(0, 0, 255);\n  text(\"Y - RIGHT/VERTICAL\", 5, height*.625, width, 100);\n}\n"
  },
  {
    "path": "examples/7_custom_waves/customWaves_noiseXY/customWaves_noiseXY.pde",
    "content": "/*\n customWaves_buildXY\n You don't have to use primitive forms,\n you can also generate the waveforms by your own code and logic.\n cc teddavis.org 2017-23\n */\n\nimport ddf.minim.*; // minim req to gen audio\nimport xyscope.*;   // import XYscope\nXYscope xy;         // create XYscope instance\n\nfloat[] yWave, xWave;\n\nvoid setup() {\n  size(512, 512);\n  background(0);\n\n  // initialize custom local minim\n  xy = new XYscope(this);\n\n  // function below to build waves\n  genNoiseWave();\n}\n\n\nvoid draw() {\n  background(0);\n\n  // draw analytics\n  xy.drawAll();\n}\n\nvoid keyPressed() {\n  // refresh noise with spacebar\n  if (keyCode == 32) {\n    genNoiseWave();\n  }\n}\n\nvoid genNoiseWave() {\n  // set new noiseSeed\n  noiseSeed(frameCount);\n\n  // get bufferSize() of output\n  println(xy.waveSize());\n\n  // initialize array for storing values\n  yWave = new float[xy.waveSize()];\n  xWave = new float[xy.waveSize()];\n  float nx = random(1);\n  float ny = random(1);\n\n  // add noise walker to waveform\n  for (int i=0; i<yWave.length; i++) {\n    xWave[i] = noise(nx)*1;\n    yWave[i] = noise(ny)*1;\n    nx+=.01;\n    ny+=.011;\n  }\n\n  // build each XY waveform with array of 0.0 – 1.0 values\n  xy.buildX(xWave);\n  xy.buildY(yWave);\n}\n"
  },
  {
    "path": "examples/7_custom_waves/setWaveforms_noise/setWaveforms_noise.pde",
    "content": "/*\n\n*/\n\nimport ddf.minim.*; // minim req to gen audio\nimport xyscope.*;   // import XYscope\nXYscope xy;         // create XYscope instance\n\nvoid setup() {\n  size(512, 512); // declares size of output window\n\n  xy = new XYscope(this);\n  xy.getMixerInfo(); // lists all audio devices\n\n  genWaveforms();\n}\n\nvoid draw() {\n  background(0);\n\n  // build using custom waveforms\n  genWaveforms();\n\n  xy.drawWaveform();\n  xy.drawXY();\n}\n\nvoid genWaveforms() {\n  float[] waveX = new float[xy.bufferSize()];\n  float[] waveY = new float[xy.bufferSize()];\n\n  for (int i=0; i<waveX.length; i++) {\n    waveX[i] = sin(i*TWO_PI/512);//map(i, 0, waveX.length, -1, 1);\n    waveY[i] = -.5 + noise(frameCount*.015+i*TWO_PI/512);\n  }\n  \n  // set waveform with arrays of -1.0 to 1.0 values\n  xy.setWaveforms(waveX, waveY); // optionally set waveZ too\n}\n"
  },
  {
    "path": "examples/8_music/MidiScope/MidiScope.pde",
    "content": "/*\n  MidiScope\n  use a MIDI file to set the freq of any XYscope graphics!\n  cc teddavis.org 2019-23\n*/\n\nString midiTrack = \"teapot.MID\"; // filename in /data folder\nint midiTranspose = -3; // shift key of midi track\nfloat midiBPM = 91;\n\nimport ddf.minim.*; // minim req to gen audio\nimport xyscope.*;   // import XYscope\nXYscope xy;         // create XYscope instance\n\nimport ddf.minim.ugens.*; // freq from pitch\n// MIDI code at bottom\n\nPShape obj;\n\nvoid setup() {\n  size(512, 512, P3D);\n\n  xy = new XYscope(this);\n\n  setupMidi(midiTrack);\n  obj = loadShape(\"teapot.obj\");\n}\n\nvoid draw() {\n  background(0);\n\n  // 3D teapot!\n  xy.clearWaves();\n  pushMatrix();\n  translate(width/2, height/2);\n  scale(2);\n  rotateZ(PI);\n  rotateY(radians(frameCount+ xy.freq().x));\n\n  // break 3D obj into its pieces\n  int children = obj.getChildCount();\n\n  // render vertices in XYscope\n  xy.beginShape();\n  for (int i = 0; i < children; i++) {\n    PShape child = obj.getChild(i);\n    int total = child.getVertexCount();\n\n    for (int j = 0; j < total; j++) {\n      PVector v = child.getVertex(j);\n      xy.vertex(v.x, v.y, v.z);\n    }\n  }\n  xy.endShape();\n  popMatrix();\n\n  xy.buildWaves();\n  xy.drawXY();\n  xy.drawWaveform();\n}\n\nvoid mouseDragged() {\n  xy.point(mouseX, mouseY);\n}\n\nvoid keyPressed() {\n  if (keyCode == 8) { // DELETE\n    xy.clearWaves();\n  }\n}\n\n// MIDI details\nimport javax.sound.midi.*;\nSequencer sequencer;\nSequence sequence;\n\nclass MidiReceiver implements Receiver {\n  void close() { }\n  \n  void send( MidiMessage msg, long timeStamp ) {\n    if ( msg instanceof ShortMessage ) {\n      ShortMessage sm = (ShortMessage)msg;\n      if ( sm.getCommand() == ShortMessage.NOTE_ON ) {\n        int note = sm.getData1();\n        int vel  = sm.getData2();\n        //xy.amp(map(vel, 0, 127, 0, 1));\n        xy.freq(Frequency.ofMidiNote(note + (midiTranspose * 12)).asHz());\n        xy.resetWaves();\n      }\n    }\n  }\n}\n\nvoid setupMidi(String midiFile) {\n  // load midi\n  try {\n    sequencer = MidiSystem.getSequencer( false );\n    sequencer.open();\n    sequence = MidiSystem.getSequence( createInput( midiFile ) );\n    sequencer.setSequence( sequence );\n    sequencer.setTempoInBPM( midiBPM );\n    sequencer.getTransmitter().setReceiver( new MidiReceiver() );\n    sequencer.setLoopCount( Sequencer.LOOP_CONTINUOUSLY );\n    sequencer.start();\n  } catch( MidiUnavailableException ex ) {\n    // no sequencer\n    println( \"No default sequencer, sorry bud.\" );\n  } catch( InvalidMidiDataException ex ) {\n    // oops, the file was bad\n    println( \"The midi file was hosed or not a midi file, sorry bud.\" );\n  } catch( IOException ex ) {\n    println( \"Had a problem accessing the midi file, sorry bud.\" );\n  }\n}\n"
  },
  {
    "path": "examples/8_music/audio_filters/audio_filters.pde",
    "content": "/*\n  xtra_filters\n Use minim ugens audio filters.\n Click and drag mouse to adjust moog filter\n cc teddavis.org 2018-23\n */\n\nimport ddf.minim.*; // minim req to gen audio\nimport xyscope.*;   // import XYscope\nXYscope xy;         // create XYscope instance\n\n// extra libs are required for filtering\nimport ddf.minim.ugens.*;\nMoogFilter moog;\nFlanger flange;\nDelay myDelay;\nADSR adsr;\n\n\nvoid setup() {\n  size(512, 512, P3D);\n\n\n  // initialize XYscope with default sound out\n  xy = new XYscope(this);\n\n  moog = new MoogFilter( 1200, 0.75 );\n  moog.setChannelCount(2);\n\n  myDelay = new Delay( 0.4, 0.5, false, false );\n  myDelay.setChannelCount(2);\n\n  flange = new Flanger( 0, // delay length in milliseconds ( clamped to [0,100] )\n    0.2f, // lfo rate in Hz ( clamped at low end to 0.001 )\n    1.0f, // delay depth in milliseconds ( minimum of 0 )\n    0.5f, // amount of feedback ( clamped to [0,1] )\n    0.0f, // amount of dry signal ( clamped to [0,1] )\n    0.5f // amount of wet signal ( clamped to [0,1] )\n    );\n  flange.setChannelCount(2);\n\n  //patch/unpatch moog\n  xy.sumXY.unpatch(xy.outXY);\n  xy.sumXY.patch(moog).patch(xy.outXY);\n\n  //patch/unpatch delay\n  //xy.sumXY.unpatch(xy.outXY);\n  //xy.sumXY.patch(myDelay).patch(xy.outXY);\n\n  //patch/unpatch flange\n  //xy.sumXY.unpatch(xy.outXY);\n  //xy.sumXY.patch(flange).patch(xy.outXY);\n}\n\nvoid draw() {\n  background(0);\n\n  // clear waves like refreshing background\n  xy.clearWaves();\n\n  // grid of squares\n  int grid = 4;\n  for (int i=0; i < grid; i++) {\n    for (int j=0; j < grid; j++) {\n      //xy.rect(i*width/grid, j*height/grid, width/grid, height/grid);\n    }\n  }\n\n  pushMatrix();\n  translate(width/2, height/2);\n  rotateY(radians(frameCount/2));\n  xy.box(width/2);\n  popMatrix();\n\n  // enable for delay demo\n  //xy.rect(mouseX, mouseY, 200, 200);\n\n  // adjust moog filter\n  if (mousePressed) {\n    float freq = constrain( map(mouseX, 0, width, 2, 12000), 2, 12000);\n    float rez  = constrain( map(mouseY, height, 0, 0, 1), 0, 1);\n    println(\"freq: \"+freq+\" / rez: \"+rez);\n\n    moog.frequency.setLastValue(freq);\n    moog.resonance.setLastValue(rez);\n  }\n\n\n  // build audio from shapes\n  xy.buildWaves();\n\n  // draw all analytics\n  xy.drawAll();\n}\n\nvoid keyPressed() {\n  if ( key == '1' ) moog.type = MoogFilter.Type.LP;\n  if ( key == '2' ) moog.type = MoogFilter.Type.HP;\n  if ( key == '3' ) moog.type = MoogFilter.Type.BP;\n}\n"
  },
  {
    "path": "examples/8_music/freq_keyboard_notes/freq_keyboard_notes.pde",
    "content": "/*\n freq_keyboard_notes\n Use your keyboard (A, B, C, D, E, F, G + 1, 2, 3, 4, 5, 6, 7..)\n to play musical pitches of your graphics!\n cc teddavis.org 2023\n */\n\nimport ddf.minim.*; // minim req to gen audio\nimport xyscope.*;   // import XYscope\nXYscope xy;         // create XYscope instance\n\n// ugens to convert pitch to freq\nimport ddf.minim.ugens.*;\nString keyLetter = \"A\", keyOctave = \"3\";\n\nvoid setup() {\n  size(512, 512);\n\n  // initialize XYscope with default sound out\n  xy = new XYscope(this);\n}\n\nvoid draw() {\n  background(0);\n  xy.clearWaves(); // clear waves\n\n  keyboardNotes(); // use keyboard to set note pitch (freq)\n\n  xy.ellipse(width/2, width/2, width/2, width/2);\n\n  xy.buildWaves(); // build audio from shapes\n  xy.drawAll(); // draw all analytics\n}\n\nvoid keyboardNotes() {\n  if (keyPressed) {\n    if (key == 'a' || key == 'b' || key == 'c' || key == 'd' || key == 'e' || key == 'f' || key == 'g') {\n      keyLetter = (key+\"\").toUpperCase();\n    }\n    if (key == '1' || key == '2' || key == '3' || key == '4' || key == '5' || key == '6' || key == '7' || key == '8' || key == '9' || key == '0') {\n      keyOctave = key + \"\";\n    }\n    xy.freq(Frequency.ofPitch(keyLetter+keyOctave).asHz());\n  }\n}\n"
  },
  {
    "path": "examples/9_misc/freq_amp_modulation/freq_amp_modulation.pde",
    "content": "/* \n freq_amp_modulation\n Basic modulation of amp + freq of waves\n Wild effects can be had by changing in unique ways\n cc teddavis.org 2017\n */\n\nimport ddf.minim.*; // minim req to gen audio\nimport xyscope.*;   // import XYscope\nXYscope xy;         // create XYscope instance\n\nvoid setup() {\n  size(512, 512);\n\n  // initialize XYscope with default sound out\n  xy = new XYscope(this);\n}\n\n\nvoid draw() {\n  background(0);\n\n  // clear waves like refreshing background\n  xy.clearWaves();\n\n  // modulate amp with mouseX left - right\n  xy.amp(norm(mouseY, height, 0));\n\n  // modulate just one value for fun scaling\n  // xy.ampX(norm(mouseX, 0, width));\n  // xy.ampY(norm(mouseX, 0, width));\n\n  // modulate freq with mouseY up - down\n  xy.freq(map(mouseX, 0, width, 0, 440));\n\n  // draw house\n  drawHouse();\n\n  // build audio from shapes\n  xy.buildWaves();\n\n  // draw all analytics\n  xy.drawAll();\n}\n\nvoid drawHouse() {\n  pushMatrix();\n  translate(width*.5, height*.75);\n  float scl = 2;\n  xy.beginShape();\n  xy.vertex(-width*.1*scl, 0);\n  xy.vertex(width*.1*scl, 0);\n  xy.vertex(width*.1*scl, -height*.2*scl);\n  xy.vertex(0, -height*.3*scl);\n  xy.vertex(-width*.1*scl, -height*.2*scl);\n  xy.vertex(-width*.1*scl, 0);\n  xy.vertex(-width*.05*scl, 0);\n  xy.vertex(-width*.05*scl, -height*.1*scl);\n  xy.vertex(-width*.0*scl, -height*.1*scl);\n  xy.vertex(-width*.0*scl, 0);\n  xy.endShape();\n  popMatrix();\n}\n"
  },
  {
    "path": "examples/9_misc/multiscopes_class/multiscopes_class.pde",
    "content": "/* \n multiscopes_class\n Control as many oscilloscopes as you have audio card outputs.\n Create Aggregate devices in Utilities » Audio/Midi \n for pairs of stereo devices from multi-channel DAC\n cc teddavis.org 2017\n */\n\nimport ddf.minim.*; // minim req to gen audio\nimport xyscope.*;   // import XYscope\nXYscope xy;         // create XYscope instance\n\n// how many scopes, audio channels to drive?\nint scopeCount = 4;\n\n// replace with your own aggregate device names\n//String[] mixerName = {\"MK3_12\", \"MK3_34\", \"MK3_56\", \"MK3_78\"};\nString[] mixerName = {\"\", \"\", \"\", \"\"};\n\n// custom class below\nScopeDraw[] sd = new ScopeDraw[scopeCount];\n\nvoid setup() {\n  size(512, 512);\n  background(0);\n\n  // initiate class instances (\n  // XYscope is within class\n  for (int i=0; i<sd.length; i++) {\n    sd[i] = new ScopeDraw(this, mixerName[i]);\n  }\n}\n\nvoid draw() {\n  background(0);\n\n  // run each class in draw\n  for (int i=0; i<sd.length; i++) {\n    sd[i].display();\n  }\n}\n\nvoid keyPressed() {\n  // pass keyPressed into class\n  for (int i=0; i<sd.length; i++) {\n    sd[i].checkKey(keyCode);\n  }\n}\n\nclass ScopeDraw {\n  // create instance of XYscope\n  XYscope xy;\n\n  float x, y, s, v;\n\n  // tell class it's parent, needed for XYscope + mixer\n  ScopeDraw(PApplet theParent, String scopeID) {\n\n    // initialize XYscope with custom sound out\n    xy = new XYscope(theParent, scopeID);\n\n    v = random(1, 10);\n    s = floor(random(10, 100));\n    x = -s;\n    y = height/2-s/2;\n  }\n\n  void display() {\n    // clear waves like refreshing background\n    xy.clearWaves();\n\n    x += v;\n    if (x > width) {\n      x = -s;\n      v = random(1, 10);\n    }\n\n    xy.rect(x, y, s, s);\n\n    // build audio from shapes\n    xy.buildWaves();\n\n    // draw just two analytics\n    xy.drawWave();\n    xy.drawXY();\n  }\n\n  // process keyPressed\n  void checkKey(int keyC) {\n    if (keyC == 38) { // UP\n      xy.freq(xy.freq().x+.5);\n    } else if (keyC == 40) { // DOWN\n      xy.freq(xy.freq().x-.5);\n    }\n  }\n}\n"
  },
  {
    "path": "license.txt",
    "content": "\t\t   GNU LESSER GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n\n  This version of the GNU Lesser General Public License incorporates\nthe terms and conditions of version 3 of the GNU General Public\nLicense, supplemented by the additional permissions listed below.\n\n  0. Additional Definitions. \n\n  As used herein, \"this License\" refers to version 3 of the GNU Lesser\nGeneral Public License, and the \"GNU GPL\" refers to version 3 of the GNU\nGeneral Public License.\n\n  \"The Library\" refers to a covered work governed by this License,\nother than an Application or a Combined Work as defined below.\n\n  An \"Application\" is any work that makes use of an interface provided\nby the Library, but which is not otherwise based on the Library.\nDefining a subclass of a class defined by the Library is deemed a mode\nof using an interface provided by the Library.\n\n  A \"Combined Work\" is a work produced by combining or linking an\nApplication with the Library.  The particular version of the Library\nwith which the Combined Work was made is also called the \"Linked\nVersion\".\n\n  The \"Minimal Corresponding Source\" for a Combined Work means the\nCorresponding Source for the Combined Work, excluding any source code\nfor portions of the Combined Work that, considered in isolation, are\nbased on the Application, and not on the Linked Version.\n\n  The \"Corresponding Application Code\" for a Combined Work means the\nobject code and/or source code for the Application, including any data\nand utility programs needed for reproducing the Combined Work from the\nApplication, but excluding the System Libraries of the Combined Work.\n\n  1. Exception to Section 3 of the GNU GPL.\n\n  You may convey a covered work under sections 3 and 4 of this License\nwithout being bound by section 3 of the GNU GPL.\n\n  2. Conveying Modified Versions.\n\n  If you modify a copy of the Library, and, in your modifications, a\nfacility refers to a function or data to be supplied by an Application\nthat uses the facility (other than as an argument passed when the\nfacility is invoked), then you may convey a copy of the modified\nversion:\n\n   a) under this License, provided that you make a good faith effort to\n   ensure that, in the event an Application does not supply the\n   function or data, the facility still operates, and performs\n   whatever part of its purpose remains meaningful, or\n\n   b) under the GNU GPL, with none of the additional permissions of\n   this License applicable to that copy.\n\n  3. Object Code Incorporating Material from Library Header Files.\n\n  The object code form of an Application may incorporate material from\na header file that is part of the Library.  You may convey such object\ncode under terms of your choice, provided that, if the incorporated\nmaterial is not limited to numerical parameters, data structure\nlayouts and accessors, or small macros, inline functions and templates\n(ten or fewer lines in length), you do both of the following:\n\n   a) Give prominent notice with each copy of the object code that the\n   Library is used in it and that the Library and its use are\n   covered by this License.\n\n   b) Accompany the object code with a copy of the GNU GPL and this license\n   document.\n\n  4. Combined Works.\n\n  You may convey a Combined Work under terms of your choice that,\ntaken together, effectively do not restrict modification of the\nportions of the Library contained in the Combined Work and reverse\nengineering for debugging such modifications, if you also do each of\nthe following:\n\n   a) Give prominent notice with each copy of the Combined Work that\n   the Library is used in it and that the Library and its use are\n   covered by this License.\n\n   b) Accompany the Combined Work with a copy of the GNU GPL and this license\n   document.\n\n   c) For a Combined Work that displays copyright notices during\n   execution, include the copyright notice for the Library among\n   these notices, as well as a reference directing the user to the\n   copies of the GNU GPL and this license document.\n\n   d) Do one of the following:\n\n       0) Convey the Minimal Corresponding Source under the terms of this\n       License, and the Corresponding Application Code in a form\n       suitable for, and under terms that permit, the user to\n       recombine or relink the Application with a modified version of\n       the Linked Version to produce a modified Combined Work, in the\n       manner specified by section 6 of the GNU GPL for conveying\n       Corresponding Source.\n\n       1) Use a suitable shared library mechanism for linking with the\n       Library.  A suitable mechanism is one that (a) uses at run time\n       a copy of the Library already present on the user's computer\n       system, and (b) will operate properly with a modified version\n       of the Library that is interface-compatible with the Linked\n       Version. \n\n   e) Provide Installation Information, but only if you would otherwise\n   be required to provide such information under section 6 of the\n   GNU GPL, and only to the extent that such information is\n   necessary to install and execute a modified version of the\n   Combined Work produced by recombining or relinking the\n   Application with a modified version of the Linked Version. (If\n   you use option 4d0, the Installation Information must accompany\n   the Minimal Corresponding Source and Corresponding Application\n   Code. If you use option 4d1, you must provide the Installation\n   Information in the manner specified by section 6 of the GNU GPL\n   for conveying Corresponding Source.)\n\n  5. Combined Libraries.\n\n  You may place library facilities that are a work based on the\nLibrary side by side in a single library together with other library\nfacilities that are not Applications and are not covered by this\nLicense, and convey such a combined library under terms of your\nchoice, if you do both of the following:\n\n   a) Accompany the combined library with a copy of the same work based\n   on the Library, uncombined with any other library facilities,\n   conveyed under the terms of this License.\n\n   b) Give prominent notice with the combined library that part of it\n   is a work based on the Library, and explaining where to find the\n   accompanying uncombined form of the same work.\n\n  6. Revised Versions of the GNU Lesser General Public License.\n\n  The Free Software Foundation may publish revised and/or new versions\nof the GNU Lesser General Public License from time to time. Such new\nversions will be similar in spirit to the present version, but may\ndiffer in detail to address new problems or concerns.\n\n  Each version is given a distinguishing version number. If the\nLibrary as you received it specifies that a certain numbered version\nof the GNU Lesser General Public License \"or any later version\"\napplies to it, you have the option of following the terms and\nconditions either of that published version or of any later version\npublished by the Free Software Foundation. If the Library as you\nreceived it does not specify a version number of the GNU Lesser\nGeneral Public License, you may choose any version of the GNU Lesser\nGeneral Public License ever published by the Free Software Foundation.\n\n  If the Library as you received it specifies that a proxy can decide\nwhether future versions of the GNU Lesser General Public License shall\napply, that proxy's public statement of acceptance of any version is\npermanent authorization for you to choose that version for the\nLibrary."
  },
  {
    "path": "resources/README.md",
    "content": "## How to install ##library.name##\n\n### Install with the Contribution Manager\n\nAdd contributed Libraries by selecting the menu item _Sketch_ → _Import Library..._ → _Add Library..._ This will open the Contribution Manager, where you can browse for ##library.name##, or any other Library you want to install.\n\nNot all available Libraries have been converted to show up in this menu. If a Library isn't there, it will need to be installed manually by following the instructions below.\n\n### Manual Install\n\nContributed Libraries may be downloaded separately and manually placed within the `libraries` folder of your Processing sketchbook. To find (and change) the Processing sketchbook location on your computer, open the Preferences window from the Processing application (PDE) and look for the \"Sketchbook location\" item at the top.\n\nBy default the following locations are used for your sketchbook folder: \n  * For Mac users, the sketchbook folder is located inside `~/Documents/Processing` \n  * For Windows users, the sketchbook folder is located inside `My Documents/Processing`\n\nDownload ##library.name## from ##library.url##\n\nUnzip and copy the contributed Library's folder into the `libraries` folder in the Processing sketchbook. You will need to create this `libraries` folder if it does not exist.\n\nThe folder structure for Library ##library.name## should be as follows:\n\n```\nProcessing\n  libraries\n    ##library.name##\n      examples\n      library\n        ##library.name##.jar\n      reference\n      src\n```\n             \nSome folders like `examples` or `src` might be missing. After Library ##library.name## has been successfully installed, restart the Processing application.\n\n### Troubleshooting\n\nIf you're having trouble, have a look at the [Processing Wiki](https://github.com/processing/processing/wiki/How-to-Install-a-Contributed-Library) for more information, or contact the author [##author.name##](##author.url##).\n"
  },
  {
    "path": "resources/build.properties",
    "content": "# Create a Library for the Processing open source programming language and \n# environment (http://processing.org/)\n#\n# Customize the build properties to make the ant-build-process work for your \n# environment. How? Please read the comments below.\n#\n# The default properties are set for OS X. Please refer to comments for Windows\n# settings.\n\n\n# Where is your Processing sketchbook located? \n# If you are not sure, check the sketchbook location in your Processing \n# application preferences.\n# ${user.home} points the compiler to your home directory.\n# For windows the default path to your sketchbook would be\n# ${user.home}/My Documents/Processing (make adjustments below)\n\n#sketchbook.location=${user.home}/My Documents/Processing\nsketchbook.location=${user.home}/Documents/Processing\n\n\n# Where are the jar files located that are required for compiling your Library \n# such as e.g. core.jar?\n# By default the local classpath location points to folder libs inside Eclipse's\n# workspace (by default found in your home directory).\n# For Windows, the default path would be\n# ${user.home}/Documents/workspace/libs (make adjustments below)\n# For OS X,the following path will direct you into Processing's application\n# package, in case you put Processing inside your Applications folder. \n\nclasspath.local.location=lib\n#classpath.local.location=/Applications/Processing_3_3_2.app/Contents/Java/core/library\n#${user.home}/Documents/workspace/\n\n# Add all jar files that are required for compiling your project to the local \n# and project classpath. Use a comma as delimiter. These jar files must be \n# inside your classpath.local.location folder.\n\nclasspath.local.include=core.jar\n\n\n# Add Processing's libraries folder to the classpath.\n# If you don't need to include the libraries folder to your classpath, comment \n# out the following line.\n\n#classpath.libraries.location=${sketchbook.location}/libraries\nclasspath.libraries.location=\n\n# Set the java version that should be used to compile your Library.\n\njava.target.version=17\n\n\n# Set the description of the Ant build.xml file.\n\nant.description=Processing Library Ant build file.\n\n\n# Give your Library a name. The name must not contain spaces or special \n# characters.\n\nproject.name=XYscope\n\n\n# The name as the user will see it. This can contain spaces and special \n# characters.\n\nproject.prettyName=XYscope\n\n\n# Use 'normal' or 'fast' as value for project.compile.\n# 'fast' will only compile the project into your sketchbook.\n# 'normal' will compile the distribution including the javadoc-reference and all\n# web-files (the compile process here takes longer).\n# All files compiled with project.compile=normal are stored in the distribution \n# folder.\n\nproject.compile=normal\n\n\n# Set your name and URL, used for the web page and properties file.\n\nauthor.name=Ted Davis\nauthor.url=https://www.teddavis.org\n\n\n# Set the web page for your Library.\n# This is NOT a direct link to where to download it.\n\nlibrary.url=https://teddavis.org/xyscope\n\n\n# Set the category (or categories) of your Library from the following list:\n#   \"3D\"            \"Animation\"     \"Compilations\"      \"Data\"          \n#   \"Fabrication\"   \"Geometry\"      \"GUI\"               \"Hardware\"      \n#   \"I/O\"           \"Language\"      \"Math\"              \"Simulation\"    \n#   \"Sound\"         \"Utilities\"     \"Typography\"        \"Video & Vision\"\n# \n# If a value other than those listed is used, your Library will listed as \n# \"Other\". Many categories must be comma-separated.\n\nlibrary.categories=Animation, Sound, Hardware\n\n\n# A short sentence (or fragment) to summarize the Library's function. This will \n# be shown from inside the PDE when the Library is being installed. Avoid \n# repeating the name of your Library here. Also, avoid saying anything redundant \n# like mentioning that it's a Library. This should start with a capitalized \n# letter, and end with a period.\n\nlibrary.sentence= XYScope is a library for Processing to render graphics on a vector display (oscilloscope, laser) by converting them to audio.\n\n\n# Additional information suitable for the Processing website. The value of\n# 'sentence' always will be prepended, so you should start by writing the\n# second sentence here. If your Library only works on certain operating systems,\n# mention it here.\n\nlibrary.paragraph= This includes most primitive shapes (point, line, rect, ellipse, vertex, box, sphere, torus...) by converting those points to waveforms (oscillators with custom wavetables) and generating audio in real time using the Minim library. Vector graphics shine on a vector display and now you can view your generative works like never before! Tested on MacOS 10.9+ / Windows / Linux (RPi!).\nlibrary.acknowledgements= Acknowledgements:<br><a href=\"http://dailydrawbot.tumblr.com\" target=\"_blank\">Just Van Rossum</a> for the enlightening conversation on my X-Y attempts.<br><a href=\"https://stefaniebraeuer.ch/\" target=\"_blank\">Stefanie Bruer</a> for feeding the obsession with crucial theory + context.<br><a href=\"https://asdfg.me\" target=\"_blank\">Hansi Raber</a> for Java meta insights + finding external WaveTable bug!\n\n# Set the source code repository for your project.\n# We recommend Bitbucket (https://bitbucket.org) or GitHub (https://github.com).\n\nsource.host=GitHub\nsource.url=https://github.com/ffd8/xyscope\nsource.repository=https://github.com/ffd8/xyscope.git\n\n\n# The current version of your Library. \n# This number must be parsable as an int. It increments once with each release. \n# This is used to compare different versions of the same Library, and check if \n# an update is available.\n\nlibrary.version=5\n\n\n# The version as the user will see it.\n\nlibrary.prettyVersion=3.0.0\n\n\n# The min and max revision of Processing compatible with your Library.\n# Note that these fields use the revision and not the version of Processing, \n# parsable as an int. For example, the revision number for 2.2.1 is 227. \n# You can find the revision numbers in the change log: https://raw.githubusercontent.com/processing/processing/master/build/shared/revisions.txt\n# Only use maxRevision (or minRevision), when your Library is known to \n# break in a later (or earlier) release. Otherwise, use the default value 0.\n\ncompatible.minRevision=0\ncompatible.maxRevision=0\n\n\n# The platforms and Processing version that the Library has been tested\n# against. This information is only used in the generated webpage.\n\ntested.platform=MacOS, Windows, Linux\ntested.processingVersion=3.3.7\n\n\n# Additional information for the generated webpage.\n\nlibrary.copyright=(cc) 2017-23\nlibrary.dependencies=Minim + (Geomerative, OpenCV, openkinect, Video for xtra_ examples)\nlibrary.keywords=oscilloscope, x-y mode, laser, vectrex, vector, vector display, vector monitor, vector graphics\n\n\n# Include javadoc references into your project's javadocs.\n\n#javadoc.java.href=http://docs.oracle.com/javase/7/docs/api/\njavadoc.java.href=http://docs.oracle.com/javase/8/docs/api/\njavadoc.processing.href=http://processing.github.io/processing-javadocs/core/\n"
  },
  {
    "path": "resources/build.xml",
    "content": "<project name=\"Processing Library\" default=\"clean\" basedir=\"../\">\n\n    \n<!--\t\n\t\tProperties for your project should be set and modified in the  \n\t\tbuild.properties file (located in the same folder as this build.xml file).\n\t\t\n\t\tTHIS FILE SHOULD NOT BE EDITED, unless you know what you are doing.\n\t\tIf you have recommendations for improvements, please let Elie know \n\t\tat prisonerjohn@gmail.com\n-->\n    \n\t\n\n\t<property file=\"./resources/build.properties\" />\n\t\n\t<description>\n        ${ant.description}\n    </description>\n    \t\n\t\n\t\n    \n\t<property name=\"line\" value=\"------------------------------------------------------------------------------------------------\" />\n\t\n\t<condition property=\"is.normal\">\n\t\t<equals arg1=\"${project.compile}\" arg2=\"normal\" />\n\t</condition>\n\t\n\t\n\t<!-- set the OS properties -->\n\t<condition property=\"is.mac\">\n        <os family=\"mac\" />\n\t</condition>\n\t<condition property=\"is.windows\">\n        <os family=\"windows\" />\n\t</condition>\n\t<condition property=\"is.unix\">\n        <os family=\"unix\" />\n\t</condition>\n\t\n\t\n\t<property name=\"project.jar.name\" value=\"${project.name}.jar\"/>\n\t<property name=\"project.src\" location=\"src\"/>\n\t<property name=\"project.tmp\" location=\"tmp\"/>\n\t<property name=\"project.web\" location=\"web\"/>\n\t<property name=\"project.data\" location=\"data\"/>\n\t<property name=\"project.lib\" location=\"lib\"/>\n\t<property name=\"project.bin\" location=\"bin\"/>\n\t<property name=\"project.bin.data\" location=\"${project.bin}/data\"/>\n\t<property name=\"project.examples\" location=\"examples\"/>\n\t<property name=\"project.reference\" location=\"reference\"/>\n\t<property name=\"project.dist\" location=\"distribution\"/>\n\t<property name=\"project.dist.version\" location=\"distribution/${project.name}-${library.version}\"/>\n\t<property name=\"project.changesource\" location=\"changelog.txt\"/>\n\t<property name=\"project.changedest\" location=\"${project.dist.version}/changelog.txt\"/>\n\t<property name=\"install.source\" location=\"resources/README.md\"/>\n\t<property name=\"install.destination\" location=\"${project.dist.version}/README.md\"/>\n\t<property name=\"libprops.source\" location=\"resources/library.properties\"/>\n\t\n\t<taskdef resource=\"net/sf/antcontrib/antcontrib.properties\">\n\t\t<classpath>\n\t\t\t<pathelement location=\"./resources/code/ant-contrib-1.0b3.jar\"/>\n\t\t</classpath>\n\t</taskdef>\n\n\n\t<path id=\"classpath\">\n\t\t<fileset dir=\"${classpath.local.location}\" includes=\"${classpath.local.include}\" />\n\t\t<fileset dir=\"${classpath.libraries.location}\" includes=\"**/*.jar\" />\n\t\t<fileset dir=\"${project.lib}\" includes=\"**/*.jar\" />\n\t</path>\t\t\n\n\t\n\n\t<!-- Create the time stamp -->\n\t<tstamp> \n\t\t<format property=\"date\" pattern=\"MM/dd/yyyy\" offset=\"0\" unit=\"hour\"/>\n\t</tstamp>\n\t\n\t<target name=\"init\"> \n\t\t<echo>${line}\n    Building the Processing Library ${project.name} ${library.version}\n${line}\n\tsrc path        ${project.src}\n\tbin path        ${project.bin}\n\tclasspath.local\t${classpath.local.location}\n\tsketchbook      ${sketchbook.location}\n\tjava version    ${java.target.version}\n${line}\n\t</echo>\n\t\n\t\n\t<mkdir dir=\"${project.bin}\"/>\n\t</target>\n\t\n\t\n\t\n\t<target name=\"library.init\" depends=\"init\"> \n\t\t<echo message=\"init library ...\" />\t\n\t</target>\n\t\n\t\n\t\n\t<target name=\"library.run\" depends=\"library.init\"> \n\t\t<echo message=\"building library ...\" />\n\t\t<antcall target=\"generate.structure\"><param name=\"folder\" value=\"library\"/></antcall>\n\t\t<antcall target=\"generate.source\" />\n\t\t<antcall target=\"compile\" />\n\t  \t<antcall target=\"generate.jar\"><param name=\"folder\" value=\"library\"/></antcall>\n\t  \t<antcall target=\"generate.javadoc\" />\n\t\t<antcall target=\"generate.libprops\" />\n\t\t<antcall target=\"copyToSketchbook\"><param name=\"folder\" value=\"libraries\"/></antcall>\n\t\t<antcall target=\"generate.distribution\" />\n\t\t<antcall target=\"generate.install.library\" />\n\t\t<antcall target=\"generate.project.changelog\" />\n\t\t<antcall target=\"generate.web\" />\n\t  \t<antcall target=\"generate.zip\" />\t\n\t  \t<delete dir=\"${project.tmp}\"/>\n\t</target>\n\t\n\t\n\t\n\t<target name=\"generate.libprops\" if=\"is.normal\">\n\t\t<property name=\"libprops.destination\" location=\"${project.tmp}/${project.name}/library.properties\"/>\n\t\t<copy file=\"${libprops.source}\" tofile=\"${libprops.destination}\" />\n\t\t<antcall target=\"parse.file\"><param name=\"file\" value=\"${libprops.destination}\"/></antcall>\n\t</target>\n\t\t\n\t\n\t\n\t<target name=\"copyToSketchbook\">\n\t\t<echo message=\"copying files to the ${folder} folder in your sketchbook.\" />\n\t\t<!-- copy the jar file to processing's sketchbook libraries folder -->\n\t\t<delete dir=\"${sketchbook.location}/${folder}/${project.name}\" />\n  \t  \t<mkdir dir=\"${sketchbook.location}/${folder}/${project.name}\" />\n  \t\t<copy todir=\"${sketchbook.location}/${folder}/${project.name}\">\n  \t\t\t<fileset dir=\"${project.tmp}/${project.name}\"/>\n  \t\t</copy> \n\t</target>\n\t\n\t\n\t<target name=\"compile\">\n\t\t<javac srcdir=\"${project.tmp}/${project.name}/src\" destdir=\"${project.bin}\" source=\"${java.target.version}\" target=\"${java.target.version}\" includeantruntime=\"false\">\n\t\t\t<classpath>\n\t\t\t\t<path refid=\"classpath\"/>\n\t\t\t</classpath>\n\t\t\t<compilerarg value=\"-Xlint\"/>\n\t\t</javac>\n\t\t<copy todir=\"${project.bin.data}\">\n\t\t\t<fileset dir=\"${project.data}\" excludes=\"README\" />\n\t\t</copy>\n\t</target>\n\t\n\t\n\t<target name=\"generate.jar\">\n\t\t<jar jarfile=\"${project.tmp}/${project.name}/${folder}/${project.jar.name}\" basedir=\"${project.bin}\"/>\n\t</target>\n\t\n\t\n\t<target name=\"generate.structure\">\n\t\t<delete dir=\"${project.tmp}\" />\n\t\t<mkdir dir=\"${project.tmp}\" />\n\t\t<mkdir dir=\"${project.tmp}/${project.name}\" />\n\t  \t<mkdir dir=\"${project.tmp}/${project.name}/${folder}\" />\n  \t\t<mkdir dir=\"${project.tmp}/${project.name}/examples\" />\n\t  \t<mkdir dir=\"${project.tmp}/${project.name}/reference\" />\n  \t\t<mkdir dir=\"${project.tmp}/${project.name}/src\" />\n  \t\t<copy todir=\"${project.tmp}/${project.name}/examples\">\n  \t\t\t<fileset dir=\"${project.examples}\">\n\t  \t\t\t<exclude name=\"**/*README*\"/>\n  \t\t\t</fileset>\n\t  \t</copy>\n  \t\t<copy todir=\"${project.tmp}/${project.name}/src\">\n  \t\t\t<fileset dir=\"${project.src}\"/>\n\t  \t</copy>\n\t  \t<copy todir=\"${project.tmp}/${project.name}/${folder}\">\n  \t\t\t<fileset dir=\"${project.lib}\" excludes=\"README core.jar jsminim.jar minim.jar\" />\n\t  \t</copy>\n\t</target>\n\t\n\t<target name=\"generate.source\" if=\"is.normal\">\n\t\t<antcall target=\"generate.source.win\"/>\n\t\t<antcall target=\"generate.source.nix\"/>\n\t</target>\n\t\n\t\n\t<!-- These two targets are pretty much the same, except for the delimiter (can't find a better way of doing this) -->\n\t<target name=\"generate.source.win\" if=\"is.windows\">\n\t\t<echo message=\"generating source (windows) ...\"/>\n\t    <path id=\"src.contents\"><fileset dir=\"${project.tmp}/${project.name}/src\" includes=\"**/*.java\" /></path>\n\t    <property name=\"src.list\" refid=\"src.contents\" />\n\t    <foreach list=\"${src.list}\" param=\"file\" target=\"parse.file\" delimiter=\";\" />\n\t</target>\n\t<target name=\"generate.source.nix\" unless=\"is.windows\">\n\t\t<echo message=\"generating source (mac/linux) ...\"/>\n\t\t<path id=\"src.contents\"><fileset dir=\"${project.tmp}/${project.name}/src\" includes=\"**/*.java\" /></path>\n\t    <property name=\"src.list\" refid=\"src.contents\" />\n\t    <foreach list=\"${src.list}\" param=\"file\" target=\"parse.file\" delimiter=\":\" />\n\t</target>\n\n\t\n\t<target name=\"generate.distribution\" if=\"is.normal\">\n\t\t<mkdir dir=\"${project.dist}\"/>\n\t\t<delete dir=\"${project.dist.version}\"/>\n\t\t<mkdir dir=\"${project.dist.version}\" />\n\t\t<mkdir dir=\"${project.dist.version}/${project.name}\" />\n\t\t<move file=\"${project.tmp}/${project.name}\" toDir=\"${project.dist.version}\" />\n\t</target>\n\t\n\t\n\t\n\t<target name=\"generate.javadoc\" if=\"is.normal\">\n\t  \t<!-- create the java reference of the Library -->\n\t\t<javadoc bottom=\"Processing Library ${project.name} by ${author.name}. ${library.copyright}\" \n\t\t\t\tclasspath=\"${classpath.local.location}/core.jar;{project.bin}\" \n\t\t\t\tdestdir=\"${project.tmp}/${project.name}/reference\" \n\t\t\t\tverbose=\"false\" \n\t\t\t\tstylesheetfile=\"resources/stylesheet.css\" \n\t\t\t\tdoctitle=\"Javadocs: ${project.name}\" \n\t\t\t\tpublic=\"true\" version=\"false\" \n\t\t\t\twindowtitle=\"Javadocs: ${project.name}\">\n \t\t\t\n \t\t\t<link href=\"${javadoc.java.href}\" />\n\t\t\t<link href=\"${javadoc.processing.href}\" />\n\t\t\t<!-- <taglet name=\"ExampleTaglet\" path=\"resources/code\" /> -->\n \t\t\t<fileset dir=\"${project.tmp}/${project.name}/src\" defaultexcludes=\"yes\">\n\t\t\t<!-- add packages to be added to reference. -->\n\t\t\t\t<include name=\"**/*\"/>\n\t\t\t</fileset>\n\t \t</javadoc>\n\t</target>\n\t\n\t\n\t<target name=\"generate.web\" if=\"is.normal\">\n\t\t\n\t\t<mkdir dir=\"${project.dist.version}/web\" />\n  \t\t<copy todir=\"${project.dist.version}/web/reference\">\n  \t\t\t<fileset dir=\"${project.dist.version}/${project.name}/reference\" />\n  \t\t</copy>\n  \t\n  \t\t<copy todir=\"${project.dist.version}/web/examples\">\n  \t\t\t<fileset dir=\"${project.dist.version}/${project.name}/examples\" />\n\t  \t</copy>\n\t  \t\n\t  \t<copy todir=\"${project.dist.version}/web\">\n  \t\t\t<fileset dir=\"${project.web}\" />\n\t  \t</copy>\n\t  \t<copy todir=\"${project.dist.version}/reference\">\n  \t\t\t<fileset dir=\"${project.web}\" />\n\t  \t</copy>\n\t  \t\n\t  \t<antcall target=\"parse.file\"><param name=\"file\" value=\"${project.dist.version}/web/index.html\"/></antcall>\n\t\t\n\t\t<antcall target=\"processExamples\" />\n\t\t\n\t\t<replaceregexp file=\"${project.dist.version}/web/index.html\" match=\"##examples##\" replace=\"\" flags=\"g\" />\n\t</target>\n\t\n\t\n    \n    <!-- find and replace ##placeholder## keywords in a file -->\n    <target name=\"parse.file\">\n        <echo message=\"${file}\" />\n        \n        <replaceregexp file=\"${file}\" match=\"##date##\" replace=\"${date}\" flags=\"g\" />\n        <replaceregexp file=\"${file}\" match=\"##copyright##\" replace=\"${library.copyright}\" flags=\"g\" />\n        \n    \t<replaceregexp file=\"${file}\" match=\"##author##\" replace=\"${author.name} ${author.url}\" flags=\"g\" />\n    \t<replaceregexp file=\"${file}\" match=\"##author.name##\" replace=\"${author.name}\" flags=\"g\" />\n    \t<replaceregexp file=\"${file}\" match=\"##author.url##\" replace=\"${author.url}\" flags=\"g\" />\n    \t        \n    \t<replaceregexp file=\"${file}\" match=\"##library.name##\" replace=\"${project.prettyName}\" flags=\"g\" />\n    \t<replaceregexp file=\"${file}\" match=\"##project.name##\" replace=\"${project.name}\" flags=\"g\" />\n    \t        \n        <replaceregexp file=\"${file}\" match=\"##library.version##\" replace=\"${library.version}\" flags=\"g\" />\n        <replaceregexp file=\"${file}\" match=\"##library.prettyVersion##\" replace=\"${library.prettyVersion}\" flags=\"g\" /> \n        \n        <replaceregexp file=\"${file}\" match=\"##compatible.minRevision##\" replace=\"${compatible.minRevision}\" flags=\"g\" />\n        <replaceregexp file=\"${file}\" match=\"##compatible.maxRevision##\" replace=\"${compatible.maxRevision}\" flags=\"g\" /> \n        \n        <replaceregexp file=\"${file}\" match=\"##library.url##\" replace=\"${library.url}\" flags=\"g\" />\n        <replaceregexp file=\"${file}\" match=\"##library.categories##\" replace=\"${library.categories}\" flags=\"g\" />\n        <replaceregexp file=\"${file}\" match=\"##library.sentence##\" replace=\"${library.sentence}\" flags=\"g\" />\n    \t<replaceregexp file=\"${file}\" match=\"##library.paragraph##\" replace=\"${library.paragraph}\" flags=\"g\" />\n    \t<replaceregexp file=\"${file}\" match=\"##library.acknowledgements##\" replace=\"${library.acknowledgements}\" flags=\"g\" />\n    \t<replaceregexp file=\"${file}\" match=\"##library.keywords##\" replace=\"${library.keywords}\" flags=\"g\" />\n        <replaceregexp file=\"${file}\" match=\"##library.dependencies##\" replace=\"${library.dependencies}\" flags=\"g\" />\n    \t\n    \t<replaceregexp file=\"${file}\" match=\"##source.host##\" replace=\"${source.host}\" flags=\"g\" />\n    \t<replaceregexp file=\"${file}\" match=\"##source.url##\" replace=\"${source.url}\" flags=\"g\" />\n    \t<replaceregexp file=\"${file}\" match=\"##source.repository##\" replace=\"${source.repository}\" flags=\"g\" />\n    \t\n    \t<replaceregexp file=\"${file}\" match=\"##tested.platform##\" replace=\"${tested.platform}\" flags=\"g\" />\n    \t<replaceregexp file=\"${file}\" match=\"##tested.processingVersion##\" replace=\"${tested.processingVersion}\" flags=\"g\" />\n    </target>\n\t\n\t\n\t\n\t<target name=\"generate.install.library\" if=\"is.normal\">\t\n\t\t<copy file=\"${install.source}\" tofile=\"${install.destination}\" />\n\t\t\n\t\t<antcall target=\"parse.file\"><param name=\"file\" value=\"${install.destination}\"/></antcall>\n\t</target>\t\n\t\n\t<target name=\"generate.project.changelog\" if=\"is.normal\">\t\n\t\t<copy file=\"${project.changesource}\" tofile=\"${project.changedest}\" />\n\t\t\n\t\t<antcall target=\"parse.file\"><param name=\"file\" value=\"${project.changedest}\"/></antcall>\n\t</target>\t\n\t\n\t\t\n\n\t\n\t\n\t\n\t<target name=\"generate.zip\" if=\"is.normal\">\n\t\t<!-- zip the distribution of the Library -->\n\t\t\n\t\t<move todir=\"${project.dist.version}/tmp/${project.name}\">\n  \t\t\t<fileset dir=\"${project.dist.version}/${project.name}\" />\n  \t\t</move>\n  \t\t\n  \t\t<copy file=\"${project.dist.version}/tmp/${project.name}/library.properties\" tofile=\"${project.dist.version}/web/download/${project.name}.txt\" />\n\t\t        \n\t\t<zip destfile=\"${project.dist.version}/${project.name}.zip\"\n  \t       basedir=\"${project.dist.version}/tmp\"\n  \t       excludes=\"**/.DS_Store\"\n\t\t/>\n\t\t\n        <move file=\"${project.dist.version}/${project.name}.zip\" todir=\"${project.dist.version}/web/download\" />\n\t\t\n\t\t<copy file=\"${project.dist.version}/web/download/${project.name}.zip\" tofile=\"${project.dist.version}/web/download/${project.name}-${library.version}.zip\" />\n\t\t<copy file=\"${project.dist.version}/web/download/${project.name}.txt\" tofile=\"${project.dist.version}/web/download/${project.name}-${library.version}.txt\" />\n\t\t\n\t\t<move todir=\"${project.dist.version}\">\n\t\t\t<fileset dir=\"${project.dist.version}/web\" />\n\t\t</move>\n\t\t\n\t\t<delete dir=\"${project.dist.version}/tmp\" />\n\t</target>\n\t\n\t\n\t\n\t<!-- parsing the examples folder -->\n\t<target name=\"processExamples\">\n\t\t<dirset id=\"examples.contents\" dir=\"${project.examples}\" excludes=\"*/*\"/>\n\t\t<property name=\"examples.list\" refid=\"examples.contents\"/>\n\t\t<foreach list=\"${examples.list}\" target=\"addExamples\" param=\"exampleDir\" delimiter=\";\">\n\t\t</foreach>\n\t</target>\n\t\t\n\t\n    \n    <target name=\"addExamples\">\n        <echo>${exampleDir}</echo>\n        <propertyregex property=\"pde\"\n              input=\"${exampleDir}\"\n              regexp=\"^.*\\/(.*)$\"\n              select=\"\\1\"\n              casesensitive=\"false\"\n              defaultValue=\"${exampleDir}\" />\n\n          <propertyregex property=\"data\"\n              input=\"${exampleDir}\"\n              regexp=\"data$\"\n              select=\"true\"\n              casesensitive=\"false\"\n              defaultValue=\"false\" />\n\n          <if>\n              <equals arg1=\"${data}\" arg2=\"false\" />\n              <then>\n                  <replaceregexp file=\"${project.dist.version}/web/index.html\"\n                      match=\"(##examples##)\"\n                      replace=\"&lt;li&gt;&lt;a href=&quot;examples/${exampleDir}/${pde}.pde&quot;&gt;${exampleDir}&lt;/a&gt;&lt;/li&gt; \\1\"\n                      flags=\"g\" />\n              </then>\n              <else>\n                  <echo message=\"Data folder, attention.\" />\n              </else>\n          </if>\n    </target>   \n    \n\t\n\t\n\t<target name=\"clean\" depends=\"library.run\">\n\t\t<delete dir=\"${project.bin}\"/>\n\t\t<delete dir=\"${project.tmp}\"/>\n\t\t<echo>\n\t\t\n${line}\nName        ${project.name} \nVersion     ${library.prettyVersion} (${library.version})\nCompiled    ${project.compile}\nSketchbook  ${sketchbook.location}\n${line}\ndone, finished.\n${line}\n\t\t</echo>\n\t</target>\n\t\n</project>\n\n"
  },
  {
    "path": "resources/code/ExampleTaglet.java",
    "content": "/*\n * Copyright 2002 Sun Microsystems, Inc. All  Rights Reserved.\n *\n * Redistribution and use in source and binary forms, with or\n * without modification, are permitted provided that the following\n * conditions are met:\n *\n * -Redistributions of source code must retain the above copyright\n *  notice, this list of conditions and the following disclaimer.\n *\n * -Redistribution in binary form must reproduce the above copyright\n *  notice, this list of conditions and the following disclaimer in\n *  the documentation and/or other materials provided with the\n *  distribution.\n *\n * Neither the name of Sun Microsystems, Inc. or the names of\n * contributors may be used to endorse or promote products derived\n * from this software without specific prior written permission.\n *\n * This software is provided \"AS IS,\" without a warranty of any\n * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND\n * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY\n * EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY\n * DAMAGES OR LIABILITIES  SUFFERED BY LICENSEE AS A RESULT OF OR\n * RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THE SOFTWARE OR\n * ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE\n * FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT,\n * SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER\n * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF\n * THE USE OF OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN\n * ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.\n *\n * You acknowledge that Software is not designed, licensed or\n * intended for use in the design, construction, operation or\n * maintenance of any nuclear facility.\n */\n\nimport com.sun.tools.doclets.Taglet;\nimport com.sun.javadoc.*;\nimport java.util.Map;\nimport java.io.*;\n/**\n * A sample Taglet representing @example. This tag can be used in any kind of\n * {@link com.sun.javadoc.Doc}.  It is not an inline tag. The text is displayed\n * in yellow to remind the developer to perform a task.  For\n * example, \"@example Hello\" would be shown as:\n * <DL>\n * <DT>\n * <B>To Do:</B>\n * <DD><table cellpadding=2 cellspacing=0><tr><td bgcolor=\"yellow\">Fix this!\n * </td></tr></table></DD>\n * </DL>\n *\n * @author Jamie Ho\n * @since 1.4\n */\n\npublic class ExampleTaglet implements Taglet {\n    \n    private static final String NAME = \"example\";\n    private static final String HEADER = \"example To Do:\";\n\n    /**\n     * Return the name of this custom tag.\n     */\n    public String getName() {\n        return NAME;\n    }\n    \n    /**\n     * Will return true since <code>@example</code>\n     * can be used in field documentation.\n     * @return true since <code>@example</code>\n     * can be used in field documentation and false\n     * otherwise.\n     */\n    public boolean inField() {\n        return true;\n    }\n\n    /**\n     * Will return true since <code>@example</code>\n     * can be used in constructor documentation.\n     * @return true since <code>@example</code>\n     * can be used in constructor documentation and false\n     * otherwise.\n     */\n    public boolean inConstructor() {\n        return true;\n    }\n    \n    /**\n     * Will return true since <code>@example</code>\n     * can be used in method documentation.\n     * @return true since <code>@example</code>\n     * can be used in method documentation and false\n     * otherwise.\n     */\n    public boolean inMethod() {\n        return true;\n    }\n    \n    /**\n     * Will return true since <code>@example</code>\n     * can be used in method documentation.\n     * @return true since <code>@example</code>\n     * can be used in overview documentation and false\n     * otherwise.\n     */\n    public boolean inOverview() {\n        return true;\n    }\n\n    /**\n     * Will return true since <code>@example</code>\n     * can be used in package documentation.\n     * @return true since <code>@example</code>\n     * can be used in package documentation and false\n     * otherwise.\n     */\n    public boolean inPackage() {\n        return true;\n    }\n\n    /**\n     * Will return true since <code>@example</code>\n     * can be used in type documentation (classes or interfaces).\n     * @return true since <code>@example</code>\n     * can be used in type documentation and false\n     * otherwise.\n     */\n    public boolean inType() {\n        return true;\n    }\n    \n    /**\n     * Will return false since <code>@example</code>\n     * is not an inline tag.\n     * @return false since <code>@example</code>\n     * is not an inline tag.\n     */\n    \n    public boolean isInlineTag() {\n        return false;\n    }\n    \n    /**\n     * Register this Taglet.\n     * @param tagletMap  the map to register this tag to.\n     */\n    public static void register(Map tagletMap) {\n       ExampleTaglet tag = new ExampleTaglet();\n       Taglet t = (Taglet) tagletMap.get(tag.getName());\n       if (t != null) {\n           tagletMap.remove(tag.getName());\n       }\n       tagletMap.put(tag.getName(), tag);\n    }\n\n    /**\n     * Given the <code>Tag</code> representation of this custom\n     * tag, return its string representation.\n     * @param tag   the <code>Tag</code> representation of this custom tag.\n     */\n    public String toString(Tag tag) {\n        return createHTML(readFile(tag.text()));\n    }\n     \n    \n    /**\n     * Given an array of <code>Tag</code>s representing this custom\n     * tag, return its string representation.\n     * @param tags  the array of <code>Tag</code>s representing of this custom tag.\n     */\n    public String toString(Tag[] tags) {\n        if (tags.length == 0) {\n            return null;\n        }\n\t\treturn createHTML(readFile(tags[0].text()));\n    }\n    \n    \n    \n    String createHTML(String theString) {\n    \tif(theString!=null) {\n        String dd = \"<script type=\\\"text/javascript\\\">\\n\" +\n        \t\t\t\"<!--\\n\"+ \n        \t\t\t\"document.getElementsByTagName('html')[0].className = 'isjs';\" +\n\t\t\t\t\t\"function toggle(dt) { var display, dd=dt; do{ dd = dd.nextSibling } while(dd.tagName!='DD'); toOpen =!dd.style.display;\" +\n  \t\t\t\t\t\"dd.style.display = toOpen? 'block':''; dt.getElementsByTagName('span')[0].innerHTML  = toOpen? '-':'+' ; }\\n\" +\n  \t\t\t\t\t\"-->\\n</script>\";\n\n\t\treturn dd+\"\\n<div id=\\\"test\\\" class=\\\"toggleList\\\">\" +\n\t\t\"<dl><dt onclick=\\\"toggle(this);\\\"><span>+</span>Example</dt>\" +\n\t\t\"<dd><pre>\"+theString+\"</pre>\" +\n\t\t\"</dd></dl></div>\";\n\t\t} \n    \treturn \"\";\n    }\n    \n    \n    /**\n     * check if the examples directory exists and return the example as given in the tag.\n     * @param theExample the name of the example\n     */\n     String readFile(String theExample) { \n\t\tString record = \"\";\n\t\tString myResult = \"\";\n\t\tint recCount = 0;\n\t\tString myDir = \"../examples\";\n\t\tFile file=new File(myDir);\n\t\tif(file.exists()==false) {\n\t\t\tmyDir = \"./examples\";\n\t\t}\n        try { \n\t\t\tFileReader fr = new FileReader(myDir+\"/\"+theExample+\"/\"+theExample+\".pde\");\n\t\t\tBufferedReader br = new BufferedReader(fr);\n\t\t\trecord = new String();\n\t\t\twhile ((record = br.readLine()) != null) {\n\t\t\t\tmyResult += record+\"\\n\";\n\t\t\t} \n\t\t} catch (IOException e) { \n\t\t\tSystem.out.println(e);\n\t\t\treturn null;\n\t\t}\n\t\treturn myResult;\n     }\n}\n\n\n"
  },
  {
    "path": "resources/code/doc.sh",
    "content": "# a shell script to create a java documentation \n# for a processing Library. \n# \n# make changes to the variables below so they \n# fit the structure of your Library\n\n# the package name of your Library\npackage=template;\n\n# source folder location\nsrc=../src;\n\n# the destination folder of your documentation\ndest=../documentation;\n\n\n# compile the java documentation\njavadoc -d $dest -stylesheetfile ./stylesheet.css -sourcepath ${src} ${package}\n"
  },
  {
    "path": "resources/library.properties",
    "content": "# More on this file here: https://github.com/processing/processing/wiki/Library-Basics\n# UTF-8 supported.\n\n# The name of your Library as you want it formatted.\nname = ##library.name##\n\n# List of authors. Links can be provided using the syntax [author name](url).\nauthors = [##author.name##](##author.url##)\n\n# A web page for your Library, NOT a direct link to where to download it.\nurl = ##library.url##\n\n# The category (or categories) of your Library, must be from the following list:\n#   \"3D\"            \"Animation\"     \"Compilations\"      \"Data\"          \n#   \"Fabrication\"   \"Geometry\"      \"GUI\"               \"Hardware\"      \n#   \"I/O\"           \"Language\"      \"Math\"              \"Simulation\"    \n#   \"Sound\"         \"Utilities\"     \"Typography\"        \"Video & Vision\"\n# \n# If a value other than those listed is used, your Library will listed as \n# \"Other\". Many categories must be comma-separated.\ncategories = ##library.categories##\n\n# A short sentence (or fragment) to summarize the Library's function. This will \n# be shown from inside the PDE when the Library is being installed. Avoid \n# repeating the name of your Library here. Also, avoid saying anything redundant \n# like mentioning that it's a Library. This should start with a capitalized \n# letter, and end with a period.\nsentence = ##library.sentence##\n\n# Additional information suitable for the Processing website. The value of\n# 'sentence' always will be prepended, so you should start by writing the\n# second sentence here. If your Library only works on certain operating systems,\n# mention it here.\nparagraph = ##library.paragraph## \n\n# Links in the 'sentence' and 'paragraph' attributes can be inserted using the\n# same syntax as for authors. \n# That is, [here is a link to Processing](http://processing.org/)\n\n# A version number that increments once with each release. This is used to \n# compare different versions of the same Library, and check if an update is \n# available. You should think of it as a counter, counting the total number of \n# releases you've had.\nversion = ##library.version##  # This must be parsable as an int\n\n# The version as the user will see it. If blank, the version attribute will be \n# used here. This should be a single word, with no spaces.\nprettyVersion = ##library.prettyVersion##  # This is treated as a String\n\n# The min and max revision of Processing compatible with your Library.\n# Note that these fields use the revision and not the version of Processing, \n# parsable as an int. For example, the revision number for 2.2.1 is 227. \n# You can find the revision numbers in the change log: https://raw.githubusercontent.com/processing/processing/master/build/shared/revisions.txt\n# Only use maxRevision (or minRevision), when your Library is known to \n# break in a later (or earlier) release. Otherwise, use the default value 0.\nminRevision = ##compatible.minRevision##\nmaxRevision = ##compatible.maxRevision##\n"
  },
  {
    "path": "resources/stylesheet.css",
    "content": "/* Javadoc style sheet */\n/*\nOverall document style\n*/\n\n@import url('resources/fonts/dejavu.css');\n\nbody {\n    background-color:#ffffff;\n    color:#353833;\n    font-family:'DejaVu Sans', Arial, Helvetica, sans-serif;\n    font-size:14px;\n    margin:0;\n}\na:link, a:visited {\n    text-decoration:none;\n    color:#4A6782;\n}\na:hover, a:focus {\n    text-decoration:none;\n    color:#bb7a2a;\n}\na:active {\n    text-decoration:none;\n    color:#4A6782;\n}\na[name] {\n    color:#353833;\n}\na[name]:hover {\n    text-decoration:none;\n    color:#353833;\n}\npre {\n    font-family:'DejaVu Sans Mono', monospace;\n    font-size:14px;\n}\nh1 {\n    font-size:20px;\n}\nh2 {\n    font-size:18px;\n}\nh3 {\n    font-size:16px;\n    font-style:italic;\n}\nh4 {\n    font-size:13px;\n}\nh5 {\n    font-size:12px;\n}\nh6 {\n    font-size:11px;\n}\nul {\n    list-style-type:disc;\n}\ncode, tt {\n    font-family:'DejaVu Sans Mono', monospace;\n    font-size:14px;\n    padding-top:4px;\n    margin-top:8px;\n    line-height:1.4em;\n}\ndt code {\n    font-family:'DejaVu Sans Mono', monospace;\n    font-size:14px;\n    padding-top:4px;\n}\ntable tr td dt code {\n    font-family:'DejaVu Sans Mono', monospace;\n    font-size:14px;\n    vertical-align:top;\n    padding-top:4px;\n}\nsup {\n    font-size:8px;\n}\n/*\nDocument title and Copyright styles\n*/\n.clear {\n    clear:both;\n    height:0px;\n    overflow:hidden;\n}\n.aboutLanguage {\n    float:right;\n    padding:0px 21px;\n    font-size:11px;\n    z-index:200;\n    margin-top:-9px;\n}\n.legalCopy {\n    margin-left:.5em;\n}\n.bar a, .bar a:link, .bar a:visited, .bar a:active {\n    color:#FFFFFF;\n    text-decoration:none;\n}\n.bar a:hover, .bar a:focus {\n    color:#bb7a2a;\n}\n.tab {\n    background-color:#0066FF;\n    color:#ffffff;\n    padding:8px;\n    width:5em;\n    font-weight:bold;\n}\n/*\nNavigation bar styles\n*/\n.bar {\n    background-color:#4D7A97;\n    color:#FFFFFF;\n    padding:.8em .5em .4em .8em;\n    height:auto;/*height:1.8em;*/\n    font-size:11px;\n    margin:0;\n}\n.topNav {\n    background-color:#4D7A97;\n    color:#FFFFFF;\n    float:left;\n    padding:0;\n    width:100%;\n    clear:right;\n    height:2.8em;\n    padding-top:10px;\n    overflow:hidden;\n    font-size:12px; \n}\n.bottomNav {\n    margin-top:10px;\n    background-color:#4D7A97;\n    color:#FFFFFF;\n    float:left;\n    padding:0;\n    width:100%;\n    clear:right;\n    height:2.8em;\n    padding-top:10px;\n    overflow:hidden;\n    font-size:12px;\n}\n.subNav {\n    background-color:#dee3e9;\n    float:left;\n    width:100%;\n    overflow:hidden;\n    font-size:12px;\n}\n.subNav div {\n    clear:left;\n    float:left;\n    padding:0 0 5px 6px;\n    text-transform:uppercase;\n}\nul.navList, ul.subNavList {\n    float:left;\n    margin:0 25px 0 0;\n    padding:0;\n}\nul.navList li{\n    list-style:none;\n    float:left;\n    padding: 5px 6px;\n    text-transform:uppercase;\n}\nul.subNavList li{\n    list-style:none;\n    float:left;\n}\n.topNav a:link, .topNav a:active, .topNav a:visited, .bottomNav a:link, .bottomNav a:active, .bottomNav a:visited {\n    color:#FFFFFF;\n    text-decoration:none;\n    text-transform:uppercase;\n}\n.topNav a:hover, .bottomNav a:hover {\n    text-decoration:none;\n    color:#bb7a2a;\n    text-transform:uppercase;\n}\n.navBarCell1Rev {\n    background-color:#F8981D;\n    color:#253441;\n    margin: auto 5px;\n}\n.skipNav {\n    position:absolute;\n    top:auto;\n    left:-9999px;\n    overflow:hidden;\n}\n/*\nPage header and footer styles\n*/\n.header, .footer {\n    clear:both;\n    margin:0 20px;\n    padding:5px 0 0 0;\n}\n.indexHeader {\n    margin:10px;\n    position:relative;\n}\n.indexHeader span{\n    margin-right:15px;\n}\n.indexHeader h1 {\n    font-size:13px;\n}\n.title {\n    color:#2c4557;\n    margin:10px 0;\n}\n.subTitle {\n    margin:5px 0 0 0;\n}\n.header ul {\n    margin:0 0 15px 0;\n    padding:0;\n}\n.footer ul {\n    margin:20px 0 5px 0;\n}\n.header ul li, .footer ul li {\n    list-style:none;\n    font-size:13px;\n}\n/*\nHeading styles\n*/\ndiv.details ul.blockList ul.blockList ul.blockList li.blockList h4, div.details ul.blockList ul.blockList ul.blockListLast li.blockList h4 {\n    background-color:#dee3e9;\n    border:1px solid #d0d9e0;\n    margin:0 0 6px -8px;\n    padding:7px 5px;\n}\nul.blockList ul.blockList ul.blockList li.blockList h3 {\n    background-color:#dee3e9;\n    border:1px solid #d0d9e0;\n    margin:0 0 6px -8px;\n    padding:7px 5px;\n}\nul.blockList ul.blockList li.blockList h3 {\n    padding:0;\n    margin:15px 0;\n}\nul.blockList li.blockList h2 {\n    padding:0px 0 20px 0;\n}\n/*\nPage layout container styles\n*/\n.contentContainer, .sourceContainer, .classUseContainer, .serializedFormContainer, .constantValuesContainer {\n    clear:both;\n    padding:10px 20px;\n    position:relative;\n}\n.indexContainer {\n    margin:10px;\n    position:relative;\n    font-size:12px;\n}\n.indexContainer h2 {\n    font-size:13px;\n    padding:0 0 3px 0;\n}\n.indexContainer ul {\n    margin:0;\n    padding:0;\n}\n.indexContainer ul li {\n    list-style:none;\n    padding-top:2px;\n}\n.contentContainer .description dl dt, .contentContainer .details dl dt, .serializedFormContainer dl dt {\n    font-size:12px;\n    font-weight:bold;\n    margin:10px 0 0 0;\n    color:#4E4E4E;\n}\n.contentContainer .description dl dd, .contentContainer .details dl dd, .serializedFormContainer dl dd {\n    margin:5px 0 10px 0px;\n    font-size:14px;\n    font-family:'DejaVu Sans Mono',monospace;\n}\n.serializedFormContainer dl.nameValue dt {\n    margin-left:1px;\n    font-size:1.1em;\n    display:inline;\n    font-weight:bold;\n}\n.serializedFormContainer dl.nameValue dd {\n    margin:0 0 0 1px;\n    font-size:1.1em;\n    display:inline;\n}\n/*\nList styles\n*/\nul.horizontal li {\n    display:inline;\n    font-size:0.9em;\n}\nul.inheritance {\n    margin:0;\n    padding:0;\n}\nul.inheritance li {\n    display:inline;\n    list-style:none;\n}\nul.inheritance li ul.inheritance {\n    margin-left:15px;\n    padding-left:15px;\n    padding-top:1px;\n}\nul.blockList, ul.blockListLast {\n    margin:10px 0 10px 0;\n    padding:0;\n}\nul.blockList li.blockList, ul.blockListLast li.blockList {\n    list-style:none;\n    margin-bottom:15px;\n    line-height:1.4;\n}\nul.blockList ul.blockList li.blockList, ul.blockList ul.blockListLast li.blockList {\n    padding:0px 20px 5px 10px;\n    border:1px solid #ededed; \n    background-color:#f8f8f8;\n}\nul.blockList ul.blockList ul.blockList li.blockList, ul.blockList ul.blockList ul.blockListLast li.blockList {\n    padding:0 0 5px 8px;\n    background-color:#ffffff;\n    border:none;\n}\nul.blockList ul.blockList ul.blockList ul.blockList li.blockList {\n    margin-left:0;\n    padding-left:0;\n    padding-bottom:15px;\n    border:none;\n}\nul.blockList ul.blockList ul.blockList ul.blockList li.blockListLast {\n    list-style:none;\n    border-bottom:none;\n    padding-bottom:0;\n}\ntable tr td dl, table tr td dl dt, table tr td dl dd {\n    margin-top:0;\n    margin-bottom:1px;\n}\n/*\nTable styles\n*/\n.overviewSummary, .memberSummary, .typeSummary, .useSummary, .constantsSummary, .deprecatedSummary {\n    width:100%;\n    border-left:1px solid #EEE; \n    border-right:1px solid #EEE; \n    border-bottom:1px solid #EEE; \n}\n.overviewSummary, .memberSummary  {\n    padding:0px;\n}\n.overviewSummary caption, .memberSummary caption, .typeSummary caption,\n.useSummary caption, .constantsSummary caption, .deprecatedSummary caption {\n    position:relative;\n    text-align:left;\n    background-repeat:no-repeat;\n    color:#253441;\n    font-weight:bold;\n    clear:none;\n    overflow:hidden;\n    padding:0px;\n    padding-top:10px;\n    padding-left:1px;\n    margin:0px;\n    white-space:pre;\n}\n.overviewSummary caption a:link, .memberSummary caption a:link, .typeSummary caption a:link,\n.useSummary caption a:link, .constantsSummary caption a:link, .deprecatedSummary caption a:link,\n.overviewSummary caption a:hover, .memberSummary caption a:hover, .typeSummary caption a:hover,\n.useSummary caption a:hover, .constantsSummary caption a:hover, .deprecatedSummary caption a:hover,\n.overviewSummary caption a:active, .memberSummary caption a:active, .typeSummary caption a:active,\n.useSummary caption a:active, .constantsSummary caption a:active, .deprecatedSummary caption a:active,\n.overviewSummary caption a:visited, .memberSummary caption a:visited, .typeSummary caption a:visited,\n.useSummary caption a:visited, .constantsSummary caption a:visited, .deprecatedSummary caption a:visited {\n    color:#FFFFFF;\n}\n.overviewSummary caption span, .memberSummary caption span, .typeSummary caption span,\n.useSummary caption span, .constantsSummary caption span, .deprecatedSummary caption span {\n    white-space:nowrap;\n    padding-top:5px;\n    padding-left:12px;\n    padding-right:12px;\n    padding-bottom:7px;\n    display:inline-block;\n    float:left;\n    background-color:#F8981D;\n    border: none;\n    height:16px;\n}\n.memberSummary caption span.activeTableTab span {\n    white-space:nowrap;\n    padding-top:5px;\n    padding-left:12px;\n    padding-right:12px;\n    margin-right:3px;\n    display:inline-block;\n    float:left;\n    background-color:#F8981D;\n    height:16px;\n}\n.memberSummary caption span.tableTab span {\n    white-space:nowrap;\n    padding-top:5px;\n    padding-left:12px;\n    padding-right:12px;\n    margin-right:3px;\n    display:inline-block;\n    float:left;\n    background-color:#4D7A97;\n    height:16px;\n}\n.memberSummary caption span.tableTab, .memberSummary caption span.activeTableTab {\n    padding-top:0px;\n    padding-left:0px;\n    padding-right:0px;\n    background-image:none;\n    float:none;\n    display:inline;\n}\n.overviewSummary .tabEnd, .memberSummary .tabEnd, .typeSummary .tabEnd,\n.useSummary .tabEnd, .constantsSummary .tabEnd, .deprecatedSummary .tabEnd {\n    display:none;\n    width:5px;\n    position:relative;\n    float:left;\n    background-color:#F8981D;\n}\n.memberSummary .activeTableTab .tabEnd {\n    display:none;\n    width:5px;\n    margin-right:3px;\n    position:relative; \n    float:left;\n    background-color:#F8981D;\n}\n.memberSummary .tableTab .tabEnd {\n    display:none;\n    width:5px;\n    margin-right:3px;\n    position:relative;\n    background-color:#4D7A97;\n    float:left;\n\n}\n.overviewSummary td, .memberSummary td, .typeSummary td,\n.useSummary td, .constantsSummary td, .deprecatedSummary td {\n    text-align:left;\n    padding:0px 0px 12px 10px;\n}\nth.colOne, th.colFirst, th.colLast, .useSummary th, .constantsSummary th,\ntd.colOne, td.colFirst, td.colLast, .useSummary td, .constantsSummary td{\n    vertical-align:top;\n    padding-right:0px;\n    padding-top:8px;\n    padding-bottom:3px;\n}\nth.colFirst, th.colLast, th.colOne, .constantsSummary th {\n    background:#dee3e9;\n    text-align:left;\n    padding:8px 3px 3px 7px;\n}\ntd.colFirst, th.colFirst {\n    white-space:nowrap;\n    font-size:13px;\n}\ntd.colLast, th.colLast {\n    font-size:13px;\n}\ntd.colOne, th.colOne {\n    font-size:13px;\n}\n.overviewSummary td.colFirst, .overviewSummary th.colFirst,\n.useSummary td.colFirst, .useSummary th.colFirst,\n.overviewSummary td.colOne, .overviewSummary th.colOne,\n.memberSummary td.colFirst, .memberSummary th.colFirst,\n.memberSummary td.colOne, .memberSummary th.colOne,\n.typeSummary td.colFirst{\n    width:25%;\n    vertical-align:top;\n}\ntd.colOne a:link, td.colOne a:active, td.colOne a:visited, td.colOne a:hover, td.colFirst a:link, td.colFirst a:active, td.colFirst a:visited, td.colFirst a:hover, td.colLast a:link, td.colLast a:active, td.colLast a:visited, td.colLast a:hover, .constantValuesContainer td a:link, .constantValuesContainer td a:active, .constantValuesContainer td a:visited, .constantValuesContainer td a:hover {\n    font-weight:bold;\n}\n.tableSubHeadingColor {\n    background-color:#EEEEFF;\n}\n.altColor {\n    background-color:#FFFFFF;\n}\n.rowColor {\n    background-color:#EEEEEF;\n}\n/*\nContent styles\n*/\n.description pre {\n    margin-top:0;\n}\n.deprecatedContent {\n    margin:0;\n    padding:10px 0;\n}\n.docSummary {\n    padding:0;\n}\n\nul.blockList ul.blockList ul.blockList li.blockList h3 {\n    font-style:normal;\n}\n\ndiv.block {\n    font-size:14px;\n    font-family:'DejaVu Serif', Georgia, \"Times New Roman\", Times, serif;\n}\n\ntd.colLast div {\n    padding-top:0px;\n}\n\n\ntd.colLast a {\n    padding-bottom:3px;\n}\n/*\nFormatting effect styles\n*/\n.sourceLineNo {\n    color:green;\n    padding:0 30px 0 0;\n}\nh1.hidden {\n    visibility:hidden;\n    overflow:hidden;\n    font-size:10px;\n}\n.block {\n    display:block;\n    margin:3px 10px 2px 0px;\n    color:#474747;\n}\n.deprecatedLabel, .descfrmTypeLabel, .memberNameLabel, .memberNameLink,\n.overrideSpecifyLabel, .packageHierarchyLabel, .paramLabel, .returnLabel,\n.seeLabel, .simpleTagLabel, .throwsLabel, .typeNameLabel, .typeNameLink {\n    font-weight:bold;\n}\n.deprecationComment, .emphasizedPhrase, .interfaceName {\n    font-style:italic;\n}\n\ndiv.block div.block span.deprecationComment, div.block div.block span.emphasizedPhrase,\ndiv.block div.block span.interfaceName {\n    font-style:normal;\n}\n\ndiv.contentContainer ul.blockList li.blockList h2{\n    padding-bottom:0px;\n}"
  },
  {
    "path": "src/xyscope/XYWavetable.java",
    "content": "package xyscope;\n/*\n * Special update to Minim Waveform class by Hansi Raber (Sep 2018),\n * fixing an array out of bounds bug when changing waveform quickly.\n * */\n\nimport java.util.Random;\n\nimport ddf.minim.ugens.Waveform;\n\n/**\n * Wavetable wraps a float array of any size and lets you sample the array using\n * a normalized value [0,1]. This means that if you have an array that is 2048\n * samples long, then value(0.5) will give you the 1024th sample. You will most\n * often use Wavetables as the Waveform in an Oscil, but other uses are also\n * possible. Additionally, Wavetable provides a set of methods for transforming\n * the samples it contains.\n * \n * @example Synthesis/WavetableMethods\n * \n * @related Waveform\n * @related Waves\n * @related WavetableGenerator\n * \n * @author Mark Godfrey &lt;mark.godfrey@gatech.edu&gt;\n */\n\npublic class XYWavetable implements Waveform\n{\n\n\tprivate float[]\twaveform;\n\n\t/**\n\t * Construct a Wavetable that contains <code>size</code> entries.\n\t * \n\t * @param size\n\t * \t\t\tint: the number of samples the Wavetable should contain\n\t * \n\t * @related Wavetable\n\t */\n\tpublic XYWavetable(int size)\n\t{\n\t\twaveform = new float[size];\n\t}\n\n\t/**\n\t * Construct a Wavetable that will use <code>waveform</code> as the float\n\t * array to sample from. This <em>will not</em> copy <code>waveform</code>,\n\t * it will use it directly.\n\t * \n\t * @param waveform\n\t * \t\t\tfloat[]: the float array this Wavetable will sample\n\t * \n\t * @related Wavetable\n\t */\n\tpublic XYWavetable(float[] waveform)\n\t{\n\t\tthis.waveform = waveform;\n\t}\n\n\t/**\n\t * Make a new Wavetable that has the same waveform values as\n\t * <code>wavetable</code>. This will <em>copy</em> the values from the\n\t * provided Wavetable into this Wavetable's waveform.\n\t * \n\t * @param wavetable\n\t * \t\t\tWavetable: the Wavetable to copy\n\t * \n\t * @related Wavetable\n\t */\n\tpublic XYWavetable(XYWavetable wavetable)\n\t{\n\t\twaveform = new float[wavetable.waveform.length];\n\t\tSystem.arraycopy( wavetable.waveform, 0, waveform, 0, waveform.length );\n\t}\n\n\t/**\n\t * Sets this Wavetable's waveform to the one provided. This\n\t * <em>will not</em> copy the values from the provided waveform, it will use\n\t * the waveform directly.\n\t * \n\t * @param waveform\n\t * \t\t\t\tfloat[]: the new sample data\n\t * \n\t * @related Wavetable\n\t */\n\tpublic void setWaveform(float[] waveform)\n\t{\n\t\tthis.waveform = waveform;\n\t}\n\n\t/**\n\t * Returns the value of the i<sup>th</sup> entry in this Wavetable's\n\t * waveform. This is equivalent to getWaveform()[i].\n\t * \n\t * @shortdesc Returns the value of the i<sup>th</sup> entry in this Wavetable's\n\t * waveform.\n\t * \n\t * @param i\n\t * \t\t\tint: the index of the sample to return\n\t * \n\t * @return float: the value of the sample at i\n\t * \n\t * @related Wavetable\n\t */\n\tpublic float get(int i)\n\t{\n\t\treturn waveform[i];\n\t}\n\n\t/**\n\t * Sample the Wavetable using a value in the range [0,1]. For instance, if\n\t * the Wavetable has 1024 values in its float array, then calling value(0.5)\n\t * will return the 512th value in the array. If the result is that it needs\n\t * say the 456.65th value, this will interpolate between the surrounding\n\t * values.\n\t * \n\t * @shortdesc Sample the Wavetable using a value in the range [0,1].\n\t * \n\t * @example Synthesis/WavetableMethods\n\t * \n\t * @param at\n\t *            float: a value in the range [0, 1]\n\t * \n\t * @return float: this Wavetable sampled at the requested interval\n\t * \n\t * @related Wavetable\n\t */\n\tpublic float value(float at)\n\t{\n\t\tfloat[] wave = this.waveform; // create local waveform for thread safe\n\t\tif(wave.length==0) return 0;\n\t\tfloat whichSample = wave.length * (((at%1)+1)%1);\n\n\t\t// linearly interpolate between the two samples we want.\n\t\tint lowSamp = ((int)whichSample)%wave.length;\n\t\tint hiSamp = (lowSamp + 1)%wave.length;\n\t\tfloat rem = whichSample - lowSamp;\n\t\t\n\t\treturn wave[lowSamp] + rem\n\t\t\t\t* ( wave[hiSamp] - wave[lowSamp] );\n\n\t}\n\n\t/**\n\t * Returns the underlying waveform, <em>not</em> a copy of it.\n\t * \n\t * @return float[]: the float array managed by this Wavetable\n\t * \n\t * @related Wavetable\n\t */\n\tpublic float[] getWaveform()\n\t{\n\t\treturn waveform;\n\t}\n\n\t/**\n\t * Sets the i<sup>th</sup> entry of the underlying waveform to\n\t * <code>value</code>. This is equivalent to:\n\t * <p>\n\t * <code>getWaveform()[i] = value;</code>\n\t * \n\t * @param i\n\t * \t\t\tint: the index of the sample to set\n\t * @param value\n\t * \t\t\tfloat: the new sample value\n\t * \n\t * @related Wavetable\n\t */\n\tpublic void set(int i, float value)\n\t{\n\t\twaveform[i] = value;\n\t}\n\n\t/**\n\t * Returns the length of the underlying waveform. This is equivalent to:\n\t * <p>\n\t * <code>getWaveform().length</code>\n\t * \n\t * @return int: the length of the underlying float array\n\t * \n\t * @related Wavetable\n\t */\n\tpublic int size()\n\t{\n\t\treturn waveform.length;\n\t}\n\n\t/**\n\t * Multiplies each value of the underlying waveform by <code>scale</code>.\n\t * \n\t * @param scale\n\t * \t\t\tfloat: the amount to scale the Wavetable with\n\t * \n\t * @related Wavetable\n\t */\n\tpublic void scale(float scale)\n\t{\n\t\tfor ( int i = 0; i < waveform.length; i++ )\n\t\t{\n\t\t\twaveform[i] *= scale;\n\t\t}\n\t}\n\n\t/**\n\t * Apply a DC offset to this Wavetable. In other words, add\n\t * <code>amount</code> to every sample.\n\t * \n\t * @param amount\n\t *            float: the amount to add to every sample in the table\n\t *            \n\t * @related Wavetable\n\t */\n\tpublic void offset(float amount)\n\t{\n\t\tfor ( int i = 0; i < waveform.length; ++i )\n\t\t{\n\t\t\twaveform[i] += amount;\n\t\t}\n\t}\n\n\t/**\n\t * Normalizes the Wavetable by finding the largest amplitude in the table\n\t * and scaling the table by the inverse of that amount. The result is that\n\t * the largest value in the table will now have an amplitude of 1 and\n\t * everything else is scaled proportionally.\n\t * \n\t * @example Synthesis/WavetableMethods\n\t * \n\t * @related Wavetable\n\t */\n\tpublic void normalize()\n\t{\n\t\tfloat max = Float.MIN_VALUE;\n\t\tfor ( int i = 0; i < waveform.length; i++ )\n\t\t{\n\t\t\tif ( Math.abs( waveform[i] ) > max )\n\t\t\t\tmax = Math.abs( waveform[i] );\n\t\t}\n\t\tscale( 1 / max );\n\t}\n\n\t/**\n\t * Flips the table around 0. Equivalent to <code>flip(0)</code>.\n\t * \n\t * @see #flip(float)\n\t * @related flip ( )\n\t * @related Wavetable\n\t */\n\tpublic void invert()\n\t{\n\t\tflip( 0 );\n\t}\n\n\t/**\n\t * Flip the values in the table around a particular value. For example, if\n\t * you flip around 2, values greater than 2 will become less than two by the\n\t * same amount and values less than 2 will become greater than 2 by the same\n\t * amount. 3 -&gt; 1, 0 -&gt; 4, etc.\n\t * \n\t * @shortdesc Flip the values in the table around a particular value.\n\t * \n\t * @example Synthesis/WavetableMethods\n\t * \n\t * @param in\n\t *            float: the value to flip the table around\n\t *            \n\t * @related Wavetable\n\t */\n\tpublic void flip(float in)\n\t{\n\t\tfor ( int i = 0; i < waveform.length; i++ )\n\t\t{\n\t\t\tif ( waveform[i] > in )\n\t\t\t\twaveform[i] = in - ( waveform[i] - in );\n\t\t\telse\n\t\t\t\twaveform[i] = in + ( in - waveform[i] );\n\t\t}\n\t}\n\n\t/**\n\t * Adds Gaussian noise to the waveform.\n\t * \n\t * @example Synthesis/WavetableMethods\n\t * \n\t * @param sigma\n\t *            float: the amount to scale the random values by, in effect how\n\t *            \"loud\" the added noise will be.\n\t *            \n\t * @related Wavetable\n\t */\n\tpublic void addNoise(float sigma)\n\t{\n\t\tRandom rgen = new Random();\n\t\tfor ( int i = 0; i < waveform.length; i++ )\n\t\t{\n\t\t\twaveform[i] += ( (float)rgen.nextGaussian() ) * sigma;\n\t\t}\n\t}\n\n\t/**\n\t * Inverts all values in the table that are less than zero. -1 -&gt; 1, -0.2 -&gt; 0.2, etc.\n\t * \n\t * @example Synthesis/WavetableMethods\n\t * \n\t * @related Wavetable\n\t */\n\tpublic void rectify()\n\t{\n\t\tfor ( int i = 0; i < waveform.length; i++ )\n\t\t{\n\t\t\tif ( waveform[i] < 0 )\n\t\t\t\twaveform[i] *= -1;\n\t\t}\n\t}\n\n\t/**\n\t * Smooth out the values in the table by using a moving average window.\n\t * \n\t * @example Synthesis/WavetableMethods\n\t * \n\t * @param windowLength\n\t *            int: how many samples large the window should be\n\t *            \n\t * @related Wavetable\n\t */\n\tpublic void smooth(int windowLength)\n\t{\n\t\tif ( windowLength < 1 )\n\t\t\treturn;\n\t\tfloat[] temp = (float[])waveform.clone();\n\t\tfor ( int i = windowLength; i < waveform.length; i++ )\n\t\t{\n\t\t\tfloat avg = 0;\n\t\t\tfor ( int j = i - windowLength; j <= i; j++ )\n\t\t\t{\n\t\t\t\tavg += temp[j] / windowLength;\n\t\t\t}\n\t\t\twaveform[i] = avg;\n\t\t}\n\t}\n\n\t/**\n\t * Warping works by choosing a point in the waveform, the warpPoint, and\n\t * then specifying where it should move to, the warpTarget. Both values\n\t * should be normalized (i.e. in the range [0,1]). What will happen is that\n\t * the waveform data in front of and behind the warpPoint will be squashed\n\t * or stretch to fill the space defined by where the warpTarget is. For\n\t * instance, if you took Waves.SQUARE and called warp( 0.5, 0.2 ), you would\n\t * wind up with a square wave with a 20 percent duty cycle, the same as\n\t * using Waves.square( 0.2 ). This is because the crossover point of a\n\t * square wave is halfway through and warping it such that the crossover is\n\t * moved to 20% through the waveform is equivalent to changing the duty\n\t * cycle. Or course, much more interesting things happen when warping a more\n\t * complex waveform, such as one returned by the Waves.randomNHarms method,\n\t * especially if it is warped more than once.\n\t * \n\t * @shortdesc Warping works by choosing a point in the waveform, the\n\t *            warpPoint, and then specifying where it should move to, the\n\t *            warpTarget.\n\t *            \n\t * @example Synthesis/WavetableMethods\n\t * \n\t * @param warpPoint\n\t *            float: the point in the wave for to be moved, expressed as a\n\t *            normalized value.\n\t * @param warpTarget\n\t *            float: the point in the wave to move the warpPoint to,\n\t *            expressed as a normalized value.\n\t *            \n\t * @related Wavetable\n\t */\n\tpublic void warp(float warpPoint, float warpTarget)\n\t{\n\t\tfloat[] newWave = new float[waveform.length];\n\t\tfor ( int s = 0; s < newWave.length; ++s )\n\t\t{\n\t\t\tfloat lookup = (float)s / newWave.length;\n\t\t\tif ( lookup <= warpTarget )\n\t\t\t{\n\t\t\t\t// normalize look up to [0,warpTarget], expand to [0,warpPoint]\n\t\t\t\tlookup = ( lookup / warpTarget ) * warpPoint;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// map (warpTarget,1] to (warpPoint,1]\n\t\t\t\tlookup = warpPoint + ( 1 - ( 1 - lookup ) / ( 1 - warpTarget ) ) * ( 1 - warpPoint );\n\t\t\t}\n\t\t\tnewWave[s] = value( lookup );\n\t\t}\n\t\twaveform = newWave;\n\t}\n\n}\n"
  },
  {
    "path": "src/xyscope/XYscope.java",
    "content": "/*\n * cc ted davis 2017-23\n */\n\npackage xyscope;\n\nimport processing.core.*;\nimport static processing.core.PApplet.*;\n\nimport java.util.ArrayList;\n\n// minim\nimport ddf.minim.*;\nimport ddf.minim.ugens.*;\nimport javax.sound.sampled.*;\n\n/**\n * Render vector graphics on a vector display (oscilloscope\n * X-Y mode, laser) by converting them to audio .\n *\n */\n\npublic class XYscope {\n\n\t// myParent is a reference to the parent sketch\n\tPApplet myParent;\n\n\t/**\n\t * Collection of current shapes rendered by buildWaves().\n\t */\n\n\tpublic XYShapeList shapes = new XYShapeList();\n\n\t// minim\n\tpublic Minim minim, minimZ;\n\tpublic AudioRecorder recorder;\n\n\t/**\n\t * minim AudioOutput, for customizing audio out.\n\t */\n\n\tpublic AudioOutput outXY, outZ;\n\n\t/**\n\t * minim Summer, for customizing patching filters.\n\t */\n\tpublic Summer sumXY, sumZ;\n\n\t/**\n\t * minim Oscil, for customizing XYZ oscillators.\n\t */\n\tpublic Oscil waveX, waveY, waveZ;\n\n\t/**\n\t * minim Wavetable, for customizing XYZ oscillators.\n\t */\n\tpublic XYWavetable tableX, tableY, tableZ;\n\tPan panX = new Pan(-1);\n\tPan panY = new Pan(1);\n\n\tMixer.Info[] mixerInfo;\n\n\tfloat initAmp = 1.0f;\n\tPVector amp = new PVector(initAmp, initAmp, initAmp);\n\tfloat initFreq = 50f; // 43.065\n\tPVector freq = new PVector(initFreq, initFreq, initFreq);\n\n\t// Mixing audio channels\n\tAudioOutput mixXY;\n\tboolean useMix = false;\n\n\tint sampleRate = 44100; // def 44100\n\tint bufferSize = 512; // def 1024\n\tString mixerName = \"\";\n\t\n\tint waveSizeVal = bufferSize;\n\tint waveSizeValOG = waveSizeVal;\n\tint maxPoints = waveSizeValOG;\n\tpublic float[] shapeX = new float[waveSizeVal];\n\tpublic float[] shapeY = new float[waveSizeVal];\n\tpublic float[] shapeZ = new float[waveSizeVal];\n\tfloat[] shapePreX = new float[waveSizeVal];\n\tfloat[] shapePreY = new float[waveSizeVal];\n\tfloat[] shapePreZ = new float[waveSizeVal];\n\t\n\tboolean debugWave = false;\n\tint debugSize = 10;\n\tboolean busy = false;\n\n\tboolean useLimitPoints = false;\n\tint limitPointsVal = waveSizeValOG;\n\n\tint ellipseDetail = 30;\n\n\tboolean useEase = false;\n\tfloat easeVal = .1f;\n\tboolean useZ = false;\n\tboolean useSmooth = false;\n\tint smoothVal = 12;\n\tboolean useLimitPath = false;\n\tfloat limitVal = 1;\n\n\tfloat zaxisMax = 1f;\n\tfloat zaxisMin = -1f;\n\tint zoffset = 1;\n\t\n\t// TYPE VARS\n\tString hershey_font[];\n\tint hheight = 21;\n\tfloat hleading = 30f;\n\tfloat hfactor = 1;\n\tint textAlignX = 37;\n\tint textAlignY = 101;\n\n\t\n\t/**\n\t * List of built-in Hershey Fonts available\n\t */\n\tpublic String[] fonts = {\"astrology\", \"cursive\", \"cyrilc_1\", \"cyrillic\", \"futural\", \"futuram\", \"gothgbt\", \"gothgrt\", \"gothiceng\", \"gothicger\", \"gothicita\", \"gothitt\", \"greek\", \"greekc\", \"greeks\", \"japanese\", \"markers\", \"mathlow\", \"mathupp\", \"meteorology\", \"music\", \"rowmand\", \"rowmans\", \"rowmant\", \"scriptc\", \"scripts\", \"symbolic\", \"timesg\", \"timesi\", \"timesib\", \"timesr\", \"timesrb\"};\n\t\n\t// VECTREX VARS\n\tboolean useVectrex = false;\n\tfloat vectrexAmp = .82f;\n\tfloat vectrexAmpInit = .6f;\n\tint vectrexRotation = 0;\n\tint vectrexWidth = 310;\n\tint vectrexHeight = 410;\n\n\t// LASER VARS\n\tboolean useLaser = false;\n\tpublic Minim minimR, minimBG;\n\n\t/**\n\t * minim Oscil, for customizing Laser RGB oscillators.\n\t */\n\tpublic Oscil waveR, waveG, waveB; \n\n\tpublic XYWavetable tableR, tableG, tableB;\n\tPan panR = new Pan(1);\n\tPan panG = new Pan(-1);\n\tPan panB = new Pan(1);\n\tpublic AudioOutput outR, outGB;\n\n\tpublic float[] shapeR = new float[waveSizeVal];\n\tpublic float[] shapeG = new float[waveSizeVal];\n\tpublic float[] shapeB = new float[waveSizeVal];\n\tprivate XYShape RGBshape = new XYShape();\n\tPVector lsFreq = new PVector(initFreq, initFreq, initFreq);\n\tPVector lsWB = new PVector(250, 220, 90);\n\tPVector lsMin = new PVector(0, 0, 0);\n\tPVector lsDash = new PVector(1, 1, 1);\n\tMoogFilter moog;\n\tfloat laserLPFVal = 10000.0f;\n\tfloat laserCutoffVal = 20f;\n\n\tint xyWidth, xyHeight;\n\n\t/**\n\t * Initialize library in setup(), use default system audio out setting.\n\t * \n\t * @param theParent\n\t *            PApplet to apply to, typically 'this'\n\t */\n\tpublic XYscope(PApplet theParent) {\n\t\tmyParent = theParent;\n\t\twelcome();\n\t\tinitMinim();\n\t\tsetOutput();\n\t}\n\n\t/**\n\t * Initialize instance of XYscope and patch to an already existing signal.\n\t * \n\t * @param theParent\n\t *            PApplet to apply to, typically 'this'\n\t * @param outMix\n\t *            AudioOutput to merge instance and of XYscope to\n\t */\n\tpublic XYscope(PApplet theParent, AudioOutput outMix) {\n\t\tmyParent = theParent;\n\t\twelcome();\n\t\tinitMinim();\n\t\tsetWaveTable(outMix);\n\t\tuseMix = true;\n\t}\n\n\t/**\n\t * Initialize library in setup(), custom soundcard by String for XY.\n\t * \n\t * @param theParent\n\t *            PApplet to apply to, typically 'this'\n\t * @param xyMixer\n\t *            Name of custom sound mixer to use for XY.\n\t */\n\tpublic XYscope(PApplet theParent, String xyMixer) {\n\t\tmyParent = theParent;\n\t\twelcome();\n\t\tgetMixerInfo();\n\t\tinitMinim();\n\t\tsetMixer(xyMixer);\n\t\tsetOutput();\n\t}\n\n\t/**\n\t * Initialize library in setup(), using default soundcard and set custom\n\t * sample rate (44100, 192000).\n\t * \n\t * @param theParent\n\t *            PApplet to apply to, typically 'this'\n\t * @param sampleR\n\t *            Sample rate for soundcard (44100, 48000, 96000, 192000).\n\t */\n\tpublic XYscope(PApplet theParent, int sampleR) {\n\t\tmyParent = theParent;\n\t\twelcome();\n\t\tinitMinim();\n\t\tsampleRate = sampleR;\n\t\tsetOutput();\n\t}\n\n\t/**\n\t * Initialize library in setup(), custom soundcard by string for XY and set\n\t * custom sample rate (44100, 192000).\n\t * \n\t * @param theParent\n\t *            PApplet to apply to, typically 'this'\n\t * @param xyMixer\n\t *            Name of custom sound mixer to use for XY.\n\t * @param sampleR\n\t *            Sample rate for soundcard (44100, 48000, 96000, 192000).\n\t */\n\tpublic XYscope(PApplet theParent, String xyMixer, int sampleR) {\n\t\tmyParent = theParent;\n\t\twelcome();\n\t\tgetMixerInfo();\n\t\tinitMinim();\n\t\tsetMixer(xyMixer);\n\t\tsampleRate = sampleR;\n\t\tsetOutput();\n\t}\n\t\n\t/**\n\t * Initialize library in setup(), custom soundcard by string for XY and set\n\t * custom sample rate (44100, 192000).\n\t * \n\t * @param theParent\n\t *            PApplet to apply to, typically 'this'\n\t * @param xyMixer\n\t *            Name of custom sound mixer to use for XY.\n\t * @param sampleRateVal\n\t *            Sample rate for soundcard (44100, 48000, 96000, 192000).\n\t * @param bufferSizeVal\n\t *            Size of buffer/latency for cpu/soundcard (128, 256, 512, 1024, 2048).\n\t */\n\tpublic XYscope(PApplet theParent, String xyMixer, int sampleRateVal, int bufferSizeVal) {\n\t\tmyParent = theParent;\n\t\twelcome();\n\t\tgetMixerInfo();\n\t\tinitMinim();\n\t\tsetMixer(xyMixer);\n\t\tsampleRate = sampleRateVal;\n\t\tbufferSize = bufferSizeVal;\n\t\tsetOutput();\n\t}\n\n\tprivate void welcome() {\n\t\tSystem.out.println(\"XYscope 3.0.0 - https://teddavis.org/xyscope\");\n\t\txyWidth = myParent.width;\n\t\txyHeight = myParent.height;\n\t\tinitText();\n\t}\n\t\n\tprivate void initText() {\n\t\ttextFont(\"meteorology\");\n\t}\n\n\t/**\n\t * Lists all audio input/output options available\n\t */\n\tpublic void getMixerInfo() {\n\t\tmixerInfo = AudioSystem.getMixerInfo();\n\t\tfor (int i = 0; i < mixerInfo.length; i++) {\n\t\t\tprintln(i + \" = \" + mixerInfo[i].getName());\n\t\t}\n\t}\n\n\tprivate static Mixer getMixerByName(String toFind) {\n\t\tfor (Mixer.Info info : AudioSystem.getMixerInfo()) {\n\t\t\tif (toFind.equals(info.getName())) {\n\t\t\t\treturn AudioSystem.getMixer(info);\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n\n\tprivate void initMinim() {\n\t\tminim = new Minim(myParent);\n\t\tminimZ = new Minim(myParent);\n\t\tminimR = new Minim(myParent);\n\t\tminimBG = new Minim(myParent);\n\t}\n\t\n\t// *** add mixer() as getter and setter?? add bufferSize as initial option param\n\n\tprivate void setMixer(String xyMixer) {\n\t\tgetMixerInfo();\n\t\tMixer mixer = getMixerByName(xyMixer);\n\t\tminim.setOutputMixer(mixer);\n\t}\n\t\n\tprivate void setOutput() {\n\t\toutXY = minim.getLineOut(Minim.STEREO, bufferSize, sampleRate);\n\t\tsetWaveTable();\n\t}\n\t\n\tpublic int sampleRate() {\n\t\treturn sampleRate;\n\t}\n\t\n\tpublic void sampleRate(int sampleRateVal) {\n\t\tsampleRate = sampleRateVal;\n\t\tsetOutput();\n\t}\n\t\n\tpublic int bufferSize() {\n\t\treturn outXY.bufferSize();\n\t}\n\t\n\tpublic void bufferSize(int bufferSizeVal) {\n\t\tif(bufferSizeVal > 16) {\n\t\t\tbufferSize = bufferSizeVal;\n\t\t}\n\t\tsetOutput();\n\t}\n\n\tprivate void setWaveTable() {\n\t\tsumXY = new Summer();\n\t\tsumXY.setChannelCount(2);\n\n\t\ttableX = new XYWavetable(2);\n\t\twaveX = new Oscil(freq.x, amp.x, tableX);\n\t\ttableX.setWaveform(shapeX);\n\t\twaveX.patch(panX).patch(sumXY);\n\n\t\ttableY = new XYWavetable(2);\n\t\twaveY = new Oscil(freq.y, amp.y, tableY);\n\t\ttableY.setWaveform(shapeY);\n\t\twaveY.patch(panY).patch(sumXY);\n\n\t\twaveReset();\n\t\tsumXY.patch(outXY);\n\n\t}\n\n\tprivate void setWaveTable(AudioOutput outMix) {\n\t\ttableX = new XYWavetable(2);\n\t\twaveX = new Oscil(freq.x, amp.x, tableX);\n\t\ttableX.setWaveform(shapeX);\n\t\twaveX.patch(panX).patch(outMix);\n\n\t\ttableY = new XYWavetable(2);\n\t\twaveY = new Oscil(freq.y, amp.y, tableY);\n\t\ttableY.setWaveform(shapeY);\n\t\twaveY.patch(panY).patch(outMix);\n\n\t\tmixXY = outMix;\n\n\t\twaveReset();\n\t}\n\n\tprivate void setWaveTableZ() {\n\t\tif (useZ) {\n\t\t\ttableZ = new XYWavetable(2);\n\t\t\twaveZ = new Oscil(freq.z, amp.z, tableZ);\n\t\t\ttableZ.setWaveform(shapeZ);\n\t\t\twaveZ.patch(outZ); // need pan?? or gets full amp to both channels?\n\t\t\twaveReset();\n\t\t\tsumXY.unpatch(outXY);\n\t\t\tsumXY.patch(outXY);\n\n\t\t}\n\t}\n\n\t/**\n\t * Reset time-step used by XYZ oscillators if they slip when changing\n\t * frequencies.\n\t * \n\t */\n\tpublic void waveReset() {\n\t\twaveX.reset();\n\t\twaveY.reset();\n\t\tif (useZ)\n\t\t\twaveZ.reset();\n\t}\n\t\n\t/**\n\t * Reset time-step used by XYZ oscillators if they slip when changing\n\t * frequencies.\n\t * \n\t */\n\tpublic void resetWaves() {\n\t\twaveReset();\n\t}\n\n\t/**\n\t * Patch z-axis to custom soundcard by String. Note: Auto z-axis has been\n\t * disabled until solved, until then, one can manually buildZ()\n\t * \n\t * @param zMixer\n\t *            Name of custom sound mixer to use for Z.\n\t */\n\tpublic void z(String zMixer) {\n\t\tMixer mixerZ = getMixerByName(zMixer);\n\t\tminimZ.setOutputMixer(mixerZ);\n\t\toutZ = minimZ.getLineOut(Minim.STEREO, waveSizeValOG);\n\t\tuseZ = true;\n\t\tsetWaveTableZ();\n\t}\n\n\t/**\n\t * Patch z-axis to custom soundcard by String and set custom sample rate\n\t * (44100, 192000). Note: Auto z-axis has been disabled until solved, until\n\t * then, one can manually buildZ()\n\t * \n\t * @param zMixer\n\t *            Name of custom sound mixer to use for Z.\n\t * \n\t * @param sampleR\n\t *            Sample rate for soundcard (44100, 48000, 192000).\n\t * \n\t */\n\tpublic void z(String zMixer, int sampleR) {\n\t\tMixer mixerZ = getMixerByName(zMixer);\n\t\tminimZ.setOutputMixer(mixerZ);\n\t\toutZ = minimZ.getLineOut(Minim.STEREO, waveSizeValOG, sampleR);\n\t\tuseZ = true;\n\t\tsetWaveTableZ();\n\t}\n\n\t/**\n\t * Check if z-axis waveform is being automatically drawn from added shapes.\n\t * \n\t * @return boolean\n\t */\n\tpublic boolean zAuto() {\n\t\treturn useZ;\n\t}\n\n\t/**\n\t * Enabled by default for automatic generation of z-axis waveform based on\n\t * added shapes. Disable if creating your own waveform (used for blanking,\n\t * dotted line, etc. Note: Only works if using a second audio output channel\n\t * \n\t * @param zAutoBool\n\t *            true/false for generating z waveform\n\t */\n\tpublic void zAuto(boolean zAutoBool) {\n\t\tuseZ = zAutoBool;\n\t}\n\n\t/**\n\t * Get ArrayList of all coordinates used for vector drawing as an ArrayList\n\t * of PVector's.\n\t * \n\t * @return ArrayList of PVector\n\t */\n\tpublic ArrayList<PVector> wavePoints() {\n\t\treturn shapes.getPoints();\n\t}\n\n\t/**\n\t * Get min and max values for z-axis output as two value array.\n\t * \n\t * @return array containing [zaxisMin, zaxisMax]\n\t */\n\tpublic float[] zRange() {\n\t\tfloat[] zRangeFloat = { zaxisMin, zaxisMax };\n\t\treturn zRangeFloat;\n\t}\n\n\t/**\n\t * Set min and max values for z-axis output. Necessary for any inverted\n\t * z-axis devices.\n\t * <p>\n\t * default is zMin: 1, zMax: -1\n\t * \n\t * @param zMin\n\t *            float between -1 to 1\n\t * @param zMax\n\t *            float between -1 to 1\n\t */\n\tpublic void zRange(float zMin, float zMax) {\n\t\tzaxisMin = zMin;\n\t\tzaxisMax = zMax;\n\t}\n\n\t/**\n\t * Returns current value for limiting drawing by number of points.\n\t * \n\t * @return limitVal\n\t */\n\tpublic float limitPoints(){\n\t\treturn limitPointsVal;\n\t}\n\n\t/**\n\t * Limit drawing by number of points\n\t * \n\t * @param newLimitPointsVal int for limiting number of points\n\t */\n\tpublic void limitPoints(int newLimitPointsVal){\n\t\tif(newLimitPointsVal == 0){\n\t\t\tuseLimitPoints = false;\n\t\t}else{\n\t\t\tlimitPointsVal = abs(newLimitPointsVal);\n\t\t\tuseLimitPoints = true;\n\t\t}\n\t}\n\n\n\t/**\n\t * Returns current value for border that limits rendering to edges of screen.\n\t * \n\t * @return limitVal\n\t */\n\tpublic float limitPath(){\n\t\treturn limitVal;\n\t}\n\n\t/**\n\t * Only render points within specified border from the edge\n\t * \n\t * @param newLimitVal float for border limit from edges\n\t */\n\tpublic void limitPath(float newLimitVal){\n\t\tlimitVal = newLimitVal;\n\t\tuseLimitPath = true;\n\t}\n\n\t/**\n\t * Use XYscope on a modded Vectrex monitor for XYZ input. This will automatically adjust the canvas and amplitude settings to match the ratio of the Vectrex.\n\t * \n\t */\n\tpublic void vectrex(){\n\t\tvectrex(vectrexWidth, vectrexHeight, vectrexAmpInit, vectrexRotation);\n\t}\n\n\t/**\n\t * Use XYscope on a modded Vectrex monitor for XYZ input. This will automatically adjust the canvas and amplitude settings to match the ratio of the Vectrex. You can customize the rotation of the monitor +/- 90° if landscape oriented.\n\t * \n\t * @param vrot\n\t *            int for degrees of rotation, 90 or -90\n\t */\n\tpublic void vectrex(int vrot){\n\t\tif(vrot == 90){\n\t\t\tvectrexRotation = vrot;\n\t\t\tvectrex(vectrexHeight, vectrexWidth, vectrexAmpInit, vectrexRotation);\n\t\t}else if(vrot == -90){\n\t\t\tvectrexRotation = vrot;\n\t\t\tvectrex(vectrexHeight, vectrexWidth, vectrexAmpInit, vectrexRotation);\n\t\t}else {\n\t\t\tvectrexRotation = 0;\n\t\t\tvectrex(vectrexWidth, vectrexHeight, vectrexAmpInit, vectrexRotation);\n\t\t}\n\t}\n\n\t/**\n\t * Use XYscope on a modded Vectrex monitor for XYZ input. Set custom width, height, initial amplitude scaling and rotation/orientation. \n\t * \n\t * @param vw\n\t *            int for width of canvas, default is 330\n\t * @param vh\n\t *            int for height of canvas, default is 410\n\t * @param vamp\n\t *            float for initial amplitude adjustment of signal to screen (0.0 - 1.0), default is .6\n\t * @param vrot\n\t *            int for degrees of rotation, 90 or -90, default is 0\n\t */\n\tpublic void vectrex(int vw, int vh, float vamp, int vrot){\n\t\tuseVectrex = true;\n\t\tvectrexRotation = vrot;\n\t\tmyParent.getSurface().setResizable(true);\n\t\tmyParent.getSurface().setSize(vw, vh);\n\t\txyWidth = vw;\n\t\txyHeight = vh;\n\t\tvectrexAmpInit = vamp;\n\t\tamp(vectrexAmpInit);\n\t}\n\n\n\t/**\n\t * Get current amplitude difference used for ratio of Vectrex.\n\t * \n\t * @return float\n\t */\n\tpublic float vectrexRatio(){\n\t\treturn vectrexAmp;\n\t}\n\n\n\t/**\n\t * Set current amplitude difference used for ratio of Vectrex.\n\t * \n\t * @param vectrexAmpVal\n\t *            float for amplitude difference (0.0 - 1.0), default is .82\n\t */\n\tpublic void vectrexRatio(float vectrexAmpVal){\n\t\tvectrexAmp = constrain(vectrexAmpVal, 0f, 1f);\n\t\tamp(vectrexAmpInit);\n\t}\n\n\t/**\n\t * Activate use of Laser's RGB by assigning 3 additional (2x stereo pairs) audio channels for controlling the RGB modulation.\n\t * \n\t * @param inR\n\t *            String for name of audio channel for Red\n\t * @param inBG\n\t *            String for name of audio channel for Blue/Green\n\t */\n\tpublic void laser(String inR, String inBG){\n\t\tMixer mixerR = getMixerByName(inR);\n\t\tMixer mixerBG = getMixerByName(inBG);\n\t\tminimR.setOutputMixer(mixerR);\n\t\tminimBG.setOutputMixer(mixerBG);\n\t\toutR = minimR.getLineOut(Minim.STEREO, waveSizeValOG);\n\t\toutGB = minimBG.getLineOut(Minim.STEREO, waveSizeValOG);\n\t\tsetWaveTableRGB();\n\t\tuseLaser = true;\n\n\t\t//LPF\n\t\tmoog = new MoogFilter( laserLPFVal, 0f );\n\t\tmoog.setChannelCount(2);\n\t\tsumXY.unpatch(outXY);\n\t\tsumXY.patch(moog).patch(outXY);\n\t}\n\n\tprivate void setWaveTableRGB() {\n\t\ttableR = new XYWavetable(2);\n\t\twaveR = new Oscil(freq.x, amp.x, tableR);\n\t\ttableR.setWaveform(shapeR);\n\t\twaveR.patch(panR).patch(outR);\n\n\t\ttableG = new XYWavetable(2);\n\t\twaveG = new Oscil(freq.x, amp.x, tableG);\n\t\ttableG.setWaveform(shapeG);\n\t\twaveG.patch(panG).patch(outGB);\n\n\t\ttableB = new XYWavetable(2);\n\t\twaveB = new Oscil(freq.x, amp.x, tableB);\n\t\ttableB.setWaveform(shapeB);\n\t\twaveB.patch(panB).patch(outGB);\n\t}\n\n\t/**\n\t * Returns current frequency of Laser's low-pass-filter (LPF).\n\t * \n\t * @return float\n\t */\n\tpublic float laserLPF(){\n\t\treturn laserLPFVal;\n\t}\n\n\t/**\n\t * Set new frequency for Laser's low-pass-filter (LPF) as float. Be careful to stay within range that's safe for your Galvos.\n\t * \n\t * @param newLaserLPFVal\n\t *            float between 0.1 - 20000.0\n\t */\n\tpublic void laserLPF(float newLaserLPFVal){\n\t\tlaserLPFVal = constrain(newLaserLPFVal, .1f, 20000f);\n\t\tmoog.frequency.setLastValue(laserLPFVal);\n\t}\n\n\t/**\n\t * Returns current value spot-killer (minimum size of drawing for laser).\n\t * \n\t * @return float\n\t */\n\tpublic float spotKiller(){\n\t\treturn laserCutoffVal;\n\t}\n\n\t/**\n\t * Set new value for spotKiller (won't draw if XY shape is smaller than provided value).\n\t * \n\t * @param newLaserCutoffVal\n\t *            float \n\t */\n\tpublic void spotKiller(float newLaserCutoffVal){\n\t\tlaserCutoffVal = abs(newLaserCutoffVal);\n\t}\n\n\t/**\n\t * Returns current minimum values set for RGB laser.\n\t * \n\t * @return PVector\n\t */\n\tpublic PVector strokeMin(){\n\t\treturn lsMin;\n\t}\n\n\t/**\n\t * Set new minimum values for RGB laser, as 3 floats.\n\t * \n\t * @param minR\n\t *            value between 0.0 - 255.0\n\t * @param minG\n\t *            value between 0.0 - 255.0\n\t * @param minB\n\t *            value between 0.0 - 255.0\n\t */\n\tpublic void strokeMin(float minR, float minG, float minB){\n\t\tstrokeMin(new PVector(minR, minG, minB));\n\t}\n\n\t/**\n\t * Set new minimum values for RGB laser, as PVector.\n\t * \n\t * @param minPV\n\t *            PVector with 3 values between 0.0 - 255.0\n\t */\n\tpublic void strokeMin(PVector minPV){\n\t\tlsMin = new PVector(minPV.x, minPV.y, minPV.z);\n\t}\n\t\n\t/**\n\t * Returns current white balance (mixture for white) settings for RGB laser.\n\t * \n\t * @return PVector\n\t */\n\tpublic PVector strokeWB(){\n\t\treturn lsWB;\n\t}\n\n\t/**\n\t * Set white balance (mixture for white) for RGB laser, as 3 floats.\n\t * \n\t * @param wbR\n\t *            value between 0.0 - 255.0\n\t * @param wbG\n\t *            value between 0.0 - 255.0\n\t * @param wbB\n\t *            value between 0.0 - 255.0\n\t */\n\tpublic void strokeWB(float wbR, float wbG, float wbB){\n\t\tstrokeWB(new PVector(wbR, wbG, wbB));\n\t}\n\n\t/**\n\t * Set white balance (mixture for white) for RGB laser, as PVector.\n\t * \n\t * @param wbPV\n\t *            PVector with 3 values between 0.0 - 255.0\n\t */\n\tpublic void strokeWB(PVector wbPV){\n\t\tlsWB = new PVector(wbPV.x, wbPV.y, wbPV.z);\n\t}\n\n\t/**\n\t * Returns current dashes used for RGB waves of laser.\n\t * \n\t * @return PVector\n\t */\n\tpublic PVector strokeDash(){\n\t\treturn lsDash;\n\t}\n\n\t/**\n\t * Set same number of dashes for RGB laser.\n\t * \n\t * @param newDash\n\t *            int\n\t */\n\tpublic void strokeDash(int newDash){\n\t\tstrokeDash(new PVector(newDash, newDash, newDash));\n\t}\n\n\t/**\n\t * Set seperate number of dashes per color for RGB laser.\n\t * \n\t * @param newDashR\n\t *            int\n\t * @param newDashG\n\t *            int\n\t * @param newDashB\n\t *            int\n\t */\n\tpublic void strokeDash(int newDashR, int newDashG, int newDashB){\n\t\tstrokeDash(new PVector(newDashR, newDashG, newDashB));\n\t}\n\n\t/**\n\t * Set dashes for RGB laser, as PVector.\n\t * \n\t * @param newDash\n\t *            PVector with 3 values\n\t */\n\tpublic void strokeDash(PVector newDash){\n\t\tlsDash = new PVector(newDash.x, newDash.y, newDash.z);\n\t}\n\n\t/**\n\t * Set stroke for RGB laser, as 3 floats.\n\t * \n\t * @param r\n\t *            float from 0.0 – 255.0\n\t * @param g\n\t *            float from 0.0 – 255.0\n\t * @param b\n\t *            float from 0.0 – 255.0\n\t */\n\tpublic void stroke(float r, float g, float b){\n\t\tstroke(new PVector(r, g, b));\n\t}\n\n\t/**\n\t * Set stroke for RGB laser, as PVector.\n\t * \n\t * @param newDash\n\t *            PVector with 3 values, from 0.0 – 255.0\n\t */\n\tpublic void stroke(PVector rgb){\n\t\tfloat mr = 0f;\n\t\tfloat mg = 0f;\n\t\tfloat mb = 0f;\n\t\tif(rgb.x > 0f)\n\t\t\tmr = map(rgb.x, 0f, 255f, (lsMin.x/255f), 1f);\n\t\tif(rgb.y > 0f)\n\t\t\tmg = map(rgb.y, 0f, 255f, (lsMin.y/255f), 1f);\n\t\tif(rgb.z > 0f)\n\t\t\tmb = map(rgb.z, 0f, 255f, (lsMin.z/255f), 1f);\n\t\t\n\t\tif(rgb.x == 255f && rgb.y == 255f && rgb.z == 255f){\n\t\t\tmr = lsWB.x / 255f;\n\t\t\tmg = lsWB.y / 255f;\n\t\t\tmb = lsWB.z / 255f;\n\t\t}\n\t\tRGBshape.add(new PVector(mr, mg, mb));\n\t}\n\n\t/**\n\t * Get current frequency for R, G, B oscillators for laser as a PVector.\n\t * \n\t * @return PVector\n\t */\n\tpublic PVector strokeFreq() {\n\t\treturn lsFreq;\n\t}\n\n\t/**\n\t * Set new frequency for all RGB oscillators of laser together as single float.\n\t * \n\t * @param newFreq\n\t *            float\n\t */\n\tpublic void strokeFreq(float newFreq) {\n\t\tlsFreq = new PVector(newFreq, newFreq, newFreq);\n\t\tstrokeFreq(lsFreq);\n\t}\n\n\t/**\n\t * Set new frequency for all R + G + B oscillators of laser.\n\t * \n\t * @param newFreqR\n\t *            float\n\t * @param newFreqG\n\t *            float\n\t * @param newFreqB\n\t *            float\n\t */\n\tpublic void strokeFreq(float newFreqR, float newFreqG, float newFreqB) {\n\t\tlsFreq = new PVector(newFreqR, newFreqG, newFreqB);\n\t\tstrokeFreq(lsFreq);\n\t}\n\n\t/**\n\t * Set new frequency for each RGB oscillator of laser separately using a PVector.\n\t * \n\t * @param newFreq\n\t *            PVector\n\t */\n\tpublic void strokeFreq(PVector newFreq) {\n\t\tlsFreq = newFreq;\n\t\twaveR.setFrequency(lsFreq.x);\n\t\twaveG.setFrequency(lsFreq.y);\n\t\twaveB.setFrequency(lsFreq.z);\n\t}\n\n\t/**\n\t * Get current amplitude setting of XY oscillators.\n\t * \n\t * @return float\n\t */\n\tpublic PVector amp() {\n\t\treturn amp;\n\t}\n\n\t/**\n\t * Set new amplitude for both XYZ oscillators as float.\n\t * \n\t * @param newAmp\n\t *            value between 0.0 - 1.0\n\t */\n\tpublic void amp(float newAmp) {\n\t\tamp.x = constrain(newAmp, 0f, 1f);\n\t\tamp.y = constrain(newAmp, 0f, 1f);\n\t\tif(useVectrex){\n\t\t\tamp.x *= vectrexAmp;\n\t\t}\n\n\t\twaveX.setAmplitude(amp.x);\n\t\twaveY.setAmplitude(amp.y);\n\t\tif (useZ) {\n\t\t\tamp.z = constrain(newAmp, 0f, 1f);\n\t\t\twaveZ.setAmplitude(amp.z);\n\t\t}\n\t}\n\n\t/**\n\t * Set new amplitude for both X + Y oscillators as float.\n\t * \n\t * @param newAmpX\n\t *            value between 0.0 - 1.0\n\t * @param newAmpY\n\t *            value between 0.0 - 1.0\n\t */\n\tpublic void amp(float newAmpX, float newAmpY) {\n\t\tamp.x = constrain(newAmpX, 0f, 1f);\n\t\tif(useVectrex)\n\t\t\tamp.x *= vectrexAmp;\n\t\tamp.y = constrain(newAmpY, 0f, 1f);\n\t\twaveX.setAmplitude(amp.x);\n\t\twaveY.setAmplitude(amp.y);\n\t}\n\n\t/**\n\t * Set new amplitude for both X + Y + Z oscillators as float.\n\t * \n\t * @param newAmpX\n\t *            value between 0.0 - 1.0\n\t * @param newAmpY\n\t *            value between 0.0 - 1.0\n\t * @param newAmpZ\n\t *            value between 0.0 - 1.0\n\t */\n\tpublic void amp(float newAmpX, float newAmpY, float newAmpZ) {\n\t\tamp.x = constrain(newAmpX, 0f, 1f);\n\t\tif(useVectrex)\n\t\t\tamp.x *= vectrexAmp;\n\t\tamp.y = constrain(newAmpY, 0f, 1f);\n\t\twaveX.setAmplitude(amp.x);\n\t\twaveY.setAmplitude(amp.y);\n\t\tif (useZ) {\n\t\t\tamp.z = constrain(newAmpZ, 0f, 1f);\n\t\t\twaveZ.setAmplitude(amp.z);\n\t\t}\n\t}\n\n\t/**\n\t * Set new amplitude for each XYZ oscillator separately using a PVector for\n\t * the values.\n\t * \n\t * @param newAmp\n\t *            PVector of values between 0.0 - 1.0\n\t */\n\tpublic void amp(PVector newAmp) {\n\t\tfloat tempX = constrain(newAmp.x, 0f, 1f);\n\t\tif(useVectrex)\n\t\t\ttempX *= vectrexAmp;\n\t\tfloat tempY = constrain(newAmp.y, 0f, 1f);\n\t\twaveX.setAmplitude(tempX);\n\t\twaveY.setAmplitude(tempY);\n\t\tif (useZ) {\n\t\t\tfloat tempZ = constrain(newAmp.z, 0f, 1f);\n\t\t\twaveZ.setAmplitude(tempZ);\n\t\t}\n\t}\n\n\t/**\n\t * Get current frequency for X, Y, Z oscillators as a PVector.\n\t * \n\t * @return PVector\n\t */\n\tpublic PVector freq() {\n\t\treturn freq;\n\t}\n\n\t/**\n\t * Set new frequency for all XYZ oscillators together as single float.\n\t * \n\t * @param newFreq\n\t *            float\n\t */\n\tpublic void freq(float newFreq) {\n\t\tfreq = new PVector(newFreq, newFreq, newFreq);\n\t\twaveX.setFrequency(freq.x);\n\t\twaveY.setFrequency(freq.y);\n\t\tif (useZ)\n\t\t\twaveZ.setFrequency(freq.z);\n\t}\n\n\t/**\n\t * Set new frequency for all X + Y oscillators.\n\t * \n\t * @param newFreqX\n\t *            float\n\t * @param newFreqY\n\t *            float\n\t */\n\tpublic void freq(float newFreqX, float newFreqY) {\n\t\tfreq.x = newFreqX;\n\t\tfreq.y = newFreqY;\n\t\twaveX.setFrequency(freq.x);\n\t\twaveY.setFrequency(freq.y);\n\t}\n\n\t/**\n\t * Set new frequency for all X + Y + Z oscillators.\n\t * \n\t * @param newFreqX\n\t *            float\n\t * @param newFreqY\n\t *            float\n\t * @param newFreqZ\n\t *            float\n\t */\n\tpublic void freq(float newFreqX, float newFreqY, float newFreqZ) {\n\t\tfreq.x = newFreqX;\n\t\tfreq.y = newFreqY;\n\t\twaveX.setFrequency(freq.x);\n\t\twaveY.setFrequency(freq.y);\n\t\tif (useZ) {\n\t\t\tfreq.z = newFreqZ;\n\t\t\twaveZ.setFrequency(freq.z);\n\t\t}\n\t}\n\n\t/**\n\t * Set new frequency for each XYZ oscillator separately using a PVector for\n\t * the values.\n\t * \n\t * @param newFreq\n\t *            PVector\n\t */\n\tpublic void freq(PVector newFreq) {\n\t\tfreq = newFreq;\n\t\twaveX.setFrequency(freq.x);\n\t\twaveY.setFrequency(freq.y);\n\t\tif (useZ)\n\t\t\twaveZ.setFrequency(freq.z);\n\t}\n\t\n\t/**\n\t * Adjust the (x, y) panning, mainly useful if swapping cables digitally. Default (-1.0, 1.0)\n\t * \n\t * @param panXVal\n\t *            float - pan for x/left channel\n\t * @param panYVal\n\t *            float - pan for y/right channel\t */\n\tpublic void pan(float panXVal, float panYVal) {\n\t\tpanX.setPan(panXVal);\n\t\tpanY.setPan(panYVal);\n\t}\n\n\t/**\n\t * Enable/Disable easing transitions from one set of buildWaves() to the\n\t * next. Default is false. Deprecated in v2.0+\n\t * \n\t * @param easeBool\n\t *            true/false\n\t */\n\tpublic void ease(boolean easeBool) {\n\t\tuseEase = easeBool;\n\t}\n\n\t/**\n\t * Check if easing between each frame of buildWaves() is enabled.\n\t * Deprecated in v2.0+\n\t * \n\t * @return boolean\n\t */\n\tpublic boolean ease() {\n\t\treturn useEase;\n\t}\n\n\t/**\n\t * Returns current easeAmount, 0.0 - 1.0. Deprecated in v2.0+\n\t * \n\t * @return float\n\t */\n\tpublic float easeAmount() {\n\t\treturn easeVal;\n\t}\n\n\t/**\n\t * Set new easing value for speed between buildWave() transitions. Deprecated in v2.0+\n\t * \n\t * @param newEaseValue\n\t *            float between 0.0 - 1.0\n\t */\n\tpublic void easeAmount(float newEaseValue) {\n\t\teaseVal = newEaseValue;\n\t}\n\n\t/**\n\t * Enable/Disable debug view for comparing waveform to shape.\n\t * \n\t * @param debugBool\n\t *            true/false\n\t */\n\tpublic void debugView(boolean debugBool) {\n\t\tdebugWave = debugBool;\n\t}\n\n\t/**\n\t * Check if debugView is active.\n\t * \n\t * @return boolean\n\t */\n\tpublic boolean debugView() {\n\t\treturn debugWave;\n\t}\n\n\t/**\n\t * Get size of wavetables. By default, it's the same as the\n\t * outXY.bufferSize()\n\t * \n\t * @return newSize\n\t *            int\n\t */\n\tpublic int waveSize() {\n\t\treturn waveSizeVal;\n\t}\n\n\t/**\n\t * Set custom size for wavetables. By default, it's the same as the\n\t * outXY.bufferSize()\n\t * \n\t * @param newSize\n\t *            int\n\t */\n\tpublic void waveSize(int newSize) {\n\t\twaveSizeVal = newSize;\n\t\tshapeY = new float[waveSizeVal];\n\t\tshapeX = new float[waveSizeVal];\n\t\t// shapeZ = new float[waveSizeVal];\n\t\tshapePreY = new float[waveSizeVal];\n\t\tshapePreX = new float[waveSizeVal];\n\t\t// shapePreZ = new float[waveSizeVal];\n\t\ttableX.setWaveform(shapeX);\n\t\ttableY.setWaveform(shapeY);\n\t\t// if (useZ)\n\t\t// tableZ.setWaveform(shapeZ);\n\t}\n\n\t/**\n\t * Clears the waveforms from previous buildWaves(). Useful to call at top of\n\t * draw(), similar to using background() to clear the slate before building\n\t * the waveforms at the bottom of your draw with buildWaves().\n\t */\n\tpublic void clearWaves() {\n\t\tif(!busy){\n\t\t\tfor (int i = 0; i < shapeX.length; i++) {\n\t\t\t\tshapePreX[i] = 0;\n\t\t\t\tshapePreY[i] = 0;\n\t\t\t}\n\n\t\t\tif (useZ) {\n\t\t\t\tfor (int i = 0; i < shapeZ.length; i++) {\n\t\t\t\t\tshapePreZ[i] = zaxisMin;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tshapes = new XYShapeList();\n\t\t\tcurrentShape = null;\n\n\t\t\tif(useLaser)\n\t\t\t\tRGBshape = new XYShape();\n\t\t}\n\t}\n\t\n\t\n\tprivate double lerp(double start, double end, double amt) {\n\t\t  return start + (end-start)*amt;\n\t\t}\n\t\n\tpublic void buildWavesTest(ArrayList<PVector> wc) {\n\t\tif(wc.size() > 128) {\n\t\t\twaveSize(floor((wc.size())/2)*2);\n\t\t}\n//\t\twaveSize(1024);\n\t\tfloat[] mfx = new float[wc.size()];\n\t\tfloat[] mfy = new float[wc.size()];\n\t\tfor(int i=0; i<wc.size();i++) {\n\t\t\tPVector tc = wc.get(i);\n\t\t\tmfx[i] = map(tc.x, 0f, 1f, -1f, 1f);\n\t\t\tmfy[i] = map(tc.y, 0f, 1f, 1f, -1f);\n\t\t}\n\t\ttableX.setWaveform(mfx);\n\t\ttableY.setWaveform(mfy);\n\t}\n\n\t/**\n\t * Generate the XY oscillator waveforms from all added shapes for sending\n\t * audio to vector display. Call this after drawing any primitive shapes.\n\t * New Rendering mode in place, if old dots style is preferred, use\n\t * buildWaves(-1).\n\t * \n\t * @param bwm int for buildWaves mode\n\t */\n\tpublic void buildWaves(int bwm) {\n\t\tif(bwm == 0) { // waveform gen v4 mar 2020... 23\n\t\t\tif (shapes.size() > 0) {\n\t\t\t\tdouble wave_size = shapes.getPoints().size()*stepsSize;\n\t\t\t\tdouble total_dist = shapes.getDistance();\n\t\t\t\tdouble tot = 0.0;\n\t\t\t\tArrayList<PVector> wave_col = new ArrayList<PVector>();\n\t\t\t\tfor (int i=0; i < shapes.size(); i++) {\n\t\t\t\t\tdouble shapeDist = shapes.get(i).getDistance();\n\t\t\t\t\tArrayList<PVector> pv = shapes.get(i);\n\t\t\t\t\tfor (int j=0; j < pv.size()-1; j++) { // what about point?\n\t\t\t\t\t\tPVector p1 = pv.get(j);\n\t\t\t\t\t\tPVector p2 = pv.get(j+1);\n\t\t\t\t\t\tdouble line_dist = dist(p1.x, p1.y, p2.x, p2.y);\n\t\t\t\t\t\ttot += line_dist;\n\t\t\t\t\t\tdouble sec_per = Math.round(1+line_dist / total_dist * wave_size);\n\t\t\t\t\t\tdouble steps = 1.0 / sec_per;\n\t\t\t\t\t\tfor (int k=0; k <= sec_per; k++) {\n\t\t\t\t\t\t\tPVector seg = PVector.lerp(p1, p2, (float)((float)k*steps));\n\t\t\t\t\t\t\twave_col.add(seg);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\tfloat[] mfx = new float[waveSize()];\n\t\t\t\tfloat[] mfy = new float[waveSize()];\n\t\t\t\tfloat[] mfz = new float[waveSize()];\n\t\t\t\tfor(int i=0; i<waveSize();i++) {\n\t\t\t\t\tint waveIndex = floor(map(i, 0, waveSize(), 0, wave_col.size()));\n\t\t\t\t\tPVector tc = wave_col.get(waveIndex);\n\t\t\t\t\tmfx[i] = tc.x * 2f - 1f;\n\t\t\t\t\tmfy[i] = tc.y * -2f + 1f;\n\t\t\t\t\tmfz[i] = zaxisMax;\n\t\t\t\t\t\n\t\t\t\t\t// *** double check if z works w/ shape on.off..\n\t\t\t\t\tif(tc.z == 1f)\n\t\t\t\t\t\tmfz[i] = zaxisMin;\n\t\t\t\t\t\n\t\t\t\t\t// *** test vectrex\n\t\t\t\t\tfloat tfxx = mfx[i];\n\t\t\t\t\tfloat tfyy = mfy[i];\n\n\t\t\t\t\tif(useVectrex){\n\t\t\t\t\t\tif(vectrexRotation == 90){\n\t\t\t\t\t\t\tmfx[i] = tfyy;\n\t\t\t\t\t\t\tmfy[i] = tfxx*-1;\n\t\t\t\t\t\t}else if(vectrexRotation == -90){\n\t\t\t\t\t\t\tmfx[i] = tfyy*-1;\n\t\t\t\t\t\t\tmfy[i] = tfxx;\n\t\t\t\t\t\t}else if(vectrexRotation == 0){\n\t\t\t\t\t\t\tmfx[i] = tfxx*-1;\n\t\t\t\t\t\t\tmfy[i] = tfyy*-1;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\tsetWaveforms(mfx, mfy, mfz);\n\t\t\t\t\n\t\t\t\tif(useLaser){\n\t\t\t\t\tbuildLaser();\n\t\t\t\t}\n\t\t\t}else {\n\t\t\t\temptyWave();\n\t\t\t}\n\t\t} else if(bwm == -3){ // waveform gen v3 sep 2018\n\t\t\tif (shapes.size() > 0) {\n\t\t\t\tXYWavetable mx = new XYWavetable(2);\n\t\t\t\tXYWavetable my = new XYWavetable(2);\n\t\t\t\tXYWavetable mz = new XYWavetable(2);\n\t\t\t\tfloat[] mfx = new float[0];\n\t\t\t\tfloat[] mfy = new float[0];\n\t\t\t\tfloat[] mfz = new float[0];\n\n\t\t\t\tfor (XYShape shape : shapes) {\n\t\t\t\t\tXYWavetable tx = new XYWavetable(2);\n\t\t\t\t\tXYWavetable ty = new XYWavetable(2);\n\t\t\t\t\tXYWavetable tz = new XYWavetable(2);\n\t\t\t\t\tfloat[] tfx = new float[shape.size()];\n\t\t\t\t\tfloat[] tfy = new float[shape.size()];\n\t\t\t\t\tfloat[] tfz = new float[shape.size()];\n\n\t\t\t\t\tfor (int i = 0; i < shape.size(); i++) {\n\t\t\t\t\t\tif(i < tfx.length){\n\t\t\t\t\t\t\tPVector tc = shape.get(i);\n\n\t\t\t\t\t\t\ttfx[i] = map(tc.x, 0f, 1f, -1f, 1f);\n\t\t\t\t\t\t\ttfy[i] = map(tc.y, 0f, 1f, 1f, -1f);\n\t\t\t\t\t\t\ttfz[i] = zaxisMin;\n\n\t\t\t\t\t\t\tif(tc.z == 1f)\n\t\t\t\t\t\t\t\ttfz[i] = zaxisMax;\n\n\t\t\t\t\t\t\tfloat tfxx = tfx[i];\n\t\t\t\t\t\t\tfloat tfyy = tfy[i];\n\n\t\t\t\t\t\t\tif(useVectrex){\n\t\t\t\t\t\t\t\tif(vectrexRotation == 90){\n\t\t\t\t\t\t\t\t\ttfx[i] = tfyy;\n\t\t\t\t\t\t\t\t\ttfy[i] = tfxx*-1;\n\t\t\t\t\t\t\t\t}else if(vectrexRotation == -90){\n\t\t\t\t\t\t\t\t\ttfx[i] = tfyy*-1;\n\t\t\t\t\t\t\t\t\ttfy[i] = tfxx;\n\t\t\t\t\t\t\t\t}else if(vectrexRotation == 0){\n\t\t\t\t\t\t\t\t\ttfx[i] = tfxx*-1;\n\t\t\t\t\t\t\t\t\ttfy[i] = tfyy*-1;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\ttx.setWaveform(tfx);\n\t\t\t\t\tty.setWaveform(tfy);\n\t\t\t\t\ttz.setWaveform(tfz);\n\t\t\t\t\tmfx = concat(mfx, tx.getWaveform());\n\t\t\t\t\tmfy = concat(mfy, ty.getWaveform());\n\t\t\t\t\tmfz = concat(mfz, tz.getWaveform());\n\n\t\t\t\t}\n\n\t\t\t\tsetWaveforms(mfx, mfy, mfz);\n\t\t\t\t\n\t\t\t\tif(useLaser){\n\t\t\t\t\tbuildLaser();\n\t\t\t\t}\n\t\t\t}else{\n\t\t\t\temptyWave();\n\t\t\t}\n\t\t} else if (bwm == -2) { // waveform gen v2 may 2018\n\t\t\tif (shapes.size() > 0) {\n\t\t\t\tif (shapes.totalSize() < waveSizeValOG) {\n\t\t\t\t\tif (waveSize() != shapes.totalSize()) {\n\t\t\t\t\t\twaveSize(shapes.totalSize());\n\t\t\t\t\t}\n\n\t\t\t\t\tint SID = 0;\n\t\t\t\t\tif (waveSize() == shapes.totalSize()) {\n\t\t\t\t\t\tfor (XYShape shape : shapes) {\n\t\t\t\t\t\t\tfor (int i = 0; i < shape.size(); i++) {\n\t\t\t\t\t\t\t\tshapePreX[SID] = map(shape.get(i).x, 0f, 1f, -1f, 1f);\n\t\t\t\t\t\t\t\tshapePreY[SID] = map(shape.get(i).y, 0f, 1f, 1f, -1f);\n\t\t\t\t\t\t\t\tSID++;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tif (waveSize() != waveSizeValOG)\n\t\t\t\t\t\twaveSize(waveSizeValOG);\n\n\t\t\t\t\tArrayList<PVector> ts = shapes.getPoints();\n\t\t\t\t\tfor (int i = 0; i < shapeX.length; i++) {\n\t\t\t\t\t\tint ptsSel = (int) Math.floor(map(i, 0f, shapeX.length, 0, ts.size()));\n\t\t\t\t\t\tshapePreX[i] = map(ts.get(ptsSel).x, 0f, 1f, -1f, 1f);\n\t\t\t\t\t\tshapePreY[i] = map(ts.get(ptsSel).y, 0f, 1f, 1f, -1f);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\twaveSize(1);\n\t\t\t\tshapePreX[0] = 0f;\n\t\t\t\tshapePreY[0] = 0f;\n\t\t\t}\n\t\t} else if (bwm == -1) { // waveform gen v1 jul 2017\n\t\t\tif (waveSize() != waveSizeValOG)\n\t\t\t\twaveSize(waveSizeValOG);\n\t\t\tif (shapes.size() > 0) {\n\t\t\t\tArrayList<PVector> ts = shapes.getPoints();\n\t\t\t\tfor (int i = 0; i < shapeX.length; i++) {\n\t\t\t\t\tint ptsSel = (int) Math.floor(map(i, 0f, shapeX.length, 0, ts.size()));\n\t\t\t\t\tshapePreX[i] = map(ts.get(ptsSel).x, 0f, 1f, -1f, 1f);\n\t\t\t\t\tshapePreY[i] = map(ts.get(ptsSel).y, 0f, 1f, 1f, -1f);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif(bwm == -1 || bwm == -2){\n\t\t\t// easing\n\t\t\tif (useEase) {\n\t\t\t\teaseWaves();\n\t\t\t} else {\n\t\t\t\tfor (int i = 0; i < shapePreX.length; i++) {\n\t\t\t\t\tshapeX[i] = shapePreX[i];\n\t\t\t\t\tshapeY[i] = shapePreY[i];\n\n\t\t\t\t\tif(useVectrex){\n\t\t\t\t\t\tif(vectrexRotation == 90){\n\t\t\t\t\t\t\tshapeX[i] = shapePreY[i];\n\t\t\t\t\t\t\tshapeY[i] = shapePreX[i]*-1;\n\t\t\t\t\t\t}else if(vectrexRotation == -90){\n\t\t\t\t\t\t\tshapeX[i] = shapePreY[i]*-1;\n\t\t\t\t\t\t\tshapeY[i] = shapePreX[i];\n\t\t\t\t\t\t}else if(vectrexRotation == 0){\n\t\t\t\t\t\t\tshapeX[i] = shapePreX[i]*-1;\n\t\t\t\t\t\t\tshapeY[i] = shapePreY[i]*-1;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (useZ)\n\t\t\t\t\t\tshapeZ[i] = shapePreZ[i];\n\t\t\t\t}\n\n\t\t\t\tif (useZ) {\n\t\t\t\t\tfor (int i = 0; i < shapePreZ.length; i++) {\n\t\t\t\t\t\tshapeZ[i] = shapePreZ[i];\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// smooth\n\t\t\tif (useSmooth) {\n\t\t\t\ttableX.smooth(smoothVal);\n\t\t\t\ttableY.smooth(smoothVal);\n\t\t\t}\n\t\t}\n\t}\n\t\n\tpublic void setWaveforms(float[] mfx, float[] mfy) {\n\t\tfloat[] mfz = new float[0];\n\t\tsetWaveforms(mfx, mfy, mfz);\n\t}\n\t\n\tpublic void setWaveforms(float[] mfx, float[] mfy, float[] mfz) {\n\t\t// limit points\n\t\tif(useLimitPoints && (mfx.length > limitPointsVal || mfy.length > limitPointsVal)){\n\t\t\tfloat[] lx = new float[limitPointsVal];\n\t\t\tfloat[] ly = new float[limitPointsVal];\n\t\t\tfloat[] lz = new float[limitPointsVal];\n\t\t\tfor(int i=0; i < limitPointsVal; i++){\n\t\t\t\tint mfxSel = floor(map(i, 0, limitPointsVal, 0, mfx.length));\n\t\t\t\tint mfySel = floor(map(i, 0, limitPointsVal, 0, mfy.length));\n\t\t\t\tint mfzSel = floor(map(i, 0, limitPointsVal, 0, mfz.length));\n\t\t\t\tlx[i] = mfx[mfxSel];\n\t\t\t\tly[i] = mfy[mfySel];\n\t\t\t\tlz[i] = mfz[mfzSel];\n\t\t\t}\n\t\t\ttableX.setWaveform(lx);\n\t\t\ttableY.setWaveform(ly);\n\t\t\tif(useZ)\n\t\t\t\ttableZ.setWaveform(lz);\n\t\t}else{\n\t\t\ttableX.setWaveform(mfx);\n\t\t\ttableY.setWaveform(mfy);\n\t\t\tif(useZ)\n\t\t\t\ttableZ.setWaveform(mfz);\n\t\t}\n\t}\n\t\n\tprivate void emptyWave() {\n\t\ttableX.setWaveform(new float[0]);\n\t\ttableY.setWaveform(new float[0]);\n\t\tif(useZ)\n\t\t\ttableZ.setWaveform(new float[0]);\n\n\t\tif(useLaser){\n\t\t\ttableR.setWaveform(new float[0]);\n\t\t\ttableG.setWaveform(new float[0]);\n\t\t\ttableB.setWaveform(new float[0]);\n\t\t}\n\t}\n\t\n\t// *** remove??\n\tpublic void warpWave(int mx, int pmx) {\n\t\t float warpPoint = constrain( (float)pmx / myParent.width, 0, 1 );\n\t\t float warpTarget = constrain( (float)mx / myParent.width, 0, 1 );\n\t\t tableX.warp( warpPoint, warpTarget );\n\t\t tableY.warp( warpPoint, warpTarget );\n\t}\n\t\n\tprivate void buildLaser() {\n\t\t// spotkiller checkSize\n\t\tboolean checkSize = false;\n\t\tAudioOutput tempXY = outXY;\n\n\t\tfor (int i = 0; i < tempXY.bufferSize() - 1; i++) {\n\t\t\tfloat lAudio = abs(tempXY.left.get(i) * (float) xyWidth / 2);\n\t\t\tfloat rAudio = abs(tempXY.right.get(i) * (float) xyHeight / 2);\n\t\t\tif(lAudio > laserCutoffVal || rAudio > laserCutoffVal)\n\t\t\t\tcheckSize = true;\n\t\t}\n\t\tif(checkSize){\n\t\t\tif(RGBshape.size() > 0){\n\t\t\t\tbuildColorWave(tableR, \"x\", floor(lsDash.x));\n\t\t\t\tbuildColorWave(tableG, \"y\", floor(lsDash.y));\n\t\t\t\tbuildColorWave(tableB, \"z\", floor(lsDash.z));\n\t\t\t}else{\n\t\t\t\tfloat[] tfr = {lsWB.x/255f};\n\t\t\t\tfloat[] tfg = {lsWB.y/255f};\n\t\t\t\tfloat[] tfb = {lsWB.z/255f};\n\t\t\t\ttableR.setWaveform(tfr);\n\t\t\t\ttableG.setWaveform(tfg);\n\t\t\t\ttableB.setWaveform(tfb);\n\t\t\t}\n\t\t}else{\n\t\t\ttableR.setWaveform(new float[0]);\n\t\t\ttableG.setWaveform(new float[0]);\n\t\t\ttableB.setWaveform(new float[0]);\n\t\t}\n\t}\n\n\tprivate void buildColorWave(XYWavetable tableTemp, String RGBval, int dashTemp){\n\t\tXYShape shapeTemp = new XYShape();\t\t\t\t\t\t\n\t\tfor (int i = 0; i < RGBshape.size()*dashTemp; i++) {\n\t\t\tint RGBshapeIndex = floor(map(i, 0, RGBshape.size()*dashTemp, 0, RGBshape.size()));\n\t\t\tPVector tc = RGBshape.get(RGBshapeIndex);\n\t\t\tif(i%2==0 && dashTemp > 1) {\n\t\t\t\ttc = new PVector(-1, -1, -1);\n\t\t\t}\n\t\t\tshapeTemp.add(tc);\n\t\t}\n\n\t\tfloat[] tfr = new float[shapeTemp.size()];\n\t\tfor (int i = 0; i < shapeTemp.size(); i++) {\n\t\t\tPVector tc = shapeTemp.get(i);\n\t\t\tif(RGBval == \"x\") {\n\t\t\t\ttfr[i] = tc.x;\n\t\t\t}else if(RGBval == \"y\") {\n\t\t\t\ttfr[i] = tc.y;\n\t\t\t}else if(RGBval == \"z\") {\n\t\t\t\ttfr[i] = tc.z;\n\t\t\t}\n\t\t\t\n\t\t}\n\t\ttableTemp.setWaveform(tfr);\n\t}\n\n\t/**\n\t * Generate the XY(Z) oscillator waveforms from all added points/shapes for\n\t * sending audio to vector display. Call this after drawing any primitive\n\t * shapes.\n\t */\n\tpublic void buildWaves() {\n\t\tbuildWaves(0);\n\t}\n\n\t/**\n\t * Build custom X oscillator waveform. Use waveSize() to ensure you send the\n\t * right number of values.\n\t * \n\t * @param newWave\n\t *            Array of normalized floats between 0.0 - 1.0\n\t *            \n\t * @see #waveSize()\n\t * \n\t */\n\tpublic void buildX(float[] newWave) {\n\t\tfor (int i = 0; i < newWave.length; i++) {\n\t\t\tint sel = (int) Math.floor(map(i, 0f, newWave.length, 0f, shapePreX.length));\n\t\t\tshapePreX[i] = map(newWave[sel], 0f, 1f, -1f, 1f);\n\t\t}\n\n\t\tif (useEase) {\n\t\t\teaseWaves(shapePreX, shapeX);\n\t\t} else {\n\t\t\tfor (int i = 0; i < shapePreX.length; i++) {\n\t\t\t\tshapeX[i] = shapePreX[i];\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Build custom Y oscillator waveform. Use waveSize() to ensure you send the\n\t * right number of values.\n\t * \n\t * @param newWave\n\t *            Array of normalized floats between 0.0 - 1.0\n\t * @see #waveSize()\n\t */\n\tpublic void buildY(float[] newWave) {\n\t\tfor (int i = 0; i < newWave.length; i++) {\n\t\t\tint sel = (int) Math.floor(map(i, 0f, newWave.length, 0f, shapePreY.length));\n\t\t\tshapePreY[i] = map(newWave[sel], 0f, 1f, 1f, -1f);\n\t\t}\n\n\t\tif (useEase) {\n\t\t\teaseWaves(shapePreY, shapeY);\n\t\t} else {\n\t\t\tfor (int i = 0; i < shapePreY.length; i++) {\n\t\t\t\tshapeY[i] = shapePreY[i];\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Build custom Z oscillator waveform. Use waveSize() to ensure you send the\n\t * right number of values.\n\t * \n\t * @param newWave\n\t *            Array of normalized floats between 0.0 - 1.0\n\t * @see #waveSize()\n\t */\n\tpublic void buildZ(float[] newWave) {\n\t\tfor (int i = 0; i < newWave.length; i++) {\n\t\t\tint sel = (int) Math.floor(map(i, 0f, newWave.length, 0f, shapePreZ.length));\n\t\t\tshapePreZ[i] = map(newWave[sel], 0f, 1f, zaxisMin, zaxisMax);\n\t\t}\n\n\t\tif (useEase) {\n\t\t\teaseWaves(shapePreZ, shapeZ);\n\t\t} else {\n\t\t\tfor (int i = 0; i < shapePreZ.length; i++) {\n\t\t\t\tshapeZ[i] = shapePreZ[i];\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate void easeWaves(float[] sShape, float[] tShape) {\n\t\tfor (int i = 0; i < sShape.length; i++) {\n\t\t\tfloat targetX = sShape[i];\n\t\t\tfloat dx = targetX - tShape[i];\n\t\t\ttShape[i] += dx * easeVal;\n\t\t}\n\t}\n\n\tprivate void easeWaves() {\n\t\teaseWaves(shapePreX, shapeX);\n\t\teaseWaves(shapePreY, shapeY);\n\n\t\tif (useZ) {\n\t\t\teaseWaves(shapePreZ, shapeZ);\n\n\t\t}\n\t}\n\n\t/**\n\t * Check if waveform smoothing is enabled/disabled. Deprecated since v3\n\t * \n\t * @return boolean\n\t */\n\tpublic boolean smoothWaves() {\n\t\treturn useSmooth;\n\t}\n\n\t/**\n\t * Enable/disable Smooth waveforms to reduce visibility of points in\n\t * drawing. Default is false. Deprecated since v3\n\t * \n\t * @param smoothWavesBool\n\t *            true/false\n\t */\n\tpublic void smoothWaves(boolean smoothWavesBool) {\n\t\tuseSmooth = smoothWavesBool;\n\t}\n\n\t/**\n\t * Get number of steps for smoothing waveforms. Deprecated since v3\n\t * \n\t * @return int\n\t * \n\t * @see <a href=\n\t *      \"http://code.compartmental.net/minim/wavetable_method_smooth.html\">Minim\n\t *      » Wavetable » smooth()</a>\n\t */\n\tpublic int smoothWavesAmount() {\n\t\treturn smoothVal;\n\t}\n\n\t/**\n\t * Set number of steps for smoothing waveforms. Default is 12. Deprecated since v3\n\t * \n\t * @param swAmount\n\t *            new int value for smoothening waveform\n\t * @see <a href=\n\t *      \"http://code.compartmental.net/minim/wavetable_method_smooth.html\">Minim\n\t *      » Wavetable » smooth()</a>\n\t */\n\tpublic void smoothWavesAmount(int swAmount) {\n\t\tsmoothVal = swAmount;\n\t}\n\n\t/**\n\t * Draw all information\n\t * <ul>\n\t * <li>drawPath()\n\t * <li>drawWaveform()\n\t * <li>drawWave()\n\t * <li>drawXY()\n\t * <li>drawPoints()\n\t * </ul>\n\t */\n\tpublic void drawAll() {\n\t\tdrawPath();\n\t\tdrawWaveform();\n\t\tdrawWave();\n\t\tdrawXY();\n\t\tdrawPoints();\n\t}\n\t\n\tpublic void drawPath() {\n\t\tdrawPath(255, 255, 255);\n\t}\n\n\t/**\n\t * Draw path of points remapped from normalized values to width + height of\n\t * sketch.\n\t */\n\tpublic void drawPath(float r, float g, float b) {\n\t\tmyParent.pushStyle();\n\t\tmyParent.noFill();\n\t\tmyParent.stroke(r, g, b);\n\t\tmyParent.pushMatrix();\n\t\tmyParent.beginShape();\n\t\tfor (XYShape shape : shapes) {\n\t\t\tfor (int i = 0; i < shape.size(); i++) {\n\t\t\t\tfloat x = map(shape.get(i).x, 0f, 1f, 0f, xyWidth);\n\t\t\t\tfloat y = map(shape.get(i).y, 0f, 1f, 0f, xyHeight);\n\t\t\t\tmyParent.vertex(x, y);\n\t\t\t}\n\t\t}\n\t\tmyParent.endShape(OPEN);\n\t\tmyParent.popMatrix();\n\t\tmyParent.popStyle();\n\t}\n\t\n\tpublic void drawPoints() {\n\t\tdrawPoints(0, 255, 0);\n\t}\n\n\t/**\n\t * Draw points (as 3px ellipses) remapped from normalized values to width +\n\t * height of sketch.\n\t */\n\tpublic void drawPoints(float r, float g, float b) {\n\t\tmyParent.pushStyle();\n\t\tmyParent.fill(r, g, b);\n\t\tmyParent.noStroke();\n\t\tmyParent.pushMatrix();\n\t\tfor (XYShape shape : shapes) {\n\t\t\tfor (int i = 0; i < shape.size(); i++) {\n\t\t\t\tfloat x = map(shape.get(i).x, 0f, 1f, 0f, xyWidth);\n\t\t\t\tfloat y = map(shape.get(i).y, 0f, 1f, 0f, xyHeight);\n\t\t\t\tmyParent.ellipse(x, y, 3, 3);\n\t\t\t}\n\t\t}\n\t\tmyParent.popMatrix();\n\t\tmyParent.noFill();\n\t\tmyParent.popStyle();\n\t}\n\t\n\tpublic void drawXY() {\n\t\tdrawXY(50, 255, 50);\n\t}\n\n\t/**\n\t * Simulate X-Y mode of oscilloscope output.\n\t */\n\tpublic void drawXY(float r, float g, float b) {\n\t\tmyParent.pushStyle();\n\t\tmyParent.noFill();\n\t\tmyParent.stroke(r, g, b);\n\t\tmyParent.pushMatrix();\n\t\tmyParent.translate(xyWidth / 2, xyHeight / 2);\n\t\tmyParent.beginShape();\n\t\tAudioOutput tempXY;\n\t\tif (useMix) {\n\t\t\ttempXY = mixXY;\n\t\t} else {\n\t\t\ttempXY = outXY;\n\t\t}\n\n\t\tfor (int i = 0; i < tempXY.bufferSize() - 1; i++) {\n\t\t\tfloat lAudio = tempXY.left.get(i) * (float) xyWidth / 2;\n\t\t\tfloat rAudio = tempXY.right.get(i) * (float) xyHeight / 2;\n\n\t\t\tif(useVectrex){\n\t\t\t\tif(vectrexRotation == 90){\n\t\t\t\t\trAudio = tempXY.left.get(i) * (float) xyHeight / 2;\n\t\t\t\t\tlAudio = tempXY.right.get(i) * (float) xyWidth / 2 * -1f * vectrexAmp;\n\t\t\t\t}else if(vectrexRotation == -90){\n\t\t\t\t\trAudio = tempXY.left.get(i) * (float) xyHeight / 2 * -1f;\n\t\t\t\t\tlAudio = tempXY.right.get(i) * (float) xyWidth / 2 * vectrexAmp;\n\t\t\t\t}else if(vectrexRotation == 0){\n\t\t\t\t\tlAudio = tempXY.left.get(i) * (float) xyWidth / 2 * -1f;\n\t\t\t\t\trAudio = tempXY.right.get(i) * (float) xyHeight / 2 * -1f * vectrexAmp;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tmyParent.curveVertex(lAudio, rAudio * -1f);\n\t\t}\n\n\t\tmyParent.endShape();\n\n\t\tif (debugWave) {\n\t\t\tfloat mouseT = (myParent.mouseX / (float) xyWidth);\n\t\t\tfloat mx = tableX.value(mouseT) * (float) xyWidth / 2 * amp.x;\n\t\t\tfloat my = -tableY.value(mouseT) * (float) xyHeight / 2 * amp.y;\n\t\t\tmyParent.pushStyle();\n\t\t\tmyParent.fill(r, g, b);\n\t\t\tmyParent.noStroke();\n\t\t\tmyParent.ellipse(mx, my, debugSize, debugSize);\n\t\t\tmyParent.popStyle();\n\t\t}\n\n\t\tmyParent.popMatrix();\n\t\tmyParent.popStyle();\n\t}\n\n\tpublic void drawWaveform(){\n\t\tdrawWaveform(50, 50, 255, 255, 50, 50);\n\t}\n\t\n\t/**\n\t * Draw waveform of all XYZ oscillators.\n\t * <ul>\n\t * <li>Red: X</li>\n\t * <li>Blue: Y</li>\n\t * <li>Green: Z</li>\n\t * </ul>\n\t */\n\tpublic void drawWaveform(float lr, float lg, float lb, float rr, float rg, float rb) {\n\t\tmyParent.pushStyle();\n\t\tmyParent.noFill();\n\t\tmyParent.pushMatrix();\n\t\tmyParent.beginShape();\n\n\t\t// X -> L\n\t\tmyParent.stroke(lr, lg, lb);\n\t\tfor (int i = 0; i < xyWidth; i++) {\n\t\t\tmyParent.vertex(i, (float) xyHeight * .25f\n\t\t\t\t\t- ((float) xyHeight * .125f) * tableX.value((float) i / (float) xyWidth));\n\t\t}\n\t\tmyParent.endShape();\n\n\t\t// Y -> R\n\t\tmyParent.stroke(rr, rg, rb);\n\t\tmyParent.beginShape();\n\t\tfor (int i = 0; i < xyWidth; i++) {\n\t\t\tmyParent.vertex(i, (float) xyHeight * .75f\n\t\t\t\t\t- ((float) xyHeight * 0.125f) * tableY.value((float) i / (float) xyWidth));\n\t\t}\n\t\tmyParent.endShape();\n\n\n\t\tif (debugWave) {\n\t\t\tfloat mouseT = (myParent.mouseX / (float) xyWidth);\n\t\t\tfloat lx = myParent.mouseX;\n\t\t\tfloat ly = (float) xyHeight * .25f - ((float) xyHeight * .125f)\n\t\t\t\t\t* tableX.value((float) myParent.mouseX / (float) xyWidth);\n\t\t\tfloat ry = (float) xyHeight * .75f - ((float) xyHeight * .125f)\n\t\t\t\t\t* tableY.value((float) myParent.mouseX / (float) xyWidth);\n\t\t\tmyParent.pushStyle();\n\t\t\tmyParent.noStroke();\n\t\t\tmyParent.fill(lr, lg, lb);\n\t\t\tmyParent.ellipse(lx - 2, ly, debugSize, debugSize);\n\t\t\tmyParent.fill(rr, rg, rb);\n\t\t\tmyParent.ellipse(lx - 2, ry, debugSize, debugSize);\n\t\t\tmyParent.popStyle();\n\t\t}\n\n\t\t// Z\n\t\tif (useZ) {\n\t\t\tmyParent.stroke(50, 255, 50);\n\t\t\tmyParent.beginShape();\n\t\t\tfor (int i = 0; i < xyWidth; i++) {\n\t\t\t\tmyParent.vertex(i, (float) xyHeight * .5f\n\t\t\t\t\t\t- ((float) xyHeight * 0.125f) * tableZ.value((float) i / (float) xyWidth));\n\t\t\t}\n\n\t\t\tmyParent.endShape();\n\t\t}\n\t\tmyParent.popMatrix();\n\t\tmyParent.popStyle();\n\t}\n\n\t/**\n\t * Draw waveform of all laser RGB oscillators.\n\t * <ul>\n\t * <li>Red: R</li>\n\t * <li>Green: G</li>\n\t * <li>Blue: B</li>\n\t * </ul>\n\t */\n\tpublic void drawRGB(){\n\t\tif(useLaser){\n\t\t\tmyParent.pushStyle();\n\t\t\tmyParent.noFill();\n\t\t\tmyParent.pushMatrix();\n\n\t\t\t//R\n\t\t\tmyParent.stroke(255, 50, 50);\n\t\t\tmyParent.beginShape();\n\t\t\tfor (int i = 0; i < xyWidth; i++) {\n\t\t\t\tmyParent.vertex(i, (float) xyHeight * .25f\n\t\t\t\t\t\t- ((float) xyHeight * 0.125f) * tableR.value((float) i / (float) xyWidth));\n\t\t\t}\n\t\t\tmyParent.endShape();\n\n\t\t\t//G\n\t\t\tmyParent.stroke(50, 255, 50);\n\t\t\tmyParent.beginShape();\n\t\t\tfor (int i = 0; i < xyWidth; i++) {\n\t\t\t\tmyParent.vertex(i, (float) xyHeight * .5f\n\t\t\t\t\t\t- ((float) xyHeight * 0.125f) * tableG.value((float) i / (float) xyWidth));\n\t\t\t}\n\t\t\tmyParent.endShape();\n\n\t\t\t//B\n\t\t\tmyParent.stroke(50, 50, 255);\n\t\t\tmyParent.beginShape();\n\t\t\tfor (int i = 0; i < xyWidth; i++) {\n\t\t\t\tmyParent.vertex(i, (float) xyHeight * .75f\n\t\t\t\t\t\t- ((float) xyHeight * 0.125f) * tableB.value((float) i / (float) xyWidth));\n\t\t\t}\n\t\t\tmyParent.endShape();\n\n\t\t\tmyParent.popMatrix();\n\t\t\tmyParent.popStyle();\n\t\t}\n\t}\n\n\t/**\n\t * Draw wave of all XYZ oscillators.\n\t */\n\tpublic void drawWave() {\n\t\tmyParent.pushStyle();\n\t\tmyParent.stroke(255);\n\t\tmyParent.noFill();\n\t\tmyParent.pushMatrix();\n\t\tmyParent.beginShape();\n\n\t\tAudioOutput tempXY;\n\t\tif (useMix) {\n\t\t\ttempXY = mixXY;\n\t\t} else {\n\t\t\ttempXY = outXY;\n\t\t}\n\n\t\tfor (int i = 0; i < tempXY.bufferSize() - 1; i++) {\n\t\t\tfloat xAudio = map(i, 0, tempXY.bufferSize(), 0, xyWidth);\n\t\t\tfloat lAudio = tempXY.left.get(i);\n\t\t\t// curveVertex(lAudio, rAudio*-1);\n\t\t\tmyParent.vertex(xAudio, xyHeight * .25f - (xyHeight * .25f) * lAudio);\n\t\t}\n\t\tmyParent.endShape();\n\n\t\tmyParent.beginShape();\n\t\tfor (int i = 0; i < tempXY.bufferSize() - 1; i++) {\n\t\t\tfloat xAudio = map(i, 0, tempXY.bufferSize(), 0, xyWidth);\n\t\t\tfloat rAudio = tempXY.right.get(i);\n\t\t\t// curveVertex(lAudio, rAudio*-1);\n\t\t\tmyParent.vertex(xAudio, xyHeight * .75f + (xyHeight * .25f) * rAudio);\n\t\t}\n\t\tmyParent.endShape();\n\n\t\tif (useZ) {\n\t\t\tmyParent.beginShape();\n\t\t\tfor (int i = 0; i < outZ.bufferSize() - 1; i++) {\n\t\t\t\tfloat xAudio = map(i, 0, outZ.bufferSize(), 0, xyWidth);\n\t\t\t\tfloat lAudio = outZ.left.get(i);\n\t\t\t\t// curveVertex(lAudio, rAudio*-1);\n\t\t\t\tmyParent.vertex(xAudio, xyHeight * .5f - (xyHeight * .25f) * lAudio);\n\t\t\t}\n\t\t\tmyParent.endShape();\n\n\t\t}\n\t\tmyParent.popMatrix();\n\t\tmyParent.popStyle();\n\t}\n\n\tint rectM;\n\n\t/**\n\t * Get detail (number of points) for drawing an ellipse.\n\t * \n\t * @return int\n\t */\n\tpublic int ellipseDetail() {\n\t\treturn ellipseDetail;\n\t}\n\n\t/**\n\t * Set detail (number of points) for drawing an ellipse.\n\t * \n\t * @param newED\n\t *            int for facets of ellipse\n\t */\n\tpublic void ellipseDetail(int newED) {\n\t\tellipseDetail = abs(newED);\n\t}\n\t\n\t/**\n\t * Set detail (number of points) for drawing an ellipse.\n\t * \n\t * @param newED\n\t *            float for facets of ellipse (rounded)\n\t */\n\tpublic void ellipseDetail(float newED) {\n\t\tellipseDetail = abs(round(newED));\n\t}\n\n\t/**\n\t * Set rectMode (similar to Processing function).\n\t * \n\t * @param rectModeVal\n\t *            CORNER or CENTER\n\t * @see <a href=\"https://processing.org/reference/rectMode_.html\">Processing\n\t *      Reference » rectMode()</a>\n\t */\n\tpublic void rectMode(int rectModeVal) {\n\t\tif (rectModeVal == 0) {\n\t\t\trectM = 0;\n\t\t} else if (rectModeVal == 3) {\n\t\t\trectM = 3;\n\t\t}\n\t}\n\t\n\tint stepsSize = 24;\n\t/**\n\t * Get steps multiplier (number of segments) between each point.\n\t * \n\t * @return int\n\t */\n\tpublic int steps() {\n\t\treturn stepsSize;\n\t}\n\n\t/**\n\t * Set steps multiplier (number of segments) between each point.\n\t * \n\t * @param newSteps\n\t *            float for segments multiplier (rounded)\n\t */\n\tpublic void steps(float newSteps) {\n\t\tstepsSize = parseInt(newSteps);\n\t}\n\t\n\t// non-params drawing\n\tpublic void point() {\n\t\tline(xyWidth/2, xyHeight/2, xyWidth/2+1, xyHeight/2+1);\n\t}\n\t\n\t/**\n\t * Draw point, expects point(x, y).\n\t * \n\t * @param x float - x position of point\n\t * @param y float - y position of point\n\t * \n\t * @see <a href=\"https://processing.org/reference/point_.html\">Processing\n\t *      Reference » point()</a>\n\t */\n\tpublic void point(float x, float y) {\n\t\tline(x, y, x+1, y+1);\n\t}\n\n\t/**\n\t * Draw point, expects point(x, y, z).\n\t * \n\t * @param x float - x position of point\n\t * @param y float - y position of point\n\t * @param z float - z position of point\n\t * \n\t * @see <a href=\"https://processing.org/reference/point_.html\">Processing\n\t *      Reference » point()</a>\n\t */\n\tpublic void point(float x, float y, float z) {\n\t\tline(x, y, z, x+1, y+1, z+1);\n\t}\n\t\n\t// non-params drawing\n\tpublic void line() {\n\t\tbeginShape();\n\t\tvertex(0, 0);\n\t\tvertex(xyWidth, xyHeight);\n\t\tendShape();\n\t}\n\n\t/**\n\t * Draw line, expects line(x1, y1, x2, y2).\n\t * \n\t * @param x1 float - first x position of point\n\t * @param y1 float - first y position of point\n\t * @param x2 float - second x position of point\n\t * @param y2 float - second y position of point\n\t * \n\t * @see <a href=\"https://processing.org/reference/line_.html\">Processing\n\t *      Reference » line()</a>\n\t */\n\tpublic void line(float x1, float y1, float x2, float y2) {\n\t\tbeginShape();\n\t\tvertex(x1, y1);\n\t\tvertex(x2, y2);\n\t\tendShape();\n\t}\n\n\t/**\n\t * Draw line, expects line(x1, y1, z1, x2, y2, z2).\n\t * \n\t * @param x1 float - first x position of point\n\t * @param y1 float - first y position of point\n\t * @param z1 float - first z position of point\n\t * @param x2 float - second x position of point\n\t * @param y2 float - second y position of point\n\t * @param z2 float - second z position of point\n\t * \n\t * @see <a href=\"https://processing.org/reference/line_.html\">Processing\n\t *      Reference » line()</a>\n\t */\n\tpublic void line(float x1, float y1, float z1, float x2, float y2, float z2) {\n\t\tbeginShape();\n\t\tvertex(x1, y1, z1);\n\t\tvertex(x2, y2, z2);\n\t\tendShape();\n\t}\n\t\n\t// non-params drawing\n\t\tpublic void square() {\n\t\t\tint x = xyWidth/2;\n\t\t\tint y = xyHeight/2;\n\t\t\tint w = xyHeight;\n\t\t\tif (rectM == 3) {\n\t\t\t\tx -= w / 2;\n\t\t\t\ty -= w / 2;\n\t\t\t}\n\t\t\tvertexRect(x, y, w, w);\n\t\t}\n\t\n\t/**\n\t * Draw square, expects square(x, y, extent).\n\t * \n\t * @param x float - x position of square\n\t * @param y float - y position of square\n\t * @param extent float - width + height of square\n\t * \n\t * @see <a href=\"https://processing.org/reference/square_.html\">Processing\n\t *      Reference » rect()</a>\n\t */\n\tpublic void square(float x, float y, float e) {\n\t\tif (rectM == 3) {\n\t\t\tx -= e / 2;\n\t\t\ty -= e / 2;\n\t\t}\n\t\tvertexRect(x, y, e, e);\n\t}\n\t\n\t// non-params drawing\n\tpublic void rect() {\n\t\tint x = xyWidth/2;\n\t\tint y = xyHeight/2;\n\t\tint w = xyHeight;\n\t\tif (rectM == 3) {\n\t\t\tx -= w / 2;\n\t\t\ty -= w / 2;\n\t\t}\n\t\tvertexRect(x, y, w, w);\n\t}\n\t\n\t/**\n\t * Draw rectangle (square), expects rect(x, y, w).\n\t * \n\t * @param x float - x position of rectangle\n\t * @param y float - y position of rectangle\n\t * @param w float - width of rectangle\n\t * \n\t * @see <a href=\"https://processing.org/reference/rect_.html\">Processing\n\t *      Reference » rect()</a>\n\t */\n\tpublic void rect(float x, float y, float w) {\n\t\tif (rectM == 3) {\n\t\t\tx -= w / 2;\n\t\t\ty -= w / 2;\n\t\t}\n\t\tvertexRect(x, y, w, w);\n\t}\n\n\t/**\n\t * Draw rectangle, expects rect(x, y, w, h).\n\t * \n\t * @param x float - x position of rectangle\n\t * @param y float - y position of rectangle\n\t * @param w float - width of rectangle\n\t * @param h float - height of rectangle\n\t * \n\t * @see <a href=\"https://processing.org/reference/rect_.html\">Processing\n\t *      Reference » rect()</a>\n\t */\n\tpublic void rect(float x, float y, float w, float h) {\n\t\tif (rectM == 3) {\n\t\t\tx -= w / 2;\n\t\t\ty -= h / 2;\n\t\t}\n\t\tvertexRect(x, y, w, h);\n\t}\n\n\tprivate void vertexRect(float x1, float y1, float w1, float h1) {\n\t\tbeginShape();\n\t\tvertex(x1, y1);\n\t\tvertex(x1 + w1, y1);\n\t\tvertex(x1 + w1, y1 + h1);\n\t\tvertex(x1, y1 + h1);\n\t\tvertex(x1, y1);\n\t\t//\t\tif (useZ)\n\t\t//\t\t\tvertex(x1, y1);\n\t\tendShape();\n\t}\n\t\n\t// non-params drawing\n\tpublic void circle() {\n\t\tvertexEllipse(xyWidth/2, xyHeight/2, xyHeight, xyHeight);\n\t}\n\t\n\tpublic void ellipse() {\n\t\tvertexEllipse(xyWidth/2, xyHeight/2, xyHeight, xyHeight);\n\t}\n\t\n\t/**\n\t * Draw circle (ellipse), expects circle(x, y, extent).\n\t * \n\t * @param x float - x position of circle\n\t * @param y float - y position of circle\n\t * @param extent float - width + height of circle\n\t * \n\t * @see <a href=\"https://processing.org/reference/circle_.html\">Processing\n\t *      Reference » ellipse()</a>\n\t */\n\tpublic void circle(float x, float y, float e) {\n\t\tvertexEllipse(x, y, e, e);\n\t}\n\t\n\t/**\n\t * Draw ellipse (circle), expects ellipse(x, y, w).\n\t * \n\t * @param x float - x position of ellipse\n\t * @param y float - y position of ellipse\n\t * @param d float - diameter of ellipse\n\t * \n\t * @see <a href=\"https://processing.org/reference/ellipse_.html\">Processing\n\t *      Reference » ellipse()</a>\n\t */\n\tpublic void ellipse(float x, float y, float d) {\n\t\tvertexEllipse(x, y, d, d);\n\t}\n\n\t/**\n\t * Draw ellipse, expects ellipse(x, y, w, h).\n\t * \n\t * @param x float - x position of ellipse\n\t * @param y float - y position of ellipse\n\t * @param w float - width of ellipse\n\t * @param h float - height of ellipse\n\t * \n\t * @see <a href=\"https://processing.org/reference/ellipse_.html\">Processing\n\t *      Reference » ellipse()</a>\n\t */\n\tpublic void ellipse(float x, float y, float w, float h) {\n\t\tvertexEllipse(x, y, w, h);\n\t}\n\n\t// vertexEllipse!\n\t// based on\n\t// http://stackoverflow.com/questions/5886628/effecient-way-to-draw-ellipse-with-opengl-or-d3d\n\tprivate void vertexEllipse(float cx, float cy, float rx, float ry) {\n\t\tfloat theta = TWO_PI / (float) ellipseDetail;\n\t\tfloat c = cos(theta);// precalculate the sine and cosine\n\t\tfloat s = sin(theta);\n\t\tfloat t;\n\n\t\tfloat x = .5f;// we start at angle = 0\n\t\tfloat y = 0f;\n\n\t\tbeginShape();\n\t\tfor (int ii = 0; ii < ellipseDetail + 1; ii++) {\n\t\t\t// apply radius and offset\n\t\t\tvertex(x * rx + cx, y * ry + cy);// output vertex\n\t\t\t// apply the rotation matrix\n\t\t\tt = x;\n\t\t\tx = c * x - s * y;\n\t\t\ty = s * t + c * y;\n\n\t\t}\n\t\tendShape();\n\t}\n\t\n\t// non-param drawings\n\tpublic void lissajous(){\n\t\tlissajous(xyWidth/2, xyHeight/2, xyHeight/2, 1, 2, 0, 180);\n\t}\n\t\n\t/**\n\t * Draw lissajous curve, expects lissajous(xPos, yPos, radius, ratioA, ratioB, phase, resolution).\n\t * \n\t * @param xPos float - x position of lissajous\n\t * @param yPos float - y position of lissajous\n\t * @param radius float - size of lissajous\n\t * @param ratioA float - lissajous ratio part A\n\t * @param ratioB float - lissajous ratio part B\n\t * @param phase float - phase of lissajous curve\n\t * @param resolution float - number of points for lissajous curve\n\t * \n\t */\n\tpublic void lissajous(float xPos, float yPos, float radius, float ratioA, float ratioB, float phase, float resolution){\n\t\tresolution = constrain(resolution, 1f, 360f);\n\t\tbeginShape();\n\t\tfor(int i=0; i < resolution+1; i++){\n\t\t\tfloat theta = TWO_PI/resolution;\n\t\t\tfloat x = sin(i*theta*ratioA)*radius;\n\t\t\tfloat y = sin(radians(phase)+i*theta*ratioB)*radius;\n\t\t\tvertex(xPos + x, yPos + y);\n\t\t}\n\t\tendShape();\n\t}\n\t\n\tpublic void box() {\n\t\tbox(xyHeight/2, xyHeight/2, xyHeight/2);\n\t}\n\n\t/**\n\t * Draw box, expects box(size).\n\t * \n\t * @param size float - size of box\n\t * \n\t * @see <a href=\"https://processing.org/reference/box_.html\">Processing\n\t *      Reference » box()</a>\n\t */\n\n\tpublic void box(float size) {\n\t\tbox(size, size, size);\n\t}\n\n\t/**\n\t * Draw box, expects box(rx, ryz).\n\t * \n\t * @param rx float - size of box in x-axis\n\t * @param ryz float - size of box in y/z-axis\n\t * \n\t * @see <a href=\"https://processing.org/reference/box_.html\">Processing\n\t *      Reference » box()</a>\n\t */\n\n\tpublic void box(float rx, float ryz) {\n\t\tbox(rx, ryz, ryz);\n\t}\n\n\t/**\n\t * Draw box, expects box(rx, ry, rz).\n\t * \n\t * @param rx float - size of box in x-axis\n\t * @param ry float - size of box in y-axis\n\t * @param rz float - size of box in z-axis\n\t * \n\t * @see <a href=\"https://processing.org/reference/box_.html\">Processing\n\t *      Reference » box()</a>\n\t */\n\n\t// extended from: https://stackoverflow.com/a/72277489/10885535\n\tpublic void box(float rxt, float ryt, float rzt) {\n\t\t// half size: keep the pivot at the center of the mesh\n\t\tfloat rx = rxt * .5f;\n\t\tfloat ry = ryt * .5f;\n\t\tfloat rz = rzt * .5f;\n\t\tbeginShape();\n\n\t\t// back (-z)\n\t\tvertex(-rx, -ry, -rz);\n\t\tvertex(+rx, -ry, -rz);\n\t\tvertex(+rx, +ry, -rz);\n\t\tvertex(-rx, +ry, -rz);\n\t\t// slide to otherside\n\t\tvertex(-rx, -ry, -rz);\n\t\t// front (+z)\n\t\tvertex(-rx, -ry, +rz);\n\t\tvertex(+rx, -ry, +rz);\n\t\tvertex(+rx, +ry, +rz);\n\t\tvertex(-rx, +ry, +rz);\n\t\t// top (-y)\n\t\tvertex(-rx, -ry, +rz);\n\t\tvertex(-rx, -ry, -rz);\n\t\tvertex(+rx, -ry, -rz);\n\t\tvertex(+rx, -ry, +rz);\n\t\t// bottom (+y)\n\t\tvertex(+rx, +ry, +rz);\n\t\tvertex(+rx, +ry, -rz);\n\t\tvertex(-rx, +ry, -rz);\n\t\tvertex(-rx, +ry, +rz);\n\t\t// left (-x)\n\t\tvertex(-rx, -ry, +rz);\n\t\tvertex(-rx, -ry, -rz);\n\t\tvertex(-rx, +ry, -rz);\n\t\tvertex(-rx, +ry, +rz);\n\t\t// slide to otherside\n\t\tvertex(-rx, -ry, +rz);\n\t\t// right (+x)\n\t\tvertex(+rx, -ry, +rz);\n\t\tvertex(+rx, -ry, -rz);\n\t\tvertex(+rx, +ry, -rz);\n\t\tvertex(+rx, +ry, +rz);\n\t\tendShape();\n\t}\n\n\t// based on: Examples » Topics » Textures » Texture Sphere\n\tint dx = 24;\n\tint dy = 24;\n\t\n\t// non-param drawing\n\tpublic void sphere() {\n\t\tellipsoid(xyHeight/3, xyHeight/3, xyHeight/3, dx, dy);\n\t}\n\t\n\tpublic void ellipsoid() {\n\t\tellipsoid(xyHeight/3, xyHeight/3, xyHeight/3, dx, dy);\n\t}\n\t\n\t/**\n\t * Draw sphere, expects sphere(size).\n\t * \n\t * @param size float - size of sphere\n\t * \n\t * @see <a href=\"https://processing.org/reference/sphere_.html\">Processing\n\t *      Reference » sphere()</a>\n\t */\n\t\n\tpublic void sphere(float rs) {\n\t\tellipsoid(rs, rs, rs, dx, dy);\n\t}\n\n\t/**\n\t * Draw sphere, expects sphere(size, verticesCount).\n\t * \n\t * @param size float - size of sphere\n\t * @param verticesCount int - number of horzontal + vertical vertices\n\t * \n\t * @see <a href=\"https://processing.org/reference/sphere_.html\">Processing\n\t *      Reference » sphere()</a>\n\t */\n\t\n\tpublic void sphere(float rs, int dxy) {\n\t\tellipsoid(rs, rs, rs, dxy, dxy);\n\t}\n\t\n\t/**\n\t * Draw sphere, expects sphere(size, verticiesW, verticiesH).\n\t * \n\t * @param size float - size of sphere\n\t * @param verticiesW int - number of horzontal vertices\n\t * @param verticiesH int - number of vertical vertices\n\t * \n\t * @see <a href=\"https://processing.org/reference/sphere_.html\">Processing\n\t *      Reference » sphere()</a>\n\t */\n\n\tpublic void sphere(float rs, int dx, int dy) {\n\t\tellipsoid(rs, rs, rs, dx, dy);\n\t}\n\t\n\t/**\n\t * Draw ellipsoid, expects ellipsoid(rx, ry, rz).\n\t * \n\t * @param rx float - size in x-axis\n\t * @param ry float - size in y-axis\n\t * @param rz float - size in z-axis\n\t * \n\t * @see <a href=\"https://processing.org/reference/sphere_.html\">Processing\n\t *      Reference » sphere()</a>\n\t */\n\n\tpublic void ellipsoid(float rx, float ry, float rz) {\n\t\tellipsoid(rx, ry, rz, dx, dy);\n\t}\n\n\t/**\n\t * Draw ellipsoid, expects ellipsoid(rx, ry, rz, verticesCount).\n\t * \n\t * @param rx float - size in x-axis\n\t * @param ry float - size in y-axis\n\t * @param rz float - size in z-axis\n\t * @param verticesCount int - number of horzontal + vertical vertices\n\t * \n\t * @see <a href=\"https://processing.org/reference/sphere_.html\">Processing\n\t *      Reference » sphere()</a>\n\t */\n\t\n\tpublic void ellipsoid(float rx, float ry, float rz, int dxy) {\n\t\tellipsoid(rx, ry, rz, dxy, dxy);\n\t}\n\n\t/**\n\t * Draw ellipsoid, expects ellipsoid(rx, ry, rz, verticesCount).\n\t * \n\t * @param rx float - size in x-axis\n\t * @param ry float - size in y-axis\n\t * @param rz float - size in z-axis\n\t * @param verticiesW int - number of horzontal vertices\n\t * @param verticiesH int - number of vertical vertices\n\t * \n\t * @see <a href=\"https://processing.org/reference/sphere_.html\">Processing\n\t *      Reference » sphere()</a>\n\t */\n\t\n\tpublic void ellipsoid(float rxt, float ryt, float rzt, int dx, int dy) {\n\n\t\tfloat rx = rxt;// * .5f;\n\t\tfloat ry = ryt;// * .5f;\n\t\tfloat rz = rzt;// * .5f;\n\t\t\t\t\n\t\tint numPointsW;\n\t\tint numPointsH_2pi;\n\t\tint numPointsH;\n\n\t\tfloat[] coorX;\n\t\tfloat[] coorY;\n\t\tfloat[] coorZ;\n\t\tfloat[] multXZ;\n\n\t\tint numvW = constrain(dx, 1, 50);\n\t\tint numvH_2pi = constrain(dy, 1, 50);\n\n\t\t// The number of points around the width and height\n\t\tnumPointsW=numvW+1;\n\t\tnumPointsH_2pi=numvH_2pi;  // How many actual pts around the sphere (not just from top to bottom)\n\t\tnumPointsH=ceil((float)numPointsH_2pi/2f)+1;  // How many pts from top to bottom (abs(....) b/c of the possibility of an odd numPointsH_2pi)\n\n\t\tcoorX=new float[numPointsW];   // All the x-coor in a horizontal circle radius 1\n\t\tcoorY=new float[numPointsH];   // All the y-coor in a vertical circle radius 1\n\t\tcoorZ=new float[numPointsW];   // All the z-coor in a horizontal circle radius 1\n\t\tmultXZ=new float[numPointsH];  // The radius of each horizontal circle (that you will multiply with coorX and coorZ)\n\n\t\tfor (int i=0; i<numPointsW; i++) {  // For all the points around the width\n\t\t\tfloat thetaW=i*2f*PI/(numPointsW-1);\n\t\t\tcoorX[i]=sin(thetaW);\n\t\t\tcoorZ[i]=cos(thetaW);\n\t\t}\n\n\t\tfor (int i=0; i<numPointsH; i++) {  // For all points from top to bottom\n\t\t\tif (parseInt(numPointsH_2pi/2) != (float)numPointsH_2pi/2 && i==numPointsH-1) {  // If the numPointsH_2pi is odd and it is at the last pt\n\t\t\t\tfloat thetaH=(i-1f)*2f*PI/(numPointsH_2pi);\n\t\t\t\tcoorY[i]=cos(PI+thetaH);\n\t\t\t\tmultXZ[i]=0f;\n\t\t\t} else {\n\t\t\t\t//The numPointsH_2pi and 2 below allows there to be a flat bottom if the numPointsH is odd\n\t\t\t\tfloat thetaH=i*2f*PI/(numPointsH_2pi);\n\n\t\t\t\t//PI+ below makes the top always the point instead of the bottom.\n\t\t\t\tcoorY[i]=cos(PI+thetaH);\n\t\t\t\tmultXZ[i]=sin(thetaH);\n\t\t\t}\n\t\t}\n\n\t\tbeginShape();\n\t\tfor (int i=0; i<(numPointsH-1); i++) {  // For all the rings but top and bottom\n\t\t\t// Goes into the array here instead of loop to save time\n\t\t\tfloat coory=coorY[i];\n\t\t\tfloat cooryPlus=coorY[i+1];\n\n\t\t\tfloat multxz=multXZ[i];\n\t\t\tfloat multxzPlus=multXZ[i+1];\n\n\t\t\tfor (int j=0; j<numPointsW; j++) { // For all the pts in the ring\n\t\t\t\tvertex(coorX[j]*multxz*rx, coory*ry, coorZ[j]*multxz*rz);\n\t\t\t\tvertex(coorX[j]*multxzPlus*rx, cooryPlus*ry, coorZ[j]*multxzPlus*rz);\n\t\t\t}\n\t\t}\n\t\tendShape();\n\t}\n\t\n\t// non-param drawing\n\tpublic void torus() {\n\t\ttorus(xyHeight/4, xyHeight/6, dx, dy);\n\t}\n\t\n\tpublic void torus(float radius, float tubeRadius) {\n\t\ttorus(radius, tubeRadius, dx, dy);\n\t}\n\t\n\tpublic void torus(float radius, float tubeRadius, int dxy) {\n\t\ttorus(radius, tubeRadius, dxy, dxy);\n\t}\n\t\n\t/**\n\t * Draw torus, expects torus(radius, tubeRadius, detailX, detailY).\n\t * \n\t * @param radius float - radius of torus\n\t * @param tubeRadius float - radius of torus tube\n\t * @param detailX int - number of horzontal vertices\n\t * @param detailY int - number of vertical vertices\n\t * \n\t */\n\t\n\t// built upon: https://processing.org/examples/toroid.html\n\tpublic void torus(float radius, float tubeRadius, int dx, int dy) {\n\t  dx = constrain(dx, 1, 50);\n\t  dy = constrain(dy, 1, 50);\n\t  //tubeRadius = constrain(tubeRadius, 1, radius);\n\n\t  float angle = 0f;\n\t  float latheAngle = 0f;\n\t  PVector vertices[] = new PVector[dx+1];\n\t  PVector vertices2[] = new PVector[dx+1];\n\n\t  // fill arrays\n\t  for (int i=0; i<=dx; i++) {\n\t    vertices[i] = new PVector();\n\t    vertices2[i] = new PVector();\n\t    vertices[i].x = radius + sin(radians(angle))*tubeRadius;\n\t    vertices[i].z = cos(radians(angle))*tubeRadius;\n\t    angle+=360.0f/dx;\n\t  }\n\n\t  // draw toroid\n\t  latheAngle = 0f;\n\t  for (int i=0; i<=dy; i++) {\n\t    beginShape();\n\n\t    for (int j=0; j<=dx; j++) {\n\t      if (i > 0) {\n\t        vertex(vertices2[j].x, vertices2[j].y, vertices2[j].z);\n\t      }\n\t      vertices2[j].x = cos(radians(latheAngle))*vertices[j].x;\n\t      vertices2[j].y = sin(radians(latheAngle))*vertices[j].x;\n\t      vertices2[j].z = vertices[j].z;\n\t      vertex(vertices2[j].x, vertices2[j].y, vertices2[j].z);\n\t    }\n\t    latheAngle+=360.0f/dy;\n\t    endShape();\n\t  }\n\t}\n\t\n\t/**\n\t * Begin multi-vertex shape.\n\t * \n\t * @see <a href=\n\t *      \"https://processing.org/reference/beginShape_.html\">Processing\n\t *      Reference » beginShape()</a>\n\t */\n\tpublic void beginShape() {\n\t\tcurrentShape = new XYShape();\n\t\tshapes.add(currentShape);\n\t}\n\t\n\tpublic void vertex() {\n\t\tvertex(new PVector(myParent.random(xyWidth), myParent.random(xyHeight), 0), false);\n\t}\n\t\n\tpublic void curveVertex() {\n\t\tvertex(new PVector(myParent.random(xyWidth), myParent.random(xyHeight), 0), false);\n\t}\n\n\t/**\n\t * Currently sent as normal vertex (to be fixed). Simply here for code »\n\t * vectorcode compatibility.\n\t * \n\t * @param x float - x position of vertex point\n\t * @param y float - y position of vertex point\n\t * \n\t * @see <a href=\n\t *      \"https://processing.org/reference/curveVertex_.html\">Processing\n\t *      Reference » curveVertex()</a>\n\t */\n\tpublic void curveVertex(float x, float y) {\n\t\tvertex(new PVector(x, y, 0), false);\n\t}\n\n\t/**\n\t * Currently sent as normal vertex (to be fixed). Simply here for code »\n\t * vectorcode compatibility.\n\t * \n\t * @param x float - x position of vertex point\n\t * @param y float - y position of vertex point\n\t * @param z float - z position of vertex point\n\t * \n\t * @see <a href=\n\t *      \"https://processing.org/reference/curveVertex_.html\">Processing\n\t *      Reference » curveVertex()</a>\n\t */\n\tpublic void curveVertex(float x, float y, float z) {\n\t\tvertex(new PVector(x, y, z), true);\n\t}\n\n\t/**\n\t * Add vertex to complex shape. Expects vertex(x, y).\n\t * \n\t * @param x float - x position of vertex point\n\t * @param y float - y position of vertex point\n\t * \n\t * @see <a href=\"https://processing.org/reference/vertex_.html\">Processing\n\t *      Reference » vertex()</a>\n\t */\n\tpublic void vertex(float x, float y) {\n\t\tvertex(new PVector(x, y, 0), false);\n\t}\n\n\t/**\n\t * Add vertex to complex shape. Expects vertex(x, y, z).\n\t * \n\t * @param x float - x position of vertex point\n\t * @param y float - y position of vertex point\n\t * @param z float - z position of vertex point\n\t * \n\t * @see <a href=\"https://processing.org/reference/vertex_.html\">Processing\n\t *      Reference » vertex()</a>\n\t */\n\tpublic void vertex(float x, float y, float z) {\n\t\tvertex(new PVector(x, y, z), true);\n\t}\n\t\n\t/**\n\t * Add vertex to complex shape. Expects vertex(PVector()).\n\t * \n\t * @param p PVector – pass xy[z] position as PVector.\n\t * \n\t * @see <a href=\"https://processing.org/reference/vertex_.html\">Processing\n\t *      Reference » vertex()</a>\n\t */\n\tpublic void vertex(PVector p) {\n\t\tif(p.z == 0f) {\n\t\t\tvertexAdd(p, false);\n\t\t}else {\n\t\t\tvertexAdd(p, true);\n\t\t}\n\t}\n\n\tprivate void vertex(PVector p, boolean mode3D) {\n\t\tvertexAdd(p, mode3D);\n\t}\n\n\tprivate void vertexAdd(PVector p, boolean mode3D) {\n\t\tfloat x, y;\n\t\tif(mode3D){\n\t\t\tx = norm(myParent.screenX(p.x, p.y, p.z), 0f, xyWidth + 0f);\n\t\t\ty = norm(myParent.screenY(p.x, p.y, p.z), 0f, xyHeight + 0f);\n\t\t}else{\n\t\t\tx = norm(myParent.screenX(p.x, p.y), 0f, xyWidth + 0f);\n\t\t\ty = norm(myParent.screenY(p.x, p.y), 0f, xyHeight + 0f);\n\n\t\t}\n\t\tPVector normP = new PVector(x, y, 0);\n\t\tif(useLimitPath){\n\t\t\tfloat sx, sy;\n\t\t\tif(mode3D){\n\t\t\t\tsx = myParent.screenX(p.x, p.y, p.z);\n\t\t\t\tsy = myParent.screenY(p.x, p.y, p.z);\n\t\t\t}else{\n\t\t\t\tsx = myParent.screenX(p.x, p.y);\n\t\t\t\tsy = myParent.screenY(p.x, p.y);\n\t\t\t}\n\t\t\tif ((sx >= limitVal && sx <= xyWidth - limitVal) && (sy >= limitVal && sy <= xyHeight - limitVal)) {\n\t\t\t\tcurrentShape.add(normP);\n\t\t\t}else {\n\t\t\t\tendShape();\n\t\t\t\tbeginShape();\n//\t\t\t\tinitShape = true;\n\t\t\t}\n\t\t}else{\n\t\t\tcurrentShape.add(normP);\n\t\t}\n\t}\n\t\n\t/**\n\t * End complex shape.\n\t * \n\t * @see <a href=\"https://processing.org/reference/endShape_.html\">Processing\n\t *      Reference » endShape()</a>\n\t */\n\tpublic void endShape(int close) {\n\t\tif(close == 2) {\n\t\t\tif(currentShape.size() > 0) {\n\t\t\t\tPVector lastC = currentShape.get(0);\n\t\t\t\tcurrentShape.add(new PVector(lastC.x, lastC.y, 0));\n\t\t\t}\n\t\t}\n\t\tendShape();\n//\t\t// not necessary in current setup. maybe useful later for z-axis\n//\t\tif(currentShape.size() > 1) {\n//\t\t\tcurrentShape.get(currentShape.size()-1).z = 1f;\n//\t\t}else {\n//\t\t\t// Calculate index of last element\n//\t        int index = shapes.size() - 1;\n//\t  \n//\t        // Delete last element by passing index\n//\t        shapes.remove(index);\n//\t\t}\n\t}\n\n\t/**\n\t * End complex shape.\n\t * \n\t * @see <a href=\"https://processing.org/reference/endShape_.html\">Processing\n\t *      Reference » endShape()</a>\n\t */\n\tpublic void endShape() {\n\t\t// not necessary in current setup. maybe useful later for z-axis\n\t\tif(currentShape.size() > 1) {\n\t\t\tcurrentShape.get(currentShape.size()-1).z = 1f;\n\t\t}else {\n\t\t\t// Calculate index of last element\n\t        int index = shapes.size() - 1;\n\t  \n\t        // Delete last element by passing index\n\t        shapes.remove(index);\n\t\t}\n\t}\n\t\n\t/*\n\t * RECORDER OUT FUNCTIONS */\n\t\n\tpublic void recorderBegin() {\n\t\trecorderBegin(\"XYscope\");\n\t}\n\n\tpublic void recorderBegin(String filename) {\n\t\tString date = new java.text.SimpleDateFormat(\"yyyy_MM_dd_kkmmssSSS\").format(new java.util.Date()); \n\t  \trecorder = minim.createRecorder(outXY, filename + \"_\" + date + \".wav\");\n\t\trecorder.beginRecord();\n\t\tprintln(\"XYscope - beginRecord\");\n\t}\n\n\tpublic void recorderEnd() {\n\t\trecorder.endRecord();\n\t\trecorder.save();\n\t\tprintln(\"XYscope - endRecord + saved!\");\n\t}\n\n\t\n\t\n\t/* \n\t * HERSHEY Font meets Processing text() functions \n\t * HUGE THX to this lib: https://github.com/ixd-hof/HersheyFont\n\t * \n\t * */\n\t\n\tprivate boolean contains(String[] arr, String val) {\n\t\tfor(int i=0; i<arr.length; i++) {\n\t\t\tif(arr[i].equals(val)) { \n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\t\n\t/**\n\t * Set Hershey Font for built-in text rendering. Use println(xy.fonts) for complete list available.\n\t * \n\t * @param fontName\n\t *            String of Hershey Font name\n\t */\n\tpublic void textFont(String fontName) {\n\t\tif(contains(fonts, fontName)) {\n\t\t\tString [] hershey_font_org;\n\t\t\thershey_font_org = myParent.loadStrings(\"hershey_fonts/\" + fontName + \".jhf\");\n\n\t\t\tString hershey_font_string = \"\";\n\n\t\t\tfor (int i=0; i<hershey_font_org.length; i++) {\n\t\t\t\tString line = hershey_font_org[i].trim();\n\t\t\t\tif (line.charAt(0) >= 48 && line.charAt(0) <= 57)\n\t\t\t\t\thershey_font_string += line + \"\\n\";\n\t\t\t\telse {\n\t\t\t\t\thershey_font_string = hershey_font_string.substring(0, hershey_font_string.length()-1) + line + \"\\n\";\n\t\t\t\t}\n\t\t\t}\n\t\t\thershey_font = hershey_font_string.split(\"\\n\");\n\t\t}\n\t}\n\t\n\t/**\n\t * Get size for built-in Hershey text rendering.\n\t * \n\t * @return Float - size of text\n\t */\n\tpublic float textSize(){\n\t\treturn hfactor;\n\t}\n\t\n\t/**\n\t * Set size for built-in Hershey text rendering.\n\t * \n\t * @param fontSize\n\t *            Float - size of text\n\t */\n\tpublic void textSize(float fontSize){\n\t\thfactor = fontSize/hheight;\n\t}\n\t\n\t/**\n\t * Get the spacing between lines of text in units of pixels.\n\t * \n\t * @return Float - the size in pixels for spacing between lines\n\t */\n\tpublic float textLeading(){\n\t\treturn hleading;\n\t}\n\t\n\t/**\n\t * Sets the spacing between lines of text in units of pixels.\n\t * \n\t * @param fontLeading\n\t *            Float - \tthe size in pixels for spacing between lines\n\t */\n\tpublic void textLeading(float leading){\n\t\thleading = leading;\n\t}\n\t\t\n\t/**\n\t * Set horizontal alignment of built in Hershey text rending. LEFT (default), CENTER or RIGHT.\n\t * \n\t * @param taX\n\t *            LEFT, CENTER, RIGHT\n\t */\n\tpublic void textAlign(int taX) {\n\t\ttextAlign(taX, textAlignY);\n\t}\n\t\n\t/**\n\t * Set horizontal and vertical alignment of built in Hershey text rending. Horizontal: LEFT (default), CENTER or RIGHT. Vertical: TOP, CENTER (default), BOTTOM.\n\t * \n\t * @param taX\n\t *            LEFT, CENTER, RIGHT\n\t * @param taY\n\t *            TOP, CENTER, BOTTOM\n\t */\n\tpublic void textAlign(int taX, int taY) {\n\t\tif(taX == 37 || taX == 3 || taX == 39) {\n\t\t\ttextAlignX = taX;\n\t\t}\n\t\tif(taY == 101 || taY == 3 || taY == 102) {\n\t\t\ttextAlignY = taY;\n\t\t}\n\t}\n\t\n\t// non-param drawing\n\tpublic void text() {\n\t\ttext(\"XYscope\", xyWidth/2, xyHeight/2);\n\t}\n\t\n\tpublic void text(float s, float x, float y) {\n\t\ttext(nf(s), x, y);\n\t}\n\tpublic void text(int s, float x, float y) {\n\t\ttext(nf(s), x, y);\n\t}\n\t\n\t\n\t/**\n\t * Render text using built in Hershey Fonts. \n\t * \n\t * @param s\n\t *            String - text to display\n\t * @param x\n\t *            float - horizontal position of text\n\t * @param y\n\t *            float - vertical position of text\n\t */\n\tpublic void text(String s, float x, float y) {\n\t\tString[] parts = splitTokens(s, \"\\n\\r\");\n\t\t\n\t\tswitch(textAlignY) {\n\t\tcase 101:\n\t\t\ty += hfactor * 12;\n\t\t\tbreak;\n\t\t\n\t\tcase 102:\n\t\t\ty -= hfactor * 21 * parts.length;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tif(parts.length > 1) {\n\t\t\t\ty -= hfactor * 21 * parts.length/2;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\t\n\t\tfloat yOffset = y;\n\t\tfor(int i=0; i<parts.length; i++) {\n\t\t\ttextParse(parts[i], x, yOffset);\n\t\t\tyOffset += hfactor * hheight + hleading;\n\t\t}\n\t}\n\t\n\tpublic void textParse(String s, float x, float y) {\n//\t\tPVector ta = textOffset(s, x, y);\n\t\t\n\t\tx += 5 * hfactor;\n\t\t\n\t\tswitch(textAlignX) {\n\t\tcase 3:\n\t\t\tx -= textWidth(s) / 2;\n\t\t\tbreak;\n\t\tcase 39:\n\t\t\tx -= textWidth(s);\n\t\t\tbreak;\n\t\t}\n\t\t\n\t\tswitch(textAlignY) {\n\t\tcase 101:\n\t\t\ty += hfactor * 12;\n\t\t\tbreak;\n\t\t\n\t\tcase 102:\n\t\t\ty -= hfactor * 12;\n\t\t\tbreak;\n\t\t}\n\t\t\n\t\tmyParent.pushMatrix();\t\t\n\t\tmyParent.translate(x, y);\n\t\t\n\t\tfor (int i=0; i<s.length(); i++){\n\t\t\tdraw_character(s.charAt(i)); // custom drawChar for XYscope\n\t\t}\n\t\tmyParent.popMatrix();\n\t}\n\t\n\t/**\n\t * Calculate width of provided character of text, for built-in Hershey Font . \n\t * \n\t * @param s\n\t *            char - character to measure width of\n\t *            \n\t * @return float\n\t */\t\n\tpublic float textWidth(int c) {\n\t\tString h = hershey_font[c - 32 ];\n\n\t\tint start_col = h.indexOf(\" \");\n\n\t\tint h_left = hershey2coord(h.charAt(start_col+3));\n\t\tint h_right = hershey2coord(h.charAt(start_col+4));\n\t\tfloat h_width = h_right - h_left * hfactor;\n\n\t\treturn h_width + 5 * hfactor;\n\t}\n\t\n\t/**\n\t * Calculate width of provided string of text, for built-in Hershey Font . \n\t * \n\t * @param s\n\t *            String - text to measure width of\n\t *            \n\t * @return float\n\t */\t\n\tpublic float textWidth(String s) {\n\t\tString[] parts = splitTokens(s, \"\\n\\r\");\n\t\t\n\t\tfloat offxMax = 0;\n\t\tfor(int i=0; i<parts.length; i++) {\n\t\t\tfloat offxTemp = textWidthParse(parts[i]);\n\t\t\tif(offxTemp > offxMax) {\n\t\t\t\toffxMax = offxTemp;\n\t\t\t}\n\t\t}\n\t\treturn offxMax;\n\t}\n\t\n\tfloat textWidthParse(String s) {\n\t\tfloat offx = 0;\n\t\tfor (int k=0; k<s.length (); k++){\n\t\t\tint c = s.charAt(k);\n\t\t\tString h = hershey_font[c - 32 ];\n\n\t\t\tint start_col = h.indexOf(\" \");\n\n\t\t\tint h_left = hershey2coord(h.charAt(start_col+3));\n\t\t\tint h_right = hershey2coord(h.charAt(start_col+4));\n\t\t\tfloat h_width = h_right - h_left * hfactor;\n\t\t\toffx += h_width + 5 * hfactor;\n\t\t}\n\t\treturn offx;\n\t}\n\t\n\t/**\n\t * Process and return 2D-array (points in paths) of PVector's from provided text, x, y coordinates using built in Hershey Fonts. \n\t * \n\t * @param s\n\t *            String - text to display\n\t * @param x\n\t *            float - horizontal position of text\n\t * @param y\n\t *            float - vertical position of text\n\t *            \n\t * @return 2D-Array of <PVector>\n\t */\n\tpublic PVector[][] textPaths(String s, float x, float y) {\n\t\tString[] parts = splitTokens(s, \"\\n\\r\");\n\t\t\n\t\tswitch(textAlignY) {\n\t\tcase 101:\n\t\t\ty += hfactor * 12;\n\t\t\tbreak;\n\t\t\n\t\tcase 102:\n\t\t\ty -= hfactor * 21 * parts.length;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\ty -= hfactor * 21 * parts.length/2;\n\t\t\tbreak;\n\t\t}\n\t\t\n\t\tArrayList<ArrayList<PVector>> cooords = new ArrayList(s.length());\n\t\tfloat yOffset = y;\n\t\tfor(int i=0; i<parts.length; i++) {\n\t\t\ttextPathsParse(parts[i], x, yOffset, cooords);\n\t\t\tyOffset += hfactor * hheight + hleading;\n\t\t}\n\t\t\n\t\tPVector[][] coordsArray = new PVector[cooords.size()][];\n\t\tfor(int i=0; i < cooords.size(); i++) {\n\t\t\tcoordsArray[i] = new PVector[cooords.get(i).size()];\n\t\t\tfor(int j=0; j < cooords.get(i).size(); j++) {\n\t\t\t\tcoordsArray[i][j] = cooords.get(i).get(j);\n\t\t\t}\n\t\t}\n\n\t\treturn coordsArray;\t\n\t}\n\t\n\tvoid textPathsParse(String s, float x, float y, ArrayList<ArrayList<PVector>> coords) {\n\t\t\n\t\tx += 5 * hfactor;\n\t\t\n\t\tswitch(textAlignX) {\n\t\tcase 3:\n\t\t\tx -= textWidth(s) / 2;\n\t\t\tbreak;\n\t\tcase 39:\n\t\t\tx -= textWidth(s);\n\t\t\tbreak;\n\t\t}\n\t\t\n\t\tfloat offx = x;\n\t\tfloat offy = y;\n\t\tfor (int k=0; k<s.length (); k++){\n\t\t\tint c = s.charAt(k);\n\t\t\tString h = hershey_font[c - 32 ];\n\n\t\t\tint start_col = h.indexOf(\" \");\n\n\t\t\tint h_left = hershey2coord(h.charAt(start_col+3));\n\t\t\tint h_right = hershey2coord(h.charAt(start_col+4));\n\t\t\tfloat h_width = h_right - h_left * hfactor;\n\n\t\t\tString[] h_vertices = h.substring(start_col+5, h.length()).replaceAll(\" R\", \" \").split(\" \");\n\n\t\t\tfor (int i=0; i<h_vertices.length; i++) {\n\t\t\t\tArrayList<PVector> coord = new ArrayList(h_vertices[i].length());\n\t\t\t\tfor (int j=2; j<h_vertices[i].length (); j+=2) {\n\t\t\t\t\tfloat hx0 = hershey2coord(h_vertices[i].charAt(j-2)) * hfactor;\n\t\t\t\t\tfloat hy0 = hershey2coord(h_vertices[i].charAt(j-1)) * hfactor;\n\t\t\t\t\tcoord.add(new PVector(offx + hx0, offy+hy0));\n\t\t\t\t\tfloat hx1 = hershey2coord(h_vertices[i].charAt(j)) * hfactor;\n\t\t\t\t\tfloat hy1 = hershey2coord(h_vertices[i].charAt(j+1)) * hfactor;\n\t\t\t\t\tcoord.add(new PVector(offx+hx1, offy+hy1));\n\t\t\t\t}\n\t\t\t\tcoords.add(coord);\n\t\t\t}\n\t\t\toffx += h_width + 5 * hfactor;\n\t\t}\n\t}\n\n\tprivate void draw_character(int c) {\n\t\tString h = hershey_font[c - 32 ];\n\n\t\tint start_col = h.indexOf(\" \");\n\n\t\tint h_left = hershey2coord(h.charAt(start_col+3));\n\t\tint h_right = hershey2coord(h.charAt(start_col+4));\n\t\tfloat h_width = h_right - h_left * hfactor;\n\t\tString[] h_vertices = h.substring(start_col+5, h.length()).replaceAll(\" R\", \" \").split(\" \");\n\t\tfor (int i=0; i<h_vertices.length; i++) {\n\t\t\tbeginShape();\n\t\t\tfor (int j=2; j<h_vertices[i].length (); j+=2) {\n\t\t\t\tfloat hx0 = hershey2coord(h_vertices[i].charAt(j-2)) * hfactor;\n\t\t\t\tfloat hy0 = hershey2coord(h_vertices[i].charAt(j-1)) * hfactor;\n\t\t\t\tvertex(hx0, hy0);\n\t\t\t\tfloat hx1 = hershey2coord(h_vertices[i].charAt(j)) * hfactor;\n\t\t\t\tfloat hy1 = hershey2coord(h_vertices[i].charAt(j+1)) * hfactor;\n\t\t\t\tvertex(hx1, hy1);\n\t\t\t}\n\t\t\tendShape();\n\t\t}\n\t\tmyParent.translate(h_width + 5 * hfactor, 0);\n\t}\n\n\tprivate int hershey2coord(char c) {\n\t\treturn c - 'R';\n\t}\n\n\tprivate XYShape currentShape = null;\n\n\tpublic class XYShapeList extends ArrayList<XYShape> {\n\t\t\n\t\t/**\n\t\t * Returns float of total distance of drawn shapes, XYShapeList.\n\t\t * \n\t\t * @return float\n\t\t * \n\t\t */\n\t\tpublic double getDistance() {\n\t\t\tdouble sum = 0.0f;\n\t\t\tfor (XYShape shape : this) {\n\t\t\t\tsum += shape.getDistance();\n\t\t\t}\n\t\t\treturn sum;\n\t\t}\n\n\t\t/**\n\t\t * Returns int of total number of points in drawn shapes, XYShapeList.\n\t\t * \n\t\t * @return float\n\t\t * \n\t\t */\n\t\tpublic int totalSize() {\n\t\t\tint tsCounter = 0;\n\t\t\tfor (XYShape shape : this) {\n\t\t\t\tfor (int i = 0; i < shape.size(); i++) {\n\t\t\t\t\ttsCounter++;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn tsCounter;\n\t\t}\n\n\t\t/**\n\t\t * Returns ArrayList<PVector> of coordinates for all drawn shapes, XYShapeList.\n\t\t * \n\t\t * @return float\n\t\t * \n\t\t */\n\t\tpublic ArrayList<PVector> getPoints() {\n\t\t\tArrayList<PVector> gp = new ArrayList<PVector>();\n\t\t\tfor (XYShape shape : this) {\n\t\t\t\tfor (int i = 0; i < shape.size(); i++) {\n\t\t\t\t\tgp.add(shape.get(i));\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn gp;\n\t\t}\n\t}\n\n\tpublic class XYShape extends ArrayList<PVector> {\n\n\t\tpublic double getDistance() {\n\t\t\tdouble sum = 0.0f;\n\t\t\tfor (int i = 0; i < size() - 1; i++) {\n\t\t\t\tsum += dist(get(i).x, get(i).y, get(i+1).x, get(i+1).y);//get(i).dist(get(i + 1));\n\t\t\t}\n\t\t\treturn sum;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "web/includes/css/styles.css",
    "content": "@font-face {\n  font-family: 'roboto_mono';\n  src: url('../fonts/robotomono-regular-webfont.woff2') format('woff2'),\n  url('../fonts/robotomono-regular-webfont.woff') format('woff');\n  font-weight: 400;\n  font-style: normal;\n}\n@font-face {\n  font-family: 'roboto_mono';\n  src: url('../fonts/robotomono-light-webfont.woff2') format('woff2'),\n  url('../fonts/robotomono-light-webfont.woff') format('woff');\n  font-weight: 200;\n  font-style: normal;\n}\n\nbody {\n  max-width: 760px;\n  margin: 0 auto;\n  padding: 25px;\n  color: #333333;\n  font-family: \"roboto_mono\", sans-serif;\n  font-size: 11pt;\n  line-height: 1.5em;\n  font-weight:200;\n}\n\na{\n  color:#000;\n}\na:visited{\n  color:#666;\n}\n\nli{\n  line-height:2em;\n}\n\nh1, h2{\n    padding:8px 0 8px 0;\n}\n\nh1 {\n  line-height: 1;\n  font-weight:400;\n  border-bottom: solid 2px #000;\n  border-top: solid 2px #000;\n}\n\nh2 {\n  font-size:22pt;\n  border-top: solid 1px #000;\n  border-bottom: solid 1px #000;\n  margin-top:50px;\n}\n\nh3 {\n  font-size:18pt;\n  margin-top:35px;\n  margin-bottom:0;\n  text-decoration: underline;\n}\n\nh4 {\n  font-size:14pt;\n  margin-top:20px;\n  margin-bottom:0;\n}\n\nhr{\n  border:none;\n  border-bottom:1px dotted #000;\n  margin-top:20px;\n}\n\npre > code{\n  border-radius: 5px;\n  border:1px solid #ddd !important;\n  background: #fafafa !important;\n  display: inline-block;\n  padding: 0 8px;\n  font-family: \"roboto_mono\";\n  font-weight: 400;\n}\n\nblockquote p{\n  background:#fafafa;\n  border-left:3px solid #aaa;\n  padding:5px;\n  font-weight:400;\n}\n"
  },
  {
    "path": "web/includes/js/highlight/a11y-dark.css",
    "content": "/* a11y-dark theme */\n/* Based on the Tomorrow Night Eighties theme: https://github.com/isagalaev/highlight.js/blob/master/src/styles/tomorrow-night-eighties.css */\n/* @author: ericwbailey */\n\n/* Comment */\n.hljs-comment,\n.hljs-quote {\n  color: #d4d0ab;\n}\n\n/* Red */\n.hljs-variable,\n.hljs-template-variable,\n.hljs-tag,\n.hljs-name,\n.hljs-selector-id,\n.hljs-selector-class,\n.hljs-regexp,\n.hljs-deletion {\n  color: #ffa07a;\n}\n\n/* Orange */\n.hljs-number,\n.hljs-built_in,\n.hljs-builtin-name,\n.hljs-literal,\n.hljs-type,\n.hljs-params,\n.hljs-meta,\n.hljs-link {\n  color: #f5ab35;\n}\n\n/* Yellow */\n.hljs-attribute {\n  color: #ffd700;\n}\n\n/* Green */\n.hljs-string,\n.hljs-symbol,\n.hljs-bullet,\n.hljs-addition {\n  color: #abe338;\n}\n\n/* Blue */\n.hljs-title,\n.hljs-section {\n  color: #00e0e0;\n}\n\n/* Purple */\n.hljs-keyword,\n.hljs-selector-tag {\n  color: #dcc6e0;\n}\n\n.hljs {\n  display: block;\n  overflow-x: auto;\n  background: #2b2b2b;\n  color: #f8f8f2;\n  padding: 0.5em;\n}\n\n.hljs-emphasis {\n  font-style: italic;\n}\n\n.hljs-strong {\n  font-weight: bold;\n}\n\n@media screen and (-ms-high-contrast: active) {\n  .hljs-addition,\n  .hljs-attribute,\n  .hljs-built_in,\n  .hljs-builtin-name,\n  .hljs-bullet,\n  .hljs-comment,\n  .hljs-link,\n  .hljs-literal,\n  .hljs-meta,\n  .hljs-number,\n  .hljs-params,\n  .hljs-string,\n  .hljs-symbol,\n  .hljs-type,\n  .hljs-quote {\n        color: highlight;\n    }\n\n    .hljs-keyword,\n    .hljs-selector-tag {\n        font-weight: bold;\n    }\n}\n"
  },
  {
    "path": "web/includes/js/highlight/a11y-light.css",
    "content": "/* a11y-light theme */\n/* Based on the Tomorrow Night Eighties theme: https://github.com/isagalaev/highlight.js/blob/master/src/styles/tomorrow-night-eighties.css */\n/* @author: ericwbailey */\n\n/* Comment */\n.hljs-comment,\n.hljs-quote {\n  color: #696969;\n}\n\n/* Red */\n.hljs-variable,\n.hljs-template-variable,\n.hljs-tag,\n.hljs-name,\n.hljs-selector-id,\n.hljs-selector-class,\n.hljs-regexp,\n.hljs-deletion {\n  color: #d91e18;\n}\n\n/* Orange */\n.hljs-number,\n.hljs-built_in,\n.hljs-builtin-name,\n.hljs-literal,\n.hljs-type,\n.hljs-params,\n.hljs-meta,\n.hljs-link {\n  color: #aa5d00;\n}\n\n/* Yellow */\n.hljs-attribute {\n  color: #aa5d00;\n}\n\n/* Green */\n.hljs-string,\n.hljs-symbol,\n.hljs-bullet,\n.hljs-addition {\n  color: #008000;\n}\n\n/* Blue */\n.hljs-title,\n.hljs-section {\n  color: #007faa;\n}\n\n/* Purple */\n.hljs-keyword,\n.hljs-selector-tag {\n  color: #7928a1;\n}\n\n.hljs {\n  display: block;\n  overflow-x: auto;\n  background: #fefefe;\n  color: #545454;\n  padding: 0.5em;\n}\n\n.hljs-emphasis {\n  font-style: italic;\n}\n\n.hljs-strong {\n  font-weight: bold;\n}\n\n@media screen and (-ms-high-contrast: active) {\n  .hljs-addition,\n  .hljs-attribute,\n  .hljs-built_in,\n  .hljs-builtin-name,\n  .hljs-bullet,\n  .hljs-comment,\n  .hljs-link,\n  .hljs-literal,\n  .hljs-meta,\n  .hljs-number,\n  .hljs-params,\n  .hljs-string,\n  .hljs-symbol,\n  .hljs-type,\n  .hljs-quote {\n        color: highlight;\n    }\n\n    .hljs-keyword,\n    .hljs-selector-tag {\n        font-weight: bold;\n    }\n}\n"
  },
  {
    "path": "web/includes/js/highlight/docco.css",
    "content": "/*\nDocco style used in http://jashkenas.github.com/docco/ converted by Simon Madine (@thingsinjars)\n*/\n\n.hljs {\n  display: block;\n  overflow-x: auto;\n  padding: 0.5em;\n  color: #000;\n  background: #f8f8ff;\n}\n\n.hljs-comment,\n.hljs-quote {\n  color: #408080;\n  font-style: italic;\n}\n\n.hljs-keyword,\n.hljs-selector-tag,\n.hljs-literal,\n.hljs-subst {\n  color: #954121;\n}\n\n.hljs-number {\n  color: #40a070;\n}\n\n.hljs-string,\n.hljs-doctag {\n  color: #219161;\n}\n\n.hljs-selector-id,\n.hljs-selector-class,\n.hljs-section,\n.hljs-type {\n  color: #19469d;\n}\n\n.hljs-params {\n  color: #00f;\n}\n\n.hljs-title {\n  color: #458;\n  font-weight: bold;\n}\n\n.hljs-tag,\n.hljs-name,\n.hljs-attribute {\n  color: #000080;\n  font-weight: normal;\n}\n\n.hljs-variable,\n.hljs-template-variable {\n  color: #008080;\n}\n\n.hljs-regexp,\n.hljs-link {\n  color: #b68;\n}\n\n.hljs-symbol,\n.hljs-bullet {\n  color: #990073;\n}\n\n.hljs-built_in,\n.hljs-builtin-name {\n  color: #0086b3;\n}\n\n.hljs-meta {\n  color: #999;\n  font-weight: bold;\n}\n\n.hljs-deletion {\n  background: #fdd;\n}\n\n.hljs-addition {\n  background: #dfd;\n}\n\n.hljs-emphasis {\n  font-style: italic;\n}\n\n.hljs-strong {\n  font-weight: bold;\n}\n"
  },
  {
    "path": "web/includes/js/highlight/github.css",
    "content": "/*\n\ngithub.com style (c) Vasily Polovnyov <vast@whiteants.net>\n\n*/\n\n.hljs {\n  display: block;\n  overflow-x: auto;\n  padding: 0.5em;\n  color: #333;\n  background: #f8f8f8;\n}\n\n.hljs-comment,\n.hljs-quote {\n  color: #998;\n  font-style: italic;\n}\n\n.hljs-keyword,\n.hljs-selector-tag,\n.hljs-subst {\n  color: #333;\n  font-weight: bold;\n}\n\n.hljs-number,\n.hljs-literal,\n.hljs-variable,\n.hljs-template-variable,\n.hljs-tag .hljs-attr {\n  color: #008080;\n}\n\n.hljs-string,\n.hljs-doctag {\n  color: #d14;\n}\n\n.hljs-title,\n.hljs-section,\n.hljs-selector-id {\n  color: #900;\n  font-weight: bold;\n}\n\n.hljs-subst {\n  font-weight: normal;\n}\n\n.hljs-type,\n.hljs-class .hljs-title {\n  color: #458;\n  font-weight: bold;\n}\n\n.hljs-tag,\n.hljs-name,\n.hljs-attribute {\n  color: #000080;\n  font-weight: normal;\n}\n\n.hljs-regexp,\n.hljs-link {\n  color: #009926;\n}\n\n.hljs-symbol,\n.hljs-bullet {\n  color: #990073;\n}\n\n.hljs-built_in,\n.hljs-builtin-name {\n  color: #0086b3;\n}\n\n.hljs-meta {\n  color: #999;\n  font-weight: bold;\n}\n\n.hljs-deletion {\n  background: #fdd;\n}\n\n.hljs-addition {\n  background: #dfd;\n}\n\n.hljs-emphasis {\n  font-style: italic;\n}\n\n.hljs-strong {\n  font-weight: bold;\n}\n"
  },
  {
    "path": "web/includes/js/highlight/highlight.pack.js",
    "content": "/*\n  Highlight.js 10.0.1 (33af2ea5)\n  License: BSD-3-Clause\n  Copyright (c) 2006-2020, Ivan Sagalaev\n*/\n!function(e,n){\"object\"==typeof exports&&\"undefined\"!=typeof module?module.exports=n():\"function\"==typeof define&&define.amd?define(n):(e=e||self).hljs=n()}(this,(function(){\"use strict\";function e(n){Object.freeze(n);var t=\"function\"==typeof n;return Object.getOwnPropertyNames(n).forEach((function(r){!n.hasOwnProperty(r)||null===n[r]||\"object\"!=typeof n[r]&&\"function\"!=typeof n[r]||t&&(\"caller\"===r||\"callee\"===r||\"arguments\"===r)||Object.isFrozen(n[r])||e(n[r])})),n}function n(e){return e.replace(/&/g,\"&amp;\").replace(/</g,\"&lt;\").replace(/>/g,\"&gt;\")}function t(e){var n,t={},r=Array.prototype.slice.call(arguments,1);for(n in e)t[n]=e[n];return r.forEach((function(e){for(n in e)t[n]=e[n]})),t}function r(e){return e.nodeName.toLowerCase()}var a=Object.freeze({__proto__:null,escapeHTML:n,inherit:t,nodeStream:function(e){var n=[];return function e(t,a){for(var i=t.firstChild;i;i=i.nextSibling)3===i.nodeType?a+=i.nodeValue.length:1===i.nodeType&&(n.push({event:\"start\",offset:a,node:i}),a=e(i,a),r(i).match(/br|hr|img|input/)||n.push({event:\"stop\",offset:a,node:i}));return a}(e,0),n},mergeStreams:function(e,t,a){var i=0,s=\"\",o=[];function l(){return e.length&&t.length?e[0].offset!==t[0].offset?e[0].offset<t[0].offset?e:t:\"start\"===t[0].event?e:t:e.length?e:t}function c(e){s+=\"<\"+r(e)+[].map.call(e.attributes,(function(e){return\" \"+e.nodeName+'=\"'+n(e.value).replace(/\"/g,\"&quot;\")+'\"'})).join(\"\")+\">\"}function u(e){s+=\"</\"+r(e)+\">\"}function d(e){(\"start\"===e.event?c:u)(e.node)}for(;e.length||t.length;){var g=l();if(s+=n(a.substring(i,g[0].offset)),i=g[0].offset,g===e){o.reverse().forEach(u);do{d(g.splice(0,1)[0]),g=l()}while(g===e&&g.length&&g[0].offset===i);o.reverse().forEach(c)}else\"start\"===g[0].event?o.push(g[0].node):o.pop(),d(g.splice(0,1)[0])}return s+n(a.substr(i))}});const i=\"</span>\",s=e=>!!e.kind;class o{constructor(e,n){this.buffer=\"\",this.classPrefix=n.classPrefix,e.walk(this)}addText(e){this.buffer+=n(e)}openNode(e){if(!s(e))return;let n=e.kind;e.sublanguage||(n=`${this.classPrefix}${n}`),this.span(n)}closeNode(e){s(e)&&(this.buffer+=i)}span(e){this.buffer+=`<span class=\"${e}\">`}value(){return this.buffer}}class l{constructor(){this.rootNode={children:[]},this.stack=[this.rootNode]}get top(){return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){this.top.children.push(e)}openNode(e){let n={kind:e,children:[]};this.add(n),this.stack.push(n)}closeNode(){if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)}walk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,n){return\"string\"==typeof n?e.addText(n):n.children&&(e.openNode(n),n.children.forEach(n=>this._walk(e,n)),e.closeNode(n)),e}static _collapse(e){e.children&&(e.children.every(e=>\"string\"==typeof e)?(e.text=e.children.join(\"\"),delete e.children):e.children.forEach(e=>{\"string\"!=typeof e&&l._collapse(e)}))}}class c extends l{constructor(e){super(),this.options=e}addKeyword(e,n){\"\"!==e&&(this.openNode(n),this.addText(e),this.closeNode())}addText(e){\"\"!==e&&this.add(e)}addSublanguage(e,n){let t=e.root;t.kind=n,t.sublanguage=!0,this.add(t)}toHTML(){return new o(this,this.options).value()}finalize(){}}function u(e){return e&&e.source||e}const d=\"(-?)(\\\\b0[xX][a-fA-F0-9]+|(\\\\b\\\\d+(\\\\.\\\\d*)?|\\\\.\\\\d+)([eE][-+]?\\\\d+)?)\",g={begin:\"\\\\\\\\[\\\\s\\\\S]\",relevance:0},h={className:\"string\",begin:\"'\",end:\"'\",illegal:\"\\\\n\",contains:[g]},f={className:\"string\",begin:'\"',end:'\"',illegal:\"\\\\n\",contains:[g]},p={begin:/\\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\\b/},m=function(e,n,r){var a=t({className:\"comment\",begin:e,end:n,contains:[]},r||{});return a.contains.push(p),a.contains.push({className:\"doctag\",begin:\"(?:TODO|FIXME|NOTE|BUG|XXX):\",relevance:0}),a},b=m(\"//\",\"$\"),v=m(\"/\\\\*\",\"\\\\*/\"),x=m(\"#\",\"$\");var _=Object.freeze({__proto__:null,IDENT_RE:\"[a-zA-Z]\\\\w*\",UNDERSCORE_IDENT_RE:\"[a-zA-Z_]\\\\w*\",NUMBER_RE:\"\\\\b\\\\d+(\\\\.\\\\d+)?\",C_NUMBER_RE:d,BINARY_NUMBER_RE:\"\\\\b(0b[01]+)\",RE_STARTERS_RE:\"!|!=|!==|%|%=|&|&&|&=|\\\\*|\\\\*=|\\\\+|\\\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\\\?|\\\\[|\\\\{|\\\\(|\\\\^|\\\\^=|\\\\||\\\\|=|\\\\|\\\\||~\",BACKSLASH_ESCAPE:g,APOS_STRING_MODE:h,QUOTE_STRING_MODE:f,PHRASAL_WORDS_MODE:p,COMMENT:m,C_LINE_COMMENT_MODE:b,C_BLOCK_COMMENT_MODE:v,HASH_COMMENT_MODE:x,NUMBER_MODE:{className:\"number\",begin:\"\\\\b\\\\d+(\\\\.\\\\d+)?\",relevance:0},C_NUMBER_MODE:{className:\"number\",begin:d,relevance:0},BINARY_NUMBER_MODE:{className:\"number\",begin:\"\\\\b(0b[01]+)\",relevance:0},CSS_NUMBER_MODE:{className:\"number\",begin:\"\\\\b\\\\d+(\\\\.\\\\d+)?(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?\",relevance:0},REGEXP_MODE:{begin:/(?=\\/[^\\/\\n]*\\/)/,contains:[{className:\"regexp\",begin:/\\//,end:/\\/[gimuy]*/,illegal:/\\n/,contains:[g,{begin:/\\[/,end:/\\]/,relevance:0,contains:[g]}]}]},TITLE_MODE:{className:\"title\",begin:\"[a-zA-Z]\\\\w*\",relevance:0},UNDERSCORE_TITLE_MODE:{className:\"title\",begin:\"[a-zA-Z_]\\\\w*\",relevance:0},METHOD_GUARD:{begin:\"\\\\.\\\\s*[a-zA-Z_]\\\\w*\",relevance:0}}),E=\"of and for in not or if then\".split(\" \");function R(e,n){return n?+n:(t=e,E.includes(t.toLowerCase())?0:1);var t}const N=n,w=t,{nodeStream:y,mergeStreams:O}=a;return function(n){var r=[],a={},i={},s=[],o=!0,l=/((^(<[^>]+>|\\t|)+|(?:\\n)))/gm,d=\"Could not find the language '{}', did you forget to load/include a language module?\",g={noHighlightRe:/^(no-?highlight)$/i,languageDetectRe:/\\blang(?:uage)?-([\\w-]+)\\b/i,classPrefix:\"hljs-\",tabReplace:null,useBR:!1,languages:void 0,__emitter:c};function h(e){return g.noHighlightRe.test(e)}function f(e,n,t,r){var a={code:n,language:e};T(\"before:highlight\",a);var i=a.result?a.result:p(a.language,a.code,t,r);return i.code=a.code,T(\"after:highlight\",i),i}function p(e,n,r,i){var s=n;function l(e,n){var t=v.case_insensitive?n[0].toLowerCase():n[0];return e.keywords.hasOwnProperty(t)&&e.keywords[t]}function c(){null!=_.subLanguage?function(){if(\"\"!==k){var e=\"string\"==typeof _.subLanguage;if(!e||a[_.subLanguage]){var n=e?p(_.subLanguage,k,!0,E[_.subLanguage]):m(k,_.subLanguage.length?_.subLanguage:void 0);_.relevance>0&&(T+=n.relevance),e&&(E[_.subLanguage]=n.top),w.addSublanguage(n.emitter,n.language)}else w.addText(k)}}():function(){var e,n,t,r;if(_.keywords){for(n=0,_.lexemesRe.lastIndex=0,t=_.lexemesRe.exec(k),r=\"\";t;){r+=k.substring(n,t.index);var a=null;(e=l(_,t))?(w.addText(r),r=\"\",T+=e[1],a=e[0],w.addKeyword(t[0],a)):r+=t[0],n=_.lexemesRe.lastIndex,t=_.lexemesRe.exec(k)}r+=k.substr(n),w.addText(r)}else w.addText(k)}(),k=\"\"}function h(e){e.className&&w.openNode(e.className),_=Object.create(e,{parent:{value:_}})}var f={};function b(n,t){var a,i=t&&t[0];if(k+=n,null==i)return c(),0;if(\"begin\"==f.type&&\"end\"==t.type&&f.index==t.index&&\"\"===i){if(k+=s.slice(t.index,t.index+1),!o)throw(a=Error(\"0 width match regex\")).languageName=e,a.badRule=f.rule,a;return 1}if(f=t,\"begin\"===t.type)return function(e){var n=e[0],t=e.rule;return t.__onBegin&&(t.__onBegin(e)||{}).ignoreMatch?function(e){return 0===_.matcher.regexIndex?(k+=e[0],1):(A=!0,0)}(n):(t&&t.endSameAsBegin&&(t.endRe=RegExp(n.replace(/[-\\/\\\\^$*+?.()|[\\]{}]/g,\"\\\\$&\"),\"m\")),t.skip?k+=n:(t.excludeBegin&&(k+=n),c(),t.returnBegin||t.excludeBegin||(k=n)),h(t),t.returnBegin?0:n.length)}(t);if(\"illegal\"===t.type&&!r)throw(a=Error('Illegal lexeme \"'+i+'\" for mode \"'+(_.className||\"<unnamed>\")+'\"')).mode=_,a;if(\"end\"===t.type){var l=function(e){var n=e[0],t=s.substr(e.index),r=function e(n,t){if(function(e,n){var t=e&&e.exec(n);return t&&0===t.index}(n.endRe,t)){for(;n.endsParent&&n.parent;)n=n.parent;return n}if(n.endsWithParent)return e(n.parent,t)}(_,t);if(r){var a=_;a.skip?k+=n:(a.returnEnd||a.excludeEnd||(k+=n),c(),a.excludeEnd&&(k=n));do{_.className&&w.closeNode(),_.skip||_.subLanguage||(T+=_.relevance),_=_.parent}while(_!==r.parent);return r.starts&&(r.endSameAsBegin&&(r.starts.endRe=r.endRe),h(r.starts)),a.returnEnd?0:n.length}}(t);if(null!=l)return l}return k+=i,i.length}var v=M(e);if(!v)throw console.error(d.replace(\"{}\",e)),Error('Unknown language: \"'+e+'\"');!function(e){function n(n,t){return RegExp(u(n),\"m\"+(e.case_insensitive?\"i\":\"\")+(t?\"g\":\"\"))}class r{constructor(){this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0}addRule(e,n){n.position=this.position++,this.matchIndexes[this.matchAt]=n,this.regexes.push([n,e]),this.matchAt+=function(e){return RegExp(e.toString()+\"|\").exec(\"\").length-1}(e)+1}compile(){0===this.regexes.length&&(this.exec=()=>null);let e=this.regexes.map(e=>e[1]);this.matcherRe=n(function(e,n){for(var t=/\\[(?:[^\\\\\\]]|\\\\.)*\\]|\\(\\??|\\\\([1-9][0-9]*)|\\\\./,r=0,a=\"\",i=0;i<e.length;i++){var s=r+=1,o=u(e[i]);for(i>0&&(a+=\"|\"),a+=\"(\";o.length>0;){var l=t.exec(o);if(null==l){a+=o;break}a+=o.substring(0,l.index),o=o.substring(l.index+l[0].length),\"\\\\\"==l[0][0]&&l[1]?a+=\"\\\\\"+(+l[1]+s):(a+=l[0],\"(\"==l[0]&&r++)}a+=\")\"}return a}(e),!0),this.lastIndex=0}exec(e){this.matcherRe.lastIndex=this.lastIndex;let n=this.matcherRe.exec(e);if(!n)return null;let t=n.findIndex((e,n)=>n>0&&null!=e),r=this.matchIndexes[t];return Object.assign(n,r)}}class a{constructor(){this.rules=[],this.multiRegexes=[],this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(e){if(this.multiRegexes[e])return this.multiRegexes[e];let n=new r;return this.rules.slice(e).forEach(([e,t])=>n.addRule(e,t)),n.compile(),this.multiRegexes[e]=n,n}considerAll(){this.regexIndex=0}addRule(e,n){this.rules.push([e,n]),\"begin\"===n.type&&this.count++}exec(e){let n=this.getMatcher(this.regexIndex);n.lastIndex=this.lastIndex;let t=n.exec(e);return t&&(this.regexIndex+=t.position+1,this.regexIndex===this.count&&(this.regexIndex=0)),t}}function i(e){let n=e.input[e.index-1],t=e.input[e.index+e[0].length];if(\".\"===n||\".\"===t)return{ignoreMatch:!0}}if(e.contains&&e.contains.includes(\"self\"))throw Error(\"ERR: contains `self` is not supported at the top-level of a language.  See documentation.\");!function r(s,o){s.compiled||(s.compiled=!0,s.__onBegin=null,s.keywords=s.keywords||s.beginKeywords,s.keywords&&(s.keywords=function(e,n){var t={};return\"string\"==typeof e?r(\"keyword\",e):Object.keys(e).forEach((function(n){r(n,e[n])})),t;function r(e,r){n&&(r=r.toLowerCase()),r.split(\" \").forEach((function(n){var r=n.split(\"|\");t[r[0]]=[e,R(r[0],r[1])]}))}}(s.keywords,e.case_insensitive)),s.lexemesRe=n(s.lexemes||/\\w+/,!0),o&&(s.beginKeywords&&(s.begin=\"\\\\b(\"+s.beginKeywords.split(\" \").join(\"|\")+\")(?=\\\\b|\\\\s)\",s.__onBegin=i),s.begin||(s.begin=/\\B|\\b/),s.beginRe=n(s.begin),s.endSameAsBegin&&(s.end=s.begin),s.end||s.endsWithParent||(s.end=/\\B|\\b/),s.end&&(s.endRe=n(s.end)),s.terminator_end=u(s.end)||\"\",s.endsWithParent&&o.terminator_end&&(s.terminator_end+=(s.end?\"|\":\"\")+o.terminator_end)),s.illegal&&(s.illegalRe=n(s.illegal)),null==s.relevance&&(s.relevance=1),s.contains||(s.contains=[]),s.contains=[].concat(...s.contains.map((function(e){return function(e){return e.variants&&!e.cached_variants&&(e.cached_variants=e.variants.map((function(n){return t(e,{variants:null},n)}))),e.cached_variants?e.cached_variants:function e(n){return!!n&&(n.endsWithParent||e(n.starts))}(e)?t(e,{starts:e.starts?t(e.starts):null}):Object.isFrozen(e)?t(e):e}(\"self\"===e?s:e)}))),s.contains.forEach((function(e){r(e,s)})),s.starts&&r(s.starts,o),s.matcher=function(e){let n=new a;return e.contains.forEach(e=>n.addRule(e.begin,{rule:e,type:\"begin\"})),e.terminator_end&&n.addRule(e.terminator_end,{type:\"end\"}),e.illegal&&n.addRule(e.illegal,{type:\"illegal\"}),n}(s))}(e)}(v);var x,_=i||v,E={},w=new g.__emitter(g);!function(){for(var e=[],n=_;n!==v;n=n.parent)n.className&&e.unshift(n.className);e.forEach(e=>w.openNode(e))}();var y,O,k=\"\",T=0,L=0;try{var A=!1;for(_.matcher.considerAll();A?A=!1:(_.matcher.lastIndex=L,_.matcher.considerAll()),y=_.matcher.exec(s);)O=b(s.substring(L,y.index),y),L=y.index+O;return b(s.substr(L)),w.closeAllNodes(),w.finalize(),x=w.toHTML(),{relevance:T,value:x,language:e,illegal:!1,emitter:w,top:_}}catch(n){if(n.message&&n.message.includes(\"Illegal\"))return{illegal:!0,illegalBy:{msg:n.message,context:s.slice(L-100,L+100),mode:n.mode},sofar:x,relevance:0,value:N(s),emitter:w};if(o)return{relevance:0,value:N(s),emitter:w,language:e,top:_,errorRaised:n};throw n}}function m(e,n){n=n||g.languages||Object.keys(a);var t=function(e){const n={relevance:0,emitter:new g.__emitter(g),value:N(e),illegal:!1,top:E};return n.emitter.addText(e),n}(e),r=t;return n.filter(M).filter(k).forEach((function(n){var a=p(n,e,!1);a.language=n,a.relevance>r.relevance&&(r=a),a.relevance>t.relevance&&(r=t,t=a)})),r.language&&(t.second_best=r),t}function b(e){return g.tabReplace||g.useBR?e.replace(l,(function(e,n){return g.useBR&&\"\\n\"===e?\"<br>\":g.tabReplace?n.replace(/\\t/g,g.tabReplace):\"\"})):e}function v(e){var n,t,r,a,s,o=function(e){var n,t=e.className+\" \";if(t+=e.parentNode?e.parentNode.className:\"\",n=g.languageDetectRe.exec(t)){var r=M(n[1]);return r||(console.warn(d.replace(\"{}\",n[1])),console.warn(\"Falling back to no-highlight mode for this block.\",e)),r?n[1]:\"no-highlight\"}return t.split(/\\s+/).find(e=>h(e)||M(e))}(e);h(o)||(T(\"before:highlightBlock\",{block:e,language:o}),g.useBR?(n=document.createElement(\"div\")).innerHTML=e.innerHTML.replace(/\\n/g,\"\").replace(/<br[ \\/]*>/g,\"\\n\"):n=e,s=n.textContent,r=o?f(o,s,!0):m(s),(t=y(n)).length&&((a=document.createElement(\"div\")).innerHTML=r.value,r.value=O(t,y(a),s)),r.value=b(r.value),T(\"after:highlightBlock\",{block:e,result:r}),e.innerHTML=r.value,e.className=function(e,n,t){var r=n?i[n]:t,a=[e.trim()];return e.match(/\\bhljs\\b/)||a.push(\"hljs\"),e.includes(r)||a.push(r),a.join(\" \").trim()}(e.className,o,r.language),e.result={language:r.language,re:r.relevance},r.second_best&&(e.second_best={language:r.second_best.language,re:r.second_best.relevance}))}function x(){if(!x.called){x.called=!0;var e=document.querySelectorAll(\"pre code\");r.forEach.call(e,v)}}const E={disableAutodetect:!0,name:\"Plain text\"};function M(e){return e=(e||\"\").toLowerCase(),a[e]||a[i[e]]}function k(e){var n=M(e);return n&&!n.disableAutodetect}function T(e,n){var t=e;s.forEach((function(e){e[t]&&e[t](n)}))}Object.assign(n,{highlight:f,highlightAuto:m,fixMarkup:b,highlightBlock:v,configure:function(e){g=w(g,e)},initHighlighting:x,initHighlightingOnLoad:function(){window.addEventListener(\"DOMContentLoaded\",x,!1)},registerLanguage:function(e,t){var r;try{r=t(n)}catch(n){if(console.error(\"Language definition for '{}' could not be registered.\".replace(\"{}\",e)),!o)throw n;console.error(n),r=E}r.name||(r.name=e),a[e]=r,r.rawDefinition=t.bind(null,n),r.aliases&&r.aliases.forEach((function(n){i[n]=e}))},listLanguages:function(){return Object.keys(a)},getLanguage:M,requireLanguage:function(e){var n=M(e);if(n)return n;throw Error(\"The '{}' language is required, but not loaded.\".replace(\"{}\",e))},autoDetection:k,inherit:w,addPlugin:function(e,n){s.push(e)}}),n.debugMode=function(){o=!1},n.safeMode=function(){o=!0},n.versionString=\"10.0.1\";for(const n in _)\"object\"==typeof _[n]&&e(_[n]);return Object.assign(n,_),n}({})}));hljs.registerLanguage(\"xml\",function(){\"use strict\";return function(e){var n={className:\"symbol\",begin:\"&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;\"},a={begin:\"\\\\s\",contains:[{className:\"meta-keyword\",begin:\"#?[a-z_][a-z1-9_-]+\",illegal:\"\\\\n\"}]},s=e.inherit(a,{begin:\"\\\\(\",end:\"\\\\)\"}),t=e.inherit(e.APOS_STRING_MODE,{className:\"meta-string\"}),i=e.inherit(e.QUOTE_STRING_MODE,{className:\"meta-string\"}),c={endsWithParent:!0,illegal:/</,relevance:0,contains:[{className:\"attr\",begin:\"[A-Za-z0-9\\\\._:-]+\",relevance:0},{begin:/=\\s*/,relevance:0,contains:[{className:\"string\",endsParent:!0,variants:[{begin:/\"/,end:/\"/,contains:[n]},{begin:/'/,end:/'/,contains:[n]},{begin:/[^\\s\"'=<>`]+/}]}]}]};return{name:\"HTML, XML\",aliases:[\"html\",\"xhtml\",\"rss\",\"atom\",\"xjb\",\"xsd\",\"xsl\",\"plist\",\"wsf\",\"svg\"],case_insensitive:!0,contains:[{className:\"meta\",begin:\"<![a-z]\",end:\">\",relevance:10,contains:[a,i,t,s,{begin:\"\\\\[\",end:\"\\\\]\",contains:[{className:\"meta\",begin:\"<![a-z]\",end:\">\",contains:[a,s,i,t]}]}]},e.COMMENT(\"\\x3c!--\",\"--\\x3e\",{relevance:10}),{begin:\"<\\\\!\\\\[CDATA\\\\[\",end:\"\\\\]\\\\]>\",relevance:10},n,{className:\"meta\",begin:/<\\?xml/,end:/\\?>/,relevance:10},{className:\"tag\",begin:\"<style(?=\\\\s|>)\",end:\">\",keywords:{name:\"style\"},contains:[c],starts:{end:\"</style>\",returnEnd:!0,subLanguage:[\"css\",\"xml\"]}},{className:\"tag\",begin:\"<script(?=\\\\s|>)\",end:\">\",keywords:{name:\"script\"},contains:[c],starts:{end:\"<\\/script>\",returnEnd:!0,subLanguage:[\"javascript\",\"handlebars\",\"xml\"]}},{className:\"tag\",begin:\"</?\",end:\"/?>\",contains:[{className:\"name\",begin:/[^\\/><\\s]+/,relevance:0},c]}]}}}());hljs.registerLanguage(\"css\",function(){\"use strict\";return function(e){var n={begin:/(?:[A-Z\\_\\.\\-]+|--[a-zA-Z0-9_-]+)\\s*:/,returnBegin:!0,end:\";\",endsWithParent:!0,contains:[{className:\"attribute\",begin:/\\S/,end:\":\",excludeEnd:!0,starts:{endsWithParent:!0,excludeEnd:!0,contains:[{begin:/[\\w-]+\\(/,returnBegin:!0,contains:[{className:\"built_in\",begin:/[\\w-]+/},{begin:/\\(/,end:/\\)/,contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.CSS_NUMBER_MODE]}]},e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,e.C_BLOCK_COMMENT_MODE,{className:\"number\",begin:\"#[0-9A-Fa-f]+\"},{className:\"meta\",begin:\"!important\"}]}}]};return{name:\"CSS\",case_insensitive:!0,illegal:/[=\\/|'\\$]/,contains:[e.C_BLOCK_COMMENT_MODE,{className:\"selector-id\",begin:/#[A-Za-z0-9_-]+/},{className:\"selector-class\",begin:/\\.[A-Za-z0-9_-]+/},{className:\"selector-attr\",begin:/\\[/,end:/\\]/,illegal:\"$\",contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},{className:\"selector-pseudo\",begin:/:(:)?[a-zA-Z0-9\\_\\-\\+\\(\\)\"'.]+/},{begin:\"@(page|font-face)\",lexemes:\"@[a-z-]+\",keywords:\"@page @font-face\"},{begin:\"@\",end:\"[{;]\",illegal:/:/,returnBegin:!0,contains:[{className:\"keyword\",begin:/@\\-?\\w[\\w]*(\\-\\w+)*/},{begin:/\\s/,endsWithParent:!0,excludeEnd:!0,relevance:0,keywords:\"and or not only\",contains:[{begin:/[a-z-]+:/,className:\"attribute\"},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.CSS_NUMBER_MODE]}]},{className:\"selector-tag\",begin:\"[a-zA-Z-][a-zA-Z0-9_-]*\",relevance:0},{begin:\"{\",end:\"}\",illegal:/\\S/,contains:[e.C_BLOCK_COMMENT_MODE,n]}]}}}());hljs.registerLanguage(\"json\",function(){\"use strict\";return function(n){var e={literal:\"true false null\"},i=[n.C_LINE_COMMENT_MODE,n.C_BLOCK_COMMENT_MODE],t=[n.QUOTE_STRING_MODE,n.C_NUMBER_MODE],a={end:\",\",endsWithParent:!0,excludeEnd:!0,contains:t,keywords:e},l={begin:\"{\",end:\"}\",contains:[{className:\"attr\",begin:/\"/,end:/\"/,contains:[n.BACKSLASH_ESCAPE],illegal:\"\\\\n\"},n.inherit(a,{begin:/:/})].concat(i),illegal:\"\\\\S\"},s={begin:\"\\\\[\",end:\"\\\\]\",contains:[n.inherit(a)],illegal:\"\\\\S\"};return t.push(l,s),i.forEach((function(n){t.push(n)})),{name:\"JSON\",contains:t,keywords:e,illegal:\"\\\\S\"}}}());hljs.registerLanguage(\"javascript\",function(){\"use strict\";return function(e){var n={begin:/<[A-Za-z0-9\\\\._:-]+/,end:/\\/[A-Za-z0-9\\\\._:-]+>|\\/>/},a=\"[A-Za-z$_][0-9A-Za-z$_]*\",s={keyword:\"in of if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const export super debugger as async await static import from as\",literal:\"true false null undefined NaN Infinity\",built_in:\"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document Symbol Set Map WeakSet WeakMap Proxy Reflect Promise\"},r={className:\"number\",variants:[{begin:\"\\\\b(0[bB][01]+)n?\"},{begin:\"\\\\b(0[oO][0-7]+)n?\"},{begin:e.C_NUMBER_RE+\"n?\"}],relevance:0},i={className:\"subst\",begin:\"\\\\$\\\\{\",end:\"\\\\}\",keywords:s,contains:[]},t={begin:\"html`\",end:\"\",starts:{end:\"`\",returnEnd:!1,contains:[e.BACKSLASH_ESCAPE,i],subLanguage:\"xml\"}},c={begin:\"css`\",end:\"\",starts:{end:\"`\",returnEnd:!1,contains:[e.BACKSLASH_ESCAPE,i],subLanguage:\"css\"}},o={className:\"string\",begin:\"`\",end:\"`\",contains:[e.BACKSLASH_ESCAPE,i]};i.contains=[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,t,c,o,r,e.REGEXP_MODE];var l=i.contains.concat([e.C_BLOCK_COMMENT_MODE,e.C_LINE_COMMENT_MODE]),d={className:\"params\",begin:/\\(/,end:/\\)/,excludeBegin:!0,excludeEnd:!0,contains:l};return{name:\"JavaScript\",aliases:[\"js\",\"jsx\",\"mjs\",\"cjs\"],keywords:s,contains:[{className:\"meta\",relevance:10,begin:/^\\s*['\"]use (strict|asm)['\"]/},{className:\"meta\",begin:/^#!/,end:/$/},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,t,c,o,e.C_LINE_COMMENT_MODE,e.COMMENT(\"/\\\\*\\\\*\",\"\\\\*/\",{relevance:0,contains:[{className:\"doctag\",begin:\"@[A-Za-z]+\",contains:[{className:\"type\",begin:\"\\\\{\",end:\"\\\\}\",relevance:0},{className:\"variable\",begin:a+\"(?=\\\\s*(-)|$)\",endsParent:!0,relevance:0},{begin:/(?=[^\\n])\\s/,relevance:0}]}]}),e.C_BLOCK_COMMENT_MODE,r,{begin:/[{,\\n]\\s*/,relevance:0,contains:[{begin:a+\"\\\\s*:\",returnBegin:!0,relevance:0,contains:[{className:\"attr\",begin:a,relevance:0}]}]},{begin:\"(\"+e.RE_STARTERS_RE+\"|\\\\b(case|return|throw)\\\\b)\\\\s*\",keywords:\"return throw case\",contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.REGEXP_MODE,{className:\"function\",begin:\"(\\\\(.*?\\\\)|\"+a+\")\\\\s*=>\",returnBegin:!0,end:\"\\\\s*=>\",contains:[{className:\"params\",variants:[{begin:a},{begin:/\\(\\s*\\)/},{begin:/\\(/,end:/\\)/,excludeBegin:!0,excludeEnd:!0,keywords:s,contains:l}]}]},{begin:/,/,relevance:0},{className:\"\",begin:/\\s/,end:/\\s*/,skip:!0},{variants:[{begin:\"<>\",end:\"</>\"},{begin:n.begin,end:n.end}],subLanguage:\"xml\",contains:[{begin:n.begin,end:n.end,skip:!0,contains:[\"self\"]}]}],relevance:0},{className:\"function\",beginKeywords:\"function\",end:/\\{/,excludeEnd:!0,contains:[e.inherit(e.TITLE_MODE,{begin:a}),d],illegal:/\\[|%/},{begin:/\\$[(.]/},e.METHOD_GUARD,{className:\"class\",beginKeywords:\"class\",end:/[{;=]/,excludeEnd:!0,illegal:/[:\"\\[\\]]/,contains:[{beginKeywords:\"extends\"},e.UNDERSCORE_TITLE_MODE]},{beginKeywords:\"constructor\",end:/\\{/,excludeEnd:!0},{begin:\"(get|set)\\\\s*(?=\"+a+\"\\\\()\",end:/{/,keywords:\"get set\",contains:[e.inherit(e.TITLE_MODE,{begin:a}),{begin:/\\(\\)/},d]}],illegal:/#(?!!)/}}}());hljs.registerLanguage(\"plaintext\",function(){\"use strict\";return function(t){return{name:\"Plain text\",aliases:[\"text\",\"txt\"],disableAutodetect:!0}}}());"
  },
  {
    "path": "web/includes/js/render.js",
    "content": "(function () {\n  var file = file || \"README.md?\"+Math.floor(Math.random(9999));\n  var reader = new stmd.DocParser();\n  var writer = new stmd.HtmlRenderer();\n  var xhr = new XMLHttpRequest();\n  xhr.onreadystatechange = function () {\n    if(xhr.readyState === 4 && xhr.status === 200) {\n      display(xhr);\n      \n      // sharable anchors\n      if(location.hash.length > 1){\n        const yourElement = document.getElementById(location.hash.substring(1 ));\n        const y = yourElement.getBoundingClientRect().top + window.pageYOffset;\n        window.scrollTo({top: y, behavior: 'smooth'});\n      }\n      \n      // code highlighting!\n      document.querySelectorAll('pre code').forEach((block) => {\n        hljs.highlightBlock(block);\n      });\n    }\n  };\n\n  function display(xhr) {\n    var parsed = reader.parse(xhr.responseText);\n    var content = writer.renderBlock(parsed);\n    document.getElementById('md').innerHTML = content;\n\n    // fix anchors\n    var hs = document.querySelectorAll(\"h1, h2, h3, h4, h5, h6\");\n    for(var i=0; i<hs.length;i++){\n      var anchorName = hs[i].innerHTML.toLowerCase().split(\" \").join(\"-\");\n      hs[i].setAttribute('id', anchorName);\n      hs[i].setAttribute('name', anchorName);\n    }\n\n    // fix links\n    var links = document.getElementsByTagName(\"a\");\n    for(var i=0; i<links.length;i++){\n      if(links[i].getAttribute('href').substring(0, 1) != \"#\"){\n        links[i].setAttribute('target', '_blank');\n      }\n    }\n\n    // table of contents\n    // var toc = document.getElementById('table-of-contents').nextSibling\n      //   var toc = document.getElementsByTagName(\"ul\")[0];\n      // toc.classList.add('sticky')\n      // toc.style.position = 'sticky'\n      // toc.style.top = '10px'\n\n\n\n    /* try to extract h1 title and use as title for page\n       if no h1, use name of file\n    */\n    try {\n      document.title = document.querySelector('h1').textContent\n    } catch (e) {\n      document.title = file;\n    }\n\n  }\n\n  xhr.open('GET', file);\n  xhr.send();\n  xhr.onerror = function() { \n    document.getElementById('spacer').style.display = 'none'\n  };\n})();\n\n"
  },
  {
    "path": "web/includes/js/stmd.js",
    "content": "// stmd.js - CommonMark in javascript\n// Copyright (C) 2014 John MacFarlane\n// License: BSD3.\n\n// Basic usage:\n//\n// var stmd = require('stmd');\n// var parser = new stmd.DocParser();\n// var renderer = new stmd.HtmlRenderer();\n// console.log(renderer.render(parser.parse('Hello *world*')));\n\n(function(exports) {\n\n// Some regexps used in inline parser:\n\nvar ESCAPABLE = '[!\"#$%&\\'()*+,./:;<=>?@[\\\\\\\\\\\\]^_`{|}~-]';\nvar ESCAPED_CHAR = '\\\\\\\\' + ESCAPABLE;\nvar IN_DOUBLE_QUOTES = '\"(' + ESCAPED_CHAR + '|[^\"\\\\x00])*\"';\nvar IN_SINGLE_QUOTES = '\\'(' + ESCAPED_CHAR + '|[^\\'\\\\x00])*\\'';\nvar IN_PARENS = '\\\\((' + ESCAPED_CHAR + '|[^)\\\\x00])*\\\\)';\nvar REG_CHAR = '[^\\\\\\\\()\\\\x00-\\\\x20]';\nvar IN_PARENS_NOSP = '\\\\((' + REG_CHAR + '|' + ESCAPED_CHAR + ')*\\\\)';\nvar TAGNAME = '[A-Za-z][A-Za-z0-9]*';\nvar BLOCKTAGNAME = '(?:article|header|aside|hgroup|iframe|blockquote|hr|body|li|map|button|object|canvas|ol|caption|output|col|p|colgroup|pre|dd|progress|div|section|dl|table|td|dt|tbody|embed|textarea|fieldset|tfoot|figcaption|th|figure|thead|footer|footer|tr|form|ul|h1|h2|h3|h4|h5|h6|video|script|style)';\nvar ATTRIBUTENAME = '[a-zA-Z_:][a-zA-Z0-9:._-]*';\nvar UNQUOTEDVALUE = \"[^\\\"'=<>`\\\\x00-\\\\x20]+\";\nvar SINGLEQUOTEDVALUE = \"'[^']*'\";\nvar DOUBLEQUOTEDVALUE = '\"[^\"]*\"';\nvar ATTRIBUTEVALUE = \"(?:\" + UNQUOTEDVALUE + \"|\" + SINGLEQUOTEDVALUE + \"|\" + DOUBLEQUOTEDVALUE + \")\";\nvar ATTRIBUTEVALUESPEC = \"(?:\" + \"\\\\s*=\" + \"\\\\s*\" + ATTRIBUTEVALUE + \")\";\nvar ATTRIBUTE = \"(?:\" + \"\\\\s+\" + ATTRIBUTENAME + ATTRIBUTEVALUESPEC + \"?)\";\nvar OPENTAG = \"<\" + TAGNAME + ATTRIBUTE + \"*\" + \"\\\\s*/?>\";\nvar CLOSETAG = \"</\" + TAGNAME + \"\\\\s*[>]\";\nvar OPENBLOCKTAG = \"<\" + BLOCKTAGNAME + ATTRIBUTE + \"*\" + \"\\\\s*/?>\";\nvar CLOSEBLOCKTAG = \"</\" + BLOCKTAGNAME + \"\\\\s*[>]\";\nvar HTMLCOMMENT = \"<!--([^-]+|[-][^-]+)*-->\";\nvar PROCESSINGINSTRUCTION = \"[<][?].*?[?][>]\";\nvar DECLARATION = \"<![A-Z]+\" + \"\\\\s+[^>]*>\";\nvar CDATA = \"<!\\\\[CDATA\\\\[([^\\\\]]+|\\\\][^\\\\]]|\\\\]\\\\][^>])*\\\\]\\\\]>\";\nvar HTMLTAG = \"(?:\" + OPENTAG + \"|\" + CLOSETAG + \"|\" + HTMLCOMMENT + \"|\" +\n           PROCESSINGINSTRUCTION + \"|\" + DECLARATION + \"|\" + CDATA + \")\";\nvar HTMLBLOCKOPEN = \"<(?:\" + BLOCKTAGNAME + \"[\\\\s/>]\" + \"|\" +\n    \"/\" + BLOCKTAGNAME + \"[\\\\s>]\" + \"|\" + \"[?!])\";\n\nvar reHtmlTag = new RegExp('^' + HTMLTAG, 'i');\n\nvar reHtmlBlockOpen = new RegExp('^' + HTMLBLOCKOPEN, 'i');\n\nvar reLinkTitle = new RegExp(\n    '^(?:\"(' + ESCAPED_CHAR + '|[^\"\\\\x00])*\"' +\n    '|' +\n    '\\'(' + ESCAPED_CHAR + '|[^\\'\\\\x00])*\\'' +\n    '|' +\n    '\\\\((' + ESCAPED_CHAR + '|[^)\\\\x00])*\\\\))');\n\nvar reLinkDestinationBraces = new RegExp(\n    '^(?:[<](?:[^<>\\\\n\\\\\\\\\\\\x00]' + '|' + ESCAPED_CHAR + '|' + '\\\\\\\\)*[>])');\n\nvar reLinkDestination = new RegExp(\n    '^(?:' + REG_CHAR + '+|' + ESCAPED_CHAR + '|' + IN_PARENS_NOSP + ')*');\n\nvar reEscapable = new RegExp(ESCAPABLE);\n\nvar reAllEscapedChar = new RegExp('\\\\\\\\(' + ESCAPABLE + ')', 'g');\n\nvar reEscapedChar = new RegExp('^\\\\\\\\(' + ESCAPABLE + ')');\n\nvar reAllTab = /\\t/g;\n\nvar reHrule = /^(?:(?:\\* *){3,}|(?:_ *){3,}|(?:- *){3,}) *$/;\n\n// Matches a character with a special meaning in markdown,\n// or a string of non-special characters.\nvar reMain = /^(?:[\\n`\\[\\]\\\\!<&*_]|[^\\n`\\[\\]\\\\!<&*_]+)/m;\n\n// UTILITY FUNCTIONS\n\n// Replace backslash escapes with literal characters.\nvar unescape = function(s) {\n  return s.replace(reAllEscapedChar, '$1');\n};\n\n// Returns true if string contains only space characters.\nvar isBlank = function(s) {\n  return /^\\s*$/.test(s);\n};\n\n// Normalize reference label: collapse internal whitespace\n// to single space, remove leading/trailing whitespace, case fold.\nvar normalizeReference = function(s) {\n  return s.trim()\n          .replace(/\\s+/,' ')\n          .toUpperCase();\n};\n\n// Attempt to match a regex in string s at offset offset.\n// Return index of match or null.\nvar matchAt = function(re, s, offset) {\n  var res = s.slice(offset).match(re);\n  if (res) {\n    return offset + res.index;\n  } else {\n    return null;\n  }\n};\n\n// Convert tabs to spaces on each line using a 4-space tab stop.\nvar detabLine = function(text) {\n  if (text.indexOf('\\t') == -1) {\n    return text;\n  } else {\n    var lastStop = 0;\n    return text.replace(reAllTab, function(match, offset) {\n      var result = '    '.slice((offset - lastStop) % 4);\n      lastStop = offset + 1;\n      return result;\n    });\n  }\n};\n\n// INLINE PARSER\n\n// These are methods of an InlineParser object, defined below.\n// An InlineParser keeps track of a subject (a string to be\n// parsed) and a position in that subject.\n\n// If re matches at current position in the subject, advance\n// position in subject and return the match; otherwise return null.\nvar match = function(re) {\n  var match = re.exec(this.subject.slice(this.pos));\n  if (match) {\n    this.pos += match.index + match[0].length;\n    return match[0];\n  } else {\n    return null;\n  }\n};\n\n// Returns the character at the current subject position, or null if\n// there are no more characters.\nvar peek = function() {\n  return this.subject[this.pos] || null;\n};\n\n// Parse zero or more space characters, including at most one newline\nvar spnl = function() {\n  this.match(/^ *(?:\\n *)?/);\n  return 1;\n};\n\n// All of the parsers below try to match something at the current position\n// in the subject.  If they succeed in matching anything, they\n// push an inline element onto the 'inlines' list.  They return the\n// number of characters parsed (possibly 0).\n\n// Attempt to parse backticks, adding either a backtick code span or a\n// literal sequence of backticks to the 'inlines' list.\nvar parseBackticks = function(inlines) {\n  var startpos = this.pos;\n  var ticks = this.match(/^`+/);\n  if (!ticks) {\n    return 0;\n  }\n  var afterOpenTicks = this.pos;\n  var foundCode = false;\n  var match;\n  while (!foundCode && (match = this.match(/`+/m))) {\n    if (match == ticks) {\n      inlines.push({ t: 'Code', c: this.subject.slice(afterOpenTicks,\n                                   this.pos - ticks.length)\n             .replace(/[ \\n]+/g,' ')\n             .trim() });\n      return (this.pos - startpos);\n    }\n  }\n  // If we got here, we didn't match a closing backtick sequence.\n  inlines.push({ t: 'Str', c: ticks });\n  this.pos = afterOpenTicks;\n  return (this.pos - startpos);\n};\n\n// Parse a backslash-escaped special character, adding either the escaped\n// character, a hard line break (if the backslash is followed by a newline),\n// or a literal backslash to the 'inlines' list.\nvar parseEscaped = function(inlines) {\n  var subj = this.subject,\n      pos  = this.pos;\n  if (subj[pos] === '\\\\') {\n    if (subj[pos + 1] === '\\n') {\n      inlines.push({ t: 'Hardbreak' });\n      this.pos = this.pos + 2;\n      return 2;\n    } else if (reEscapable.test(subj[pos + 1])) {\n      inlines.push({ t: 'Str', c: subj[pos + 1] });\n      this.pos = this.pos + 2;\n      return 2;\n    } else {\n      this.pos++;\n      inlines.push({t: 'Str', c: '\\\\'});\n      return 1;\n    }\n  } else {\n    return 0;\n  }\n};\n\n// Attempt to parse an autolink (URL or email in pointy brackets).\nvar parseAutolink = function(inlines) {\n  var m;\n  var dest;\n  if ((m = this.match(/^<([a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)>/))) {  // email autolink\n    dest = m.slice(1,-1);\n    inlines.push({ t: 'Link', label: [{ t: 'Str', c: dest }],\n                   destination: 'mailto:' + dest });\n    return m.length;\n  } else if ((m = this.match(/^<(?:coap|doi|javascript|aaa|aaas|about|acap|cap|cid|crid|data|dav|dict|dns|file|ftp|geo|go|gopher|h323|http|https|iax|icap|im|imap|info|ipp|iris|iris.beep|iris.xpc|iris.xpcs|iris.lwz|ldap|mailto|mid|msrp|msrps|mtqp|mupdate|news|nfs|ni|nih|nntp|opaquelocktoken|pop|pres|rtsp|service|session|shttp|sieve|sip|sips|sms|snmp|soap.beep|soap.beeps|tag|tel|telnet|tftp|thismessage|tn3270|tip|tv|urn|vemmi|ws|wss|xcon|xcon-userid|xmlrpc.beep|xmlrpc.beeps|xmpp|z39.50r|z39.50s|adiumxtra|afp|afs|aim|apt|attachment|aw|beshare|bitcoin|bolo|callto|chrome|chrome-extension|com-eventbrite-attendee|content|cvs|dlna-playsingle|dlna-playcontainer|dtn|dvb|ed2k|facetime|feed|finger|fish|gg|git|gizmoproject|gtalk|hcp|icon|ipn|irc|irc6|ircs|itms|jar|jms|keyparc|lastfm|ldaps|magnet|maps|market|message|mms|ms-help|msnim|mumble|mvn|notes|oid|palm|paparazzi|platform|proxy|psyc|query|res|resource|rmi|rsync|rtmp|secondlife|sftp|sgn|skype|smb|soldat|spotify|ssh|steam|svn|teamspeak|things|udp|unreal|ut2004|ventrilo|view-source|webcal|wtai|wyciwyg|xfire|xri|ymsgr):[^<>\\x00-\\x20]*>/i))) {\n    dest = m.slice(1,-1);\n    inlines.push({ t: 'Link', label: [{ t: 'Str', c: dest }],\n                   destination: dest });\n    return m.length;\n  } else {\n    return 0;\n  }\n};\n\n// Attempt to parse a raw HTML tag.\nvar parseHtmlTag = function(inlines) {\n  var m = this.match(reHtmlTag);\n  if (m) {\n    inlines.push({ t: 'Html', c: m });\n    return m.length;\n  } else {\n    return 0;\n  }\n};\n\n// Scan a sequence of characters == c, and return information about\n// the number of delimiters and whether they are positioned such that\n// they can open and/or close emphasis or strong emphasis.  A utility\n// function for strong/emph parsing.\nvar scanDelims = function(c) {\n  var numdelims = 0;\n  var first_close_delims = 0;\n  var char_before, char_after;\n  var startpos = this.pos;\n\n  char_before = this.pos === 0 ? '\\n' :\n    this.subject[this.pos - 1];\n\n  while (this.peek() === c) {\n    numdelims++;\n    this.pos++;\n  }\n\n  char_after = this.peek() || '\\n';\n\n  var can_open = numdelims > 0 && numdelims <= 3 && !(/\\s/.test(char_after));\n  var can_close = numdelims > 0 && numdelims <= 3 && !(/\\s/.test(char_before));\n  if (c === '_') {\n    can_open = can_open && !((/[a-z0-9]/i).test(char_before));\n    can_close = can_close && !((/[a-z0-9]/i).test(char_after));\n  }\n  this.pos = startpos;\n  return { numdelims: numdelims,\n           can_open: can_open,\n           can_close: can_close };\n};\n\n// Attempt to parse emphasis or strong emphasis in an efficient way,\n// with no backtracking.\nvar parseEmphasis = function(inlines) {\n  var startpos = this.pos;\n  var c ;\n  var first_close = 0;\n  var nxt = this.peek();\n  if (nxt == '*' || nxt == '_') {\n    c = nxt;\n  } else {\n    return 0;\n  }\n\n  var numdelims;\n  var delimpos;\n\n  // Get opening delimiters.\n  res = this.scanDelims(c);\n  numdelims = res.numdelims;\n  this.pos += numdelims;\n  // We provisionally add a literal string.  If we match appropriate\n  // closing delimiters, we'll change this to Strong or Emph.\n  inlines.push({t: 'Str',\n               c: this.subject.substr(this.pos - numdelims, numdelims)});\n  // Record the position of this opening delimiter:\n  delimpos = inlines.length - 1;\n\n  if (!res.can_open || numdelims === 0) {\n    return 0;\n  }\n\n  var first_close_delims = 0;\n\n  switch (numdelims) {\n  case 1:  // we started with * or _\n    while (true) {\n      res = this.scanDelims(c);\n      if (res.numdelims >= 1 && res.can_close) {\n        this.pos += 1;\n        // Convert the inline at delimpos, currently a string with the delim,\n        // into an Emph whose contents are the succeeding inlines\n        inlines[delimpos].t = 'Emph';\n        inlines[delimpos].c = inlines.slice(delimpos + 1);\n        inlines.splice(delimpos + 1);\n        break;\n      } else {\n        if (this.parseInline(inlines) === 0) {\n          break;\n        }\n      }\n    }\n    return (this.pos - startpos);\n\n  case 2:  // We started with ** or __\n    while (true) {\n      res = this.scanDelims(c);\n      if (res.numdelims >= 2 && res.can_close) {\n        this.pos += 2;\n        inlines[delimpos].t = 'Strong';\n        inlines[delimpos].c = inlines.slice(delimpos + 1);\n        inlines.splice(delimpos + 1);\n        break;\n      } else {\n        if (this.parseInline(inlines) === 0) {\n          break;\n        }\n      }\n    }\n    return (this.pos - startpos);\n\n  case 3:  // We started with *** or ___\n    while (true) {\n      res = this.scanDelims(c);\n      if (res.numdelims >= 1 && res.numdelims <= 3 && res.can_close &&\n            res.numdelims != first_close_delims) {\n\n        if (first_close_delims === 1 && numdelims > 2) {\n          res.numdelims = 2;\n        } else if (first_close_delims === 2) {\n          res.numdelims = 1;\n        } else if (res.numdelims === 3) {\n          // If we opened with ***, then we interpret *** as ** followed by *\n          // giving us <strong><em>\n          res.numdelims = 1;\n        }\n\n        this.pos += res.numdelims;\n\n        if (first_close > 0) { // if we've already passed the first closer:\n          inlines[delimpos].t = first_close_delims === 1 ? 'Strong' : 'Emph';\n          inlines[delimpos].c = [\n             { t: first_close_delims === 1 ? 'Emph' : 'Strong',\n               c: inlines.slice(delimpos + 1, first_close)}\n            ].concat(inlines.slice(first_close + 1));\n          inlines.splice(delimpos + 1);\n          break;\n        } else {  // this is the first closer; for now, add literal string;\n                  // we'll change this when he hit the second closer\n          inlines.push({t: 'Str',\n                        c: this.subject.slice(this.pos - res.numdelims,\n                                              this.pos) });\n          first_close = inlines.length - 1;\n          first_close_delims = res.numdelims;\n        }\n      } else {  // parse another inline element, til we hit the end\n        if (this.parseInline(inlines) === 0) {\n          break;\n        }\n      }\n    }\n    return (this.pos - startpos);\n\n  default:\n    return res;\n  }\n\n  return 0;\n};\n\n// Attempt to parse link title (sans quotes), returning the string\n// or null if no match.\nvar parseLinkTitle = function() {\n  var title = this.match(reLinkTitle);\n  if (title) {\n    // chop off quotes from title and unescape:\n    return unescape(title.substr(1, title.length - 2));\n  } else {\n    return null;\n  }\n};\n\n// Attempt to parse link destination, returning the string or\n// null if no match.\nvar parseLinkDestination = function() {\n  var res = this.match(reLinkDestinationBraces);\n  if (res) {  // chop off surrounding <..>:\n    return unescape(res.substr(1, res.length - 2));\n  } else {\n    res = this.match(reLinkDestination);\n    if (res !== null) {\n      return unescape(res);\n    } else {\n      return null;\n    }\n  }\n};\n\n// Attempt to parse a link label, returning number of characters parsed.\nvar parseLinkLabel = function() {\n  if (this.peek() != '[') {\n    return 0;\n  }\n  var startpos = this.pos;\n  var nest_level = 0;\n  if (this.label_nest_level > 0) {\n    // If we've already checked to the end of this subject\n    // for a label, even with a different starting [, we\n    // know we won't find one here and we can just return.\n    // This avoids lots of backtracking.\n    // Note:  nest level 1 would be: [foo [bar]\n    //        nest level 2 would be: [foo [bar [baz]\n    this.label_nest_level--;\n    return 0;\n  }\n  this.pos++;  // advance past [\n  var c;\n  while ((c = this.peek()) && (c != ']' || nest_level > 0)) {\n    switch (c) {\n      case '`':\n        this.parseBackticks([]);\n        break;\n      case '<':\n        this.parseAutolink([]) || this.parseHtmlTag([]) || this.parseString([]);\n        break;\n      case '[':  // nested []\n        nest_level++;\n        this.pos++;\n        break;\n      case ']':  // nested []\n        nest_level--;\n        this.pos++;\n        break;\n      case '\\\\':\n        this.parseEscaped([]);\n        break;\n      default:\n        this.parseString([]);\n    }\n  }\n  if (c === ']') {\n    this.label_nest_level = 0;\n    this.pos++; // advance past ]\n    return this.pos - startpos;\n  } else {\n    if (!c) {\n      this.label_nest_level = nest_level;\n    }\n    this.pos = startpos;\n    return 0;\n  }\n};\n\n// Parse raw link label, including surrounding [], and return\n// inline contents.  (Note:  this is not a method of InlineParser.)\nvar parseRawLabel = function(s) {\n  // note:  parse without a refmap; we don't want links to resolve\n  // in nested brackets!\n  return new InlineParser().parse(s.substr(1, s.length - 2), {});\n};\n\n// Attempt to parse a link.  If successful, add the link to\n// inlines.\nvar parseLink = function(inlines) {\n  var startpos = this.pos;\n  var reflabel;\n  var n;\n  var dest;\n  var title;\n\n  n = this.parseLinkLabel();\n  if (n === 0) {\n    return 0;\n  }\n  var afterlabel = this.pos;\n  var rawlabel = this.subject.substr(startpos, n);\n\n  // if we got this far, we've parsed a label.\n  // Try to parse an explicit link: [label](url \"title\")\n  if (this.peek() == '(') {\n    this.pos++;\n    if (this.spnl() &&\n        ((dest = this.parseLinkDestination()) !== null) &&\n        this.spnl() &&\n        // make sure there's a space before the title:\n        (/^\\s/.test(this.subject[this.pos - 1]) &&\n         (title = this.parseLinkTitle() || '') || true) &&\n        this.spnl() &&\n        this.match(/^\\)/)) {\n        inlines.push({ t: 'Link',\n                       destination: dest,\n                       title: title,\n                       label: parseRawLabel(rawlabel) });\n        return this.pos - startpos;\n     } else {\n        this.pos = startpos;\n        return 0;\n     }\n  }\n  // If we're here, it wasn't an explicit link. Try to parse a reference link.\n  // first, see if there's another label\n  var savepos = this.pos;\n  this.spnl();\n  var beforelabel = this.pos;\n  n = this.parseLinkLabel();\n  if (n == 2) {\n    // empty second label\n    reflabel = rawlabel;\n  } else if (n > 0) {\n    reflabel = this.subject.slice(beforelabel, beforelabel + n);\n  } else {\n    this.pos = savepos;\n    reflabel = rawlabel;\n  }\n  // lookup rawlabel in refmap\n  var link = this.refmap[normalizeReference(reflabel)];\n  if (link) {\n    inlines.push({t: 'Link',\n                  destination: link.destination,\n                  title: link.title,\n                  label: parseRawLabel(rawlabel) });\n    return this.pos - startpos;\n  } else {\n    this.pos = startpos;\n    return 0;\n  }\n  // Nothing worked, rewind:\n  this.pos = startpos;\n  return 0;\n};\n\n// Attempt to parse an entity, adding to inlines if successful.\nvar parseEntity = function(inlines) {\n  var m;\n  if ((m = this.match(/^&(?:#x[a-f0-9]{1,8}|#[0-9]{1,8}|[a-z][a-z0-9]{1,31});/i))) {\n      inlines.push({ t: 'Entity', c: m });\n      return m.length;\n  } else {\n      return  0;\n  }\n};\n\n// Parse a run of ordinary characters, or a single character with\n// a special meaning in markdown, as a plain string, adding to inlines.\nvar parseString = function(inlines) {\n  var m;\n  if ((m = this.match(reMain))) {\n    inlines.push({ t: 'Str', c: m });\n    return m.length;\n  } else {\n    return 0;\n  }\n};\n\n// Parse a newline.  If it was preceded by two spaces, return a hard\n// line break; otherwise a soft line break.\nvar parseNewline = function(inlines) {\n  if (this.peek() == '\\n') {\n    this.pos++;\n    var last = inlines[inlines.length - 1];\n    if (last && last.t == 'Str' && last.c.slice(-2) == '  ') {\n      last.c = last.c.replace(/ *$/,'');\n      inlines.push({ t: 'Hardbreak' });\n    } else {\n      if (last && last.t == 'Str' && last.c.slice(-1) == ' ') {\n        last.c = last.c.slice(0, -1);\n      }\n      inlines.push({ t: 'Softbreak' });\n    }\n    return 1;\n  } else {\n    return 0;\n  }\n};\n\n// Attempt to parse an image.  If the opening '!' is not followed\n// by a link, add a literal '!' to inlines.\nvar parseImage = function(inlines) {\n  if (this.match(/^!/)) {\n    var n = this.parseLink(inlines);\n    if (n === 0) {\n      inlines.push({ t: 'Str', c: '!' });\n      return 1;\n    } else if (inlines[inlines.length - 1] &&\n               inlines[inlines.length - 1].t == 'Link') {\n      inlines[inlines.length - 1].t = 'Image';\n      return n+1;\n    } else {\n      throw \"Shouldn't happen\";\n    }\n  } else {\n    return 0;\n  }\n};\n\n// Attempt to parse a link reference, modifying refmap.\nvar parseReference = function(s, refmap) {\n  this.subject = s;\n  this.pos = 0;\n  var rawlabel;\n  var dest;\n  var title;\n  var matchChars;\n  var startpos = this.pos;\n  var match;\n\n  // label:\n  matchChars = this.parseLinkLabel();\n  if (matchChars === 0) {\n    return 0;\n  } else {\n    rawlabel = this.subject.substr(0, matchChars);\n  }\n\n  // colon:\n  if (this.peek() === ':') {\n    this.pos++;\n  } else {\n    this.pos = startpos;\n    return 0;\n  }\n\n  //  link url\n  this.spnl();\n\n  dest = this.parseLinkDestination();\n  if (dest === null || dest.length === 0) {\n    this.pos = startpos;\n    return 0;\n  }\n\n  var beforetitle = this.pos;\n  this.spnl();\n  title = this.parseLinkTitle();\n  if (title === null) {\n    title = '';\n    // rewind before spaces\n    this.pos = beforetitle;\n  }\n\n  // make sure we're at line end:\n  if (this.match(/^ *(?:\\n|$)/) === null) {\n    this.pos = startpos;\n    return 0;\n  }\n\n  var normlabel = normalizeReference(rawlabel);\n\n  if (!refmap[normlabel]) {\n    refmap[normlabel] = { destination: dest, title: title };\n  }\n  return this.pos - startpos;\n};\n\n// Parse the next inline element in subject, advancing subject position\n// and adding the result to 'inlines'.\nvar parseInline = function(inlines) {\n  var c = this.peek();\n  var res;\n  switch(c) {\n  case '\\n':\n    res = this.parseNewline(inlines);\n    break;\n  case '\\\\':\n    res = this.parseEscaped(inlines);\n    break;\n  case '`':\n    res = this.parseBackticks(inlines);\n    break;\n  case '*':\n  case '_':\n    res = this.parseEmphasis(inlines);\n    break;\n  case '[':\n    res = this.parseLink(inlines);\n    break;\n  case '!':\n    res = this.parseImage(inlines);\n    break;\n  case '<':\n    res = this.parseAutolink(inlines) ||\n      this.parseHtmlTag(inlines);\n    break;\n  case '&':\n    res = this.parseEntity(inlines);\n    break;\n  default:\n  }\n  return res || this.parseString(inlines);\n};\n\n// Parse s as a list of inlines, using refmap to resolve references.\nvar parseInlines = function(s, refmap) {\n  this.subject = s;\n  this.pos = 0;\n  this.refmap = refmap || {};\n  var inlines = [];\n  while (this.parseInline(inlines)) ;\n  return inlines;\n};\n\n// The InlineParser object.\nfunction InlineParser(){\n  return {\n    subject: '',\n    label_nest_level: 0, // used by parseLinkLabel method\n    pos: 0,\n    refmap: {},\n    match: match,\n    peek: peek,\n    spnl: spnl,\n    parseBackticks: parseBackticks,\n    parseEscaped: parseEscaped,\n    parseAutolink: parseAutolink,\n    parseHtmlTag: parseHtmlTag,\n    scanDelims: scanDelims,\n    parseEmphasis: parseEmphasis,\n    parseLinkTitle: parseLinkTitle,\n    parseLinkDestination: parseLinkDestination,\n    parseLinkLabel: parseLinkLabel,\n    parseLink: parseLink,\n    parseEntity: parseEntity,\n    parseString: parseString,\n    parseNewline: parseNewline,\n    parseImage: parseImage,\n    parseReference: parseReference,\n    parseInline: parseInline,\n    parse: parseInlines\n  };\n}\n\n// DOC PARSER\n\n// These are methods of a DocParser object, defined below.\n\nvar makeBlock = function(tag, start_line, start_column) {\n  return { t: tag,\n           open: true,\n           last_line_blank: false,\n           start_line: start_line,\n           start_column: start_column,\n           end_line: start_line,\n           children: [],\n           parent: null,\n           // string_content is formed by concatenating strings, in finalize:\n           string_content: \"\",\n           strings: [],\n           inline_content: []\n        };\n};\n\n// Returns true if parent block can contain child block.\nvar canContain = function(parent_type, child_type) {\n  return ( parent_type == 'Document' ||\n           parent_type == 'BlockQuote' ||\n           parent_type == 'ListItem' ||\n           (parent_type == 'List' && child_type == 'ListItem') );\n};\n\n// Returns true if block type can accept lines of text.\nvar acceptsLines = function(block_type) {\n  return ( block_type == 'Paragraph' ||\n           block_type == 'IndentedCode' ||\n           block_type == 'FencedCode' );\n};\n\n// Returns true if block ends with a blank line, descending if needed\n// into lists and sublists.\nvar endsWithBlankLine = function(block) {\n  if (block.last_line_blank) {\n    return true;\n  }\n  if ((block.t == 'List' || block.t == 'ListItem') && block.children.length > 0) {\n    return endsWithBlankLine(block.children[block.children.length - 1]);\n  } else {\n    return false;\n  }\n};\n\n// Break out of all containing lists, resetting the tip of the\n// document to the parent of the highest list, and finalizing\n// all the lists.  (This is used to implement the \"two blank lines\n// break of of all lists\" feature.)\nvar breakOutOfLists = function(block, line_number) {\n  var b = block;\n  var last_list = null;\n  do {\n    if (b.t === 'List') {\n      last_list = b;\n    }\n    b = b.parent;\n  } while (b);\n\n  if (last_list) {\n    while (block != last_list) {\n      this.finalize(block, line_number);\n      block = block.parent;\n    }\n    this.finalize(last_list, line_number);\n    this.tip = last_list.parent;\n  }\n};\n\n// Add a line to the block at the tip.  We assume the tip\n// can accept lines -- that check should be done before calling this.\nvar addLine = function(ln, offset) {\n  var s = ln.slice(offset);\n  if (!(this.tip.open)) {\n    throw({ msg: \"Attempted to add line (\" + ln + \") to closed container.\" });\n  }\n  this.tip.strings.push(s);\n};\n\n// Add block of type tag as a child of the tip.  If the tip can't\n// accept children, close and finalize it and try its parent,\n// and so on til we find a block that can accept children.\nvar addChild = function(tag, line_number, offset) {\n  while (!canContain(this.tip.t, tag)) {\n    this.finalize(this.tip, line_number);\n  }\n\n  var column_number = offset + 1; // offset 0 = column 1\n  var newBlock = makeBlock(tag, line_number, column_number);\n  this.tip.children.push(newBlock);\n  newBlock.parent = this.tip;\n  this.tip = newBlock;\n  return newBlock;\n};\n\n// Parse a list marker and return data on the marker (type,\n// start, delimiter, bullet character, padding) or null.\nvar parseListMarker = function(ln, offset) {\n  var rest = ln.slice(offset);\n  var match;\n  var spaces_after_marker;\n  var data = {};\n  if (rest.match(reHrule)) {\n    return null;\n  }\n  if ((match = rest.match(/^[*+-]( +|$)/))) {\n    spaces_after_marker = match[1].length;\n    data.type = 'Bullet';\n    data.bullet_char = match[0][0];\n\n  } else if ((match = rest.match(/^(\\d+)([.)])( +|$)/))) {\n    spaces_after_marker = match[3].length;\n    data.type = 'Ordered';\n    data.start = parseInt(match[1]);\n    data.delimiter = match[2];\n  } else {\n    return null;\n  }\n  var blank_item = match[0].length === rest.length;\n  if (spaces_after_marker >= 5 ||\n      spaces_after_marker < 1 ||\n      blank_item) {\n        data.padding = match[0].length - spaces_after_marker + 1;\n  } else {\n        data.padding = match[0].length;\n  }\n  return data;\n};\n\n// Returns true if the two list items are of the same type,\n// with the same delimiter and bullet character.  This is used\n// in agglomerating list items into lists.\nvar listsMatch = function(list_data, item_data) {\n  return (list_data.type === item_data.type &&\n          list_data.delimiter === item_data.delimiter &&\n          list_data.bullet_char === item_data.bullet_char);\n};\n\n// Analyze a line of text and update the document appropriately.\n// We parse markdown text by calling this on each line of input,\n// then finalizing the document.\nvar incorporateLine = function(ln, line_number) {\n\n  var all_matched = true;\n  var last_child;\n  var first_nonspace;\n  var offset = 0;\n  var match;\n  var data;\n  var blank;\n  var indent;\n  var last_matched_container;\n  var i;\n  var CODE_INDENT = 4;\n\n  var container = this.doc;\n  var oldtip = this.tip;\n\n  // Convert tabs to spaces:\n  ln = detabLine(ln);\n\n  // For each containing block, try to parse the associated line start.\n  // Bail out on failure: container will point to the last matching block.\n  // Set all_matched to false if not all containers match.\n  while (container.children.length > 0) {\n    last_child = container.children[container.children.length - 1];\n    if (!last_child.open) {\n      break;\n    }\n    container = last_child;\n\n    match = matchAt(/[^ ]/, ln, offset);\n    if (match === null) {\n      first_nonspace = ln.length;\n      blank = true;\n    } else {\n      first_nonspace = match;\n      blank = false;\n    }\n    indent = first_nonspace - offset;\n\n    switch (container.t) {\n      case 'BlockQuote':\n        var matched = indent <= 3 && ln[first_nonspace] === '>';\n        if (matched) {\n          offset = first_nonspace + 1;\n          if (ln[offset] === ' ') {\n            offset++;\n          }\n        } else {\n          all_matched = false;\n        }\n        break;\n\n      case 'ListItem':\n        if (indent >= container.list_data.marker_offset +\n                      container.list_data.padding) {\n          offset += container.list_data.marker_offset +\n                    container.list_data.padding;\n        } else if (blank) {\n          offset = first_nonspace;\n        } else {\n          all_matched = false;\n        }\n        break;\n\n      case 'IndentedCode':\n        if (indent >= CODE_INDENT) {\n          offset += CODE_INDENT;\n        } else if (blank) {\n          offset = first_nonspace;\n        } else {\n          all_matched = false;\n        }\n        break;\n\n      case 'ATXHeader':\n      case 'SetextHeader':\n      case 'HorizontalRule':\n        // a header can never container > 1 line, so fail to match:\n        all_matched = false;\n        break;\n\n      case 'FencedCode':\n        // skip optional spaces of fence offset\n        i = container.fence_offset;\n        while (i > 0 && ln[offset] === ' ') {\n          offset++;\n          i--;\n        }\n        break;\n\n      case 'HtmlBlock':\n        if (blank) {\n          all_matched = false;\n        }\n        break;\n\n      case 'Paragraph':\n        if (blank) {\n          container.last_line_blank = true;\n          all_matched = false;\n        }\n        break;\n\n      default:\n    }\n\n    if (!all_matched) {\n      container = container.parent; // back up to last matching block\n      break;\n    }\n  }\n\n  last_matched_container = container;\n\n  // This function is used to finalize and close any unmatched\n  // blocks.  We aren't ready to do this now, because we might\n  // have a lazy paragraph continuation, in which case we don't\n  // want to close unmatched blocks.  So we store this closure for\n  // use later, when we have more information.\n  var closeUnmatchedBlocks = function(mythis) {\n    // finalize any blocks not matched\n    while (!already_done && oldtip != last_matched_container) {\n      mythis.finalize(oldtip, line_number);\n      oldtip = oldtip.parent;\n    }\n    var already_done = true;\n  };\n\n  // Check to see if we've hit 2nd blank line; if so break out of list:\n  if (blank && container.last_line_blank) {\n    this.breakOutOfLists(container, line_number);\n  }\n\n  // Unless last matched container is a code block, try new container starts,\n  // adding children to the last matched container:\n  while (container.t != 'FencedCode' &&\n         container.t != 'IndentedCode' &&\n         container.t != 'HtmlBlock' &&\n         // this is a little performance optimization:\n         matchAt(/^[ #`~*+_=<>0-9-]/,ln,offset) !== null) {\n\n    match = matchAt(/[^ ]/, ln, offset);\n    if (match === null) {\n      first_nonspace = ln.length;\n      blank = true;\n    } else {\n      first_nonspace = match;\n      blank = false;\n    }\n    indent = first_nonspace - offset;\n\n    if (indent >= CODE_INDENT) {\n      // indented code\n      if (this.tip.t != 'Paragraph' && !blank) {\n        offset += CODE_INDENT;\n        closeUnmatchedBlocks(this);\n        container = this.addChild('IndentedCode', line_number, offset);\n      } else { // indent > 4 in a lazy paragraph continuation\n        break;\n      }\n\n    } else if (ln[first_nonspace] === '>') {\n      // blockquote\n      offset = first_nonspace + 1;\n      // optional following space\n      if (ln[offset] === ' ') {\n        offset++;\n      }\n      closeUnmatchedBlocks(this);\n      container = this.addChild('BlockQuote', line_number, offset);\n\n    } else if ((match = ln.slice(first_nonspace).match(/^#{1,6}(?: +|$)/))) {\n      // ATX header\n      offset = first_nonspace + match[0].length;\n      closeUnmatchedBlocks(this);\n      container = this.addChild('ATXHeader', line_number, first_nonspace);\n      container.level = match[0].trim().length; // number of #s\n      // remove trailing ###s:\n      container.strings =\n            [ln.slice(offset).replace(/(?:(\\\\#) *#*| *#+) *$/,'$1')];\n      break;\n\n    } else if ((match = ln.slice(first_nonspace).match(/^`{3,}(?!.*`)|^~{3,}(?!.*~)/))) {\n      // fenced code block\n      var fence_length = match[0].length;\n      closeUnmatchedBlocks(this);\n      container = this.addChild('FencedCode', line_number, first_nonspace);\n      container.fence_length = fence_length;\n      container.fence_char = match[0][0];\n      container.fence_offset = first_nonspace - offset;\n      offset = first_nonspace + fence_length;\n      break;\n\n    } else if (matchAt(reHtmlBlockOpen, ln, first_nonspace) !== null) {\n      // html block\n      closeUnmatchedBlocks(this);\n      container = this.addChild('HtmlBlock', line_number, first_nonspace);\n      // note, we don't adjust offset because the tag is part of the text\n      break;\n\n    } else if (container.t == 'Paragraph' &&\n               container.strings.length === 1 &&\n               ((match = ln.slice(first_nonspace).match(/^(?:=+|-+) *$/)))) {\n      // setext header line\n      closeUnmatchedBlocks(this);\n      container.t = 'SetextHeader'; // convert Paragraph to SetextHeader\n      container.level = match[0][0] === '=' ? 1 : 2;\n      offset = ln.length;\n\n    } else if (matchAt(reHrule, ln, first_nonspace) !== null) {\n      // hrule\n      closeUnmatchedBlocks(this);\n      container = this.addChild('HorizontalRule', line_number, first_nonspace);\n      offset = ln.length - 1;\n      break;\n\n    } else if ((data = parseListMarker(ln, first_nonspace))) {\n      // list item\n      closeUnmatchedBlocks(this);\n      data.marker_offset = indent;\n      offset = first_nonspace + data.padding;\n\n      // add the list if needed\n      if (container.t !== 'List' ||\n          !(listsMatch(container.list_data, data))) {\n           container = this.addChild('List', line_number, first_nonspace);\n           container.list_data = data;\n      }\n\n      // add the list item\n      container = this.addChild('ListItem', line_number, first_nonspace);\n      container.list_data = data;\n\n    } else {\n      break;\n\n    }\n\n    if (acceptsLines(container.t)) {\n      // if it's a line container, it can't contain other containers\n      break;\n    }\n  }\n\n  // What remains at the offset is a text line.  Add the text to the\n  // appropriate container.\n\n  match = matchAt(/[^ ]/, ln, offset);\n  if (match === null) {\n    first_nonspace = ln.length;\n    blank = true;\n  } else {\n    first_nonspace = match;\n    blank = false;\n  }\n  indent = first_nonspace - offset;\n\n  // First check for a lazy paragraph continuation:\n  if (this.tip !== last_matched_container &&\n      !blank &&\n      this.tip.t == 'Paragraph' &&\n      this.tip.strings.length > 0) {\n     // lazy paragraph continuation\n\n    this.last_line_blank = false;\n    this.addLine(ln, offset);\n\n  } else { // not a lazy continuation\n\n    // finalize any blocks not matched\n    closeUnmatchedBlocks(this);\n\n    // Block quote lines are never blank as they start with >\n    // and we don't count blanks in fenced code for purposes of tight/loose\n    // lists or breaking out of lists.  We also don't set last_line_blank\n    // on an empty list item.\n    container.last_line_blank = blank &&\n      !(container.t == 'BlockQuote' ||\n        container.t == 'FencedCode' ||\n        (container.t == 'ListItem' &&\n         container.children.length === 0 &&\n         container.start_line == line_number));\n\n    var cont = container;\n    while (cont.parent) {\n      cont.parent.last_line_blank = false;\n      cont = cont.parent;\n    }\n\n    switch (container.t) {\n    case 'IndentedCode':\n    case 'HtmlBlock':\n      this.addLine(ln, offset);\n      break;\n\n    case 'FencedCode':\n      // check for closing code fence:\n      match = (indent <= 3 &&\n               ln[first_nonspace] == container.fence_char &&\n               ln.slice(first_nonspace).match(/^(?:`{3,}|~{3,})(?= *$)/));\n      if (match && match[0].length >= container.fence_length) {\n        // don't add closing fence to container; instead, close it:\n        this.finalize(container, line_number);\n      } else {\n        this.addLine(ln, offset);\n      }\n      break;\n\n    case 'ATXHeader':\n    case 'SetextHeader':\n    case 'HorizontalRule':\n      // nothing to do; we already added the contents.\n      break;\n\n    default:\n      if (acceptsLines(container.t)) {\n        this.addLine(ln, first_nonspace);\n      } else if (blank) {\n        // do nothing\n      } else if (container.t != 'HorizontalRule' &&\n                 container.t != 'SetextHeader') {\n        // create paragraph container for line\n        container = this.addChild('Paragraph', line_number, first_nonspace);\n        this.addLine(ln, first_nonspace);\n      } else {\n        console.log(\"Line \" + line_number.toString() +\n                     \" with container type \" + container.t +\n                     \" did not match any condition.\");\n\n      }\n    }\n  }\n};\n\n// Finalize a block.  Close it and do any necessary postprocessing,\n// e.g. creating string_content from strings, setting the 'tight'\n// or 'loose' status of a list, and parsing the beginnings\n// of paragraphs for reference definitions.  Reset the tip to the\n// parent of the closed block.\nvar finalize = function(block, line_number) {\n  var pos;\n  // don't do anything if the block is already closed\n  if (!block.open) {\n    return 0;\n  }\n  block.open = false;\n  if (line_number > block.start_line) {\n    block.end_line = line_number - 1;\n  } else {\n    block.end_line = line_number;\n  }\n\n  switch (block.t) {\n  case 'Paragraph':\n    block.string_content = block.strings.join('\\n').replace(/^  */m,'');\n\n    // try parsing the beginning as link reference definitions:\n    while (block.string_content[0] === '[' &&\n           (pos = this.inlineParser.parseReference(block.string_content,\n                                                   this.refmap))) {\n      block.string_content = block.string_content.slice(pos);\n      if (isBlank(block.string_content)) {\n        block.t = 'ReferenceDef';\n        break;\n      }\n    }\n    break;\n\n  case 'ATXHeader':\n  case 'SetextHeader':\n  case 'HtmlBlock':\n    block.string_content = block.strings.join('\\n');\n    break;\n\n  case 'IndentedCode':\n    block.string_content = block.strings.join('\\n').replace(/(\\n *)*$/,'\\n');\n    break;\n\n  case 'FencedCode':\n    // first line becomes info string\n    block.info = unescape(block.strings[0].trim());\n    if (block.strings.length == 1) {\n      block.string_content = '';\n    } else {\n      block.string_content = block.strings.slice(1).join('\\n') + '\\n';\n    }\n    break;\n\n  case 'List':\n    block.tight = true; // tight by default\n\n    var numitems = block.children.length;\n    var i = 0;\n    while (i < numitems) {\n      var item = block.children[i];\n      // check for non-final list item ending with blank line:\n      var last_item = i == numitems - 1;\n      if (endsWithBlankLine(item) && !last_item) {\n        block.tight = false;\n        break;\n      }\n      // recurse into children of list item, to see if there are\n      // spaces between any of them:\n      var numsubitems = item.children.length;\n      var j = 0;\n      while (j < numsubitems) {\n        var subitem = item.children[j];\n        var last_subitem = j == numsubitems - 1;\n        if (endsWithBlankLine(subitem) && !(last_item && last_subitem)) {\n          block.tight = false;\n          break;\n        }\n        j++;\n      }\n      i++;\n    }\n    break;\n\n  default:\n    break;\n  }\n\n  this.tip = block.parent || this.top;\n};\n\n// Walk through a block & children recursively, parsing string content\n// into inline content where appropriate.\nvar processInlines = function(block) {\n  switch(block.t) {\n    case 'Paragraph':\n    case 'SetextHeader':\n    case 'ATXHeader':\n      block.inline_content =\n        this.inlineParser.parse(block.string_content.trim(), this.refmap);\n      block.string_content = \"\";\n      break;\n    default:\n      break;\n  }\n\n  if (block.children) {\n    for (var i = 0; i < block.children.length; i++) {\n      this.processInlines(block.children[i]);\n    }\n  }\n\n};\n\n// The main parsing function.  Returns a parsed document AST.\nvar parse = function(input) {\n  this.doc = makeBlock('Document', 1, 1);\n  this.tip = this.doc;\n  this.refmap = {};\n  var lines = input.replace(/\\n$/,'').split(/\\r\\n|\\n|\\r/);\n  var len = lines.length;\n  for (var i = 0; i < len; i++) {\n    this.incorporateLine(lines[i], i+1);\n  }\n  while (this.tip) {\n    this.finalize(this.tip, len - 1);\n  }\n  this.processInlines(this.doc);\n  return this.doc;\n};\n\n\n// The DocParser object.\nfunction DocParser(){\n  return {\n    doc: makeBlock('Document', 1, 1),\n    tip: this.doc,\n    refmap: {},\n    inlineParser: new InlineParser(),\n    breakOutOfLists: breakOutOfLists,\n    addLine: addLine,\n    addChild: addChild,\n    incorporateLine: incorporateLine,\n    finalize: finalize,\n    processInlines: processInlines,\n    parse: parse\n  };\n}\n\n// HTML RENDERER\n\n// Helper function to produce content in a pair of HTML tags.\nvar inTags = function(tag, attribs, contents, selfclosing) {\n  var result = '<' + tag;\n  if (attribs) {\n    var i = 0;\n    var attrib;\n    while ((attrib = attribs[i]) !== undefined) {\n      result = result.concat(' ', attrib[0], '=\"', attrib[1], '\"');\n      i++;\n    }\n  }\n  if (contents) {\n    result = result.concat('>', contents, '</', tag, '>');\n  } else if (selfclosing) {\n    result = result + ' />';\n  } else {\n    result = result.concat('></', tag, '>');\n  }\n  return result;\n};\n\n// Render an inline element as HTML.\nvar renderInline = function(inline) {\n  var attrs;\n  switch (inline.t) {\n    case 'Str':\n      return this.escape(inline.c);\n    case 'Softbreak':\n      return this.softbreak;\n    case 'Hardbreak':\n      return inTags('br',[],\"\",true) + '\\n';\n    case 'Emph':\n      return inTags('em', [], this.renderInlines(inline.c));\n    case 'Strong':\n      return inTags('strong', [], this.renderInlines(inline.c));\n    case 'Html':\n      return inline.c;\n    case 'Entity':\n      return inline.c;\n    case 'Link':\n      attrs = [['href', this.escape(inline.destination, true)]];\n      if (inline.title) {\n        attrs.push(['title', this.escape(inline.title, true)]);\n      }\n      return inTags('a', attrs, this.renderInlines(inline.label));\n    case 'Image':\n      attrs = [['src', this.escape(inline.destination, true)],\n                   ['alt', this.escape(this.renderInlines(inline.label))]];\n      if (inline.title) {\n        attrs.push(['title', this.escape(inline.title, true)]);\n      }\n      return inTags('img', attrs, \"\", true);\n    case 'Code':\n      return inTags('code', [], this.escape(inline.c));\n    default:\n      console.log(\"Uknown inline type \" + inline.t);\n      return \"\";\n  }\n};\n\n// Render a list of inlines.\nvar renderInlines = function(inlines) {\n  var result = '';\n  for (var i=0; i < inlines.length; i++) {\n    result = result + this.renderInline(inlines[i]);\n  }\n  return result;\n};\n\n// Render a single block element.\nvar renderBlock = function(block, in_tight_list) {\n  var tag;\n  var attr;\n  var info_words;\n  switch (block.t) {\n    case 'Document':\n      var whole_doc = this.renderBlocks(block.children);\n      return (whole_doc === '' ? '' : whole_doc + '\\n');\n    case 'Paragraph':\n      if (in_tight_list) {\n        return this.renderInlines(block.inline_content);\n      } else {\n        return inTags('p', [], this.renderInlines(block.inline_content));\n      }\n      break;\n    case 'BlockQuote':\n      var filling = this.renderBlocks(block.children);\n      return inTags('blockquote', [], filling === '' ? this.innersep :\n          this.innersep + this.renderBlocks(block.children) + this.innersep);\n    case 'ListItem':\n      return inTags('li', [], this.renderBlocks(block.children, in_tight_list).trim());\n    case 'List':\n      tag = block.list_data.type == 'Bullet' ? 'ul' : 'ol';\n      attr = (!block.list_data.start || block.list_data.start == 1) ?\n              [] : [['start', block.list_data.start.toString()]];\n      return inTags(tag, attr, this.innersep +\n                    this.renderBlocks(block.children, block.tight) +\n                    this.innersep);\n    case 'ATXHeader':\n    case 'SetextHeader':\n      tag = 'h' + block.level;\n      return inTags(tag, [], this.renderInlines(block.inline_content));\n    case 'IndentedCode':\n      return inTags('pre', [],\n              inTags('code', [], this.escape(block.string_content)));\n    case 'FencedCode':\n      info_words = block.info.split(/ +/);\n      attr = info_words.length === 0 || info_words[0].length === 0 ?\n                   [] : [['class','language-' +\n                                   this.escape(info_words[0],true)]];\n      return inTags('pre', [],\n              inTags('code', attr, this.escape(block.string_content)));\n    case 'HtmlBlock':\n      return block.string_content;\n    case 'ReferenceDef':\n      return \"\";\n    case 'HorizontalRule':\n      return inTags('hr',[],\"\",true);\n    default:\n      console.log(\"Uknown block type \" + block.t);\n      return \"\";\n  }\n};\n\n// Render a list of block elements, separated by this.blocksep.\nvar renderBlocks = function(blocks, in_tight_list) {\n  var result = [];\n  for (var i=0; i < blocks.length; i++) {\n    if (blocks[i].t !== 'ReferenceDef') {\n      result.push(this.renderBlock(blocks[i], in_tight_list));\n    }\n  }\n  return result.join(this.blocksep);\n};\n\n// The HtmlRenderer object.\nfunction HtmlRenderer(){\n  return {\n    // default options:\n    blocksep: '\\n',  // space between blocks\n    innersep: '\\n',  // space between block container tag and contents\n    softbreak: '\\n', // by default, soft breaks are rendered as newlines in HTML\n                     // set to \"<br />\" to make them hard breaks\n                     // set to \" \" if you want to ignore line wrapping in source\n    escape: function(s, preserve_entities) {\n      if (preserve_entities) {\n      return s.replace(/[&](?![#](x[a-f0-9]{1,8}|[0-9]{1,8});|[a-z][a-z0-9]{1,31};)/gi,'&amp;')\n              .replace(/[<]/g,'&lt;')\n              .replace(/[>]/g,'&gt;')\n              .replace(/[\"]/g,'&quot;');\n      } else {\n      return s.replace(/[&]/g,'&amp;')\n              .replace(/[<]/g,'&lt;')\n              .replace(/[>]/g,'&gt;')\n              .replace(/[\"]/g,'&quot;');\n      }\n    },\n    renderInline: renderInline,\n    renderInlines: renderInlines,\n    renderBlock: renderBlock,\n    renderBlocks: renderBlocks,\n    render: renderBlock\n  };\n}\n\nexports.DocParser = DocParser;\nexports.HtmlRenderer = HtmlRenderer;\n\n})(typeof exports === 'undefined' ? this.stmd = {} : exports);\n"
  },
  {
    "path": "web/index.html",
    "content": "<html lang=\"en\"><head>\n\t<meta charset=\"utf-8\">\n\t<title>XYscope</title>\n\t<link rel=\"stylesheet\" href=\"includes/css/styles.css\">\n\t<link rel=\"stylesheet\" href=\"includes/js/highlight/docco.css\">\n\t<script src=\"includes/js/highlight/highlight.pack.js\"></script>\n\t<style type=\"text/css\">\n\t\t#spacer{\n\t\t\tposition: relative;\n\t\t\theight: 100vh;\n\t\t}\n\t\tcanvas{\n\t\t\tposition:fixed;\n\t\t\ttop:0;\n\t\t\tleft:0;\n\t\t\tz-index:-1;\n\t\t\twidth:100vw;\n\t\t\theight:100vh;\n\t\t\tmargin:0;\n\t\t}\n\t\tcode{\n\t\t\tbackground:#fafafa;\n\t\t\tborder:1px solid #ddd;\n\t\t\tborder-radius:3px;\n\t\t\tpadding:3px;\n\t\t}\n\t</style>\n\t<script type=\"text/javascript\" src=\"https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.min.js\"></script>\n</head>\n<body>\n\t<div id=\"md\"><h1 id=\"xyscope\" name=\"xyscope\">XYscope</h1>\n<p>v 3.0.0<br>\ncc <a href=\"https://teddavis.org\" target=\"_blank\">teddavis.org</a> 2017 – 2023</p>\n<p>Processing library to render vector graphics on vector displays.</p>\n<p>XYscope converts the coordinates of primative shapes (point, line, rect, ellipse, vertex, box, sphere, torus...)to audio waveforms (oscillators with custom wavetables) which are sent to an analog display, revealing their graphics. This process of generating real-time audio from vector drawings is built upon the amazing Minim library. Vector graphics shine on a vector display and now we can view our generative works like never before! Tested on MacOS, Windows, Linux (RPi!).</p>\n<p><a href=\"http://www.teddavis.org/xyscope\" target=\"_blank\">teddavis.org/xyscope</a><br>\n<a href=\"https://github.com/ffd8/xyscope\" target=\"_blank\">github.com/ffd8/xyscope</a></p>\n<h2 id=\"table-of-contents\" name=\"table-of-contents\">Table of Contents</h2>\n<ul>\n<li><a href=\"#installation\">Installation</a></li>\n<li><a href=\"#audio-interfaces\">Audio Interfaces</a></li>\n<li><a href=\"#vector-displays\">Vector Displays</a></li>\n<li><a href=\"#getting-started\">Getting Started</a></li>\n<li><a href=\"#additive-synthesis\">Additive Synthesis</a></li>\n<li><a href=\"#references\">References</a></li>\n<li><a href=\"#extras\">Extras </a></li>\n</ul>\n<h2 id=\"installation\" name=\"installation\">Installation</h2>\n<p>Add via Processing's Library Manager:</p>\n<ul>\n<li>Sketch menu » Import Library... » Add Library... (v4 Manage Libraries...)</li>\n<li>Search for 'XYscope' and click <code>Install</code>.</li>\n<li>Search for 'Minim' and click <code>Install</code>.</li>\n</ul>\n<p>This library relies on and is infinitely thankful for <a href=\"https://github.com/ddf/Minim\" target=\"_blank\">Minim</a>!<br>\nSearch for and add the following libraries for various examples:<br>\n<a href=\"https://github.com/rikrd/geomerative\" target=\"_blank\">Geomerative</a>, <a href=\"https://github.com/atduskgreg/opencv-processing\" target=\"_blank\">OpenCV for Processing</a>, <a href=\"https://github.com/shiffman/OpenKinect-for-Processing\" target=\"_blank\">openkinect</a>, <a href=\"https://github.com/processing/processing-video\" target=\"_blank\">video</a>, <a href=\"https://github.com/Syphon/Processing\" target=\"_blank\">syphon</a></p>\n<p>Add manually, <a href=\"https://github.com/ffd8/xyscope/releases/latest\" target=\"_blank\">download latest release</a> and expand into <code>~/Processing/libraries</code>:</p>\n<h2 id=\"audio-interfaces\" name=\"audio-interfaces\">Audio Interfaces</h2>\n<p><em>&lt; $5</em><br>\nAt the very least you can use your computer's headphone jack with an <a href=\"https://duckduckgo.com/?q=1%2F8%22+to+RCA&amp;t=h_&amp;iax=images&amp;ia=images\" target=\"_blank\"><code>1/8\" to RCA</code></a> cable. However you'll soon want to get a <a href=\"https://www.expert-sleepers.co.uk/siwacompatibility.html\" target=\"_blank\">DC-Coupled</a> audio interface for a cleaner and more stable visual (not wobbling/centering constantly).</p>\n<p><em>&lt; $20</em><br>\nFor workshops I like to use some variant of the 48Khz <a href=\"https://www.delock.com/produkt/61645/merkmale.html\" target=\"_blank\">Delock 61645</a> or 96Khz <a href=\"https://www.delock.com/produkt/63926/merkmale.html\" target=\"_blank\">Delock 63926</a> – you'll find the same chip under many different brands and casings. For modern laptops, there's also a very compact 96Khz <a href=\"https://www.delock.com/produkt/66304/merkmale.html\" target=\"_blank\">Delock USB-C</a> version.</p>\n<p><em>&gt; $150+</em><br>\nMany of us in the community found the <a href=\"https://motu.com/products/motuaudio/ultralite-mk3\" target=\"_blank\">MOTU Ultralite Mk3 Hybrid</a> (or newer versions) to be an ideal audio interface. Price varies from used to new. It offers, 10-channels of DC-Coupled 196Khz output, which is useful to drive multiple oscilloscopes or RGB lasers (X, Y, R, G, B).</p>\n<h3 id=\"list\" name=\"list\">List</h3>\n<p>Check which interface is plugged in and how to reference it.</p>\n<pre><code class=\"hljs javascript\">xy.getMixerInfo(); <span class=\"hljs-comment\">// lists available audio devices</span>\n</code></pre>\n<h3 id=\"select\" name=\"select\">Select</h3>\n<p>By default XYscope uses the system settings audio-output, however we can specify a custom sound card (Digital Analog Converter – DAC) and sample rate.</p>\n<pre><code class=\"hljs javascript\">xy = <span class=\"hljs-keyword\">new</span> XYscope(<span class=\"hljs-keyword\">this</span>); <span class=\"hljs-comment\">// system settings soundcard </span>\nxy = <span class=\"hljs-keyword\">new</span> XYscope(<span class=\"hljs-keyword\">this</span>, <span class=\"hljs-string\">\"MOTU 1_2\"</span>); <span class=\"hljs-comment\">// specify a soundcard/channel to use </span>\nxy = <span class=\"hljs-keyword\">new</span> XYscope(<span class=\"hljs-keyword\">this</span>, <span class=\"hljs-number\">96000</span>); <span class=\"hljs-comment\">// custom sample rate ( &gt; finer detail if card supports) </span>\nxy = <span class=\"hljs-keyword\">new</span> XYscope(<span class=\"hljs-keyword\">this</span>, <span class=\"hljs-string\">\"MOTU 1_2\"</span>, <span class=\"hljs-number\">96000</span>); <span class=\"hljs-comment\">// custom soundcard, custom sample rate </span>\n</code></pre>\n<p>Or select a previous XYscope instance as the output for additive-synthesis:</p>\n<pre><code class=\"hljs javascript\">xy2 = <span class=\"hljs-keyword\">new</span> XYscope(<span class=\"hljs-keyword\">this</span>, xy.outXY); <span class=\"hljs-comment\">// send 2nd instance to 1st</span>\n</code></pre>\n<h3 id=\"aggregate-device\" name=\"aggregate-device\">Aggregate Device</h3>\n<p>If you have a multi-channel DAC, we need to create multiple 2ch stereo pairs, since Processing/Minim can't handel multi-channel devices. Especially useful with a great DAC like the MOTU Ultralite Mk3 Hybrid (or newer), offering 10ch of DC-Coupled outputs, so one could control 5 oscillscopes at once! Have only tested using MacOS:</p>\n<ul>\n<li>Press <code>CMD + Spacebar</code> (opens Spotlight Search)</li>\n<li>Type <code>Audio Midi Setup</code>, press <code>ENTER</code></li>\n<li>Click <code>+</code> in lower left, select <code>Create Aggregate Device</code></li>\n<li>Tick checkbox of your multi-channel audio device</li>\n<li>Click <code>Configure Speakers...</code> in lower right</li>\n<li>Set configuration to <code>Stereo</code>, select channels desired</li>\n<li>Rename to something like: <code>MOTU 1_2</code></li>\n<li>Repeat above for each stereo pair, ie, <code>MOTU 3_4</code>, <code>MOTU 5_6</code>...</li>\n<li>They can now be selected by name when initializing XYscope!</li>\n</ul>\n<h3 id=\"multi-channel-output-device\" name=\"multi-channel-output-device\">Multi-Channel Output Device</h3>\n<p>Within <code>Audio Midi Setup</code>, you can <code>Create Multi-Output Device</code> to send your output to multiple devices, ie. <a href=\"https://existential.audio/blackhole/\" target=\"_blank\">Blackhole</a> + DAC + Speakers, so you can view it <a href=\"https://www.oscilloscopemusic.com/software/oscilloscope/\" target=\"_blank\">virtually</a>, on analog device, and hear it.</p>\n<ul>\n<li>Press <code>CMD + Spacebar</code> (opens Spotlight Search)</li>\n<li>Type <code>Audio Midi Setup</code>, press <code>ENTER</code></li>\n<li>Click <code>+</code> in lower left, select <code>Create Multi-Channel Device</code></li>\n<li>Tick checkbox of best device first, then select additional</li>\n<li>Set sample rate to highest available, 48000 or 96000+</li>\n<li>Rename for clarity, ie <code>DAC + SPEAKERS</code> or <code>BLACKHOLE + SPEAKERS</code>.</li>\n</ul>\n<h2 id=\"vector-displays\" name=\"vector-displays\">Vector Displays</h2>\n<p>Now we need a vector display to see our glowing output!</p>\n<h3 id=\"virtual\" name=\"virtual\">Virtual</h3>\n<p><a href=\"https://www.oscilloscopemusic.com/software/oscilloscope/\" target=\"_blank\">Oscilloscope</a> by Hansi Raber (adopting <a href=\"http://m1el.github.io/woscope-how/\" target=\"_blank\">m1el's woscope</a> 'physical rendering') is a fantastic way to see your XYscope drawings while on the go without a physical device. You'll want to install <a href=\"https://existential.audio/blackhole/\" target=\"_blank\">Blackhole</a> (MacOS) or <a href=\"https://vb-audio.com/Cable/index.htm\" target=\"_blank\">VB-CABLE</a> (Windows) to re-route your system audio to a virtual source for Oscilloscope to render it.</p>\n<h3 id=\"analog-oscilloscope\" name=\"analog-oscilloscope\">Analog Oscilloscope</h3>\n<p>This is what we really want! They have a Cathode-ray Tube (CRT) that is the magic behind this obsession. You'll find them for ~$50 used on auction websites – be sure it has 2-channels (z-axis input is a bonus) and that they show images of a sharp working beam. You'll need a few <a href=\"https://duckduckgo.com/?q=RCA+to+BNC&amp;t=h_&amp;iar=images&amp;iax=images&amp;ia=images\" target=\"_blank\"><code>RCA to BNC</code></a> adaptors to interface with it. Have fun playing with all the knobs to put it into <code>XY Mode</code> so that the 2-channels drive the beam X/Y (Horizontal/Vertical).</p>\n<h3 id=\"x-y-monitor\" name=\"x-y-monitor\">X-Y Monitor</h3>\n<p>Similar to an analog oscilloscope, but usually has a larger display and reduced controls for X-Y (+Z) input, leaving away many of the features on an oscilloscope we won't use. They're more rare, expensive, but great if you stumble upon one. Don't confuse these with a 'vector monitor' which is used for TV broadcast and won't draw X-Y coordinates.</p>\n<h3 id=\"vectrex\" name=\"vectrex\">Vectrex</h3>\n<p>A vector-graphics video game system of the 1980s, these amazing 9\" displays can be <a href=\"http://users.sussex.ac.uk/~ad207/adweb/assets/vectrexminijackinputmod2014.pdf\" target=\"_blank\">very carefully modified</a> (<strong>CAREFUL - at own risk</strong>) to override (on-demand) the videogame control of the monitor's XYZ inputs. It's ideal to use <a href=\"https://www.youtube.com/watch?v=q3sKZA2r7qk\" target=\"_blank\">switching jacks</a> so videogames still works when cables are unplugged. You'll also want to apply the <a href=\"https://www.facebook.com/groups/vectorsynthesis/posts/832237263652516/\" target=\"_blank\">SPOT KILLER MOD</a>, but BE SURE to apply an appropriately high-voltage rated switch, so it can be toggled on and off.</p>\n<p>XYscope has a special mode when using a Vectrex for the aspect ratio. See example <code>vextrex</code> within <code>5_displays</code>, but the main notes are:</p>\n<pre><code class=\"hljs javascript\"><span class=\"hljs-comment\">/* within setup() */</span>\nxy.vectrex(<span class=\"hljs-number\">90</span>); <span class=\"hljs-comment\">// -90/90 for landscape, 0 for portrait</span>\n\n<span class=\"hljs-comment\">// optionally z-axis for blanking</span>\nxy.z(<span class=\"hljs-string\">\"MOTU 3-4\"</span>); <span class=\"hljs-comment\">// use custom 3rd channel for z-axis</span>\n<span class=\"hljs-comment\">//xy.zRange(.5, 0); // set min/max values for beam on/off</span>\n</code></pre>\n<h3 id=\"laser\" name=\"laser\">Laser</h3>\n<p>Once you want something bigger than most screens, you'll want to move to an RGB Laser. They're BIG and BRIGHT, but also much slower and more dangerous! It's slower because it mechanically moves galvos/mirrors for the X-Y and dangerous, because, LASERS! Nevertheless, they can be controlled via the ILDA analog input, for which I've developed an easy to build <a href=\"https://github.com/ffd8/dac_ilda\" target=\"_blank\">dac_ilda adaptor</a>. To control a laser, you'll need a DAC (sound card) with a minimum of 5-channels, for sending <code>X, Y, R, G, B</code> signals. See example <code>laser</code> within <code>5_displays</code>, but the main notes are:</p>\n<pre><code class=\"hljs javascript\"><span class=\"hljs-comment\">/* within setup() */</span>\n<span class=\"hljs-comment\">// only stereo pairs in processing, so it's broken to R, GB</span>\n<span class=\"hljs-comment\">// incase 2nd channel of R pair is useful for blanking/etc.</span>\nxy.laser(<span class=\"hljs-string\">\"MOTU 3-4\"</span>, <span class=\"hljs-string\">\"MOTU 5-6\"</span>); <span class=\"hljs-comment\">//xy.laser(mixerR, mixerGB);</span>\n\n<span class=\"hljs-comment\">/* within setup() or draw() */</span>\n<span class=\"hljs-comment\">// RGB waveforms have own freq, so we can them out of sync</span>\nxy.strokeFreq(<span class=\"hljs-number\">50.05</span>, <span class=\"hljs-number\">50.1</span>, <span class=\"hljs-number\">50.2</span>);\n  \nxy.strokeDash(<span class=\"hljs-number\">8</span>); <span class=\"hljs-comment\">// optional dashes in the RGB stroke</span>\n\nxy.stroke(<span class=\"hljs-number\">0</span>, <span class=\"hljs-number\">255</span>, <span class=\"hljs-number\">255</span>); <span class=\"hljs-comment\">// set RGB stroke before shapes (0-255)</span>\n\nxy.limitPath(<span class=\"hljs-number\">0</span>); <span class=\"hljs-comment\">// avoid drawing any forms beyond view</span>\n</code></pre>\n<h2 id=\"getting-started\" name=\"getting-started\">Getting Started</h2>\n<p>Our basic template for an XYscope sketch involves:</p>\n<pre><code class=\"hljs javascript\"><span class=\"hljs-keyword\">import</span> ddf.minim.*; <span class=\"hljs-comment\">// minim req to gen audio</span>\n<span class=\"hljs-keyword\">import</span> xyscope.*;   <span class=\"hljs-comment\">// import XYscope</span>\nXYscope xy;         <span class=\"hljs-comment\">// create XYscope instance</span>\n \n<span class=\"hljs-keyword\">void</span> <span class=\"hljs-keyword\">set</span><span class=\"hljs-title\">up</span>(){ \n  size(<span class=\"hljs-number\">512</span>, <span class=\"hljs-number\">512</span>); <span class=\"hljs-comment\">// window size, optionally add P3D</span>\n \n  xy = <span class=\"hljs-keyword\">new</span> XYscope(<span class=\"hljs-keyword\">this</span>, <span class=\"hljs-string\">\"\"</span>); <span class=\"hljs-comment\">// define XYscope instance, \"custom_dac\" optional</span>\n  xy.getMixerInfo(); <span class=\"hljs-comment\">// lists all audio devices</span>\n} \n \n<span class=\"hljs-keyword\">void</span> draw(){ \n  background(<span class=\"hljs-number\">0</span>); \n  xy.clearWaves();  <span class=\"hljs-comment\">// clear waves from previous drawing</span>\n  \n  <span class=\"hljs-comment\">// draw shapes here</span>\n  xy.circle(width/<span class=\"hljs-number\">2</span>, height/<span class=\"hljs-number\">2</span>, width); <span class=\"hljs-comment\">// it all began with a circle....</span>\n  \n  xy.buildWaves(); <span class=\"hljs-comment\">// build shapes to audio waves</span>\n  xy.drawAll();\n} \n</code></pre>\n<h2 id=\"additive-synthesis\" name=\"additive-synthesis\">Additive Synthesis</h2>\n<p>Something very unique to this workflow is sending multiple audio signals to the oscilloscope, which interfer and modulate one another. As these waves combine, their amp and freq determine its influence on other waves. Key is having different frequencies and amplitudes to modulate off one another. The lower the frequency, the more it will push other waves around. The lower the amp, the less influence it has on the additive waveform. We can simply open two sketches, both being sent to the same sound card to see this in action, however we can also achieve it within a single sketch.</p>\n<pre><code class=\"hljs javascript\"><span class=\"hljs-keyword\">import</span> ddf.minim.*; <span class=\"hljs-comment\">// minim req to gen audio</span>\n<span class=\"hljs-keyword\">import</span> xyscope.*;   <span class=\"hljs-comment\">// import XYscope</span>\nXYscope xy, xy2;    <span class=\"hljs-comment\">// create 2x XYscope instances</span>\n\n<span class=\"hljs-keyword\">void</span> <span class=\"hljs-keyword\">set</span><span class=\"hljs-title\">up</span>() {\n  size(<span class=\"hljs-number\">512</span>, <span class=\"hljs-number\">512</span>); <span class=\"hljs-comment\">// declares size of output window</span>\n  xy = <span class=\"hljs-keyword\">new</span> XYscope(<span class=\"hljs-keyword\">this</span>);\n  xy2 = <span class=\"hljs-keyword\">new</span> XYscope(<span class=\"hljs-keyword\">this</span>, xy.outXY); <span class=\"hljs-comment\">// patch 2nd instance to 1st output</span>\n}\n\n<span class=\"hljs-keyword\">void</span> draw() {\n  background(<span class=\"hljs-number\">0</span>);\n  <span class=\"hljs-comment\">// set main shapes</span>\n  xy.clearWaves();\n  xy.circle(width/<span class=\"hljs-number\">2</span>, height/<span class=\"hljs-number\">2</span>, width/<span class=\"hljs-number\">2</span>);\n  xy.buildWaves();\n  xy.drawPath(<span class=\"hljs-number\">0</span>, <span class=\"hljs-number\">255</span>, <span class=\"hljs-number\">255</span>); <span class=\"hljs-comment\">// custom stroke color</span>\n  xy.drawXY(); <span class=\"hljs-comment\">// displays combined output</span>\n\n  <span class=\"hljs-comment\">// additive synth on 2nd instance of XYscope</span>\n  xy2.clearWaves();\n  xy2.freq(mouseX); <span class=\"hljs-comment\">// without speakers, test really high freqs!</span>\n  <span class=\"hljs-comment\">//xy2.amp(.2); // experiment with high freq, low amp modulation</span>\n  xy2.circle(width/<span class=\"hljs-number\">2</span>, height/<span class=\"hljs-number\">2</span>, mouseY);\n  xy2.buildWaves();\n  xy2.drawPath(<span class=\"hljs-number\">255</span>, <span class=\"hljs-number\">255</span>, <span class=\"hljs-number\">0</span>); <span class=\"hljs-comment\">// custom stroke color</span>\n}\n</code></pre>\n<p>Additional tips:</p>\n<ul>\n<li>No need to stop at just 2, add as many as needed!</li>\n<li>Ratio between freqs is crucial, diff of +/- .1 animates things.</li>\n<li>Really low frequencies animate shapes over that path.</li>\n<li>Really high frequencies display shape made of 2nd shape.</li>\n<li>Play with position of 2nd shape, from center to corners.</li>\n</ul>\n<h2 id=\"references\" name=\"references\">References</h2>\n<p>XYscope is a class, so after an instance has been defined to a variable, we'll use that prefix in front of every function listed below, ie: <code>xy.ellipse()</code>. This enables us to have multiple XYscope instances running parallel, which will reveal wild and crazy audio/visuals. All examples below use <code>xy</code> as the instance prefix.</p>\n<ul>\n<li><a href=\"#initialize-xyscope\">Initialize XYscope</a></li>\n<li><a href=\"#z-axis\">Z-axis</a>\n<ul>\n<li><a href=\"#z()\">z()</a></li>\n<li><a href=\"#zauto()\">zAuto()</a></li>\n</ul></li>\n<li><a href=\"#audio-interface-settings\">Audio Interface Settings</a>\n<ul>\n<li><a href=\"#getmixerinfo()\">getMixerInfo()</a></li>\n<li><a href=\"#samplerate()\">sampleRate()</a></li>\n<li><a href=\"#buffersize()\">bufferSize()</a></li>\n</ul></li>\n<li><a href=\"#waves\">Waves</a>\n<ul>\n<li><a href=\"#resetwaves()\">resetWaves()</a></li>\n<li><a href=\"#clearwaves()\">clearWaves()</a></li>\n<li><a href=\"#buildwaves()\">buildWaves()</a></li>\n<li><a href=\"#buildx()\">buildX()</a></li>\n<li><a href=\"#buildy()\">buildY()</a></li>\n<li><a href=\"#buildz()\">buildZ()</a></li>\n<li><a href=\"#wavesize()\">waveSize()</a></li>\n<li><a href=\"#setwaveforms()\">setWaveforms()</a></li>\n</ul></li>\n<li><a href=\"#points\">Points</a>\n<ul>\n<li><a href=\"#wavepoints()\">wavePoints()</a></li>\n<li><a href=\"#steps()\">steps()</a></li>\n<li><a href=\"#limitpoints()\">limitPoints()</a></li>\n<li><a href=\"#limitpath()\">limitPath()</a></li>\n</ul></li>\n<li><a href=\"#record-audio\">Record Audio</a>\n<ul>\n<li><a href=\"#recorderbegin()\">recorderBegin()</a></li>\n<li><a href=\"#recorderend()\">recorderEnd()</a></li>\n</ul></li>\n<li><a href=\"#primitive-shapes\">Primitive Shapes</a>\n<ul>\n<li><a href=\"#point()\">point()</a></li>\n<li><a href=\"#line()\">line()</a></li>\n<li><a href=\"#square()\">square()</a></li>\n<li><a href=\"#rect()\">rect()</a></li>\n<li><a href=\"#circle()\">circle()</a></li>\n<li><a href=\"#ellipse()\">ellipse()</a></li>\n<li><a href=\"#complex-shape\">complex shape</a></li>\n<li><a href=\"#lissajous()\">lissajous()</a></li>\n<li><a href=\"#box()\">box()</a></li>\n<li><a href=\"#sphere()\">sphere()</a></li>\n<li><a href=\"#ellipsoid()\">ellipsoid()</a></li>\n<li><a href=\"#torus()\">torus()</a></li>\n</ul></li>\n<li><a href=\"#text\">Text</a>\n<ul>\n<li><a href=\"#text()\">text()</a></li>\n<li><a href=\"#textpaths()\">textPaths()</a></li>\n<li><a href=\"#textfont()\">textFont()</a></li>\n<li><a href=\"#textsize()\">textSize()</a></li>\n<li><a href=\"#textleading()\">textLeading()</a></li>\n<li><a href=\"#textalign()\">textAlign()</a></li>\n<li><a href=\"#textwidth()\">textWidth()</a></li>\n</ul></li>\n<li><a href=\"#modulation\">Modulation</a>\n<ul>\n<li><a href=\"#freq()\">freq()</a></li>\n<li><a href=\"#amp()\">amp()</a></li>\n<li><a href=\"#pan()\">pan()</a></li>\n</ul></li>\n<li><a href=\"#vectrex\">Vectrex</a></li>\n<li><a href=\"#laser\">Laser</a></li>\n<li><a href=\"#rendering\">Rendering</a></li>\n<li><a href=\"#vars\">Vars</a></li>\n</ul>\n<hr>\n<h3 id=\"initialize-xyscope\" name=\"initialize-xyscope\">Initialize XYscope</h3>\n<pre><code class=\"hljs javascript\">xy = <span class=\"hljs-keyword\">new</span> XYscope(<span class=\"hljs-keyword\">this</span>); <span class=\"hljs-comment\">// system audio settings</span>\nxy = <span class=\"hljs-keyword\">new</span> XYscope(<span class=\"hljs-keyword\">this</span>, <span class=\"hljs-string\">\"custom_dac\"</span>); <span class=\"hljs-comment\">// custom audio card</span>\nxy = <span class=\"hljs-keyword\">new</span> XYscope(<span class=\"hljs-keyword\">this</span>, xy_instance.outXY); <span class=\"hljs-comment\">// another XYscope out</span>\nxy = <span class=\"hljs-keyword\">new</span> XYscope(<span class=\"hljs-keyword\">this</span>, sampleRate); <span class=\"hljs-comment\">// default sampleRate is 41000</span>\nxy = <span class=\"hljs-keyword\">new</span> XYscope(<span class=\"hljs-keyword\">this</span>, <span class=\"hljs-string\">\"custom_dac\"</span>, sampleRate); <span class=\"hljs-comment\">// default sampleRate is 41000</span>\nxy = <span class=\"hljs-keyword\">new</span> XYscope(<span class=\"hljs-keyword\">this</span>, <span class=\"hljs-string\">\"custom_dac\"</span>, sampleRate, bufferSize); <span class=\"hljs-comment\">// default bufferSize is 512</span>\n</code></pre>\n<hr>\n<h3 id=\"z-axis\" name=\"z-axis\">Z-axis</h3>\n<h4 id=\"z()\" name=\"z()\">z()</h4>\n<pre><code class=\"hljs javascript\">xy.z(<span class=\"hljs-string\">\"custom_dac\"</span>); <span class=\"hljs-comment\">// set audio out for z-axis (blanking)</span>\nxy.z(<span class=\"hljs-string\">\"custom_dac\"</span>, sampleRate); <span class=\"hljs-comment\">// add custom sampleRate</span>\n</code></pre>\n<h4 id=\"zauto()\" name=\"zauto()\">zAuto()</h4>\n<pre><code class=\"hljs javascript\"><span class=\"hljs-comment\">// somewhat deprecated.. may return</span>\nxy.zAuto(); <span class=\"hljs-comment\">// get setting for using z-axis</span>\nxy.zAuto(boolean); <span class=\"hljs-comment\">// set auto use of z-axis </span>\n</code></pre>\n<hr>\n<h3 id=\"audio-interface-settings\" name=\"audio-interface-settings\">Audio Interface Settings</h3>\n<h4 id=\"getmixerinfo()\" name=\"getmixerinfo()\">getMixerInfo()</h4>\n<pre><code class=\"hljs javascript\">xy.getMixerInfo(); <span class=\"hljs-comment\">// list connected audio interfaces</span>\n</code></pre>\n<h4 id=\"samplerate()\" name=\"samplerate()\">sampleRate()</h4>\n<pre><code class=\"hljs javascript\">xy.sampleRate(); <span class=\"hljs-comment\">// get current sampleRate, default 41000</span>\nxy.sampleRate(newRate); <span class=\"hljs-comment\">// set new sampleRate (int)</span>\n</code></pre>\n<h4 id=\"buffersize()\" name=\"buffersize()\">bufferSize()</h4>\n<pre><code class=\"hljs javascript\">xy.bufferSize(); <span class=\"hljs-comment\">// get current bufferSize, default 512</span>\nxy.bufferSize(newSize); <span class=\"hljs-comment\">// set  new bufferSize (int)</span>\n</code></pre>\n<hr>\n<h3 id=\"waves\" name=\"waves\">Waves</h3>\n<h4 id=\"resetwaves()\" name=\"resetwaves()\">resetWaves()</h4>\n<pre><code class=\"hljs javascript\">xy.resetWaves(); <span class=\"hljs-comment\">// fix any sync issues with phase of waves</span>\n</code></pre>\n<h4 id=\"clearwaves()\" name=\"clearwaves()\">clearWaves()</h4>\n<pre><code class=\"hljs javascript\">xy.clearWaves(); <span class=\"hljs-comment\">// clear wavetables from any previous drawing</span>\n</code></pre>\n<h4 id=\"buildwaves()\" name=\"buildwaves()\">buildWaves()</h4>\n<pre><code class=\"hljs javascript\">xy.buildWaves(); <span class=\"hljs-comment\">// compile drawn shapes into new wavetables/audio</span>\nxy.buildWaves(<span class=\"hljs-number\">-1</span>); <span class=\"hljs-comment\">// draw in v1 style</span>\nxy.buildWaves(<span class=\"hljs-number\">-2</span>); <span class=\"hljs-comment\">// draw in v2 style</span>\nxy.buildWaves(<span class=\"hljs-number\">-3</span>); <span class=\"hljs-comment\">// draw in v3 style</span>\n</code></pre>\n<h4 id=\"buildx()\" name=\"buildx()\">buildX()</h4>\n<pre><code class=\"hljs javascript\">xy.buildX(newWave); <span class=\"hljs-comment\">// build wavetableX with float[] array</span>\n</code></pre>\n<h4 id=\"buildy()\" name=\"buildy()\">buildY()</h4>\n<pre><code class=\"hljs javascript\">xy.buildY(newWave); <span class=\"hljs-comment\">// build wavetableY with float[] array</span>\n</code></pre>\n<h4 id=\"buildz()\" name=\"buildz()\">buildZ()</h4>\n<pre><code class=\"hljs javascript\">xy.buildZ(newWave); <span class=\"hljs-comment\">// build wavetableZ with float[] array</span>\n</code></pre>\n<h4 id=\"wavesize()\" name=\"wavesize()\">waveSize()</h4>\n<pre><code class=\"hljs javascript\">xy.waveSize(); <span class=\"hljs-comment\">// get current size of wavetables</span>\nxy.waveSize(newSize); <span class=\"hljs-comment\">// set new wavetable size, default is 512 </span>\n</code></pre>\n<h4 id=\"setwaveforms()\" name=\"setwaveforms()\">setWaveforms()</h4>\n<pre><code class=\"hljs javascript\">xy.setWaveforms(wfX, wfY, wfZ); <span class=\"hljs-comment\">// set wavetables with float[] arrays </span>\n</code></pre>\n<hr>\n<h3 id=\"points\" name=\"points\">Points</h3>\n<h4 id=\"wavepoints()\" name=\"wavepoints()\">wavePoints()</h4>\n<pre><code class=\"hljs javascript\">xy.wavePoints(); <span class=\"hljs-comment\">// get PVector ArrayList of all drawn coordinates (0.0 – 1.0)</span>\n</code></pre>\n<h4 id=\"steps()\" name=\"steps()\">steps()</h4>\n<pre><code class=\"hljs javascript\">xy.steps(); <span class=\"hljs-comment\">// get current steps between points, default 24</span>\nxy.steps(newVal); <span class=\"hljs-comment\">// set step multiplier, segments, between points</span>\n</code></pre>\n<h4 id=\"limitpoints()\" name=\"limitpoints()\">limitPoints()</h4>\n<pre><code class=\"hljs javascript\">xy.limitPoints(); <span class=\"hljs-comment\">// get number of limited drawing points</span>\nxy.limitPoints(newLimit); <span class=\"hljs-comment\">// set new limit of points for drawing</span>\n</code></pre>\n<h4 id=\"limitpath()\" name=\"limitpath()\">limitPath()</h4>\n<pre><code class=\"hljs javascript\">xy.limitPath(); <span class=\"hljs-comment\">// get current limit of drawn path</span>\nxy.limitPath(newLimit); <span class=\"hljs-comment\">// avoid drawn vectors beyond border edges (px)</span>\n</code></pre>\n<hr>\n<h3 id=\"record-audio\" name=\"record-audio\">Record Audio</h3>\n<p>Easily save your drawings as .wav audio files! Just use a keyPress to initiate starting and stopping the recording (see demo). Recording uses our set sampleRate.</p>\n<h4 id=\"recorderbegin()\" name=\"recorderbegin()\">recorderBegin()</h4>\n<pre><code class=\"hljs javascript\">xy.recorderBegin(); <span class=\"hljs-comment\">// will name file \"XYscope_timestamp.wav\"</span>\nxy.recorderBegin(<span class=\"hljs-string\">\"customName\"</span>); <span class=\"hljs-comment\">// set name, timestamp added</span>\n</code></pre>\n<h4 id=\"recorderend()\" name=\"recorderend()\">recorderEnd()</h4>\n<pre><code class=\"hljs javascript\">xy.recorderEnd(); <span class=\"hljs-comment\">// complete recording</span>\n</code></pre>\n<hr>\n<h3 id=\"primitive-shapes\" name=\"primitive-shapes\">Primitive Shapes</h3>\n<p>Most primitives from Processing have been ported, so you only need to add <code>xy.</code> in front of them! They can also be used without parameters, for quickly testing.</p>\n<h4 id=\"point()\" name=\"point()\">point()</h4>\n<pre><code class=\"hljs css\"><span class=\"hljs-selector-tag\">xy</span><span class=\"hljs-selector-class\">.point</span>();\n<span class=\"hljs-selector-tag\">xy</span><span class=\"hljs-selector-class\">.point</span>(<span class=\"hljs-selector-tag\">x</span>, <span class=\"hljs-selector-tag\">y</span>);\n<span class=\"hljs-selector-tag\">xy</span><span class=\"hljs-selector-class\">.point</span>(<span class=\"hljs-selector-tag\">x</span>, <span class=\"hljs-selector-tag\">y</span>, <span class=\"hljs-selector-tag\">z</span>);\n</code></pre>\n<h4 id=\"line()\" name=\"line()\">line()</h4>\n<pre><code class=\"hljs css\"><span class=\"hljs-selector-tag\">xy</span><span class=\"hljs-selector-class\">.line</span>();\n<span class=\"hljs-selector-tag\">xy</span><span class=\"hljs-selector-class\">.line</span>(<span class=\"hljs-selector-tag\">x1</span>, <span class=\"hljs-selector-tag\">y1</span>, <span class=\"hljs-selector-tag\">x2</span>, <span class=\"hljs-selector-tag\">y2</span>);\n<span class=\"hljs-selector-tag\">xy</span><span class=\"hljs-selector-class\">.line</span>(<span class=\"hljs-selector-tag\">x1</span>, <span class=\"hljs-selector-tag\">y1</span>, <span class=\"hljs-selector-tag\">z1</span>, <span class=\"hljs-selector-tag\">x2</span>, <span class=\"hljs-selector-tag\">y2</span>, <span class=\"hljs-selector-tag\">z2</span>);\n</code></pre>\n<h4 id=\"square()\" name=\"square()\">square()</h4>\n<pre><code class=\"hljs javascript\">xy.rectMode(); <span class=\"hljs-comment\">// default CORNER, CENTER to draw center out</span>\n\nxy.square();\nxy.square(x, y, w);\n</code></pre>\n<h4 id=\"rect()\" name=\"rect()\">rect()</h4>\n<pre><code class=\"hljs javascript\">xy.rectMode(); <span class=\"hljs-comment\">// default CORNER, CENTER to draw center out</span>\n\nxy.rect();\nxy.rect(x, y, w);\nxy.rect(x, y, w, h);\n</code></pre>\n<h4 id=\"circle()\" name=\"circle()\">circle()</h4>\n<pre><code class=\"hljs javascript\">xy.ellipseDetail(); <span class=\"hljs-comment\">// get current facets of ellipse</span>\nxy.ellipseDetail(newVal); <span class=\"hljs-comment\">// set new count of facets, default 30</span>\n\nxy.circle();\nxy.circle(x, y, w);\n</code></pre>\n<h4 id=\"ellipse()\" name=\"ellipse()\">ellipse()</h4>\n<pre><code class=\"hljs javascript\">xy.ellipseDetail(); <span class=\"hljs-comment\">// get current facets of ellipse</span>\nxy.ellipseDetail(newVal); <span class=\"hljs-comment\">// set new count of facets, default 30</span>\n\nxy.ellipse();\nxy.ellipse(x, y, w);\nxy.ellipse(x, y, w, h);\n</code></pre>\n<h4 id=\"complex-shape\" name=\"complex-shape\">complex shape</h4>\n<pre><code class=\"hljs javascript\">xy.beginShape();\n\nxy.vertex();\nxy.vertex(x, y);\nxy.vertex(x, y, z); <span class=\"hljs-comment\">// if in P3D mode</span>\n<span class=\"hljs-comment\">// ...</span>\n\nxy.endShape();\nxy.endShape(CLOSE); <span class=\"hljs-comment\">// closes form</span>\n</code></pre>\n<h4 id=\"lissajous()\" name=\"lissajous()\">lissajous()</h4>\n<pre><code class=\"hljs css\"><span class=\"hljs-selector-tag\">xy</span><span class=\"hljs-selector-class\">.lissajous</span>();\n<span class=\"hljs-selector-tag\">xy</span><span class=\"hljs-selector-class\">.lissajous</span>(<span class=\"hljs-selector-tag\">x</span>, <span class=\"hljs-selector-tag\">y</span>, <span class=\"hljs-selector-tag\">radius</span>, <span class=\"hljs-selector-tag\">ratioA</span>, <span class=\"hljs-selector-tag\">ratioB</span>, <span class=\"hljs-selector-tag\">phase</span>, <span class=\"hljs-selector-tag\">resolution</span>);\n</code></pre>\n<h4 id=\"box()\" name=\"box()\">box()</h4>\n<pre><code class=\"hljs css\"><span class=\"hljs-selector-tag\">xy</span><span class=\"hljs-selector-class\">.box</span>();\n<span class=\"hljs-selector-tag\">xy</span><span class=\"hljs-selector-class\">.box</span>(<span class=\"hljs-selector-tag\">size</span>);\n<span class=\"hljs-selector-tag\">xy</span><span class=\"hljs-selector-class\">.box</span>(<span class=\"hljs-selector-tag\">rx</span>, <span class=\"hljs-selector-tag\">ryz</span>);\n<span class=\"hljs-selector-tag\">xy</span><span class=\"hljs-selector-class\">.box</span>(<span class=\"hljs-selector-tag\">rx</span>, <span class=\"hljs-selector-tag\">ry</span>, <span class=\"hljs-selector-tag\">rz</span>);\n</code></pre>\n<h4 id=\"sphere()\" name=\"sphere()\">sphere()</h4>\n<pre><code class=\"hljs javascript\">xy.sphere();\nxy.sphere(size);\nxy.sphere(size, detailXY); <span class=\"hljs-comment\">// default 24</span>\nxy.sphere(size, detailX, detailY); <span class=\"hljs-comment\">// default 24, 24</span>\n</code></pre>\n<h4 id=\"ellipsoid()\" name=\"ellipsoid()\">ellipsoid()</h4>\n<pre><code class=\"hljs javascript\">xy.ellipsoid();\nxy.ellipsoid(rx, ry, rz);\nxy.ellipsoid(rx, ry, rz, detailXY); <span class=\"hljs-comment\">// default 24</span>\nxy.ellipsoid(rx, ry, rz, detailX, detailY); <span class=\"hljs-comment\">// default 24, 24</span>\n</code></pre>\n<h4 id=\"torus()\" name=\"torus()\">torus()</h4>\n<pre><code class=\"hljs javascript\">xy.torus();\nxy.torus(radius, tubeRadius);\nxy.torus(radius, tubeRadius, detailXY); <span class=\"hljs-comment\">// default 24</span>\nxy.torus(radius, tubeRadius, detailX, detailY); <span class=\"hljs-comment\">// default 24, 24</span>\n</code></pre>\n<hr>\n<h3 id=\"text\" name=\"text\">Text</h3>\n<h4 id=\"text()\" name=\"text()\">text()</h4>\n<pre><code class=\"hljs javascript\">xy.text(); <span class=\"hljs-comment\">// paramless text drawing</span>\nxy.text(<span class=\"hljs-string\">\"string\"</span>, x, y); <span class=\"hljs-comment\">// use '\\n' for multi-line text</span>\n</code></pre>\n<h4 id=\"textpaths()\" name=\"textpaths()\">textPaths()</h4>\n<p>Use coordinates of Hershey text for manipulating type!</p>\n<pre><code class=\"hljs javascript\">xy.textPaths(<span class=\"hljs-string\">\"string\"</span>, x, y); <span class=\"hljs-comment\">// return 2D Array of PVector coords</span>\n</code></pre>\n<h4 id=\"textfont()\" name=\"textfont()\">textFont()</h4>\n<pre><code class=\"hljs javascript\">println(xy.fonts); <span class=\"hljs-comment\">// print list of avilable Hershey fonts</span>\nxy.textFont(<span class=\"hljs-string\">\"fontname\"</span>); <span class=\"hljs-comment\">// set active Hershey font</span>\n</code></pre>\n<h4 id=\"textsize()\" name=\"textsize()\">textSize()</h4>\n<pre><code class=\"hljs javascript\">xy.textSize(); <span class=\"hljs-comment\">// get current textSize</span>\nxy.textSize(newSize); <span class=\"hljs-comment\">// set new textSize</span>\n</code></pre>\n<h4 id=\"textleading()\" name=\"textleading()\">textLeading()</h4>\n<pre><code class=\"hljs javascript\">xy.textLeading(); <span class=\"hljs-comment\">// get current textLeading</span>\nxy.textLeading(newSize); <span class=\"hljs-comment\">// set new textLeading</span>\n</code></pre>\n<h4 id=\"textalign()\" name=\"textalign()\">textAlign()</h4>\n<pre><code class=\"hljs javascript\">xy.textAlign(hAlign); <span class=\"hljs-comment\">// Horz: LEFT (default) / CENTER / RIGHT</span>\nxy.textAlign(hAlign, vAlign); <span class=\"hljs-comment\">// Vert options: TOP / CENTER / BOTTOM</span>\n</code></pre>\n<h4 id=\"textwidth()\" name=\"textwidth()\">textWidth()</h4>\n<pre><code class=\"hljs javascript\">xy.textWidth(int); <span class=\"hljs-comment\">// from 32 characters, get width of char</span>\nxy.textWidth(<span class=\"hljs-string\">\"string\"</span>); <span class=\"hljs-comment\">// get width of text for positioning</span>\n</code></pre>\n<hr>\n<h3 id=\"modulation\" name=\"modulation\">Modulation</h3>\n<h4 id=\"freq()\" name=\"freq()\">freq()</h4>\n<p>Frequency of oscillators</p>\n<pre><code class=\"hljs javascript\"><span class=\"hljs-comment\">// get</span>\nxy.freq(); <span class=\"hljs-comment\">// get PVector (.x, .y, .z) of freqs</span>\nxy.freq().x;   <span class=\"hljs-comment\">// get frequency of x oscillator </span>\n\n<span class=\"hljs-comment\">// set</span>\nxy.freq(freqXY);  <span class=\"hljs-comment\">// default is 50.0 </span>\nxy.freq(freqX, freqY); <span class=\"hljs-comment\">// set x, y frequencies </span>\nxy.freq(freqX, freqY, freqZ); <span class=\"hljs-comment\">// set x, y, z frequencies </span>\nxy.freq(PVector freqXYZ); <span class=\"hljs-comment\">// set as PVector </span>\n \nxy.resetWaves(); <span class=\"hljs-comment\">// occasionally needed if they slip out of phase </span>\n</code></pre>\n<h4 id=\"amp()\" name=\"amp()\">amp()</h4>\n<p>Amplitude of oscillators</p>\n<pre><code class=\"hljs javascript\"><span class=\"hljs-comment\">// get</span>\nxy.amp();   <span class=\"hljs-comment\">// get PVector (.x, .y, .z) of amps</span>\nxy.amp().x; <span class=\"hljs-comment\">// get amplitude of x oscillator</span>\n\n<span class=\"hljs-comment\">// set</span>\nxy.amp(ampXY); <span class=\"hljs-comment\">// default is 1.0 </span>\nxy.amp(ampX, ampY); <span class=\"hljs-comment\">// set x, y to specific amplitudes </span>\nxy.amp(ampX, ampY, ampZ); <span class=\"hljs-comment\">// set x, y, z to specific amplitudes </span>\nxy.amp(PVector ampXYZ); <span class=\"hljs-comment\">// set as PVector</span>\n</code></pre>\n<h4 id=\"pan()\" name=\"pan()\">pan()</h4>\n<p>Optionally swap the hard pan of XY rather than physical cables</p>\n<pre><code class=\"hljs javascript\">xy.pan(leftPan, rightPan); <span class=\"hljs-comment\">// default is -1.0, 1.0</span>\n</code></pre>\n<hr>\n<h3 id=\"vectrex\" name=\"vectrex\">Vectrex</h3>\n<pre><code class=\"hljs javascript\">xy.vectrex(<span class=\"hljs-number\">0</span>); <span class=\"hljs-comment\">// 0 for portrait, -90/90 for landscape orientation</span>\nxy.vectrex(vw, vh, vamp, vrot); <span class=\"hljs-comment\">// custom width, height, amp, rotation</span>\n\nxy.vectrexRatio(); <span class=\"hljs-comment\">// get current ratio</span>\nxy.vectrexRatio(newRatio); <span class=\"hljs-comment\">// set new ratio, default is .82</span>\n</code></pre>\n<hr>\n<h3 id=\"laser\" name=\"laser\">Laser</h3>\n<pre><code class=\"hljs javascript\">xy.laser(<span class=\"hljs-string\">\"dac_Red\"</span>, <span class=\"hljs-string\">\"dac_GreenBlue\"</span>); <span class=\"hljs-comment\">// custom 2ch pairs for R, GB</span>\n\n<span class=\"hljs-comment\">// be cautious of laser galvos, can help smooth graphics</span>\nxy.laserLPF(); <span class=\"hljs-comment\">// get value of laser's LowPassFilter</span>\nxy.laserLPF(newLPF); <span class=\"hljs-comment\">// set mew value of LowPassFilter (0.1 – 20000.0)</span>\n\n<span class=\"hljs-comment\">// avoid dangerous hotspot, only draw laser if shape is big enough</span>\nxy.spotKiller(); <span class=\"hljs-comment\">// get current min size of drawing for laser</span>\nxy.spotKiller(newVal); <span class=\"hljs-comment\">// set min drawing size to prevent laser hotspot</span>\n\nxy.stroke(r, g, b); <span class=\"hljs-comment\">// set RGB laser stroke color</span>\nxy.stroke(PVector rgb); <span class=\"hljs-comment\">// set as PVector</span>\n\nxy.strokeFreq(); <span class=\"hljs-comment\">// get laser RGB channel frequencies</span>\nxy.strokeFreq(freqRGB); <span class=\"hljs-comment\">// set laser RGB freq as group</span>\nxy.strokeFreq(freqR, freqG, freqB); <span class=\"hljs-comment\">// set laser R, G, B separately</span>\nxy.strokeFreq(PVector freqRGB); <span class=\"hljs-comment\">// set as PVector</span>\n\n<span class=\"hljs-comment\">// some lasers ignore values below __ when setting colors, </span>\n<span class=\"hljs-comment\">// use this to set lowest point when color mixing</span>\nxy.strokeMin(); <span class=\"hljs-comment\">// get curret min RGB values for laser stroke</span>\nxy.strokeMin(minR, minG, minB); <span class=\"hljs-comment\">// set new min values (0.0 - 255.0)</span>\nxy.strokeMin(PVector minPV); <span class=\"hljs-comment\">// set new min as PVector</span>\n\nxy.strokeWB(wbR, wbG, wbB); <span class=\"hljs-comment\">// set values needed for white light</span>\nxy.strokeWB(PVector wbRGB); <span class=\"hljs-comment\">// set values as PVector</span>\n\nxy.strokeDash(); <span class=\"hljs-comment\">// get current dash setting</span>\nxy.strokeDash(sdRGB); <span class=\"hljs-comment\">// set laser dash count for RGB strokes</span>\nxy.strokeDash(sdR, sdG, sdB); <span class=\"hljs-comment\">// set dashes per R, G, B</span>\nxy.strokeDash(PVector sdRGB); <span class=\"hljs-comment\">// set as PVector</span>\n</code></pre>\n<hr>\n<h3 id=\"rendering\" name=\"rendering\">Rendering</h3>\n<pre><code class=\"hljs javascript\">xy.drawAll(); <span class=\"hljs-comment\">// all views below </span>\nxy.drawPath(); <span class=\"hljs-comment\">// shapes being drawn </span>\nxy.drawPoints(); <span class=\"hljs-comment\">// points along paths of drawing </span>\nxy.drawWaveform(); <span class=\"hljs-comment\">// drawing as oscillator's waveform </span>\nxy.drawWave(); <span class=\"hljs-comment\">// waveform over time at frequency </span>\nxy.drawXY(); <span class=\"hljs-comment\">// simulated xy-mode oscilloscope </span>\n\nxy.debugView(); <span class=\"hljs-comment\">// check if debugView active</span>\nxy.debugView(boolean); <span class=\"hljs-comment\">// compare drawXY() to drawWaveform()</span>\n</code></pre>\n<h3 id=\"vars\" name=\"vars\">Vars</h3>\n<p>Lastly, many variables within XYscope are public for your vector hacking needs.</p>\n<pre><code class=\"hljs javascript\">shapes; <span class=\"hljs-comment\">// XYShapeList, processed by buildWaves()</span>\nminim, minimZ; <span class=\"hljs-comment\">// instances of Minim</span>\nrecorder; <span class=\"hljs-comment\">// instance for audio recording</span>\noutXY, outZ; <span class=\"hljs-comment\">// AudioOutput (used to patch for additive-synth)</span>\nsumXY, sumZ; <span class=\"hljs-comment\">// Summer for patching filters</span>\nwaveX, waveY, waveZ; <span class=\"hljs-comment\">// Oscil for each XYZ oscillators</span>\ntableX, tableY, tableZ; <span class=\"hljs-comment\">// XYWavetable, applied to oscillator</span>\nshapeX, shapeY, shapeZ; <span class=\"hljs-comment\">// float[] used for wavetables</span>\nfonts; <span class=\"hljs-comment\">// list of Hershey fonts</span>\n\nminimR, minimBG; <span class=\"hljs-comment\">// instances of Minim</span>\nwaveR, waveG, waveB; <span class=\"hljs-comment\">// Oscil for each RGB oscillators</span>\ntableR, tableG, tableB; <span class=\"hljs-comment\">// XYWavetable, applied to oscillator</span>\nshapeR, shapeG, shapeB; <span class=\"hljs-comment\">// float[] used for wavetables</span>\n\n</code></pre>\n<h2 id=\"extras\" name=\"extras\">Extras</h2>\n<h3 id=\"contributing\" name=\"contributing\">Contributing</h3>\n<p>Found a bug, missing feature, and/or created a project with XYscope? Let me know!<br>\nCreate an <a href=\"https://github.com/ffd8/xyscope/issues\" target=\"_blank\">issue on GitHub</a>.</p>\n<h3 id=\"license\" name=\"license\">License</h3>\n<p>This project is licensed under the LGPL License - see <a href=\"https://github.com/ffd8/xyscope/blob/master/license.txt\" target=\"_blank\">LICENSE.md</a> for details.</p>\n<h3 id=\"shoutouts\" name=\"shoutouts\">Shoutouts</h3>\n<ul>\n<li><a href=\"http://dailydrawbot.tumblr.com\" target=\"_blank\">Just Van Rossum</a>, the enlightening conversation on my X-Y attempts.</li>\n<li><a href=\"https://stefaniebraeuer.ch/\" target=\"_blank\">Stefanie Bräuer</a>, feeding the obsession with crucial theory + context.</li>\n<li><a href=\"https://asdfg.me\" target=\"_blank\">Hansi Raber</a>, java meta insights + finding external WaveTable bug!</li>\n<li><a href=\"https://github.com/processing/processing-library-template\" target=\"_blank\">Processing Library Template</a></li>\n</ul>\n</div>\n\n\t<div id=\"spacer\"></div>\n\n\t<script type=\"text/javascript\">\n\n\t\twindow.addEventListener('scroll', (e)=>{\n\t\t\tlet spacer = document.getElementById('spacer')\n\t\t\tif(window.scrollY > spacer.offsetTop - 1){\n\t\t\t\tbeamOpacity = 250\n\t\t\t\tbgOpacity = 5\n\t\t\t\tbgTint = 220\n\t\t\t}else{\n\t\t\t\tbeamOpacity = 50\n\t\t\t\tbgOpacity = 10\n\t\t\t\tbgTint = 250\n\t\t\t}\n\t\t})\n\n\t\tfunction windowResized() {\n\t\t\tresizeCanvas(windowWidth, windowHeight);\n\t\t}\n\n\n\t\t// XYscopeBG v.1\n\t\t// cc teddavis.org 2023\n\n\t\tlet bgOpacity = 10, bgTint = 250, beamOpacity = 50, fc = 0\n\n\t\tfunction setup() {\n\t\t\tcreateCanvas(windowWidth, windowHeight);\n\t\t\tpixelDensity(.4)\n\t\t}\n\n\t\tfunction draw() {\n\t\t\tnoFill();\n\t\t\tbackground(bgTint, 250, bgTint, bgOpacity)\n\t\t\tstroke(100, 250, 100, beamOpacity)\n\t\t\txyDrawAni(width / 2, height / 2, frameCount * .003);\n\t\t\tfc += 1.0;\n\t\t}\n\n\t\tfunction xyDrawAni(xOff, yOff, phase) {\n\t\t\tpush();\n\t\t\ttranslate(xOff + noise(frameCount * .01) * 15, yOff + noise(frameCount * .011) * 15)\n\t\t\tlet c = xyCoords[fc % (xyCoords.length - 1)]\n\t\t\tlet c2 = xyCoords[(fc % (xyCoords.length - 1)) + 1]\n\t\t\tlet d = dist(c[0], c[1], c2[0], c2[1]) * 500\n\t\t\tif(d > 10) {\n\t\t\t\td = 2\n\t\t\t}\n\t\t\tstrokeWeight(d + noise(frameCount*.5)*2)\n\t\t\tline(c[0] * width / 2, c[1] * -width / 2, c2[0] * width / 2, c2[1] * -width / 2)\n\t\t\tpop();\n\t\t}\n\n\t\tlet xyCoords = [\n\t\t\t[-0.8933222, 0.17857146],\n\t\t\t[-0.88827753, 0.17100447],\n\t\t\t[-0.8832329, 0.16343749],\n\t\t\t[-0.8780809, 0.1557095],\n\t\t\t[-0.87303627, 0.14814258],\n\t\t\t[-0.8678843, 0.1404146],\n\t\t\t[-0.86283964, 0.1328476],\n\t\t\t[-0.85768765, 0.12511963],\n\t\t\t[-0.852643, 0.11755264],\n\t\t\t[-0.847491, 0.10982466],\n\t\t\t[-0.8424464, 0.10225773],\n\t\t\t[-0.8372944, 0.09452975],\n\t\t\t[-0.83224976, 0.08696276],\n\t\t\t[-0.82720506, 0.07939577],\n\t\t\t[-0.8220531, 0.07166779],\n\t\t\t[-0.8170085, 0.0641008],\n\t\t\t[-0.8118565, 0.05637288],\n\t\t\t[-0.8068118, 0.048805892],\n\t\t\t[-0.8016598, 0.04107791],\n\t\t\t[-0.7966152, 0.033510923],\n\t\t\t[-0.7914632, 0.025782943],\n\t\t\t[-0.78641856, 0.018215954],\n\t\t\t[-0.78126657, 0.010488033],\n\t\t\t[-0.77622193, 0.0029210448],\n\t\t\t[-0.77106994, -0.0048069954],\n\t\t\t[-0.7660253, -0.012373924],\n\t\t\t[-0.7609806, -0.019940853],\n\t\t\t[-0.7558286, -0.027668834],\n\t\t\t[-0.75078404, -0.03523588],\n\t\t\t[-0.74563205, -0.042963862],\n\t\t\t[-0.74058735, -0.05053079],\n\t\t\t[-0.73543537, -0.058258772],\n\t\t\t[-0.7303907, -0.0658257],\n\t\t\t[-0.7252388, -0.0735538],\n\t\t\t[-0.7201941, -0.08112073],\n\t\t\t[-0.7150421, -0.08884871],\n\t\t\t[-0.7099975, -0.09641564],\n\t\t\t[-0.70495284, -0.10398269],\n\t\t\t[-0.69980085, -0.11171055],\n\t\t\t[-0.69475615, -0.1192776],\n\t\t\t[-0.6896042, -0.12700558],\n\t\t\t[-0.68531084, 0.17808849],\n\t\t\t[-0.6904628, 0.1703605],\n\t\t\t[-0.6955075, 0.16279352],\n\t\t\t[-0.7006595, 0.15506554],\n\t\t\t[-0.70570415, 0.14749855],\n\t\t\t[-0.71085614, 0.13977057],\n\t\t\t[-0.7159008, 0.13220364],\n\t\t\t[-0.72105277, 0.12447566],\n\t\t\t[-0.7260974, 0.11690867],\n\t\t\t[-0.73114204, 0.10934168],\n\t\t\t[-0.73629403, 0.1016137],\n\t\t\t[-0.74133873, 0.09404671],\n\t\t\t[-0.7464907, 0.08631873],\n\t\t\t[-0.7515353, 0.0787518],\n\t\t\t[-0.7566873, 0.07102382],\n\t\t\t[-0.761732, 0.06345683],\n\t\t\t[-0.76688397, 0.055728853],\n\t\t\t[-0.7719286, 0.048161864],\n\t\t\t[-0.7770806, 0.040433884],\n\t\t\t[-0.78212523, 0.032866955],\n\t\t\t[-0.7871699, 0.025299966],\n\t\t\t[-0.79232186, 0.017571986],\n\t\t\t[-0.7973665, 0.010004997],\n\t\t\t[-0.8025185, 0.0022770166],\n\t\t\t[-0.8075631, -0.005289912],\n\t\t\t[-0.8127151, -0.013017893],\n\t\t\t[-0.81775975, -0.02058494],\n\t\t\t[-0.82291174, -0.028312922],\n\t\t\t[-0.82795644, -0.03587985],\n\t\t\t[-0.8331084, -0.04360783],\n\t\t\t[-0.83815306, -0.05117476],\n\t\t\t[-0.843305, -0.05890274],\n\t\t\t[-0.8483497, -0.06646979],\n\t\t\t[-0.8533943, -0.07403672],\n\t\t\t[-0.8585463, -0.0817647],\n\t\t\t[-0.86359096, -0.08933163],\n\t\t\t[-0.86874294, -0.09705973],\n\t\t\t[-0.8737876, -0.104626656],\n\t\t\t[-0.87893957, -0.112354636],\n\t\t\t[-0.8839842, -0.119921565],\n\t\t\t[-0.8891362, -0.12764955],\n\t\t\t[-0.6450803, 0.17751396],\n\t\t\t[-0.63927907, 0.17026228],\n\t\t\t[-0.6335986, 0.16316175],\n\t\t\t[-0.6277973, 0.15591013],\n\t\t\t[-0.62211686, 0.14880955],\n\t\t\t[-0.6164364, 0.14170897],\n\t\t\t[-0.6106351, 0.13445735],\n\t\t\t[-0.60495466, 0.12735683],\n\t\t\t[-0.5991534, 0.12010515],\n\t\t\t[-0.59347296, 0.113004625],\n\t\t\t[-0.58767164, 0.105753005],\n\t\t\t[-0.5819912, 0.09865242],\n\t\t\t[-0.5761899, 0.0914008],\n\t\t\t[-0.57050943, 0.08430022],\n\t\t\t[-0.5647081, 0.0770486],\n\t\t\t[-0.5590277, 0.06994808],\n\t\t\t[-0.55334723, 0.062847495],\n\t\t\t[-0.5475459, 0.055595875],\n\t\t\t[-0.54186547, 0.048495293],\n\t\t\t[-0.53606415, 0.041243672],\n\t\t\t[-0.5303837, 0.03414309],\n\t\t\t[-0.5268788, 0.02666974],\n\t\t\t[-0.5268788, 0.017586589],\n\t\t\t[-0.5268788, 0.008310139],\n\t\t\t[-0.5268788, -7.7307224E-4],\n\t\t\t[-0.5268788, -0.010049462],\n\t\t\t[-0.5268788, -0.019132614],\n\t\t\t[-0.5268788, -0.028409123],\n\t\t\t[-0.5268788, -0.037492275],\n\t\t\t[-0.5268788, -0.046575427],\n\t\t\t[-0.5268788, -0.055851817],\n\t\t\t[-0.5268788, -0.06493509],\n\t\t\t[-0.5268788, -0.07421148],\n\t\t\t[-0.5268788, -0.08329463],\n\t\t\t[-0.5268788, -0.09257114],\n\t\t\t[-0.5268788, -0.10165429],\n\t\t\t[-0.5268788, -0.11093068],\n\t\t\t[-0.5268788, -0.12001395],\n\t\t\t[-0.5268788, -0.12929034],\n\t\t\t[-0.41049004, 0.17524779],\n\t\t\t[-0.41617054, 0.16814727],\n\t\t\t[-0.4219718, 0.16089559],\n\t\t\t[-0.42765224, 0.15379506],\n\t\t\t[-0.43345356, 0.14654344],\n\t\t\t[-0.439134, 0.13944286],\n\t\t\t[-0.44493532, 0.13219124],\n\t\t\t[-0.45061576, 0.12509066],\n\t\t\t[-0.45641708, 0.11783904],\n\t\t\t[-0.46209753, 0.110738456],\n\t\t\t[-0.46789885, 0.103486836],\n\t\t\t[-0.4735793, 0.09638631],\n\t\t\t[-0.4793806, 0.08913463],\n\t\t\t[-0.48506105, 0.08203411],\n\t\t\t[-0.4907415, 0.07493353],\n\t\t\t[-0.4965428, 0.06768191],\n\t\t\t[-0.50222325, 0.060581326],\n\t\t\t[-0.5080245, 0.053329706],\n\t\t\t[-0.513705, 0.046229124],\n\t\t\t[-0.5195063, 0.038977504],\n\t\t\t[-0.5251867, 0.03187698],\n\t\t\t[-0.19694203, 0.035439014],\n\t\t\t[-0.20098484, 0.043524623],\n\t\t\t[-0.20511365, 0.05178231],\n\t\t\t[-0.20898443, 0.05952382],\n\t\t\t[-0.21758366, 0.06239021],\n\t\t\t[-0.22618294, 0.065256655],\n\t\t\t[-0.23478216, 0.06812304],\n\t\t\t[-0.24356437, 0.071050465],\n\t\t\t[-0.2521636, 0.07391685],\n\t\t\t[-0.26074708, 0.074404776],\n\t\t\t[-0.26979113, 0.074404776],\n\t\t\t[-0.27902752, 0.074404776],\n\t\t\t[-0.28807157, 0.074404776],\n\t\t\t[-0.29730803, 0.074404776],\n\t\t\t[-0.30540574, 0.07202625],\n\t\t\t[-0.31418788, 0.06909889],\n\t\t\t[-0.32278717, 0.06623244],\n\t\t\t[-0.3313864, 0.063366055],\n\t\t\t[-0.3401686, 0.060438633],\n\t\t\t[-0.34540755, 0.054534853],\n\t\t\t[-0.34953636, 0.046277165],\n\t\t\t[-0.35357916, 0.038191557],\n\t\t\t[-0.35770798, 0.02993393],\n\t\t\t[-0.35409528, 0.022364438],\n\t\t\t[-0.3499664, 0.01410681],\n\t\t\t[-0.3459236, 0.0060212016],\n\t\t\t[-0.34119266, -8.6021423E-4],\n\t\t\t[-0.3331071, -0.004902959],\n\t\t\t[-0.32484943, -0.009031773],\n\t\t\t[-0.31676382, -0.013074517],\n\t\t\t[-0.30879664, -0.015751839],\n\t\t\t[-0.29970902, -0.017569304],\n\t\t\t[-0.29081076, -0.019348979],\n\t\t\t[-0.28172314, -0.021166563],\n\t\t\t[-0.27282488, -0.02294612],\n\t\t\t[-0.26373726, -0.024763703],\n\t\t\t[-0.254839, -0.026543379],\n\t\t\t[-0.24575138, -0.028360844],\n\t\t\t[-0.2375421, -0.030364037],\n\t\t\t[-0.22928447, -0.03449285],\n\t\t\t[-0.22119886, -0.038535595],\n\t\t\t[-0.21311325, -0.04257846],\n\t\t\t[-0.20717806, -0.048255563],\n\t\t\t[-0.20313525, -0.05634117],\n\t\t\t[-0.19900644, -0.0645988],\n\t\t\t[-0.19496363, -0.07268441],\n\t\t\t[-0.19410348, -0.081082106],\n\t\t\t[-0.19418949, -0.08945775],\n\t\t\t[-0.1983183, -0.09771538],\n\t\t\t[-0.2023611, -0.10580099],\n\t\t\t[-0.20648992, -0.114058614],\n\t\t\t[-0.21172887, -0.119962454],\n\t\t\t[-0.22051108, -0.12288988],\n\t\t\t[-0.2291103, -0.12575626],\n\t\t\t[-0.23770958, -0.12862265],\n\t\t\t[-0.24649179, -0.13155007],\n\t\t\t[-0.25458944, -0.13392854],\n\t\t\t[-0.2638259, -0.13392854],\n\t\t\t[-0.27286994, -0.13392854],\n\t\t\t[-0.2821064, -0.13392854],\n\t\t\t[-0.2911504, -0.13392854],\n\t\t\t[-0.29973388, -0.13344061],\n\t\t\t[-0.3083331, -0.13057423],\n\t\t\t[-0.3171153, -0.1276468],\n\t\t\t[-0.32571453, -0.12478042],\n\t\t\t[-0.33449674, -0.12185311],\n\t\t\t[-0.34291303, -0.11904764],\n\t\t\t[-0.34678382, -0.11130607],\n\t\t\t[-0.35091263, -0.103048444],\n\t\t\t[-0.35495543, -0.094962835],\n\t\t\t[0.032593846, 0.031673253],\n\t\t\t[0.026177287, 0.03808981],\n\t\t\t[0.019624233, 0.044642866],\n\t\t\t[0.013207674, 0.051059425],\n\t\t\t[0.006654501, 0.05761248],\n\t\t\t[-4.1782856E-4, 0.062104344],\n\t\t\t[-0.008675456, 0.06623316],\n\t\t\t[-0.016761065, 0.07027596],\n\t\t\t[-0.024846673, 0.07431877],\n\t\t\t[-0.033485413, 0.074404776],\n\t\t\t[-0.042529464, 0.074404776],\n\t\t\t[-0.05176592, 0.074404776],\n\t\t\t[-0.06080997, 0.074404776],\n\t\t\t[-0.06966156, 0.074404776],\n\t\t\t[-0.07757515, 0.07044798],\n\t\t\t[-0.085832775, 0.06631917],\n\t\t\t[-0.09391838, 0.062276363],\n\t\t\t[-0.101198256, 0.057749033],\n\t\t\t[-0.107614815, 0.051332474],\n\t\t\t[-0.11416793, 0.04477936],\n\t\t\t[-0.12058449, 0.03836286],\n\t\t\t[-0.12700105, 0.03194624],\n\t\t\t[-0.13095403, 0.024456024],\n\t\t\t[-0.13382041, 0.015856802],\n\t\t\t[-0.13674784, 0.0070745945],\n\t\t\t[-0.13961422, -0.0015246868],\n\t\t\t[-0.14254165, -0.010306835],\n\t\t\t[-0.14406633, -0.018529177],\n\t\t\t[-0.14406633, -0.027745724],\n\t\t\t[-0.14406633, -0.036770344],\n\t\t\t[-0.14382237, -0.045374632],\n\t\t\t[-0.14095598, -0.053973913],\n\t\t\t[-0.13808954, -0.062573195],\n\t\t\t[-0.13516217, -0.07135534],\n\t\t\t[-0.13229573, -0.079954624],\n\t\t\t[-0.12936836, -0.08873689],\n\t\t\t[-0.123587966, -0.0948832],\n\t\t\t[-0.11703485, -0.10143626],\n\t\t\t[-0.11061829, -0.10785282],\n\t\t\t[-0.10406524, -0.11440587],\n\t\t\t[-0.0977031, -0.119907856],\n\t\t\t[-0.08944547, -0.12403667],\n\t\t\t[-0.08135986, -0.12807941],\n\t\t\t[-0.073102236, -0.13220823],\n\t\t\t[-0.06504333, -0.13392854],\n\t\t\t[-0.05599928, -0.13392854],\n\t\t\t[-0.046762824, -0.13392854],\n\t\t\t[-0.037718832, -0.13392854],\n\t\t\t[-0.028482378, -0.13392854],\n\t\t\t[-0.02054578, -0.13169205],\n\t\t\t[-0.012288153, -0.12756336],\n\t\t\t[-0.0042025447, -0.12352049],\n\t\t\t[0.004055023, -0.11939168],\n\t\t\t[0.010204077, -0.11358678],\n\t\t\t[0.01675725, -0.10703361],\n\t\t\t[0.023173809, -0.10061705],\n\t\t\t[0.029726863, -0.094064],\n\t\t\t[0.1719358, 0.07345861],\n\t\t\t[0.16385019, 0.06941581],\n\t\t\t[0.15559256, 0.065286994],\n\t\t\t[0.14750695, 0.06124419],\n\t\t\t[0.14065313, 0.05611074],\n\t\t\t[0.13423657, 0.04969418],\n\t\t\t[0.12768352, 0.043141127],\n\t\t\t[0.12126696, 0.036724567],\n\t\t\t[0.11471391, 0.030171454],\n\t\t\t[0.11180377, 0.022260427],\n\t\t\t[0.10887635, 0.013478279],\n\t\t\t[0.10600996, 0.0048790574],\n\t\t\t[0.10314357, -0.0037201643],\n\t\t\t[0.10021615, -0.012502432],\n\t\t\t[0.09942329, -0.020833254],\n\t\t\t[0.09942329, -0.03004992],\n\t\t\t[0.09942329, -0.03907442],\n\t\t\t[0.10039914, -0.04757023],\n\t\t\t[0.103265524, -0.05616951],\n\t\t\t[0.10619295, -0.06495166],\n\t\t\t[0.109059334, -0.07355094],\n\t\t\t[0.11198676, -0.08233309],\n\t\t\t[0.11512339, -0.09010482],\n\t\t\t[0.121676564, -0.09665799],\n\t\t\t[0.12809312, -0.10307455],\n\t\t\t[0.13450968, -0.10949111],\n\t\t\t[0.14106274, -0.11604416],\n\t\t\t[0.14785099, -0.12093997],\n\t\t\t[0.15610862, -0.12506878],\n\t\t\t[0.16419423, -0.12911165],\n\t\t\t[0.17245185, -0.13324046],\n\t\t\t[0.1807555, -0.13392854],\n\t\t\t[0.18999195, -0.13392854],\n\t\t\t[0.19903588, -0.13392854],\n\t\t\t[0.20827234, -0.13392854],\n\t\t\t[0.21731639, -0.13392854],\n\t\t\t[0.22500825, -0.13065994],\n\t\t\t[0.23326588, -0.12653112],\n\t\t\t[0.24135149, -0.12248826],\n\t\t\t[0.24891543, -0.11836505],\n\t\t\t[0.255332, -0.11194849],\n\t\t\t[0.26188505, -0.10539544],\n\t\t\t[0.2683016, -0.09897888],\n\t\t\t[0.27485478, -0.092425704],\n\t\t\t[0.27927554, -0.0854435],\n\t\t\t[0.28220284, -0.07666135],\n\t\t\t[0.28506923, -0.06806207],\n\t\t\t[0.28799665, -0.0592798],\n\t\t\t[0.29086304, -0.050680637],\n\t\t\t[0.29287565, -0.042530656],\n\t\t\t[0.29287565, -0.03331411],\n\t\t\t[0.29287565, -0.024289489],\n\t\t\t[0.29287565, -0.015072942],\n\t\t\t[0.29025316, -0.0070135593],\n\t\t\t[0.28732586, 0.0017686486],\n\t\t\t[0.28445935, 0.01036793],\n\t\t\t[0.28153205, 0.019150078],\n\t\t\t[0.27866566, 0.0277493],\n\t\t\t[0.27335298, 0.03440368],\n\t\t\t[0.26693642, 0.04082024],\n\t\t\t[0.26038337, 0.047373295],\n\t\t\t[0.2539668, 0.053789854],\n\t\t\t[0.2478888, 0.05969584],\n\t\t\t[0.23963118, 0.06382465],\n\t\t\t[0.23154557, 0.06786746],\n\t\t\t[0.22328794, 0.07199627],\n\t\t\t[0.21539211, 0.074404776],\n\t\t\t[0.20615566, 0.074404776],\n\t\t\t[0.1971116, 0.074404776],\n\t\t\t[0.18787527, 0.074404776],\n\t\t\t[0.17883122, 0.074404776],\n\t\t\t[0.34681904, 0.07034129],\n\t\t\t[0.34681904, 0.061246872],\n\t\t\t[0.34681904, 0.052152455],\n\t\t\t[0.34681904, 0.0428645],\n\t\t\t[0.34681904, 0.033770084],\n\t\t\t[0.34681904, 0.02448219],\n\t\t\t[0.34681904, 0.015387714],\n\t\t\t[0.34681904, 0.00609982],\n\t\t\t[0.34681904, -0.0029946566],\n\t\t\t[0.34681904, -0.01228261],\n\t\t\t[0.34681904, -0.021376967],\n\t\t\t[0.34681904, -0.03066492],\n\t\t\t[0.34681904, -0.03975928],\n\t\t\t[0.34681904, -0.04904723],\n\t\t\t[0.34681904, -0.05814171],\n\t\t\t[0.34681904, -0.067236066],\n\t\t\t[0.34681904, -0.07652402],\n\t\t\t[0.34681904, -0.085618496],\n\t\t\t[0.34681904, -0.09490645],\n\t\t\t[0.34681904, -0.10400081],\n\t\t\t[0.34681904, -0.11328876],\n\t\t\t[0.34681904, -0.12238312],\n\t\t\t[0.34681904, -0.13167107],\n\t\t\t[0.34681904, -0.14076555],\n\t\t\t[0.34681904, -0.1500535],\n\t\t\t[0.34681904, -0.15914786],\n\t\t\t[0.34681904, -0.16824234],\n\t\t\t[0.34681904, -0.17753029],\n\t\t\t[0.34681904, -0.18662465],\n\t\t\t[0.34681904, -0.1959126],\n\t\t\t[0.34681904, -0.20500708],\n\t\t\t[0.34681904, -0.21429491],\n\t\t\t[0.34681904, -0.22338939],\n\t\t\t[0.34681904, -0.23267734],\n\t\t\t[0.34927642, 0.03221929],\n\t\t\t[0.35582948, 0.038772404],\n\t\t\t[0.36224604, 0.045188963],\n\t\t\t[0.3687992, 0.051742017],\n\t\t\t[0.37521577, 0.058158576],\n\t\t\t[0.38243008, 0.062448382],\n\t\t\t[0.3906877, 0.066577196],\n\t\t\t[0.3987733, 0.07062],\n\t\t\t[0.40653527, 0.074404776],\n\t\t\t[0.41557932, 0.074404776],\n\t\t\t[0.42481577, 0.074404776],\n\t\t\t[0.43385983, 0.074404776],\n\t\t\t[0.44309616, 0.074404776],\n\t\t\t[0.45150173, 0.07414675],\n\t\t\t[0.45975935, 0.070017934],\n\t\t\t[0.46784496, 0.06597513],\n\t\t\t[0.4761027, 0.061846256],\n\t\t\t[0.48306847, 0.057202935],\n\t\t\t[0.48948503, 0.050786376],\n\t\t\t[0.49603808, 0.044233322],\n\t\t\t[0.50245464, 0.037816763],\n\t\t\t[0.5090077, 0.03126365],\n\t\t\t[0.5125221, 0.023724139],\n\t\t\t[0.5154495, 0.01494199],\n\t\t\t[0.5183159, 0.006342709],\n\t\t\t[0.52124333, -0.002439499],\n\t\t\t[0.5241097, -0.011038661],\n\t\t\t[0.5253905, -0.01948917],\n\t\t\t[0.5253905, -0.02851379],\n\t\t\t[0.5253905, -0.03753829],\n\t\t\t[0.5249026, -0.046106458],\n\t\t\t[0.5220362, -0.05470574],\n\t\t\t[0.5191088, -0.06348801],\n\t\t\t[0.5162424, -0.07208717],\n\t\t\t[0.51331496, -0.080869436],\n\t\t\t[0.5105095, -0.08928573],\n\t\t\t[0.5042294, -0.095565796],\n\t\t\t[0.49781287, -0.101982355],\n\t\t\t[0.4912598, -0.10853541],\n\t\t\t[0.48484325, -0.11495197],\n\t\t\t[0.47816706, -0.120337844],\n\t\t\t[0.47008145, -0.12438071],\n\t\t\t[0.46199584, -0.12842345],\n\t\t\t[0.4537382, -0.13255227],\n\t\t\t[0.44559777, -0.13392854],\n\t\t\t[0.4363613, -0.13392854],\n\t\t\t[0.42731726, -0.13392854],\n\t\t\t[0.4180808, -0.13392854],\n\t\t\t[0.40903687, -0.13392854],\n\t\t\t[0.4010098, -0.13126206],\n\t\t\t[0.3929242, -0.1272192],\n\t\t\t[0.38466656, -0.12309039],\n\t\t\t[0.37658095, -0.11904764],\n\t\t\t[0.370574, -0.113040686],\n\t\t\t[0.36402082, -0.10648751],\n\t\t\t[0.35760427, -0.10007095],\n\t\t\t[0.3510512, -0.0935179],\n\t\t\t[0.6080917, -0.014880896],\n\t\t\t[0.61737823, -0.014880896],\n\t\t\t[0.62647116, -0.014880896],\n\t\t\t[0.6357577, -0.014880896],\n\t\t\t[0.64485073, -0.014880896],\n\t\t\t[0.65413725, -0.014880896],\n\t\t\t[0.6632302, -0.014880896],\n\t\t\t[0.6725167, -0.014880896],\n\t\t\t[0.68160975, -0.014880896],\n\t\t\t[0.6907028, -0.014880896],\n\t\t\t[0.6999893, -0.014880896],\n\t\t\t[0.70908225, -0.014880896],\n\t\t\t[0.71836877, -0.014880896],\n\t\t\t[0.7274618, -0.014880896],\n\t\t\t[0.73674834, -0.014880896],\n\t\t\t[0.74584126, -0.014880896],\n\t\t\t[0.7551278, -0.014880896],\n\t\t\t[0.76422083, -0.014880896],\n\t\t\t[0.77350736, -0.014880896],\n\t\t\t[0.7826003, -0.014880896],\n\t\t\t[0.78376114, -0.0073924065],\n\t\t\t[0.78376114, 0.001632154],\n\t\t\t[0.78376114, 0.010656714],\n\t\t\t[0.78178275, 0.01883775],\n\t\t\t[0.7777399, 0.026923358],\n\t\t\t[0.77361107, 0.035180986],\n\t\t\t[0.7695683, 0.043266594],\n\t\t\t[0.76387477, 0.049648285],\n\t\t\t[0.7575165, 0.05600649],\n\t\t\t[0.75073063, 0.06115812],\n\t\t\t[0.742645, 0.065200925],\n\t\t\t[0.7343874, 0.06932974],\n\t\t\t[0.7263018, 0.07337254],\n\t\t\t[0.7180797, 0.074404776],\n\t\t\t[0.70884323, 0.074404776],\n\t\t\t[0.6997992, 0.074404776],\n\t\t\t[0.69056284, 0.074404776],\n\t\t\t[0.6815188, 0.074404776],\n\t\t\t[0.6735734, 0.071394205],\n\t\t\t[0.66548777, 0.0673514],\n\t\t\t[0.65723, 0.06322253],\n\t\t\t[0.6496961, 0.059387326],\n\t\t\t[0.64314306, 0.052834213],\n\t\t\t[0.6367265, 0.046417654],\n\t\t\t[0.6301733, 0.0398646],\n\t\t\t[0.62375677, 0.03344804],\n\t\t\t[0.61903393, 0.026651561],\n\t\t\t[0.6161065, 0.017869353],\n\t\t\t[0.6132401, 0.009270132],\n\t\t\t[0.6103127, 4.8792362E-4],\n\t\t\t[0.6074462, -0.0081112385],\n\t\t\t[0.6051897, -0.016417027],\n\t\t\t[0.6051897, -0.025441527],\n\t\t\t[0.6051897, -0.034658194],\n\t\t\t[0.6051897, -0.043682694],\n\t\t\t[0.6076292, -0.051961303],\n\t\t\t[0.61049557, -0.060560584],\n\t\t\t[0.6133621, -0.069159865],\n\t\t\t[0.6162895, -0.077942014],\n\t\t\t[0.6191559, -0.086541295],\n\t\t\t[0.62416637, -0.093381405],\n\t\t\t[0.6305829, -0.099797964],\n\t\t\t[0.637136, -0.10635102],\n\t\t\t[0.64355254, -0.11276758],\n\t\t\t[0.6498326, -0.11904764],\n\t\t\t[0.6577462, -0.12300444],\n\t\t\t[0.6660038, -0.12713325],\n\t\t\t[0.67408943, -0.131176],\n\t\t\t[0.682096, -0.13392854],\n\t\t\t[0.69114006, -0.13392854],\n\t\t\t[0.7001841, -0.13392854],\n\t\t\t[0.70942056, -0.13392854],\n\t\t\t[0.7184645, -0.13392854],\n\t\t\t[0.72681785, -0.13263834],\n\t\t\t[0.73490345, -0.12859547],\n\t\t\t[0.7431611, -0.12446666],\n\t\t\t[0.7512467, -0.12042391],\n\t\t\t[0.7579584, -0.11508846],\n\t\t\t[0.764375, -0.1086719],\n\t\t\t[0.770928, -0.10211885],\n\t\t\t[0.7773446, -0.09570229]\n\t\t]\n\t</script>\n</body>\n</html>"
  }
]